diff --git a/src/components/common/TitleEditorPopover.tsx b/src/components/common/TitleEditorPopover.tsx index 3e8e11c5..745eca41 100644 --- a/src/components/common/TitleEditorPopover.tsx +++ b/src/components/common/TitleEditorPopover.tsx @@ -23,6 +23,7 @@ interface TitleEditorPopoverProps { ariaLabel: string; isPending?: boolean; titleClassName?: string; + showOnMobile?: boolean; } export function TitleEditorPopover({ @@ -33,6 +34,7 @@ export function TitleEditorPopover({ ariaLabel, isPending = false, titleClassName = 'max-w-60 truncate', + showOnMobile = false, }: TitleEditorPopoverProps) { const [editTitle, setEditTitle] = useState(title); @@ -47,7 +49,10 @@ export function TitleEditorPopover({ ); diff --git a/src/components/common/layout/LoginButton.tsx b/src/components/common/layout/LoginButton.tsx index 52fd208f..60c46472 100644 --- a/src/components/common/layout/LoginButton.tsx +++ b/src/components/common/layout/LoginButton.tsx @@ -6,18 +6,17 @@ * 로그인 상태: 사용자 이름 + 프로필 이미지 (클릭 시 로그아웃/회원탈퇴 드롭다운) */ import { useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import { apiClient } from '@/api/client'; import LoginIcon from '@/assets/icons/icon-login.svg?react'; import LogoutIcon from '@/assets/icons/icon-logout.svg?react'; -import { Popover } from '@/components/common/Popover'; +import { Dropdown } from '@/components/common/Dropdown'; import { UserAvatar } from '@/components/common/UserAvatar'; import { useAuthStore } from '@/stores/authStore'; import { useHomeStore } from '@/stores/homeStore'; -import { useThemeStore } from '@/stores/themeStore'; import { isAnonymousEmail } from '@/utils/auth'; import { showToast } from '@/utils/toast'; import { getUserDisplayName } from '@/utils/user'; @@ -28,14 +27,11 @@ import { WithdrawConfirmModal } from './WithdrawConfirmModal'; export function LoginButton() { const queryClient = useQueryClient(); const navigate = useNavigate(); - const { pathname } = useLocation(); const accessToken = useAuthStore((s) => s.accessToken); const user = useAuthStore((s) => s.user); const openLoginModal = useAuthStore((s) => s.openLoginModal); const logout = useAuthStore((s) => s.logout); const resetHome = useHomeStore((s) => s.reset); - const resolvedTheme = useThemeStore((s) => s.resolvedTheme); - const setTheme = useThemeStore((s) => s.setTheme); const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false); const [isWithdrawing, setIsWithdrawing] = useState(false); @@ -43,8 +39,6 @@ export function LoginButton() { const isGuest = !accessToken; const isAnon = accessToken && isAnonymousEmail(user?.email); const isSocial = accessToken && user?.email && !isAnonymousEmail(user.email); - const isSlideRoute = /\/slide\/?$/.test(pathname); - const isDark = resolvedTheme === 'dark'; const handleLogout = () => { logout(); @@ -57,12 +51,7 @@ export function LoginButton() { // 로그인 전 (게스트) if (isGuest) { return ( - } - onClick={openLoginModal} - iconOnlyOnMobile={isSlideRoute} - /> + } onClick={openLoginModal} iconOnlyOnMobile /> ); } @@ -74,12 +63,7 @@ export function LoginButton() { // 소셜이 아닌데 여기까지 왔다면(비정상 상태) 방어 if (!isSocial) { return ( - } - onClick={openLoginModal} - iconOnlyOnMobile={isSlideRoute} - /> + } onClick={openLoginModal} iconOnlyOnMobile /> ); } @@ -101,101 +85,40 @@ export function LoginButton() { return ( <> - - - {displayName} - + {displayName} } - > - {({ close }) => ( -
-
-

내 계정

-
- -
-

{displayName}

-

{user.email}

-
-
-
- -
- -
- -
- -
- -
- -
-
- )} -
+ items={[ + { + id: 'logout', + label: ( + + 로그아웃 + + + ), + onClick: handleLogout, + variant: 'danger', + }, + { + id: 'withdraw', + label: '회원 탈퇴', + onClick: () => setIsWithdrawModalOpen(true), + variant: 'danger', + }, + ]} + /> s.openShareModal); - const { pathname } = useLocation(); - const isSlideRoute = /\/slide\/?$/.test(pathname); return ( - } - onClick={openShareModal} - iconOnlyOnMobile={isSlideRoute} - /> + } onClick={openShareModal} iconOnlyOnMobile /> ); } diff --git a/src/components/slide/script/SlideTitle.tsx b/src/components/slide/script/SlideTitle.tsx index 831ff506..6bd69405 100644 --- a/src/components/slide/script/SlideTitle.tsx +++ b/src/components/slide/script/SlideTitle.tsx @@ -76,6 +76,7 @@ function SlideTitleEditable({ isCollapsed={isCollapsed} ariaLabel="슬라이드 이름 변경" titleClassName="max-w-40 truncate" + showOnMobile /> ); } diff --git a/src/pages/SlidePage.tsx b/src/pages/SlidePage.tsx index 1d4bac58..79cd810d 100644 --- a/src/pages/SlidePage.tsx +++ b/src/pages/SlidePage.tsx @@ -14,6 +14,11 @@ export default function SlidePage() { const slideIdParam = searchParams.get('slideId'); const currentSlide = slides?.find((s) => s.slideId === slideIdParam) ?? slides?.[0]; + const currentIndex = currentSlide + ? (slides?.findIndex((s) => s.slideId === currentSlide.slideId) ?? -1) + : -1; + const hasPrev = currentIndex > 0; + const hasNext = !!slides && currentIndex >= 0 && currentIndex < slides.length - 1; /** * 슬라이드 로드 에러 처리 @@ -49,15 +54,71 @@ export default function SlidePage() { } }, [projectId, currentSlide?.slideId]); + const goPrev = () => { + if (!slides || !hasPrev) return; + setSearchParams({ slideId: slides[currentIndex - 1].slideId }, { replace: true }); + }; + + const goNext = () => { + if (!slides || !hasNext) return; + setSearchParams({ slideId: slides[currentIndex + 1].slideId }, { replace: true }); + }; + return (
-
+
+ +
+
+ + + {slides && currentIndex >= 0 ? `${currentIndex + 1} / ${slides.length}` : '- / -'} + + +
+ +
+ +
+
); }