diff --git a/src/app/(layout)/advice/_components/AdviceEditor/index.tsx b/src/app/(layout)/advice/_components/AdviceEditor/index.tsx index 29d5993..8acd4c6 100644 --- a/src/app/(layout)/advice/_components/AdviceEditor/index.tsx +++ b/src/app/(layout)/advice/_components/AdviceEditor/index.tsx @@ -10,9 +10,11 @@ import RecommendTemplates from '../RecommendTemplates'; import IconHelp from '@/assets/icons/icon-help.svg'; import { usePostAdvice } from '@/hooks/models/usePostAdvice'; import { useSpellCheck } from '@/hooks/models/usePostSpellCheck'; +import { useAuth } from '@/hooks/useAuth'; export default function AdviceEditor() { const { currentTemplate } = useTemplateStore(); + const { requireAuth } = useAuth(); const [draftContent, setDraftContent] = useState(currentTemplate?.content || ''); const [tags, setTags] = useState([]); const [aiMode, setAiMode] = useState(true); @@ -52,10 +54,12 @@ export default function AdviceEditor() { const handleSubmit = () => { if (aiMode) { - mutate({ - content: draftContent, - tags: tags.filter((tag) => tag.trim() !== ''), - }); + if (requireAuth()) { + mutate({ + content: draftContent, + tags: tags.filter((tag) => tag.trim() !== ''), + }); + } } else { spellCheckMutate({ content: draftContent, diff --git a/src/app/(layout)/advice/_components/RecommendTemplates/index.tsx b/src/app/(layout)/advice/_components/RecommendTemplates/index.tsx index 44d5883..4ad2de2 100644 --- a/src/app/(layout)/advice/_components/RecommendTemplates/index.tsx +++ b/src/app/(layout)/advice/_components/RecommendTemplates/index.tsx @@ -6,10 +6,20 @@ import Card from '@/components/ui/Card'; import { TemplateType } from '@/types'; import { useTemplatesRecommendation } from '@/hooks/template/useTemplatesRecommendation'; import { useTemplatesLikes } from '@/hooks/template/useTemplateLikes'; +import { useAuth } from '@/hooks/useAuth'; +import { checkAuth } from '@/utils/checkAuth'; export default function RecommendTemplates() { + const { requireAuth } = useAuth(); + const [isAuthenticated, setIsAuthenticated] = useState(() => { + if (typeof window !== 'undefined') { + return checkAuth(); + } + return false; + }); + const { data: recommendData } = useTemplatesRecommendation(); - const { data: likeData } = useTemplatesLikes(); + const { data: likeData } = useTemplatesLikes({ enabled: isAuthenticated }); const asideList = [ { label: 'recommend', text: '추천 템플릿' }, @@ -19,12 +29,28 @@ export default function RecommendTemplates() { const [asideState, setAsideState] = useState('recommend'); const [templates, setTemplates] = useState([]); + useEffect(() => { + setIsAuthenticated(checkAuth()); + }, []); + const handleAside = (label: string) => { + if (label === 'like' && !requireAuth()) { + return; + } setAsideState(label); }; useEffect(() => { - const response = asideState === 'recommend' ? recommendData : likeData; + let response; + + if (asideState === 'recommend') { + response = recommendData; + } else if (asideState === 'like' && isAuthenticated) { + response = likeData; + } else { + response = null; + } + const templates = response?.templates.map((templates) => ({ title: templates.title, @@ -36,7 +62,7 @@ export default function RecommendTemplates() { })) ?? []; setTemplates(templates); - }, [recommendData, likeData, asideState]); + }, [recommendData, likeData, asideState, isAuthenticated]); return (
diff --git a/src/assets/icons/image-login.svg b/src/assets/icons/image-login.svg new file mode 100644 index 0000000..dbe6c7e --- /dev/null +++ b/src/assets/icons/image-login.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/ui/Header/index.tsx b/src/components/ui/Header/index.tsx index 734431d..cab0420 100644 --- a/src/components/ui/Header/index.tsx +++ b/src/components/ui/Header/index.tsx @@ -2,14 +2,18 @@ import { useEffect, useState } from 'react'; import Link from 'next/link'; +import { useRouter } from 'next/navigation'; import { usePathname } from 'next/navigation'; import { cn } from '@/lib/utils'; +import { useAuth } from '@/hooks/useAuth'; import { Button } from '../Button'; import Logo from '@/assets/logo.svg'; export default function Header() { const pathname = usePathname(); + const router = useRouter(); const [isLogin, setIsLogin] = useState(false); + const { requireAuth } = useAuth(); const menus = [ { label: '조언받기', href: '/advice' }, @@ -21,7 +25,7 @@ export default function Header() { const token = localStorage.getItem('token'); const user = localStorage.getItem('user'); setIsLogin(!!(token && user)); - }, []); + }, [isLogin]); return (
@@ -33,13 +37,21 @@ export default function Header() { {menus.map((menu) => { const isActive = pathname.startsWith(menu.href); return ( - { + if (menu.label === '보관함') { + if (requireAuth()) { + router.push(menu.href); + } + } else { + router.push(menu.href); + } + }} className={cn('text-layout-grey5 button-lg', isActive && 'text-layout-grey6')} > {menu.label} - + ); })} @@ -54,7 +66,9 @@ export default function Header() { variant="grey" size="small" onClick={() => { - alert('로그아웃!'); + window.localStorage.removeItem('token'); + window.localStorage.removeItem('user'); + window.location.reload(); }} > 로그아웃 diff --git a/src/components/ui/Modal/LoginModal/index.tsx b/src/components/ui/Modal/LoginModal/index.tsx new file mode 100644 index 0000000..20606ad --- /dev/null +++ b/src/components/ui/Modal/LoginModal/index.tsx @@ -0,0 +1,41 @@ +import { useRouter } from 'next/navigation'; +import { useLoginModalStore } from '@/stores/useLoginModal'; +import { Spacing } from '../../Spacing'; +import { Button } from '../../Button'; +import IconClose from '@/assets/icons/icon-close.svg'; +import ImageLogin from '@/assets/icons/image-login.svg'; + +export default function LoginModal() { + const router = useRouter(); + + const { isLoginOpen, closeLoginModal } = useLoginModalStore(); + + if (!isLoginOpen) return null; + + return ( +
+ + + +
이 기능은 로그인 후에 사용할 수 있습니다.
+ +
로그인하고 더 많은 기능을 누려보세요!
+ +
+ + +
+ +
+ ); +} diff --git a/src/components/ui/Modal/ModalContainer.tsx b/src/components/ui/Modal/ModalContainer.tsx index 58341b6..f1982ea 100644 --- a/src/components/ui/Modal/ModalContainer.tsx +++ b/src/components/ui/Modal/ModalContainer.tsx @@ -2,16 +2,19 @@ import { useModalStore } from '@/stores/useModalStore'; import { useUnsaveModalStore } from '@/stores/useUnsaveModalStore'; +import { useLoginModalStore } from '@/stores/useLoginModal'; import ViewModal from './ViewModal'; import EditModal from './EditModal'; import UsingModal from './UsingModal'; import ProfileModal from './ProfileModal'; import UnSaveModal from './UnsaveModal'; +import LoginModal from './LoginModal'; import { useEffect } from 'react'; export default function ModalContainer() { const { currentModal, draftTitle, draftContent, draftTags, selectedTemplateId } = useModalStore(); const { isUnsaveOpen } = useUnsaveModalStore(); + const { isLoginOpen} = useLoginModalStore(); useEffect(() => { if (currentModal) { @@ -25,7 +28,7 @@ export default function ModalContainer() { }; }, [currentModal]); - if (!currentModal && !isUnsaveOpen) return null; + if (!currentModal && !isUnsaveOpen && !isLoginOpen) return null; return ( <> @@ -57,6 +60,13 @@ export default function ModalContainer() { )} + {isLoginOpen && ( +
+
e.stopPropagation()}> + +
+
+ )} ); } diff --git a/src/components/ui/Modal/ViewModal/index.tsx b/src/components/ui/Modal/ViewModal/index.tsx index 7b854db..5a598cc 100644 --- a/src/components/ui/Modal/ViewModal/index.tsx +++ b/src/components/ui/Modal/ViewModal/index.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; import { useRouter } from 'next/navigation'; import { useModalStore } from '@/stores/useModalStore'; import { useTemplateStore } from '@/stores/useTemplateStore'; +import { useAuth } from '@/hooks/useAuth'; import { getTemplateDetail, TemplateDetail } from '@/services/template/getTemplateDetail'; import { Spacing } from '../../Spacing'; import { Button } from '../../Button'; @@ -23,6 +24,8 @@ export default function ViewModal() { const { selectedTemplateId, openModal, closeModal } = useModalStore(); const { setCurrentTemplate } = useTemplateStore(); + const { requireAuth } = useAuth(); + const [template, setTemplate] = useState(null); const [folderId, setFolderId] = useState(); const [loading, setLoading] = useState(true); @@ -54,13 +57,15 @@ export default function ViewModal() { if (loading || !template) return
불러오는 중...
; const handleCilckUse = () => { - closeModal(); - openModal('using', { - templateId: template.templateId, - draftTitle: template.title, - draftContent: template.content, - draftTags: template.tags, - }); + if (requireAuth()) { + closeModal(); + openModal('using', { + templateId: template.templateId, + draftTitle: template.title, + draftContent: template.content, + draftTags: template.tags, + }); + } }; const handleClickEdit = (template: TemplateDetail) => { @@ -80,7 +85,9 @@ export default function ViewModal() { }; const handleDropdown = () => { - setDropdown((prev) => !prev); + if (requireAuth()) { + setDropdown((prev) => !prev); + } }; const handleCopyClipBoard = (text: string) => { @@ -98,10 +105,12 @@ export default function ViewModal() { }; const handleAuthor = () => { - closeModal(); - openModal('profile', { - templateId: template.templateId, - }); + if (requireAuth()) { + closeModal(); + openModal('profile', { + templateId: template.templateId, + }); + } }; return ( diff --git a/src/hooks/template/useTemplateLikes.ts b/src/hooks/template/useTemplateLikes.ts index c3c956b..ef07891 100644 --- a/src/hooks/template/useTemplateLikes.ts +++ b/src/hooks/template/useTemplateLikes.ts @@ -1,9 +1,10 @@ import { getTemplateLikes } from '@/services/template/getTemplateLikes'; import { useQuery } from '@tanstack/react-query'; -export const useTemplatesLikes = () => { +export const useTemplatesLikes = (options?: { enabled?: boolean }) => { return useQuery({ queryKey: ['templates', 'likes'], queryFn: () => getTemplateLikes(), + enabled: options?.enabled ?? true, }); }; diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx new file mode 100644 index 0000000..3db792e --- /dev/null +++ b/src/hooks/useAuth.tsx @@ -0,0 +1,19 @@ +import { checkAuth } from '@/utils/checkAuth'; +import { useLoginModalStore } from '@/stores/useLoginModal'; + +export const useAuth = () => { + const { openLoginModal } = useLoginModalStore(); + + const requireAuth = (): boolean => { + const isAuthenticated = checkAuth(); + + if (!isAuthenticated) { + openLoginModal(); + return false; + } + + return true; + }; + + return { requireAuth }; +}; diff --git a/src/stores/useLoginModal.ts b/src/stores/useLoginModal.ts new file mode 100644 index 0000000..0fd3118 --- /dev/null +++ b/src/stores/useLoginModal.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand'; + +interface LoginModalState { + isLoginOpen: boolean; + openLoginModal: () => void; + closeLoginModal: () => void; +} + +export const useLoginModalStore = create((set) => ({ + isLoginOpen: false, + openLoginModal: () => set({ isLoginOpen: true }), + closeLoginModal: () => set({ isLoginOpen: false }), +})); diff --git a/src/utils/checkAuth.ts b/src/utils/checkAuth.ts new file mode 100644 index 0000000..bad22bf --- /dev/null +++ b/src/utils/checkAuth.ts @@ -0,0 +1,8 @@ +export const checkAuth = (): boolean => { + if (typeof window === 'undefined') return false; + + const token = localStorage.getItem('token'); + const user = localStorage.getItem('user'); + + return !!(token && user); +};