diff --git a/app/myInfo/page.tsx b/app/myInfo/page.tsx index 8718d3b..8a35079 100644 --- a/app/myInfo/page.tsx +++ b/app/myInfo/page.tsx @@ -1,18 +1,5 @@ -'use client'; - -import React from 'react'; import MyInfoStyle from '../../components/MyInfoStyle'; -import useMyInfo from '../../lib/hooks/useMyInfo'; export default function MyInfoPage() { - const { data: userData, isLoading, isError, error } = useMyInfo(); - - if (isLoading) return
Loading...
; - - if (isError) - return
Error: {error instanceof Error ? error.message : JSON.stringify(error)}
; - - if (!userData) return
로그인이 필요합니다.
; - - return ; + return ; } diff --git a/app/purchase/[id]/page.tsx b/app/purchase/[id]/page.tsx index 7c9d324..9828f10 100644 --- a/app/purchase/[id]/page.tsx +++ b/app/purchase/[id]/page.tsx @@ -10,6 +10,8 @@ import FinalPaymentSummary from '../../../components/payment/FinalPaymentSummary import RaffleItemConfirmationModal from '../../../components/Modal/RaffleItemConfirmationModal'; import { getNotFreeRaffleDataDetail } from '../../../api/raffle/raffleApi'; import { postPurchaseItem } from '../../../api/raffle/purchaseItemApi'; +import useAuthStore from '../../../lib/store/useAuthStore'; +import Skeleton from '../../../components/Skeleton'; export default function PurchasePage({ params, @@ -26,11 +28,13 @@ export default function PurchasePage({ const [isPurchaseChecked, setIsPurchaseChecked] = useState(false); const [isRaffleConfirmationModalOpen, setIsRaffleConfirmationModalOpen] = useState(false); - const userToken = localStorage.getItem('access_token'); + const userToken = useAuthStore((state) => state.userToken); - if (!userToken) { - throw new Error('userToken이 없습니다.'); - } + useEffect(() => { + if (!userToken) { + router.push('/'); + } + }, [userToken, router]); const { data: raffleDetailItem } = useQuery({ queryKey: ['getNotFreeRaffleDataDetail'], @@ -66,7 +70,23 @@ export default function PurchasePage({ }; if (!raffleDetailItem) { - return
로딩 중...
; + return ( +
+

상품 결제

+
+
+ + + +
+
+ + + +
+
+
+ ); } return ( diff --git a/components/AuthHandler.tsx b/components/AuthHandler.tsx index 2368a24..a70da3a 100644 --- a/components/AuthHandler.tsx +++ b/components/AuthHandler.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; +import Cookies from 'js-cookie'; import useAuthStore from '../lib/store/useAuthStore'; export default function AuthHandler() { @@ -21,8 +22,8 @@ export default function AuthHandler() { * 그리고 router를 이용하여 홈으로 이동. */ if (accessToken && refreshToken) { - localStorage.setItem('access_token', accessToken); - localStorage.setItem('refresh_token', refreshToken); + Cookies.set('access_token', accessToken); + Cookies.set('refresh_token', refreshToken); setUserToken(accessToken, refreshToken); router.replace('/'); } diff --git a/components/MyInfoStyle.tsx b/components/MyInfoStyle.tsx index 7bd4b68..44339e8 100644 --- a/components/MyInfoStyle.tsx +++ b/components/MyInfoStyle.tsx @@ -1,9 +1,19 @@ +'use client'; + import React from 'react'; import { useRouter } from 'next/navigation'; -import { UserData } from '../lib/types/user'; +import useMyInfo from '../lib/hooks/useMyInfo'; -export default function MyInfoStyle({ userData }: { userData: UserData }) { +export default function MyInfoStyle() { const router = useRouter(); + const { data: userData, isLoading, isError, error } = useMyInfo(); + + if (isLoading) return
로딩 중...
; + + if (isError) + return
Error: {error instanceof Error ? error.message : JSON.stringify(error)}
; + + if (!userData) return
로그인이 필요합니다.
; const handleMemberInfoNavigation = () => { router.push('/myInfo/memberInfo'); diff --git a/components/ProfilePopover.tsx b/components/ProfilePopover.tsx index edab0c3..ebfafec 100644 --- a/components/ProfilePopover.tsx +++ b/components/ProfilePopover.tsx @@ -8,10 +8,10 @@ export default function ProfilePopover({ onClose }: { onClose: () => void }) { const popoverRef = useRef(null); const router = useRouter(); - const handleLogout = () => { logout(); localStorage.removeItem('access_token'); + router.push('/'); onClose(); }; @@ -20,7 +20,6 @@ export default function ProfilePopover({ onClose }: { onClose: () => void }) { onClose(); }; - return (
void }) {
diff --git a/components/Skeleton.tsx b/components/Skeleton.tsx new file mode 100644 index 0000000..4819a7f --- /dev/null +++ b/components/Skeleton.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export default function Skeleton({ className }: { className?: string }) { + return ( +
+
+
+ ); +} diff --git a/lib/store/useAuthStore.ts b/lib/store/useAuthStore.ts index e2229cb..e297d33 100644 --- a/lib/store/useAuthStore.ts +++ b/lib/store/useAuthStore.ts @@ -1,5 +1,6 @@ import { create } from 'zustand'; -import { createJSONStorage, persist } from 'zustand/middleware'; +import { persist } from 'zustand/middleware'; +import Cookies from 'js-cookie'; import { AuthStore } from '../types/authStores'; const useAuthStore = create()( @@ -9,22 +10,21 @@ const useAuthStore = create()( refreshToken: '', setUserToken: (userToken, refreshToken) => { if (typeof window !== 'undefined') { - localStorage.setItem('access_token', userToken); - localStorage.setItem('refresh_token', refreshToken); + Cookies.set('access_token', userToken); + Cookies.set('refresh_token', refreshToken); } set({ userToken, refreshToken }); }, logout: () => { if (typeof window !== 'undefined') { - localStorage.removeItem('access_token'); - localStorage.removeItem('refresh_token'); + Cookies.remove('access_token'); + Cookies.remove('refresh_token'); } set({ userToken: '', refreshToken: '' }); }, }), { name: 'auth-storage', - storage: typeof window !== 'undefined' ? createJSONStorage(() => localStorage) : undefined, }, ), ); diff --git a/package-lock.json b/package-lock.json index 0504159..739ff75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "eslint-plugin-jsx-a11y": "^6.9.0", "eslint-plugin-react": "^7.35.1", "eslint-plugin-react-hooks": "^4.6.2", + "js-cookie": "^3.0.5", "next": "14.2.5", "nodemailer": "^6.9.14", "react": "^18", @@ -33,6 +34,7 @@ "@storybook/nextjs": "^8.2.9", "@storybook/react": "^8.2.9", "@storybook/test": "^8.2.9", + "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/nodemailer": "^6.4.15", "@types/react": "^18", @@ -4946,6 +4948,12 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -10262,6 +10270,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 8774969..c3cc250 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "eslint-plugin-jsx-a11y": "^6.9.0", "eslint-plugin-react": "^7.35.1", "eslint-plugin-react-hooks": "^4.6.2", + "js-cookie": "^3.0.5", "next": "14.2.5", "nodemailer": "^6.9.14", "react": "^18", @@ -39,6 +40,7 @@ "@storybook/nextjs": "^8.2.9", "@storybook/react": "^8.2.9", "@storybook/test": "^8.2.9", + "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/nodemailer": "^6.4.15", "@types/react": "^18", diff --git a/tailwind.config.ts b/tailwind.config.ts index d584bbc..a37f102 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -16,6 +16,14 @@ const config: Config = { 'white-shadow': 'rgb(255, 255, 255) 3px 0px 0px, rgb(255, 255, 255) 2.83487px 0.981584px 0px, rgb(255, 255, 255) 2.35766px 1.85511px 0px, rgb(255, 255, 255) 1.62091px 2.52441px 0px, rgb(255, 255, 255) 0.705713px 2.91581px 0px, rgb(255, 255, 255) -0.287171px 2.98622px 0px, rgb(255, 255, 255) -1.24844px 2.72789px 0px, rgb(255, 255, 255) -2.07227px 2.16926px 0px, rgb(255, 255, 255) -2.66798px 1.37182px 0px, rgb(255, 255, 255) -2.96998px 0.42336px 0px, rgb(255, 255, 255) -2.94502px -0.571704px 0px, rgb(255, 255, 255) -2.59586px -1.50383px 0px, rgb(255, 255, 255) -1.96093px -2.27041px 0px, rgb(255, 255, 255) -1.11013px -2.78704px 0px, rgb(255, 255, 255) -0.137119px -2.99686px 0px, rgb(255, 255, 255) 0.850987px -2.87677px 0px, rgb(255, 255, 255) 1.74541px -2.43999px 0px, rgb(255, 255, 255) 2.44769px -1.73459px 0px, rgb(255, 255, 255) 2.88051px -0.838247px 0px', }, + keyframes: { + shimmer: { + '100%': { transform: 'translateX(100%)' }, + }, + }, + animation: { + shimmer: 'shimmer 2s infinite', + }, }, colors: {