Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
22 changes: 22 additions & 0 deletions src/app/(user-page)/my-meeting/_features/CardRightSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
useExpelMutation,
useMemberStatusMutation,
} from '@/hooks/mutations/useMyMeetingMutation';
import { myMeetingKeys } from '@/hooks/queries/useMyMeetingQueries';
import { useBannerQueries } from '@/hooks/queries/useMyPageQueries';
import { useQueryClient } from '@tanstack/react-query';
import Image from 'next/image';
import { useState } from 'react';
import { getMyMeetingMemberProfile } from 'service/api/mymeeting';
import type { Member } from 'types/myMeeting';

import ModalProfile from './ModalProfile';
Expand Down Expand Up @@ -82,10 +85,27 @@
setIsUserProfileModalOpen(true);
};

const queryClient = useQueryClient();

const handlePrefetchProfile = async (member: Member) => {
const queryKey = myMeetingKeys.memberProfile(meetingId, member.userId);

// 캐시에 데이터가 있는지 확인
const cachedData = queryClient.getQueryData(queryKey);

if (!cachedData) {
await queryClient.prefetchQuery({
queryKey,
queryFn: () =>
getMyMeetingMemberProfile({ meetingId, userId: member.userId }),
});
}
};

// 프로필 보기 할 유저
const [selectedUser, setSelectedUser] = useState<Member | null>(null);

const { data: currentUser, isLoading, error } = useBannerQueries();

Check warning on line 108 in src/app/(user-page)/my-meeting/_features/CardRightSection.tsx

View workflow job for this annotation

GitHub Actions / check

'error' is assigned a value but never used. Allowed unused vars must match /^_/u
if (isLoading || !currentUser) {
return;
}
Expand Down Expand Up @@ -124,6 +144,7 @@
variant={'outline'}
className="h-[40px] w-[93px]"
onClick={(e) => handleOpenProfileModal(e, member)}
onMouseEnter={() => handlePrefetchProfile(member)}
>
프로필보기
</Button>
Expand Down Expand Up @@ -180,6 +201,7 @@
setIsUserProfileModalOpen={setIsUserProfileModalOpen}
setIsUserListModalOpen={setIsUserListModalOpen}
currentUser={currentUser}
handlePrefetchProfile={handlePrefetchProfile}
/>
</Modal>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/app/(user-page)/my-meeting/_features/ModalUserList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Tag } from '@/components/ui/Tag';
import { useBannerQueries } from '@/hooks/queries/useMyPageQueries';

Check warning on line 2 in src/app/(user-page)/my-meeting/_features/ModalUserList.tsx

View workflow job for this annotation

GitHub Actions / check

'useBannerQueries' is defined but never used. Allowed unused vars must match /^_/u
import Image from 'next/image';
import React from 'react';
import { Dispatch, SetStateAction, useState } from 'react';

Check warning on line 5 in src/app/(user-page)/my-meeting/_features/ModalUserList.tsx

View workflow job for this annotation

GitHub Actions / check

'useState' is defined but never used. Allowed unused vars must match /^_/u
import type { IBanner, Member } from 'types/myMeeting';

import { Button } from '../../../../components/ui/Button';
Expand All @@ -14,13 +14,15 @@
setSelectedUser,
currentUser,
className,
handlePrefetchProfile,
}: {
memberList: Member[];
setSelectedUser: Dispatch<React.SetStateAction<Member | null>>;
setIsUserProfileModalOpen: Dispatch<SetStateAction<boolean>>;
setIsUserListModalOpen: Dispatch<SetStateAction<boolean>>;
currentUser: IBanner;
className?: string;
handlePrefetchProfile: (member: Member) => Promise<void>;
}) => {
const handleProfileClick = (user: Member) => {
setSelectedUser(user);
Expand Down Expand Up @@ -55,6 +57,7 @@
onClick={() => handleProfileClick(user)}
variant="outline"
size="sm"
onMouseEnter={() => handlePrefetchProfile(user)}
>
프로필 보기
</Button>
Expand Down
38 changes: 36 additions & 2 deletions src/app/(user-page)/my-meeting/my/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,56 @@
import NotYet from '@/components/common/NotYet';
import { myMeetingKeys } from '@/hooks/queries/useMyMeetingQueries';
import { QUERY_KEYS } from '@/hooks/queries/useMyPageQueries';
import {
HydrationBoundary,
QueryClient,
dehydrate,
} from '@tanstack/react-query';
import { getMyMeetingManage } from 'service/api/mymeeting';
import { getBanner } from 'service/api/mypageProfile';
import { Paginated } from 'types/meeting';
import { IMyMeetingManage } from 'types/myMeeting';

import Created from '../_features/Created';
// import Joined from '../../components/Joined';
import Tab from '../_features/Tab';

export default function Page({
export default async function Page({
searchParams,
}: {
searchParams: { type: string };
}) {
const type = searchParams?.type;

const queryClient = new QueryClient();

// 배너
await queryClient.prefetchQuery({
queryKey: QUERY_KEYS.banner(),
queryFn: () => getBanner(),
});

if (type === 'created') {
// 내가 만든 모임 리스트 prefetch
await queryClient.prefetchInfiniteQuery({
queryKey: myMeetingKeys.manage(),
queryFn: ({ pageParam }) => getMyMeetingManage(pageParam),
getNextPageParam: (lastPage: Paginated<IMyMeetingManage>) =>
lastPage.nextCursor ?? false,
initialPageParam: 0,
});
} else {
// 참여 중인 모임 prefetch
}

return (
<div>
<div className="mt-6 flex flex-col gap-[24px]">
<Tab type={type} />
</div>
{type === 'created' ? <Created /> : <NotYet />}
<HydrationBoundary state={dehydrate(queryClient)}>
{type === 'created' ? <Created /> : <NotYet />}
</HydrationBoundary>
</div>
);
}
13 changes: 13 additions & 0 deletions src/app/(user-page)/mypage/MyPageClient.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import { prefetchProfileData } from '@/hooks/queries/useMyPageQueries';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter, useSearchParams } from 'next/navigation';
import { useCallback, useEffect, useRef, useState } from 'react';

Expand All @@ -18,6 +20,7 @@ import TechStackInfo from './_features/TechStackInfo';
const MyPageClient = () => {
const router = useRouter();
const searchParams = useSearchParams();
const queryClient = useQueryClient();

// URL에서 탭 값만 가져오기
const tabFromUrl = searchParams.get('tab') || TAB_TYPES.BASIC;
Expand All @@ -37,6 +40,11 @@ const MyPageClient = () => {
[TAB_TYPES.PASSWORD]: null,
});

// 컴포넌트 마운트 시 프로필 데이터 prefetch
useEffect(() => {
prefetchProfileData(queryClient);
}, [queryClient]);

// URL 변경 감지 및 상태 업데이트 (중요: 뒤로가기/앞으로가기 처리)
useEffect(() => {
const currentTabFromUrl = searchParams.get('tab') || TAB_TYPES.BASIC;
Expand All @@ -59,6 +67,9 @@ const MyPageClient = () => {

// 탭 변경 핸들러
const handleTabChange = (tab: string) => {
// 탭 변경 전에 해당 탭에 필요한 데이터 prefetch
prefetchProfileData(queryClient);

setActiveTab(tab);
updateUrl(tab);
};
Expand All @@ -69,6 +80,8 @@ const MyPageClient = () => {
};

const handleEnableEdit = () => {
// 편집 모드로 전환하기 전에 프로필 데이터 다시 prefetch하여 최신 데이터 확보
prefetchProfileData(queryClient);
setEditModeSection(activeTab);
};

Expand Down
6 changes: 2 additions & 4 deletions src/app/(user-page)/mypage/_features/BasicEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import Dropdown from '@/components/common/Dropdown';
import { Button } from '@/components/ui/Button';
import {
useProfileQuery,
useUpdateProfileMutation,
} from '@/hooks/queries/useMyPageQueries';
import { useUpdateProfileMutation } from '@/hooks/mutations/useMyPageMutation';
import { useProfileQuery } from '@/hooks/queries/useMyPageQueries';
import { useCallback, useEffect, useState } from 'react';
import { Controller, useForm, useWatch } from 'react-hook-form';

Expand Down
6 changes: 2 additions & 4 deletions src/app/(user-page)/mypage/_features/ContactEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import {
useProfileQuery,
useUpdateContactInfoMutation,
} from '@/hooks/queries/useMyPageQueries';
import { useUpdateContactInfoMutation } from '@/hooks/mutations/useMyPageMutation';
import { useProfileQuery } from '@/hooks/queries/useMyPageQueries';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';

Expand Down
2 changes: 1 addition & 1 deletion src/app/(user-page)/mypage/_features/PasswordEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useToast } from '@/components/common/ToastContext';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { useUpdatePasswordMutation } from '@/hooks/queries/useMyPageQueries';
import { useUpdatePasswordMutation } from '@/hooks/mutations/useMyPageMutation';
import { useForm } from 'react-hook-form';

interface PasswordFormData {
Expand Down
6 changes: 2 additions & 4 deletions src/app/(user-page)/mypage/_features/ProfileImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import EditLogo from '@/assets/icon/editLogo.svg';
import { useToast } from '@/components/common/ToastContext';
import { Button } from '@/components/ui/Button';
import Modal from '@/components/ui/modal/Modal';
import {
useProfileQuery,
useUpdateProfileImageMutation,
} from '@/hooks/queries/useMyPageQueries';
import { useUpdateProfileImageMutation } from '@/hooks/mutations/useMyPageMutation';
import { useProfileQuery } from '@/hooks/queries/useMyPageQueries';
import { useQueryClient } from '@tanstack/react-query';
import { Pencil } from 'lucide-react';
import Image from 'next/image';
Expand Down
6 changes: 2 additions & 4 deletions src/app/(user-page)/mypage/_features/TechStackEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
'use client';

import { Button } from '@/components/ui/Button';
import { useUpdateSkillsMutation } from '@/hooks/mutations/useMyPageMutation';
// Button 컴포넌트 import 추가
import {
useProfileQuery,
useUpdateSkillsMutation,
} from '@/hooks/queries/useMyPageQueries';
import { useProfileQuery } from '@/hooks/queries/useMyPageQueries';
import useTechSelection from '@/hooks/useTechSelection';
import { getIconColor, getIconsByCategory } from '@/util/getIconDetail';
import { useQueryClient } from '@tanstack/react-query';
Expand Down
25 changes: 18 additions & 7 deletions src/app/(user-page)/mypage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
// page.tsx (서버 컴포넌트)
import { prefetchProfileData } from '@/hooks/queries/useMyPageQueries';
import { QueryClient, dehydrate } from '@tanstack/react-query';
import { HydrationBoundary } from '@tanstack/react-query';
import { Suspense } from 'react';

import MyPageClient from './MyPageClient';
import ProfileImage from './_features/ProfileImage';
import SkeletonBasicInfo from './_features/skeletons/SkeletonBasicInfo';

export default function MyPage() {
export default async function MyPage() {
// 서버 컴포넌트에서 QueryClient 생성
const queryClient = new QueryClient();

// 필요한 데이터 프리페치
await prefetchProfileData(queryClient);

return (
<div className="flex flex-col px-[24px] pb-[100px]">
<div className="md:mb-8">
<ProfileImage />
</div>
<HydrationBoundary state={dehydrate(queryClient)}>
<div className="md:mb-8">
<ProfileImage />
</div>

<Suspense fallback={<SkeletonBasicInfo />}>
<MyPageClient />
</Suspense>
<Suspense fallback={<SkeletonBasicInfo />}>
<MyPageClient />
</Suspense>
</HydrationBoundary>
</div>
);
}
70 changes: 70 additions & 0 deletions src/hooks/mutations/useMyPageMutation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import {
updateContactInfo,
updatePassword,
updateProfile,
updateProfileImage,
updateSkills,
} from '../../service/api/mypageProfile';
import {
IContactInfoUpdateRequest,
IPasswordUpdateRequest,
IProfileUpdateRequest,
} from '../../types/mypageTypes';
import { QUERY_KEYS } from '../queries/useMyPageQueries';

// 프로필 정보 업데이트 커스텀 훅
export const useUpdateProfileMutation = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (data: IProfileUpdateRequest) => updateProfile(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.profile() });
},
});
};

// 연락처 정보 업데이트 커스텀 훅
export const useUpdateContactInfoMutation = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (data: IContactInfoUpdateRequest) => updateContactInfo(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.profile() });
},
});
};

// 프로필 이미지 업데이트 커스텀 훅
export const useUpdateProfileImageMutation = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (file: File) => updateProfileImage(file),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.profile() });
},
});
};

// 비밀번호 업데이트 커스텀 훅
export const useUpdatePasswordMutation = () => {
return useMutation({
mutationFn: (data: IPasswordUpdateRequest) => updatePassword(data),
});
};

// 기술 스택 업데이트 커스텀 훅
export const useUpdateSkillsMutation = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (skillArray: string[]) => updateSkills(skillArray),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.profile() });
},
});
};
4 changes: 3 additions & 1 deletion src/hooks/queries/useMyMeetingQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
getMyMeetingManage,
getMyMeetingMemberProfile,
} from 'service/api/mymeeting';
import { Paginated } from 'types/meeting';
import { IMyMeetingManage } from 'types/myMeeting';

export const myMeetingKeys = {
all: ['mymeeting'] as const,
Expand All @@ -19,7 +21,7 @@ export const useInfiniteMyMeetingManageQueries = () => {
queryKey: myMeetingKeys.manage(),
queryFn: ({ pageParam }) => getMyMeetingManage(pageParam),
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => {
getNextPageParam: (lastPage: Paginated<IMyMeetingManage>, pages) => {
console.log('[mutation] lastPage: ', lastPage);
return lastPage.nextCursor ?? null;
},
Expand Down
Loading
Loading