Skip to content

Commit 8bb232d

Browse files
Feat/ 마이페이지 닉네임 수정시 myProfile컴포넌트와 동기화 (#121)
* [feat]칵테일 댓글 ui * [chore] merge전 커밋 * [chore] 확인용 커밋 * 댓글에러 * 포맷 * 커뮤니티 댓글 수정 * [chore]풀 전용 커밋 * [feat] 칵테일 댓글등록 * [feat]로그인 유저 킵 버튼 * [feat] 나만의 바 라벨링 * [feat]로그인 한 유저 킵버튼 * [feaet] 상세페이지 킵 * [feat]레시피페이지정렬 * [feat] 댓글 클릭시 해당 글로 보내주는 기능 * [feat]댓글 작성 토스트 * [feat]댓글 validation * [feat]댓글 validation * [fix] 댓글, 킵 아이템 비로그인 경고 * [fix] mypage 401에러 대응 * [feat] 마이페이지 프로필 수정 동기화 * [chore]포매팅 --------- Co-authored-by: EunbinJung <[email protected]>
1 parent 3f275b2 commit 8bb232d

File tree

11 files changed

+113
-57
lines changed

11 files changed

+113
-57
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: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ 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';
910

1011
export const metadata: Metadata = {
1112
title: { default: 'SSOUL', template: 'SSOUL | %s' },
@@ -21,24 +22,26 @@ export default function RootLayout({
2122
return (
2223
<html lang="ko-KR">
2324
<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 />
25+
<Provider>
26+
<Header />
27+
<IdleHandler />
28+
<main className="flex flex-1 pt-[2.75rem] md:pt-[3.75rem]">{children}</main>
29+
<FooterWrapper />
2830

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

41-
<ScrollTopBtnWrapper />
43+
<ScrollTopBtnWrapper />
44+
</Provider>
4245
</body>
4346
<KaKaoScript />
4447
</html>

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: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
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+
const queryClient = useQueryClient();
208

219
const fetchProfile = async () => {
2210
const res = await fetch(`${getApi}/me/profile`, {
2311
method: 'GET',
2412
credentials: 'include',
2513
});
2614
const json = await res.json();
27-
setProfile(json.data);
15+
16+
return json.data;
2817
};
2918

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

34-
return { profile, fetchProfile };
44+
return { fetchProfile, profile, patchNickName };
3545
}
3646
export default useFetchProfile;

src/domains/mypage/components/EditNickName.tsx

Lines changed: 5 additions & 11 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,6 +28,7 @@ function EditNickName({
2728
}: Props) {
2829
const [defaultNickname, setDefaultNickname] = useState(nickname);
2930
const { toastSuccess, toastError } = useToast();
31+
const { patchNickName } = useFetchProfile();
3032

3133
useEffect(() => {
3234
setEditNickName(nickname);
@@ -35,22 +37,14 @@ function EditNickName({
3537

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

4244
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-
});
5448
await setIsOpen(false);
5549
toastSuccess('닉네임이 저장되었습니다.');
5650
};

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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ 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';
89

910
function MySetting() {
10-
const { profile } = useFetchProfile();
11+
const { fetchProfile } = useFetchProfile();
12+
const { data: profile } = useQuery({ queryKey: ['myProfile'], queryFn: fetchProfile });
1113
const [isOpen, setIsOpen] = useState(false);
1214
const [isQuit, setIsQuit] = useState(false);
1315
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+
}

src/domains/recipe/components/main/Cocktails.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import CocktailSearchBar from './CocktailSearchBar';
1010
import useSearchControl from '../../hook/useSearchControl';
1111
import CocktailSearch from '../../api/CocktailSearch';
1212

13+
1314
function Cocktails() {
1415
const [data, setData] = useState<Cocktail[]>([]);
1516
const [lastId, setLastId] = useState<number | null>(null);

0 commit comments

Comments
 (0)