import { AnimatePresence, LayoutGroup, MotionConfig, motion } from 'framer-motion' import { Fragment, useEffect, useMemo, useState } from 'react' import { BrowserRouter, Navigate, Route, Routes, useLocation } from 'react-router-dom' import DevModeWatermark from '@/components/DevModeWatermark' import RouteBlockerOverlay from '@/components/RouteBlockerOverlay' import TopNav from '@/components/TopNav' import DialogueProvider from '@/components/dialogues/DialogueProvider' import { Toaster } from '@/components/ui/toaster' import { apiPost, isApiError } from '@/lib/api' import { applyClientAnimationMode, applyClientAppearance, type ClientAnimationMode, fetchUserThemeSlots, fetchUserSettings, getClientThemeMode, hasStoredClientThemeSelection, normaliseUserThemeSlots, setCachedUserThemeSlots, seedClientThemeMode } from '@/lib/settings' import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings' import { UnsavedChangesGuardProvider } from '@/lib/useUnsavedChangesGuard' import { KeyboardShortcutsProvider } from '@/lib/useKeyboardShortcuts' import DeerjikistDetailPage from '@/pages/deerjikists/DeerjikistDetailPage' import MaterialBasePage from '@/pages/materials/MaterialBasePage' import MaterialDetailPage from '@/pages/materials/MaterialDetailPage' import MaterialHistoryPage from '@/pages/materials/MaterialHistoryPage' import MaterialListPage from '@/pages/materials/MaterialListPage' import MaterialNewPage from '@/pages/materials/MaterialNewPage' import MaterialSyncSuppressionsPage from '@/pages/materials/MaterialSyncSuppressionsPage' // import MaterialSearchPage from '@/pages/materials/MaterialSearchPage' import MorePage from '@/pages/MorePage' import GekanatorPage from '@/pages/GekanatorPage' import NicoTagListPage from '@/pages/tags/NicoTagListPage' import NotFound from '@/pages/NotFound' import TOSPage from '@/pages/TOSPage.mdx' import PostDetailPage from '@/pages/posts/PostDetailPage' import PostHistoryPage from '@/pages/posts/PostHistoryPage' import PostListPage from '@/pages/posts/PostListPage' import PostNewPage from '@/pages/posts/PostNewPage' import PostImportPage from '@/pages/posts/PostImportPage' import PostSearchPage from '@/pages/posts/PostSearchPage' import ServiceUnavailable from '@/pages/ServiceUnavailable' import SettingPage from '@/pages/users/SettingPage' import TagDetailPage from '@/pages/tags/TagDetailPage' import TagHistoryPage from '@/pages/tags/TagHistoryPage' import TagListPage from '@/pages/tags/TagListPage' import TheatreDetailPage from '@/pages/theatres/TheatreDetailPage' import WikiDetailPage from '@/pages/wiki/WikiDetailPage' import WikiDiffPage from '@/pages/wiki/WikiDiffPage' import WikiEditPage from '@/pages/wiki/WikiEditPage' import WikiHistoryPage from '@/pages/wiki/WikiHistoryPage' import WikiNewPage from '@/pages/wiki/WikiNewPage' import WikiSearchPage from '@/pages/wiki/WikiSearchPage' import type { Dispatch, FC, SetStateAction } from 'react' import type { User } from '@/types' const RouteTransitionWrapper = ({ animationMode, user, setUser }: { animationMode: ClientAnimationMode user: User | null setUser: Dispatch> }) => { const location = useLocation () if (animationMode === 'off') { return ( }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> ) } return ( }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> }/> ) } const PostDetailRoute = ({ user }: { user: User | null }) => { const location = useLocation () const key = location.pathname return } const App: FC = () => { const [user, setUser] = useState (null) const [status, setStatus] = useState (200) const behaviourSettings = useClientBehaviourSettings () const animationMode = behaviourSettings.animation ?? 'normal' const LayoutWrapper = animationMode === 'off' ? Fragment : LayoutGroup const appLayoutTransition = useMemo ( () => ( animationMode === 'off' ? { duration: 0 } : animationMode === 'reduced' ? { duration: .08, ease: 'linear' as const } : { duration: .2, ease: 'easeOut' as const } ), [animationMode], ) useEffect (() => { applyClientAppearance () const mediaQuery = window.matchMedia ('(prefers-color-scheme: dark)') const handleThemeChange = () => { if (getClientThemeMode () === 'system') applyClientAppearance () } mediaQuery.addEventListener ('change', handleThemeChange) return () => mediaQuery.removeEventListener ('change', handleThemeChange) }, []) useEffect (() => { applyClientAnimationMode (animationMode) }, [animationMode]) useEffect (() => { const createUser = async () => { const data = await apiPost<{ code: string; user: User }> ('/users') if (data.code) { localStorage.setItem ('user_code', data.code) setUser (data.user) } } const code = localStorage.getItem ('user_code') if (code) { void (async () => { try { const data = await apiPost<{ valid: boolean; user: User }> ('/users/verify', { code }) if (data.valid) setUser (data.user) else await createUser () } catch (err) { if (isApiError (err)) setStatus (err.response?.status ?? 200) } }) () } else createUser () }, []) useEffect (() => { if (!(user)) return void (async () => { try { const [settings, themeSlots] = await Promise.all ([ fetchUserSettings (), fetchUserThemeSlots (), ]) const storedThemeSlots = normaliseUserThemeSlots (themeSlots) setCachedUserThemeSlots (storedThemeSlots) if (!(hasStoredClientThemeSelection ())) seedClientThemeMode (settings.theme) applyClientAppearance () } catch { return } }) () }, [user]) switch (status) { case 503: return } return ( <> {import.meta.env.DEV && } ) } export default App