diff --git a/src/hooks/useAxios.ts b/src/hooks/useAxios.ts new file mode 100644 index 0000000..ac59166 --- /dev/null +++ b/src/hooks/useAxios.ts @@ -0,0 +1,93 @@ +import axios from 'axios' +import { useSpinnerStore } from '@/store/spinnerStore' + +export const useAxios = () => { + const { setIsShow } = useSpinnerStore() + + const axiosInstance = (url: string | null | undefined) => { + const baseURL = url || process.env.NEXT_PUBLIC_API_URL + const instance = axios.create({ + baseURL, + headers: { + Accept: 'application/json', + }, + }) + + instance.interceptors.request.use( + (config) => { + // console.log('🚀 ~ config:', config) + setIsShow(true) + return config + }, + (error) => { + return Promise.reject(error) + }, + ) + + instance.interceptors.response.use( + (response) => { + response.data = transferResponse(response) + setIsShow(false) + return response + }, + (error) => { + // 에러 처리 로직 + return Promise.reject(error) + }, + ) + + return instance + } + + // response데이터가 array, object에 따라 분기하여 키 변환 + const transferResponse = (response: any) => { + if (!response.data) return response.data + + // 배열인 경우 각 객체의 키를 변환 + if (Array.isArray(response.data)) { + return response.data.map((item: any) => transformObjectKeys(item)) + } + + // 단일 객체인 경우 + return transformObjectKeys(response.data) + } + + // camel case object 반환 + const transformObjectKeys = (obj: any): any => { + if (Array.isArray(obj)) { + return obj.map(transformObjectKeys) + } + + if (obj !== null && typeof obj === 'object') { + return Object.keys(obj).reduce((acc: any, key: string) => { + let transformedKey = key + + // Handle uppercase snake_case (e.g., USER_NAME -> userName) + // Handle lowercase snake_case (e.g., user_name -> userName) + if (/^[A-Z_]+$/.test(key) || /^[a-z_]+$/.test(key)) { + transformedKey = snakeToCamel(key) + } + // Handle single uppercase word (e.g., ROLE -> role) + else if (/^[A-Z]+$/.test(key)) { + transformedKey = key.toLowerCase() + } + // Preserve existing camelCase + + acc[transformedKey] = transformObjectKeys(obj[key]) + return acc + }, {}) + } + + return obj + } + + const snakeToCamel = (str: string): string => { + return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', '')) + } + + return { + axiosInstance, + transferResponse, + transformObjectKeys, + } +} diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index 1bfa614..6610cea 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -1,11 +1,10 @@ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import type { SurveyBasicInfo, SurveyDetailInfo, SurveyDetailRequest, SurveyDetailCoverRequest, SurveyRegistRequest } from '@/types/Survey' -import { axiosInstance } from '@/libs/axios' -import { useSurveyFilterStore } from '@/store/surveyFilterStore' -import { queryStringFormatter } from '@/utils/common-utils' -import { useSessionStore } from '@/store/session' import { useMemo } from 'react' -import { AxiosResponse } from 'axios' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { useSurveyFilterStore } from '@/store/surveyFilterStore' +import { useSessionStore } from '@/store/session' +import { useAxios } from './useAxios' +import { queryStringFormatter } from '@/utils/common-utils' export const requiredFields = [ { @@ -75,6 +74,7 @@ export function useServey(id?: number): { const queryClient = useQueryClient() const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore() const { session } = useSessionStore() + const { axiosInstance } = useAxios() const { data: surveyListData, diff --git a/src/libs/axios.ts b/src/libs/axios.ts index 0abc6ab..6007c47 100644 --- a/src/libs/axios.ts +++ b/src/libs/axios.ts @@ -1,3 +1,4 @@ +import { useSpinnerStore } from '@/store/spinnerStore' import axios from 'axios' export const axiosInstance = (url: string | null | undefined) => { diff --git a/src/providers/EdgeProvider.tsx b/src/providers/EdgeProvider.tsx index 77b8edd..e04f0e5 100644 --- a/src/providers/EdgeProvider.tsx +++ b/src/providers/EdgeProvider.tsx @@ -8,6 +8,8 @@ import { usePopupController } from '@/store/popupController' import { useSideNavState } from '@/store/sideNavState' import { useSessionStore } from '@/store/session' import { tracking } from '@/libs/tracking' +import Spinner from '@/components/ui/common/Spinner' +import { useSpinnerStore } from '@/store/spinnerStore' declare global { interface Window { @@ -28,6 +30,7 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp const { reset } = useSideNavState() const { setAlertMsg, setAlertBtn, setAlert, setAlert2, setAlert2BtnYes, setAlert2BtnNo } = usePopupController() const { session, setSession } = useSessionStore() + const { isShow, setIsShow } = useSpinnerStore() /** * 사용자 이벤트 트래킹 처리 @@ -110,5 +113,10 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp handlePageEvent(pathname) }, [pathname]) - return <>{children} + return ( + <> + {children} + {isShow && } + + ) } diff --git a/src/store/spinnerStore.ts b/src/store/spinnerStore.ts new file mode 100644 index 0000000..976382e --- /dev/null +++ b/src/store/spinnerStore.ts @@ -0,0 +1,21 @@ +import { create } from 'zustand' + +type SpinnerState = { + isShow: boolean + setIsShow: (isShow: boolean) => void + resetCount: () => void +} + +type InitialState = { + isShow: boolean +} + +const initialState: InitialState = { + isShow: false, +} + +export const useSpinnerStore = create((set) => ({ + ...initialState, + setIsShow: (isShow: boolean) => set({ isShow }), + resetCount: () => set(initialState), +}))