|
|
@@ -1,27 +1,49 @@
|
|
|
'use client';
|
|
|
|
|
|
-import { useState, useCallback } from 'react';
|
|
|
+import { useState, useCallback, useEffect } from 'react';
|
|
|
import Link from 'next/link';
|
|
|
+import { usePathname } from 'next/navigation';
|
|
|
import Styles from '../styles/common.module.scss';
|
|
|
import useAuth from '@/hooks/useAuth';
|
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
|
-import { faBars, faXmark } from '@fortawesome/free-solid-svg-icons';
|
|
|
+import { faBars, faXmark, faCoins, faComments, faNewspaper, faHeadset, faUser, faRightToBracket } from '@fortawesome/free-solid-svg-icons';
|
|
|
+import { faCommentDots, faClock } from '@fortawesome/free-regular-svg-icons';
|
|
|
+import ChatSidebar from '@/app/component/chat/ChatSidebar';
|
|
|
|
|
|
type Props = {
|
|
|
children: React.ReactNode;
|
|
|
- sidebarContent?: React.ReactNode;
|
|
|
};
|
|
|
|
|
|
-export default function Layout({ children, sidebarContent }: Props) {
|
|
|
+export default function Layout({ children }: Props) {
|
|
|
const { isAuthenticated, isLoading, logout } = useAuth();
|
|
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
|
+ const [chatOpen, setChatOpen] = useState(false);
|
|
|
+ const pathname = usePathname();
|
|
|
|
|
|
const toggleSidebar = useCallback(() => {
|
|
|
setSidebarOpen((prev) => !prev);
|
|
|
}, []);
|
|
|
|
|
|
- const closeSidebar = useCallback(() => {
|
|
|
+ const toggleChat = useCallback(() => {
|
|
|
+ setChatOpen((prev) => !prev);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const closeOverlay = useCallback(() => {
|
|
|
setSidebarOpen(false);
|
|
|
+ setChatOpen(false);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const [currentTime, setCurrentTime] = useState('');
|
|
|
+ useEffect(() => {
|
|
|
+ const updateTime = () => {
|
|
|
+ const now = new Date();
|
|
|
+ setCurrentTime(
|
|
|
+ `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
|
|
|
+ );
|
|
|
+ };
|
|
|
+ updateTime();
|
|
|
+ const timer = setInterval(updateTime, 1000);
|
|
|
+ return () => clearInterval(timer);
|
|
|
}, []);
|
|
|
|
|
|
if (isLoading) {
|
|
|
@@ -30,33 +52,33 @@ export default function Layout({ children, sidebarContent }: Props) {
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
- <div id='container' className={`${Styles.container}${sidebarOpen ? ` ${Styles.sidebarOpen}` : ''}`}>
|
|
|
+ <div id='container' className={`${Styles.container}${sidebarOpen ? ` ${Styles.sidebarOpen}` : ''}${chatOpen ? ` ${Styles.chatOpen}` : ''}`}>
|
|
|
|
|
|
{/* 상단 내용 */}
|
|
|
- <header id='header' className={`${Styles.header} flex items-center justify-between w-full px-4`}>
|
|
|
- <div className='flex items-center gap-2'>
|
|
|
- <button type='button' className={Styles.hamburger} onClick={toggleSidebar} aria-label='메뉴'>
|
|
|
- <FontAwesomeIcon icon={sidebarOpen ? faXmark : faBars} />
|
|
|
- </button>
|
|
|
- <Link href='/' className='font-bold text-xl'>
|
|
|
- <picture>
|
|
|
- <source src="image.webp" type="image/webp" />
|
|
|
- <img src="/resources/logo.svg" alt="bitforum logo" />
|
|
|
- </picture>
|
|
|
- </Link>
|
|
|
- </div>
|
|
|
- <nav className='flex grow items-center justify-between'>
|
|
|
+ <header id='header' className={`${Styles.header} flex items-center w-full px-4`}>
|
|
|
+ <button type='button' className={Styles.hamburger} onClick={toggleSidebar} aria-label='메뉴'>
|
|
|
+ <FontAwesomeIcon icon={sidebarOpen ? faXmark : faBars} />
|
|
|
+ </button>
|
|
|
+ <Link href='/' className={Styles.logo}>
|
|
|
+ <picture>
|
|
|
+ <source src="image.webp" type="image/webp" />
|
|
|
+ <img src="/resources/logo.svg" alt="bitforum logo" />
|
|
|
+ </picture>
|
|
|
+ </Link>
|
|
|
+ <nav className={Styles.pcNav}>
|
|
|
<ul className='flex gap-4'>
|
|
|
<li>
|
|
|
<Link href='/'>
|
|
|
코인
|
|
|
</Link>
|
|
|
</li>
|
|
|
+ {/*
|
|
|
<li>
|
|
|
<Link href='/latest'>
|
|
|
토론
|
|
|
</Link>
|
|
|
</li>
|
|
|
+ */}
|
|
|
<li>
|
|
|
<Link href='/news'>
|
|
|
뉴스
|
|
|
@@ -69,7 +91,10 @@ export default function Layout({ children, sidebarContent }: Props) {
|
|
|
</li>
|
|
|
</ul>
|
|
|
{!isAuthenticated ? (
|
|
|
- <ul className='flex gap-4'>
|
|
|
+ <ul className='flex gap-4 items-center'>
|
|
|
+ <li className={`${Styles.clock} text-sm opacity-70`} style={{ fontVariantNumeric: 'tabular-nums' }}>
|
|
|
+ <FontAwesomeIcon icon={faClock} className='mr-1' />{currentTime}
|
|
|
+ </li>
|
|
|
<li>
|
|
|
<a href='/login'>
|
|
|
로그인
|
|
|
@@ -82,7 +107,10 @@ export default function Layout({ children, sidebarContent }: Props) {
|
|
|
</li>
|
|
|
</ul>
|
|
|
) : (
|
|
|
- <ul className='flex gap-4'>
|
|
|
+ <ul className='flex gap-4 items-center'>
|
|
|
+ <li className={`${Styles.clock} text-sm opacity-70`} style={{ fontVariantNumeric: 'tabular-nums' }}>
|
|
|
+ <FontAwesomeIcon icon={faClock} className='mr-1' />{currentTime}
|
|
|
+ </li>
|
|
|
<li>
|
|
|
<Link href='/profile'>
|
|
|
내 정보
|
|
|
@@ -96,15 +124,13 @@ export default function Layout({ children, sidebarContent }: Props) {
|
|
|
</ul>
|
|
|
)}
|
|
|
</nav>
|
|
|
+ <button type='button' className={Styles.chatToggle} onClick={toggleChat} aria-label='채팅'>
|
|
|
+ <FontAwesomeIcon icon={faCommentDots} />
|
|
|
+ </button>
|
|
|
</header>
|
|
|
|
|
|
- {/* 좌측 사이드바 */}
|
|
|
- <aside id='aside' className={Styles.aside}>
|
|
|
- {sidebarContent}
|
|
|
- </aside>
|
|
|
-
|
|
|
{/* 모바일 오버레이 */}
|
|
|
- <div className={Styles.overlay} onClick={closeSidebar} />
|
|
|
+ <div className={Styles.overlay} onClick={closeOverlay} />
|
|
|
|
|
|
{/* 메인 내용 */}
|
|
|
<main id='main' className={`${Styles.main} relative`}>
|
|
|
@@ -112,21 +138,56 @@ export default function Layout({ children, sidebarContent }: Props) {
|
|
|
</main>
|
|
|
|
|
|
{/* 우측 채팅 사이드바 */}
|
|
|
- <aside id='chatAside' className={Styles.chatAside}></aside>
|
|
|
+ <aside id='chatAside' className={Styles.chatAside}>
|
|
|
+ <ChatSidebar />
|
|
|
+ </aside>
|
|
|
|
|
|
{/* 하단 내용 */}
|
|
|
<footer id='footer' className={`${Styles.footer} px-4`}>
|
|
|
<ol>
|
|
|
- {/* 최신글 표시 ▼ */}
|
|
|
- <li>111111</li>
|
|
|
+ <li>
|
|
|
+ <Link href='/docs/teams'>이용약관</Link>
|
|
|
+ </li>
|
|
|
|
|
|
- {/* 공지사항 표시 ▲ */}
|
|
|
- <li>222222</li>
|
|
|
+ <li>
|
|
|
+ <Link href='/docs/privacy'>개인정보처리방침</Link>
|
|
|
+ </li>
|
|
|
|
|
|
{/* 저작권 표시 */}
|
|
|
<li>© 2025 PLAYR. All rights reserved.</li>
|
|
|
</ol>
|
|
|
</footer>
|
|
|
+
|
|
|
+ {/* 모바일 하단 탭바 */}
|
|
|
+ <nav className={Styles.bottomTab}>
|
|
|
+ <Link href='/' className={pathname === '/' ? Styles.active : ''}>
|
|
|
+ <FontAwesomeIcon icon={faCoins} />
|
|
|
+ <span>코인</span>
|
|
|
+ </Link>
|
|
|
+ <Link href='/latest' className={pathname.startsWith('/latest') || (pathname.startsWith('/board') && !pathname.startsWith('/board/notice')) || pathname.startsWith('/post') ? Styles.active : ''}>
|
|
|
+ <FontAwesomeIcon icon={faComments} />
|
|
|
+ <span>토론</span>
|
|
|
+ </Link>
|
|
|
+ <Link href='/news' className={pathname.startsWith('/news') ? Styles.active : ''}>
|
|
|
+ <FontAwesomeIcon icon={faNewspaper} />
|
|
|
+ <span>뉴스</span>
|
|
|
+ </Link>
|
|
|
+ <Link href='/board/notice' className={pathname.startsWith('/board/notice') || pathname.startsWith('/support') ? Styles.active : ''}>
|
|
|
+ <FontAwesomeIcon icon={faHeadset} />
|
|
|
+ <span>고객지원</span>
|
|
|
+ </Link>
|
|
|
+ {isAuthenticated ? (
|
|
|
+ <Link href='/profile' className={pathname.startsWith('/profile') ? Styles.active : ''}>
|
|
|
+ <FontAwesomeIcon icon={faUser} />
|
|
|
+ <span>내 정보</span>
|
|
|
+ </Link>
|
|
|
+ ) : (
|
|
|
+ <a href='/login'>
|
|
|
+ <FontAwesomeIcon icon={faRightToBracket} />
|
|
|
+ <span>로그인</span>
|
|
|
+ </a>
|
|
|
+ )}
|
|
|
+ </nav>
|
|
|
</div>
|
|
|
</>
|
|
|
);
|