From 8abffe58ab9d29653d891727ed15555f02ba20bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=AF=BC=EC=A3=BC?= Date: Sat, 30 Aug 2025 18:23:59 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=EB=AF=B8=EB=A6=AC=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=ED=94=84=EB=A1=9C=ED=95=84=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=ED=95=98=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/user/ui/user-profile-modal.tsx | 330 ++++++++++-------- .../ui/reservation-user-card.tsx | 1 - 2 files changed, 188 insertions(+), 143 deletions(-) diff --git a/src/entities/user/ui/user-profile-modal.tsx b/src/entities/user/ui/user-profile-modal.tsx index a9ab7436..041aaa84 100644 --- a/src/entities/user/ui/user-profile-modal.tsx +++ b/src/entities/user/ui/user-profile-modal.tsx @@ -1,6 +1,7 @@ 'use client'; import { XIcon } from 'lucide-react'; +import { useState } from 'react'; import { useUserProfileQuery } from '@/entities/user/model/use-user-profile-query'; import KeywordReview from '@/entities/user/ui/keyword-review'; import ProfileInfoCard from '@/entities/user/ui/profile-info-card'; @@ -23,167 +24,212 @@ export default function UserProfileModal({ memberId, trigger, }: UserProfileModalProps) { + const [open, setOpen] = useState(false); + + return ( + + {trigger} + + {open && ( + + + + setOpen(false)} + /> + + + )} + + ); +} + +function UserProfileBody({ + memberId, + onClose, +}: { + memberId: number; + onClose: () => void; +}) { const { data: profile, isLoading, isError } = useUserProfileQuery(memberId); const { data: positiveKeywordsData } = useUserPositiveKeywordsQuery({ memberId, }); - if (isLoading || isError || !profile || !positiveKeywordsData) return null; - - const positiveKeywords = positiveKeywordsData?.keywords || []; + if (isLoading) { + return ( + <> +
+ 불러오는 중… + + ); + } + + if (isError || !profile || !positiveKeywordsData) { + return ( + <> +
+ 프로필을 불러오지 못했습니다. + + ); + } + + const positiveKeywords = positiveKeywordsData.keywords ?? []; const temperPreset = getSincerityPresetByLevelName( profile.sincerityTemp.levelName, ); return ( - - {trigger} - - - - - - {profile.memberProfile.memberName}님의 프로필 - - - - - - - -
- +
+ + +
+ + +
+
+ {profile.memberProfile.mbti && ( + {profile.memberProfile.mbti} + )} + {profile.memberProfile.interests.slice(0, 4).map((interest) => ( + + {interest.name} + + ))} +
+ +
+
+ {profile.memberProfile.memberName} +
+ +
+ +
+ + t.techStackName) + .join(', ')} + /> + t.label) + .join(', ')} + /> + + +
+ +
+ +
+ + 받은 평가 + + +
+ {/* todo: 기획 fix되면 수정 */} + {/* n명의 유저들이 이런 점이 좋다고 했어요. */} +
    + {positiveKeywords.length > 0 ? ( + positiveKeywords.map((keyword) => ( + + )) + ) : ( + + 아직 받은 평가가 없습니다. + + )} +
+
+
+ + + ); +} -
- -
- - 받은 평가 - - -
- {/* todo: 기획 fix되면 수정 */} - {/* n명의 유저들이 이런 점이 좋다고 했어요. */} - -
    - {positiveKeywords.length > 0 ? ( - positiveKeywords.map((keyword) => ( - - )) - ) : ( - - 아직 받은 평가가 없습니다. - - )} -
-
-
- - - - +function Header({ title, onClose }: { title: string; onClose: () => void }) { + return ( + + + {title} + + + + + + ); +} + +function Field({ icon, value }: { icon: React.ReactNode; value?: string }) { + return ( +
+ {icon} + + {value ?? ''} + +
); } diff --git a/src/features/study/participation/ui/reservation-user-card.tsx b/src/features/study/participation/ui/reservation-user-card.tsx index 4e23e6b3..fc0aa743 100644 --- a/src/features/study/participation/ui/reservation-user-card.tsx +++ b/src/features/study/participation/ui/reservation-user-card.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import UserProfileModal from '@/entities/user/ui/user-profile-modal'; import UserAvatar from '@/shared/ui/avatar'; import Badge from '@/shared/ui/badge'; From 449fa3550752474feab3a8affa0a0f1b5093120f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=AF=BC=EC=A3=BC?= Date: Sat, 30 Aug 2025 18:25:16 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=ED=86=A0=EA=B8=80=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20=EC=8A=A4=ED=84=B0?= =?UTF-8?q?=EB=94=94=20=EC=8B=A0=EC=B2=AD=20=EB=AA=A9=EB=A1=9D=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/model/use-user-profile-query.ts | 72 ++++++++++++++----- src/features/study/model/use-study-query.ts | 1 + .../participation/ui/reservation-list.tsx | 29 ++------ src/features/study/ui/study-card.tsx | 2 +- 4 files changed, 63 insertions(+), 41 deletions(-) diff --git a/src/entities/user/model/use-user-profile-query.ts b/src/entities/user/model/use-user-profile-query.ts index 374fbcf9..d31a5f19 100644 --- a/src/entities/user/model/use-user-profile-query.ts +++ b/src/entities/user/model/use-user-profile-query.ts @@ -1,13 +1,10 @@ import { sendGTMEvent } from '@next/third-parties/google'; -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { getUserProfile, patchAutoMatching, } from '@/entities/user/api/get-user-profile'; -import type { - GetUserProfileResponse, - PatchAutoMatchingParams, -} from '@/entities/user/api/types'; +import type { GetUserProfileResponse } from '@/entities/user/api/types'; import { hashValue } from '@/shared/lib/hash'; export const useUserProfileQuery = (memberId: number) => { @@ -19,23 +16,62 @@ export const useUserProfileQuery = (memberId: number) => { }); }; +export interface PatchAutoMatchingParams { + memberId: number; + autoMatching: boolean; +} + export const usePatchAutoMatchingMutation = () => { - return useMutation({ + const qc = useQueryClient(); + + return useMutation< + void, + unknown, + PatchAutoMatchingParams, + { prev?: unknown } + >({ mutationFn: patchAutoMatching, - onSuccess: (_, variables) => { - if (variables.autoMatching) { - sendGTMEvent({ - event: 'custom_member_study_toggle_on', - dl_timestamp: new Date().toISOString(), - dl_member_id: hashValue(String(variables.memberId)), - }); - } else { - sendGTMEvent({ - event: 'custom_member_study_toggle_off', - dl_timestamp: new Date().toISOString(), - dl_member_id: hashValue(String(variables.memberId)), + + onMutate: async ({ memberId, autoMatching }) => { + await qc.cancelQueries({ queryKey: ['userProfile', memberId] }); + const prev = qc.getQueryData(['userProfile', memberId]); + if (prev && typeof prev === 'object') { + qc.setQueryData(['userProfile', memberId], { + ...(prev as any), + autoMatching, }); } + + return { prev }; + }, + + onError: (_err, { memberId }, ctx) => { + if (ctx?.prev) { + qc.setQueryData(['userProfile', memberId], ctx.prev); + } + }, + + onSuccess: async (_data, { memberId, autoMatching }) => { + await qc.invalidateQueries({ queryKey: ['userProfile', memberId] }); + + await qc.invalidateQueries({ + predicate: (q) => + Array.isArray(q.queryKey) && q.queryKey[0] === 'weeklyParticipation', + }); + + await qc.invalidateQueries({ + predicate: (q) => + Array.isArray(q.queryKey) && + q.queryKey[0] === 'weeklyReservationMembers', + }); + + sendGTMEvent({ + event: autoMatching + ? 'custom_member_study_toggle_on' + : 'custom_member_study_toggle_off', + dl_timestamp: new Date().toISOString(), + dl_member_id: hashValue(String(memberId)), + }); }, }); }; diff --git a/src/features/study/model/use-study-query.ts b/src/features/study/model/use-study-query.ts index ab782631..8976355c 100644 --- a/src/features/study/model/use-study-query.ts +++ b/src/features/study/model/use-study-query.ts @@ -23,6 +23,7 @@ export const useWeeklyParticipation = (params: string) => { queryKey: ['weeklyParticipation', params], queryFn: () => getWeeklyParticipation(params), staleTime: 60 * 1000, + enabled: !!params, }); }; diff --git a/src/features/study/participation/ui/reservation-list.tsx b/src/features/study/participation/ui/reservation-list.tsx index 284e9206..9a84875c 100644 --- a/src/features/study/participation/ui/reservation-list.tsx +++ b/src/features/study/participation/ui/reservation-list.tsx @@ -9,24 +9,24 @@ import { import ProfileDefault from '@/entities/user/ui/icon/profile-default.svg'; import { getCookie } from '@/shared/tanstack-query/cookie'; import ReservationCard from './reservation-user-card'; +import { useWeeklyParticipation } from '../../model/use-study-query'; import StartStudyModal from '../../ui/start-study-modal'; import { useInfiniteReservation } from '../model/use-participation-query'; interface ReservationListProps { - isParticipation?: boolean; + studyDate?: string; pageSize?: number; month: number; week: number; } export default function ReservationList({ - isParticipation = false, + studyDate, pageSize = 50, month, week, }: ReservationListProps) { const sentinelRef = useRef(null); - const calledRef = useRef(false); const [memberId, setMemberId] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); @@ -35,37 +35,22 @@ export default function ReservationList({ setMemberId(id ? Number(id) : null); }, []); + const { data: participation } = useWeeklyParticipation(studyDate); + const isParticipation = participation?.isParticipate ?? false; + const firstMemberId = useMemo( () => (isParticipation && memberId !== null ? memberId : null), [isParticipation, memberId], ); const { data, isLoading, isFetchingNextPage, fetchNextPage, hasNextPage } = - useInfiniteReservation(firstMemberId, pageSize); + useInfiniteReservation(firstMemberId ?? undefined, pageSize); const { data: userProfile } = useUserProfileQuery(memberId ?? 0); const { mutate: patchAutoMatching, isPending } = usePatchAutoMatchingMutation(); - useEffect(() => { - if (!memberId || isParticipation || !userProfile) return; - - const { studyApplied, autoMatching } = userProfile; - - if (studyApplied && !autoMatching && !calledRef.current) { - calledRef.current = true; - patchAutoMatching( - { memberId, autoMatching: true }, - { - onError: () => { - calledRef.current = false; - }, - }, - ); - } - }, [memberId, isParticipation, userProfile, patchAutoMatching]); - useEffect(() => { if (!hasNextPage) return; diff --git a/src/features/study/ui/study-card.tsx b/src/features/study/ui/study-card.tsx index 04082f97..453ccd4c 100644 --- a/src/features/study/ui/study-card.tsx +++ b/src/features/study/ui/study-card.tsx @@ -80,7 +80,7 @@ export default function StudyCard() { )} From 669c862cad5cce61c845097bc1edbd3739822169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=AF=BC=EC=A3=BC?= Date: Sat, 30 Aug 2025 20:04:34 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=B3=B8=EC=9D=B8=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=ED=99=95=EC=9D=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../participation/ui/reservation-list.tsx | 19 +++++++++++++------ .../ui/reservation-user-card.tsx | 13 ++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/features/study/participation/ui/reservation-list.tsx b/src/features/study/participation/ui/reservation-list.tsx index 9a84875c..1dee8a28 100644 --- a/src/features/study/participation/ui/reservation-list.tsx +++ b/src/features/study/participation/ui/reservation-list.tsx @@ -38,16 +38,19 @@ export default function ReservationList({ const { data: participation } = useWeeklyParticipation(studyDate); const isParticipation = participation?.isParticipate ?? false; + const { data: userProfile } = useUserProfileQuery(memberId ?? 0); + const autoMatching = userProfile?.autoMatching ?? false; + + const applied = autoMatching || isParticipation; + const firstMemberId = useMemo( - () => (isParticipation && memberId !== null ? memberId : null), - [isParticipation, memberId], + () => (applied && memberId !== null ? memberId : null), + [applied, memberId], ); const { data, isLoading, isFetchingNextPage, fetchNextPage, hasNextPage } = useInfiniteReservation(firstMemberId ?? undefined, pageSize); - const { data: userProfile } = useUserProfileQuery(memberId ?? 0); - const { mutate: patchAutoMatching, isPending } = usePatchAutoMatchingMutation(); @@ -109,10 +112,14 @@ export default function ReservationList({
{items.map((p) => ( - + ))} - {!isParticipation && ( + {!applied && (
{participant.name}
- {isCurrentUser && 본인} + {isCurrentUser && ( + + 본인 + + )}
{participant.simpleIntroduction} From 7951f791352350665ea404b1eca38ad20d9b5aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=AF=BC=EC=A3=BC?= Date: Sun, 31 Aug 2025 00:16:22 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=ED=86=A0=EA=B8=80=20on/off=EC=97=90?= =?UTF-8?q?=20=EB=94=B0=EB=9D=BC=20=EB=B2=84=ED=8A=BC=20=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../study/participation/ui/reservation-list.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/features/study/participation/ui/reservation-list.tsx b/src/features/study/participation/ui/reservation-list.tsx index 1dee8a28..912afdef 100644 --- a/src/features/study/participation/ui/reservation-list.tsx +++ b/src/features/study/participation/ui/reservation-list.tsx @@ -9,7 +9,6 @@ import { import ProfileDefault from '@/entities/user/ui/icon/profile-default.svg'; import { getCookie } from '@/shared/tanstack-query/cookie'; import ReservationCard from './reservation-user-card'; -import { useWeeklyParticipation } from '../../model/use-study-query'; import StartStudyModal from '../../ui/start-study-modal'; import { useInfiniteReservation } from '../model/use-participation-query'; @@ -35,17 +34,12 @@ export default function ReservationList({ setMemberId(id ? Number(id) : null); }, []); - const { data: participation } = useWeeklyParticipation(studyDate); - const isParticipation = participation?.isParticipate ?? false; - const { data: userProfile } = useUserProfileQuery(memberId ?? 0); const autoMatching = userProfile?.autoMatching ?? false; - const applied = autoMatching || isParticipation; - const firstMemberId = useMemo( - () => (applied && memberId !== null ? memberId : null), - [applied, memberId], + () => (autoMatching && memberId !== null ? memberId : null), + [autoMatching, memberId], ); const { data, isLoading, isFetchingNextPage, fetchNextPage, hasNextPage } = @@ -119,7 +113,7 @@ export default function ReservationList({ /> ))} - {!applied && ( + {!autoMatching && (