Skip to content

Commit 2ae579f

Browse files
authored
[fix, refactor] 스크롤 커스텀 및 토스트/로그인 로직 및 훅 분리 (#84)
* [fix] next이미지 경고 수정 및 챗봇 챗 로딩 위치 이동 * [style] 전체페이지 스크롤 커스텀 * [fix] toast 훅으로 변경, 로그아웃 로직 훅으로 변경 * [refactor] 훅 분리 * [docs] useToast 훅 폴더 경로 변경 * [docs] 폴더 명 변경
1 parent 431ef3a commit 2ae579f

File tree

13 files changed

+153
-107
lines changed

13 files changed

+153
-107
lines changed

src/app/design-system/page.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Button from '@/shared/components/button/Button';
44
import TextButton from '@/shared/components/button/TextButton';
55
import Input from '@/shared/components/Input-box/Input';
66
import { useState } from 'react';
7-
import { customToast } from '@/shared/components/toast/CustomToastUtils';
87
import ModalLayout from '@/shared/components/modal-pop/ModalLayout';
98
import SelectBox from '@/domains/shared/components/select-box/SelectBox';
109

@@ -13,10 +12,12 @@ import LikeBtn from '@/domains/community/components/like/LikeBtn';
1312
import Share from '@/domains/shared/components/share/Share';
1413
import Keep from '@/domains/shared/components/keep/Keep';
1514
import ConfirmModal from '@/shared/components/modal-pop/ConfirmModal';
15+
import { useToast } from '@/shared/hook/useToast';
1616

1717
function Page() {
1818
const [isModalOpen, setModalOpen] = useState(false);
1919
const [isConfirmOpen, setConfirmOpen] = useState(false);
20+
const { toastSuccess, toastInfo, toastError } = useToast();
2021

2122
return (
2223
<div className="p-6 space-y-6 bg-primary">
@@ -78,21 +79,21 @@ function Page() {
7879
<div className="flex gap-2">
7980
<button
8081
className="px-4 py-2 bg-green-300 text-black rounded"
81-
onClick={() => customToast.success('성공 메시지 \n 줄바꿈은 이렇게')}
82+
onClick={() => toastSuccess('성공 메시지 \n 줄바꿈은 이렇게')}
8283
>
8384
Success Toast
8485
</button>
8586

8687
<button
8788
className="px-4 py-2 bg-yellow-100 text-black rounded"
88-
onClick={() => customToast.info('정보 메시지')}
89+
onClick={() => toastInfo('정보 메시지')}
8990
>
9091
Info Toast
9192
</button>
9293

9394
<button
9495
className="px-4 py-2 bg-red-200 text-black rounded"
95-
onClick={() => customToast.error('오류 메시지')}
96+
onClick={() => toastError('오류 메시지')}
9697
>
9798
Error Toast
9899
</button>

src/domains/login/components/LoginRedirectHandler.tsx

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,11 @@
11
'use client';
22

3-
import { useEffect, useState } from 'react';
4-
import { usePathname, useRouter } from 'next/navigation';
5-
import { customToast } from '@/shared/components/toast/CustomToastUtils';
6-
import { getCookie, removeCookie } from '@/domains/shared/auth/utils/cookie';
7-
import { useAuthStore } from '@/domains/shared/store/auth';
83
import Spinner from '@/shared/components/spinner/Spinner';
94
import WelcomeModal from '@/domains/login/components/WelcomeModal';
5+
import { useLoginRedirect } from '../hook/useAuthHooks';
106

117
function LoginRedirectHandler() {
12-
const pathname = usePathname();
13-
const router = useRouter();
14-
const { user, updateUser } = useAuthStore();
15-
const [loading, setLoading] = useState(true);
16-
const [welcomeModalOpen, setWelcomeModalOpen] = useState(false);
17-
18-
useEffect(() => {
19-
if (!user && loading) {
20-
updateUser()
21-
.then((fetchedUser) => {
22-
if (!fetchedUser) {
23-
router.replace('/login');
24-
}
25-
})
26-
.catch(() => {
27-
router.replace('/login');
28-
})
29-
.finally(() => setLoading(false));
30-
} else {
31-
setLoading(false);
32-
}
33-
}, [user, loading, updateUser, router]);
34-
35-
useEffect(() => {
36-
if (!user || loading) return;
37-
38-
const preLoginPath = getCookie('preLoginPath') || '/';
39-
// 로그인 상태인데 이전 페이지가 /login이면 메인으로 이동
40-
if (user && preLoginPath === '/login') {
41-
router.replace('/');
42-
removeCookie('preLoginPath');
43-
return;
44-
}
45-
46-
// 첫 유저일 경우 모달 오픈
47-
if (pathname.startsWith('/login/user/first-user')) {
48-
setWelcomeModalOpen(true);
49-
}
50-
// 기존 유저일 경우
51-
else if (pathname.startsWith('/login/user/success')) {
52-
customToast.success(`${user.nickname}님 \n 로그인 성공 🎉`);
53-
router.replace(preLoginPath);
54-
removeCookie('preLoginPath');
55-
}
56-
}, [pathname, user, loading, router]);
57-
58-
// 환영 모달 닫힐 때 이동
59-
const handleCloseWelcomeModal = () => {
60-
setWelcomeModalOpen(false);
61-
const preLoginPath = getCookie('preLoginPath') || '/';
62-
removeCookie('preLoginPath');
63-
router.replace(preLoginPath);
64-
};
8+
const { loading, welcomeModalOpen, handleCloseWelcomeModal, user } = useLoginRedirect();
659

6610
if (loading) {
6711
return (

src/domains/login/components/LogoutConfirm.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import ConfirmModal from '@/shared/components/modal-pop/ConfirmModal';
2+
import { useLogout } from '../hook/useAuthHooks';
23

34
interface Props {
45
open: boolean;
56
onClose: () => void;
6-
onLogout: () => void;
77
}
88

9-
function LogoutConfirm({ open, onClose, onLogout }: Props) {
9+
function LogoutConfirm({ open, onClose }: Props) {
10+
const logoutHandler = useLogout();
11+
1012
return (
1113
<ConfirmModal
1214
open={open}
1315
onClose={onClose}
1416
description="정말 로그아웃 하시겠어요?"
15-
onConfirm={onLogout}
17+
onConfirm={async () => {
18+
await logoutHandler();
19+
onClose();
20+
}}
1621
onCancel={onClose}
1722
/>
1823
);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useAuthStore } from '@/domains/shared/store/auth';
2+
import { useCallback } from 'react';
3+
import { useEffect, useState } from 'react';
4+
import { usePathname, useRouter } from 'next/navigation';
5+
import { getCookie, removeCookie } from '@/domains/shared/auth/utils/cookie';
6+
import { useToast } from '@/shared/hook/useToast';
7+
8+
export const useLogout = () => {
9+
const logout = useAuthStore((state) => state.logout);
10+
const { toastSuccess, toastError } = useToast();
11+
12+
const handleLogout = useCallback(async () => {
13+
try {
14+
await logout();
15+
toastSuccess('로그아웃 되었습니다.');
16+
} catch (err) {
17+
console.error('로그아웃 실패', err);
18+
toastError('로그아웃 실패 ❌ 다시 시도해주세요.');
19+
}
20+
}, [logout, toastSuccess, toastError]);
21+
22+
return handleLogout;
23+
};
24+
25+
export const useLoginRedirect = () => {
26+
const router = useRouter();
27+
const pathname = usePathname();
28+
const { user, updateUser } = useAuthStore();
29+
const { toastSuccess } = useToast();
30+
31+
const [loading, setLoading] = useState(true);
32+
const [welcomeModalOpen, setWelcomeModalOpen] = useState(false);
33+
34+
useEffect(() => {
35+
if (!user && loading) {
36+
updateUser()
37+
.then((fetchedUser) => {
38+
if (!fetchedUser) router.replace('/login');
39+
})
40+
.catch(() => router.replace('/login'))
41+
.finally(() => setLoading(false));
42+
} else {
43+
setLoading(false);
44+
}
45+
}, [user, loading, updateUser, router]);
46+
47+
useEffect(() => {
48+
if (!user || loading) return;
49+
50+
const preLoginPath = getCookie('preLoginPath') || '/';
51+
52+
if (user && preLoginPath === '/login') {
53+
router.replace('/');
54+
removeCookie('preLoginPath');
55+
return;
56+
}
57+
58+
if (pathname.startsWith('/login/user/first-user')) {
59+
setWelcomeModalOpen(true);
60+
} else if (pathname.startsWith('/login/user/success')) {
61+
toastSuccess(`${user.nickname}님 \n 로그인 성공 🎉`);
62+
router.replace(preLoginPath);
63+
removeCookie('preLoginPath');
64+
}
65+
}, [pathname, user, loading, router, toastSuccess]);
66+
67+
const handleCloseWelcomeModal = () => {
68+
setWelcomeModalOpen(false);
69+
const preLoginPath = getCookie('preLoginPath') || '/';
70+
removeCookie('preLoginPath');
71+
router.replace(preLoginPath);
72+
};
73+
74+
return { loading, welcomeModalOpen, handleCloseWelcomeModal, user };
75+
};

src/domains/recommend/components/BotMessage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Image from 'next/image';
55
import { useState } from 'react';
66
import BotCocktailCard from './BotCocktailCard';
77
import BotOptions from './BotOptions';
8+
import TypingIndicator from './TypingIndicator';
89

910
interface Message {
1011
id: string;
@@ -82,6 +83,7 @@ function BotMessage() {
8283
)}
8384
</div>
8485
))}
86+
<TypingIndicator />
8587
</div>
8688
</article>
8789
);

src/domains/recommend/components/ChatSection.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useState } from 'react';
44
import BotMessage from './BotMessage';
55
import UserMessage from './UserMessage';
66
import MessageInput from './MessageInput';
7-
import TypingIndicator from './TypingIndicator';
87

98
function ChatSection() {
109
const [messages, setMessages] = useState<string[]>([]);
@@ -18,7 +17,7 @@ function ChatSection() {
1817
<h2 className="sr-only">대화 목록 및 입력 창</h2>
1918
<div className="flex flex-col gap-10 pb-20">
2019
<BotMessage />
21-
<TypingIndicator />
20+
2221
{messages.map((msg, i) => (
2322
<UserMessage key={i} message={msg} />
2423
))}

src/domains/recommend/components/TypingIndicator.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ function TypingIndicator() {
66
<div className="relative flex items-center w-fit p-3 rounded-2xl rounded-tl-none bg-white text-black overflow-hidden">
77
<p className="inline-block animate-fade-in">준비 중…</p>
88
<div className="relative w-10 h-10 animate-shake">
9-
<Image src={shaker} alt="Cocktail Shaker" fill className="object-contain" priority />
9+
<Image
10+
src={shaker}
11+
alt=""
12+
width={40}
13+
height={40}
14+
className="object-contain"
15+
priority
16+
aria-hidden
17+
/>
1018
</div>
1119
</div>
1220
);

src/domains/shared/store/auth.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { customToast } from '@/shared/components/toast/CustomToastUtils';
21
import { create } from 'zustand';
32
import { persist } from 'zustand/middleware';
43

@@ -36,7 +35,6 @@ export const useAuthStore = create<AuthState>()(
3635
setUser: (user, token) => {
3736
const updatedUser = { ...user, abv_degree: 5.0 };
3837
set({ user: updatedUser, accessToken: token, isLoggedIn: true });
39-
customToast.success(`${updatedUser.nickname}님, 로그인 성공 🎉`);
4038
},
4139

4240
logout: async () => {
@@ -45,10 +43,8 @@ export const useAuthStore = create<AuthState>()(
4543
method: 'POST',
4644
credentials: 'include',
4745
});
48-
customToast.success('로그아웃 되었습니다.');
4946
set({ user: null, accessToken: null, isLoggedIn: false });
5047
} catch (err) {
51-
customToast.error('로그아웃 실패❌ \n 다시 시도해주세요.');
5248
console.error('로그아웃 실패', err);
5349
}
5450
},

src/shared/components/header/DropdownMenu.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,7 @@ function DropdownMenu({ isClicked, setIsClicked, visible, setVisible }: Props) {
160160
</div>
161161
</nav>
162162
{logoutModalOpen && (
163-
<LogoutConfirm
164-
open={logoutModalOpen}
165-
onClose={() => setLogoutModalOpen(false)}
166-
onLogout={async () => {
167-
await logout();
168-
setLogoutModalOpen(false);
169-
setIsClicked(false);
170-
}}
171-
/>
163+
<LogoutConfirm open={logoutModalOpen} onClose={() => setLogoutModalOpen(false)} />
172164
)}
173165
</>,
174166
document.body

src/shared/components/header/HeaderBtn.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { useState } from 'react';
1010
import LogoutConfirm from '@/domains/login/components/LogoutConfirm';
1111

1212
function HeaderBtn({ pathname }: { pathname: string }) {
13-
const { isLoggedIn, logout } = useAuthStore();
13+
const { isLoggedIn } = useAuthStore();
1414
const router = useRouter();
1515
const [logoutModalOpen, setLogoutModalOpen] = useState(false);
1616

@@ -78,14 +78,7 @@ function HeaderBtn({ pathname }: { pathname: string }) {
7878

7979
{/* 로그아웃 확인 모달 */}
8080
{logoutModalOpen && (
81-
<LogoutConfirm
82-
open={logoutModalOpen}
83-
onClose={() => setLogoutModalOpen(false)}
84-
onLogout={async () => {
85-
await logout();
86-
setLogoutModalOpen(false);
87-
}}
88-
/>
81+
<LogoutConfirm open={logoutModalOpen} onClose={() => setLogoutModalOpen(false)} />
8982
)}
9083
</>
9184
);

0 commit comments

Comments
 (0)