-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FE] 회원탈퇴 페이지 퍼블리싱 및 api 연결 #858
base: fe-dev
Are you sure you want to change the base?
Changes from 19 commits
1d98cba
491e591
03f9a9b
2befc2e
4164f71
c14d0ba
72a9b54
2d1866b
8fbe783
c96202d
87b0d5f
70aa691
e15a3e8
38e4a0b
9de88ef
996b977
ed84d4e
3cbb44c
e492ba7
342ddb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
// TODO: (@weadie) 반복되서 쓰이는 이 api/events가 추후 수정 가능성이 있어서 일단 편집하기 편하게 이 변수를 재사용하도록 했습니다. | ||
export const USER_API_PREFIX = '/api/events'; | ||
export const ADMIN_API_PREFIX = '/api/admin/events'; | ||
export const MEMBER_API_PREFIX = '/api/users'; | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import {Event, EventCreationData, EventId, EventName, User} from 'types/serviceType'; | ||
import {WithErrorHandlingStrategy} from '@errors/RequestGetError'; | ||
|
||
import {ADMIN_API_PREFIX, USER_API_PREFIX} from '@apis/endpointPrefix'; | ||
import {ADMIN_API_PREFIX, USER_API_PREFIX, MEMBER_API_PREFIX} from '@apis/endpointPrefix'; | ||
import {requestGet, requestPatch, requestPostWithResponse} from '@apis/fetcher'; | ||
import {WithEventId} from '@apis/withId.type'; | ||
|
||
|
@@ -45,9 +45,10 @@ export const requestPatchEventName = async ({eventId, eventName}: RequestPatchEv | |
|
||
export type RequestPatchUser = Partial<User>; | ||
|
||
// TODO: (@soha) 해당 요청은 user.ts 파일로 이동하는 건 어떨지? | ||
export const requestPatchUser = async (args: RequestPatchUser) => { | ||
return requestPatch({ | ||
endpoint: `/api/users`, | ||
endpoint: MEMBER_API_PREFIX, | ||
body: { | ||
...args, | ||
}, | ||
Comment on lines
+48
to
54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 소하가 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오~ 이슈도 만들어두셨군용~! |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import {BASE_URL} from '@apis/baseUrl'; | ||
import {MEMBER_API_PREFIX} from '@apis/endpointPrefix'; | ||
import {requestDelete} from '@apis/fetcher'; | ||
|
||
export const requestDeleteUser = async () => { | ||
await requestDelete({ | ||
baseUrl: BASE_URL.HD, | ||
endpoint: `${MEMBER_API_PREFIX}`, | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
import type {Meta, StoryObj} from '@storybook/react'; | ||
|
||
import {useEffect, useState} from 'react'; | ||
|
||
import Checkbox from './Checkbox'; | ||
|
||
const meta = { | ||
title: 'Components/Checkbox', | ||
component: Checkbox, | ||
tags: ['autodocs'], | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
argTypes: { | ||
labelText: { | ||
description: '', | ||
control: {type: 'text'}, | ||
}, | ||
isChecked: { | ||
description: '', | ||
control: {type: 'boolean'}, | ||
}, | ||
onChange: { | ||
description: '', | ||
control: {type: 'object'}, | ||
}, | ||
}, | ||
} satisfies Meta<typeof Checkbox>; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Playground: Story = { | ||
args: { | ||
isChecked: false, | ||
onChange: () => {}, | ||
labelText: '체크박스', | ||
}, | ||
render: ({isChecked, onChange, labelText, ...args}) => { | ||
const [isCheckedState, setIsCheckedState] = useState(isChecked); | ||
const [labelTextState, setLabelTextState] = useState(labelText); | ||
|
||
useEffect(() => { | ||
setIsCheckedState(isChecked); | ||
setLabelTextState(labelText); | ||
}, [isChecked, labelText]); | ||
|
||
const handleToggle = () => { | ||
setIsCheckedState(!isCheckedState); | ||
onChange(); | ||
}; | ||
|
||
return <Checkbox {...args} isChecked={isCheckedState} onChange={handleToggle} labelText={labelTextState} />; | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import {css} from '@emotion/react'; | ||
|
||
import {WithTheme} from '@components/Design/type/withTheme'; | ||
|
||
interface CheckboxStyleProps { | ||
isChecked: boolean; | ||
} | ||
|
||
export const checkboxStyle = () => | ||
css({ | ||
display: 'flex', | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
gap: '0.75rem', | ||
cursor: 'pointer', | ||
}); | ||
|
||
export const inputGroupStyle = ({theme, isChecked}: WithTheme<CheckboxStyleProps>) => | ||
css({ | ||
position: 'relative', | ||
display: 'flex', | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
|
||
'.check-icon': { | ||
position: 'absolute', | ||
}, | ||
|
||
'.checkbox-input': { | ||
width: '1.375rem', | ||
height: '1.375rem', | ||
border: '1px solid', | ||
borderRadius: '0.5rem', | ||
borderColor: isChecked ? theme.colors.primary : theme.colors.tertiary, | ||
backgroundColor: isChecked ? theme.colors.primary : theme.colors.white, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
import {useTheme} from '@components/Design/theme/HDesignProvider'; | ||
|
||
import Text from '../Text/Text'; | ||
import Icon from '../Icon/Icon'; | ||
|
||
import {checkboxStyle, inputGroupStyle} from './Checkbox.style'; | ||
|
||
interface Props { | ||
labelText: string; | ||
isChecked: boolean; | ||
onChange: () => void; | ||
} | ||
|
||
const Checkbox = ({labelText, isChecked = false, onChange}: Props) => { | ||
const {theme} = useTheme(); | ||
return ( | ||
<label css={checkboxStyle}> | ||
<div css={inputGroupStyle({theme, isChecked})}> | ||
{isChecked ? <Icon iconType="check" className="check-icon" /> : null} | ||
<input type="checkbox" checked={isChecked} onChange={onChange} className="checkbox-input" /> | ||
</div> | ||
<Text size="bodyBold">{labelText}</Text> | ||
</label> | ||
); | ||
}; | ||
|
||
export default Checkbox; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
import type {Meta, StoryObj} from '@storybook/react'; | ||
|
||
import {useEffect, useState} from 'react'; | ||
|
||
import Textarea from './Textarea'; | ||
|
||
const meta = { | ||
title: 'Components/Textarea', | ||
component: Textarea, | ||
tags: ['autodocs'], | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
argTypes: { | ||
// isFocus: { | ||
// description: '', | ||
// control: {type: 'boolean'}, | ||
// }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용하지 않는 주석 있습니다~! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 으알멐ㄹ 감사합니댱 |
||
placeholder: { | ||
description: '', | ||
control: {type: 'text'}, | ||
}, | ||
maxLength: { | ||
description: '', | ||
control: {type: 'number'}, | ||
}, | ||
|
||
value: { | ||
description: '', | ||
control: {type: 'text'}, | ||
}, | ||
}, | ||
} satisfies Meta<typeof Textarea>; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Playground: Story = { | ||
args: { | ||
placeholder: '내용을 입력해주세요.', | ||
maxLength: 100, | ||
value: '', | ||
}, | ||
render: ({placeholder, value, maxLength, ...args}) => { | ||
const [placeholderState, setPlaceholderState] = useState(placeholder); | ||
const [maxLengthState, setMaxLengthState] = useState(maxLength); | ||
const [valueState, setValueState] = useState(value); | ||
|
||
useEffect(() => { | ||
setPlaceholderState(placeholder); | ||
setMaxLengthState(maxLength); | ||
setValueState(value); | ||
}, [maxLength, placeholder, value]); | ||
|
||
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
setValueState(event.target.value); | ||
}; | ||
|
||
return ( | ||
<Textarea | ||
{...args} | ||
value={valueState} | ||
onChange={handleChange} | ||
placeholder={placeholderState} | ||
maxLength={maxLengthState} | ||
/> | ||
); | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import {css} from '@emotion/react'; | ||
|
||
import {WithTheme} from '@components/Design/type/withTheme'; | ||
|
||
import {inputBoxAnimationStyle} from '../Input/Input.style'; | ||
|
||
interface Props { | ||
height?: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. height를 효과적으로 건네받을 수 있는 좋은 방법을 생각해 봐야 할 것 같아요!! |
||
isFocus: boolean; | ||
} | ||
|
||
export const textareaStyle = ({theme, isFocus, height}: WithTheme<Props>) => | ||
css( | ||
{ | ||
backgroundColor: theme.colors.lightGrayContainer, | ||
border: '1px solid', | ||
borderColor: isFocus ? theme.colors.primary : theme.colors.lightGrayContainer, | ||
borderRadius: '1rem', | ||
padding: '12px', | ||
overflowWrap: 'break-word', | ||
whiteSpace: 'pre-wrap', | ||
color: theme.colors.onTertiary, | ||
|
||
width: '100%', | ||
height: height ? height : '100%', | ||
'::placeholder': { | ||
color: theme.colors.gray, | ||
}, | ||
}, | ||
theme.typography.body, | ||
inputBoxAnimationStyle(), | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react'; | ||
|
||
import {useTheme} from '@components/Design/theme/HDesignProvider'; | ||
|
||
import {textareaStyle} from './Textarea.style'; | ||
import {TextareaProps} from './Textarea.type'; | ||
|
||
const Textarea: React.FC<TextareaProps> = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textrea( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 코드에 대한 피드백은 아니지만 react 19 버전에선 forwardRef가 사라지고 직접 ref prop을 넘겨줄 수 있다고 하더라구요! |
||
{placeholder, maxLength, onChange, value, height, ...htmlProps}: TextareaProps, | ||
ref, | ||
) { | ||
const {theme} = useTheme(); | ||
const inputRef = useRef<HTMLTextAreaElement>(null); | ||
const [isFocus, setIsFocus] = useState(false); | ||
|
||
useImperativeHandle(ref, () => inputRef.current!); | ||
|
||
useEffect(() => { | ||
inputRef.current?.addEventListener('focus', () => setIsFocus(true)); | ||
inputRef.current?.addEventListener('blur', () => setIsFocus(false)); | ||
return () => { | ||
inputRef.current?.removeEventListener('focus', () => setIsFocus(true)); | ||
inputRef.current?.removeEventListener('blur', () => setIsFocus(false)); | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<textarea | ||
maxLength={maxLength} | ||
placeholder={placeholder} | ||
onChange={onChange} | ||
value={value} | ||
ref={inputRef} | ||
css={textareaStyle({theme, isFocus, height})} | ||
{...htmlProps} | ||
></textarea> | ||
); | ||
}); | ||
export default Textarea; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export interface TextareaStyleProps { | ||
height?: string; | ||
} | ||
|
||
export interface TextareaCustomProps { | ||
value: string; | ||
maxLength?: number; | ||
placeholder?: string; | ||
} | ||
|
||
export type TextareaOptionProps = TextareaStyleProps & TextareaCustomProps; | ||
|
||
export type TextareaProps = React.ComponentProps<'textarea'> & TextareaOptionProps; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ export const ROUTER_URLS = { | |
event: EVENT, | ||
login: '/login', | ||
myPage: '/mypage', | ||
withdraw: '/mypage/withdraw', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 동의합니다. 탈퇴 페이지는 마이페이지/탈퇴가 더 어울리는 것 같아요 |
||
guestEventLogin: `${EVENT_WITH_EVENT_ID}/admin/guest/login`, | ||
memberEventLogin: `${EVENT_WITH_EVENT_ID}/admin/member/login`, | ||
kakaoLoginRedirectUri: process.env.KAKAO_REDIRECT_URI, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
맞아요 저도 이 부분 헷갈리는데 소하가 논의하고 싶은 점에 적어준 것처럼 바꿔주는 것이 더 좋을 것 같아요!