import React, { useState, useEffect, useRef } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { HomeIcon, AcademicCapIcon, BookOpenIcon, HandThumbUpIcon, WrenchScrewdriverIcon, UserIcon, ArrowRightOnRectangleIcon } from '@heroicons/react/24/outline'; import HitokotoBar from './HitokotoBar'; import { api } from '../services/api'; interface User { name: string; email: string; role: string; } const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => { const location = useLocation(); const [isTransitioning, setIsTransitioning] = useState(false); const previousPathRef = useRef(location.pathname); const userData = localStorage.getItem('user'); const user: User | null = userData ? JSON.parse(userData) : null; const [unreadCount, setUnreadCount] = useState(0); // Handle page transitions useEffect(() => { // Only trigger transition if path actually changed if (location.pathname !== previousPathRef.current) { setIsTransitioning(true); previousPathRef.current = location.pathname; // Reset week selection to Week 1 when navigating between pages const previousPath = previousPathRef.current; if (location.pathname === '/tutorial-tasks' && previousPath && !previousPath.includes('/tutorial-tasks')) { // Use URL parameter to force Week 1 window.history.replaceState(null, '', '/tutorial-tasks?week=1'); localStorage.setItem('selectedTutorialWeek', '1'); } else if (location.pathname === '/weekly-practice' && previousPath && !previousPath.includes('/weekly-practice')) { // Use URL parameter to force Week 1 window.history.replaceState(null, '', '/weekly-practice?week=1'); localStorage.setItem('selectedWeeklyPracticeWeek', '1'); } // Determine transition duration based on destination page let transitionDuration = 800; // Default duration // Longer duration for heavy pages if (location.pathname === '/tutorial-tasks' || location.pathname === '/weekly-practice') { transitionDuration = 1200; // Longer for content-heavy pages } // Special case: Weekly Practice → Tutorial Tasks (add 500ms delay) if (location.pathname === '/tutorial-tasks' && previousPath && previousPath.includes('/weekly-practice')) { // Check if navigating to Week 2 (use localStorage since URL might not be updated yet) const tutorialWeek = localStorage.getItem('selectedTutorialWeek'); if (tutorialWeek === '2') { transitionDuration = 2500; // Extended duration for Week 2 (2000 + 500) } else { transitionDuration = 1700; // Standard duration for Week 1 (1200 + 500) } } // End transition after content is loaded (wait for DOM updates) const timer = setTimeout(() => { setIsTransitioning(false); }, transitionDuration); return () => clearTimeout(timer); } }, [location.pathname]); // Admin unread message badge (non-invasive) useEffect(() => { let timer: any; const run = async () => { try { if (user?.role !== 'admin') return; const token = localStorage.getItem('token') || ''; const base = (((api.defaults as any)?.baseURL as string) || '').replace(/\/$/, ''); const resp = await fetch(`${base}/api/messages/unread-count`, { headers: { 'Authorization': `Bearer ${token}`, 'user-role': 'admin', 'user-info': userData || '' } }); if (resp.ok) { const data = await resp.json(); if (typeof data?.count === 'number') setUnreadCount(data.count); } } catch {} }; run(); if (user?.role === 'admin') { timer = setInterval(run, 60000); } return () => { if (timer) clearInterval(timer); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [user?.role]); const handleLogout = () => { localStorage.removeItem('token'); localStorage.removeItem('user'); window.location.href = '/'; }; let navigation = [ { name: 'Home', href: '/dashboard', icon: HomeIcon }, { name: 'Tutorial Tasks', href: '/tutorial-tasks', icon: AcademicCapIcon }, { name: 'Weekly Practice', href: '/weekly-practice', icon: BookOpenIcon }, { name: 'Votes', href: '/votes', icon: HandThumbUpIcon }, { name: 'Toolkit', href: '/toolkit', icon: WrenchScrewdriverIcon }, { name: 'Slides', href: '/slides', icon: BookOpenIcon }, { name: 'Feedback', href: '/feedback', icon: UserIcon }, ]; // Hide Slides for visitors if (!user || user.role === 'visitor') { navigation = navigation.filter(item => item.name !== 'Slides'); } // Add Manage link for admin users if (user?.role === 'admin') { navigation.push({ name: 'Manage', href: '/manage', icon: UserIcon }); } return (
{/* Navigation */} {/* Mobile Navigation */}
{navigation.map((item) => { const isActive = location.pathname === item.href; return (
{item.name}
); })}
{/* Main Content */}
{!isTransitioning && children}
{/* Transition Loading Indicator */} {isTransitioning && (
Loading...
)}
); }; export default Layout;