Header.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. 'use client';
  2. import Styles from '../styles/layout.module.scss';
  3. import { useCallback } from 'react';
  4. import Link from 'next/link';
  5. import { usePathname } from 'next/navigation';
  6. import useAuth from '@/hooks/useAuth';
  7. import useDragScroll from '@/hooks/useDragScroll';
  8. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
  9. import { faBars, faXmark } from '@fortawesome/free-solid-svg-icons';
  10. import NotificationBell from '@/app/component/NotificationBell';
  11. import Profile from '@/app/component/Profile';
  12. import PointChargeIcon from '@/public/icons/layout/point.svg';
  13. const navItems = [
  14. { href: '/', label: '생방송' },
  15. { href: '/feed', label: '피드' },
  16. { href: '/ranking', label: '순위' },
  17. { href: '/market', label: '상점' },
  18. { href: '/attendance', label: '출석부' },
  19. { href: '/board/notice', label: '고객지원' },
  20. ];
  21. type Props = {
  22. sidebarOpen: boolean;
  23. onToggle: () => void;
  24. };
  25. export default function Header({ sidebarOpen, onToggle }: Props)
  26. {
  27. const { isAuthenticated } = useAuth();
  28. const pathname = usePathname();
  29. const dragScroll = useDragScroll<HTMLDivElement>();
  30. const handlePopupCharge = useCallback(() => {
  31. const w = 450, h = 635;
  32. const left = window.screenX + (window.outerWidth - w) / 2;
  33. const top = window.screenY + (window.outerHeight - h) / 2;
  34. window.open('/charge', 'charge', `width=${w},height=${h},left=${left},top=${top},scrollbars=no,resizable=no`);
  35. }, []);
  36. return (
  37. <header id='header' className={Styles.header}>
  38. {/* 1줄: 로고 + 우측 아이콘 (PC에서는 전체 내비) */}
  39. <div className={Styles.headerRow1}>
  40. <button type='button' className={Styles.hamburger} onClick={onToggle} aria-label='메뉴'>
  41. <FontAwesomeIcon icon={sidebarOpen ? faXmark : faBars} />
  42. </button>
  43. <Link href='/' className={Styles.logo}>
  44. <picture>
  45. <source src="image.webp" type="image/webp" />
  46. {/* <img src="/resources/logo.jpg" alt="DPOT" /> */}
  47. 로고 자리
  48. </picture>
  49. </Link>
  50. {/* PC 내비게이션 */}
  51. <nav className={Styles.pcNav}>
  52. <ul className='flex gap-4'>
  53. {navItems.map(item => (
  54. <li key={item.label}>
  55. <Link href={item.href}>{item.label}</Link>
  56. </li>
  57. ))}
  58. </ul>
  59. </nav>
  60. {/* 우측 아이콘 (항상 표시) */}
  61. <div className={Styles.headerActions}>
  62. {isAuthenticated && (
  63. <>
  64. <button type="button" className={Styles.chargeBtn} onClick={handlePopupCharge} title="포인트 충전">
  65. <PointChargeIcon width={24} height={24} />
  66. </button>
  67. <NotificationBell />
  68. </>
  69. )}
  70. <Profile />
  71. </div>
  72. </div>
  73. {/* 2줄: 모바일 전용 가로 스크롤 탭 */}
  74. <div className={Styles.headerRow2}>
  75. <div
  76. className={Styles.mobileTabScroll}
  77. ref={dragScroll.ref}
  78. onMouseDown={dragScroll.onMouseDown}
  79. onMouseMove={dragScroll.onMouseMove}
  80. onMouseUp={dragScroll.onMouseUp}
  81. onMouseLeave={dragScroll.onMouseLeave}
  82. >
  83. {navItems.map(item => (
  84. <Link
  85. key={item.label}
  86. href={item.href}
  87. className={`${Styles.mobileTab}${pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href)) ? ` ${Styles.mobileTabActive}` : ''}`}
  88. >
  89. {item.label}
  90. </Link>
  91. ))}
  92. </div>
  93. </div>
  94. </header>
  95. );
  96. }