Skip to content

Commit 07dad69

Browse files
authored
Merge pull request #237 from boostcampwm-2024/feat/#226-layout-api
레이아웃 및 api 최적화
2 parents bb63a10 + e4a285a commit 07dad69

File tree

18 files changed

+156
-50
lines changed

18 files changed

+156
-50
lines changed

front/package-lock.json

Lines changed: 35 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

front/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
},
1515
"dependencies": {
1616
"@tanstack/react-query": "^5.60.6",
17+
"@tanstack/react-query-devtools": "^5.62.0",
1718
"axios": "^1.7.7",
1819
"class-variance-authority": "^0.7.0",
1920
"es-toolkit": "^1.27.0",

front/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import ToastContainer from '@/components/Toast/ToastContainer.tsx';
55
import AuthProvider from '@/providers/AuthProvider';
66
import QueryProvider from '@/providers/QueryProvider';
77
import router from '@/routes/index';
8+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
89

910
function App() {
1011
return (
1112
<QueryProvider>
1213
<AuthProvider>
1314
<RouterProvider router={router} />
1415
<ToastContainer />
16+
<ReactQueryDevtools />
1517
</AuthProvider>
1618
</QueryProvider>
1719
);

front/src/api/axios.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,27 @@ export const apiClient = axios.create({
2323
withCredentials: true,
2424
});
2525

26-
const EXCLUDING_AUTH_ERROR_REDIRECT_URL_LIST = ['/user'];
2726
const UN_AUTHENTICATION_ERROR_STATUS = 403;
27+
const EXCLUDE_AUTHENTICATION_ERROR_API_URL_LIST = ['/user'];
2828
const UN_AUTHORIZATION_ERROR_STATUS = 401;
29+
const EXCLUDE_AUTHORIZATION_ERROR_API_URL_LIST = ['/user/login'];
30+
2931
const isAuthenticateError = (error: AxiosError) => {
3032
if (
3133
error.status === UN_AUTHENTICATION_ERROR_STATUS &&
3234
error.config?.url &&
33-
!EXCLUDING_AUTH_ERROR_REDIRECT_URL_LIST.includes(error.config.url)
35+
!EXCLUDE_AUTHENTICATION_ERROR_API_URL_LIST.includes(error.config.url)
3436
)
3537
return true;
3638
return false;
3739
};
3840

3941
const isAuthorizationError = (error: AxiosError) => {
40-
if (error.status === UN_AUTHORIZATION_ERROR_STATUS) {
42+
if (
43+
error.status === UN_AUTHORIZATION_ERROR_STATUS &&
44+
error.config?.url &&
45+
!EXCLUDE_AUTHORIZATION_ERROR_API_URL_LIST.includes(error.config.url)
46+
) {
4147
return true;
4248
}
4349
return false;
@@ -52,6 +58,7 @@ const isServerError = (error: AxiosError) => {
5258

5359
const isError = (error: unknown) => error && isAxiosError<CustomError>(error);
5460
//TODO 500 에러 처리 필요
61+
5562
apiClient.interceptors.response.use(
5663
(response) => response,
5764
(error) => {
@@ -60,12 +67,10 @@ apiClient.interceptors.response.use(
6067
toast.error('로그인이 필요합니다.\n로그인 후 이용해주세요.');
6168
auth.logout();
6269
router.navigate(ROUTE_URL.USER.LOGIN, { replace: true });
63-
}
64-
if (isAuthorizationError(error)) {
70+
} else if (isAuthorizationError(error)) {
6571
toast.error('잘못된 접근입니다.\n다시 시도해주세요.');
6672
router.navigate('/', { replace: true });
67-
}
68-
if (isServerError(error)) {
73+
} else if (isServerError(error)) {
6974
toast.error('서버에 문제가 있습니다.\n잠시 후 다시 시도해주세요.');
7075
}
7176
throw error;

front/src/components/Captcha/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ChangeEvent, useEffect, useRef, useState } from 'react';
2+
import { Link } from 'react-router-dom';
23
import { LoadCanvasTemplate, loadCaptchaEnginge, validateCaptcha } from 'react-simple-captcha';
34

45
import Button from '@/components/common/Button.tsx';
@@ -31,7 +32,7 @@ export default function Captcha({ goNextStep }: CaptchaProps) {
3132
setIsValid(false);
3233
if (InputRef.current) {
3334
const input = InputRef.current! as HTMLInputElement;
34-
input.value = '';
35+
setInputData('');
3536
input.focus();
3637
}
3738
}
@@ -66,8 +67,10 @@ export default function Captcha({ goNextStep }: CaptchaProps) {
6667
</ul>
6768

6869
<div className="flex gap-4">
69-
<Button color={'cancel'}>
70-
<span className="text-label1 text-typo-display">취소</span>
70+
<Button color={'cancel'} asChild>
71+
<Link to={'/'} className="text-label1 text-typo-display">
72+
취소
73+
</Link>
7174
</Button>
7275
<Button color={'primary'} onClick={validateAndGoNextStep}>
7376
<span className="text-label1 text-typo-display">확인</span>

front/src/components/Navbar/index.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Link } from 'react-router-dom';
1+
import { Link, useNavigate } from 'react-router-dom';
22

33
import { CustomError } from '@/api/axios.ts';
44
import { deleteReservation, getReservation } from '@/api/reservation.ts';
@@ -7,6 +7,7 @@ import { postLogout } from '@/api/user.ts';
77
import { useAuthContext } from '@/hooks/useAuthContext.tsx';
88

99
import ReservationCard from '@/components/Navbar/ReservationCard.tsx';
10+
import { toast } from '@/components/Toast/index.ts';
1011
import Button from '@/components/common/Button';
1112
import Icon from '@/components/common/Icon';
1213
import Popover from '@/components/common/Popover';
@@ -22,6 +23,7 @@ const RESERVATION_DELETE_MUTATION_KEY = ['reservation'];
2223

2324
export default function Navbar() {
2425
const { isLogin, userId, logout } = useAuthContext();
26+
const navigate = useNavigate();
2527
const queryClient = useQueryClient();
2628
const { data: reservations } = useQuery<Reservation[], CustomError>({
2729
queryKey: [`reservation`],
@@ -34,8 +36,12 @@ export default function Navbar() {
3436
mutationKey: RESERVATION_DELETE_MUTATION_KEY,
3537
mutationFn: deleteReservation,
3638
onSuccess: () => {
39+
toast.warning('예매내역이 삭제되었습니다.');
3740
return queryClient.refetchQueries({ queryKey: ['reservation'] });
3841
},
42+
onError: () => {
43+
toast.error('예매내역 삭제에 실패했습니다.\n 잠시 후 다시 시도해주세요');
44+
},
3945
});
4046

4147
const deletingReservationIdList = useMutationState({
@@ -45,16 +51,19 @@ export default function Navbar() {
4551

4652
const { mutate: requestLogout } = useMutation({
4753
mutationFn: postLogout,
48-
onError: () => {},
4954
onSuccess: () => {
50-
if (logout) logout();
55+
if (logout) {
56+
logout();
57+
toast.warning('로그아웃 되었습니다');
58+
navigate('/', { replace: true });
59+
}
5160
},
5261
});
5362

5463
const handleLogOut = () => {
5564
requestLogout();
5665
};
57-
//TODO 예약 내역
66+
5867
const isReservation = reservations && reservations.length > 0;
5968
const widthClass = `w-[${POPOVER_WIDTH}px]`;
6069
return (
@@ -77,10 +86,13 @@ export default function Navbar() {
7786
<Popover.Overlay>
7887
<Popover.Content>
7988
<div
80-
className={cx(widthClass, `flex flex-col gap-6 rounded-xl border bg-white p-6 shadow-2xl`)}>
89+
className={cx(
90+
widthClass,
91+
`flex max-h-[80vh] min-h-[300px] flex-col gap-6 rounded-xl border bg-white p-6 shadow-2xl`,
92+
)}>
8193
<h3 className="px-4 text-left text-heading3">예매 현황</h3>
8294
<Separator direction="row" />
83-
<div className="flex max-h-[800px] flex-col gap-6 overflow-y-scroll pr-4">
95+
<div className="flex max-h-[800px] flex-grow flex-col gap-6 overflow-y-scroll pr-4">
8496
{isReservation ? (
8597
reservations.map((reservation) => (
8698
<ReservationCard
@@ -91,7 +103,9 @@ export default function Navbar() {
91103
/>
92104
))
93105
) : (
94-
<div className="w-full text-heading2 text-typo-sub">현재 예매된 내역이 없습니다. </div>
106+
<div className="m-auto w-full text-heading2 text-typo-sub">
107+
현재 예매된 내역이 없습니다.{' '}
108+
</div>
95109
)}
96110
</div>
97111
<Separator direction="row" />

front/src/components/Toast/Toast.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default function Toast({ type, text, close }: ToastProps) {
5151
}
5252

5353
const toastVariant = cva(
54-
`flex gap-4 px-4 py-3 relative h-fit w-[300px] items-center rounded border whitespace-pre-line`,
54+
`flex gap-4 px-4 py-3 relative h-fit w-[300px] items-center rounded border whitespace-pre-line z-20`,
5555
{
5656
variants: {
5757
type: {

front/src/components/Toast/ToastContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default function ToastContainer() {
3636
};
3737
}, []);
3838
return (
39-
<div className="fixed left-8 top-20 flex flex-col gap-5 overflow-visible transition-all">
39+
<div className="fixed left-8 top-20 z-20 flex flex-col gap-5 overflow-visible transition-all">
4040
{toastList.map((toast) => (
4141
<Toast
4242
key={toast.id}

front/src/components/common/Popover.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
ReactElement,
55
ReactNode,
66
createContext,
7+
useCallback,
78
useContext,
9+
useEffect,
810
useRef,
911
useState,
1012
} from 'react';
@@ -87,19 +89,35 @@ interface IContent {
8789
}
8890
const Content = ({ children }: IContent) => {
8991
const { isOpen, triggerRef } = usePopoverContext();
92+
const [position, setPosition] = useState({ x: 0, y: 0 });
9093
const hasButtonRef = triggerRef && triggerRef.current;
9194
const canOpen = isOpen && hasButtonRef;
9295
//TODO 타입 정리필요, 상수 정리
93-
const { top, height } = triggerRef!.current!.getBoundingClientRect();
96+
const updatePosition = useCallback(() => {
97+
if (triggerRef && triggerRef.current) {
98+
const trigger = triggerRef.current!;
99+
const { top, height } = trigger.getBoundingClientRect();
100+
setPosition({ x: top, y: height });
101+
}
102+
}, [triggerRef]);
103+
104+
useEffect(() => {
105+
if (isOpen) {
106+
window.addEventListener('scroll', updatePosition);
107+
}
108+
return () => {
109+
window.removeEventListener('scroll', updatePosition);
110+
};
111+
}, [isOpen, updatePosition]);
94112

95113
return (
96114
<>
97115
{canOpen &&
98116
createPortal(
99117
<div
100-
className="fixed z-[999] cursor-default"
118+
className="fixed z-10 cursor-default"
101119
style={{
102-
top: top + height + 24,
120+
top: position.x + position.y + 24,
103121
right: 32,
104122
}}>
105123
{children}

front/src/components/loaders/WithLogin.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ import { Navigate } from 'react-router-dom';
33

44
import { useAuthContext } from '@/hooks/useAuthContext.tsx';
55

6+
import { toast } from '@/components/Toast/index.ts';
7+
68
import { ROUTE_URL } from '@/constants/index.ts';
79

810
//TODO toast 알림 추가 필ㄹ요
911
export default function WithLogin({ children }: PropsWithChildren) {
1012
const { isLogin } = useAuthContext();
11-
if (!isLogin) return <Navigate to={ROUTE_URL.USER.LOGIN} />;
13+
if (!isLogin) {
14+
toast.warning('로그인이 필요한 서비스입니다.\n로그인 후 이용해주세요.');
15+
return <Navigate to={ROUTE_URL.USER.LOGIN} />;
16+
}
1217
return <>{children}</>;
1318
}

0 commit comments

Comments
 (0)