-
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.
* chore: 리액트 헬멧 라이브러리 제거 (#658) * chore: 리액트 헬멧 라이브러리 제거 (#658) * refactor: 셀럽잇 추천 맛집 섹션 분리 (#658) * refactor: 배너 섹션 분리 (#658) * refactor: 카테고리 섹션 분리 (#658) * refactor: 셀럽 베스트 섹션 분리 (#658) * refactor: 지역맛집 섹션 분리 (#658) * refactor: 메인페이지 리팩터링 (#658) * refactor: msw handler 폴더명 수정 및 랜덤 응답 로직 구현 랜덤으로 성공과 실패 응답을 보낸다. * chore: 에러핸들링 개발환경 구축 개발 모드에서 에러 오버레이 일시 중단 * refactor: 셀럽잇 추천 맛집 스켈레톤 컴포넌트 분리 (#661) * feat: 에러바운더리 구현 (#661) * style: 셀럽잇 추천맛집 서브 컴포넌트명 수정 및 에러바운더리 적용 (#661) * style: 핸들러 명 수정에 대한 적용 * design: 셀럽잇 추천 맛집 음식점 카드 좋아요 기능 보이기 (#661) * refactor: useToggleLikeNotUpdate 에러 분기 429, 401 에러 핸들링 * fix: ifram 안보이는 현상 처리 * fix: 에러 오버레이 안보이는 현상 처리
- Loading branch information
1 parent
fbcc768
commit 7ac6f37
Showing
11 changed files
with
1,458 additions
and
2,632 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
46 changes: 46 additions & 0 deletions
46
frontend/src/components/@common/ErrorBoundary/ErrorBoundary.tsx
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,46 @@ | ||
import { Component, ReactElement, ReactNode } from 'react'; | ||
|
||
interface FallbackRenderProps { | ||
resetErrorBoundary: () => void; | ||
} | ||
interface Props { | ||
children: ReactNode; | ||
fallbackRender: ({ resetErrorBoundary }: FallbackRenderProps) => ReactElement; | ||
reset: () => void; | ||
} | ||
|
||
interface State { | ||
hasError: boolean; | ||
} | ||
|
||
class ErrorBoundary extends Component<Props, State> { | ||
constructor(props: Props) { | ||
super(props); | ||
this.state = { hasError: false }; | ||
} | ||
|
||
static getDerivedStateFromError(): State { | ||
return { | ||
hasError: true, | ||
}; | ||
} | ||
|
||
resetErrorBoundary() { | ||
const { reset } = this.props; | ||
reset(); | ||
this.setState({ hasError: false }); | ||
} | ||
|
||
render() { | ||
const { hasError } = this.state; | ||
const { children, fallbackRender } = this.props; | ||
|
||
if (hasError) { | ||
return fallbackRender({ resetErrorBoundary: () => this.resetErrorBoundary() }); | ||
} | ||
|
||
return children; | ||
} | ||
} | ||
|
||
export default ErrorBoundary; |
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
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,4 +1,4 @@ | ||
export { DetailPageSuccessHandler } from '~/mocks/handler/detailPage/handler'; | ||
export { MainPageSuccessHandler } from '~/mocks/handler/mainPage/handler'; | ||
export { MainPageSuccessHandler } from '~/mocks/handler/mapPages/handler'; | ||
export { WishListPageSuccessHandler } from '~/mocks/handler/wishListPage/handler'; | ||
export { newMainPageHandler } from '~/mocks/handler/newMainPage/handler'; | ||
export { newMainPageHandler } from '~/mocks/handler/mainPage/handler'; |
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,148 +1,26 @@ | ||
import { rest } from 'msw'; | ||
import restaurants from '~/mocks/data/restaurants'; | ||
import { recommendation } from '~/mocks/data/recommendation'; | ||
import { RECOMMENDED_REGION } from '~/constants/recommendedRegion'; | ||
import { getRandomNumber } from '~/utils/getRandomNumber'; | ||
|
||
import restaurants from '../../data/restaurants'; | ||
import celebs from '../../data/celebs'; | ||
import { profile } from '../../data/user'; | ||
|
||
import type { Celeb } from '~/@types/celeb.types'; | ||
import type { RestaurantData, RestaurantListData } from '~/@types/api.types'; | ||
|
||
export const MainPageSuccessHandler = [ | ||
rest.get('/restaurants', (req, res, ctx) => { | ||
const pageSize = 18; | ||
|
||
export const newMainPageHandler = [ | ||
rest.get('/address', (req, res, ctx) => { | ||
const queryParams = req.url.searchParams; | ||
const sort = queryParams.get('sort') || 'distance'; | ||
const page = Number(queryParams.get('page')) || 0; | ||
const celebId = Number(queryParams.get('celebId')) || null; | ||
const category = queryParams.get('category') || null; | ||
const lowLatitude = Number(queryParams.get('lowLatitude')); | ||
const highLatitude = Number(queryParams.get('highLatitude')); | ||
const lowLongitude = Number(queryParams.get('lowLongitude')); | ||
const highLongitude = Number(queryParams.get('highLongitude')); | ||
|
||
const filteredRestaurants = restaurants.filter(({ celebs, category: restaurantCategory }) => { | ||
const hasCelebId = celebId ? celebs.map(({ id }) => id).includes(celebId) : true; | ||
const isMatchCategory = category ? category === restaurantCategory : true; | ||
return hasCelebId && isMatchCategory; | ||
}); | ||
|
||
const sortedRestaurants = filteredRestaurants | ||
.filter(restaurant => { | ||
return ( | ||
restaurant.lat >= lowLatitude && | ||
restaurant.lat <= highLatitude && | ||
restaurant.lng >= lowLongitude && | ||
restaurant.lng <= highLongitude | ||
); | ||
}) | ||
.sort((prev, current) => { | ||
if (sort === 'like') return current.likeCount - prev.likeCount; | ||
return prev.distance - current.distance; | ||
}); | ||
|
||
function moveCelebToFrontById(celebs: Celeb[], targetId: number): Celeb[] { | ||
const targetIndex = celebs.findIndex(celeb => celeb.id === targetId); | ||
|
||
if (targetIndex === -1) return celebs; | ||
|
||
const newArray = [...celebs]; | ||
const [movedCeleb] = newArray.splice(targetIndex, 1); | ||
newArray.unshift(movedCeleb); | ||
|
||
return newArray; | ||
} | ||
|
||
const content: RestaurantData[] = sortedRestaurants | ||
.slice(page * pageSize, (page + 1) * pageSize) | ||
.map(({ celebs, ...etc }) => { | ||
const sortedCelebs: Celeb[] = moveCelebToFrontById(celebs, celebId); | ||
return { celebs: sortedCelebs, ...etc }; | ||
}); | ||
const regionKey = queryParams.get('codes') as keyof typeof RECOMMENDED_REGION; | ||
const regions = RECOMMENDED_REGION[regionKey].name; | ||
|
||
const restaurantListData: RestaurantListData = { | ||
content, | ||
currentElementsCount: content.length, | ||
currentPage: page, | ||
pageSize, | ||
totalElementsCount: sortedRestaurants.length, | ||
totalPage: Math.ceil(sortedRestaurants.length / pageSize), | ||
}; | ||
|
||
return res(ctx.status(200), ctx.json(restaurantListData)); | ||
}), | ||
|
||
rest.get('/celebs', (req, res, ctx) => { | ||
return res(ctx.status(200), ctx.json(celebs)); | ||
}), | ||
|
||
rest.get('/oauth/login/:oauthType', (req, res, ctx) => { | ||
const code = req.url.searchParams.get('code') ?? null; | ||
|
||
if (code === null) { | ||
return res(ctx.status(401), ctx.json({ message: '인증되지 않은 code입니다.' })); | ||
} | ||
|
||
const currentDate = new Date(); | ||
const sixHoursInMilliseconds = 6 * 60 * 60 * 1000; | ||
const expirationDate = new Date(currentDate.getTime() + sixHoursInMilliseconds); | ||
|
||
return res(ctx.cookie('JSESSION', `${code}`, { expires: expirationDate }), ctx.status(200)); | ||
}), | ||
|
||
rest.get('/oauth/logout/:oauthType', (req, res, ctx) => { | ||
const { JSESSION } = req.cookies; | ||
|
||
if (JSESSION === undefined) { | ||
return res(ctx.status(401), ctx.json({ message: '만료된 세션입니다.' })); | ||
} | ||
|
||
return res(ctx.status(200), ctx.cookie('JSESSION', '', { expires: new Date() })); | ||
}), | ||
|
||
rest.delete('/oauth/withdraw/:oauthType', (req, res, ctx) => { | ||
const { JSESSION } = req.cookies; | ||
|
||
if (JSESSION === undefined) { | ||
return res(ctx.status(401), ctx.json({ message: '만료된 세션입니다.' })); | ||
} | ||
|
||
// 회원 탈퇴 시에 쿠키 바로 만료 | ||
return res(ctx.status(204), ctx.cookie('JSESSION', '', { expires: new Date() })); | ||
}), | ||
|
||
rest.get('/members/my', (req, res, ctx) => { | ||
const { JSESSION } = req.cookies; | ||
|
||
if (JSESSION === undefined) { | ||
return res(ctx.status(401), ctx.json({ message: '만료된 세션입니다.' })); | ||
} | ||
|
||
// 쿠키 갱신 | ||
return res(ctx.status(200), ctx.json(profile)); | ||
}), | ||
|
||
rest.post('/restaurants/:restaurantId/like', (req, res, ctx) => { | ||
const { JSESSION } = req.cookies; | ||
const { restaurantId } = req.params; | ||
|
||
const restaurant = restaurants.find(restaurant => restaurant.id === Number(restaurantId)); | ||
restaurant.isLiked ? (restaurant['isLiked'] = false) : (restaurant['isLiked'] = true); | ||
|
||
if (JSESSION === undefined) { | ||
return res(ctx.status(401), ctx.json({ message: '만료된 세션입니다.' })); | ||
} | ||
|
||
return res(ctx.status(200)); | ||
}), | ||
]; | ||
const restaurantFilteredByRegion = restaurants.filter(({ roadAddress }) => | ||
regions.some(region => roadAddress.includes(region)), | ||
); | ||
|
||
export const MainPageErrorHandler = [ | ||
rest.post('/restaurants/:restaurantId/like', (req, res, ctx) => { | ||
return res(ctx.status(401)); | ||
return res(ctx.status(200), ctx.json({ content: restaurantFilteredByRegion })); | ||
}), | ||
|
||
rest.delete('/oauth/withdraw/:oauthType', (req, res, ctx) => { | ||
return res(ctx.status(401)); | ||
rest.get('/main-page/recommendation', (req, res, ctx) => { | ||
const responses = [res(ctx.status(401)), res(ctx.status(401)), res(ctx.status(200), ctx.json(recommendation))]; | ||
return responses[getRandomNumber()]; | ||
// return res(ctx.status(400), ctx.json(recommendation)); | ||
// return res(ctx.status(200), ctx.json(recommendation)); | ||
}), | ||
]; |
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,152 @@ | ||
import { rest } from 'msw'; | ||
|
||
import restaurants from '../../data/restaurants'; | ||
import celebs from '../../data/celebs'; | ||
import { profile } from '../../data/user'; | ||
|
||
import type { Celeb } from '~/@types/celeb.types'; | ||
import type { RestaurantData, RestaurantListData } from '~/@types/api.types'; | ||
import { getRandomNumber } from '~/utils/getRandomNumber'; | ||
|
||
export const MainPageSuccessHandler = [ | ||
rest.get('/restaurants', (req, res, ctx) => { | ||
const pageSize = 18; | ||
|
||
const queryParams = req.url.searchParams; | ||
const sort = queryParams.get('sort') || 'distance'; | ||
const page = Number(queryParams.get('page')) || 0; | ||
const celebId = Number(queryParams.get('celebId')) || null; | ||
const category = queryParams.get('category') || null; | ||
const lowLatitude = Number(queryParams.get('lowLatitude')); | ||
const highLatitude = Number(queryParams.get('highLatitude')); | ||
const lowLongitude = Number(queryParams.get('lowLongitude')); | ||
const highLongitude = Number(queryParams.get('highLongitude')); | ||
|
||
const filteredRestaurants = restaurants.filter(({ celebs, category: restaurantCategory }) => { | ||
const hasCelebId = celebId ? celebs.map(({ id }) => id).includes(celebId) : true; | ||
const isMatchCategory = category ? category === restaurantCategory : true; | ||
return hasCelebId && isMatchCategory; | ||
}); | ||
|
||
const sortedRestaurants = filteredRestaurants | ||
.filter(restaurant => { | ||
return ( | ||
restaurant.lat >= lowLatitude && | ||
restaurant.lat <= highLatitude && | ||
restaurant.lng >= lowLongitude && | ||
restaurant.lng <= highLongitude | ||
); | ||
}) | ||
.sort((prev, current) => { | ||
if (sort === 'like') return current.likeCount - prev.likeCount; | ||
return prev.distance - current.distance; | ||
}); | ||
|
||
function moveCelebToFrontById(celebs: Celeb[], targetId: number): Celeb[] { | ||
const targetIndex = celebs.findIndex(celeb => celeb.id === targetId); | ||
|
||
if (targetIndex === -1) return celebs; | ||
|
||
const newArray = [...celebs]; | ||
const [movedCeleb] = newArray.splice(targetIndex, 1); | ||
newArray.unshift(movedCeleb); | ||
|
||
return newArray; | ||
} | ||
|
||
const content: RestaurantData[] = sortedRestaurants | ||
.slice(page * pageSize, (page + 1) * pageSize) | ||
.map(({ celebs, ...etc }) => { | ||
const sortedCelebs: Celeb[] = moveCelebToFrontById(celebs, celebId); | ||
return { celebs: sortedCelebs, ...etc }; | ||
}); | ||
|
||
const restaurantListData: RestaurantListData = { | ||
content, | ||
currentElementsCount: content.length, | ||
currentPage: page, | ||
pageSize, | ||
totalElementsCount: sortedRestaurants.length, | ||
totalPage: Math.ceil(sortedRestaurants.length / pageSize), | ||
}; | ||
|
||
return res(ctx.status(200), ctx.json(restaurantListData)); | ||
}), | ||
|
||
rest.get('/celebs', (req, res, ctx) => { | ||
return res(ctx.status(200), ctx.json(celebs)); | ||
}), | ||
|
||
rest.get('/oauth/login/:oauthType', (req, res, ctx) => { | ||
const code = req.url.searchParams.get('code') ?? null; | ||
|
||
if (code === null) { | ||
return res(ctx.status(401), ctx.json({ message: '인증되지 않은 code입니다.' })); | ||
} | ||
|
||
const currentDate = new Date(); | ||
const sixHoursInMilliseconds = 6 * 60 * 60 * 1000; | ||
const expirationDate = new Date(currentDate.getTime() + sixHoursInMilliseconds); | ||
|
||
return res(ctx.cookie('JSESSION', `${code}`, { expires: expirationDate }), ctx.status(200)); | ||
}), | ||
|
||
rest.get('/oauth/logout/:oauthType', (req, res, ctx) => { | ||
const { JSESSION } = req.cookies; | ||
|
||
if (JSESSION === undefined) { | ||
return res(ctx.status(401), ctx.json({ message: '만료된 세션입니다.' })); | ||
} | ||
|
||
return res(ctx.status(200), ctx.cookie('JSESSION', '', { expires: new Date() })); | ||
}), | ||
|
||
rest.delete('/oauth/withdraw/:oauthType', (req, res, ctx) => { | ||
const { JSESSION } = req.cookies; | ||
|
||
if (JSESSION === undefined) { | ||
return res(ctx.status(401), ctx.json({ message: '만료된 세션입니다.' })); | ||
} | ||
|
||
// 회원 탈퇴 시에 쿠키 바로 만료 | ||
return res(ctx.status(204), ctx.cookie('JSESSION', '', { expires: new Date() })); | ||
}), | ||
|
||
rest.get('/members/my', (req, res, ctx) => { | ||
const { JSESSION } = req.cookies; | ||
|
||
if (JSESSION === undefined) { | ||
return res(ctx.status(401), ctx.json({ message: '만료된 세션입니다.' })); | ||
} | ||
|
||
// 쿠키 갱신 | ||
return res(ctx.status(200), ctx.json(profile)); | ||
}), | ||
|
||
rest.post('/restaurants/:restaurantId/like', (req, res, ctx) => { | ||
const { JSESSION } = req.cookies; | ||
const { restaurantId } = req.params; | ||
|
||
const restaurant = restaurants.find(restaurant => restaurant.id === Number(restaurantId)); | ||
restaurant.isLiked ? (restaurant['isLiked'] = false) : (restaurant['isLiked'] = true); | ||
|
||
const responses = [res(ctx.status(429)), res(ctx.status(429)), res(ctx.status(200))]; | ||
return responses[getRandomNumber()]; | ||
|
||
if (JSESSION === undefined) { | ||
return res(ctx.status(401), ctx.json({ message: '만료된 세션입니다.' })); | ||
} | ||
|
||
return res(ctx.status(200)); | ||
}), | ||
]; | ||
|
||
export const MainPageErrorHandler = [ | ||
rest.post('/restaurants/:restaurantId/like', (req, res, ctx) => { | ||
return res(ctx.status(401)); | ||
}), | ||
|
||
rest.delete('/oauth/withdraw/:oauthType', (req, res, ctx) => { | ||
return res(ctx.status(401)); | ||
}), | ||
]; |
Oops, something went wrong.