diff --git a/src/app/(root)/layout.tsx b/src/app/(root)/layout.tsx index 36a3238..e32fcef 100644 --- a/src/app/(root)/layout.tsx +++ b/src/app/(root)/layout.tsx @@ -1,28 +1,16 @@ 'use client'; -import { useState } from 'react'; import Image from 'next/image'; import { usePathname, useRouter } from 'next/navigation'; import { useCurrentUser } from '@/hooks/useCurrentUser'; -import { - Sidebar, - SidebarButton, - SidebarTeamSelect, - SidebarAddButton, - MobileHeader, - MobileDrawer, -} from '@/components/sidebar'; -import boardSmall from '@/assets/icons/board/boardSmall.svg'; -import boardLarge from '@/assets/icons/board/boardLarge.svg'; -import chessSmall from '@/assets/icons/chess/chessSmall.svg'; -import chessBig from '@/assets/icons/chess/chessBig.svg'; +import { Sidebar, MobileHeader } from '@/components/sidebar'; +import TeamSidebarDropdown from './[teamid]/_domain/components/Team/TeamSidebarDropdown'; import humanBig from '@/assets/buttons/human/humanBig.svg'; import styles from './layout.module.css'; export default function RootLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const router = useRouter(); - const [isDrawerOpen, setIsDrawerOpen] = useState(false); const { data: user, isPending } = useCurrentUser(); // isPending: 최초 로딩 중 (undefined) @@ -30,11 +18,12 @@ export default function RootLayout({ children }: { children: React.ReactNode }) // user !== null: 로그인 const isLoggedIn = !isPending && user !== null && user !== undefined; const isLanding = pathname === '/'; - const firstGroup = user?.memberships?.[0]?.group; - // [teamid] 페이지는 자체 모바일 헤더(TeamNavClient)를 사용하므로 root layout의 MobileHeader를 숨김 - const knownPaths = ['/', '/addteam', '/boards', '/mypage', '/history', '/list']; - const isTeamIdPage = !knownPaths.some((p) => pathname === p || pathname.startsWith(p + '/')); + // 자체 사이드바가 없는 페이지에서만 root layout 사이드바 표시 + const rootSidebarPaths = ['/', '/boards', '/mypage']; + const showRootSidebar = rootSidebarPaths.some( + (p) => pathname === p || pathname.startsWith(p + '/'), + ); const handleProfileClick = () => { if (isLoggedIn) { @@ -51,126 +40,53 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return (
- router.push('/addteam')} - profileImage={ - user?.image ? ( - - ) : ( - - ) - } - profileName={user?.nickname ?? '사용자'} - profileTeam={firstGroup?.name ?? ''} - teamSelect={(isCollapsed: boolean) => - firstGroup ? ( - !isCollapsed ? ( - - ) : ( - - ) - } - label={firstGroup.name} - isSelected - onClick={() => router.push(`/${firstGroup.id}`)} + {showRootSidebar && ( + router.push('/addteam')} + profileImage={ + user?.image ? ( + ) : ( - - ) : ( - - ) - } - label={firstGroup.name} - isActive - iconOnly - onClick={() => router.push(`/${firstGroup.id}`)} - /> + ) - ) : null - } - addButton={ - isLoggedIn - ? (isCollapsed: boolean) => ( - <> - {!isCollapsed && ( - router.push('/addteam')} /> - )} -
- - } - label="자유게시판" - isActive - iconOnly={isCollapsed} - href="/boards" - /> - - ) - : undefined - } - /> - {!isTeamIdPage && ( - <> - - ) : undefined - } - onMenuClick={() => setIsDrawerOpen(true)} - onProfileClick={handleProfileClick} - /> - setIsDrawerOpen(false)}> - } - label="자유게시판" - isActive - href="/boards" - onClick={() => setIsDrawerOpen(false)} - /> - - + } + profileName={user?.nickname ?? '사용자'} + profileTeam={user?.memberships?.[0]?.group?.name ?? ''} + teamSelect={ + isLoggedIn + ? (isCollapsed: boolean) => + : undefined + } + /> + )} + {showRootSidebar && ( + + ) : undefined + } + onProfileClick={handleProfileClick} + onLogout={handleLogout} + drawerContent={} + /> )}
{children}
diff --git a/src/app/(root)/mypage/page.tsx b/src/app/(root)/mypage/page.tsx index badc6c1..6f497c4 100644 --- a/src/app/(root)/mypage/page.tsx +++ b/src/app/(root)/mypage/page.tsx @@ -33,6 +33,7 @@ export default function ProfilePage() { } = useUser(); const [showToast, setShowToast] = useState(false); + const [successToast, setSuccessToast] = useState(null); const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false); const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false); const newPasswordRef = useRef(null); @@ -47,6 +48,7 @@ export default function ProfilePage() { const result = await updateProfile(); if (result.success) { setShowToast(false); + setSuccessToast('이름이 변경되었습니다.'); } }; @@ -63,6 +65,7 @@ export default function ProfilePage() { setIsPasswordModalOpen(false); if (newPasswordRef.current) newPasswordRef.current.value = ''; if (confirmPasswordRef.current) confirmPasswordRef.current.value = ''; + setSuccessToast('비밀번호가 변경되었습니다.'); } }; @@ -171,8 +174,8 @@ export default function ProfilePage() { - {hasChanges && ( -
+
+ {hasChanges && ( setShowToast(false)} className={styles.toast} /> -
- )} + )} + {successToast && ( + setSuccessToast(null)} + className={styles.toast} + /> + )} +
void; + /** 드로어 내부 콘텐츠 (전달 시 햄버거 메뉴 클릭으로 드로어 표시) */ + drawerContent?: ReactNode; /** 프로필 버튼 클릭 시 호출되는 콜백 */ onProfileClick?: () => void; /** 로그아웃 클릭 시 호출되는 콜백 */ @@ -37,16 +41,27 @@ type MobileHeaderProps = { export default function MobileHeader({ isLoggedIn, profileImage, - onMenuClick, + drawerContent, onProfileClick, onLogout, onLogoClick, logoWidth = 102, logoHeight = 20, }: MobileHeaderProps) { + const router = useRouter(); const [showProfileMenu, setShowProfileMenu] = useState(false); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); const profileMenuRef = useRef(null); + const defaultLogout = useCallback(async () => { + await fetch('/api/auth/logout', { method: 'POST' }); + router.push('/login'); + }, [router]); + + const handleLogout = onLogout ?? defaultLogout; + const handleProfileClick = onProfileClick ?? (() => router.push('/mypage')); + const handleLogoClick = onLogoClick ?? (() => router.push('/addteam')); + useEffect(() => { if (!showProfileMenu) return; const handleClickOutside = (e: MouseEvent) => { @@ -61,12 +76,7 @@ export default function MobileHeader({ if (!isLoggedIn) { return (
-
+
COWORKERS
@@ -79,17 +89,12 @@ export default function MobileHeader({ -
+
COWORKERS
@@ -109,7 +114,7 @@ export default function MobileHeader({ className={styles.profileMenuItem} onClick={() => { setShowProfileMenu(false); - onProfileClick?.(); + handleProfileClick(); }} > 마이페이지 @@ -119,7 +124,7 @@ export default function MobileHeader({ className={`${styles.profileMenuItem} ${styles.profileMenuDanger}`} onClick={() => { setShowProfileMenu(false); - onLogout?.(); + handleLogout(); }} > 로그아웃 @@ -127,6 +132,11 @@ export default function MobileHeader({ )} + {drawerContent && ( + setIsDrawerOpen(false)}> + {drawerContent} + + )} ); } diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx index dc814ed..8967b00 100644 --- a/src/components/sidebar/Sidebar.tsx +++ b/src/components/sidebar/Sidebar.tsx @@ -1,7 +1,8 @@ 'use client'; import type { ReactNode } from 'react'; -import { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; import Image from 'next/image'; import clsx from 'clsx'; import { motion, AnimatePresence } from 'framer-motion'; @@ -49,10 +50,20 @@ export default function Sidebar({ onLogout, onLogoClick, }: SidebarProps) { + const router = useRouter(); const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed ?? false); const [showProfileMenu, setShowProfileMenu] = useState(false); const profileMenuRef = useRef(null); + const defaultLogout = useCallback(async () => { + await fetch('/api/auth/logout', { method: 'POST' }); + router.push('/login'); + }, [router]); + + const handleLogout = onLogout ?? defaultLogout; + const handleProfileClick = onProfileClick ?? (() => router.push('/mypage')); + const handleLogoClick = onLogoClick ?? (() => router.push('/addteam')); + useEffect(() => { if (!showProfileMenu) return; const handleClickOutside = (e: MouseEvent) => { @@ -72,7 +83,7 @@ export default function Sidebar({ const renderFooter = () => { if (footer) { return ( -
+
{renderSlot(footer)}
); @@ -80,7 +91,7 @@ export default function Sidebar({ if (!isLoggedIn) { return ( - + {!isCollapsed && ( { setShowProfileMenu(false); - onProfileClick?.(); + handleProfileClick(); }} > 마이페이지 @@ -120,7 +131,7 @@ export default function Sidebar({ className={`${styles.profileMenuItem} ${styles.profileMenuDanger}`} onClick={() => { setShowProfileMenu(false); - onLogout?.(); + handleLogout(); }} > 로그아웃 @@ -155,12 +166,7 @@ export default function Sidebar({ transition={{ duration: 0.3, ease: 'easeInOut' }} >
-
+
{isCollapsed ? (