diff --git a/src/app/design-system/page.tsx b/src/app/design-system/page.tsx index f1b53c2..0c65a68 100644 --- a/src/app/design-system/page.tsx +++ b/src/app/design-system/page.tsx @@ -4,7 +4,6 @@ import Button from '@/shared/components/button/Button'; import TextButton from '@/shared/components/button/TextButton'; import Input from '@/shared/components/Input-box/Input'; import { useState } from 'react'; -import { customToast } from '@/shared/components/toast/CustomToastUtils'; import ModalLayout from '@/shared/components/modal-pop/ModalLayout'; import SelectBox from '@/domains/shared/components/select-box/SelectBox'; @@ -13,10 +12,12 @@ import LikeBtn from '@/domains/community/components/like/LikeBtn'; import Share from '@/domains/shared/components/share/Share'; import Keep from '@/domains/shared/components/keep/Keep'; import ConfirmModal from '@/shared/components/modal-pop/ConfirmModal'; +import { useToast } from '@/shared/hook/useToast'; function Page() { const [isModalOpen, setModalOpen] = useState(false); const [isConfirmOpen, setConfirmOpen] = useState(false); + const { toastSuccess, toastInfo, toastError } = useToast(); return (
@@ -78,21 +79,21 @@ function Page() {
diff --git a/src/domains/login/components/LoginRedirectHandler.tsx b/src/domains/login/components/LoginRedirectHandler.tsx index 79d03b1..65a9c47 100644 --- a/src/domains/login/components/LoginRedirectHandler.tsx +++ b/src/domains/login/components/LoginRedirectHandler.tsx @@ -1,67 +1,11 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { usePathname, useRouter } from 'next/navigation'; -import { customToast } from '@/shared/components/toast/CustomToastUtils'; -import { getCookie, removeCookie } from '@/domains/shared/auth/utils/cookie'; -import { useAuthStore } from '@/domains/shared/store/auth'; import Spinner from '@/shared/components/spinner/Spinner'; import WelcomeModal from '@/domains/login/components/WelcomeModal'; +import { useLoginRedirect } from '../hook/useAuthHooks'; function LoginRedirectHandler() { - const pathname = usePathname(); - const router = useRouter(); - const { user, updateUser } = useAuthStore(); - const [loading, setLoading] = useState(true); - const [welcomeModalOpen, setWelcomeModalOpen] = useState(false); - - useEffect(() => { - if (!user && loading) { - updateUser() - .then((fetchedUser) => { - if (!fetchedUser) { - router.replace('/login'); - } - }) - .catch(() => { - router.replace('/login'); - }) - .finally(() => setLoading(false)); - } else { - setLoading(false); - } - }, [user, loading, updateUser, router]); - - useEffect(() => { - if (!user || loading) return; - - const preLoginPath = getCookie('preLoginPath') || '/'; - // 로그인 상태인데 이전 페이지가 /login이면 메인으로 이동 - if (user && preLoginPath === '/login') { - router.replace('/'); - removeCookie('preLoginPath'); - return; - } - - // 첫 유저일 경우 모달 오픈 - if (pathname.startsWith('/login/user/first-user')) { - setWelcomeModalOpen(true); - } - // 기존 유저일 경우 - else if (pathname.startsWith('/login/user/success')) { - customToast.success(`${user.nickname}님 \n 로그인 성공 🎉`); - router.replace(preLoginPath); - removeCookie('preLoginPath'); - } - }, [pathname, user, loading, router]); - - // 환영 모달 닫힐 때 이동 - const handleCloseWelcomeModal = () => { - setWelcomeModalOpen(false); - const preLoginPath = getCookie('preLoginPath') || '/'; - removeCookie('preLoginPath'); - router.replace(preLoginPath); - }; + const { loading, welcomeModalOpen, handleCloseWelcomeModal, user } = useLoginRedirect(); if (loading) { return ( diff --git a/src/domains/login/components/LogoutConfirm.tsx b/src/domains/login/components/LogoutConfirm.tsx index 330f25b..8329aac 100644 --- a/src/domains/login/components/LogoutConfirm.tsx +++ b/src/domains/login/components/LogoutConfirm.tsx @@ -1,18 +1,23 @@ import ConfirmModal from '@/shared/components/modal-pop/ConfirmModal'; +import { useLogout } from '../hook/useAuthHooks'; interface Props { open: boolean; onClose: () => void; - onLogout: () => void; } -function LogoutConfirm({ open, onClose, onLogout }: Props) { +function LogoutConfirm({ open, onClose }: Props) { + const logoutHandler = useLogout(); + return ( { + await logoutHandler(); + onClose(); + }} onCancel={onClose} /> ); diff --git a/src/domains/login/hook/useAuthHooks.ts b/src/domains/login/hook/useAuthHooks.ts new file mode 100644 index 0000000..6fd5a96 --- /dev/null +++ b/src/domains/login/hook/useAuthHooks.ts @@ -0,0 +1,75 @@ +import { useAuthStore } from '@/domains/shared/store/auth'; +import { useCallback } from 'react'; +import { useEffect, useState } from 'react'; +import { usePathname, useRouter } from 'next/navigation'; +import { getCookie, removeCookie } from '@/domains/shared/auth/utils/cookie'; +import { useToast } from '@/shared/hook/useToast'; + +export const useLogout = () => { + const logout = useAuthStore((state) => state.logout); + const { toastSuccess, toastError } = useToast(); + + const handleLogout = useCallback(async () => { + try { + await logout(); + toastSuccess('로그아웃 되었습니다.'); + } catch (err) { + console.error('로그아웃 실패', err); + toastError('로그아웃 실패 ❌ 다시 시도해주세요.'); + } + }, [logout, toastSuccess, toastError]); + + return handleLogout; +}; + +export const useLoginRedirect = () => { + const router = useRouter(); + const pathname = usePathname(); + const { user, updateUser } = useAuthStore(); + const { toastSuccess } = useToast(); + + const [loading, setLoading] = useState(true); + const [welcomeModalOpen, setWelcomeModalOpen] = useState(false); + + useEffect(() => { + if (!user && loading) { + updateUser() + .then((fetchedUser) => { + if (!fetchedUser) router.replace('/login'); + }) + .catch(() => router.replace('/login')) + .finally(() => setLoading(false)); + } else { + setLoading(false); + } + }, [user, loading, updateUser, router]); + + useEffect(() => { + if (!user || loading) return; + + const preLoginPath = getCookie('preLoginPath') || '/'; + + if (user && preLoginPath === '/login') { + router.replace('/'); + removeCookie('preLoginPath'); + return; + } + + if (pathname.startsWith('/login/user/first-user')) { + setWelcomeModalOpen(true); + } else if (pathname.startsWith('/login/user/success')) { + toastSuccess(`${user.nickname}님 \n 로그인 성공 🎉`); + router.replace(preLoginPath); + removeCookie('preLoginPath'); + } + }, [pathname, user, loading, router, toastSuccess]); + + const handleCloseWelcomeModal = () => { + setWelcomeModalOpen(false); + const preLoginPath = getCookie('preLoginPath') || '/'; + removeCookie('preLoginPath'); + router.replace(preLoginPath); + }; + + return { loading, welcomeModalOpen, handleCloseWelcomeModal, user }; +}; diff --git a/src/domains/recommend/components/BotMessage.tsx b/src/domains/recommend/components/BotMessage.tsx index 80652ec..d9bb2b8 100644 --- a/src/domains/recommend/components/BotMessage.tsx +++ b/src/domains/recommend/components/BotMessage.tsx @@ -5,6 +5,7 @@ import Image from 'next/image'; import { useState } from 'react'; import BotCocktailCard from './BotCocktailCard'; import BotOptions from './BotOptions'; +import TypingIndicator from './TypingIndicator'; interface Message { id: string; @@ -82,6 +83,7 @@ function BotMessage() { )}
))} +
); diff --git a/src/domains/recommend/components/ChatSection.tsx b/src/domains/recommend/components/ChatSection.tsx index 8dfc12b..de25c7e 100644 --- a/src/domains/recommend/components/ChatSection.tsx +++ b/src/domains/recommend/components/ChatSection.tsx @@ -4,7 +4,6 @@ import { useState } from 'react'; import BotMessage from './BotMessage'; import UserMessage from './UserMessage'; import MessageInput from './MessageInput'; -import TypingIndicator from './TypingIndicator'; function ChatSection() { const [messages, setMessages] = useState([]); @@ -18,7 +17,7 @@ function ChatSection() {

대화 목록 및 입력 창

- + {messages.map((msg, i) => ( ))} diff --git a/src/domains/recommend/components/TypingIndicator.tsx b/src/domains/recommend/components/TypingIndicator.tsx index 4d77749..6704dda 100644 --- a/src/domains/recommend/components/TypingIndicator.tsx +++ b/src/domains/recommend/components/TypingIndicator.tsx @@ -6,7 +6,15 @@ function TypingIndicator() {

준비 중…

- Cocktail Shaker +
); diff --git a/src/domains/shared/store/auth.ts b/src/domains/shared/store/auth.ts index 26f0796..f4ac90b 100644 --- a/src/domains/shared/store/auth.ts +++ b/src/domains/shared/store/auth.ts @@ -1,4 +1,3 @@ -import { customToast } from '@/shared/components/toast/CustomToastUtils'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; @@ -36,7 +35,6 @@ export const useAuthStore = create()( setUser: (user, token) => { const updatedUser = { ...user, abv_degree: 5.0 }; set({ user: updatedUser, accessToken: token, isLoggedIn: true }); - customToast.success(`${updatedUser.nickname}님, 로그인 성공 🎉`); }, logout: async () => { @@ -45,10 +43,8 @@ export const useAuthStore = create()( method: 'POST', credentials: 'include', }); - customToast.success('로그아웃 되었습니다.'); set({ user: null, accessToken: null, isLoggedIn: false }); } catch (err) { - customToast.error('로그아웃 실패❌ \n 다시 시도해주세요.'); console.error('로그아웃 실패', err); } }, diff --git a/src/shared/components/header/DropdownMenu.tsx b/src/shared/components/header/DropdownMenu.tsx index b333b04..be084c6 100644 --- a/src/shared/components/header/DropdownMenu.tsx +++ b/src/shared/components/header/DropdownMenu.tsx @@ -160,15 +160,7 @@ function DropdownMenu({ isClicked, setIsClicked, visible, setVisible }: Props) {
{logoutModalOpen && ( - setLogoutModalOpen(false)} - onLogout={async () => { - await logout(); - setLogoutModalOpen(false); - setIsClicked(false); - }} - /> + setLogoutModalOpen(false)} /> )} , document.body diff --git a/src/shared/components/header/HeaderBtn.tsx b/src/shared/components/header/HeaderBtn.tsx index 07808a4..3c5b4be 100644 --- a/src/shared/components/header/HeaderBtn.tsx +++ b/src/shared/components/header/HeaderBtn.tsx @@ -10,7 +10,7 @@ import { useState } from 'react'; import LogoutConfirm from '@/domains/login/components/LogoutConfirm'; function HeaderBtn({ pathname }: { pathname: string }) { - const { isLoggedIn, logout } = useAuthStore(); + const { isLoggedIn } = useAuthStore(); const router = useRouter(); const [logoutModalOpen, setLogoutModalOpen] = useState(false); @@ -78,14 +78,7 @@ function HeaderBtn({ pathname }: { pathname: string }) { {/* 로그아웃 확인 모달 */} {logoutModalOpen && ( - setLogoutModalOpen(false)} - onLogout={async () => { - await logout(); - setLogoutModalOpen(false); - }} - /> + setLogoutModalOpen(false)} /> )} ); diff --git a/src/shared/components/toast/CustomToastUtils.tsx b/src/shared/components/toast/CustomToastUtils.tsx deleted file mode 100644 index 5da1b3c..0000000 --- a/src/shared/components/toast/CustomToastUtils.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import toast from 'react-hot-toast'; -import CustomToast from './CustomToast'; - -export const customToast = { - success: (message: string) => { - toast.dismiss(); - toast( toast.dismiss()} />); - }, - info: (message: string) => { - toast.dismiss(); - toast( toast.dismiss()} />); - }, - error: (message: string) => { - toast.dismiss(); - toast( toast.dismiss()} />); - }, -}; diff --git a/src/shared/hook/useToast.tsx b/src/shared/hook/useToast.tsx new file mode 100644 index 0000000..e45a30c --- /dev/null +++ b/src/shared/hook/useToast.tsx @@ -0,0 +1,22 @@ +import { useCallback } from 'react'; +import toast from 'react-hot-toast'; +import CustomToast from '../components/toast/CustomToast'; + +export const useToast = () => { + const toastSuccess = useCallback((message: string) => { + toast.dismiss(); + toast( toast.dismiss()} />); + }, []); + + const toastInfo = useCallback((message: string) => { + toast.dismiss(); + toast( toast.dismiss()} />); + }, []); + + const toastError = useCallback((message: string) => { + toast.dismiss(); + toast( toast.dismiss()} />); + }, []); + + return { toastSuccess, toastInfo, toastError }; +}; diff --git a/src/shared/styles/_base.css b/src/shared/styles/_base.css index 3b997f9..c408e1a 100644 --- a/src/shared/styles/_base.css +++ b/src/shared/styles/_base.css @@ -6,4 +6,30 @@ select { appearance: none; } + + /* scroll 커스텀 */ + *::-webkit-scrollbar { + width: 6px; + } + + *::-webkit-scrollbar-track { + background-color: var(--color-primary); + border-radius: 10px; + } + + *::-webkit-scrollbar-thumb { + cursor: pointer; + border-radius: 10px; + background-color: var(--color-gray); + transition: background-color 0.3s ease; + } + + *::-webkit-scrollbar-thumb:hover { + background-color: var(--color-secondary); + } + + *::-webkit-scrollbar-button { + width: 0; + height: 0; + } }