Skip to content

Commit

Permalink
feat: 회원탈퇴 페이지 퍼블리싱 및 api 연결 (#858)
Browse files Browse the repository at this point in the history
* fix: Icon 컴포넌트 색상 변경 기능 에러 수정

* feat: Checkbox 컴포넌트 구현

* feat: TextArea 컴포넌트 구현

* feat: 회원탈퇴 url을 마이페이지 버튼에 연결하기

* chore: textarea, checkbox 컴포넌트 Design의 index.tsx에 추가

* feat: 탈퇴하기 페이지 funnel 생성

* feat: 탈퇴 이유 step 생성

* feat: 회원탈퇴/ 서비스를 사용하지 않는 이유 Step 구현

* feat: textarea의 height를 조절할 수 있도록 기능 추가

* feat: 탈퇴이유 기타 페이지 구현

* fix: StandingDogLogo에 누락된 style 적용으로 사이즈 작게 변경

* feat: 탈퇴전 확인 페이지 구현

* fix: 탈퇴하기 버튼 클릭시 회원 탈퇴 완료 페이지로 이동

* feat: 회원 탈퇴 완료 페이지 구현

* feat: 탈퇴 이유 선택시 오류 발생 페이지 구현

* chore: 기능 추가할 부분 주석 남기기

* feat: MEMBER_API_PREFIX (/api/users) 추가 및 반영

* feat: 회원 탈퇴 api requestDeleteUser 추가

* feat: 회원탈퇴 api 연결

* chore: 사용하지 않는 주석 제거

---------

Co-authored-by: Pakxe <[email protected]>
  • Loading branch information
soi-ha and pakxe authored Dec 18, 2024
1 parent 745b211 commit 9f257ee
Show file tree
Hide file tree
Showing 26 changed files with 671 additions and 10 deletions.
1 change: 1 addition & 0 deletions client/src/apis/request/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ 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: MEMBER_API_PREFIX,
Expand Down
9 changes: 9 additions & 0 deletions client/src/apis/request/user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import {User} from 'types/serviceType';

import {BASE_URL} from '@apis/baseUrl';
import {MEMBER_API_PREFIX} from '@apis/endpointPrefix';
import {requestDelete} from '@apis/fetcher';
import {requestGet} from '@apis/fetcher';

export const requestDeleteUser = async () => {
await requestDelete({
baseUrl: BASE_URL.HD,
endpoint: `${MEMBER_API_PREFIX}`,
});
};

export const requestGetUserInfo = async () => {
return await requestGet<User>({
baseUrl: BASE_URL.HD,
Expand Down
4 changes: 2 additions & 2 deletions client/src/assets/image/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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} />;
},
};
38 changes: 38 additions & 0 deletions client/src/components/Design/components/Checkbox/Checkbox.style.ts
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,
},
});
28 changes: 28 additions & 0 deletions client/src/components/Design/components/Checkbox/Checkbox.tsx
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;
6 changes: 5 additions & 1 deletion client/src/components/Design/components/Icon/Icon.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ export const iconStyle = ({iconType, theme, iconColor}: IconStylePropsWithTheme)
const getIconColor = ({iconType, theme, iconColor}: IconStylePropsWithTheme) => {
if (iconColor) {
return css({
color: theme.colors[iconColor as ColorKeys],
svg: {
path: {
stroke: theme.colors[iconColor as ColorKeys],
},
},
});
} else {
return css({color: theme.colors[ICON_DEFAULT_COLOR[iconType]]});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/** @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: {
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}
/>
);
},
};
32 changes: 32 additions & 0 deletions client/src/components/Design/components/Textarea/Textarea.style.ts
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;
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(),
);
40 changes: 40 additions & 0 deletions client/src/components/Design/components/Textarea/Textarea.tsx
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(
{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;
13 changes: 13 additions & 0 deletions client/src/components/Design/components/Textarea/Textarea.type.ts
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;
4 changes: 4 additions & 0 deletions client/src/components/Design/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import DepositToggle from './components/DepositToggle/DepositToggle';
import Amount from './components/Amount/Amount';
import Dropdown from './components/Dropdown/Dropdown';
import DropdownButton from './components/Dropdown/DropdownButton';
import Checkbox from './components/Checkbox/Checkbox';
import Textarea from './components/Textarea/Textarea';
import {Select} from './components/Select';

export {
Expand Down Expand Up @@ -55,4 +57,6 @@ export {
DropdownButton,
useTheme,
Select,
Checkbox,
Textarea,
};
8 changes: 6 additions & 2 deletions client/src/components/Logo/StandingDogLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import Image from '@components/Design/components/Image/Image';

import getImageUrl from '@utils/getImageUrl';

import {logoStyle} from './Logo.style';
import {logoStyle, logoImageStyle} from './Logo.style';

const StandingDogLogo = () => {
return (
<div css={logoStyle}>
<Image src={getImageUrl('standingDog', 'webp')} fallbackSrc={getImageUrl('standingDog', 'png')} />
<Image
src={getImageUrl('standingDog', 'webp')}
fallbackSrc={getImageUrl('standingDog', 'png')}
css={logoImageStyle}
/>
</div>
);
};
Expand Down
1 change: 1 addition & 0 deletions client/src/constants/routerUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ROUTER_URLS = {
event: EVENT,
login: '/login',
myPage: '/mypage',
withdraw: '/mypage/withdraw',
createdEvents: '/mypage/events',
guestEventLogin: `${EVENT_WITH_EVENT_ID}/admin/guest/login`,
memberEventLogin: `${EVENT_WITH_EVENT_ID}/admin/member/login`,
Expand Down
21 changes: 21 additions & 0 deletions client/src/hooks/queries/user/useRequestDeleteUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {useMutation, useQueryClient} from '@tanstack/react-query';

import {requestDeleteUser} from '@apis/request/user';

import QUERY_KEYS from '@constants/queryKeys';

const useRequestDeleteUser = () => {
const queryClient = useQueryClient();

const {mutateAsync, ...rest} = useMutation({
mutationFn: () => requestDeleteUser(),
onSuccess: () => {
queryClient.invalidateQueries({queryKey: [QUERY_KEYS.kakaoLogin]});
queryClient.invalidateQueries({queryKey: [QUERY_KEYS.kakaoClientId]});
},
});

return {deleteAsyncUser: mutateAsync, ...rest};
};

export default useRequestDeleteUser;
Loading

0 comments on commit 9f257ee

Please sign in to comment.