Skip to content
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

Open
wants to merge 20 commits into
base: fe-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1d98cba
fix: Icon 컴포넌트 색상 변경 기능 에러 수정
soi-ha Dec 16, 2024
491e591
feat: Checkbox 컴포넌트 구현
soi-ha Dec 16, 2024
03f9a9b
feat: TextArea 컴포넌트 구현
soi-ha Dec 17, 2024
2befc2e
feat: 회원탈퇴 url을 마이페이지 버튼에 연결하기
soi-ha Dec 17, 2024
4164f71
chore: textarea, checkbox 컴포넌트 Design의 index.tsx에 추가
soi-ha Dec 17, 2024
c14d0ba
feat: 탈퇴하기 페이지 funnel 생성
soi-ha Dec 17, 2024
72a9b54
feat: 탈퇴 이유 step 생성
soi-ha Dec 17, 2024
2d1866b
feat: 회원탈퇴/ 서비스를 사용하지 않는 이유 Step 구현
soi-ha Dec 17, 2024
8fbe783
feat: textarea의 height를 조절할 수 있도록 기능 추가
soi-ha Dec 17, 2024
c96202d
feat: 탈퇴이유 기타 페이지 구현
soi-ha Dec 17, 2024
87b0d5f
fix: StandingDogLogo에 누락된 style 적용으로 사이즈 작게 변경
soi-ha Dec 17, 2024
70aa691
feat: 탈퇴전 확인 페이지 구현
soi-ha Dec 17, 2024
e15a3e8
fix: 탈퇴하기 버튼 클릭시 회원 탈퇴 완료 페이지로 이동
soi-ha Dec 17, 2024
38e4a0b
feat: 회원 탈퇴 완료 페이지 구현
soi-ha Dec 17, 2024
9de88ef
feat: 탈퇴 이유 선택시 오류 발생 페이지 구현
soi-ha Dec 17, 2024
996b977
chore: 기능 추가할 부분 주석 남기기
soi-ha Dec 17, 2024
ed84d4e
feat: MEMBER_API_PREFIX (/api/users) 추가 및 반영
soi-ha Dec 17, 2024
3cbb44c
feat: 회원 탈퇴 api requestDeleteUser 추가
soi-ha Dec 17, 2024
e492ba7
feat: 회원탈퇴 api 연결
soi-ha Dec 17, 2024
342ddb9
chore: 사용하지 않는 주석 제거
soi-ha Dec 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/src/apis/endpointPrefix.ts
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';
Comment on lines 2 to +4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요 저도 이 부분 헷갈리는데 소하가 논의하고 싶은 점에 적어준 것처럼 바꿔주는 것이 더 좋을 것 같아요!

5 changes: 3 additions & 2 deletions client/src/apis/request/event.ts
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';

Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

소하가 user.ts 의 파일을 만들고, user 관련 엔티티들을 여기에 모으는게 확실히 좋아 보이기는 해요!
제가 남겼던 #854 PR의 논의하기에서 디렉토리 관련 논의를 할 때 함께 논의해봐요! 프엔회의 합시다!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오~ 이슈도 만들어두셨군용~!
그리고 이 부분은 다른 프엔분들 작업과도 겹쳐서 꼭 논의해봐야 할 것 같아요!

Expand Down
10 changes: 10 additions & 0 deletions client/src/apis/request/user.ts
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}`,
});
};
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;
Copy link
Contributor

Choose a reason for hiding this comment

The 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(),
);
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(
Copy link
Contributor

Choose a reason for hiding this comment

The 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;
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',
Copy link
Contributor

Choose a reason for hiding this comment

The 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,
Expand Down
Loading
Loading