From e9883dc8829c6e61b0a3559e25a9c878fe4af0f3 Mon Sep 17 00:00:00 2001 From: Yeolyi Date: Sun, 7 Apr 2024 02:51:14 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20server=20action=EC=9D=98=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20status=20code=EB=A5=BC=20=ED=81=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=96=B8=ED=8A=B8=EC=97=90=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yaml | 2 +- actions/errorHandler.ts | 19 +++++++++++++++++++ actions/reservation.ts | 5 +++-- apis/common.ts | 11 ++--------- apis/index.ts | 3 +-- .../helper/modals/useAddReservation.ts | 11 ++++------- utils/serverActionError.ts | 16 ++++++++++++++++ 7 files changed, 46 insertions(+), 21 deletions(-) create mode 100644 actions/errorHandler.ts create mode 100644 utils/serverActionError.ts diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index f7eed7b88..bb63d769d 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -4,7 +4,7 @@ on: push: branches: - main - - qa + - qa2 jobs: build: diff --git a/actions/errorHandler.ts b/actions/errorHandler.ts new file mode 100644 index 000000000..7d8317b43 --- /dev/null +++ b/actions/errorHandler.ts @@ -0,0 +1,19 @@ +// https://github.com/vercel/next.js/discussions/49506 + +'use server'; + +import { isDynamicUsageError } from 'next/dist/export/helpers/is-dynamic-usage-error'; + +import { CustomError } from '@/utils/serverActionError'; + +export const withErrorHandler = , U>(fn: (...args: T) => Promise) => { + return async (...args: T): Promise => { + try { + return await fn(...args); + } catch (e) { + if (isDynamicUsageError(e)) throw e; + + return { error: { message: (e as Error).message } }; + } + }; +}; diff --git a/actions/reservation.ts b/actions/reservation.ts index b9c2904db..138fdf6cf 100644 --- a/actions/reservation.ts +++ b/actions/reservation.ts @@ -6,18 +6,19 @@ import { FETCH_TAG_RESERVATION } from '@/constants/network'; import { Reservation, ReservationPostBody, ReservationPreview } from '@/types/reservation'; +import { withErrorHandler } from './errorHandler'; import { deleteRequest, getRequest, postRequest } from '../apis'; const reservationPath = '/reservation'; -export const postReservation = async (body: ReservationPostBody) => { +export const postReservation = withErrorHandler(async (body: ReservationPostBody) => { await postRequest(reservationPath, { body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, jsessionID: true, }); revalidateTag(FETCH_TAG_RESERVATION); -}; +}); export const getWeeklyReservation = async (params: { roomId: number; diff --git a/apis/common.ts b/apis/common.ts index f53446a12..e6debd0bd 100644 --- a/apis/common.ts +++ b/apis/common.ts @@ -1,14 +1,7 @@ export const BASE_URL = 'https://cse-dev-waffle.bacchus.io/api/v1'; -export class NetworkError extends Error { - statusCode: number; - constructor(statusCode: number) { - super(`${statusCode} 에러`); - this.statusCode = statusCode; - } -} - export const checkError = (response: Response) => { if (response.ok) return; - throw new NetworkError(response.status); + // server action 에러 처리를 위해 status code만 깔끔하게 담음 + throw new Error(response.status.toString()); }; diff --git a/apis/index.ts b/apis/index.ts index a6fa91d7d..8addfc44c 100644 --- a/apis/index.ts +++ b/apis/index.ts @@ -2,7 +2,7 @@ import { cookies } from 'next/headers'; import { objToQueryString } from '@/utils/convertParams'; -import { BASE_URL, NetworkError, checkError } from './common'; +import { BASE_URL, checkError } from './common'; type CredentialRequestInit = RequestInit & { jsessionID?: boolean }; @@ -53,7 +53,6 @@ const fetchWithRetry = async ( try { return await _fetch(url, method, init); } catch (e) { - if (e instanceof NetworkError) throw e; if (remain === 0) throw e; console.error(`fetchWithRetry: ${e} ${url} ${method} ${init}`); diff --git a/app/[locale]/reservations/[roomType]/[roomName]/helper/modals/useAddReservation.ts b/app/[locale]/reservations/[roomType]/[roomName]/helper/modals/useAddReservation.ts index b6198935e..0f826a080 100644 --- a/app/[locale]/reservations/[roomType]/[roomName]/helper/modals/useAddReservation.ts +++ b/app/[locale]/reservations/[roomType]/[roomName]/helper/modals/useAddReservation.ts @@ -2,12 +2,11 @@ import { useState, FormEventHandler } from 'react'; import { postReservation } from '@/actions/reservation'; -import { NetworkError } from '@/apis/common'; - import { ReservationPostBody } from '@/types/reservation'; import { isSameDay } from '@/utils/date'; import { refreshPage } from '@/utils/refreshPage'; +import { handleServerAction } from '@/utils/serverActionError'; import { infoToast, errorToast } from '@/utils/toast'; import getOptimalEndTime from './getOptimalEndTime'; @@ -30,7 +29,7 @@ export default function useAddReservation(roomId: number) { } try { - await postReservation(body); + handleServerAction(await postReservation(body)); // TODO: revalidate같은거 써서 좀 더 예쁘게 refreshPage(); } catch (e) { @@ -103,14 +102,12 @@ const checkSubmit = (body: ReservationPostBody) => { }; const handleError = (e: unknown) => { - if (e instanceof NetworkError) { - if (e.statusCode === 409) { + if (e instanceof Error) { + if (e.message === '409') { errorToast('해당 위치에 이미 예약이 존재합니다.'); } else { errorToast(e.message); } - } else if (e instanceof Error) { - errorToast(e.message); } else { errorToast('알 수 없는 에러'); } diff --git a/utils/serverActionError.ts b/utils/serverActionError.ts new file mode 100644 index 000000000..d65094404 --- /dev/null +++ b/utils/serverActionError.ts @@ -0,0 +1,16 @@ +export type CustomError = { error: { message: string } }; + +export const isApiError = (response: T | CustomError): response is CustomError => { + return typeof response === 'object' && response !== null && 'error' in response; +}; + +export function throwIfError(response: T): asserts response is Exclude { + if (isApiError(response)) { + throw new Error(response.error.message); + } +} + +export function handleServerAction(response: T): Exclude { + throwIfError(response); + return response; +}