From a4aa6569a2c52afead96d2eb459457a10bc71bde Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 01:28:15 +0900 Subject: [PATCH 01/17] =?UTF-8?q?remove:=20=EC=95=88=EC=93=B0=EB=8A=94=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/accessories.svg | 11 ---- src/assets/icons/chevron_right.svg | 3 - src/assets/icons/dropdown_up.svg | 3 - src/assets/react.svg | 1 - .../Combination/CombinationDetailTag.tsx | 30 --------- src/components/Combination/CombinationTag.tsx | 32 ---------- src/components/ProductCard/ProductLife.tsx | 13 ---- src/constants/storageKeys.ts | 3 - src/hooks/useRecentlyViewed.ts | 41 ------------ src/pages/support/CustomerCenterPage.tsx | 5 -- src/pages/support/FaqPage.tsx | 6 -- src/pages/support/NoticesPage.tsx | 6 -- src/pages/support/PrivacyPolicyPage.tsx | 6 -- src/pages/support/TermsPage.tsx | 6 -- src/routes/PublicRoutes.tsx | 12 ---- src/types/designToken.ts | 64 ------------------- src/utils/recentlyViewedStorage.ts | 53 --------------- 17 files changed, 295 deletions(-) delete mode 100644 src/assets/icons/accessories.svg delete mode 100644 src/assets/icons/chevron_right.svg delete mode 100644 src/assets/icons/dropdown_up.svg delete mode 100644 src/assets/react.svg delete mode 100644 src/components/Combination/CombinationDetailTag.tsx delete mode 100644 src/components/Combination/CombinationTag.tsx delete mode 100644 src/components/ProductCard/ProductLife.tsx delete mode 100644 src/constants/storageKeys.ts delete mode 100644 src/hooks/useRecentlyViewed.ts delete mode 100644 src/pages/support/CustomerCenterPage.tsx delete mode 100644 src/pages/support/FaqPage.tsx delete mode 100644 src/pages/support/NoticesPage.tsx delete mode 100644 src/pages/support/PrivacyPolicyPage.tsx delete mode 100644 src/pages/support/TermsPage.tsx delete mode 100644 src/types/designToken.ts delete mode 100644 src/utils/recentlyViewedStorage.ts diff --git a/src/assets/icons/accessories.svg b/src/assets/icons/accessories.svg deleted file mode 100644 index 42154299..00000000 --- a/src/assets/icons/accessories.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/assets/icons/chevron_right.svg b/src/assets/icons/chevron_right.svg deleted file mode 100644 index 782dd196..00000000 --- a/src/assets/icons/chevron_right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/icons/dropdown_up.svg b/src/assets/icons/dropdown_up.svg deleted file mode 100644 index 301d07fd..00000000 --- a/src/assets/icons/dropdown_up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9b..00000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/Combination/CombinationDetailTag.tsx b/src/components/Combination/CombinationDetailTag.tsx deleted file mode 100644 index 7df9d187..00000000 --- a/src/components/Combination/CombinationDetailTag.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { COMBINATION_NAME_STYLE_MAP, type CombinationName } from '@/constants/combination'; - -type CombinationDetailTagProps = { - name: CombinationName; - info: string; - className?: string; -}; - -const CombinationDetailTag = ({ name, info, className = '' }: CombinationDetailTagProps) => { - const infoText = name === '라이프스타일' ? `#${info}` : info; - return ( -
- {infoText} -
- ); -}; - -export default CombinationDetailTag; diff --git a/src/components/Combination/CombinationTag.tsx b/src/components/Combination/CombinationTag.tsx deleted file mode 100644 index 7b60b9ef..00000000 --- a/src/components/Combination/CombinationTag.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { - COMBINATION_NAME_STYLE_MAP, - COMBINATION_STATUS_STYLE_MAP, - type CombinationName, - type CombinationStatus, -} from '@/constants/combination'; - -type CombinationTagProps = { - name: CombinationName; - status: CombinationStatus; - className?: string; -}; - -const CombinationTag = ({ name, status, className = '' }: CombinationTagProps) => { - return ( - - {name}: - {status} - - ); -}; - -export default CombinationTag; diff --git a/src/components/ProductCard/ProductLife.tsx b/src/components/ProductCard/ProductLife.tsx deleted file mode 100644 index 45775024..00000000 --- a/src/components/ProductCard/ProductLife.tsx +++ /dev/null @@ -1,13 +0,0 @@ -type ProductLifeProps = { - label: string; -}; - -const ProductLife = ({ label }: ProductLifeProps) => { - return ( -
- #{label} -
- ); -}; - -export default ProductLife; diff --git a/src/constants/storageKeys.ts b/src/constants/storageKeys.ts deleted file mode 100644 index 76c4ce47..00000000 --- a/src/constants/storageKeys.ts +++ /dev/null @@ -1,3 +0,0 @@ -// localStorage 키 상수 -export const RECENTLY_VIEWED_DEVICES = 'RECENTLY_VIEWED_DEVICES'; -export const RECENTLY_VIEWED_MAX_COUNT = 10; diff --git a/src/hooks/useRecentlyViewed.ts b/src/hooks/useRecentlyViewed.ts deleted file mode 100644 index 2cda4785..00000000 --- a/src/hooks/useRecentlyViewed.ts +++ /dev/null @@ -1,41 +0,0 @@ -// import { useState, useEffect, useCallback } from 'react'; -// import type { RecentlyViewedDevice } from '@/types/recentlyViewed'; -// import { -// getRecentlyViewedDevices, -// addRecentlyViewedDevice, -// removeRecentlyViewedDevice, -// } from '@/utils/recentlyViewedStorage'; -// import { MOCK_RECENTLY_VIEWED_DEVICES } from '@/constants/mockData'; - -// export const useRecentlyViewed = () => { -// const [devices, setDevices] = useState([]); - -// // 초기 로드 (localStorage가 비어있으면 mockdata 사용) -// useEffect(() => { -// const storedDevices = getRecentlyViewedDevices(); -// if (storedDevices.length > 0) { -// setDevices(storedDevices); -// } else { -// setDevices(MOCK_RECENTLY_VIEWED_DEVICES); -// } -// }, []); - -// // 기기 추가 -// const addDevice = useCallback((device: Omit) => { -// addRecentlyViewedDevice(device); -// setDevices(getRecentlyViewedDevices()); -// }, []); - -// // 기기 제거 -// const removeDevice = useCallback((deviceId: number) => { -// removeRecentlyViewedDevice(deviceId); -// setDevices(getRecentlyViewedDevices()); -// }, []); - -// return { -// devices, -// addDevice, -// removeDevice, -// hasDevices: devices.length > 0, -// }; -// }; diff --git a/src/pages/support/CustomerCenterPage.tsx b/src/pages/support/CustomerCenterPage.tsx deleted file mode 100644 index b7b59bc5..00000000 --- a/src/pages/support/CustomerCenterPage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const CustomerCenterPage = () => { - return
CustomerCenterPage
; -}; - -export default CustomerCenterPage; diff --git a/src/pages/support/FaqPage.tsx b/src/pages/support/FaqPage.tsx deleted file mode 100644 index f0b4bbc6..00000000 --- a/src/pages/support/FaqPage.tsx +++ /dev/null @@ -1,6 +0,0 @@ -const FaqPage = () => { - return
FaqPage
; -}; - -export default FaqPage; - diff --git a/src/pages/support/NoticesPage.tsx b/src/pages/support/NoticesPage.tsx deleted file mode 100644 index 873d08f1..00000000 --- a/src/pages/support/NoticesPage.tsx +++ /dev/null @@ -1,6 +0,0 @@ -const NoticesPage = () => { - return
NoticesPage
; -}; - -export default NoticesPage; - diff --git a/src/pages/support/PrivacyPolicyPage.tsx b/src/pages/support/PrivacyPolicyPage.tsx deleted file mode 100644 index 9d34aede..00000000 --- a/src/pages/support/PrivacyPolicyPage.tsx +++ /dev/null @@ -1,6 +0,0 @@ -const PrivacyPolicyPage = () => { - return
PrivacyPolicyPage
; -}; - -export default PrivacyPolicyPage; - diff --git a/src/pages/support/TermsPage.tsx b/src/pages/support/TermsPage.tsx deleted file mode 100644 index d2e10aab..00000000 --- a/src/pages/support/TermsPage.tsx +++ /dev/null @@ -1,6 +0,0 @@ -const TermsPage = () => { - return
TermsPage
; -}; - -export default TermsPage; - diff --git a/src/routes/PublicRoutes.tsx b/src/routes/PublicRoutes.tsx index 0998e181..7bd97300 100644 --- a/src/routes/PublicRoutes.tsx +++ b/src/routes/PublicRoutes.tsx @@ -75,18 +75,6 @@ export const PublicRoutes = { ], }, - // 푸터 페이지들 - // { - // path: 'support', - // children: [ - // { path: 'customer-center', element: }, - // { path: 'faq', element: }, - // { path: 'notices', element: }, - // { path: 'terms', element: }, - // { path: 'privacy-policy', element: }, - // ], - // }, - // 전체 미매칭 fallback { path: '*', element: }, ], diff --git a/src/types/designToken.ts b/src/types/designToken.ts deleted file mode 100644 index fd4c1c84..00000000 --- a/src/types/designToken.ts +++ /dev/null @@ -1,64 +0,0 @@ -export type ColorToken = - /* Main Color */ - | 'blue-100' - | 'blue-200' - | 'blue-300' - | 'blue-400' - | 'blue-500' - | 'blue-600' - | 'blue-700' - | 'blue-800' - | 'blue-900' - - /* gray scale */ - | 'white' - | 'gray-100' - | 'gray-200' - | 'gray-300' - | 'gray-400' - | 'gray-500' - | 'black' - | 'black-50' - - /* Warning */ - | 'warning' - - /* sub1 (tag color) */ - | 'light-green' - | 'dark-green' - | 'light-yellow' - | 'dark-yellow' - | 'light-purple' - | 'dark-purple' - - /* sub2 (tag status color) */ - | 'optimal' - | 'normal' - | 'poor'; - -export type TypographyToken = - /* Service Name */ - | 'font-service-name' - | 'font-service-name-sm' - - /* Heading */ - | 'font-heading-1' - | 'font-heading-2' - | 'font-heading-3' - | 'font-heading-4' - - /* Body (semibold) */ - | 'font-body-1-sm' - | 'font-body-2-sm' - | 'font-body-3-sm' - | 'font-body-4-sm' - - /* body (regular) */ - | 'font-body-1-r' - | 'font-body-2-r' - | 'font-body-3-r' - | 'font-body-4-r' - - /* Caption */ - | 'font-caption-sm' - | 'font-caption-r'; diff --git a/src/utils/recentlyViewedStorage.ts b/src/utils/recentlyViewedStorage.ts deleted file mode 100644 index 805ed515..00000000 --- a/src/utils/recentlyViewedStorage.ts +++ /dev/null @@ -1,53 +0,0 @@ -// import { RECENTLY_VIEWED_DEVICES, RECENTLY_VIEWED_MAX_COUNT } from '@/constants/storageKeys'; -// import type { RecentlyViewedDevice } from '@/types/recentlyViewed/recentlyViewed'; - -// // localStorage를 조작하는 유틸리티 함수 -// // 나머지 파일에서는 localStorage를 직접 사용하지 않고 이 파일의 함수를 사용하도록 함 - -// // 최근 본 기기 목록 가져오기 -// export const getRecentlyViewedDevices = (): RecentlyViewedDevice[] => { -// try { -// const data = localStorage.getItem(RECENTLY_VIEWED_DEVICES); -// if (!data) return []; -// return JSON.parse(data) as RecentlyViewedDevice[]; -// } catch { -// return []; -// } -// }; - -// // 기기 추가 (가장 최근 것이 맨 앞, 중복 제거, 최대 개수 제한) -// export const addRecentlyViewedDevice = ( -// device: Omit -// ): void => { -// const devices = getRecentlyViewedDevices(); - -// // 이미 있는 기기는 제거 (중복 방지) -// const filteredDevices = devices.filter((d) => d.id !== device.id); - -// // 새 기기를 맨 앞에 추가 -// const newDevice: RecentlyViewedDevice = { -// ...device, -// viewedAt: Date.now(), -// }; - -// const updatedDevices = [newDevice, ...filteredDevices].slice(0, RECENTLY_VIEWED_MAX_COUNT); - -// localStorage.setItem(RECENTLY_VIEWED_DEVICES, JSON.stringify(updatedDevices)); -// }; - -// // 특정 기기 삭제 -// export const removeRecentlyViewedDevice = (deviceId: number): void => { -// const devices = getRecentlyViewedDevices(); -// const filteredDevices = devices.filter((d) => d.id !== deviceId); -// localStorage.setItem(RECENTLY_VIEWED_DEVICES, JSON.stringify(filteredDevices)); -// }; - -// // 전체 삭제 -// export const clearRecentlyViewedDevices = (): void => { -// localStorage.removeItem(RECENTLY_VIEWED_DEVICES); -// }; - -// // 최근 본 기기 존재 여부 -// export const hasRecentlyViewedDevices = (): boolean => { -// return getRecentlyViewedDevices().length > 0; -// }; From c10f71e31783d745c7a44b2e0a907ded1f045851 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 02:17:08 +0900 Subject: [PATCH 02/17] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=82=AC=EC=9A=A9=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useAuth.ts | 2 +- src/layouts/RootLayout.tsx | 2 +- src/pages/devices/DeviceSearchPage.tsx | 15 ++++++--------- .../onboarding/OnboardingCombinationPage.tsx | 8 ++++---- src/pages/onboarding/OnboardingCompletePage.tsx | 16 ++++++++-------- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 0cde65aa..86f9e1bd 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -6,7 +6,7 @@ import type { UserProfileResult } from '@/types/mypage/user'; export type UserProfile = UserProfileResult; /* -인증 상태를 관리하는 훅 +인증 상태를 관리하는 훅 (RootLayout에서 트리거한 user profile 쿼리 구독) 로그인 여부 판단 로직: - 토큰이 있고 userProfile이 있을 때만 → 로그인 상태 diff --git a/src/layouts/RootLayout.tsx b/src/layouts/RootLayout.tsx index 52b95444..39c38c16 100644 --- a/src/layouts/RootLayout.tsx +++ b/src/layouts/RootLayout.tsx @@ -3,7 +3,7 @@ import { Outlet } from 'react-router-dom'; import { useGetUserProfile } from '@/apis/mypage/getUserProfile'; const RootLayout = () => { - // 토큰이 있을 때만 유저 정보 자동 조회 + // 레이아웃에서 유저 프로필 조회 트리거 (나머지 컴포넌트는 useAuth로 구독) useGetUserProfile(); return ( diff --git a/src/pages/devices/DeviceSearchPage.tsx b/src/pages/devices/DeviceSearchPage.tsx index ed978b68..835212a6 100644 --- a/src/pages/devices/DeviceSearchPage.tsx +++ b/src/pages/devices/DeviceSearchPage.tsx @@ -29,9 +29,9 @@ import { useIntersectionObserver } from '@/hooks/useIntersectionObserver'; import { useGetCombos } from '@/apis/combo/getCombos'; import { useGetCombo } from '@/apis/combo/getComboId'; import { usePostComboDevice } from '@/apis/combo/postComboDevices'; -import { useGetUserProfile } from '@/apis/mypage/getUserProfile'; +import { useAuth } from '@/hooks/useAuth'; import { useGetBrands } from '@/apis/devices/getBrands'; -import { hasAccessToken, hasCompletedOnboarding } from '@/utils/authStorage'; +import { hasCompletedOnboarding } from '@/utils/authStorage'; // 카테고리 ID를 API deviceType으로 변환 const getCategoryDeviceType = (categoryId: number | null): string | undefined => { @@ -85,14 +85,11 @@ const DeviceSearchPage = () => { const selectedProductId = searchParams.get('productId'); const navigate = useNavigate(); - // 로그인 상태 확인 - const isLoggedIn = hasAccessToken(); - - // 사용자 프로필 조회 (로그인 시에만 자동 실행) - const { data: userProfile, isLoading: isProfileLoading } = useGetUserProfile(); + // 인증 상태 (RootLayout에서 트리거, 여기서는 구독) + const { isLoggedIn, user, isAuthLoading } = useAuth(); // 온보딩 완료 여부 확인 (로딩 중에는 false로 기본 처리) - const hasOnboarding = isProfileLoading ? false : hasCompletedOnboarding(userProfile); + const hasOnboarding = isAuthLoading ? false : hasCompletedOnboarding(user ?? undefined); const [modalView, setModalView] = useState('device'); @@ -628,7 +625,7 @@ const DeviceSearchPage = () => { diff --git a/src/pages/onboarding/OnboardingCombinationPage.tsx b/src/pages/onboarding/OnboardingCombinationPage.tsx index 730f6c16..68d9345f 100644 --- a/src/pages/onboarding/OnboardingCombinationPage.tsx +++ b/src/pages/onboarding/OnboardingCombinationPage.tsx @@ -11,14 +11,14 @@ import { } from '@/schemas/authSchema'; import { ROUTES } from '@/constants/routes'; import { usePostCreateCombination } from '@/apis/combo/postCreateCombination'; -import { useGetUserProfile } from '@/apis/mypage/getUserProfile'; +import { useAuth } from '@/hooks/useAuth'; const OnboardingCombinationPage = () => { const [step, setStep] = useState(1); const [combinationName, setCombinationName] = useState(''); const navigate = useNavigate(); const { mutateAsync: createCombo, isPending } = usePostCreateCombination(); - const { data: userProfile, isLoading: isProfileLoading } = useGetUserProfile(); + const { user, isAuthLoading } = useAuth(); const { register, @@ -30,12 +30,12 @@ const OnboardingCombinationPage = () => { }); // 프로필 초기 로딩 중이면 로딩 스피너 표시 - if (isProfileLoading) { + if (isAuthLoading) { return ; } // 라이프스타일 태그가 없으면 라이프스타일 선택 페이지로 리다이렉트 - if (!userProfile?.lifestyleList?.length) { + if (!user?.lifestyleList?.length) { return ; } diff --git a/src/pages/onboarding/OnboardingCompletePage.tsx b/src/pages/onboarding/OnboardingCompletePage.tsx index 859fd5f6..690f9b8a 100644 --- a/src/pages/onboarding/OnboardingCompletePage.tsx +++ b/src/pages/onboarding/OnboardingCompletePage.tsx @@ -2,21 +2,21 @@ import { useEffect, useState } from 'react'; import { useNavigate, Navigate } from 'react-router-dom'; import OnboardingLines from '@/assets/icons/onboarding_lines.svg?react'; import LoadingSpinner from '@/components/LoadingSpinner'; -import { useGetUserProfile } from '@/apis/mypage/getUserProfile'; +import { useAuth } from '@/hooks/useAuth'; import { usePostOnboardingComplete } from '@/apis/onboarding/postComplete'; import { ROUTES } from '@/constants/routes'; const OnboardingCompletePage = () => { const navigate = useNavigate(); - const { data: userProfile, isLoading: isProfileLoading } = useGetUserProfile(); + const { user, isAuthLoading } = useAuth(); const { mutateAsync: completeOnboarding } = usePostOnboardingComplete(); const [isCompleted, setIsCompleted] = useState(false); - const userName = userProfile?.username ?? ''; + const userName = user?.username ?? ''; - // 검증 조건(온보딩 과정 스킵하고 바로 들어오는 유저 대비비) - const isAlreadyCompleted = userProfile?.isOnboardingCompleted; - const hasNoLifestyleTags = !userProfile?.lifestyleList?.length; - const shouldSkipApiCall = isProfileLoading || isAlreadyCompleted || hasNoLifestyleTags; + // 검증 조건(온보딩 과정 스킵하고 바로 들어오는 유저 대비) + const isAlreadyCompleted = user?.isOnboardingCompleted; + const hasNoLifestyleTags = !user?.lifestyleList?.length; + const shouldSkipApiCall = isAuthLoading || isAlreadyCompleted || hasNoLifestyleTags; // 페이지 진입 시 온보딩 완료 API 호출 (검증 통과 시에만) useEffect(() => { @@ -46,7 +46,7 @@ const OnboardingCompletePage = () => { }, [isCompleted, navigate]); // 프로필 로딩 중이면 로딩 스피너 표시 - if (isProfileLoading) { + if (isAuthLoading) { return ; } From 93acf5dc70778b53f23b8f05b8089ac939b900e6 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 03:04:58 +0900 Subject: [PATCH 03/17] =?UTF-8?q?refactor:=20useAuth=EB=A1=9C=20=EC=8B=A4?= =?UTF-8?q?=EC=A0=9C=20=20=EC=98=A8=EB=B3=B4=EB=94=A9=20=EC=9C=A0=EB=AC=B4?= =?UTF-8?q?=20=EC=A0=9C=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useAuth.ts | 5 +++++ src/pages/devices/DeviceSearchPage.tsx | 7 +++---- src/routes/guards/OnboardingCompletedGuard.tsx | 4 ++-- src/routes/guards/OnboardingOnlyGuard.tsx | 4 ++-- src/utils/authStorage.ts | 9 --------- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 86f9e1bd..fc935992 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -16,6 +16,7 @@ export type UserProfile = UserProfileResult; - isLoggedIn: 로그인 여부 (토큰 + user가 모두 있을 때만 true) - user: 유저 객체 (없으면 null) - isAuthLoading: 인증 로딩 상태 (토큰은 있는데 me를 아직 못 받아온 상태) + - hasCompletedOnboarding: 온보딩 완료 여부 (API의 isOnboardingCompleted 사용) */ export const useAuth = () => { @@ -32,11 +33,15 @@ export const useAuth = () => { // 토큰이 있고 userProfile이 있을 때만 로그인 상태 const isLoggedIn = hasToken && !!user; + // 온보딩 완료 여부 (API의 isOnboardingCompleted 사용) + const hasCompletedOnboarding = !!user?.isOnboardingCompleted; + return { isLoggedIn, user: user ?? null, isAuthLoading, hasToken, + hasCompletedOnboarding, refetchUserProfile: refetch, }; }; diff --git a/src/pages/devices/DeviceSearchPage.tsx b/src/pages/devices/DeviceSearchPage.tsx index 835212a6..e325bf4d 100644 --- a/src/pages/devices/DeviceSearchPage.tsx +++ b/src/pages/devices/DeviceSearchPage.tsx @@ -31,7 +31,6 @@ import { useGetCombo } from '@/apis/combo/getComboId'; import { usePostComboDevice } from '@/apis/combo/postComboDevices'; import { useAuth } from '@/hooks/useAuth'; import { useGetBrands } from '@/apis/devices/getBrands'; -import { hasCompletedOnboarding } from '@/utils/authStorage'; // 카테고리 ID를 API deviceType으로 변환 const getCategoryDeviceType = (categoryId: number | null): string | undefined => { @@ -86,10 +85,10 @@ const DeviceSearchPage = () => { const navigate = useNavigate(); // 인증 상태 (RootLayout에서 트리거, 여기서는 구독) - const { isLoggedIn, user, isAuthLoading } = useAuth(); + const { isLoggedIn, isAuthLoading, hasCompletedOnboarding } = useAuth(); - // 온보딩 완료 여부 확인 (로딩 중에는 false로 기본 처리) - const hasOnboarding = isAuthLoading ? false : hasCompletedOnboarding(user ?? undefined); + // 온보딩 완료 여부 (로딩 중에는 false로 기본 처리) + const hasOnboarding = isAuthLoading ? false : hasCompletedOnboarding; const [modalView, setModalView] = useState('device'); diff --git a/src/routes/guards/OnboardingCompletedGuard.tsx b/src/routes/guards/OnboardingCompletedGuard.tsx index 209ca445..28cea6dc 100644 --- a/src/routes/guards/OnboardingCompletedGuard.tsx +++ b/src/routes/guards/OnboardingCompletedGuard.tsx @@ -9,13 +9,13 @@ import { ROUTES } from '@/constants/routes'; - 온보딩 미완료 시 온보딩 페이지로 리다이렉트 */ export const OnboardingCompletedGuard = () => { - const { user, isAuthLoading } = useAuth(); + const { isAuthLoading, hasCompletedOnboarding } = useAuth(); if (isAuthLoading) { return ; } - if (!user?.isOnboardingCompleted) { + if (!hasCompletedOnboarding) { alert('온보딩을 완료한 후 이용해주세요.'); return ; } diff --git a/src/routes/guards/OnboardingOnlyGuard.tsx b/src/routes/guards/OnboardingOnlyGuard.tsx index 819b417b..c2810375 100644 --- a/src/routes/guards/OnboardingOnlyGuard.tsx +++ b/src/routes/guards/OnboardingOnlyGuard.tsx @@ -9,13 +9,13 @@ import { ROUTES } from '@/constants/routes'; - 온보딩 완료 시 홈으로 리다이렉트 */ export const OnboardingOnlyGuard = () => { - const { user, isAuthLoading } = useAuth(); + const { isAuthLoading, hasCompletedOnboarding } = useAuth(); if (isAuthLoading) { return ; } - if (user?.isOnboardingCompleted) { + if (hasCompletedOnboarding) { alert('온보딩이 이미 완료되었습니다.'); return ; } diff --git a/src/utils/authStorage.ts b/src/utils/authStorage.ts index ef4f6de7..c4cff1a1 100644 --- a/src/utils/authStorage.ts +++ b/src/utils/authStorage.ts @@ -1,5 +1,4 @@ import { ACCESS_TOKEN, AUTH_STORAGE } from '@/constants/tokenKey'; -import type { UserProfileResult } from '@/types/mypage/user'; // 저장소 타입: local(영속) | session(세션) export type AuthStorageType = 'local' | 'session'; @@ -63,11 +62,3 @@ export const hasAccessToken = (): boolean => { const accessToken = getAccessToken(); return !!accessToken; }; - -// 온보딩 완료 여부 확인 함수 -// lifestyleList가 비어있지 않으면 온보딩 완료로 판단 -export const hasCompletedOnboarding = ( - userProfile: UserProfileResult | undefined -): boolean => { - return !!(userProfile?.lifestyleList && userProfile.lifestyleList.length > 0); -}; From 255c046455dca7528fe2c16409714c6a08f1a65a Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 13:53:06 +0900 Subject: [PATCH 04/17] =?UTF-8?q?design:=20=EA=B5=AC=EA=B8=80=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B2=84=ED=8A=BC=EC=97=90=20hover?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Button/GoogleLoginButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Button/GoogleLoginButton.tsx b/src/components/Button/GoogleLoginButton.tsx index 1dd814de..fc6ada27 100644 --- a/src/components/Button/GoogleLoginButton.tsx +++ b/src/components/Button/GoogleLoginButton.tsx @@ -24,7 +24,7 @@ const GoogleLoginButton = ({ onClick, className }: GoogleLoginButtonProps) => { className={clsx( 'flex items-center gap-24', 'py-8 pl-0 pr-8', - 'bg-white cursor-pointer', + 'bg-white cursor-pointer hover:bg-gray-100', className )} style={{ boxShadow: '0 0 4px 0 rgba(0, 0, 0, 0.20)' }} From ba3e788e3a35e9550d33ebabf321049e793e63f2 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 14:23:09 +0900 Subject: [PATCH 05/17] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=9C=A0=EC=A0=80=EA=B0=80=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B0=A9=EB=AC=B8=20?= =?UTF-8?q?=EC=8B=9C=20=ED=99=88=EC=9C=BC=EB=A1=9C=20=EB=A6=AC=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EB=A0=89=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/LoginPage.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx index 09c2e134..5d68c96b 100644 --- a/src/pages/auth/LoginPage.tsx +++ b/src/pages/auth/LoginPage.tsx @@ -6,10 +6,11 @@ import PrimaryInput from '@/components/Input/PrimaryInput'; import PrimaryButton from '@/components/Button/PrimaryButton'; import Checkbox from '@/assets/icons/checkbox.svg?react'; import CheckboxOn from '@/assets/icons/checkbox_on.svg?react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { useNavigate, useLocation, Navigate } from 'react-router-dom'; import { ROUTES } from '@/constants/routes'; import GoogleLoginButton from '@/components/Button/GoogleLoginButton'; import { useLogin } from '@/hooks/useLogin'; +import { useAuth } from '@/hooks/useAuth'; // 라우터 state 타입 (아이디 찾기에서 넘어올 때) type LoginPageState = { @@ -19,6 +20,7 @@ type LoginPageState = { const LoginPage = () => { const navigate = useNavigate(); const location = useLocation(); + const { isLoggedIn } = useAuth(); const [keepLogin, setKeepLogin] = useState(false); const [isCapsLockOn, setIsCapsLockOn] = useState(false); const [loginError, setLoginError] = useState(''); @@ -45,6 +47,11 @@ const LoginPage = () => { // 로그인 훅 const { loginAndFinalize, isPending } = useLogin(); + // 로그인된 상태에서 로그인 페이지 접근 시 홈으로 리다이렉트 + if (isLoggedIn) { + return ; + } + // 로그인 제출 핸들러 const onSubmit = async (data: LoginFormData) => { setLoginError(''); From bf5516699b05f9405e28f2918db193d209987c37 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 14:51:51 +0900 Subject: [PATCH 06/17] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=A7=81=ED=9B=84=20=EC=98=A8=EB=B3=B4=EB=94=A9=20=EC=9C=A0?= =?UTF-8?q?=EB=AC=B4=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=A6=AC=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EB=A0=89=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/LoginPage.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx index 5d68c96b..24995b57 100644 --- a/src/pages/auth/LoginPage.tsx +++ b/src/pages/auth/LoginPage.tsx @@ -20,7 +20,7 @@ type LoginPageState = { const LoginPage = () => { const navigate = useNavigate(); const location = useLocation(); - const { isLoggedIn } = useAuth(); + const { isLoggedIn, hasCompletedOnboarding } = useAuth(); const [keepLogin, setKeepLogin] = useState(false); const [isCapsLockOn, setIsCapsLockOn] = useState(false); const [loginError, setLoginError] = useState(''); @@ -47,9 +47,12 @@ const LoginPage = () => { // 로그인 훅 const { loginAndFinalize, isPending } = useLogin(); - // 로그인된 상태에서 로그인 페이지 접근 시 홈으로 리다이렉트 + // 로그인된 상태에서 로그인 페이지 접근 시 리다이렉트 if (isLoggedIn) { - return ; + const destination = hasCompletedOnboarding + ? ROUTES.home + : ROUTES.onboarding.lifestyle; + return ; } // 로그인 제출 핸들러 @@ -62,9 +65,7 @@ const LoginPage = () => { password: data.password, keepLogin, }); - - // 로그인 성공 시 라우팅 - navigate(ROUTES.home, { replace: true }); + // 캐시 업데이트 → 리렌더링 → isLoggedIn 가드가 자동으로 라우팅 처리 } catch (error) { // 로그인 실패 시 에러 메시지 표시 setLoginError('아이디 또는 비밀번호를 확인해주세요.'); From 9a794a839701f5ab832abffb439625b407e40be8 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 14:54:10 +0900 Subject: [PATCH 07/17] =?UTF-8?q?refactor:=20=EB=A6=AC=EC=95=A1=ED=8A=B8?= =?UTF-8?q?=20=ED=9B=85=20=EA=B7=9C=EC=B9=99=20=EC=A7=80=EC=BC=9C=EC=84=9C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignupAccountPage.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/auth/SignupAccountPage.tsx b/src/pages/auth/SignupAccountPage.tsx index 9d2e7a21..8202ba3d 100644 --- a/src/pages/auth/SignupAccountPage.tsx +++ b/src/pages/auth/SignupAccountPage.tsx @@ -16,11 +16,6 @@ import { useAuth } from '@/hooks/useAuth'; const SignupAccountPage = () => { const navigate = useNavigate(); const { isLoggedIn } = useAuth(); - - // 로그인된 상태에서 회원가입 페이지 접근 시 홈으로 리다이렉트 - if (isLoggedIn) { - return ; - } const [hasSubmitted, setHasSubmitted] = useState(false); const [hasEmailSubmitted, setHasEmailSubmitted] = useState(false); @@ -47,6 +42,11 @@ const SignupAccountPage = () => { reValidateMode: 'onChange', }); + // 로그인된 상태에서 회원가입 페이지 접근 시 홈으로 리다이렉트 + if (isLoggedIn) { + return ; + } + // 이메일 중복확인 핸들러 const handleCheckDuplicate = async () => { setHasEmailSubmitted(true); From c44dde3a6b14f702e2a1f34173ca957e4857f598 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 14:57:41 +0900 Subject: [PATCH 08/17] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=A0=84=EC=97=AD=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignupProfilePage.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/pages/auth/SignupProfilePage.tsx b/src/pages/auth/SignupProfilePage.tsx index 5fdf547a..6674890c 100644 --- a/src/pages/auth/SignupProfilePage.tsx +++ b/src/pages/auth/SignupProfilePage.tsx @@ -16,7 +16,7 @@ import { useAuth } from '@/hooks/useAuth'; const SignupProfilePage = () => { const navigate = useNavigate(); const [hasSubmitted, setHasSubmitted] = useState(false); - const { account, setProfile } = useSignupStore(); + const { account, resetSignup } = useSignupStore(); const { mutateAsync: signup } = usePostJoin(); const { loginAndFinalize } = useLogin(); const { isLoggedIn } = useAuth(); @@ -50,12 +50,6 @@ const SignupProfilePage = () => { const onSubmitValid = async (data: SignupProfileFormData) => { setHasSubmitted(true); - // zustand에 프로필 정보 저장 - setProfile({ - username: data.name, - phoneNumber: data.phone, - }); - // 회원가입 API 호출 try { await signup({ @@ -73,10 +67,14 @@ const SignupProfilePage = () => { keepLogin: false, }); + // 회원가입 성공 → zustand 초기화 (이메일/비밀번호 메모리 정리) + resetSignup(); + // 로그인 성공 시 온보딩으로 이동 navigate(ROUTES.onboarding.lifestyle, { replace: true }); } catch (loginError) { - // 로그인 실패 시 알림 + // 자동 로그인 실패해도 회원가입은 완료 → zustand 초기화 + resetSignup(); alert('회원가입은 완료되었지만 자동 로그인에 실패했습니다. 로그인 페이지에서 다시 시도해주세요.'); navigate(ROUTES.auth.login, { replace: true }); } From ced86fe15e0ea2f2ac328ddab114341545094cf2 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 15:06:35 +0900 Subject: [PATCH 09/17] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/onboarding/OnboardingCompletePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/onboarding/OnboardingCompletePage.tsx b/src/pages/onboarding/OnboardingCompletePage.tsx index 690f9b8a..bcc62050 100644 --- a/src/pages/onboarding/OnboardingCompletePage.tsx +++ b/src/pages/onboarding/OnboardingCompletePage.tsx @@ -32,7 +32,7 @@ const OnboardingCompletePage = () => { } }; complete(); - }, [shouldSkipApiCall, completeOnboarding, navigate]); + }, [shouldSkipApiCall, navigate]); // 온보딩 완료 후 5초 뒤 추천 페이지로 이동 useEffect(() => { From b4009baad9b13dc09fc120765ba3a8b77767a28b Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 15:13:58 +0900 Subject: [PATCH 10/17] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EC=9D=B8=EC=A6=9D=EC=83=81=ED=83=9C=20=ED=95=9C=20=EA=B3=B3?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignupAccountPage.tsx | 3 +-- src/pages/auth/SignupProfilePage.tsx | 4 ++-- src/stores/signupStore.ts | 5 +---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/auth/SignupAccountPage.tsx b/src/pages/auth/SignupAccountPage.tsx index 8202ba3d..21763950 100644 --- a/src/pages/auth/SignupAccountPage.tsx +++ b/src/pages/auth/SignupAccountPage.tsx @@ -90,11 +90,10 @@ const SignupAccountPage = () => { return; } - // zustand에 계정 정보 + 중복확인 여부 저장 (API 호출 시 한 번에 사용) + // zustand에 계정 정보 저장 (API 호출 시 한 번에 사용) setAccount({ email: data.email, password: data.password, - isEmailVerified: true, }); // 프로필 페이지로 이동 navigate(ROUTES.auth.signup.profile); diff --git a/src/pages/auth/SignupProfilePage.tsx b/src/pages/auth/SignupProfilePage.tsx index 6674890c..b0b2199c 100644 --- a/src/pages/auth/SignupProfilePage.tsx +++ b/src/pages/auth/SignupProfilePage.tsx @@ -16,7 +16,7 @@ import { useAuth } from '@/hooks/useAuth'; const SignupProfilePage = () => { const navigate = useNavigate(); const [hasSubmitted, setHasSubmitted] = useState(false); - const { account, resetSignup } = useSignupStore(); + const { account, isEmailVerified, resetSignup } = useSignupStore(); const { mutateAsync: signup } = usePostJoin(); const { loginAndFinalize } = useLogin(); const { isLoggedIn } = useAuth(); @@ -27,7 +27,7 @@ const SignupProfilePage = () => { } // 이메일, 비밀번호, 중복확인이 모두 완료되었는지 확인 - const isAccountComplete = account.email && account.password && account.isEmailVerified; + const isAccountComplete = account.email && account.password && isEmailVerified; // 하나라도 빠지면 계정 페이지로 리다이렉트 if (!isAccountComplete) { diff --git a/src/stores/signupStore.ts b/src/stores/signupStore.ts index 2693be32..9932183f 100644 --- a/src/stores/signupStore.ts +++ b/src/stores/signupStore.ts @@ -3,7 +3,6 @@ import { create } from 'zustand'; type SignupAccountState = { email: string; password: string; - isEmailVerified: boolean; }; type SignupProfileState = { @@ -25,7 +24,6 @@ export const useSignupStore = create((set) => ({ account: { email: '', password: '', - isEmailVerified: false, }, profile: { username: '', @@ -36,7 +34,6 @@ export const useSignupStore = create((set) => ({ setAccount: (account) => set(() => ({ account, - isEmailVerified: account.isEmailVerified, })), setProfile: (profile) => @@ -51,7 +48,7 @@ export const useSignupStore = create((set) => ({ resetSignup: () => set(() => ({ - account: { email: '', password: '', isEmailVerified: false }, + account: { email: '', password: '' }, profile: { username: '', phoneNumber: '' }, isEmailVerified: false, })), From 48546679a126d7008c8c61f39ea59eeff2584d33 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 16:15:50 +0900 Subject: [PATCH 11/17] =?UTF-8?q?refactor:=20=EB=A6=AC=EC=95=A1=ED=8A=B8?= =?UTF-8?q?=20=ED=9B=85=20=EA=B7=9C=EC=B9=99=20=EC=A7=80=EC=BC=9C=EC=84=9C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignupProfilePage.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/auth/SignupProfilePage.tsx b/src/pages/auth/SignupProfilePage.tsx index b0b2199c..b39256e7 100644 --- a/src/pages/auth/SignupProfilePage.tsx +++ b/src/pages/auth/SignupProfilePage.tsx @@ -21,6 +21,18 @@ const SignupProfilePage = () => { const { loginAndFinalize } = useLogin(); const { isLoggedIn } = useAuth(); + // 프로필 정보 입력 폼 상태 관리 + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(signupProfileSchema), + // 최초에는 에러를 숨기고, submit 이후에는 onChange로 실시간 갱신되도록 + mode: 'onChange', + reValidateMode: 'onChange', + }); + // 로그인된 상태에서 회원가입 페이지 접근 시 홈으로 리다이렉트 if (isLoggedIn) { return ; @@ -34,18 +46,6 @@ const SignupProfilePage = () => { return ; } - // 프로필 정보 입력 폼 상태 관리 - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: zodResolver(signupProfileSchema), - // 최초에는 에러를 숨기고, submit 이후에는 onChange로 실시간 갱신되도록 - mode: 'onChange', - reValidateMode: 'onChange', - }); - // 프로필 정보 제출 성공 핸들러 const onSubmitValid = async (data: SignupProfileFormData) => { setHasSubmitted(true); From 9465fd76d151fe63328ef27769db55c592272bb8 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 16:21:03 +0900 Subject: [PATCH 12/17] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=9C=A0=ED=8B=B8=ED=95=A8=EC=88=98=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/FindPasswordPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/auth/FindPasswordPage.tsx b/src/pages/auth/FindPasswordPage.tsx index 98d095d5..f140252c 100644 --- a/src/pages/auth/FindPasswordPage.tsx +++ b/src/pages/auth/FindPasswordPage.tsx @@ -115,9 +115,9 @@ const FindPasswordPage = () => { setStep(3); } } catch (error: unknown) { - const axiosError = error as { response?: { data?: { message?: string } } }; - if (axiosError.response) { - setVerifyError(axiosError.response.data?.message ?? '인증에 실패했습니다. 다시 시도해 주세요.'); + const { hasResponse, message } = parseApiError(error); + if (hasResponse) { + setVerifyError(message ?? '인증에 실패했습니다. 다시 시도해 주세요.'); return; } alert('오류가 발생했습니다. 다시 시도해 주세요.'); From 85ae2ef0e060fc508be34ea27c9f013d2f43b3b0 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 17:10:01 +0900 Subject: [PATCH 13/17] =?UTF-8?q?fix:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B0=BE=EA=B8=B0=20=EB=A9=94=EC=9D=BC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/findCredential/postFindPassword.ts | 3 +-- src/pages/auth/FindIdResultPage.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/apis/findCredential/postFindPassword.ts b/src/apis/findCredential/postFindPassword.ts index af161f87..12e6a135 100644 --- a/src/apis/findCredential/postFindPassword.ts +++ b/src/apis/findCredential/postFindPassword.ts @@ -1,4 +1,3 @@ -import { axiosInstance } from '@/apis/axios/axios'; import { cookieAxiosInstance } from '@/apis/axios/cookieAxios'; import type { SendMailRequest, @@ -14,7 +13,7 @@ import { useMutation } from '@tanstack/react-query'; export const postSendMail = async ( payload: SendMailRequest ): Promise => { - const { data } = await axiosInstance.post( + const { data } = await cookieAxiosInstance.post( '/api/find-credential/find-password/send-mail', payload ); diff --git a/src/pages/auth/FindIdResultPage.tsx b/src/pages/auth/FindIdResultPage.tsx index 0506923d..aaec1180 100644 --- a/src/pages/auth/FindIdResultPage.tsx +++ b/src/pages/auth/FindIdResultPage.tsx @@ -55,7 +55,7 @@ const FindIdResultPage = () => { 비밀번호가 생각나지 않으신가요? - ); - })} - - - - {/* Divider */} -
- - {/* Filter Section */} -
- - {/* Filters */} -
- {/* Filter Icon */} - - - {/* Price Filter */} -
- search.setSelectedPrice(Array.isArray(value) ? value : [])} - multiple - /> -
- - {/* Brand Filter */} -
- search.setSelectedBrand(value as string | null)} - /> -
-
- -
- {/* Left side - Result count */} -
-

{search.allDevices.length}

-

개 결과

-
- - {/* Right side - Sort dropdown */} - -
-
- - {/* Product Grid */} -
- {/* 초기 로딩: 데이터가 없고 로딩 중일 때만 로딩 스피너 표시 */} - {search.isSearchLoading && search.allDevices.length === 0 ? ( - - ) : search.isSearchError && search.allDevices.length === 0 ? ( -
-

검색 결과를 불러오는데 실패했습니다.

-
- ) : search.allDevices.length === 0 ? ( -
-

검색 결과가 없습니다.

-
- ) : ( -
- {search.allDevices.map((device) => ( - { - searchParams.set('productId', device.deviceId.toString()); - setSearchParams(searchParams); - }} - /> - ))} -
- )} - - {/* 무한 스크롤 트리거 */} -
- - {/* 로딩 인디케이터 */} - {search.isFetchingNextPage && ( -
-

더 불러오는 중...

-
- )} -
- - {/* Top Button - 3행이 보일 때만 표시 */} - {scroll.showTopButton && ( - - )} - - {/* Bottom Spacing */} -
- {/* Device Detail Modal */} - {selectedProduct && !combo.showSaveCompleteModal && ( - <> - {/* Background Overlay - HomeIndicator보다 높게 설정 */} -
- - {/* Modal */} -
- {combo.modalView === 'device' && ( - - )} - - {combo.modalView === 'combination' && ( - combo.setModalView('device')} - onClose={combo.handleCloseModal} - /> - )} - - {combo.modalView === 'combinationDetail' && combo.selectedCombination && ( - c.comboId === combo.selectedCombinationId)} - showAllDevices={combo.showAllDevices} - onExpandChange={combo.setShowAllDevices} - isAlreadyInCombination={combo.isAlreadyInSelectedCombination} - isAddingDevice={combo.isAddingDevice} - onAddDevice={combo.handleAddDeviceToCombination} - onBack={() => combo.setModalView('combination')} - onClose={combo.handleCloseModal} - /> - )} -
- - )} - - {/* 저장 완료 모달 - 독립적으로 표시 */} - {combo.showSaveCompleteModal && ( - - )} -
- ); -}; - -export default DeviceSearchPage; From 7e4c03290309fec5c0ca1aa741df6bc357da6e4e Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 18:53:02 +0900 Subject: [PATCH 15/17] =?UTF-8?q?fix:=20=EC=B6=A9=EB=8F=8C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useAddToCombination.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/hooks/useAddToCombination.ts b/src/hooks/useAddToCombination.ts index 995cb6d1..880b793c 100644 --- a/src/hooks/useAddToCombination.ts +++ b/src/hooks/useAddToCombination.ts @@ -5,9 +5,8 @@ import { ROUTES } from '@/constants/routes'; import { useGetCombos } from '@/apis/combo/getCombos'; import { useGetCombo } from '@/apis/combo/getComboId'; import { usePostComboDevice } from '@/apis/combo/postComboDevices'; -import { useGetUserProfile } from '@/apis/mypage/getUserProfile'; import { usePostRecentlyViewed } from '@/apis/recentlyViewed/postRecentlyViewed'; -import { hasAccessToken, hasCompletedOnboarding } from '@/utils/authStorage'; +import { useAuth } from '@/hooks/useAuth'; interface UseAddToCombinationParams { selectedProductId: string | null; @@ -20,14 +19,11 @@ export const useAddToCombination = ({ }: UseAddToCombinationParams) => { const navigate = useNavigate(); - // 로그인 상태 확인 - const isLoggedIn = hasAccessToken(); + // 인증 상태 확인 + const { isLoggedIn, hasCompletedOnboarding, isAuthLoading } = useAuth(); - // 사용자 프로필 조회 (로그인 시에만 자동 실행) - const { data: userProfile, isLoading: isProfileLoading } = useGetUserProfile(); - - // 온보딩 완료 여부 확인 (로딩 중에는 false로 기본 처리) - const hasOnboarding = isProfileLoading ? false : hasCompletedOnboarding(userProfile); + // 온보딩 완료 여부 (로딩 중에는 false로 기본 처리) + const hasOnboarding = isAuthLoading ? false : hasCompletedOnboarding; const [modalView, setModalView] = useState('device'); @@ -187,7 +183,7 @@ export const useAddToCombination = ({ isAlreadyInSelectedCombination, isAddingDevice, addToCombinationConfig, - isProfileLoading, + isProfileLoading: isAuthLoading, handleCloseModal, handleSelectCombination, handleAddDeviceToCombination, From 769bfc9ebde0728910f03279c81e87e490de1207 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 18:53:37 +0900 Subject: [PATCH 16/17] =?UTF-8?q?feat:=20=EC=A1=B0=ED=95=A9=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Combination/CombinationTag.tsx | 6 +- src/components/MyPage/CombinationCard.tsx | 93 ++++++++++--------- 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/src/components/Combination/CombinationTag.tsx b/src/components/Combination/CombinationTag.tsx index 12cd405f..1bf549c5 100644 --- a/src/components/Combination/CombinationTag.tsx +++ b/src/components/Combination/CombinationTag.tsx @@ -21,10 +21,6 @@ const STATUS_STYLE_MAP: Record = { }; const CombinationTag = ({ name, status, className = '' }: CombinationTagProps) => { - // status가 유효하지 않을 경우를 대비한 안전한 스타일 추출 - const statusStyle = STATUS_STYLE_MAP[status] || STATUS_STYLE_MAP['-']; - const displayStatus = status || '-'; - return ( {name}: - {displayStatus} + {status} ); }; diff --git a/src/components/MyPage/CombinationCard.tsx b/src/components/MyPage/CombinationCard.tsx index 59b7451a..ed84c2eb 100644 --- a/src/components/MyPage/CombinationCard.tsx +++ b/src/components/MyPage/CombinationCard.tsx @@ -2,6 +2,7 @@ import { useState, memo } from 'react'; import StarIcon from '@/assets/icons/star.svg?react'; import StarXIcon from '@/assets/icons/starx.svg?react'; import StarHoverIcon from '@/assets/icons/starhover.svg?react'; +import CombinationTag from '@/components/Combination/CombinationTag'; import { formatDate } from '@/utils/format'; import type { ComboListItem } from '@/types/combo/combo'; @@ -90,44 +91,52 @@ const CombinationCard = ({ {nameError &&

{nameError}

}
) : ( - /* 일반 모드: 조합 번호 + 생성일 + 조합명 */ -
-
-

조합{index + 1}

-

- 생성일: {formatDate(combination.createdAt)} -

+ /* 일반 모드: 조합 번호 + 생성일 + 조합명 + 태그 */ +
+
+
+

조합{index + 1}

+

+ 생성일: {formatDate(combination.createdAt)} +

+
+
+

{combination.comboName}

+ {combination.isPinned ? ( + onTogglePin(e, combination.comboId)} + className={`!w-22 !h-22 -mt-3 cursor-pointer transition-opacity ${ + hoveredStarComboId === combination.comboId ? 'opacity-80' : '' + }`} + onMouseEnter={() => setHoveredStarComboId(combination.comboId)} + onMouseLeave={() => setHoveredStarComboId(null)} + /> + ) : ( + <> + {hoveredStarComboId === combination.comboId ? ( + onTogglePin(e, combination.comboId)} + className="!w-22 !h-22 -mt-3 cursor-pointer" + onMouseEnter={() => setHoveredStarComboId(combination.comboId)} + onMouseLeave={() => setHoveredStarComboId(null)} + /> + ) : ( + onTogglePin(e, combination.comboId)} + className="!w-22 !h-22 -mt-3 cursor-pointer" + onMouseEnter={() => setHoveredStarComboId(combination.comboId)} + onMouseLeave={() => setHoveredStarComboId(null)} + /> + )} + + )} +
-
-

{combination.comboName}

- {combination.isPinned ? ( - onTogglePin(e, combination.comboId)} - className={`!w-22 !h-22 -mt-3 cursor-pointer transition-opacity ${ - hoveredStarComboId === combination.comboId ? 'opacity-80' : '' - }`} - onMouseEnter={() => setHoveredStarComboId(combination.comboId)} - onMouseLeave={() => setHoveredStarComboId(null)} - /> - ) : ( - <> - {hoveredStarComboId === combination.comboId ? ( - onTogglePin(e, combination.comboId)} - className="!w-22 !h-22 -mt-3 cursor-pointer" - onMouseEnter={() => setHoveredStarComboId(combination.comboId)} - onMouseLeave={() => setHoveredStarComboId(null)} - /> - ) : ( - onTogglePin(e, combination.comboId)} - className="!w-22 !h-22 -mt-3 cursor-pointer" - onMouseEnter={() => setHoveredStarComboId(combination.comboId)} - onMouseLeave={() => setHoveredStarComboId(null)} - /> - )} - - )} + {/* 조합 평가 태그 (연동성, 편의성, 라이프스타일) */} +
+ + +
)} @@ -143,15 +152,7 @@ const CombinationCard = ({ key={device.deviceId} className="bg-white rounded-card shadow-[0_0_4px_rgba(0,0,0,0.1)] p-12 w-244 flex items-center gap-12" > -
- {device.imageUrl && ( - {device.name} - )} -
+

{device.name}

{device.brandName}

From 8169a543f92ea17ac2bd48b93bf86a7cbba95034 Mon Sep 17 00:00:00 2001 From: Seony777 Date: Thu, 12 Feb 2026 19:15:53 +0900 Subject: [PATCH 17/17] =?UTF-8?q?feat:=20=EC=A1=B0=ED=95=A9=20=ED=8F=89?= =?UTF-8?q?=EA=B0=80=20=ED=83=9C=EA=B7=B8=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MyPage/CombinationCard.tsx | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/MyPage/CombinationCard.tsx b/src/components/MyPage/CombinationCard.tsx index ed84c2eb..8d7d6d0e 100644 --- a/src/components/MyPage/CombinationCard.tsx +++ b/src/components/MyPage/CombinationCard.tsx @@ -3,8 +3,10 @@ import StarIcon from '@/assets/icons/star.svg?react'; import StarXIcon from '@/assets/icons/starx.svg?react'; import StarHoverIcon from '@/assets/icons/starhover.svg?react'; import CombinationTag from '@/components/Combination/CombinationTag'; +import { useComboEvaluation } from '@/apis/combo/getComboEvaluation'; import { formatDate } from '@/utils/format'; import type { ComboListItem } from '@/types/combo/combo'; +import type { CombinationStatus } from '@/constants/combination'; interface CombinationCardProps { combination: ComboListItem; @@ -31,6 +33,9 @@ const CombinationCard = ({ }: CombinationCardProps) => { const [hoveredStarComboId, setHoveredStarComboId] = useState(null); + // 조합 평가 캐시 구독 (staleTime: Infinity이므로 캐시에 있으면 API 호출 없이 바로 사용) + const { data: evaluation } = useComboEvaluation(combination.comboId); + // 그라데이션 로직 const gradientThreshold = columns === 4 ? 9 : 7; const shouldShowGradient = combination.devices.length >= gradientThreshold; @@ -132,11 +137,20 @@ const CombinationCard = ({ )}
- {/* 조합 평가 태그 (연동성, 편의성, 라이프스타일) */} + {/* 조합 평가 태그 - COMBO_EVALUATION 캐시에서 등급 읽기 */}
- - - + + +
)}