From 1d437e37a7692ac0a5673ec7a0e821cac1821e5f Mon Sep 17 00:00:00 2001 From: aken-you Date: Sun, 3 Aug 2025 19:30:28 +0900 Subject: [PATCH 01/17] =?UTF-8?q?feat:=20Toast=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/global.css | 3 +++ package.json | 1 + src/app/provider/index.tsx | 22 ++++++++++++++++++++ src/shared/ui/toast/index.ts | 20 ++++++++++++++++++ yarn.lock | 39 ++++++++++-------------------------- 5 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 src/shared/ui/toast/index.ts diff --git a/app/global.css b/app/global.css index 74119091..bc9f1f89 100644 --- a/app/global.css +++ b/app/global.css @@ -225,6 +225,9 @@ https://velog.io/@oneook/tailwindcss-4.0-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%AC%E --color-background-accent-yellow-subtle: var(--color-yellow-50); --color-background-accent-yellow-default: var(--color-yellow-100); --color-background-accent-yellow-strong: var(--color-yellow-600); + --color-background-neutral-strong: var(--color-gray-900); + --color-background-success-default: var(--color-green-500); + --color-background-danger-default: var(--color-red-500); --color-fill-brand-default-default: var(--color-rose-500); --color-fill-brand-default-hover: var(--color-rose-600); diff --git a/package.json b/package.json index 76b2ac30..0f8dca0d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "react": "^19.0.0", "react-day-picker": "9.4.3", "react-dom": "^19.0.0", + "sonner": "^2.0.6", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.0.6", "tailwindcss-animate": "^1.0.7", diff --git a/src/app/provider/index.tsx b/src/app/provider/index.tsx index bc0968dc..10e16ab1 100644 --- a/src/app/provider/index.tsx +++ b/src/app/provider/index.tsx @@ -1,4 +1,5 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { Toaster } from 'sonner'; import QueryProvider from '@/app/provider/query-provider'; interface ProviderProps { @@ -8,6 +9,27 @@ interface ProviderProps { function MainProvider({ children }: ProviderProps) { return ( + {children} {process.env.NODE_ENV === 'development' && ( diff --git a/src/shared/ui/toast/index.ts b/src/shared/ui/toast/index.ts new file mode 100644 index 00000000..6b924b29 --- /dev/null +++ b/src/shared/ui/toast/index.ts @@ -0,0 +1,20 @@ +import { toast } from 'sonner'; + +const openToast = ({ + type = 'info', + text, +}: { + type?: 'success' | 'danger' | 'info'; + text: string; +}) => { + if (type === 'success') { + return toast.success(text); + } + if (type === 'danger') { + return toast.warning(text); + } + + return toast.info(text); +}; + +export { openToast }; diff --git a/yarn.lock b/yarn.lock index 4a6df8da..ffe7cabc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6852,6 +6852,11 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +sonner@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/sonner/-/sonner-2.0.6.tgz#623b73faec55229d63ec35226d42021f2119bfa7" + integrity sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q== + source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -6906,16 +6911,8 @@ strict-event-emitter@^0.5.1: resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7017,14 +7014,8 @@ stringify-object@^5.0.0: is-obj "^3.0.0" is-regexp "^3.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -7644,7 +7635,8 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -7662,15 +7654,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 33575d21624e1ce167d7146af8a3c0824865fee7 Mon Sep 17 00:00:00 2001 From: aken-you Date: Wed, 6 Aug 2025 01:15:34 +0900 Subject: [PATCH 02/17] =?UTF-8?q?fix:=20Toast=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B0=80=EB=A1=9C=20=EA=B8=B8=EC=9D=B4=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/provider/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/provider/index.tsx b/src/app/provider/index.tsx index 10e16ab1..42a04378 100644 --- a/src/app/provider/index.tsx +++ b/src/app/provider/index.tsx @@ -27,6 +27,9 @@ function MainProvider({ children }: ProviderProps) { warning: 'bg-background-danger-default', closeButton: '!font-thin', }, + style: { + width: 'fit-content', + }, }} closeButton /> From cb5f512773c0d04c13a19d6e7ffcb8d95e8a612b Mon Sep 17 00:00:00 2001 From: aken-you Date: Wed, 6 Aug 2025 01:24:37 +0900 Subject: [PATCH 03/17] =?UTF-8?q?feat:=20ApiError=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서버에서 오는 에러 객체 --- src/shared/tanstack-query/api-error.ts | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/shared/tanstack-query/api-error.ts diff --git a/src/shared/tanstack-query/api-error.ts b/src/shared/tanstack-query/api-error.ts new file mode 100644 index 00000000..0fcd1f41 --- /dev/null +++ b/src/shared/tanstack-query/api-error.ts @@ -0,0 +1,40 @@ +class ApiError extends Error { + name = 'ApiError'; + statusCode: number; + errorCode: string; + errorName: string; + message: string; + + constructor({ + statusCode, + errorCode, + errorName, + message, + }: { + statusCode: number; + errorCode: string; + errorName: string; + message: string; + }) { + super(message); + this.statusCode = statusCode; + this.errorCode = errorCode; + this.errorName = errorName; + this.message = message; + } +} + +// API 에러인지 확인하는 함수 +const isApiError = (error: unknown): error is ApiError => { + return ( + error instanceof ApiError || + (typeof error === 'object' && + error !== null && + 'statusCode' in error && + 'errorCode' in error && + 'errorName' in error && + 'message' in error) + ); +}; + +export { ApiError, isApiError }; From 1464b30f86982004a1018c909a6a21a3a7751378 Mon Sep 17 00:00:00 2001 From: aken-you Date: Wed, 6 Aug 2025 09:05:39 +0900 Subject: [PATCH 04/17] =?UTF-8?q?style:=20console.log=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/tanstack-query/axios.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/shared/tanstack-query/axios.ts b/src/shared/tanstack-query/axios.ts index d17b4a56..c1173730 100644 --- a/src/shared/tanstack-query/axios.ts +++ b/src/shared/tanstack-query/axios.ts @@ -36,14 +36,10 @@ axiosInstanceForMultipart.interceptors.request.use( axiosInstance.interceptors.request.use( (config) => { const accessToken = getCookie('accessToken'); + if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; } - // 로컬 테스트에서 사용시 주석 제거 - // console.log("------------------------") - // console.log("✅ 요청주소", config.url); - // console.log("✅ 요청 Bearer", config.headers.Authorization); - // console.log("✅ 요청내용", config); return config; }, @@ -52,18 +48,10 @@ axiosInstance.interceptors.request.use( axiosInstance.interceptors.response.use( (response) => { - // 로컬 테스트에서 사용시 주석 제거 - // console.log('------------------------'); - // console.log('✅ 응답주소', response.request.responseURL); - // console.log('✅ 응답로그', response); - return response; }, async (error) => { - console.log('에러 확인', error); - console.log('에러 상태코드:', error.response?.status); - const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; From f9296f73b9c242d7e4f6885386b9307e578fa79c Mon Sep 17 00:00:00 2001 From: aken-you Date: Wed, 6 Aug 2025 22:08:20 +0900 Subject: [PATCH 05/17] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=84=B0=EB=94=94?= =?UTF-8?q?=20=EC=8B=A0=EC=B2=AD=20useMutation=EC=9D=98=20onSuccess,=20onE?= =?UTF-8?q?rror=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/study/model/use-study-query.ts | 17 ++++++++++++++ src/features/study/ui/start-study-modal.tsx | 25 +++++---------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/features/study/model/use-study-query.ts b/src/features/study/model/use-study-query.ts index 72afc479..e8643e1a 100644 --- a/src/features/study/model/use-study-query.ts +++ b/src/features/study/model/use-study-query.ts @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/navigation'; import { completeStudy, getDailyStudies, @@ -8,6 +9,8 @@ import { postJoinStudy, putStudyDaily, } from '@/features/study/api/get-study-data'; +import { isApiError } from '@/shared/tanstack-query/api-error'; +import { openToast } from '@/shared/ui/toast'; import { CompleteStudyRequest, GetDailyStudiesParams, @@ -60,8 +63,22 @@ export const useMonthlyStudyCalendarQuery = ( // 스터디 신청 mutation export const useJoinStudyMutation = () => { + const router = useRouter(); + return useMutation({ mutationFn: (payload: JoinStudyRequest) => postJoinStudy(payload), + onSuccess: () => { + alert('스터디 신청이 완료되었습니다!'); + router.refresh(); + }, + onError: (error) => { + if (isApiError(error)) { + openToast({ + type: 'danger', + text: '스터디 신청에 실패했습니다.', + }); + } + }, }); }; diff --git a/src/features/study/ui/start-study-modal.tsx b/src/features/study/ui/start-study-modal.tsx index 4c3a190e..8a06cc20 100644 --- a/src/features/study/ui/start-study-modal.tsx +++ b/src/features/study/ui/start-study-modal.tsx @@ -2,7 +2,6 @@ import { XIcon } from 'lucide-react'; import Image from 'next/image'; -import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { useAvailableStudyTimesQuery, @@ -151,7 +150,6 @@ function StartStudyForm({ memberId }: StartStudyModalProps) { const { data: availableStudyTimes } = useAvailableStudyTimesQuery(); const { data: studySubjects } = useStudySubjectsQuery(); const { data: techStacks } = useTechStacksQuery(); - const router = useRouter(); const { mutate: joinStudy } = useJoinStudyMutation(); @@ -187,23 +185,12 @@ function StartStudyForm({ memberId }: StartStudyModalProps) { return; } - joinStudy( - { - ...form, - memberId, - githubLink: githubLink.trim() || undefined, - blogOrSnsLink: blogOrSnsLink.trim() || undefined, - }, - { - onSuccess: () => { - alert('스터디 신청이 완료되었습니다!'); - router.refresh(); - }, - onError: () => { - alert('스터디 신청 중 오류가 발생했습니다. 다시 시도해 주세요.'); - }, - }, - ); + joinStudy({ + ...form, + memberId, + githubLink: githubLink.trim() || undefined, + blogOrSnsLink: blogOrSnsLink.trim() || undefined, + }); }; return ( From 3c0a62b6a9ff34e3148d0a0977a9900145ac7939 Mon Sep 17 00:00:00 2001 From: aken-you Date: Wed, 6 Aug 2025 22:41:55 +0900 Subject: [PATCH 06/17] =?UTF-8?q?refactor:=20Sidebar=EC=97=90=EC=84=9C=20m?= =?UTF-8?q?emberId=EA=B0=80=20=EC=97=86=EC=9C=BC=EB=A9=B4=20login=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/home/sidebar.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/widgets/home/sidebar.tsx b/src/widgets/home/sidebar.tsx index 9709d142..572598cd 100644 --- a/src/widgets/home/sidebar.tsx +++ b/src/widgets/home/sidebar.tsx @@ -1,3 +1,4 @@ +import { redirect } from 'next/navigation'; import { getUserProfile } from '@/entities/user/api/get-user-profile'; import MyProfileCard from '@/features/study/ui/my-profile-card'; import StartStudyModal from '@/features/study/ui/start-study-modal'; @@ -8,6 +9,10 @@ import TodoList from '@/widgets/home/todo-list'; export default async function Sidebar() { const memberId = await getLoginUserId(); + if (!memberId) { + redirect('/login'); + } + const userProfile = await getUserProfile(memberId); return ( From aaf2d16934b3036c48bd72a993f3750267456300 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:00:46 +0900 Subject: [PATCH 07/17] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=EC=9D=B4?= =?UTF-8?q?=20=ED=95=84=EC=9A=94=ED=95=9C=20client-side=20axios=20?= =?UTF-8?q?=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/tanstack-query/axios.ts | 80 +++++++++++------------------- 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/src/shared/tanstack-query/axios.ts b/src/shared/tanstack-query/axios.ts index c1173730..c6cc36c9 100644 --- a/src/shared/tanstack-query/axios.ts +++ b/src/shared/tanstack-query/axios.ts @@ -1,7 +1,10 @@ -import axios from 'axios'; -import { getCookie, setCookie } from './cookie'; +import axios, { InternalAxiosRequestConfig, isAxiosError } from 'axios'; +import { ApiError, isApiError } from './api-error'; +import { getCookie } from './cookie'; -// json 요청용 +// * 인증이 필요한 client-side axios 인스턴스 + +// json 요청 export const axiosInstance = axios.create({ baseURL: `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/`, timeout: 10000, @@ -20,60 +23,37 @@ export const axiosInstanceForMultipart = axios.create({ }, }); -/* - accessToken 은 쿠키에 저장 - refreshToken 은 HttpOnly 쿠키로 JS에서 접근 불가, 백엔드 서버와 쿠키로 통신 -*/ +const onRequestClient = (config: InternalAxiosRequestConfig) => { + const accessToken = getCookie('accessToken'); -// multipart 요청 로깅용 -axiosInstanceForMultipart.interceptors.request.use( - (config) => { - return config; - }, - (error) => Promise.reject(error), -); + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } -axiosInstance.interceptors.request.use( - (config) => { - const accessToken = getCookie('accessToken'); + return config; +}; - if (accessToken) { - config.headers.Authorization = `Bearer ${accessToken}`; - } +axiosInstance.interceptors.request.use(onRequestClient); +axiosInstanceForMultipart.interceptors.request.use(onRequestClient); - return config; - }, - (error) => Promise.reject(error), -); +const onResponseErrorClient = async (error: unknown) => { + if (isAxiosError(error) && error.response) { + const errorResponseBody = error.response.data; -axiosInstance.interceptors.response.use( - (response) => { - return response; - }, - - async (error) => { - const originalRequest = error.config; - if (error.response?.status === 401 && !originalRequest._retry) { - originalRequest._retry = true; - try { - const refreshApi = axios.create({ - baseURL: `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/`, - }); - const res = await refreshApi.get('/auth/access-token/refresh'); - const newAccessToken = res.data.accessToken; - - if (newAccessToken) { - setCookie('accessToken', newAccessToken); - originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + if (isApiError(errorResponseBody)) { + const accessToken = getCookie('accessToken'); - return axiosInstance(originalRequest); - } - } catch (err) { - // 로그인 페이지 리다이렉트 등 처리 - return Promise.reject(err); + // 유효하지 않은 accessToken인 경우, 재발급 + if (accessToken && errorResponseBody.errorCode === 'AUTH001') { + // refresh accessToken } + + throw new ApiError(errorResponseBody); } + } +}; - return Promise.reject(error); - }, +axiosInstance.interceptors.response.use( + (config) => config, + onResponseErrorClient, ); From 069ead22867d7ebf460b4f7e3cc9db926dc6f7c1 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:01:34 +0900 Subject: [PATCH 08/17] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=EC=9D=B4=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20server-side=20axios=20=EC=9D=B8?= =?UTF-8?q?=EC=8A=A4=ED=84=B4=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/tanstack-query/axios.server.ts | 48 +++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/shared/tanstack-query/axios.server.ts diff --git a/src/shared/tanstack-query/axios.server.ts b/src/shared/tanstack-query/axios.server.ts new file mode 100644 index 00000000..5351413a --- /dev/null +++ b/src/shared/tanstack-query/axios.server.ts @@ -0,0 +1,48 @@ +import axios, { InternalAxiosRequestConfig, isAxiosError } from 'axios'; +import { ApiError, isApiError } from './api-error'; +import { getServerCookie } from '../lib/server-cookie'; + +// * 인증이 필요한 server-side axios 인스턴스 + +// json 요청 +export const axiosServerInstance = axios.create({ + baseURL: `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/`, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +const onRequestServer = async (config: InternalAxiosRequestConfig) => { + const accessToken = await getServerCookie('accessToken'); + + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + + return config; +}; + +axiosServerInstance.interceptors.request.use(onRequestServer); + +const onResponseErrorServer = async (error: unknown) => { + if (isAxiosError(error) && error.response) { + const errorResponseBody = error.response.data; + + if (isApiError(errorResponseBody)) { + const accessToken = await getServerCookie('accessToken'); + + // 유효하지 않은 accessToken인 경우, 재발급 + if (accessToken && errorResponseBody.errorCode === 'AUTH001') { + // refresh accessToken + } + + throw new ApiError(errorResponseBody); + } + } +}; + +axiosServerInstance.interceptors.response.use( + (config) => config, + onResponseErrorServer, +); From a6ad874e3dd81638fe7c7291a3009aa89a003033 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:01:54 +0900 Subject: [PATCH 09/17] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=EC=9D=B4=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20axios=20=EC=9D=B8?= =?UTF-8?q?=EC=8A=A4=ED=84=B4=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - server, client 둘 다 사용 가능 --- src/shared/tanstack-query/axios.all.ts | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/shared/tanstack-query/axios.all.ts diff --git a/src/shared/tanstack-query/axios.all.ts b/src/shared/tanstack-query/axios.all.ts new file mode 100644 index 00000000..791be0bb --- /dev/null +++ b/src/shared/tanstack-query/axios.all.ts @@ -0,0 +1,27 @@ +import axios, { isAxiosError } from 'axios'; +import { ApiError, isApiError } from './api-error'; + +// * 인증하지 않는 axios 인스턴스 +export const axiosAllInstance = axios.create({ + baseURL: `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/`, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, +}); + +axiosAllInstance.interceptors.response.use( + (response) => response, + (error) => { + if ( + error.response && + isAxiosError(error) && + isApiError(error.response.data) + ) { + throw new ApiError(error.response.data); + } + + return Promise.reject(error); + }, +); From 5f34ef6b0c22c0d9ed8adbfdeb876b4b99abdcfb Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:02:53 +0900 Subject: [PATCH 10/17] =?UTF-8?q?style:=20console.log=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/login/page.tsx | 2 -- app/sign-up/page.tsx | 1 - src/features/auth/api/auth.ts | 2 -- 3 files changed, 5 deletions(-) diff --git a/app/login/page.tsx b/app/login/page.tsx index f8e28d58..fd388481 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -9,7 +9,5 @@ export const metadata: Metadata = { // 랜딩페이지에서 로그인 모달이 뜨는 것으로 파악 export default function LoginPage() { - console.log("NEXT_PUBLIC_API_BASE_URL", process.env.NEXT_PUBLIC_API_BASE_URL) - return ; } diff --git a/app/sign-up/page.tsx b/app/sign-up/page.tsx index 42e39f7d..8ddb2820 100644 --- a/app/sign-up/page.tsx +++ b/app/sign-up/page.tsx @@ -4,7 +4,6 @@ import Landing from '@/features/auth/ui/landing'; // 랜딩페이지에서 회원가입 모달이 뜨는 것으로 파악 export default function SignupPage() { // TODO : URL 에서 토큰 파싱후 - console.log('회원가입 페이지'); return ; } diff --git a/src/features/auth/api/auth.ts b/src/features/auth/api/auth.ts index 26828943..c61a3435 100644 --- a/src/features/auth/api/auth.ts +++ b/src/features/auth/api/auth.ts @@ -8,7 +8,6 @@ import { // 회원가입 요청 API export async function signUp(data: any) { const res = await axiosInstance.post('/members', data); - console.log('signUp res', res); return res.data; } @@ -30,7 +29,6 @@ export async function uploadProfileImage( // 멤버 ID 조회 API export async function getMemberId() { const res = await axiosInstance.get(`/auth/me`); - console.log('getMemberId res', res); return res.data; } From c3bb4e3ad3c84acae7c0e0f765222d487fdc01ab Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:03:31 +0900 Subject: [PATCH 11/17] =?UTF-8?q?feat:=20access=20token=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20api=20=EC=9A=94=EC=B2=AD=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/tanstack-query/get-new-access-token.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/shared/tanstack-query/get-new-access-token.ts diff --git a/src/shared/tanstack-query/get-new-access-token.ts b/src/shared/tanstack-query/get-new-access-token.ts new file mode 100644 index 00000000..c470468b --- /dev/null +++ b/src/shared/tanstack-query/get-new-access-token.ts @@ -0,0 +1,9 @@ +import { axiosAllInstance } from './axios.all'; + +export const getNewAccessToken = async (): Promise<{ + accessToken: string; +}> => { + const res = await axiosAllInstance.get('/auth/refresh'); + + return res.data.content; +}; From c6cace2a6885e130ff633f14654cba77ea9cc473 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:05:19 +0900 Subject: [PATCH 12/17] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=EC=9D=B4?= =?UTF-8?q?=20=ED=95=84=EC=9A=94=ED=95=9C=20client-side=20axios=20?= =?UTF-8?q?=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/user/api/get-user-profile.ts | 14 ++++++++----- src/features/auth/api/auth.ts | 14 ++++++------- .../my-page/api/update-user-profile.ts | 17 ++++++++------- src/features/study/api/get-study-data.ts | 21 +++++++++++-------- .../{axios.ts => axios.client.ts} | 10 ++++----- 5 files changed, 43 insertions(+), 33 deletions(-) rename src/shared/tanstack-query/{axios.ts => axios.client.ts} (82%) diff --git a/src/entities/user/api/get-user-profile.ts b/src/entities/user/api/get-user-profile.ts index 67b9f322..7261fed1 100644 --- a/src/entities/user/api/get-user-profile.ts +++ b/src/entities/user/api/get-user-profile.ts @@ -2,12 +2,12 @@ import type { GetUserProfileResponse, PatchAutoMatchingParams, } from '@/entities/user/api/types'; -import { axiosInstance } from '@/shared/tanstack-query/axios'; +import { axiosClientInstance } from '@/shared/tanstack-query/axios.client'; export const getUserProfile = async ( memberId: number, ): Promise => { - const res = await axiosInstance.get(`/members/${memberId}/profile`); + const res = await axiosClientInstance.get(`/members/${memberId}/profile`); return res.data.content; }; @@ -16,7 +16,11 @@ export const patchAutoMatching = async ({ memberId, autoMatching, }: PatchAutoMatchingParams): Promise => { - await axiosInstance.patch(`/members/${memberId}/auto-matching`, undefined, { - params: { 'auto-matching': autoMatching }, - }); + await axiosClientInstance.patch( + `/members/${memberId}/auto-matching`, + undefined, + { + params: { 'auto-matching': autoMatching }, + }, + ); }; diff --git a/src/features/auth/api/auth.ts b/src/features/auth/api/auth.ts index c61a3435..5bf2fa3d 100644 --- a/src/features/auth/api/auth.ts +++ b/src/features/auth/api/auth.ts @@ -1,13 +1,13 @@ // API 통신만 담당하는 순수 함수들 import { - axiosInstance, - axiosInstanceForMultipart, -} from '@/shared/tanstack-query/axios'; + axiosClientInstance, + axiosClientInstanceForMultipart, +} from '@/shared/tanstack-query/axios.client'; // 회원가입 요청 API export async function signUp(data: any) { - const res = await axiosInstance.post('/members', data); + const res = await axiosClientInstance.post('/members', data); return res.data; } @@ -18,7 +18,7 @@ export async function uploadProfileImage( filename: string, file: FormData, ) { - const res = await axiosInstanceForMultipart.put( + const res = await axiosClientInstanceForMultipart.put( `/files/members/${memberId}/profile/image/${filename}`, file, ); @@ -28,14 +28,14 @@ export async function uploadProfileImage( // 멤버 ID 조회 API export async function getMemberId() { - const res = await axiosInstance.get(`/auth/me`); + const res = await axiosClientInstance.get(`/auth/me`); return res.data; } // 로그아웃 API export const logout = async (): Promise => { - const res = await axiosInstance.post('/auth/logout'); + const res = await axiosClientInstance.post('/auth/logout'); return res.data.statusCode; }; diff --git a/src/features/my-page/api/update-user-profile.ts b/src/features/my-page/api/update-user-profile.ts index 2d5b7877..e8b18d02 100644 --- a/src/features/my-page/api/update-user-profile.ts +++ b/src/features/my-page/api/update-user-profile.ts @@ -8,13 +8,16 @@ import type { UpdateUserProfileRequest, UpdateUserProfileResponse, } from '@/features/my-page/api/types'; -import { axiosInstance } from '@/shared/tanstack-query/axios'; +import { axiosClientInstance } from '@/shared/tanstack-query/axios.client'; export const updateUserProfile = async ( memberId: number, body: UpdateUserProfileRequest, ): Promise => { - const res = await axiosInstance.patch(`/members/${memberId}/profile`, body); + const res = await axiosClientInstance.patch( + `/members/${memberId}/profile`, + body, + ); return res.data.content; }; @@ -23,7 +26,7 @@ export const updateUserProfileInfo = async ( memberId: number, body: UpdateUserProfileInfoRequest, ): Promise => { - const res = await axiosInstance.patch( + const res = await axiosClientInstance.patch( `/members/${memberId}/profile/info`, body, ); @@ -34,25 +37,25 @@ export const updateUserProfileInfo = async ( export const getAvailableStudyTimes = async (): Promise< AvailableStudyTimeResponse[] > => { - const res = await axiosInstance.get('/available-study-times'); + const res = await axiosClientInstance.get('/available-study-times'); return res.data.content; }; export const getStudySubjects = async (): Promise => { - const res = await axiosInstance.get('/study-subjects'); + const res = await axiosClientInstance.get('/study-subjects'); return res.data.content; }; export const getTechStacks = async (): Promise => { - const res = await axiosInstance.get('/tech-stacks'); + const res = await axiosClientInstance.get('/tech-stacks'); return res.data.content; }; export const getStudyDashboard = async (): Promise => { - const res = await axiosInstance.get('/study/dashboard'); + const res = await axiosClientInstance.get('/study/dashboard'); return res.data.content; }; diff --git a/src/features/study/api/get-study-data.ts b/src/features/study/api/get-study-data.ts index 570e7b4e..0f58978a 100644 --- a/src/features/study/api/get-study-data.ts +++ b/src/features/study/api/get-study-data.ts @@ -11,13 +11,13 @@ import type { PrepareStudyRequest, WeeklyParticipationResponse, } from '@/features/study/api/types'; -import { axiosInstance } from '@/shared/tanstack-query/axios'; +import { axiosClientInstance } from '@/shared/tanstack-query/axios.client'; // 스터디 상세 조회 export const getDailyStudyDetail = async ( params: string, ): Promise => { - const res = await axiosInstance.get(`/study/daily/mine/${params}`); + const res = await axiosClientInstance.get(`/study/daily/mine/${params}`); return res.data.content; }; @@ -26,7 +26,7 @@ export const getDailyStudyDetail = async ( export const getDailyStudies = async ( params?: GetDailyStudiesParams, ): Promise => { - const res = await axiosInstance.get('/study/daily', { params }); + const res = await axiosClientInstance.get('/study/daily', { params }); return res.data.content; }; @@ -35,13 +35,13 @@ export const getDailyStudies = async ( export const getMonthlyStudyCalendar = async ( params: GetMonthlyCalendarParams, ): Promise => { - const res = await axiosInstance.get('/study/daily/month', { params }); + const res = await axiosClientInstance.get('/study/daily/month', { params }); return res.data.content; }; export const postDailyRetrospect = async (body: PostDailyRetrospectRequest) => { - const res = await axiosInstance.post('/study/daily/retrospect', body); + const res = await axiosClientInstance.post('/study/daily/retrospect', body); return res.data; }; @@ -51,7 +51,10 @@ export const putStudyDaily = async ( dailyId: number, body: PrepareStudyRequest, ) => { - const res = await axiosInstance.put(`/study/daily/${dailyId}/prepare`, body); + const res = await axiosClientInstance.put( + `/study/daily/${dailyId}/prepare`, + body, + ); return res.data; }; @@ -61,7 +64,7 @@ export const completeStudy = async ( dailyStudyId: number, body: CompleteStudyRequest, ) => { - const res = await axiosInstance.post( + const res = await axiosClientInstance.post( `/study/daily/${dailyStudyId}/complete`, body, ); @@ -80,7 +83,7 @@ export const postJoinStudy = async (payload: JoinStudyRequest) => { ), ); - const res = await axiosInstance.post('/matching/apply', cleanPayload); + const res = await axiosClientInstance.post('/matching/apply', cleanPayload); return res.data; }; @@ -89,7 +92,7 @@ export const postJoinStudy = async (payload: JoinStudyRequest) => { export const getWeeklyParticipation = async ( params: GetDailyStudyDetailParams2, ): Promise => { - const res = await axiosInstance.get('/study/week/participation', { + const res = await axiosClientInstance.get('/study/week/participation', { params, }); diff --git a/src/shared/tanstack-query/axios.ts b/src/shared/tanstack-query/axios.client.ts similarity index 82% rename from src/shared/tanstack-query/axios.ts rename to src/shared/tanstack-query/axios.client.ts index c6cc36c9..26a5f647 100644 --- a/src/shared/tanstack-query/axios.ts +++ b/src/shared/tanstack-query/axios.client.ts @@ -5,7 +5,7 @@ import { getCookie } from './cookie'; // * 인증이 필요한 client-side axios 인스턴스 // json 요청 -export const axiosInstance = axios.create({ +export const axiosClientInstance = axios.create({ baseURL: `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/`, timeout: 10000, headers: { @@ -15,7 +15,7 @@ export const axiosInstance = axios.create({ }); // multipart 요청용 -export const axiosInstanceForMultipart = axios.create({ +export const axiosClientInstanceForMultipart = axios.create({ baseURL: `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/`, timeout: 10000, headers: { @@ -33,8 +33,8 @@ const onRequestClient = (config: InternalAxiosRequestConfig) => { return config; }; -axiosInstance.interceptors.request.use(onRequestClient); -axiosInstanceForMultipart.interceptors.request.use(onRequestClient); +axiosClientInstance.interceptors.request.use(onRequestClient); +axiosClientInstanceForMultipart.interceptors.request.use(onRequestClient); const onResponseErrorClient = async (error: unknown) => { if (isAxiosError(error) && error.response) { @@ -53,7 +53,7 @@ const onResponseErrorClient = async (error: unknown) => { } }; -axiosInstance.interceptors.response.use( +axiosClientInstance.interceptors.response.use( (config) => config, onResponseErrorClient, ); From 688e349d0bbde40f8c3cf018e244dab77c6eee89 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:34:14 +0900 Subject: [PATCH 13/17] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=84=B1=EA=B3=B5=20=EA=B3=B5=ED=86=B5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20useMutation=EC=9D=98=20onSuccess=EC=97=90?= =?UTF-8?q?=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그아웃 API 응답 형식을 변경하여 빈 배열을 반환하도록 수정 - useLogoutMutation 훅을 생성하여 로그아웃 로직을 통합 - 로그아웃 성공 시 GTM 이벤트 전송 및 쿠키 삭제, React Query 캐시 초기화, 로그인 페이지로 리다이렉트 추가 --- src/features/auth/api/auth.ts | 5 +-- src/features/auth/model/use-auth-mutation.ts | 29 ++++++++++++++-- src/features/auth/ui/header-user-dropdown.tsx | 33 ++----------------- src/widgets/my-page/sidebar.tsx | 28 ++-------------- 4 files changed, 36 insertions(+), 59 deletions(-) diff --git a/src/features/auth/api/auth.ts b/src/features/auth/api/auth.ts index 5bf2fa3d..fc50873f 100644 --- a/src/features/auth/api/auth.ts +++ b/src/features/auth/api/auth.ts @@ -34,8 +34,9 @@ export async function getMemberId() { } // 로그아웃 API -export const logout = async (): Promise => { +// 성공하면, content는 빈배열로 응답 +export const logout = async (): Promise> => { const res = await axiosClientInstance.post('/auth/logout'); - return res.data.statusCode; + return res.data.content; }; diff --git a/src/features/auth/model/use-auth-mutation.ts b/src/features/auth/model/use-auth-mutation.ts index 4ec5aa3c..6e327941 100644 --- a/src/features/auth/model/use-auth-mutation.ts +++ b/src/features/auth/model/use-auth-mutation.ts @@ -1,7 +1,11 @@ // 데이터 변경(Mutation) 을 담당하는 커스텀 훅 -import { useMutation } from '@tanstack/react-query'; +import { sendGTMEvent } from '@next/third-parties/google'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/navigation'; import { logout, signUp, uploadProfileImage } from '@/features/auth/api/auth'; +import { hashValue } from '@/shared/lib/hash'; +import { deleteCookie, getCookie } from '@/shared/tanstack-query/cookie'; import { SignUpResponse } from './types'; // 회원가입 요청 커스텀 훅 @@ -27,7 +31,28 @@ export function useUploadProfileImageMutation() { } export const useLogoutMutation = () => { - return useMutation({ + const queryClient = useQueryClient(); + const router = useRouter(); + + return useMutation({ mutationFn: logout, + onSuccess: () => { + const memberId = getCookie('memberId'); + + if (memberId) + sendGTMEvent({ + event: 'custom_member_logout', + dl_timestamp: new Date().toISOString(), + dl_member_id: hashValue(memberId), + }); + + deleteCookie('accessToken'); + deleteCookie('memberId'); + + queryClient.clear(); + + router.push('/login'); + router.refresh(); + }, }); }; diff --git a/src/features/auth/ui/header-user-dropdown.tsx b/src/features/auth/ui/header-user-dropdown.tsx index 94292a47..b63fe292 100644 --- a/src/features/auth/ui/header-user-dropdown.tsx +++ b/src/features/auth/ui/header-user-dropdown.tsx @@ -1,48 +1,21 @@ 'use client'; -import { sendGTMEvent } from '@next/third-parties/google'; -import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/navigation'; -import { hashValue } from '@/shared/lib/hash'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/shared/shadcn/ui/dropdown-menu'; -import { deleteCookie, getCookie } from '@/shared/tanstack-query/cookie'; import UserAvatar from '@/shared/ui/avatar'; -import { logout } from '../api/auth'; +import { useLogoutMutation } from '../model/use-auth-mutation'; export default function HeaderUserDropdown({ userImg }: { userImg: string }) { - const queryClient = useQueryClient(); const router = useRouter(); + const { mutateAsync: logout } = useLogoutMutation(); const handleLogout = async () => { - try { - // 1. 서버에 로그아웃 요청 (refresh token 삭제) - await logout(); - - const memberId = getCookie('memberId'); - sendGTMEvent({ - event: 'custom_member_logout', - dl_timestamp: new Date().toISOString(), - dl_member_id: hashValue(memberId), - }); - - // 2. 클라이언트의 access token 삭제 - deleteCookie('accessToken'); - deleteCookie('memberId'); - - // 3. React Query 캐시 초기화 - queryClient.clear(); - - // 4. 홈으로 리다이렉트 - router.push('/'); - router.refresh(); // 전체 페이지 리프레시 - } catch (error) { - console.error('로그아웃 실패:', error); - } + await logout(); }; return ( diff --git a/src/widgets/my-page/sidebar.tsx b/src/widgets/my-page/sidebar.tsx index 2e9d9cb1..e3f5fbf7 100644 --- a/src/widgets/my-page/sidebar.tsx +++ b/src/widgets/my-page/sidebar.tsx @@ -1,38 +1,16 @@ 'use client'; -import { sendGTMEvent } from '@next/third-parties/google'; -import { useQueryClient } from '@tanstack/react-query'; import { usePathname, useRouter } from 'next/navigation'; -import { logout } from '@/features/auth/api/auth'; -import { hashValue } from '@/shared/lib/hash'; +import { useLogoutMutation } from '@/features/auth/model/use-auth-mutation'; import { cn } from '@/shared/shadcn/lib/utils'; -import { deleteCookie, getCookie } from '@/shared/tanstack-query/cookie'; export default function Sidebar() { const router = useRouter(); - const queryClient = useQueryClient(); const pathname = usePathname(); + const { mutateAsync: logout } = useLogoutMutation(); const handleLogout = async () => { - try { - await logout(); - - const memberId = getCookie('memberId'); - sendGTMEvent({ - event: 'custom_member_logout', - dl_timestamp: new Date().toISOString(), - dl_member_id: hashValue(memberId), - }); - - deleteCookie('accessToken'); - deleteCookie('memberId'); - - queryClient.clear(); - router.push('/'); - router.refresh(); - } catch (error) { - console.error('로그아웃 실패:', error); - } + await logout(); }; return ( From 0c2367c96986d0fc9ae8db0e4f46c4377de75c27 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:34:45 +0900 Subject: [PATCH 14/17] =?UTF-8?q?style:=20prettier=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/lib/server-cookie.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/lib/server-cookie.ts b/src/shared/lib/server-cookie.ts index 1f422428..233c8bf1 100644 --- a/src/shared/lib/server-cookie.ts +++ b/src/shared/lib/server-cookie.ts @@ -9,11 +9,11 @@ export const getServerCookie = async ( return value ?? undefined; }; -export const setServerCookie = async( +export const setServerCookie = async ( name: string, value: string, options: { path?: string } = {}, ): Promise => { const cookieStore = await cookies(); cookieStore.set(name, value, { path: '/', ...options }); -}; \ No newline at end of file +}; From 2639c8cc08554c6d017e23203da47605d9372a35 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 00:54:28 +0900 Subject: [PATCH 15/17] =?UTF-8?q?refactor:=20redirection=20url=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/redirection/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/redirection/page.tsx b/app/redirection/page.tsx index 279307b3..97e271c1 100644 --- a/app/redirection/page.tsx +++ b/app/redirection/page.tsx @@ -49,13 +49,13 @@ function RedirectionContent() { handleRedirection().catch(console.error); }, [searchParams, router, queryClient]); // 의존성 추가 - return
처리중...
; + return <>; } // useSearchParams() should be wrapped in a suspense boundary export default function RedirectionPage() { return ( - 로딩중...}> + }> ); From a0ad64a5d29654794f98041ba101b99abadf4194 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 01:45:27 +0900 Subject: [PATCH 16/17] =?UTF-8?q?refactor:=20axiosAllInstance=EC=9D=98=20w?= =?UTF-8?q?ithCredentials=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/tanstack-query/axios.all.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shared/tanstack-query/axios.all.ts b/src/shared/tanstack-query/axios.all.ts index 791be0bb..164cfc7a 100644 --- a/src/shared/tanstack-query/axios.all.ts +++ b/src/shared/tanstack-query/axios.all.ts @@ -8,7 +8,6 @@ export const axiosAllInstance = axios.create({ headers: { 'Content-Type': 'application/json', }, - withCredentials: true, }); axiosAllInstance.interceptors.response.use( From 35fb30eeab4f5234937fff2187575cff7fab91a0 Mon Sep 17 00:00:00 2001 From: aken-you Date: Thu, 7 Aug 2025 01:56:00 +0900 Subject: [PATCH 17/17] =?UTF-8?q?refactor:=20access=20token=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20=ED=95=A8=EC=88=98=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/tanstack-query/get-new-access-token.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/shared/tanstack-query/get-new-access-token.ts diff --git a/src/shared/tanstack-query/get-new-access-token.ts b/src/shared/tanstack-query/get-new-access-token.ts deleted file mode 100644 index c470468b..00000000 --- a/src/shared/tanstack-query/get-new-access-token.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { axiosAllInstance } from './axios.all'; - -export const getNewAccessToken = async (): Promise<{ - accessToken: string; -}> => { - const res = await axiosAllInstance.get('/auth/refresh'); - - return res.data.content; -};