-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat: Axios http 정의 및 get 메서드 구현 * refactor: Bookmarks 페이지 TopicCardList 분리 코드 합병 TopicCardList는 재사용을 위해 만들어진 코드이나 Axios, React Query 를 적용하면서 사용하기 애매해졌다. 이에 따라 TopicCardList를 제거하고 각 페이지에서 그 책임을 이어받는다. * design: Skeleton 컴포넌트 스타일 변경 및 적용 * refactor: SkeletonBox 공통 컴포넌트 구현 및 convertCSS 유틸 함수 구현 * refactor: Skeleton 컴포넌트 Bookmark 페이지에 적용 * refactor: http api 수정 및 useGetBookmark isLoading 상태 사용 * refactor: SeeAllNearTopics 페이지 TopicCardList 분리 및 코드 합병 * refactor: API 로직 반환값 타입 지정 * refactor: Bookmark 스켈레톤 수정 * refactor: SeeAllLatestTopics 페이지 TopicCardList 분리 및 코드 합병 * refactor: SeeAllPopularTopics 페이지 TopicCardList 분리 및 코드 합병 * refactor: AllTopics Query key 수정 및 시맨틱 태그 수정 * rename: API 명세와 페이지 이름 통일화 * refactor: 전체보기 페이지 명칭 수정 router 적용 * feat: 리프레쉬 토큰 요청 기능 추가 운영서버에 머지해야 확인가능할 것 같습니다. 기존 로직을 사용하는 운영서버와 request 값은 동일한데 cookie와 도메인 설정 문제로 실패하는 것으로 확인됩니다. * feat: query default option 설정 * 마운트 시 리페칭 해제 * 윈도우 포커스 시 리페칭 해제 * 일정 주기로 리페칭 해제 * 받아온 데이터 stale 시 리페칭 해제 * refactor: useSuspenseQuery 를 통한 선언적으로 로딩상태 처리 * fix: token 없을 때 Authorization 빈 객체로 세팅하여 비로그인 오류 해결 * refactor: withCredentials 옵션 잠시 보류 * refactor: 01.17 회의를 통한 변경 * TopicCardList 컴포넌트를 이전처럼 활용하도록 한다. 전체보기 및 즐겨찾기는 거의 동일한 형태이며 중복코드가 다량 발생하여 위와 같이 수정한다. * url을 넘겨받음에 따라서 리액트 쿼리 훅, API 요청 로직 또한 하나의 훅으로 재사용한다. --------- Co-authored-by: afds4567 <[email protected]>
- Loading branch information
1 parent
1d5a01c
commit f7d8897
Showing
16 changed files
with
293 additions
and
176 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { useSuspenseQuery } from '@tanstack/react-query'; | ||
|
||
import { getTopics } from '../../apis/new'; | ||
|
||
const useGetTopics = (url: string) => { | ||
const { data: topics, refetch } = useSuspenseQuery({ | ||
queryKey: ['topics', url], | ||
queryFn: () => getTopics(url), | ||
}); | ||
|
||
return { topics, refetch }; | ||
}; | ||
|
||
export default useGetTopics; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import axios, { | ||
AxiosInstance, | ||
AxiosRequestConfig, | ||
AxiosRequestHeaders, | ||
} from 'axios'; | ||
|
||
const API_POSTFIX = 'api'; | ||
const BASE_URL = process.env.APP_URL || `https://mapbefine.com/${API_POSTFIX}`; | ||
const token = localStorage.getItem('userToken'); | ||
|
||
const axiosInstance = axios.create({ | ||
baseURL: BASE_URL, | ||
headers: token ? { Authorization: `Bearer ${token}` } : {}, | ||
// withCredentials: true, | ||
}); | ||
|
||
let refreshResponse: Promise<Response> | null = null; | ||
|
||
export interface HttpClient extends AxiosInstance { | ||
get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>; | ||
post<T = unknown>( | ||
url: string, | ||
data?: any, | ||
config?: AxiosRequestConfig, | ||
): Promise<T>; | ||
patch<T = unknown>( | ||
url: string, | ||
data?: any, | ||
config?: AxiosRequestConfig, | ||
): Promise<T>; | ||
put<T = unknown>( | ||
url: string, | ||
data?: any, | ||
config?: AxiosRequestConfig, | ||
): Promise<T>; | ||
delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>; | ||
} | ||
|
||
export const http: HttpClient = axiosInstance; | ||
|
||
http.interceptors.response.use((res) => res.data); | ||
http.interceptors.request.use( | ||
async (config) => { | ||
const userToken = localStorage.getItem('userToken'); | ||
|
||
if (userToken && isTokenExpired(userToken)) { | ||
await updateToken(config.headers); | ||
} | ||
return config; | ||
}, | ||
(error) => Promise.reject(error), | ||
); | ||
|
||
const isTokenExpired = (token: string) => { | ||
const decodedPayloadObject = decodeToken(token); | ||
return decodedPayloadObject.exp * 1000 < Date.now(); | ||
}; | ||
|
||
const decodeToken = (token: string) => { | ||
const tokenParts = token.split('.'); | ||
|
||
if (tokenParts.length !== 3) { | ||
throw new Error('토큰이 잘못되었습니다.'); | ||
} | ||
|
||
const decodedPayloadString = atob(tokenParts[1]); | ||
|
||
return JSON.parse(decodedPayloadString); | ||
}; | ||
|
||
async function updateToken(headers: AxiosRequestHeaders) { | ||
const response = await refreshToken(headers); | ||
const responseCloned = response.clone(); | ||
const newToken = await responseCloned.json(); | ||
|
||
localStorage.setItem('userToken', newToken.accessToken); | ||
} | ||
|
||
async function refreshToken(headers: AxiosRequestHeaders): Promise<Response> { | ||
if (refreshResponse !== null) { | ||
return refreshResponse; | ||
} | ||
|
||
const accessToken = localStorage.getItem('userToken'); | ||
refreshResponse = fetch(`${BASE_URL}/refresh-token`, { | ||
method: 'POST', | ||
headers, | ||
body: JSON.stringify({ | ||
accessToken, | ||
}), | ||
}); | ||
|
||
const responseData = await refreshResponse; | ||
refreshResponse = null; | ||
|
||
if (!responseData.ok) { | ||
throw new Error('Failed to refresh access token.'); | ||
} | ||
|
||
return responseData; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { TopicCardProps } from '../../types/Topic'; | ||
import { http } from './http'; | ||
|
||
export const getTopics = (url: string) => http.get<TopicCardProps[]>(url); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,21 @@ | ||
import { keyframes, styled } from 'styled-components'; | ||
|
||
import Flex from '../common/Flex'; | ||
import Box from '../common/Box'; | ||
import Space from '../common/Space'; | ||
import SkeletonBox from './common/SkeletonBox'; | ||
|
||
function TopicCardSkeleton() { | ||
return ( | ||
<Flex $flexDirection="row"> | ||
<SkeletonImg /> | ||
<Space size={2} /> | ||
<Flex $flexDirection="column"> | ||
<SkeletonTitle /> | ||
<Space size={5} /> | ||
<SkeletonDescription /> | ||
</Flex> | ||
</Flex> | ||
<Box> | ||
<SkeletonBox width="100%" $maxWidth={212} ratio="1.6 / 1" /> | ||
<Space size={1} /> | ||
<SkeletonBox width={212} height={25} /> | ||
<Space size={5} /> | ||
<SkeletonBox width={100} height={25} /> | ||
<Space size={1} /> | ||
<SkeletonBox width={212} height={46} /> | ||
</Box> | ||
); | ||
} | ||
|
||
const skeletonAnimation = keyframes` | ||
from { | ||
opacity: 0.1; | ||
} | ||
to { | ||
opacity: 1; | ||
} | ||
`; | ||
|
||
const SkeletonImg = styled.div` | ||
width: 138px; | ||
height: 138px; | ||
border-radius: 8px; | ||
background: ${({ theme }) => theme.color.lightGray}; | ||
animation: ${skeletonAnimation} 1s infinite; | ||
`; | ||
|
||
const SkeletonTitle = styled.div` | ||
width: 172px; | ||
height: 32px; | ||
border-radius: 8px; | ||
background: ${({ theme }) => theme.color.lightGray}; | ||
animation: ${skeletonAnimation} 1s infinite; | ||
`; | ||
|
||
const SkeletonDescription = styled(SkeletonTitle)` | ||
height: 80px; | ||
`; | ||
|
||
export default TopicCardSkeleton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,40 @@ | ||
import { styled } from 'styled-components'; | ||
|
||
import Space from '../common/Space'; | ||
import SkeletonBox from './common/SkeletonBox'; | ||
import TopicCardSkeleton from './TopicCardSkeleton'; | ||
|
||
function TopicCardContainerSkeleton() { | ||
function TopicListSkeleton() { | ||
return ( | ||
<Wrapper> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
</Wrapper> | ||
<> | ||
<Space size={5} /> | ||
<SkeletonBox width={160} height={32} /> | ||
<Space size={4} /> | ||
<Space size={5} /> | ||
<TopicCardWrapper> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
</TopicCardWrapper> | ||
<Space size={4} /> | ||
<TopicCardWrapper> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
<TopicCardSkeleton /> | ||
</TopicCardWrapper> | ||
</> | ||
); | ||
} | ||
|
||
const Wrapper = styled.section` | ||
const TopicCardWrapper = styled.section` | ||
display: flex; | ||
flex-wrap: wrap; | ||
gap: 20px; | ||
width: 1036px; | ||
height: 300px; | ||
width: 1140px; | ||
`; | ||
|
||
export default TopicCardContainerSkeleton; | ||
export default TopicListSkeleton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import styled, { keyframes } from 'styled-components'; | ||
|
||
import { convertCSS } from '../../../utils/convertCSS'; | ||
|
||
interface Props { | ||
width?: number | string; | ||
height?: number | string; | ||
$maxWidth?: number | string; | ||
$maxHeight?: number | string; | ||
ratio?: string; | ||
radius?: number | string; | ||
} | ||
|
||
const skeletonAnimation = keyframes` | ||
from { | ||
opacity: 1; | ||
} | ||
50% { | ||
opacity: 0.6; | ||
} | ||
to { | ||
opacity: 1; | ||
} | ||
`; | ||
|
||
const SkeletonBox = styled.div<Props>` | ||
width: ${({ width }) => width && convertCSS(width)}; | ||
height: ${({ height }) => height && convertCSS(height)}; | ||
max-width: ${({ $maxWidth }) => $maxWidth && convertCSS($maxWidth)}; | ||
max-height: ${({ $maxHeight }) => $maxHeight && convertCSS($maxHeight)}; | ||
aspect-ratio: ${({ ratio }) => ratio}; | ||
border-radius: ${({ radius, theme }) => | ||
(radius && convertCSS(radius)) || theme.radius.small}; | ||
background: ${({ theme }) => theme.color.lightGray}; | ||
animation: ${skeletonAnimation} 1s infinite; | ||
`; | ||
|
||
export default SkeletonBox; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.