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

개인정보(성별/나이) 등록 페이지 구현, 회원탈퇴 및 닉네임 변경 API 함수에 UI와 연결 #359

Merged
merged 15 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
4 changes: 2 additions & 2 deletions frontend/__test__/api/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ describe('서버와 통신하여 유저의 정보를 불러올 수 있어야 한
expect(data).toEqual(transformUserInfoResponse(MOCK_USER_INFO));
});

test('클라이언트에서 사용하는 유저 정보 API 명세가 [nickname, postCount, voteCount, gender, birthYear]으로 존재해야한다', async () => {
test('클라이언트에서 사용하는 유저 정보 API 명세가 [nickname, gender, birthYear, postCount, voteCount]으로 존재해야한다', async () => {
const data = await getUserInfo(isLoggedIn);

const userInfoKeys = Object.keys(data ?? {});

expect(userInfoKeys).toEqual(['nickname', 'postCount', 'gender', 'voteCount', 'birthYear']);
expect(userInfoKeys).toEqual(['nickname', 'gender', 'birthYear', 'postCount', 'voteCount']);
});

test('유저의 닉네임을 수정한다', async () => {
Expand Down
17 changes: 13 additions & 4 deletions frontend/src/api/userInfo.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import type { UserInfoResponse, User, ModifyNicknameRequest } from '@type/user';
import type {
UserInfoResponse,
User,
ModifyNicknameRequest,
UpdateUserInfoRequest,
} from '@type/user';

import { deleteFetch, getFetch, patchFetch } from '@utils/fetch';

export const transformUserInfoResponse = (userInfo: UserInfoResponse): User => {
const { nickname, postCount, gender, voteCount, birthYear } = userInfo;
const { nickname, gender, birthYear, postCount, voteCount } = userInfo;

return {
nickname,
postCount,
gender,
voteCount,
birthYear,
postCount,
voteCount,
};
};

Expand All @@ -31,3 +36,7 @@ export const modifyNickname = async (nickname: string) => {
export const withdrawalMembership = async () => {
await deleteFetch(`${BASE_URL}/members/me/delete`);
};

export const updateUserInfo = async (userInfo: UpdateUserInfoRequest) => {
await patchFetch<UpdateUserInfoRequest>(`${BASE_URL}/members/me/detail`, userInfo);
};
16 changes: 7 additions & 9 deletions frontend/src/components/PostForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import SquareButton from '@components/common/SquareButton';
import TimePickerOptionList from '@components/common/TimePickerOptionList';
import WritingVoteOptionList from '@components/optionList/WritingVoteOptionList';

import { POST_DESCRIPTION_MAX_LENGTH, POST_TITLE_MAX_LENGTH } from '@constants/post';
import { CATEGORY_COUNT_LIMIT, POST_CONTENT, POST_TITLE } from '@constants/post';

import { changeCategoryToOption } from '@utils/post/changeCategoryToOption';
import { addTimeToDate, formatTimeWithOption } from '@utils/post/formatTime';
Expand All @@ -35,10 +35,6 @@ interface PostFormProps extends HTMLAttributes<HTMLFormElement> {
mutate: UseMutateFunction<any, unknown, FormData, unknown>;
}

const MAX_TITLE_LENGTH = 100;
const MAX_CONTENT_LENGTH = 1000;
const CATEGORY_COUNT_LIMIT = 3;

export default function PostForm({ data, mutate }: PostFormProps) {
const {
title,
Expand Down Expand Up @@ -163,19 +159,21 @@ export default function PostForm({ data, mutate }: PostFormProps) {
<S.Title
value={writingTitle}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleTitleChange(e, POST_TITLE_MAX_LENGTH)
handleTitleChange(e, POST_TITLE)
}
placeholder="제목을 입력해주세요"
maxLength={MAX_TITLE_LENGTH}
maxLength={POST_TITLE.MAX_LENGTH}
minLength={POST_TITLE.MIN_LENGTH}
inyeong-kang marked this conversation as resolved.
Show resolved Hide resolved
required
/>
<S.Content
value={writingContent}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
handleContentChange(e, POST_DESCRIPTION_MAX_LENGTH)
handleContentChange(e, POST_CONTENT)
}
placeholder="내용을 입력해주세요"
maxLength={MAX_CONTENT_LENGTH}
maxLength={POST_CONTENT.MAX_LENGTH}
minLength={POST_CONTENT.MIN_LENGTH}
required
/>
<S.ContentImagePartWrapper $hasImage={!!contentImageHook.contentImage}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useText } from '@hooks/useText';

import SquareButton from '@components/common/SquareButton';

import { COMMENT_MAX_LENGTH } from '@constants/comment';
import { COMMENT } from '@constants/comment';

import * as S from './style';
interface CommentTextFormProps {
Expand Down Expand Up @@ -47,7 +47,7 @@ export default function CommentTextForm({
<S.Container>
<S.TextArea
value={content}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => handleTextChange(e, COMMENT_MAX_LENGTH)}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => handleTextChange(e, COMMENT)}
/>
<S.ButtonContainer>
{handleCancelClick && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ type Story = StoryObj<typeof Dashboard>;

const MOCK_USER_INFO: User = {
nickname: '우아한 코끼리',
gender: 'MALE',
birthYear: 1989,
postCount: 4,
voteCount: 128,
gender: 'MALE',
birthYear: 1997,
};

const MOCK_CATEGORIES: Category[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ type Story = StoryObj<typeof UserProfile>;

const MOCK_USER_INFO: User = {
nickname: '우아한 코끼리',
gender: 'MALE',
birthYear: 1989,
postCount: 4,
voteCount: 128,
gender: 'FEMALE',
birthYear: 1997,
};

export const NoBadge: Story = {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/common/Drawer/Drawer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ export default meta;

const MOCK_USER_INFO: User = {
nickname: '우아한 코끼리',
gender: 'MALE',
birthYear: 1989,
postCount: 4,
voteCount: 128,
gender: 'MALE',
birthYear: 1997,
};

const MOCK_CATEGORIES: Category[] = [
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/constants/comment.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export const COMMENT_MAX_LENGTH = 200;
export const COMMENT = {
MAX_LENGTH: 200,
MIN_LENGTH: 1,
} as const;
1 change: 1 addition & 0 deletions frontend/src/constants/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export const PATH = {
USER_POST: `${BASE_PATH.USER}/posts`,
USER_VOTE: `${BASE_PATH.USER}/votes`,
USER_INFO: `${BASE_PATH.USER}/myPage`,
USER_INFO_REGISTER: `${BASE_PATH.USER}/register`,
};
12 changes: 11 additions & 1 deletion frontend/src/constants/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ export const SEARCH_KEYWORD = 'keyword';

export const MAX_FILE_SIZE = 5000000;

export const POST_TITLE_MAX_LENGTH = 100;
export const POST_TITLE = {
MAX_LENGTH: 100,
MIN_LENGTH: 2,
} as const;

export const POST_CONTENT = {
MAX_LENGTH: 1000,
MIN_LENGTH: 2,
} as const;

export const POST_DESCRIPTION_MAX_LENGTH = 1000;

Expand All @@ -55,3 +63,5 @@ export const POST_LIST_MAX_LENGTH = 10;
export const DEFAULT_CATEGORY_ID = 0;

export const DEFAULT_KEYWORD = '';

export const CATEGORY_COUNT_LIMIT = 3;
11 changes: 11 additions & 0 deletions frontend/src/constants/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { UserInfoLength } from '@type/user';

export const NICKNAME: UserInfoLength = {
MAX_LENGTH: 15,
MIN_LENGTH: 2,
} as const;

export const BIRTH_YEAR: UserInfoLength = {
MAX_LENGTH: new Date().getFullYear(),
inyeong-kang marked this conversation as resolved.
Show resolved Hide resolved
MIN_LENGTH: 1900,
inyeong-kang marked this conversation as resolved.
Show resolved Hide resolved
} as const;
9 changes: 6 additions & 3 deletions frontend/src/hooks/useText.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import React, { useState } from 'react';

export type InputLength = Record<'MAX_LENGTH' | 'MIN_LENGTH', number>;

export const useText = (originalText: string) => {
const [text, setText] = useState(originalText);

const handleTextChange = (
event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
limit: number
limit: InputLength
) => {
const { value } = event.target;
const standard = value.length;

if (standard === limit) {
event.target.setCustomValidity(`선택지 내용은 ${limit}자까지 입력 가능합니다.`);
if (standard > limit.MAX_LENGTH) {
event.target.setCustomValidity(`해당 입력값은 ${limit.MAX_LENGTH}자까지 입력 가능합니다.`);
event.target.reportValidity();
return;
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/mocks/mockData/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { UserInfoResponse } from '@type/user';

export const MOCK_USER_INFO: UserInfoResponse = {
nickname: '우아한 코끼리',
gender: 'MALE',
birthYear: 1989,
postCount: 4,
voteCount: 128,
gender: 'FEMALE',
birthYear: 1997,
};
4 changes: 4 additions & 0 deletions frontend/src/mocks/userInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export const mockUserInfo = [
return res(ctx.status(200), ctx.json(MOCK_USER_INFO));
}),

rest.patch('/members/me/detail', (req, res, ctx) => {
return res(ctx.status(200), ctx.json({ ok: '개인 정보가 성공적으로 저장되었습니다!' }));
}),
inyeong-kang marked this conversation as resolved.
Show resolved Hide resolved

rest.patch('/members/me/nickname', (req, res, ctx) => {
MOCK_USER_INFO.nickname = 'wood';

Expand Down
33 changes: 29 additions & 4 deletions frontend/src/pages/MyInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useContext } from 'react';
import { useContext, ChangeEvent } from 'react';
import { useNavigate } from 'react-router-dom';

import { AuthContext } from '@hooks/context/auth';
import { useText } from '@hooks/useText';
import { useToggle } from '@hooks/useToggle';

import { modifyNickname } from '@api/userInfo';

import Accordion from '@components/common/Accordion';
import UserProfile from '@components/common/Dashboard/UserProfile';
import IconButton from '@components/common/IconButton';
Expand All @@ -12,6 +15,8 @@ import Modal from '@components/common/Modal';
import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader';
import SquareButton from '@components/common/SquareButton';

import { NICKNAME } from '@constants/user';

import * as S from './style';

export default function MyInfo() {
Expand All @@ -20,11 +25,23 @@ export default function MyInfo() {

const { userInfo } = useContext(AuthContext).loggedInfo;

const { text: newNickname, handleTextChange: handleNicknameChange } = useText(
userInfo?.nickname ?? ''
);

if (!userInfo) {
navigate('/');
return <></>;
}

const handleModifyNickname = () => {
modifyNickname(newNickname);
Copy link
Collaborator

Choose a reason for hiding this comment

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

tanstack query에서 유저 정보를 초기화 해줘야 왼쪽 유저 정보창에서 올바르게 반영이 될 것 같습니다

Copy link
Member Author

Choose a reason for hiding this comment

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

앗 예리하시네요!! 감사합니다~~ 👍👍

};

const handleWithdrawlMembership = () => {
handleWithdrawlMembership();
Copy link
Collaborator

Choose a reason for hiding this comment

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

로그아웃도 같이 진행되어야 자연스러울 것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

피드백 반영 완~!

};

return (
<Layout isSidebarVisible={true}>
<S.Wrapper>
Expand All @@ -43,9 +60,13 @@ export default function MyInfo() {
</S.ProfileSection>
<S.UserControlSection>
<Accordion title="닉네임 변경">
<S.Input placeholder="새로운 닉네임을 입력해주세요" />
<S.Input
value={newNickname}
onChange={(e: ChangeEvent<HTMLInputElement>) => handleNicknameChange(e, NICKNAME)}
placeholder="새로운 닉네임을 입력해주세요"
/>
<S.ButtonWrapper>
<SquareButton aria-label="닉네임 변경" theme="fill">
<SquareButton aria-label="닉네임 변경" theme="fill" onClick={handleModifyNickname}>
변경
</SquareButton>
</S.ButtonWrapper>
Expand All @@ -64,7 +85,11 @@ export default function MyInfo() {
탈퇴 버튼 클릭 시, <br></br>계정은 삭제되며 복구되지 않아요.
</S.ModalDescription>
<S.ButtonListWrapper>
<SquareButton aria-label="회원 탈퇴" theme="fill">
<SquareButton
onClick={handleWithdrawlMembership}
aria-label="회원 탈퇴"
theme="fill"
>
탈퇴
</SquareButton>
<SquareButton onClick={closeComponent} aria-label="회원 탈퇴" theme="blank">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Meta, StoryObj } from '@storybook/react';

import RegisterPersonalInfo from '.';

const meta: Meta<typeof RegisterPersonalInfo> = {
component: RegisterPersonalInfo,
};

export default meta;
type Story = StoryObj<typeof RegisterPersonalInfo>;

export const Default: Story = {
render: () => <RegisterPersonalInfo />,
};
Loading