From 3ec2b5fc2358122754a7d37b6709288f85ba12d9 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Fri, 10 Nov 2023 16:59:57 +0900 Subject: [PATCH 01/23] =?UTF-8?q?config:=20axios=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 26 ++++++++++++++++++++++++++ frontend/package.json | 1 + 2 files changed, 27 insertions(+) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f8e1f60a..a8eefaf1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -39,6 +39,7 @@ "@types/styled-components": "^5.1.26", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", + "axios": "^1.6.1", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", "core-js": "^3.31.1", @@ -7760,6 +7761,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", + "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 45c878af..dbbc95a8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,6 +43,7 @@ "@types/styled-components": "^5.1.26", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", + "axios": "^1.6.1", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", "core-js": "^3.31.1", From 895be60c13b0f331d73b87de425bf64e97ac5572 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Sun, 12 Nov 2023 21:17:13 +0900 Subject: [PATCH 02/23] =?UTF-8?q?feat:=20axios=20=EC=9D=B8=EC=8A=A4?= =?UTF-8?q?=ED=84=B4=EC=8A=A4=20=EB=B0=8F=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/remotes/axios.ts | 53 ++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 frontend/src/shared/remotes/axios.ts diff --git a/frontend/src/shared/remotes/axios.ts b/frontend/src/shared/remotes/axios.ts new file mode 100644 index 00000000..86c43c66 --- /dev/null +++ b/frontend/src/shared/remotes/axios.ts @@ -0,0 +1,53 @@ +import axios from 'axios'; +import { postRefreshAccessToken } from './auth'; +import type { AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'; + +const { BASE_URL } = process.env; + +const defaultConfig: AxiosRequestConfig = { + baseURL: BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, +}; + +export const clientBasic = axios.create(defaultConfig); +export const client = axios.create(defaultConfig); + +// 요청 인터셉터 +const setToken = (config: InternalAxiosRequestConfig) => { + const accessToken = localStorage.getItem('userToken'); + + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + + return config; +}; + +// 응답 인터셉터 +const refreshAccessTokenOnAuthError = async (error: AxiosError) => { + const config = error.config; + + if (error.response?.status === 401 && config?.headers.Authorization) { + try { + const staleAccessToken = localStorage.getItem('userToken') ?? ''; + const { accessToken } = await postRefreshAccessToken(staleAccessToken); + + localStorage.setItem('userToken', accessToken); + config.headers.Authorization = `Bearer ${accessToken}`; + + return client(config); + } catch { + // window.alert('세션이 만료되었습니다. 다시 로그인 해주세요'); + } + } + + return Promise.reject(error); +}; + +client.interceptors.request.use(setToken); +client.interceptors.response.use((response) => response, refreshAccessTokenOnAuthError); + +export default client; From 69605c17cdeaeb555e160db628cbf8c4e4244af6 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Sun, 12 Nov 2023 21:17:56 +0900 Subject: [PATCH 03/23] =?UTF-8?q?refactor:=20=EC=97=91=EC=84=B8=EC=8A=A4?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20refresh=20remote=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/remotes/auth.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 frontend/src/shared/remotes/auth.ts diff --git a/frontend/src/shared/remotes/auth.ts b/frontend/src/shared/remotes/auth.ts new file mode 100644 index 00000000..8c28ab39 --- /dev/null +++ b/frontend/src/shared/remotes/auth.ts @@ -0,0 +1,11 @@ +import { clientBasic } from './axios'; + +interface RefreshAccessTokenRes { + accessToken: string; +} + +export const postRefreshAccessToken = async (staleAccessToken: string) => { + const { data } = await clientBasic.post('/reissue', staleAccessToken); + + return data; +}; From 89bdb5f001b284928182ee29b03815fc62804a67 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Sun, 12 Nov 2023 22:29:45 +0900 Subject: [PATCH 04/23] =?UTF-8?q?refactor:=20=EB=B6=84=EB=A6=AC=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20type,=20remote=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comments/components/CommentForm.tsx | 14 +++++++------- .../comments/components/CommentList.tsx | 17 +++++------------ .../src/features/comments/remotes/comments.ts | 10 ++++++++++ .../src/features/comments/types/comment.type.ts | 6 ++++++ .../killingParts/remotes/killingPart.ts | 10 ++++++++++ frontend/src/pages/PartCollectingPage.tsx | 9 ++++----- 6 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 frontend/src/features/comments/remotes/comments.ts create mode 100644 frontend/src/features/comments/types/comment.type.ts create mode 100644 frontend/src/features/killingParts/remotes/killingPart.ts diff --git a/frontend/src/features/comments/components/CommentForm.tsx b/frontend/src/features/comments/components/CommentForm.tsx index 414f47a6..97d3c60b 100644 --- a/frontend/src/features/comments/components/CommentForm.tsx +++ b/frontend/src/features/comments/components/CommentForm.tsx @@ -8,23 +8,23 @@ import Avatar from '@/shared/components/Avatar'; import useModal from '@/shared/components/Modal/hooks/useModal'; import useToastContext from '@/shared/components/Toast/hooks/useToastContext'; import { useMutation } from '@/shared/hooks/useMutation'; -import fetcher from '@/shared/remotes'; +import { postComment } from '../remotes/comments'; interface CommentFormProps { - getComment: () => Promise; + getComments: () => Promise; songId: number; partId: number; } -const CommentForm = ({ getComment, songId, partId }: CommentFormProps) => { +const CommentForm = ({ getComments, songId, partId }: CommentFormProps) => { const [newComment, setNewComment] = useState(''); const { isOpen, closeModal: closeLoginModal, openModal: openLoginModal } = useModal(); const { user } = useAuthContext(); const isLoggedIn = !!user; - const { mutateData } = useMutation(() => - fetcher(`/songs/${songId}/parts/${partId}/comments`, 'POST', { content: newComment.trim() }) + const { mutateData: postNewComment } = useMutation(() => + postComment(songId, partId, newComment.trim()) ); const { showToast } = useToastContext(); @@ -38,11 +38,11 @@ const CommentForm = ({ getComment, songId, partId }: CommentFormProps) => { const submitNewComment: React.FormEventHandler = async (event) => { event.preventDefault(); - await mutateData(); + await postNewComment(); showToast('댓글이 등록되었습니다.'); resetNewComment(); - getComment(); + await getComments(); }; return ( diff --git a/frontend/src/features/comments/components/CommentList.tsx b/frontend/src/features/comments/components/CommentList.tsx index eca83391..8ddb9900 100644 --- a/frontend/src/features/comments/components/CommentList.tsx +++ b/frontend/src/features/comments/components/CommentList.tsx @@ -6,17 +6,10 @@ import useModal from '@/shared/components/Modal/hooks/useModal'; import Spacing from '@/shared/components/Spacing'; import SRHeading from '@/shared/components/SRHeading'; import useFetch from '@/shared/hooks/useFetch'; -import fetcher from '@/shared/remotes'; +import { getComments } from '../remotes/comments'; import Comment from './Comment'; import CommentForm from './CommentForm'; -interface Comment { - id: number; - content: string; - createdAt: string; - writerNickname: string; -} - interface CommentListProps { songId: number; partId: number; @@ -24,12 +17,12 @@ interface CommentListProps { const CommentList = ({ songId, partId }: CommentListProps) => { const { isOpen, openModal, closeModal } = useModal(false); - const { data: comments, fetchData: getComment } = useFetch(() => - fetcher(`/songs/${songId}/parts/${partId}/comments`, 'GET') + const { data: comments, fetchData: refetchComments } = useFetch(() => + getComments(songId, partId) ); useEffect(() => { - getComment(); + refetchComments(); }, [partId]); if (!comments) { @@ -73,7 +66,7 @@ const CommentList = ({ songId, partId }: CommentListProps) => { ))} - + ); diff --git a/frontend/src/features/comments/remotes/comments.ts b/frontend/src/features/comments/remotes/comments.ts new file mode 100644 index 00000000..c229226e --- /dev/null +++ b/frontend/src/features/comments/remotes/comments.ts @@ -0,0 +1,10 @@ +import fetcher from '@/shared/remotes'; +import type { Comment } from '../types/comment.type'; + +export const postComment = async (songId: number, partId: number, content: string) => { + await fetcher(`/songs/${songId}/parts/${partId}/comments`, 'POST', { content }); +}; + +export const getComments = async (songId: number, partId: number): Promise => { + return await fetcher(`/songs/${songId}/parts/${partId}/comments`, 'GET'); +}; diff --git a/frontend/src/features/comments/types/comment.type.ts b/frontend/src/features/comments/types/comment.type.ts new file mode 100644 index 00000000..914d8579 --- /dev/null +++ b/frontend/src/features/comments/types/comment.type.ts @@ -0,0 +1,6 @@ +export interface Comment { + id: number; + content: string; + createdAt: string; + writerNickname: string; +} diff --git a/frontend/src/features/killingParts/remotes/killingPart.ts b/frontend/src/features/killingParts/remotes/killingPart.ts new file mode 100644 index 00000000..1c3010f1 --- /dev/null +++ b/frontend/src/features/killingParts/remotes/killingPart.ts @@ -0,0 +1,10 @@ +import fetcher from '@/shared/remotes'; +import type { SongInfo } from '@/shared/types/song'; + +// PartCollectingPage에 존재하던 remote 함수입니다. +// useFetch(() => fetcher(`/songs/${songId}`, 'GET')) 로직에서 분리하였습니다. +// SongInfo type에는 killingPart[] 필드가 있는데, 마이파트 수집용 `노래 1개` 조회에서 해당 타입이 사용되고 있습니다. +// 추후 수정되어야 합니다. +export const getSong = async (songId: number): Promise => { + return await fetcher(`/songs/${songId}`, 'GET'); +}; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 822f190b..cf858359 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -1,8 +1,8 @@ -import { useParams } from 'react-router-dom'; import { Flex } from 'shook-layout'; import { styled } from 'styled-components'; import RegisterPart from '@/features/killingParts/components/RegisterPart'; import VideoController from '@/features/killingParts/components/VideoController'; +import { getSong } from '@/features/killingParts/remotes/killingPart'; import { CollectingPartProvider } from '@/features/songs/components/CollectingPartProvider'; import SongInformation from '@/features/songs/components/SongInformation'; import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; @@ -10,13 +10,12 @@ import Youtube from '@/features/youtube/components/Youtube'; import Spacing from '@/shared/components/Spacing'; import SRHeading from '@/shared/components/SRHeading'; import useFetch from '@/shared/hooks/useFetch'; -import fetcher from '@/shared/remotes'; -import type { SongInfo } from '@/shared/types/song'; +import useValidParams from '@/shared/hooks/useValidParams'; const PartCollectingPage = () => { - const { id: songId } = useParams(); + const { id: songId } = useValidParams(); // TODO: 조회 API 만들어야함. - const { data: songInfo } = useFetch(() => fetcher(`/songs/${songId}`, 'GET')); + const { data: songInfo } = useFetch(() => getSong(Number(songId))); if (!songInfo) return; const { id, title, singer, videoLength, songVideoId, albumCoverUrl } = songInfo; From e10bfbe7f65828e1c741854dc1d2d65a3fbfb180 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 13:47:59 +0900 Subject: [PATCH 05/23] =?UTF-8?q?refactor:=20remote=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EC=9D=98=20fetcher=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/comments/remotes/comments.ts | 10 ++++--- .../killingParts/components/RegisterPart.tsx | 6 ++-- .../killingParts/hooks/killingPart.ts | 4 +-- .../killingParts/remotes/killingPart.ts | 9 ++++-- .../features/member/components/MyPartList.tsx | 4 +-- .../src/features/member/remotes/member.ts | 6 ++-- .../features/member/remotes/memberParts.ts | 6 ++-- .../src/features/member/remotes/myPage.ts | 15 ++++++++-- .../src/features/search/remotes/search.ts | 18 ++++++++---- .../src/features/singer/remotes/singer.ts | 8 +++-- frontend/src/features/songs/remotes/likes.ts | 4 +-- frontend/src/features/songs/remotes/song.ts | 14 +++++---- frontend/src/features/songs/remotes/songs.ts | 29 +++++++++---------- frontend/src/pages/EditProfilePage.tsx | 6 ++-- 14 files changed, 83 insertions(+), 56 deletions(-) diff --git a/frontend/src/features/comments/remotes/comments.ts b/frontend/src/features/comments/remotes/comments.ts index c229226e..c8a38d18 100644 --- a/frontend/src/features/comments/remotes/comments.ts +++ b/frontend/src/features/comments/remotes/comments.ts @@ -1,10 +1,12 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; import type { Comment } from '../types/comment.type'; export const postComment = async (songId: number, partId: number, content: string) => { - await fetcher(`/songs/${songId}/parts/${partId}/comments`, 'POST', { content }); + await client.post(`/songs/${songId}/parts/${partId}/comments`, { content }); }; -export const getComments = async (songId: number, partId: number): Promise => { - return await fetcher(`/songs/${songId}/parts/${partId}/comments`, 'GET'); +export const getComments = async (songId: number, partId: number) => { + const { data } = await client.get(`/songs/${songId}/parts/${partId}/comments`); + + return data; }; diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index 8e72cc36..6a741d29 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -2,19 +2,20 @@ import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; -import { usePostKillingPart } from '@/features/killingParts/remotes/usePostKillingPart'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useModal from '@/shared/components/Modal/hooks/useModal'; import Modal from '@/shared/components/Modal/Modal'; import Spacing from '@/shared/components/Spacing'; +import { useMutation } from '@/shared/hooks/useMutation'; import { toPlayingTimeText } from '@/shared/utils/convertTime'; +import { postKillingPart } from '../hooks/killingPart'; const RegisterPart = () => { const { isOpen, openModal, closeModal } = useModal(); const { user } = useAuthContext(); const { interval, partStartTime, songId } = useCollectingPartContext(); const video = useVideoPlayerContext(); - const { createKillingPart } = usePostKillingPart(); + const { mutateData: createKillingPart } = useMutation(postKillingPart); const navigate = useNavigate(); // 현재 useMutation 훅이 response 객체를 리턴하지 않고 내부적으로 처리합니다. @@ -75,7 +76,6 @@ const RegisterButton = styled.button` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { padding: 11px 15px; - font-size: 18px; } `; diff --git a/frontend/src/features/killingParts/hooks/killingPart.ts b/frontend/src/features/killingParts/hooks/killingPart.ts index d711edb7..c86e8171 100644 --- a/frontend/src/features/killingParts/hooks/killingPart.ts +++ b/frontend/src/features/killingParts/hooks/killingPart.ts @@ -1,6 +1,6 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; import type { KillingPartPostRequest } from '@/shared/types/killingPart'; export const postKillingPart = async (songId: number, body: KillingPartPostRequest) => { - return await fetcher(`/songs/${songId}/member-parts`, 'POST', body); + await client.post(`/songs/${songId}/member-parts`, body); }; diff --git a/frontend/src/features/killingParts/remotes/killingPart.ts b/frontend/src/features/killingParts/remotes/killingPart.ts index 1c3010f1..e0ef0e45 100644 --- a/frontend/src/features/killingParts/remotes/killingPart.ts +++ b/frontend/src/features/killingParts/remotes/killingPart.ts @@ -1,10 +1,13 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; import type { SongInfo } from '@/shared/types/song'; // PartCollectingPage에 존재하던 remote 함수입니다. // useFetch(() => fetcher(`/songs/${songId}`, 'GET')) 로직에서 분리하였습니다. // SongInfo type에는 killingPart[] 필드가 있는데, 마이파트 수집용 `노래 1개` 조회에서 해당 타입이 사용되고 있습니다. // 추후 수정되어야 합니다. -export const getSong = async (songId: number): Promise => { - return await fetcher(`/songs/${songId}`, 'GET'); + +export const getSong = async (songId: number) => { + const { data } = await client.get(`/songs/${songId}`); + + return data; }; diff --git a/frontend/src/features/member/components/MyPartList.tsx b/frontend/src/features/member/components/MyPartList.tsx index fb95df95..f7106d2a 100644 --- a/frontend/src/features/member/components/MyPartList.tsx +++ b/frontend/src/features/member/components/MyPartList.tsx @@ -19,8 +19,8 @@ export type LikeKillingPart = Pick< const MyPartList = () => { const [tab, setTab] = useState('Like'); - const { data: likes } = useFetch(getLikeParts); - const { data: myParts } = useFetch(getMyParts); + const { data: likes } = useFetch(getLikeParts); + const { data: myParts } = useFetch(getMyParts); if (!likes || !myParts) { return null; diff --git a/frontend/src/features/member/remotes/member.ts b/frontend/src/features/member/remotes/member.ts index b9df67e0..f8975334 100644 --- a/frontend/src/features/member/remotes/member.ts +++ b/frontend/src/features/member/remotes/member.ts @@ -1,5 +1,5 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; -export const deleteMember = (memberId: number | undefined) => () => { - return fetcher(`/members/${memberId}`, 'DELETE'); +export const deleteMember = async (memberId: number) => { + await client.delete(`/members/${memberId}`); }; diff --git a/frontend/src/features/member/remotes/memberParts.ts b/frontend/src/features/member/remotes/memberParts.ts index 28ef3613..cce5e9bb 100644 --- a/frontend/src/features/member/remotes/memberParts.ts +++ b/frontend/src/features/member/remotes/memberParts.ts @@ -1,3 +1,5 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; -export const deleteMemberParts = (partId: number) => fetcher(`/member-parts/${partId}`, 'DELETE'); +export const deleteMemberParts = async (partId: number) => { + await client.delete(`/member-parts/${partId}`); +}; diff --git a/frontend/src/features/member/remotes/myPage.ts b/frontend/src/features/member/remotes/myPage.ts index b0ab55df..99eae927 100644 --- a/frontend/src/features/member/remotes/myPage.ts +++ b/frontend/src/features/member/remotes/myPage.ts @@ -1,5 +1,14 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; +import type { LikeKillingPart } from '../components/MyPartList'; -export const getLikeParts = () => fetcher('/my-page/like-parts', 'GET'); +export const getLikeParts = async () => { + const { data } = await client.get('/my-page/like-parts'); -export const getMyParts = () => fetcher('/my-page/my-parts', 'GET'); + return data; +}; + +export const getMyParts = async () => { + const { data } = await client.get('/my-page/my-parts'); + + return data; +}; diff --git a/frontend/src/features/search/remotes/search.ts b/frontend/src/features/search/remotes/search.ts index 86208433..63b3bffa 100644 --- a/frontend/src/features/search/remotes/search.ts +++ b/frontend/src/features/search/remotes/search.ts @@ -1,13 +1,21 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; import type { SingerDetail } from '../../singer/types/singer.type'; import type { SingerSearchPreview } from '../types/search.type'; -export const getSingerSearchPreview = async (query: string): Promise => { +export const getSingerSearchPreview = async (query: string) => { const encodedQuery = encodeURIComponent(query); - return await fetcher(`/search?keyword=${encodedQuery}&type=singer`, 'GET'); + const { data } = await client.get( + `/search?keyword=${encodedQuery}&type=singer` + ); + + return data; }; -export const getSingerSearch = async (query: string): Promise => { +export const getSingerSearch = async (query: string) => { const encodedQuery = encodeURIComponent(query); - return await fetcher(`/search?keyword=${encodedQuery}&type=singer&type=song`, 'GET'); + const { data } = await client.get( + `/search?keyword=${encodedQuery}&type=singer&type=song` + ); + + return data; }; diff --git a/frontend/src/features/singer/remotes/singer.ts b/frontend/src/features/singer/remotes/singer.ts index bea1d670..b201aed7 100644 --- a/frontend/src/features/singer/remotes/singer.ts +++ b/frontend/src/features/singer/remotes/singer.ts @@ -1,6 +1,8 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; import type { SingerDetail } from '../types/singer.type'; -export const getSingerDetail = async (singerId: number): Promise => { - return await fetcher(`/singers/${singerId}`, 'GET'); +export const getSingerDetail = async (singerId: number) => { + const { data } = await client.get(`/singers/${singerId}`); + + return data; }; diff --git a/frontend/src/features/songs/remotes/likes.ts b/frontend/src/features/songs/remotes/likes.ts index 7a766f0b..96545e1c 100644 --- a/frontend/src/features/songs/remotes/likes.ts +++ b/frontend/src/features/songs/remotes/likes.ts @@ -1,5 +1,5 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; export const putKillingPartLikes = async (songId: number, partId: number, likeStatus: boolean) => { - return await fetcher(`/songs/${songId}/parts/${partId}/likes`, 'PUT', { likeStatus }); + await client.put(`/songs/${songId}/parts/${partId}/likes`, { likeStatus }); }; diff --git a/frontend/src/features/songs/remotes/song.ts b/frontend/src/features/songs/remotes/song.ts index 9039cc54..d9998094 100644 --- a/frontend/src/features/songs/remotes/song.ts +++ b/frontend/src/features/songs/remotes/song.ts @@ -1,16 +1,20 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; import type { Genre, Song } from '../types/Song.type'; import type { RecentSong } from '@/shared/types/song'; // 메인 케러셀 최신순 노래 n개 조회 api - 쿼리파람 없는경우, 응답 기본값은 5개입니다. -export const getRecentSongs = async (songCount?: number): Promise => { +export const getRecentSongs = async (songCount?: number) => { const query = songCount ? `?size=${songCount}` : ''; - return await fetcher(`/songs/recent${query}`, 'GET'); + const { data } = await client.get(`/songs/recent${query}`); + + return data; }; -export const getHighLikedSongs = async (genre: Genre): Promise => { +export const getHighLikedSongs = async (genre: Genre) => { const query = genre === 'ALL' ? '' : `?genre=${genre}`; - return await fetcher(`/songs/high-liked${query}`, 'GET'); + const { data } = await client.get(`/songs/high-liked${query}`); + + return data; }; diff --git a/frontend/src/features/songs/remotes/songs.ts b/frontend/src/features/songs/remotes/songs.ts index 004ff60e..e3d2f0df 100644 --- a/frontend/src/features/songs/remotes/songs.ts +++ b/frontend/src/features/songs/remotes/songs.ts @@ -1,27 +1,24 @@ -import fetcher from '@/shared/remotes'; +import client from '@/shared/remotes/axios'; import type { Genre } from '../types/Song.type'; import type { SongDetail, SongDetailEntries } from '@/shared/types/song'; -export const getSongDetailEntries = async ( - songId: number, - genre: Genre -): Promise => { +export const getSongDetailEntries = async (songId: number, genre: Genre) => { const query = genre === 'ALL' ? '' : `?genre=${genre}`; - return await fetcher(`/songs/high-liked/${songId}${query}`, 'GET'); + const { data } = await client.get(`/songs/high-liked/${songId}${query}`); + + return data; }; -export const getExtraPrevSongDetails = async ( - songId: number, - genre: Genre -): Promise => { +export const getExtraPrevSongDetails = async (songId: number, genre: Genre) => { const query = genre === 'ALL' ? '' : `?genre=${genre}`; - return await fetcher(`/songs/high-liked/${songId}/prev${query}`, 'GET'); + const { data } = await client.get(`/songs/high-liked/${songId}/prev${query}`); + + return data; }; -export const getExtraNextSongDetails = async ( - songId: number, - genre: Genre -): Promise => { +export const getExtraNextSongDetails = async (songId: number, genre: Genre) => { const query = genre === 'ALL' ? '' : `?genre=${genre}`; - return await fetcher(`/songs/high-liked/${songId}/next${query}`, 'GET'); + const { data } = await client.get(`/songs/high-liked/${songId}/next${query}`); + + return data; }; diff --git a/frontend/src/pages/EditProfilePage.tsx b/frontend/src/pages/EditProfilePage.tsx index 4191e8e0..3f5500d8 100644 --- a/frontend/src/pages/EditProfilePage.tsx +++ b/frontend/src/pages/EditProfilePage.tsx @@ -12,16 +12,16 @@ import { useMutation } from '@/shared/hooks/useMutation'; const EditProfilePage = () => { const { user, logout } = useAuthContext(); const { isOpen, openModal, closeModal } = useModal(); - const { mutateData } = useMutation(deleteMember(user?.memberId)); + const { mutateData: withdrawal } = useMutation(deleteMember); const navigate = useNavigate(); if (!user) { navigate(ROUTE_PATH.LOGIN); - return; + return null; } const handleWithdrawal = async () => { - await mutateData(); + await withdrawal(user.memberId); logout(); navigate(ROUTE_PATH.ROOT); }; From f2c79eea8a734c6499bed36b6d0fb3ad870d0101 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 15:45:06 +0900 Subject: [PATCH 06/23] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20remote=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/auth/remotes/login.ts | 13 +++++++++++ frontend/src/pages/AuthPage.tsx | 25 +++++++++------------ 2 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 frontend/src/features/auth/remotes/login.ts diff --git a/frontend/src/features/auth/remotes/login.ts b/frontend/src/features/auth/remotes/login.ts new file mode 100644 index 00000000..0443b663 --- /dev/null +++ b/frontend/src/features/auth/remotes/login.ts @@ -0,0 +1,13 @@ +import client from '@/shared/remotes/axios'; + +interface AccessTokenRes { + accessToken: string; +} + +export const getAccessToken = async (platform: string, code: string) => { + const { data } = await client.get(`/login/${platform}`, { + params: { code }, + }); + + return data; +}; diff --git a/frontend/src/pages/AuthPage.tsx b/frontend/src/pages/AuthPage.tsx index 700c451a..7ab3225b 100644 --- a/frontend/src/pages/AuthPage.tsx +++ b/frontend/src/pages/AuthPage.tsx @@ -1,21 +1,22 @@ import { useEffect } from 'react'; -import { Navigate, useNavigate, useParams, useSearchParams } from 'react-router-dom'; +import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; +import { getAccessToken } from '@/features/auth/remotes/login'; import path from '@/shared/constants/path'; +import useValidParams from '@/shared/hooks/useValidParams'; import accessTokenStorage from '@/shared/utils/accessTokenStorage'; -interface AccessTokenResponse { - accessToken: string; -} - const AuthPage = () => { const [searchParams] = useSearchParams(); - const { platform } = useParams(); + const { platform } = useValidParams(); const { login } = useAuthContext(); const navigate = useNavigate(); - const getAccessToken = async () => { + // TODO: 함수 네이밍을 변경해야 합니다. + // 제안: 'code' param 여부 + 분기는 함수 외부로 빼는게 어떤가요? + // 분리한다면 함수 네이밍도 쉬워질 것 같아요. + const getAccessToken1 = async () => { const code = searchParams.get('code'); if (!code) { @@ -25,13 +26,7 @@ const AuthPage = () => { return; } - const response = await fetch(`${process.env.BASE_URL}/login/${platform}?code=${code}`, { - method: 'get', - credentials: 'include', - }); - - const data = (await response.json()) as AccessTokenResponse; - const { accessToken } = data; + const { accessToken } = await getAccessToken(platform, code); if (accessToken) { login(accessToken); @@ -39,7 +34,7 @@ const AuthPage = () => { }; useEffect(() => { - getAccessToken(); + getAccessToken1(); }, []); return ; From 3ecd234eeb7fffa486b54cb29085ebf92ecd383b Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 17:44:34 +0900 Subject: [PATCH 07/23] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=9E=A5=EB=A5=B4=EB=B3=84=20fetch=20msw?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 누락된 구현 추가 --- .../songs/components/SongItem.stories.tsx | 4 +- ...{popularSongs.json => highLikedSongs.json} | 120 ++++++++++++------ frontend/src/mocks/handlers/songsHandlers.ts | 14 ++ 3 files changed, 96 insertions(+), 42 deletions(-) rename frontend/src/mocks/fixtures/{popularSongs.json => highLikedSongs.json} (83%) diff --git a/frontend/src/features/songs/components/SongItem.stories.tsx b/frontend/src/features/songs/components/SongItem.stories.tsx index eb59ac90..a86d0cdc 100644 --- a/frontend/src/features/songs/components/SongItem.stories.tsx +++ b/frontend/src/features/songs/components/SongItem.stories.tsx @@ -1,4 +1,4 @@ -import popularSongs from '@/mocks/fixtures/popularSongs.json'; +import highLikedSongs from '@/mocks/fixtures/highLikedSongs.json'; import SongItem from './SongItem'; import type { Meta, StoryObj } from '@storybook/react'; @@ -11,7 +11,7 @@ export default meta; type Story = StoryObj; -const { title, singer, albumCoverUrl, totalLikeCount } = popularSongs[0]; +const { title, singer, albumCoverUrl, totalLikeCount } = highLikedSongs[0]; export const Default: Story = { args: { diff --git a/frontend/src/mocks/fixtures/popularSongs.json b/frontend/src/mocks/fixtures/highLikedSongs.json similarity index 83% rename from frontend/src/mocks/fixtures/popularSongs.json rename to frontend/src/mocks/fixtures/highLikedSongs.json index d0667b64..477593ce 100644 --- a/frontend/src/mocks/fixtures/popularSongs.json +++ b/frontend/src/mocks/fixtures/highLikedSongs.json @@ -4,279 +4,319 @@ "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 258200 + "totalLikeCount": 258200, + "genre": "DANCE" }, { "id": 2, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 121312 + "totalLikeCount": 1, + "genre": "DANCE" }, { "id": 3, "title": "노래 길이가 좀 많이 길어요. 노래 길이가 좀 많이 길어요. 노래 길이가 좀 많이 길어요. 노래 길이가 좀 많이 길어요.", "singer": "가수 이름이 좀 길어요. 가수 이름이 좀 길어요. 가수 이름이 좀 길어요. 가수 이름이 좀 길어요. 가수 이름이 좀 길어요.", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 9000 + "totalLikeCount": 9000, + "genre": "DANCE" }, { "id": 4, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 8000 + "totalLikeCount": 8000, + "genre": "HIPHOP" }, { "id": 5, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "HIPHOP" }, { "id": 6, "title": "Seven (feat. Latto) - Clean Ver.", "singer": "정국", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/86/070/11286070_20230713181059_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "HIPHOP" }, { "id": 7, "title": "노래 길이가 좀 많이 길어요. 노래 길이가 좀 많이 길어요. 노래 길이가 좀 많이 길어요. 노래 길이가 좀 많이 길어요.", "singer": "가수 이름이 좀 길어요. 가수 이름이 좀 길어요. 가수 이름이 좀 길어요. 가수 이름이 좀 길어요. 가수 이름이 좀 길어요.", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/86/070/11286070_20230713181059_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "HIPHOP" }, { "id": 8, "title": "Seven (feat. Latto) - Clean Ver.", "singer": "정국", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/86/070/11286070_20230713181059_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "BALLAD" }, { "id": 9, "title": "Seven (feat. Latto) - Clean Ver.", "singer": "정국", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/86/070/11286070_20230713181059_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "BALLAD" }, { "id": 10, "title": "Seven (feat. Latto) - Clean Ver.", "singer": "정국", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/86/070/11286070_20230713181059_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "BALLAD" }, { "id": 11, "title": "I AM", "singer": "IVE (아이브)", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/11/297/11211297_20230410151046_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "POP" }, { "id": 12, "title": "I AM", "singer": "IVE (아이브)", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/11/297/11211297_20230410151046_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "POP" }, { "id": 13, "title": "I AM", "singer": "IVE (아이브)", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/11/297/11211297_20230410151046_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "POP" }, { "id": 14, "title": "I AM", "singer": "IVE (아이브)", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/11/297/11211297_20230410151046_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "INDIE" }, { "id": 15, "title": "I AM", "singer": "IVE (아이브)", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/11/297/11211297_20230410151046_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "INDIE" }, { "id": 16, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "INDIE" }, { "id": 17, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "TROT" }, { "id": 18, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "TROT" }, { "id": 19, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "TROT" }, { "id": 20, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "JAZZ" }, { "id": 21, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "JAZZ" }, { "id": 22, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "JAZZ" }, { "id": 23, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "EDM" }, { "id": 24, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "EDM" }, { "id": 25, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "EDM" }, { "id": 26, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "ETC" }, { "id": 27, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "ETC" }, { "id": 28, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "ETC" }, { "id": 29, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "ETC" }, { "id": 30, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "ROCK_METAL" }, { "id": 31, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "ROCK_METAL" }, { "id": 32, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "ROCK_METAL" }, { "id": 33, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "CLASSIC" }, { "id": 34, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "CLASSIC" }, { "id": 35, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "CLASSIC" }, { "id": 36, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "RHYTHM_AND_BLUES" }, { "id": 37, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "RHYTHM_AND_BLUES" }, { "id": 38, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "RHYTHM_AND_BLUES" }, { "id": 39, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "FOLK_BLUES" }, { "id": 40, "title": "Super Shy", "singer": "New Jeans", "albumCoverUrl": "https://cdnimg.melon.co.kr/cm2/album/images/112/81/456/11281456_20230706180841_500.jpg/melon/resize/260/quality/80/optimize", - "totalLikeCount": 1 + "totalLikeCount": 1, + "genre": "FOLK_BLUES" } ] diff --git a/frontend/src/mocks/handlers/songsHandlers.ts b/frontend/src/mocks/handlers/songsHandlers.ts index fd56a3b2..ca3d78e1 100644 --- a/frontend/src/mocks/handlers/songsHandlers.ts +++ b/frontend/src/mocks/handlers/songsHandlers.ts @@ -2,6 +2,7 @@ import { rest } from 'msw'; import comments from '../fixtures/comments.json'; import extraNextSongDetails from '../fixtures/extraNextSongDetails.json'; import extraPrevSongDetails from '../fixtures/extraPrevSongDetails.json'; +import highLikedSongs from '../fixtures/highLikedSongs.json'; import recentSongs from '../fixtures/recentSongs.json'; import songEntries from '../fixtures/songEntries.json'; import type { KillingPartPostRequest } from '@/shared/types/killingPart'; @@ -34,10 +35,23 @@ const songsHandlers = [ return res(ctx.status(201)); }), + rest.get(`${BASE_URL}/songs/high-liked`, (req, res, ctx) => { + const genre = req.url.searchParams.get('genre'); + + if (genre !== null) { + const targetGenreSongs = highLikedSongs.filter((song) => song.genre === genre); + + return res(ctx.status(200), ctx.json(targetGenreSongs)); + } + + return res(ctx.status(200), ctx.json(highLikedSongs)); + }), + rest.get(`${BASE_URL}/songs/high-liked/:songId`, (req, res, ctx) => { // const genre = req.url.searchParams.get('genre') return res(ctx.status(200), ctx.json(songEntries)); }), + rest.get(`${BASE_URL}/songs/high-liked/:songId/prev`, (req, res, ctx) => { // const genre = req.url.searchParams.get('genre'); const { songId } = req.params; From db04ff0712ec70c70cf4d60d19591c51b99c15ff Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 17:45:22 +0900 Subject: [PATCH 08/23] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/practice.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 frontend/practice.js diff --git a/frontend/practice.js b/frontend/practice.js deleted file mode 100644 index b85d61ba..00000000 --- a/frontend/practice.js +++ /dev/null @@ -1 +0,0 @@ -const a = new Date(); //? From a2bbcee3eefbd2775affa9208318b659772f39ef Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 18:05:25 +0900 Subject: [PATCH 09/23] =?UTF-8?q?chore:=20remote=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EB=9E=98=ED=95=91=20hook=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/components/RegisterPart.tsx | 2 +- frontend/src/features/killingParts/hooks/killingPart.ts | 6 ------ frontend/src/features/killingParts/remotes/killingPart.ts | 5 +++++ .../features/killingParts/remotes/usePostKillingPart.ts | 8 -------- 4 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 frontend/src/features/killingParts/hooks/killingPart.ts delete mode 100644 frontend/src/features/killingParts/remotes/usePostKillingPart.ts diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index 6a741d29..bda70175 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -8,7 +8,7 @@ import Modal from '@/shared/components/Modal/Modal'; import Spacing from '@/shared/components/Spacing'; import { useMutation } from '@/shared/hooks/useMutation'; import { toPlayingTimeText } from '@/shared/utils/convertTime'; -import { postKillingPart } from '../hooks/killingPart'; +import { postKillingPart } from '../remotes/killingPart'; const RegisterPart = () => { const { isOpen, openModal, closeModal } = useModal(); diff --git a/frontend/src/features/killingParts/hooks/killingPart.ts b/frontend/src/features/killingParts/hooks/killingPart.ts deleted file mode 100644 index c86e8171..00000000 --- a/frontend/src/features/killingParts/hooks/killingPart.ts +++ /dev/null @@ -1,6 +0,0 @@ -import client from '@/shared/remotes/axios'; -import type { KillingPartPostRequest } from '@/shared/types/killingPart'; - -export const postKillingPart = async (songId: number, body: KillingPartPostRequest) => { - await client.post(`/songs/${songId}/member-parts`, body); -}; diff --git a/frontend/src/features/killingParts/remotes/killingPart.ts b/frontend/src/features/killingParts/remotes/killingPart.ts index e0ef0e45..df32da66 100644 --- a/frontend/src/features/killingParts/remotes/killingPart.ts +++ b/frontend/src/features/killingParts/remotes/killingPart.ts @@ -1,4 +1,5 @@ import client from '@/shared/remotes/axios'; +import type { KillingPartPostRequest } from '@/shared/types/killingPart'; import type { SongInfo } from '@/shared/types/song'; // PartCollectingPage에 존재하던 remote 함수입니다. @@ -11,3 +12,7 @@ export const getSong = async (songId: number) => { return data; }; + +export const postKillingPart = async (songId: number, body: KillingPartPostRequest) => { + await client.post(`/songs/${songId}/member-parts`, body); +}; diff --git a/frontend/src/features/killingParts/remotes/usePostKillingPart.ts b/frontend/src/features/killingParts/remotes/usePostKillingPart.ts deleted file mode 100644 index c0620378..00000000 --- a/frontend/src/features/killingParts/remotes/usePostKillingPart.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { postKillingPart } from '@/features/killingParts/hooks/killingPart'; -import { useMutation } from '@/shared/hooks/useMutation'; - -export const usePostKillingPart = () => { - const { isLoading, error, mutateData: createKillingPart } = useMutation(postKillingPart); - - return { isLoading, error, createKillingPart }; -}; From c287cfeabcc9ff0b7e300cc04dae65addde5355b Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 20:05:19 +0900 Subject: [PATCH 10/23] =?UTF-8?q?refactor:=20remote=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20query=20parameter=20=EC=B2=98=EB=A6=AC=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/search/remotes/search.ts | 20 +++++++++++-------- frontend/src/features/songs/remotes/song.ts | 12 +++++------ frontend/src/features/songs/remotes/songs.ts | 15 ++++++++------ 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/frontend/src/features/search/remotes/search.ts b/frontend/src/features/search/remotes/search.ts index 63b3bffa..af72e0f8 100644 --- a/frontend/src/features/search/remotes/search.ts +++ b/frontend/src/features/search/remotes/search.ts @@ -3,19 +3,23 @@ import type { SingerDetail } from '../../singer/types/singer.type'; import type { SingerSearchPreview } from '../types/search.type'; export const getSingerSearchPreview = async (query: string) => { - const encodedQuery = encodeURIComponent(query); - const { data } = await client.get( - `/search?keyword=${encodedQuery}&type=singer` - ); + const { data } = await client.get(`/search`, { + params: { + keyword: query, + type: 'singer', + }, + }); return data; }; export const getSingerSearch = async (query: string) => { - const encodedQuery = encodeURIComponent(query); - const { data } = await client.get( - `/search?keyword=${encodedQuery}&type=singer&type=song` - ); + const params = new URLSearchParams(); + params.append('keyword', query); + params.append('type', 'singer'); + params.append('type', 'song'); + + const { data } = await client.get(`/search`, { params }); return data; }; diff --git a/frontend/src/features/songs/remotes/song.ts b/frontend/src/features/songs/remotes/song.ts index d9998094..a28bf81b 100644 --- a/frontend/src/features/songs/remotes/song.ts +++ b/frontend/src/features/songs/remotes/song.ts @@ -4,17 +4,17 @@ import type { RecentSong } from '@/shared/types/song'; // 메인 케러셀 최신순 노래 n개 조회 api - 쿼리파람 없는경우, 응답 기본값은 5개입니다. export const getRecentSongs = async (songCount?: number) => { - const query = songCount ? `?size=${songCount}` : ''; - - const { data } = await client.get(`/songs/recent${query}`); + const { data } = await client.get(`/songs/recent`, { + params: { size: songCount }, + }); return data; }; export const getHighLikedSongs = async (genre: Genre) => { - const query = genre === 'ALL' ? '' : `?genre=${genre}`; - - const { data } = await client.get(`/songs/high-liked${query}`); + const { data } = await client.get(`/songs/high-liked`, { + params: { genre: genre === 'ALL' ? null : genre }, + }); return data; }; diff --git a/frontend/src/features/songs/remotes/songs.ts b/frontend/src/features/songs/remotes/songs.ts index e3d2f0df..763f1bdb 100644 --- a/frontend/src/features/songs/remotes/songs.ts +++ b/frontend/src/features/songs/remotes/songs.ts @@ -3,22 +3,25 @@ import type { Genre } from '../types/Song.type'; import type { SongDetail, SongDetailEntries } from '@/shared/types/song'; export const getSongDetailEntries = async (songId: number, genre: Genre) => { - const query = genre === 'ALL' ? '' : `?genre=${genre}`; - const { data } = await client.get(`/songs/high-liked/${songId}${query}`); + const { data } = await client.get(`/songs/high-liked/${songId}`, { + params: { genre: genre === 'ALL' ? null : genre }, + }); return data; }; export const getExtraPrevSongDetails = async (songId: number, genre: Genre) => { - const query = genre === 'ALL' ? '' : `?genre=${genre}`; - const { data } = await client.get(`/songs/high-liked/${songId}/prev${query}`); + const { data } = await client.get(`/songs/high-liked/${songId}/prev`, { + params: { genre: genre === 'ALL' ? null : genre }, + }); return data; }; export const getExtraNextSongDetails = async (songId: number, genre: Genre) => { - const query = genre === 'ALL' ? '' : `?genre=${genre}`; - const { data } = await client.get(`/songs/high-liked/${songId}/next${query}`); + const { data } = await client.get(`/songs/high-liked/${songId}/next`, { + params: { genre: genre === 'ALL' ? null : genre }, + }); return data; }; From d06887ae5f9da6612bb35abe9b28bfd975344876 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 20:46:36 +0900 Subject: [PATCH 11/23] =?UTF-8?q?chore:=20import=20=EB=B0=A9=EC=8B=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/auth/remotes/login.ts | 2 +- frontend/src/features/comments/remotes/comments.ts | 2 +- frontend/src/features/killingParts/remotes/killingPart.ts | 2 +- frontend/src/features/member/remotes/member.ts | 2 +- frontend/src/features/member/remotes/memberParts.ts | 2 +- frontend/src/features/member/remotes/myPage.ts | 2 +- frontend/src/features/search/remotes/search.ts | 2 +- frontend/src/features/singer/remotes/singer.ts | 2 +- frontend/src/features/songs/remotes/likes.ts | 2 +- frontend/src/features/songs/remotes/song.ts | 2 +- frontend/src/features/songs/remotes/songs.ts | 2 +- frontend/src/shared/remotes/axios.ts | 2 -- 12 files changed, 11 insertions(+), 13 deletions(-) diff --git a/frontend/src/features/auth/remotes/login.ts b/frontend/src/features/auth/remotes/login.ts index 0443b663..fe1298fd 100644 --- a/frontend/src/features/auth/remotes/login.ts +++ b/frontend/src/features/auth/remotes/login.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; interface AccessTokenRes { accessToken: string; diff --git a/frontend/src/features/comments/remotes/comments.ts b/frontend/src/features/comments/remotes/comments.ts index c8a38d18..ed4f68e8 100644 --- a/frontend/src/features/comments/remotes/comments.ts +++ b/frontend/src/features/comments/remotes/comments.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; import type { Comment } from '../types/comment.type'; export const postComment = async (songId: number, partId: number, content: string) => { diff --git a/frontend/src/features/killingParts/remotes/killingPart.ts b/frontend/src/features/killingParts/remotes/killingPart.ts index df32da66..c5257d06 100644 --- a/frontend/src/features/killingParts/remotes/killingPart.ts +++ b/frontend/src/features/killingParts/remotes/killingPart.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; import type { KillingPartPostRequest } from '@/shared/types/killingPart'; import type { SongInfo } from '@/shared/types/song'; diff --git a/frontend/src/features/member/remotes/member.ts b/frontend/src/features/member/remotes/member.ts index f8975334..79a0c5ab 100644 --- a/frontend/src/features/member/remotes/member.ts +++ b/frontend/src/features/member/remotes/member.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; export const deleteMember = async (memberId: number) => { await client.delete(`/members/${memberId}`); diff --git a/frontend/src/features/member/remotes/memberParts.ts b/frontend/src/features/member/remotes/memberParts.ts index cce5e9bb..ba11152e 100644 --- a/frontend/src/features/member/remotes/memberParts.ts +++ b/frontend/src/features/member/remotes/memberParts.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; export const deleteMemberParts = async (partId: number) => { await client.delete(`/member-parts/${partId}`); diff --git a/frontend/src/features/member/remotes/myPage.ts b/frontend/src/features/member/remotes/myPage.ts index 99eae927..3569a3e5 100644 --- a/frontend/src/features/member/remotes/myPage.ts +++ b/frontend/src/features/member/remotes/myPage.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; import type { LikeKillingPart } from '../components/MyPartList'; export const getLikeParts = async () => { diff --git a/frontend/src/features/search/remotes/search.ts b/frontend/src/features/search/remotes/search.ts index af72e0f8..f4e19c4a 100644 --- a/frontend/src/features/search/remotes/search.ts +++ b/frontend/src/features/search/remotes/search.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; import type { SingerDetail } from '../../singer/types/singer.type'; import type { SingerSearchPreview } from '../types/search.type'; diff --git a/frontend/src/features/singer/remotes/singer.ts b/frontend/src/features/singer/remotes/singer.ts index b201aed7..0e538633 100644 --- a/frontend/src/features/singer/remotes/singer.ts +++ b/frontend/src/features/singer/remotes/singer.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; import type { SingerDetail } from '../types/singer.type'; export const getSingerDetail = async (singerId: number) => { diff --git a/frontend/src/features/songs/remotes/likes.ts b/frontend/src/features/songs/remotes/likes.ts index 96545e1c..d21a3111 100644 --- a/frontend/src/features/songs/remotes/likes.ts +++ b/frontend/src/features/songs/remotes/likes.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; export const putKillingPartLikes = async (songId: number, partId: number, likeStatus: boolean) => { await client.put(`/songs/${songId}/parts/${partId}/likes`, { likeStatus }); diff --git a/frontend/src/features/songs/remotes/song.ts b/frontend/src/features/songs/remotes/song.ts index a28bf81b..c48a110b 100644 --- a/frontend/src/features/songs/remotes/song.ts +++ b/frontend/src/features/songs/remotes/song.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; import type { Genre, Song } from '../types/Song.type'; import type { RecentSong } from '@/shared/types/song'; diff --git a/frontend/src/features/songs/remotes/songs.ts b/frontend/src/features/songs/remotes/songs.ts index 763f1bdb..21ec45c3 100644 --- a/frontend/src/features/songs/remotes/songs.ts +++ b/frontend/src/features/songs/remotes/songs.ts @@ -1,4 +1,4 @@ -import client from '@/shared/remotes/axios'; +import { client } from '@/shared/remotes/axios'; import type { Genre } from '../types/Song.type'; import type { SongDetail, SongDetailEntries } from '@/shared/types/song'; diff --git a/frontend/src/shared/remotes/axios.ts b/frontend/src/shared/remotes/axios.ts index 86c43c66..07f2e134 100644 --- a/frontend/src/shared/remotes/axios.ts +++ b/frontend/src/shared/remotes/axios.ts @@ -49,5 +49,3 @@ const refreshAccessTokenOnAuthError = async (error: AxiosError) => { client.interceptors.request.use(setToken); client.interceptors.response.use((response) => response, refreshAccessTokenOnAuthError); - -export default client; From a4a83df054288465d9f4ec14dcec76759606b499 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 21:14:09 +0900 Subject: [PATCH 12/23] =?UTF-8?q?chore:=20auth=20=EA=B4=80=EB=A0=A8=20remo?= =?UTF-8?q?te=ED=95=A8=EC=88=98=20auth/=ED=95=98=EC=9C=84=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/auth/remotes/{login.ts => auth.ts} | 9 +++++++-- frontend/src/pages/AuthPage.tsx | 2 +- frontend/src/shared/remotes/auth.ts | 11 ----------- 3 files changed, 8 insertions(+), 14 deletions(-) rename frontend/src/features/auth/remotes/{login.ts => auth.ts} (55%) delete mode 100644 frontend/src/shared/remotes/auth.ts diff --git a/frontend/src/features/auth/remotes/login.ts b/frontend/src/features/auth/remotes/auth.ts similarity index 55% rename from frontend/src/features/auth/remotes/login.ts rename to frontend/src/features/auth/remotes/auth.ts index fe1298fd..04e98290 100644 --- a/frontend/src/features/auth/remotes/login.ts +++ b/frontend/src/features/auth/remotes/auth.ts @@ -1,5 +1,4 @@ -import { client } from '@/shared/remotes/axios'; - +import { client, clientBasic } from '@/shared/remotes/axios'; interface AccessTokenRes { accessToken: string; } @@ -11,3 +10,9 @@ export const getAccessToken = async (platform: string, code: string) => { return data; }; + +export const postRefreshAccessToken = async () => { + const { data } = await clientBasic.post('/reissue'); + + return data; +}; diff --git a/frontend/src/pages/AuthPage.tsx b/frontend/src/pages/AuthPage.tsx index 7ab3225b..71cdea3e 100644 --- a/frontend/src/pages/AuthPage.tsx +++ b/frontend/src/pages/AuthPage.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; -import { getAccessToken } from '@/features/auth/remotes/login'; +import { getAccessToken } from '@/features/auth/remotes/auth'; import path from '@/shared/constants/path'; import useValidParams from '@/shared/hooks/useValidParams'; import accessTokenStorage from '@/shared/utils/accessTokenStorage'; diff --git a/frontend/src/shared/remotes/auth.ts b/frontend/src/shared/remotes/auth.ts deleted file mode 100644 index 8c28ab39..00000000 --- a/frontend/src/shared/remotes/auth.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { clientBasic } from './axios'; - -interface RefreshAccessTokenRes { - accessToken: string; -} - -export const postRefreshAccessToken = async (staleAccessToken: string) => { - const { data } = await clientBasic.post('/reissue', staleAccessToken); - - return data; -}; From 4f858e9b24c325a4d2e25c768352d762684920ae Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Mon, 13 Nov 2023 22:36:37 +0900 Subject: [PATCH 13/23] =?UTF-8?q?fix:=20refresh=20=EC=9A=94=EC=B2=AD=20API?= =?UTF-8?q?=20=EB=AA=85=EC=84=B8=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/auth/remotes/auth.ts | 1 + frontend/src/shared/remotes/axios.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/src/features/auth/remotes/auth.ts b/frontend/src/features/auth/remotes/auth.ts index 04e98290..a90bcb96 100644 --- a/frontend/src/features/auth/remotes/auth.ts +++ b/frontend/src/features/auth/remotes/auth.ts @@ -1,4 +1,5 @@ import { client, clientBasic } from '@/shared/remotes/axios'; + interface AccessTokenRes { accessToken: string; } diff --git a/frontend/src/shared/remotes/axios.ts b/frontend/src/shared/remotes/axios.ts index 07f2e134..9f1f2b39 100644 --- a/frontend/src/shared/remotes/axios.ts +++ b/frontend/src/shared/remotes/axios.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { postRefreshAccessToken } from './auth'; +import { postRefreshAccessToken } from '@/features/auth/remotes/auth'; import type { AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'; const { BASE_URL } = process.env; @@ -28,24 +28,24 @@ const setToken = (config: InternalAxiosRequestConfig) => { // 응답 인터셉터 const refreshAccessTokenOnAuthError = async (error: AxiosError) => { - const config = error.config; + const originalRequest = error.config; - if (error.response?.status === 401 && config?.headers.Authorization) { + if (error.response?.status === 401 && originalRequest?.headers.Authorization) { try { - const staleAccessToken = localStorage.getItem('userToken') ?? ''; - const { accessToken } = await postRefreshAccessToken(staleAccessToken); + const { accessToken } = await postRefreshAccessToken(); localStorage.setItem('userToken', accessToken); - config.headers.Authorization = `Bearer ${accessToken}`; + originalRequest.headers.Authorization = `Bearer ${accessToken}`; - return client(config); + return client(originalRequest); } catch { - // window.alert('세션이 만료되었습니다. 다시 로그인 해주세요'); + window.alert('세션이 만료되었습니다. 다시 로그인 해주세요'); } } return Promise.reject(error); }; +clientBasic.interceptors.request.use(setToken); client.interceptors.request.use(setToken); client.interceptors.response.use((response) => response, refreshAccessTokenOnAuthError); From 09cf9f37250b09803c43f9eea9d8b2d0f95b41bb Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Tue, 14 Nov 2023 10:38:39 +0900 Subject: [PATCH 14/23] =?UTF-8?q?refactor:=20=EC=B5=9C=EC=A2=85=20?= =?UTF-8?q?=EB=A7=8C=EB=A3=8C=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EB=A0=89=ED=8A=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/remotes/axios.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/shared/remotes/axios.ts b/frontend/src/shared/remotes/axios.ts index 9f1f2b39..b3a46703 100644 --- a/frontend/src/shared/remotes/axios.ts +++ b/frontend/src/shared/remotes/axios.ts @@ -40,6 +40,7 @@ const refreshAccessTokenOnAuthError = async (error: AxiosError) => { return client(originalRequest); } catch { window.alert('세션이 만료되었습니다. 다시 로그인 해주세요'); + window.location.href = '/login'; } } From e6bfcfea00673ed298afd1c2bf7ae8947a7c083e Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Tue, 14 Nov 2023 22:06:25 +0900 Subject: [PATCH 15/23] =?UTF-8?q?refactor:=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/auth/remotes/auth.ts | 5 +---- frontend/src/features/auth/types/auth.type.ts | 3 +++ 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 frontend/src/features/auth/types/auth.type.ts diff --git a/frontend/src/features/auth/remotes/auth.ts b/frontend/src/features/auth/remotes/auth.ts index a90bcb96..fd77a7a6 100644 --- a/frontend/src/features/auth/remotes/auth.ts +++ b/frontend/src/features/auth/remotes/auth.ts @@ -1,8 +1,5 @@ import { client, clientBasic } from '@/shared/remotes/axios'; - -interface AccessTokenRes { - accessToken: string; -} +import type { AccessTokenRes } from '../types/auth.type'; export const getAccessToken = async (platform: string, code: string) => { const { data } = await client.get(`/login/${platform}`, { diff --git a/frontend/src/features/auth/types/auth.type.ts b/frontend/src/features/auth/types/auth.type.ts new file mode 100644 index 00000000..fef72074 --- /dev/null +++ b/frontend/src/features/auth/types/auth.type.ts @@ -0,0 +1,3 @@ +export interface AccessTokenRes { + accessToken: string; +} From 744fbff3706e5911fa1677b76c54bc36534d5660 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Tue, 14 Nov 2023 22:54:55 +0900 Subject: [PATCH 16/23] =?UTF-8?q?refactor:=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20refresh=20=EC=A4=91=EB=B3=B5=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EB=B0=A9=EC=A7=80=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/remotes/axios.ts | 46 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/frontend/src/shared/remotes/axios.ts b/frontend/src/shared/remotes/axios.ts index b3a46703..1e23e57a 100644 --- a/frontend/src/shared/remotes/axios.ts +++ b/frontend/src/shared/remotes/axios.ts @@ -1,5 +1,7 @@ +/* eslint-disable prefer-const */ import axios from 'axios'; import { postRefreshAccessToken } from '@/features/auth/remotes/auth'; +import type { AccessTokenRes } from '@/features/auth/types/auth.type'; import type { AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'; const { BASE_URL } = process.env; @@ -12,8 +14,10 @@ const defaultConfig: AxiosRequestConfig = { withCredentials: true, }; -export const clientBasic = axios.create(defaultConfig); -export const client = axios.create(defaultConfig); +const clientBasic = axios.create(defaultConfig); +const client = axios.create(defaultConfig); + +let reissuePromise: Promise | null = null; // 요청 인터셉터 const setToken = (config: InternalAxiosRequestConfig) => { @@ -26,27 +30,47 @@ const setToken = (config: InternalAxiosRequestConfig) => { return config; }; -// 응답 인터셉터 -const refreshAccessTokenOnAuthError = async (error: AxiosError) => { +// 응답 에러 인터셉터 +const reissueOnExpiredTokenError = async (error: AxiosError) => { const originalRequest = error.config; + const isAuthError = error.response?.status === 401; + const hasAuthorization = !!originalRequest?.headers.Authorization; - if (error.response?.status === 401 && originalRequest?.headers.Authorization) { + if (isAuthError && hasAuthorization) { try { - const { accessToken } = await postRefreshAccessToken(); + const { accessToken } = await (reissuePromise ??= reissue()); - localStorage.setItem('userToken', accessToken); originalRequest.headers.Authorization = `Bearer ${accessToken}`; return client(originalRequest); - } catch { - window.alert('세션이 만료되었습니다. 다시 로그인 해주세요'); - window.location.href = '/login'; + } catch (error) { + return Promise.reject(error); } } return Promise.reject(error); }; +const reissue = async () => { + try { + const response = await postRefreshAccessToken(); + const { accessToken } = response; + + localStorage.setItem('userToken', accessToken); + return response; + } catch (error) { + window.alert('세션이 만료되었습니다. 다시 로그인 해주세요'); + localStorage.removeItem('userToken'); + window.location.href = '/login'; + + throw error; + } finally { + reissuePromise = null; + } +}; + clientBasic.interceptors.request.use(setToken); client.interceptors.request.use(setToken); -client.interceptors.response.use((response) => response, refreshAccessTokenOnAuthError); +client.interceptors.response.use((response) => response, reissueOnExpiredTokenError); + +export { clientBasic, client }; From 2166a72363cc690b73a3e17b8b5d51dcdc2144a6 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Tue, 14 Nov 2023 22:55:35 +0900 Subject: [PATCH 17/23] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=ED=83=80=EC=9E=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/hooks/useExtraFetch.ts | 4 ++-- frontend/src/shared/hooks/useFetch.ts | 5 +++-- frontend/src/shared/hooks/useMutation.ts | 5 +++-- frontend/src/shared/types/errorResponse.ts | 4 ++++ 4 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 frontend/src/shared/types/errorResponse.ts diff --git a/frontend/src/shared/hooks/useExtraFetch.ts b/frontend/src/shared/hooks/useExtraFetch.ts index 758cc578..4de5c702 100644 --- a/frontend/src/shared/hooks/useExtraFetch.ts +++ b/frontend/src/shared/hooks/useExtraFetch.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react'; -import type { ErrorResponse } from '@/shared/remotes'; +import type { ErrorResponse } from '../types/errorResponse'; type FetchDirection = 'prev' | 'next'; @@ -39,6 +39,6 @@ const useExtraFetch = ( export default useExtraFetch; // TODO: 현 fetch기준으로 코드 전반적인 에러처리 구조 생각해보기 -// useXXX는 fetcher에서 throw한 에러를 state에 넣어 return하고 이걸 컴포넌트에서 분기로 처리하는 구조. +// useXXX는 에서 throw한 에fetcher러를 state에 넣어 return하고 이걸 컴포넌트에서 분기로 처리하는 구조. // 서버에서 내려주는 커스텀 에러코드, 메세지 객체 활용 고민 // ex) Error class diff --git a/frontend/src/shared/hooks/useFetch.ts b/frontend/src/shared/hooks/useFetch.ts index 410d2283..b4a96dfc 100644 --- a/frontend/src/shared/hooks/useFetch.ts +++ b/frontend/src/shared/hooks/useFetch.ts @@ -2,11 +2,12 @@ import { useCallback, useEffect, useState } from 'react'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; import { useLoginPopup } from '@/features/auth/hooks/LoginPopUpContext'; import AuthError from '@/shared/remotes/AuthError'; +import type { ErrorResponse } from '../types/errorResponse'; const useFetch = (fetcher: () => Promise, defaultFetch: boolean = true) => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const { popupLoginModal } = useLoginPopup(); const { logout } = useAuthContext(); @@ -28,7 +29,7 @@ const useFetch = (fetcher: () => Promise, defaultFetch: boolean = true) => popupLoginModal(error.code); return; } - setError(error as Error); + setError(error as ErrorResponse); } finally { setIsLoading(false); } diff --git a/frontend/src/shared/hooks/useMutation.ts b/frontend/src/shared/hooks/useMutation.ts index a7289a37..7d60fec6 100644 --- a/frontend/src/shared/hooks/useMutation.ts +++ b/frontend/src/shared/hooks/useMutation.ts @@ -2,12 +2,13 @@ import { useCallback, useState } from 'react'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; import { useLoginPopup } from '@/features/auth/hooks/LoginPopUpContext'; import AuthError from '@/shared/remotes/AuthError'; +import type { ErrorResponse } from '../types/errorResponse'; // eslint-disable-next-line export const useMutation = (mutateFn: (...params: P) => Promise) => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const { popupLoginModal } = useLoginPopup(); const { logout } = useAuthContext(); @@ -30,7 +31,7 @@ export const useMutation = (mutateFn: (...params: P) => Prom popupLoginModal(error.code); return; } - setError(error as Error); + setError(error as ErrorResponse); } finally { setIsLoading(false); } diff --git a/frontend/src/shared/types/errorResponse.ts b/frontend/src/shared/types/errorResponse.ts new file mode 100644 index 00000000..c85a9e87 --- /dev/null +++ b/frontend/src/shared/types/errorResponse.ts @@ -0,0 +1,4 @@ +export interface ErrorResponse { + code: number; + message: string; +} From 8e0a22fb7fd0111ea9f2ea1c191e63c5fc7ba0f3 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Tue, 14 Nov 2023 22:56:05 +0900 Subject: [PATCH 18/23] =?UTF-8?q?chore:=20fetcher=20=EB=B0=8F=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EB=A7=8C=EB=A3=8C=20=EA=B2=80=EC=A6=9D=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/remotes/index.ts | 53 ------------------- .../src/shared/remotes/preCheckAccessToken.ts | 38 ------------- 2 files changed, 91 deletions(-) delete mode 100644 frontend/src/shared/remotes/index.ts delete mode 100644 frontend/src/shared/remotes/preCheckAccessToken.ts diff --git a/frontend/src/shared/remotes/index.ts b/frontend/src/shared/remotes/index.ts deleted file mode 100644 index 45f26d4d..00000000 --- a/frontend/src/shared/remotes/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import AuthError from '@/shared/remotes/AuthError'; -import preCheckAccessToken from '@/shared/remotes/preCheckAccessToken'; - -export interface ErrorResponse { - code: number; - message: string; -} - -const { BASE_URL } = process.env; - -const fetcher = async (url: string, method: string, body?: unknown) => { - const headers: Record = { - 'Content-type': 'application/json', - }; - - const accessToken = await preCheckAccessToken(); - - if (accessToken) { - headers['Authorization'] = `Bearer ${accessToken}`; - } - - const options: RequestInit = { - method, - headers, - }; - - if (body) { - options.body = JSON.stringify(body); - } - - const response = await fetch(`${BASE_URL}${url}`, options); - - if (!response.ok) { - const errorResponse: ErrorResponse = await response.json(); - - if (response.status >= 500) { - throw new Error(errorResponse.message); - } - - if (response.status === 401) { - throw new AuthError(errorResponse); - } - - throw new Error(errorResponse.message); - } - - const contentType = response.headers.get('content-type'); - if (!contentType || !contentType.includes('application/json')) return response; - - return response.json(); -}; - -export default fetcher; diff --git a/frontend/src/shared/remotes/preCheckAccessToken.ts b/frontend/src/shared/remotes/preCheckAccessToken.ts deleted file mode 100644 index 718e10be..00000000 --- a/frontend/src/shared/remotes/preCheckAccessToken.ts +++ /dev/null @@ -1,38 +0,0 @@ -import accessTokenStorage from '@/shared/utils/accessTokenStorage'; - -const isTokenExpiredAfter60seconds = (tokenExp: number) => { - return tokenExp * 1000 - 30 * 1000 < Date.now(); -}; - -const preCheckAccessToken = async () => { - const accessTokenWithPayload = accessTokenStorage.getTokenWithPayload(); - - if (accessTokenWithPayload) { - const { - accessToken, - payload: { exp }, - } = accessTokenWithPayload; - - if (!isTokenExpiredAfter60seconds(exp)) { - return accessToken; - } - - const response = await fetch(`${process.env.BASE_URL}/reissue`, { - method: 'POST', - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - - if (response.ok) { - const { accessToken } = await response.json(); - accessTokenStorage.setToken(accessToken); - return accessToken; - } - accessTokenStorage.removeToken(); - } - - return null; -}; - -export default preCheckAccessToken; From 0fbcfde81e4da182aa231786901e5713e53bbefb Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Wed, 15 Nov 2023 10:34:24 +0900 Subject: [PATCH 19/23] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AuthPage.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/AuthPage.tsx b/frontend/src/pages/AuthPage.tsx index 71cdea3e..1e6e47c8 100644 --- a/frontend/src/pages/AuthPage.tsx +++ b/frontend/src/pages/AuthPage.tsx @@ -13,10 +13,7 @@ const AuthPage = () => { const { login } = useAuthContext(); const navigate = useNavigate(); - // TODO: 함수 네이밍을 변경해야 합니다. - // 제안: 'code' param 여부 + 분기는 함수 외부로 빼는게 어떤가요? - // 분리한다면 함수 네이밍도 쉬워질 것 같아요. - const getAccessToken1 = async () => { + const authLogin = async () => { const code = searchParams.get('code'); if (!code) { @@ -34,7 +31,7 @@ const AuthPage = () => { }; useEffect(() => { - getAccessToken1(); + authLogin(); }, []); return ; From 6755702a3d8f636654a577ea7af9304cf11b91c6 Mon Sep 17 00:00:00 2001 From: DOBOB_LAPTOP Date: Wed, 15 Nov 2023 12:02:57 +0900 Subject: [PATCH 20/23] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/hooks/useExtraFetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/shared/hooks/useExtraFetch.ts b/frontend/src/shared/hooks/useExtraFetch.ts index 4de5c702..d44bc366 100644 --- a/frontend/src/shared/hooks/useExtraFetch.ts +++ b/frontend/src/shared/hooks/useExtraFetch.ts @@ -39,6 +39,6 @@ const useExtraFetch = ( export default useExtraFetch; // TODO: 현 fetch기준으로 코드 전반적인 에러처리 구조 생각해보기 -// useXXX는 에서 throw한 에fetcher러를 state에 넣어 return하고 이걸 컴포넌트에서 분기로 처리하는 구조. +// useXXX는 fetcher에서 throw한 에러를 state에 넣어 return하고 이걸 컴포넌트에서 분기로 처리하는 구조. // 서버에서 내려주는 커스텀 에러코드, 메세지 객체 활용 고민 // ex) Error class From 0f87b751d029979f00f34b1dbc355c52221f3312 Mon Sep 17 00:00:00 2001 From: creative-Lee Date: Tue, 28 Nov 2023 19:16:03 +0900 Subject: [PATCH 21/23] =?UTF-8?q?refactor:=20promise=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=20null=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/remotes/axios.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/shared/remotes/axios.ts b/frontend/src/shared/remotes/axios.ts index 1e23e57a..33568064 100644 --- a/frontend/src/shared/remotes/axios.ts +++ b/frontend/src/shared/remotes/axios.ts @@ -45,6 +45,8 @@ const reissueOnExpiredTokenError = async (error: AxiosError) => { return client(originalRequest); } catch (error) { return Promise.reject(error); + } finally { + reissuePromise = null; } } @@ -64,8 +66,6 @@ const reissue = async () => { window.location.href = '/login'; throw error; - } finally { - reissuePromise = null; } }; From dbf96559aaa03ccb30e35adc510d92e7501f16ad Mon Sep 17 00:00:00 2001 From: creative-Lee Date: Tue, 28 Nov 2023 19:24:32 +0900 Subject: [PATCH 22/23] =?UTF-8?q?style:=20promise=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/remotes/axios.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/shared/remotes/axios.ts b/frontend/src/shared/remotes/axios.ts index 33568064..b8b45430 100644 --- a/frontend/src/shared/remotes/axios.ts +++ b/frontend/src/shared/remotes/axios.ts @@ -17,8 +17,6 @@ const defaultConfig: AxiosRequestConfig = { const clientBasic = axios.create(defaultConfig); const client = axios.create(defaultConfig); -let reissuePromise: Promise | null = null; - // 요청 인터셉터 const setToken = (config: InternalAxiosRequestConfig) => { const accessToken = localStorage.getItem('userToken'); @@ -31,6 +29,8 @@ const setToken = (config: InternalAxiosRequestConfig) => { }; // 응답 에러 인터셉터 +let reissuePromise: Promise | null = null; + const reissueOnExpiredTokenError = async (error: AxiosError) => { const originalRequest = error.config; const isAuthError = error.response?.status === 401; From 500cae1e02a771d489804c1efe3f1db00ea6c0a7 Mon Sep 17 00:00:00 2001 From: creative-Lee Date: Sat, 9 Dec 2023 12:45:58 +0900 Subject: [PATCH 23/23] =?UTF-8?q?refactor:=20config=20type=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20=EB=A6=B0=ED=8A=B8=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 --- frontend/src/shared/remotes/axios.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/shared/remotes/axios.ts b/frontend/src/shared/remotes/axios.ts index b8b45430..dd54b893 100644 --- a/frontend/src/shared/remotes/axios.ts +++ b/frontend/src/shared/remotes/axios.ts @@ -1,12 +1,11 @@ -/* eslint-disable prefer-const */ import axios from 'axios'; import { postRefreshAccessToken } from '@/features/auth/remotes/auth'; import type { AccessTokenRes } from '@/features/auth/types/auth.type'; -import type { AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'; +import type { AxiosError, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios'; const { BASE_URL } = process.env; -const defaultConfig: AxiosRequestConfig = { +const defaultConfig: CreateAxiosDefaults = { baseURL: BASE_URL, headers: { 'Content-Type': 'application/json',