Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 '../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 (
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 '../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 (
<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/hook/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