diff --git a/src/app/(onboarding)/signup/components/NicknameForm.tsx b/src/app/(onboarding)/signup/components/NicknameForm.tsx index 69ea9b3..43ea7e8 100644 --- a/src/app/(onboarding)/signup/components/NicknameForm.tsx +++ b/src/app/(onboarding)/signup/components/NicknameForm.tsx @@ -2,6 +2,7 @@ import Button from '@/components/Button' import NicknameInput from '@/components/TextInput/NicknameInput' +import { changeNickname } from '@/lib' import { useSession } from 'next-auth/react' import { useRouter } from 'next/navigation' import SketchIcon from 'public/icons/sketchIcons-1.svg' @@ -18,6 +19,7 @@ const NicknameForm = () => { update({ name: nickname, }) + await changeNickname(nickname) router.push('/signup/complete') } diff --git a/src/app/mypage/profileEdit/components/NicknameForm.tsx b/src/app/mypage/profileEdit/components/NicknameForm.tsx index a8ab987..f1db913 100644 --- a/src/app/mypage/profileEdit/components/NicknameForm.tsx +++ b/src/app/mypage/profileEdit/components/NicknameForm.tsx @@ -4,6 +4,7 @@ import Button from '@/components/Button' import { useSession } from 'next-auth/react' import { ReactNode, useState } from 'react' import NicknameInput from '@/components/TextInput/NicknameInput' +import { changeNickname } from '@/lib' import Title from './Title' const NicknameForm = ({ children }: { children: ReactNode }) => { @@ -15,6 +16,7 @@ const NicknameForm = ({ children }: { children: ReactNode }) => { update({ name: newName, }) + await changeNickname(newName) } return ( diff --git a/src/auth.ts b/src/auth.ts index 58e35b5..ed384f1 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -2,7 +2,7 @@ import NextAuth from 'next-auth' import Kakao from 'next-auth/providers/kakao' -import { changeNickname, login, refreshAT } from './lib/api/auth' +import { login, refreshAT } from './lib/api/auth' /* eslint-disable-next-line @typescript-eslint/naming-convention */ export const { handlers, signIn, signOut, auth, unstable_update } = NextAuth({ @@ -30,7 +30,6 @@ export const { handlers, signIn, signOut, auth, unstable_update } = NextAuth({ birthDt: '2024-08-11', // TODO: 기획 대기 gender: 'F', // TODO: 기획 대기 }) - user.name = nickName user.newUser = newUser user.accessToken = accessToken @@ -47,8 +46,7 @@ export const { handlers, signIn, signOut, auth, unstable_update } = NextAuth({ async jwt({ token, user, account, trigger, session }) { if (trigger === 'update' && session?.name) { const { name } = session - await changeNickname(name, token.accessToken) // server update - token.name = name // client update + token.name = name } if (trigger === 'update' && session?.accessToken) { token.accessToken = session.accessToken diff --git a/src/lib/api/auth.ts b/src/lib/api/auth.ts index f4b1d71..c3de855 100644 --- a/src/lib/api/auth.ts +++ b/src/lib/api/auth.ts @@ -1,33 +1,53 @@ import { SignInPayload, User } from '@/types' -import { post, put } from './base' -export const login = async (body: SignInPayload): Promise => { - const res = await post('/api/v1/oauth/sign-in', { - body: JSON.stringify(body), - }) +const handleResponse = async (res: Response) => { + const text = await res.text() + + if (!res.ok) { + throw new Error( + `Request failed: ${res.status} - ${res.statusText} - ${text || 'No error message provided'}`, + ) + } + + if (!text) { + throw new Error('No response body') + } - return res.data + try { + return JSON.parse(text) + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + } catch (error: any) { + throw new Error(`Failed to parse JSON: ${error.message}`) + } } -export const changeNickname = async (nickName: string, token: string) => { - return put('/api/v1/user/nickname', { +export const login = async (body: SignInPayload): Promise => { + const res = await fetch(`${process.env.API_HOST}/api/v1/oauth/sign-in`, { + method: 'POST', + body: JSON.stringify(body), headers: { - Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', }, - body: JSON.stringify({ nickName }), }) + + const data = await handleResponse(res) + return data.data } export const refreshAT = async (refreshToken: string) => { - const res = await put('/api/v1/oauth/re-issue', { + const res = await fetch(`${process.env.API_HOST}/api/v1/oauth/re-issue`, { + method: 'PUT', headers: { + 'Content-Type': 'application/json', Authorization: `Bearer ${refreshToken}`, }, }) + const data = await handleResponse(res) + return { - accessToken: res.data.accessToken, - refreshToken: res.data.refreshToken, - expiredDate: res.data.expiredDate, + accessToken: data.data.accessToken, + refreshToken: data.data.refreshToken, + expiredDate: data.data.expiredDate, } } diff --git a/src/lib/api/base.ts b/src/lib/api/base.ts index 4dc6796..eeaa33f 100644 --- a/src/lib/api/base.ts +++ b/src/lib/api/base.ts @@ -1,4 +1,14 @@ +'use server' + +import { auth, unstable_update as update, signOut } from '@/auth' import { RequestInit } from 'next/dist/server/web/spec-extension/request' +import { refreshAT } from './auth' + +const ERRORS = { + FETCH_FAILED: '데이터를 불러오는데 실패했습니다.', + SAVE_FAILED: '데이터를 저장하는데 실패했습니다.', + DELETE_FAILED: '데이터를 삭제하는데 실패했습니다.', +} const getBaseUrl = (useMocked: boolean) => useMocked ? 'http://localhost:3001' : process.env.API_HOST @@ -9,13 +19,49 @@ const fetchApi = async ( useMocked: boolean, errorMessage: string, ) => { - const res = await fetch(getBaseUrl(useMocked) + path, options) + const session = await auth() + + const fetchOptions = { + ...options, + headers: { + 'content-type': 'application/json', + ...options?.headers, + ...(session && { Authorization: `Bearer ${session?.accessToken}` }), + }, + } + + let res = await fetch(getBaseUrl(useMocked) + path, fetchOptions) + + if (res.status === 401 && session) { + const resJson = await res.json() + if (resJson.code === 'JWT002') { + // AT expired + try { + const newToken = await refreshAT(session.refreshToken) + + await update({ + accessToken: newToken.accessToken, + refreshToken: newToken.refreshToken, + expiredDate: newToken.expiredDate, + }) + + // retry original request + res = await fetch(getBaseUrl(useMocked) + path, fetchOptions) + } catch (e) { + signOut() + } + } else { + // RT expired or invalid + signOut() + } + } if (!res.ok) { - throw new Error(errorMessage) + throw new Error(`Error: ${res.status} - ${res.statusText}. ${errorMessage}`) } - return res.json() + const text = await res.text() + return text ? JSON.parse(text) : null } export const get = async ( @@ -28,13 +74,9 @@ export const get = async ( { ...options, method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, }, useMocked, - '데이터를 불러오는데 실패했습니다.', + ERRORS.FETCH_FAILED, ) export const post = async ( @@ -47,13 +89,9 @@ export const post = async ( { ...options, method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, }, useMocked, - '데이터를 저장하는데 실패했습니다.', + ERRORS.SAVE_FAILED, ) export const put = async ( @@ -66,13 +104,9 @@ export const put = async ( { ...options, method: 'PUT', - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, }, useMocked, - '데이터를 저장하는데 실패했습니다.', + ERRORS.SAVE_FAILED, ) export const deleteApi = async ( @@ -85,11 +119,7 @@ export const deleteApi = async ( { ...options, method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, }, useMocked, - '데이터를 삭제하는데 실패했습니다.', + ERRORS.DELETE_FAILED, ) diff --git a/src/lib/api/board.ts b/src/lib/api/board.ts index 95cd244..357e916 100644 --- a/src/lib/api/board.ts +++ b/src/lib/api/board.ts @@ -1,6 +1,5 @@ import { Board, CreateBoardPayload } from '@/types' -import { get } from './base' -import { authFetch } from './customFetch/authFetch' +import { get, post } from './base' export const getBoard = async (id: string): Promise => { const res = await get(`/api/v1/boards/${id}`, { @@ -15,8 +14,7 @@ export const getBoard = async (id: string): Promise => { export const postBoard = async ( payload: CreateBoardPayload, ): Promise => { - const res = await authFetch('/api/v1/boards', { - method: 'POST', + const res = await post('/api/v1/boards', { body: JSON.stringify(payload), }) diff --git a/src/lib/api/customFetch/authFetch.ts b/src/lib/api/customFetch/authFetch.ts deleted file mode 100644 index 705bc15..0000000 --- a/src/lib/api/customFetch/authFetch.ts +++ /dev/null @@ -1,51 +0,0 @@ -'use server' - -import { auth, unstable_update as update, signOut } from '@/auth' -import { refreshAT } from '../auth' - -export const authFetch = async (path: string, options: RequestInit) => { - const session = await auth() - - let res = await fetch(process.env.API_HOST + path, { - ...options, - headers: { - 'content-type': 'application/json', - ...options?.headers, - ...(session && { Authorization: `Bearer ${session?.accessToken}` }), - }, - }) - - if (res.status === 401 && session) { - const resJson = await res.json() - if (resJson.code === 'JWT002') { - // AT expired - try { - const newToken = await refreshAT(session.refreshToken) - - await update({ - accessToken: newToken.accessToken, - refreshToken: newToken.refreshToken, - expiredDate: newToken.expiredDate, - }) - - // retry original request - res = await fetch(process.env.API_HOST + path, { - ...options, - headers: { - 'content-type': 'application/json', - ...options?.headers, - Authorization: `Bearer ${newToken.accessToken}`, - }, - }) - } catch (e) { - signOut() - } - } else { - // RT expired or invalid - signOut() - } - } - - const text = await res.text() - return text ? JSON.parse(text) : null -} diff --git a/src/lib/api/user.ts b/src/lib/api/user.ts index d76e1bd..387e5ab 100644 --- a/src/lib/api/user.ts +++ b/src/lib/api/user.ts @@ -1,15 +1,20 @@ import { WithdrawUserPayload } from '@/types' -import { authFetch } from './customFetch/authFetch' +import { get, put } from './base' export const withdraw = async (body: WithdrawUserPayload) => { - return authFetch('/api/v1/user/withdraw', { - method: 'PUT', + return put('/api/v1/user/withdraw', { body: JSON.stringify(body), }) } +export const changeNickname = async (nickName: string) => { + return put(`/api/v1/user/nickname`, { + body: JSON.stringify({ nickName }), + }) +} + export const viewProfile = async () => { - return authFetch('/api/v1/user/profile', { + return get('/api/v1/user/profile', { next: { tags: ['profile'], },