Some checks failed
CI / skip-ci-check (pull_request) Successful in 1m4s
CI / lint-and-type-check (pull_request) Has been cancelled
CI / python-lint (pull_request) Has been cancelled
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled
Add on-demand H.264/AAC web playback (RQ, ffmpeg) with API routes and Next.js proxies; extend admin UI with WebPlaybackVideo and shared hooks. Store transcode cache beside pending-photos (WEB_VIDEO_CACHE_DIR / UPLOAD_DIR) and ignore data/web_videos. Centralize FastAPI URL helpers, optional Vite and Next base paths for subfolder deploy, and fix modal reopen by using router.replace when closing the home photo viewer. Include migration, install scripts, deployment doc updates, and CI admin build env tweak. Made-with: Cursor
188 lines
5.3 KiB
TypeScript
188 lines
5.3 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
|
import { AuthProvider, useAuth } from './context/AuthContext'
|
|
import { DeveloperModeProvider } from './context/DeveloperModeContext'
|
|
import Login from './pages/Login'
|
|
import Dashboard from './pages/Dashboard'
|
|
import Search from './pages/Search'
|
|
import Scan from './pages/Scan'
|
|
import Process from './pages/Process'
|
|
import Identify from './pages/Identify'
|
|
import AutoMatch from './pages/AutoMatch'
|
|
import Modify from './pages/Modify'
|
|
import Tags from './pages/Tags'
|
|
import FacesMaintenance from './pages/FacesMaintenance'
|
|
import ApproveIdentified from './pages/ApproveIdentified'
|
|
import ManageUsers from './pages/ManageUsers'
|
|
import ReportedPhotos from './pages/ReportedPhotos'
|
|
import PendingPhotos from './pages/PendingPhotos'
|
|
import UserTaggedPhotos from './pages/UserTaggedPhotos'
|
|
import ManagePhotos from './pages/ManagePhotos'
|
|
import Settings from './pages/Settings'
|
|
import Help from './pages/Help'
|
|
import VideoPlayer from './pages/VideoPlayer'
|
|
import Layout from './components/Layout'
|
|
import PasswordChangeModal from './components/PasswordChangeModal'
|
|
import AdminRoute from './components/AdminRoute'
|
|
import { logClick, flushPendingClicks } from './services/clickLogger'
|
|
|
|
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
|
const { isAuthenticated, isLoading, passwordChangeRequired } = useAuth()
|
|
const [showPasswordModal, setShowPasswordModal] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (isAuthenticated && passwordChangeRequired) {
|
|
setShowPasswordModal(true)
|
|
}
|
|
}, [isAuthenticated, passwordChangeRequired])
|
|
|
|
if (isLoading) {
|
|
return <div className="min-h-screen flex items-center justify-center">Loading...</div>
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
return <Navigate to="/login" replace />
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{showPasswordModal && (
|
|
<PasswordChangeModal
|
|
onSuccess={() => {
|
|
setShowPasswordModal(false)
|
|
}}
|
|
/>
|
|
)}
|
|
{children}
|
|
</>
|
|
)
|
|
}
|
|
|
|
function AppRoutes() {
|
|
const { isAuthenticated } = useAuth()
|
|
|
|
// Set up global click logging for authenticated users
|
|
useEffect(() => {
|
|
if (!isAuthenticated) {
|
|
return
|
|
}
|
|
|
|
const handleClick = (event: MouseEvent) => {
|
|
const target = event.target as HTMLElement
|
|
if (target) {
|
|
logClick(target)
|
|
}
|
|
}
|
|
|
|
// Add click listener
|
|
document.addEventListener('click', handleClick, true) // Use capture phase
|
|
|
|
// Flush pending clicks on page unload
|
|
const handleBeforeUnload = () => {
|
|
flushPendingClicks()
|
|
}
|
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
|
|
return () => {
|
|
document.removeEventListener('click', handleClick, true)
|
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
// Flush any pending clicks on cleanup
|
|
flushPendingClicks()
|
|
}
|
|
}, [isAuthenticated])
|
|
|
|
return (
|
|
<Routes>
|
|
<Route path="/login" element={<Login />} />
|
|
<Route
|
|
path="/video/:id"
|
|
element={
|
|
<PrivateRoute>
|
|
<VideoPlayer />
|
|
</PrivateRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/"
|
|
element={
|
|
<PrivateRoute>
|
|
<Layout />
|
|
</PrivateRoute>
|
|
}
|
|
>
|
|
<Route index element={<Dashboard />} />
|
|
<Route path="scan" element={<Scan />} />
|
|
<Route path="process" element={<Process />} />
|
|
<Route path="search" element={<Search />} />
|
|
<Route path="identify" element={<Identify />} />
|
|
<Route path="auto-match" element={<AutoMatch />} />
|
|
<Route path="modify" element={<Modify />} />
|
|
<Route path="tags" element={<Tags />} />
|
|
<Route path="manage-photos" element={<ManagePhotos />} />
|
|
<Route path="faces-maintenance" element={<FacesMaintenance />} />
|
|
<Route
|
|
path="approve-identified"
|
|
element={
|
|
<AdminRoute featureKey="user_identified">
|
|
<ApproveIdentified />
|
|
</AdminRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="manage-users"
|
|
element={
|
|
<AdminRoute featureKey="manage_users">
|
|
<ManageUsers />
|
|
</AdminRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="reported-photos"
|
|
element={
|
|
<AdminRoute featureKey="user_reported">
|
|
<ReportedPhotos />
|
|
</AdminRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="pending-linkages"
|
|
element={
|
|
<AdminRoute featureKey="user_tagged">
|
|
<UserTaggedPhotos />
|
|
</AdminRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="pending-photos"
|
|
element={
|
|
<AdminRoute featureKey="user_uploaded">
|
|
<PendingPhotos />
|
|
</AdminRoute>
|
|
}
|
|
/>
|
|
<Route path="settings" element={<Settings />} />
|
|
<Route path="help" element={<Help />} />
|
|
</Route>
|
|
</Routes>
|
|
)
|
|
}
|
|
|
|
function App() {
|
|
return (
|
|
<AuthProvider>
|
|
<DeveloperModeProvider>
|
|
<BrowserRouter
|
|
basename={
|
|
import.meta.env.BASE_URL.replace(/\/$/, '') || undefined
|
|
}
|
|
>
|
|
<AppRoutes />
|
|
</BrowserRouter>
|
|
</DeveloperModeProvider>
|
|
</AuthProvider>
|
|
)
|
|
}
|
|
|
|
export default App
|
|
|