import { useState, useEffect, useRef } from 'react' import { Routes, Route, NavLink, Navigate, useLocation } from 'react-router-dom' import { getMe, getNewMediaCount, checkAuth, appAuthLogout } from './api' import { useAuth } from './AuthContext' import AppLogin from './pages/AppLogin' import AppSetup from './pages/AppSetup' import Login from './pages/Login' import Dashboard from './pages/Dashboard' import Feed from './pages/Feed' import Users from './pages/Users' import UserPosts from './pages/UserPosts' import Downloads from './pages/Downloads' import Search from './pages/Search' import Gallery from './pages/Gallery' import Duplicates from './pages/Duplicates' import Scrape from './pages/Scrape' import Videos from './pages/Videos' import VideoDetail from './pages/VideoDetail' import VideoUpload from './pages/VideoUpload' import UserManagement from './pages/UserManagement' // Route key mapping for nav items (null = always visible, undefined = no check needed) const allNavItems = [ { to: '/', label: 'Home', icon: HomeIcon, exact: true, routeKey: 'dashboard' }, { to: '/feed', label: 'Feed', icon: FeedIcon, routeKey: 'feed' }, { to: '/users', label: 'Users', icon: UsersIcon, routeKey: 'users' }, { to: '/search', label: 'Search', icon: SearchIcon, routeKey: 'users' }, { to: '/downloads', label: 'Downloads', icon: DownloadIcon, routeKey: 'downloads' }, { to: '/gallery', label: 'Gallery', icon: GalleryNavIcon, routeKey: 'gallery' }, { to: '/videos', label: 'Videos', icon: VideoNavIcon, routeKey: 'videos' }, { to: '/scrape', label: 'Scrape', icon: ScrapeIcon, routeKey: 'scrape' }, { to: '/settings', label: 'Settings', icon: SettingsIcon, routeKey: 'settings' }, ] const allMobileNavItems = [ { to: '/', label: 'Home', icon: HomeIcon, exact: true, routeKey: 'dashboard' }, { to: '/users', label: 'Users', icon: UsersIcon, routeKey: 'users' }, { to: '/gallery', label: 'Gallery', icon: GalleryNavIcon, routeKey: 'gallery' }, { to: '/search', label: 'Search', icon: SearchIcon, routeKey: 'users' }, { to: '/more', label: 'More', icon: MoreIcon }, ] const allMoreNavItems = [ { to: '/feed', label: 'Feed', icon: FeedIcon, routeKey: 'feed' }, { to: '/downloads', label: 'Downloads', icon: DownloadIcon, routeKey: 'downloads' }, { to: '/videos', label: 'Videos', icon: VideoNavIcon, routeKey: 'videos' }, { to: '/scrape', label: 'Scrape', icon: ScrapeIcon, routeKey: 'scrape' }, { to: '/settings', label: 'Settings', icon: SettingsIcon, routeKey: 'settings' }, ] function ProtectedRoute({ routeKey, children }) { const { hasRoute, appUser } = useAuth() // 'admin' is a special key — only admin role can access if (routeKey === 'admin') { if (appUser?.role !== 'admin') return return children } if (!hasRoute(routeKey)) { return } return children } export default function App() { const { appUser, setupRequired, loading: authLoading, hasRoute } = useAuth() // Show auth screens if needed if (authLoading) { return (
) } if (setupRequired) return if (!appUser) return // Filter nav items based on route permissions const navItems = allNavItems.filter((item) => !item.routeKey || hasRoute(item.routeKey) ) const mobileNavItems = allMobileNavItems.filter((item) => !item.routeKey || hasRoute(item.routeKey) ) const moreNavItems = allMoreNavItems.filter((item) => !item.routeKey || hasRoute(item.routeKey) ) // Add admin nav item for admins if (appUser.role === 'admin') { navItems.push({ to: '/admin/users', label: 'Admin', icon: AdminIcon }) moreNavItems.push({ to: '/admin/users', label: 'Admin', icon: AdminIcon }) } return } function AppShell({ navItems, mobileNavItems, moreNavItems, hasRoute, appUser }) { const { logout } = useAuth() const [currentUser, setCurrentUser] = useState(null) const [moreOpen, setMoreOpen] = useState(false) const [authWarning, setAuthWarning] = useState(null) const authPollRef = useRef(null) const location = useLocation() useEffect(() => { getMe().then((data) => { if (!data.error) { setCurrentUser(data) } }) }, []) // Auth validity polling (every 5 min) useEffect(() => { const poll = () => { checkAuth().then((data) => { if (data && !data.error && !data.valid) { setAuthWarning(data.error || 'Auth expired') } else if (data?.valid) { setAuthWarning(null) } }) } poll() authPollRef.current = setInterval(poll, 5 * 60 * 1000) return () => clearInterval(authPollRef.current) }, []) // Close "more" menu on route change useEffect(() => { setMoreOpen(false) }, [location.pathname]) // New media badge const [newMediaCount, setNewMediaCount] = useState(0) useEffect(() => { const fetchCount = () => { getNewMediaCount().then((data) => { if (!data.error) setNewMediaCount(data.count || 0) }) } fetchCount() const id = setInterval(fetchCount, 60000) return () => clearInterval(id) }, []) // Instantly clear badge when on gallery page useEffect(() => { if (location.pathname.startsWith('/gallery')) { setNewMediaCount(0) } }, [location.pathname]) const refreshUser = () => { getMe().then((data) => { if (!data.error) { setCurrentUser(data) } }) } const handleLogout = async () => { await appAuthLogout() logout() } const isMoreActive = moreNavItems.some((item) => item.to !== '/more' && location.pathname.startsWith(item.to) ) return (
{/* Desktop Sidebar */} {/* Main Content */}
{/* Auth Warning Banner */} {authWarning && (
Auth expired —{' '} Update credentials in Settings
)}
} /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } />
{/* Mobile Bottom Nav */}
) } /* Icon Components */ function AdminIcon({ className }) { return ( ) } function LogoutIcon({ className }) { return ( ) } function FeedIcon({ className }) { return ( ) } function UsersIcon({ className }) { return ( ) } function DownloadIcon({ className }) { return ( ) } function SearchIcon({ className }) { return ( ) } function GalleryNavIcon({ className }) { return ( ) } function ScrapeIcon({ className }) { return ( ) } function SettingsIcon({ className }) { return ( ) } function MoreIcon({ className }) { return ( ) } function HomeIcon({ className }) { return ( ) } function VideoNavIcon({ className }) { return ( ) }