Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/app/design-system/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 (
<div className="p-6 space-y-6 bg-primary">
Expand Down Expand Up @@ -78,21 +79,21 @@ function Page() {
<div className="flex gap-2">
<button
className="px-4 py-2 bg-green-300 text-black rounded"
onClick={() => customToast.success('성공 메시지 \n 줄바꿈은 이렇게')}
onClick={() => toastSuccess('성공 메시지 \n 줄바꿈은 이렇게')}
>
Success Toast
</button>

<button
className="px-4 py-2 bg-yellow-100 text-black rounded"
onClick={() => customToast.info('정보 메시지')}
onClick={() => toastInfo('정보 메시지')}
>
Info Toast
</button>

<button
className="px-4 py-2 bg-red-200 text-black rounded"
onClick={() => customToast.error('오류 메시지')}
onClick={() => toastError('오류 메시지')}
>
Error Toast
</button>
Expand Down
60 changes: 2 additions & 58 deletions src/domains/login/components/LoginRedirectHandler.tsx
Original file line number Diff line number Diff line change
@@ -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 '../hooks/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 (
Expand Down
11 changes: 8 additions & 3 deletions src/domains/login/components/LogoutConfirm.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import ConfirmModal from '@/shared/components/modal-pop/ConfirmModal';
import { useLogout } from '../hooks/useAuthHooks';

interface Props {
open: boolean;
onClose: () => void;
onLogout: () => void;
}

function LogoutConfirm({ open, onClose, onLogout }: Props) {
function LogoutConfirm({ open, onClose }: Props) {
const logoutHandler = useLogout();

return (
<ConfirmModal
open={open}
onClose={onClose}
description="정말 로그아웃 하시겠어요?"
onConfirm={onLogout}
onConfirm={async () => {
await logoutHandler();
onClose();
}}
onCancel={onClose}
/>
);
Expand Down
75 changes: 75 additions & 0 deletions src/domains/login/hooks/useAuthHooks.ts
Original file line number Diff line number Diff line change
@@ -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 };
};
2 changes: 2 additions & 0 deletions src/domains/recommend/components/BotMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -82,6 +83,7 @@ function BotMessage() {
)}
</div>
))}
<TypingIndicator />
</div>
</article>
);
Expand Down
3 changes: 1 addition & 2 deletions src/domains/recommend/components/ChatSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string[]>([]);
Expand All @@ -18,7 +17,7 @@ function ChatSection() {
<h2 className="sr-only">대화 목록 및 입력 창</h2>
<div className="flex flex-col gap-10 pb-20">
<BotMessage />
<TypingIndicator />

{messages.map((msg, i) => (
<UserMessage key={i} message={msg} />
))}
Expand Down
10 changes: 9 additions & 1 deletion src/domains/recommend/components/TypingIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ function TypingIndicator() {
<div className="relative flex items-center w-fit p-3 rounded-2xl rounded-tl-none bg-white text-black overflow-hidden">
<p className="inline-block animate-fade-in">준비 중…</p>
<div className="relative w-10 h-10 animate-shake">
<Image src={shaker} alt="Cocktail Shaker" fill className="object-contain" priority />
<Image
src={shaker}
alt=""
width={40}
height={40}
className="object-contain"
priority
aria-hidden
/>
</div>
</div>
);
Expand Down
4 changes: 0 additions & 4 deletions src/domains/shared/store/auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { customToast } from '@/shared/components/toast/CustomToastUtils';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

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

logout: async () => {
Expand All @@ -45,10 +43,8 @@ export const useAuthStore = create<AuthState>()(
method: 'POST',
credentials: 'include',
});
customToast.success('로그아웃 되었습니다.');
set({ user: null, accessToken: null, isLoggedIn: false });
} catch (err) {
customToast.error('로그아웃 실패❌ \n 다시 시도해주세요.');
console.error('로그아웃 실패', err);
}
},
Expand Down
10 changes: 1 addition & 9 deletions src/shared/components/header/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,7 @@ function DropdownMenu({ isClicked, setIsClicked, visible, setVisible }: Props) {
</div>
</nav>
{logoutModalOpen && (
<LogoutConfirm
open={logoutModalOpen}
onClose={() => setLogoutModalOpen(false)}
onLogout={async () => {
await logout();
setLogoutModalOpen(false);
setIsClicked(false);
}}
/>
<LogoutConfirm open={logoutModalOpen} onClose={() => setLogoutModalOpen(false)} />
)}
</>,
document.body
Expand Down
11 changes: 2 additions & 9 deletions src/shared/components/header/HeaderBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

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

{/* 로그아웃 확인 모달 */}
{logoutModalOpen && (
<LogoutConfirm
open={logoutModalOpen}
onClose={() => setLogoutModalOpen(false)}
onLogout={async () => {
await logout();
setLogoutModalOpen(false);
}}
/>
<LogoutConfirm open={logoutModalOpen} onClose={() => setLogoutModalOpen(false)} />
)}
</>
);
Expand Down
17 changes: 0 additions & 17 deletions src/shared/components/toast/CustomToastUtils.tsx

This file was deleted.

22 changes: 22 additions & 0 deletions src/shared/hook/useToast.tsx
Original file line number Diff line number Diff line change
@@ -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(<CustomToast type="success" message={message} onClose={() => toast.dismiss()} />);
}, []);

const toastInfo = useCallback((message: string) => {
toast.dismiss();
toast(<CustomToast type="info" message={message} onClose={() => toast.dismiss()} />);
}, []);

const toastError = useCallback((message: string) => {
toast.dismiss();
toast(<CustomToast type="error" message={message} onClose={() => toast.dismiss()} />);
}, []);

return { toastSuccess, toastInfo, toastError };
};
Loading