From 259045263ee807bbedffaee2edce75ef4cd0fe1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?DESKTOP-C2KV1NR=5C=EC=9C=A0=ED=98=84=EC=9A=B1?= Date: Sat, 12 Oct 2024 16:42:37 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20axios=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/http.ts | 0 package-lock.json | 74 +++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 api/http.ts diff --git a/api/http.ts b/api/http.ts new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json index b639965..6249e12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@radix-ui/react-select": "^2.1.2", "@tanstack/react-query": "^5.51.21", "@tanstack/react-query-devtools": "^5.51.21", + "axios": "^1.7.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "eslint": "^8.57.0", @@ -6450,6 +6451,11 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -6472,6 +6478,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -7432,6 +7448,17 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -7954,6 +7981,14 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9428,6 +9463,25 @@ "node": ">=0.4.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -9533,6 +9587,19 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -11321,7 +11388,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -11330,7 +11396,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -12827,6 +12892,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", diff --git a/package.json b/package.json index 01f9f40..a6e0cb0 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-select": "^2.1.2", "@tanstack/react-query": "^5.51.21", "@tanstack/react-query-devtools": "^5.51.21", + "axios": "^1.7.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "eslint": "^8.57.0", From 8060d218f719bb4adc55f94d5abc85a162d5bdcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?DESKTOP-C2KV1NR=5C=EC=9C=A0=ED=98=84=EC=9A=B1?= Date: Sat, 12 Oct 2024 16:48:27 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20axios=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1,=20http=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=A9=94=EB=93=9C=20=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84,=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=91=EB=8B=B5=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EC=84=A4=EC=A0=95,=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=ED=95=A8=EC=88=98(handleAxiosError)=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/http.ts | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/api/http.ts b/api/http.ts index e69de29..1aabb47 100644 --- a/api/http.ts +++ b/api/http.ts @@ -0,0 +1,80 @@ +import axios, { + AxiosError, + AxiosRequestConfig, + AxiosResponse, + InternalAxiosRequestConfig, +} from 'axios'; +import qs from 'qs'; + +type APIResponse = { + success: boolean; + msg: string; + data?: T; +}; + +export const client = axios.create({ + baseURL: 'https://api.allyouraffle.co.kr', + timeout: 3000, + timeoutErrorMessage: '서버 요청 시간 초과되었습니다.', + paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'comma' }), +}); + +const handleHeadersWithAccessToken = (config: AxiosRequestConfig): InternalAxiosRequestConfig => { + const accessToken = localStorage.getItem('access_token'); + config.headers = { + ...config.headers, + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }; + + return config as InternalAxiosRequestConfig; +}; + +client.interceptors.request.use(handleHeadersWithAccessToken); +client.interceptors.response.use( + (response: AxiosResponse) => response, + (error: AxiosError) => Promise.reject(error), +); + +export const http = { + get: function get(url: string, config?: AxiosRequestConfig) { + return client.get>(url, config).then((res) => res.data); + }, + post: function post( + url: string, + data?: Request, + config?: AxiosRequestConfig, + ) { + return client.post>(url, data, config).then((res) => res.data); + }, + put: function put( + url: string, + data?: Request, + config?: AxiosRequestConfig, + ) { + return client.put>(url, data, config).then((res) => res.data); + }, + patch: function patch( + url: string, + data?: Request, + config?: AxiosRequestConfig, + ) { + return client.patch>(url, data, config).then((res) => res.data); + }, + delete: function del(url: string, config?: AxiosRequestConfig) { + return client.delete>(url, config).then((res) => res.data); + }, +}; + +export const handleAxiosError = (error: Error) => { + if (axios.isAxiosError(error)) { + const axiosError: AxiosError = error; + console.error('Axios Error:', axiosError); + return axiosError.response?.data as APIResponse; + } + console.log('Unknown Error: ', error); + return { + success: false, + msg: '알 수 없는 오류가 발생했습니다.', + }; +}; From 8634c3478d13ad148f6f0509c1bf64f371effc69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?DESKTOP-C2KV1NR=5C=EC=9C=A0=ED=98=84=EC=9A=B1?= Date: Sun, 13 Oct 2024 11:50:59 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:ticketsApi=EB=A5=BC=20=20fetch->?= =?UTF-8?q?=20axios=EB=A1=9C=20=EA=B5=90=20=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/user/ticketsApi.ts | 54 +++++++--------------- components/Advertisement/Advertisement.tsx | 24 ++-------- components/Header/HeaderNav.tsx | 13 +++++- components/Items/ItemDetail.tsx | 2 +- components/Items/ItemStyle.tsx | 2 +- lib/hooks/useTicketPlusOne.ts | 4 +- 6 files changed, 35 insertions(+), 64 deletions(-) diff --git a/api/user/ticketsApi.ts b/api/user/ticketsApi.ts index a21ae56..682fef5 100644 --- a/api/user/ticketsApi.ts +++ b/api/user/ticketsApi.ts @@ -1,49 +1,29 @@ -import useAuthStore from '../../lib/store/useAuthStore'; -import baseURL from '../baseURL'; +import { AxiosResponse } from 'axios'; +import { client } from '../http'; /** - * 유저의 응모권 갯수 가져오기 - * @param userToken + * @description 유저의 응모권 갯수 가져오기 */ -async function getTickets(userToken: string): Promise { - const { logout } = useAuthStore.getState(); +const getTickets = async (): Promise => { try { - const response = await fetch(`${baseURL}/api/v1/user/tickets`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${userToken}`, - }, - }); - if (response.status === 401) { - console.error('인증 실패: 토큰이 만료되었습니다.'); - logout(); - } - if (!response.ok) { - throw new Error('응모권 불러오기 실패'); - } - return response.json(); + const response = await client.get('/api/v1/user/tickets'); + return response.data; } catch (error) { - throw new Error(`응모권 불러오기 실패: ${error}`); + console.log('응모권 불러오기 실패:', error); + throw error; } -} +}; -async function postTicketsPlusOne(userToken: string): Promise { +/** + * @description 유저의 으옹권 갯수 +1 추가하기 + */ +const postTicketsPlusOne = async (): Promise> => { try { - const response = await fetch(`${baseURL}/api/v1/user/tickets/plus_one`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${userToken}`, - }, - }); - if (!response.ok) { - throw new Error('티켓 추가 실패'); - } - return response.json(); + return await client.post('/api/v1/user/tickets/plus_one'); } catch (error) { - throw new Error(`티켓 추가 실패: ${error}`); + console.log('응모권 추가 실패:', error); + throw error; } -} +}; export { getTickets, postTicketsPlusOne }; diff --git a/components/Advertisement/Advertisement.tsx b/components/Advertisement/Advertisement.tsx index 3bee724..deae775 100644 --- a/components/Advertisement/Advertisement.tsx +++ b/components/Advertisement/Advertisement.tsx @@ -1,11 +1,9 @@ 'use client'; import { useEffect, useState } from 'react'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import useTicketPlusOne from '@/lib/hooks/useTicketPlusOne'; import { KakaoAdFit } from '../KakaoAdFit'; -import { postTicketsPlusOne } from '../../api/user/ticketsApi'; import Button from '../../lib/common/Button'; -import useAuthStore from '../../lib/store/useAuthStore'; import { AdvertisementProps } from '../../lib/types/advertisement'; export default function Advertisement({ onClose }: AdvertisementProps) { @@ -13,22 +11,7 @@ export default function Advertisement({ onClose }: AdvertisementProps) { const [countdown, setCountdown] = useState(3); const [isButtonEnabled, setIsButtonEnabled] = useState(false); - const userToken = useAuthStore((state) => state.userToken); - const queryClient = useQueryClient(); - - const mutate = useMutation({ - mutationKey: ['postTicketsPlusOne'], - mutationFn: () => postTicketsPlusOne(userToken), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['getTickets'] }); - alert('티켓 추가 성공'); - onClose(); - }, - onError: (error: Error) => { - alert('티켓 추가 실패'); - throw error; - }, - }); + const { mutate } = useTicketPlusOne(); useEffect(() => { if (countdown > 0) { @@ -43,8 +26,9 @@ export default function Advertisement({ onClose }: AdvertisementProps) { const handleCloseButton = () => { if (isButtonEnabled) { - mutate.mutate(); + mutate(); setIsToggle(!isToggle); + onClose(); } }; diff --git a/components/Header/HeaderNav.tsx b/components/Header/HeaderNav.tsx index c3f8801..39879ee 100644 --- a/components/Header/HeaderNav.tsx +++ b/components/Header/HeaderNav.tsx @@ -21,14 +21,23 @@ export default function HeaderNav() { * 사용자 응모권 갯수를 나타내는 useQuery * !!enabled: userToken이 존재할 때만 실행 */ - const { data: ticketsCount, isLoading } = useQuery({ + const { + data: ticketsCount, + isLoading, + isError, + error, + } = useQuery({ queryKey: ['getTickets'], - queryFn: () => getTickets(userToken), + queryFn: getTickets, + staleTime: 1000 * 60 * 1, enabled: !!userToken, }); if (isLoading) return
Loading...
; + if (isError) + return
Error: {error instanceof Error ? error.message : JSON.stringify(error)}
; + const handleProfileClick = () => { setIsPopverOpen(!isPopverOpen); }; diff --git a/components/Items/ItemDetail.tsx b/components/Items/ItemDetail.tsx index 4c1a3ac..0dbf24f 100644 --- a/components/Items/ItemDetail.tsx +++ b/components/Items/ItemDetail.tsx @@ -51,7 +51,7 @@ export default function ItemDetail({ params: { id } }: { params: { id: number } const { data: ticketsCount } = useQuery({ queryKey: ['getTickets'], - queryFn: () => getTickets(userToken), + queryFn: () => getTickets(), enabled: !!userToken, }); diff --git a/components/Items/ItemStyle.tsx b/components/Items/ItemStyle.tsx index eb951de..26e99f3 100644 --- a/components/Items/ItemStyle.tsx +++ b/components/Items/ItemStyle.tsx @@ -59,7 +59,7 @@ export default function ItemStyle({ */ const { data: ticketsCount } = useQuery({ queryKey: ['getTickets'], - queryFn: () => getTickets(userToken), + queryFn: () => getTickets(), enabled: !!userToken, staleTime: 1000 * 60, }); diff --git a/lib/hooks/useTicketPlusOne.ts b/lib/hooks/useTicketPlusOne.ts index c89a200..cac2237 100644 --- a/lib/hooks/useTicketPlusOne.ts +++ b/lib/hooks/useTicketPlusOne.ts @@ -1,14 +1,12 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import useAuthStore from '../store/useAuthStore'; import { postTicketsPlusOne } from '../../api/user/ticketsApi'; export default function useTicketPlusOne() { - const userToken = useAuthStore((state) => state.userToken); const queryClient = useQueryClient(); return useMutation({ mutationKey: ['postTicketPlusOne'], - mutationFn: () => postTicketsPlusOne(userToken), + mutationFn: postTicketsPlusOne, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['getTickets'] }); alert('응모권이 추가되었습니다.');