From 2f30aa00da9a54f15c78921b1221def2300aef09 Mon Sep 17 00:00:00 2001 From: godhyzzang <109357302+godhyzzang@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:54:19 +0900 Subject: [PATCH 1/4] =?UTF-8?q?refactor=20:=20mypage=EC=97=90=EB=8F=84=20P?= =?UTF-8?q?refetch=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/queries/categoryQueries.ts | 5 +- src/api/queries/userQueries.ts | 5 +- .../mypage/profile/ProfileClient.tsx | 253 ++++++++++++++++ src/app/(default)/mypage/profile/page.tsx | 276 ++---------------- 4 files changed, 286 insertions(+), 253 deletions(-) create mode 100644 src/app/(default)/mypage/profile/ProfileClient.tsx diff --git a/src/api/queries/categoryQueries.ts b/src/api/queries/categoryQueries.ts index f753ae8a..cb9e0956 100644 --- a/src/api/queries/categoryQueries.ts +++ b/src/api/queries/categoryQueries.ts @@ -2,9 +2,12 @@ import { apiClient } from '@/lib/apiClient'; import { FetchCategoryResponse } from '@/types/Category'; // 모든 카테고리 조회 (GET) -export const fetchAllCategories = async (): Promise => { +export const fetchAllCategories = async ( + customHeaders?: Record, +): Promise => { return apiClient('/categories/all', { method: 'GET', + customHeaders, }); }; diff --git a/src/api/queries/userQueries.ts b/src/api/queries/userQueries.ts index 166a9a12..14f9a6eb 100644 --- a/src/api/queries/userQueries.ts +++ b/src/api/queries/userQueries.ts @@ -8,9 +8,12 @@ import { } from '@/types/User'; // 사용자 정보 조회 (GET) -export const fetchUserInfo = async (): Promise => { +export const fetchUserInfo = async ( + customHeaders?: Record, +): Promise => { return apiClient('/user/me', { method: 'GET', + customHeaders, }); }; diff --git a/src/app/(default)/mypage/profile/ProfileClient.tsx b/src/app/(default)/mypage/profile/ProfileClient.tsx new file mode 100644 index 00000000..1fc40a62 --- /dev/null +++ b/src/app/(default)/mypage/profile/ProfileClient.tsx @@ -0,0 +1,253 @@ +'use client'; + +/* eslint-disable jsx-a11y/label-has-associated-control */ +import { useState, useMemo } from 'react'; +// TODO(@godhyzzang) : 나중에 프로필 업로드 기능 추가 +// import { Camera, X } from 'lucide-react'; + +import { useFetchAllCategories } from '@/api/hooks/useCategories'; +import { useUserInfo, useUpdateUserInfo } from '@/api/hooks/useUserInfo'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { useToast } from '@/hooks/use-toast'; + +export default function UserProfile() { + const { data: userData, refetch: refetchUserInfo } = useUserInfo(); + const { data: categoryData } = useFetchAllCategories(); + const { toast } = useToast(); + + // 상태 설정 + const [nickname, setNickname] = useState(userData?.data.nickname || ''); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [username, setUsername] = useState(userData?.data.username || ''); + const [selectedCategories, setSelectedCategories] = useState( + userData?.data.myCategories?.map((category) => category.id) || [], + ); + + const updateUserInfoMutation = useUpdateUserInfo(); + + // 닉네임 변경 + const handleNicknameChange = () => { + // 닉네임 길이 검증 + if (nickname.length < 4 || nickname.length > 12) { + toast({ + duration: 1000, + description: '닉네임은 4~12자 사이로 해주세요', + }); + return; + } + + // 닉네임 패턴 검증 + const nicknamePattern = /^[a-zA-Z가-힣0-9]+( [a-zA-Z가-힣0-9]+)*$/; + if (!nicknamePattern.test(nickname)) { + toast({ + duration: 1000, + description: '닉네임은 영어, 한글, 숫자 및 단일 공백만 사용해주세요', + }); + return; + } + + // 서버로 닉네임 변경 요청 + updateUserInfoMutation.mutate( + { nickname }, + { + onSuccess: () => { + toast({ + duration: 1000, + description: '닉네임이 성공적으로 변경되었어요', + }); + refetchUserInfo(); + }, + onError: () => { + toast({ duration: 1000, description: '닉네임을 변경하지 못했어요' }); + setNickname(userData?.data.nickname || ''); + }, + }, + ); + }; + + // 닉네임 변경 여부 확인 + const isNicknameChanged = useMemo(() => { + return nickname !== userData?.data.nickname; + }, [nickname, userData?.data.nickname]); + + // 카테고리 토글 + const toggleCategory = (categoryId: number) => { + setSelectedCategories((prevSelected) => { + if (prevSelected.includes(categoryId)) { + return prevSelected.filter((id) => id !== categoryId); + } + if (prevSelected.length < 5) { + return [...prevSelected, categoryId]; + } + + toast({ + duration: 1000, + description: '카테고리는 최대 5개까지 선택 가능해요', + }); + + return prevSelected; + }); + }; + + // 카테고리 변경 여부 확인 + const isCategoriesChanged = useMemo(() => { + const initialCategories = + userData?.data.myCategories?.map((category) => category.id) || []; + return ( + selectedCategories.length !== initialCategories.length || + selectedCategories.some((id) => !initialCategories.includes(id)) + ); + }, [selectedCategories, userData?.data.myCategories]); + + // 카테고리 변경 + const updateCategories = () => { + updateUserInfoMutation.mutate( + { categories: selectedCategories }, + { + onSuccess: () => { + toast({ + duration: 1000, + description: '카테고리가 성공적으로 변경되었어요', + }); + refetchUserInfo(); + }, + onError: () => { + toast({ + duration: 1000, + description: '카테고리를 변경하지 못했어요', + }); + setSelectedCategories( + userData?.data.myCategories?.map((category) => category.id) || [], + ); + }, + }, + ); + }; + + return ( +
+
+

개인정보수정

+
+
+ {/*
+
+ Profile + + +
+
*/} + +
+
+ +
+ setNickname(e.target.value)} + className="flex-1" + /> + +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+
+

관심 카테고리

+ +
+
+ {categoryData?.data.categoryList?.map((category) => ( + + ))} +
+
+
+
+ ); +} diff --git a/src/app/(default)/mypage/profile/page.tsx b/src/app/(default)/mypage/profile/page.tsx index c58c58e2..263394bd 100644 --- a/src/app/(default)/mypage/profile/page.tsx +++ b/src/app/(default)/mypage/profile/page.tsx @@ -1,261 +1,35 @@ -'use client'; +import { cookies } from 'next/headers'; -/* eslint-disable jsx-a11y/label-has-associated-control */ -import { useState, useEffect, useMemo } from 'react'; -// TODO(@godhyzzang) : 나중에 프로필 업로드 기능 추가 -// import { Camera, X } from 'lucide-react'; +import { + HydrationBoundary, + QueryClient, + dehydrate, +} from '@tanstack/react-query'; -import { useFetchAllCategories } from '@/api/hooks/useCategories'; -import { useUserInfo, useUpdateUserInfo } from '@/api/hooks/useUserInfo'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { useToast } from '@/hooks/use-toast'; +import { fetchAllCategories } from '@/api/queries/categoryQueries'; +import { fetchUserInfo } from '@/api/queries/userQueries'; -export default function UserProfile() { - const { data: userData, refetch: refetchUserInfo } = useUserInfo(); - const { data: categoryData } = useFetchAllCategories(); - const { toast } = useToast(); +import ProfileClient from './ProfileClient'; - // 상태 설정 - const [nickname, setNickname] = useState(''); - const [username, setUsername] = useState(''); - const [selectedCategories, setSelectedCategories] = useState([]); +export default async function mypageProfilePage() { + const cookieHeader = cookies().toString(); - const updateUserInfoMutation = useUpdateUserInfo(); + const queryClient = new QueryClient(); - // 초기 데이터 동기화 - useEffect(() => { - if (userData?.data) { - setNickname(userData.data.nickname || ''); - setUsername(userData.data.username || ''); - setSelectedCategories( - userData.data.myCategories?.map((category) => category.id) || [], - ); - } - }, [userData]); - - // 닉네임 변경 - const handleNicknameChange = () => { - // 닉네임 길이 검증 - if (nickname.length < 4 || nickname.length > 12) { - toast({ - duration: 1000, - description: '닉네임은 4~12자 사이로 해주세요', - }); - return; - } - - // 닉네임 패턴 검증 - const nicknamePattern = /^[a-zA-Z가-힣0-9]+( [a-zA-Z가-힣0-9]+)*$/; - if (!nicknamePattern.test(nickname)) { - toast({ - duration: 1000, - description: '닉네임은 영어, 한글, 숫자 및 단일 공백만 사용해주세요', - }); - return; - } - - // 서버로 닉네임 변경 요청 - updateUserInfoMutation.mutate( - { nickname }, - { - onSuccess: () => { - toast({ - duration: 1000, - description: '닉네임이 성공적으로 변경되었어요', - }); - refetchUserInfo(); - }, - onError: () => { - toast({ duration: 1000, description: '닉네임을 변경하지 못했어요' }); - setNickname(userData?.data.nickname || ''); - }, - }, - ); - }; - - // 닉네임 변경 여부 확인 - const isNicknameChanged = useMemo(() => { - return nickname !== userData?.data.nickname; - }, [nickname, userData?.data.nickname]); - - // 카테고리 토글 - const toggleCategory = (categoryId: number) => { - setSelectedCategories((prevSelected) => { - if (prevSelected.includes(categoryId)) { - return prevSelected.filter((id) => id !== categoryId); - } - if (prevSelected.length < 5) { - return [...prevSelected, categoryId]; - } - - toast({ - duration: 1000, - description: '카테고리는 최대 5개까지 선택 가능해요', - }); - - return prevSelected; - }); - }; - - // 카테고리 변경 여부 확인 - const isCategoriesChanged = useMemo(() => { - const initialCategories = - userData?.data.myCategories?.map((category) => category.id) || []; - return ( - selectedCategories.length !== initialCategories.length || - selectedCategories.some((id) => !initialCategories.includes(id)) - ); - }, [selectedCategories, userData?.data.myCategories]); - - // 카테고리 변경 - const updateCategories = () => { - updateUserInfoMutation.mutate( - { categories: selectedCategories }, - { - onSuccess: () => { - toast({ - duration: 1000, - description: '카테고리가 성공적으로 변경되었어요', - }); - refetchUserInfo(); - }, - onError: () => { - toast({ - duration: 1000, - description: '카테고리를 변경하지 못했어요', - }); - setSelectedCategories( - userData?.data.myCategories?.map((category) => category.id) || [], - ); - }, - }, - ); - }; + await Promise.allSettled([ + queryClient.prefetchQuery({ + queryKey: ['user'], + queryFn: () => fetchUserInfo({ Cookie: cookieHeader }), + }), + queryClient.prefetchQuery({ + queryKey: ['categories'], + queryFn: () => fetchAllCategories({ Cookie: cookieHeader }), + }), + ]); return ( -
-
-

개인정보수정

-
-
- {/*
-
- Profile - - -
-
*/} - -
-
- -
- setNickname(e.target.value)} - className="flex-1" - /> - -
-
- -
- -
- -
-
- -
- -
- -
-
-
- -
-
-

관심 카테고리

- -
-
- {categoryData?.data.categoryList?.map((category) => ( - - ))} -
-
-
-
+ + + ); } From 211f81cf71f60c5659c8a4d8b18d8b7476768b03 Mon Sep 17 00:00:00 2001 From: godhyzzang <109357302+godhyzzang@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:50:09 +0900 Subject: [PATCH 2/4] =?UTF-8?q?design=20:=20=EC=9D=B4=EB=A6=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/components/HomePageClient.tsx | 10 ++++++++-- src/components/RecommendedList.tsx | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/HomePageClient.tsx b/src/components/HomePageClient.tsx index 79c7b764..a4151d15 100644 --- a/src/components/HomePageClient.tsx +++ b/src/components/HomePageClient.tsx @@ -75,7 +75,10 @@ export default function HomePageClient({ items={listeningList?.data.listeningPreview || []} header={
-

인기 리스닝 콘텐츠

+

+ 🔥 HOT + 리스닝 콘텐츠 +

+ + Go to Page +
From 45e709abbc1e5ab30783ccd17be658d3e1ae0481 Mon Sep 17 00:00:00 2001 From: godhyzzang <109357302+godhyzzang@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:04:31 +0900 Subject: [PATCH 4/4] =?UTF-8?q?design=20:=20=EC=BA=90=EC=B9=98=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=A6=88=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RecommendedList.tsx | 6 +++++- src/components/SentenceComponent.tsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/RecommendedList.tsx b/src/components/RecommendedList.tsx index 2ddcff1b..cbe47f1e 100644 --- a/src/components/RecommendedList.tsx +++ b/src/components/RecommendedList.tsx @@ -18,7 +18,11 @@ export default function RecommendedList({ return (
-

당신을 위한 추천 콘텐츠

+

+ {' '} + 당신을 위한 추천 + PICK +

{/* 로그인 필요 안내 */} {!isLogin && ( diff --git a/src/components/SentenceComponent.tsx b/src/components/SentenceComponent.tsx index 112efa82..7dabd486 100644 --- a/src/components/SentenceComponent.tsx +++ b/src/components/SentenceComponent.tsx @@ -18,7 +18,7 @@ export default function SentenceComponent({

- 지난주에 다른 사람들이 저장한 문장 + 지난 주 사람들이 저장한 문장

{data.enDetail}