Skip to content

Commit d025835

Browse files
committed
[feat] 마이페이지 프로필 수정 동기화
1 parent 3ae278d commit d025835

File tree

11 files changed

+136
-72
lines changed

11 files changed

+136
-72
lines changed

package-lock.json

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
]
1919
},
2020
"dependencies": {
21+
"@tanstack/react-query": "^5.90.2",
2122
"@tanstack/react-virtual": "^3.13.12",
2223
"class-variance-authority": "^0.7.1",
2324
"gsap": "^3.13.0",

src/app/layout.tsx

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import FooterWrapper from '@/shared/components/footer/FooterWrapper';
66
import ScrollTopBtnWrapper from '@/shared/components/scroll-top/ScrollTopBtnWrapper';
77
import KaKaoScript from './api/kakao/KaKaoScript';
88
import IdleHandler from '@/domains/login/components/IdleHandler';
9+
import Provider from '@/shared/api/Provider';
10+
911

1012
export const metadata: Metadata = {
1113
title: { default: 'SSOUL', template: 'SSOUL | %s' },
@@ -21,24 +23,26 @@ export default function RootLayout({
2123
return (
2224
<html lang="ko-KR">
2325
<body className="relative flex flex-col min-h-screen">
24-
<Header />
25-
<IdleHandler />
26-
<main className="flex flex-1 pt-[2.75rem] md:pt-[3.75rem]">{children}</main>
27-
<FooterWrapper />
26+
<Provider>
27+
<Header />
28+
<IdleHandler />
29+
<main className="flex flex-1 pt-[2.75rem] md:pt-[3.75rem]">{children}</main>
30+
<FooterWrapper />
2831

29-
<div id="modal-root"></div>
30-
<Toaster
31-
position="top-center"
32-
toastOptions={{
33-
duration: 2000,
34-
style: {
35-
minWidth: '340px',
36-
background: 'transparent',
37-
},
38-
}}
39-
/>
32+
<div id="modal-root"></div>
33+
<Toaster
34+
position="top-center"
35+
toastOptions={{
36+
duration: 2000,
37+
style: {
38+
minWidth: '340px',
39+
background: 'transparent',
40+
},
41+
}}
42+
/>
4043

41-
<ScrollTopBtnWrapper />
44+
<ScrollTopBtnWrapper />
45+
</Provider>
4246
</body>
4347
<KaKaoScript />
4448
</html>

src/app/mypage/layout.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import { Suspense } from 'react';
55

66
function Layout({ children }: { children: React.ReactNode }) {
77
return (
8-
<Suspense fallback={<SkeletonLayout />}>
9-
<div className="max-w-1024 page-layout py-12">
10-
<MyProfile />
11-
<MyNav />
12-
<div className="mt-5">{children}</div>
13-
</div>
14-
</Suspense>
8+
9+
<Suspense fallback={<SkeletonLayout />}>
10+
<div className="max-w-1024 page-layout py-12">
11+
<MyProfile />
12+
<MyNav />
13+
<div className="mt-5">{children}</div>
14+
</div>
15+
</Suspense>
1516
);
1617
}
1718
export default Layout;

src/app/mypage/my-setting/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import MySetting from '@/domains/mypage/main/MySetting';
22

33
import { Metadata } from 'next';
44
export const metadata: Metadata = {
5-
title: 'SSOUL | 마이페이지',
5+
title: '마이페이지',
66
description: 'SSOUL 서비스에서 나의 활동을 관리할 수 있는 페이지입니다',
77
};
88

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,47 @@
11
'use client';
22
import { getApi } from '@/app/api/config/appConfig';
3-
import { useEffect, useState } from 'react';
4-
5-
interface Profile {
6-
abvDegree: number;
7-
abvLabel: string;
8-
abvLevel: number;
9-
email?: string;
10-
id: number;
11-
myCommentCount: number;
12-
myKeepCount: number;
13-
myLikedPostCount: number;
14-
myPostCount: number;
15-
nickname: string;
16-
}
3+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4+
import { Profile } from '../types/type';
175

186
function useFetchProfile() {
19-
const [profile, setProfile] = useState<Profile | null>(null);
7+
8+
const queryClient = useQueryClient()
209

2110
const fetchProfile = async () => {
2211
const res = await fetch(`${getApi}/me/profile`, {
2312
method: 'GET',
2413
credentials: 'include',
2514
});
2615
const json = await res.json();
27-
setProfile(json.data);
16+
17+
return json.data
2818
};
2919

30-
useEffect(() => {
31-
fetchProfile();
32-
}, []);
20+
const patchNickName = useMutation({
21+
mutationFn: async (nickname: string) => {
22+
const res = await fetch(`${getApi}/me/profile`, {
23+
method: 'PATCH',
24+
credentials: 'include',
25+
headers: { 'Content-Type': 'application/json' },
26+
body: JSON.stringify({nickname})
27+
})
28+
if (!res.ok) throw new Error('닉네임 수정 실패')
29+
const json = await res.json()
30+
return json.data
31+
},
32+
33+
onMutate: async (nickname) => {
34+
// 같은 키로 요청중인 fetch 중단
35+
await queryClient.cancelQueries({ queryKey: ['myProfile'] })
36+
// 캐시에 저장된 데이터를 즉시 가져오는 역할 실패시 prev로 롤백
37+
const prev = queryClient.getQueryData(['myProfile'])
38+
// 캐시 내용을 수정
39+
queryClient.setQueryData(['myProfile'], (old:Profile) => ({ ...old, nickname }))
40+
return {prev}
41+
}
42+
})
43+
const profile = useQuery({queryKey:['myProfile'], queryFn:fetchProfile})
3344

34-
return { profile, fetchProfile };
45+
return{fetchProfile , profile, patchNickName}
3546
}
3647
export default useFetchProfile;

src/domains/mypage/components/EditNickName.tsx

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Input from '@/shared/components/Input-box/Input';
55
import ModalLayout from '@/shared/components/modal-pop/ModalLayout';
66
import { useToast } from '@/shared/hook/useToast';
77
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
8+
import useFetchProfile from '../api/fetchProfile';
89

910
interface Props {
1011
open: boolean;
@@ -27,33 +28,26 @@ function EditNickName({
2728
}: Props) {
2829
const [defaultNickname, setDefaultNickname] = useState(nickname);
2930
const { toastSuccess, toastError } = useToast();
30-
31+
const { patchNickName } = useFetchProfile()
32+
3133
useEffect(() => {
3234
setEditNickName(nickname);
3335
setDefaultNickname(nickname);
3436
}, [nickname, setEditNickName]);
3537

3638
const handlesave = async () => {
37-
if (editNickName.length <= 1 || editNickName.length >= 8) {
38-
toastError('닉네임은 2글자 이상 입력해야합니다');
39-
return;
40-
}
39+
if (editNickName.length <= 1 || editNickName.length >= 8) {
40+
toastError('닉네임은 2글자 이상 8글자 이내로 입력해야합니다');
41+
return;
42+
}
4143

42-
await setNickName(editNickName);
44+
await setNickName(editNickName);
45+
// CRUD중 CUD를 관리하는 메서드
46+
await patchNickName.mutateAsync(editNickName)
4347

44-
await fetch(`${getApi}/me/profile`, {
45-
method: 'PATCH',
46-
credentials: 'include',
47-
headers: {
48-
'Content-Type': 'application/json',
49-
},
50-
body: JSON.stringify({
51-
nickname: editNickName,
52-
}),
53-
});
54-
await setIsOpen(false);
55-
toastSuccess('닉네임이 저장되었습니다.');
56-
};
48+
await setIsOpen(false);
49+
toastSuccess('닉네임이 저장되었습니다.');
50+
}
5751

5852
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
5953
setEditNickName(e.target.value);

src/domains/mypage/main/MyProfile.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import AbvGraph from '@/domains/shared/components/abv-graph/AbvGraph';
44
import MyAbv from './MyAbv';
55
import SsuryImage from './SsuryImage';
66
import useFetchProfile from '../api/fetchProfile';
7-
import { useEffect } from 'react';
7+
import { useQuery } from '@tanstack/react-query';
88

99
function MyProfile() {
10-
const { profile, fetchProfile } = useFetchProfile();
11-
useEffect(() => {
12-
fetchProfile();
13-
}, [profile?.nickname]);
10+
const { fetchProfile } = useFetchProfile();
11+
const {data} = useQuery({ queryKey: ['myProfile'], queryFn: fetchProfile });
1412

15-
if (!profile) return;
13+
if (!data) return;
1614
const {
1715
nickname,
1816
abvLevel,
@@ -21,12 +19,12 @@ function MyProfile() {
2119
myCommentCount,
2220
myKeepCount,
2321
abvDegree,
24-
} = profile;
22+
} = data
2523

2624
return (
2725
<section className="h-auto p-6 bg-white rounded-2xl">
2826
<header className="flex flex-col gap-8 md:gap-3 md:flex-row md:justify-between ">
29-
{profile && (
27+
{data && (
3028
<>
3129
<div className="flex flex-col pb-4 md:pb-0 md:flex-col gap-6">
3230
<div className="rounded-full w-17.5 h-17.5 flex items-center gap-2">

src/domains/mypage/main/MySetting.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import WithdrawModal from '@/domains/mypage/components/WithdrawModal';
55
import TextButton from '@/shared/components/button/TextButton';
66
import { useEffect, useState } from 'react';
77
import useFetchProfile from '../api/fetchProfile';
8+
import { useQuery } from '@tanstack/react-query';
9+
810

911
function MySetting() {
10-
const { profile } = useFetchProfile();
12+
const { fetchProfile } = useFetchProfile();
13+
const { data:profile } = useQuery({ queryKey: ['myProfile'], queryFn: fetchProfile });
1114
const [isOpen, setIsOpen] = useState(false);
1215
const [isQuit, setIsQuit] = useState(false);
1316
const [nickname, setNickName] = useState(profile?.nickname);

src/domains/mypage/types/type.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export interface Profile {
2+
abvDegree: number;
3+
abvLabel: string;
4+
abvLevel: number;
5+
email: string | null;
6+
id: number;
7+
myCommentCount: number
8+
myKeepCount: number
9+
myLikedPostCount: number;
10+
myPostCount: number;
11+
nickname: string;
12+
}

0 commit comments

Comments
 (0)