Files
thrilltrack-explorer/src/App.tsx
gpt-engineer-app[bot] b22546e7f2 Add audit trail and filters
Implements audit trail view for item approvals, adds approval date range filtering to moderation queue, and wires up UI and backend components (Approval History page, ItemApprovalHistory component, materialized view-based history, and query/filters integration) to support compliant reporting and time-based moderation filtering.
2025-11-12 14:06:34 +00:00

489 lines
20 KiB
TypeScript

import { lazy, Suspense, useEffect, useRef } from "react";
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
import { AuthProvider } from "@/hooks/useAuth";
import { AuthModalProvider } from "@/contexts/AuthModalContext";
import { MFAStepUpProvider } from "@/contexts/MFAStepUpContext";
import { APIConnectivityProvider, useAPIConnectivity } from "@/contexts/APIConnectivityContext";
import { LocationAutoDetectProvider } from "@/components/providers/LocationAutoDetectProvider";
import { AnalyticsWrapper } from "@/components/analytics/AnalyticsWrapper";
import { Footer } from "@/components/layout/Footer";
import { PageLoader } from "@/components/loading/PageSkeletons";
import { RouteErrorBoundary } from "@/components/error/RouteErrorBoundary";
import { AdminErrorBoundary } from "@/components/error/AdminErrorBoundary";
import { EntityErrorBoundary } from "@/components/error/EntityErrorBoundary";
import { breadcrumb } from "@/lib/errorBreadcrumbs";
import { handleError } from "@/lib/errorHandler";
import { RetryStatusIndicator } from "@/components/ui/retry-status-indicator";
import { APIStatusBanner } from "@/components/ui/api-status-banner";
import { ResilienceProvider } from "@/components/layout/ResilienceProvider";
import { useAdminRoutePreload } from "@/hooks/useAdminRoutePreload";
import { useVersionCheck } from "@/hooks/useVersionCheck";
import { cn } from "@/lib/utils";
import { PageTransition } from "@/components/layout/PageTransition";
// Core routes (eager-loaded for best UX)
import Index from "./pages/Index";
import Parks from "./pages/Parks";
import Rides from "./pages/Rides";
import Search from "./pages/Search";
import Auth from "./pages/Auth";
// Temporary test component for error logging verification
import { TestErrorLogging } from "./test-error-logging";
// Detail routes (lazy-loaded)
const ParkDetail = lazy(() => import("./pages/ParkDetail"));
const RideDetail = lazy(() => import("./pages/RideDetail"));
const ParkRides = lazy(() => import("./pages/ParkRides"));
const Manufacturers = lazy(() => import("./pages/Manufacturers"));
const ManufacturerDetail = lazy(() => import("./pages/ManufacturerDetail"));
const ManufacturerRides = lazy(() => import("./pages/ManufacturerRides"));
const ManufacturerModels = lazy(() => import("./pages/ManufacturerModels"));
const RideModelDetail = lazy(() => import("./pages/RideModelDetail"));
const RideModelRides = lazy(() => import("./pages/RideModelRides"));
const Designers = lazy(() => import("./pages/Designers"));
const DesignerDetail = lazy(() => import("./pages/DesignerDetail"));
const DesignerRides = lazy(() => import("./pages/DesignerRides"));
const ParkOwners = lazy(() => import("./pages/ParkOwners"));
const PropertyOwnerDetail = lazy(() => import("./pages/PropertyOwnerDetail"));
const OwnerParks = lazy(() => import("./pages/OwnerParks"));
const Operators = lazy(() => import("./pages/Operators"));
const OperatorDetail = lazy(() => import("./pages/OperatorDetail"));
const OperatorParks = lazy(() => import("./pages/OperatorParks"));
const BlogIndex = lazy(() => import("./pages/BlogIndex"));
const BlogPost = lazy(() => import("./pages/BlogPost"));
const Terms = lazy(() => import("./pages/Terms"));
const Privacy = lazy(() => import("./pages/Privacy"));
const SubmissionGuidelines = lazy(() => import("./pages/SubmissionGuidelines"));
const Contact = lazy(() => import("./pages/Contact"));
// Admin routes (lazy-loaded - heavy bundle)
const AdminDashboard = lazy(() => import("./pages/AdminDashboard"));
const AdminModeration = lazy(() => import("./pages/AdminModeration"));
const AdminReports = lazy(() => import("./pages/AdminReports"));
const AdminSystemLog = lazy(() => import("./pages/AdminSystemLog"));
const AdminUsers = lazy(() => import("./pages/AdminUsers"));
const AdminBlog = lazy(() => import("./pages/AdminBlog"));
const AdminSettings = lazy(() => import("./pages/AdminSettings"));
const AdminDatabaseStats = lazy(() => import("./pages/AdminDatabaseStats"));
const DatabaseMaintenance = lazy(() => import("./pages/admin/DatabaseMaintenance"));
const AdminContact = lazy(() => import("./pages/admin/AdminContact"));
const AdminEmailSettings = lazy(() => import("./pages/admin/AdminEmailSettings"));
const ErrorMonitoring = lazy(() => import("./pages/admin/ErrorMonitoring"));
const ErrorLookup = lazy(() => import("./pages/admin/ErrorLookup"));
const TraceViewer = lazy(() => import("./pages/admin/TraceViewer"));
const RateLimitMetrics = lazy(() => import("./pages/admin/RateLimitMetrics"));
const MonitoringOverview = lazy(() => import("./pages/admin/MonitoringOverview"));
const ApprovalHistory = lazy(() => import("./pages/admin/ApprovalHistory"));
// User routes (lazy-loaded)
const Profile = lazy(() => import("./pages/Profile"));
const UserSettings = lazy(() => import("./pages/UserSettings"));
const AuthCallback = lazy(() => import("./pages/AuthCallback"));
// Utility routes (lazy-loaded)
const NotFound = lazy(() => import("./pages/NotFound"));
const ForceLogout = lazy(() => import("./pages/ForceLogout"));
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false, // Disable automatic refetch on tab focus
refetchOnMount: true, // Keep refetch on component mount
refetchOnReconnect: true, // Keep refetch on network reconnect
retry: 1, // Keep retry attempts
staleTime: 30000, // 30 seconds - queries stay fresh for 30s
gcTime: 5 * 60 * 1000, // 5 minutes - keep in cache for 5 mins
},
mutations: {
onError: (error: unknown, variables: unknown, context: unknown) => {
// Track mutation errors with breadcrumbs
const contextObj = context as { endpoint?: string } | undefined;
const errorObj = error as { status?: number } | undefined;
breadcrumb.apiCall(
contextObj?.endpoint || 'mutation',
'MUTATION',
errorObj?.status || 500
);
// Handle error with tracking
handleError(error, {
action: 'Mutation failed',
metadata: {
variables,
context,
},
});
},
},
},
});
// Navigation tracking component - must be inside Router context
function NavigationTracker() {
const location = useLocation();
const prevLocation = useRef<string>('');
useEffect(() => {
const from = prevLocation.current || undefined;
breadcrumb.navigation(location.pathname, from);
prevLocation.current = location.pathname;
// Clear chunk load reload flag on successful navigation
sessionStorage.removeItem('chunk-load-reload');
}, [location.pathname]);
return null;
}
function AppContent(): React.JSX.Element {
// Check if API status banner is visible to add padding
const { isAPIReachable, isBannerDismissed } = useAPIConnectivity();
const showBanner = !isAPIReachable && !isBannerDismissed;
// Preload admin routes for moderators/admins
useAdminRoutePreload();
// Monitor for new deployments
useVersionCheck();
return (
<TooltipProvider>
<ResilienceProvider>
<APIStatusBanner />
<div className={cn(showBanner && "pt-20")}>
<NavigationTracker />
<LocationAutoDetectProvider />
<RetryStatusIndicator />
<Toaster />
<Sonner />
<div className="min-h-screen flex flex-col">
<div className="flex-1">
<Suspense fallback={<PageLoader />}>
<PageTransition>
<RouteErrorBoundary>
<Routes>
{/* Core routes - eager loaded */}
<Route path="/" element={<Index />} />
<Route path="/parks" element={<Parks />} />
<Route path="/rides" element={<Rides />} />
<Route path="/search" element={<Search />} />
<Route path="/auth" element={<Auth />} />
{/* Detail routes with entity error boundaries */}
<Route
path="/parks/:slug"
element={
<EntityErrorBoundary entityType="park">
<ParkDetail />
</EntityErrorBoundary>
}
/>
<Route
path="/parks/:parkSlug/rides"
element={
<EntityErrorBoundary entityType="park">
<ParkRides />
</EntityErrorBoundary>
}
/>
<Route
path="/parks/:parkSlug/rides/:rideSlug"
element={
<EntityErrorBoundary entityType="ride">
<RideDetail />
</EntityErrorBoundary>
}
/>
<Route path="/manufacturers" element={<Manufacturers />} />
<Route
path="/manufacturers/:slug"
element={
<EntityErrorBoundary entityType="manufacturer">
<ManufacturerDetail />
</EntityErrorBoundary>
}
/>
<Route
path="/manufacturers/:manufacturerSlug/rides"
element={
<EntityErrorBoundary entityType="manufacturer">
<ManufacturerRides />
</EntityErrorBoundary>
}
/>
<Route
path="/manufacturers/:manufacturerSlug/models"
element={
<EntityErrorBoundary entityType="manufacturer">
<ManufacturerModels />
</EntityErrorBoundary>
}
/>
<Route
path="/manufacturers/:manufacturerSlug/models/:modelSlug"
element={
<EntityErrorBoundary entityType="manufacturer">
<RideModelDetail />
</EntityErrorBoundary>
}
/>
<Route
path="/manufacturers/:manufacturerSlug/models/:modelSlug/rides"
element={
<EntityErrorBoundary entityType="manufacturer">
<RideModelRides />
</EntityErrorBoundary>
}
/>
<Route path="/designers" element={<Designers />} />
<Route
path="/designers/:slug"
element={
<EntityErrorBoundary entityType="designer">
<DesignerDetail />
</EntityErrorBoundary>
}
/>
<Route
path="/designers/:designerSlug/rides"
element={
<EntityErrorBoundary entityType="designer">
<DesignerRides />
</EntityErrorBoundary>
}
/>
<Route path="/owners" element={<ParkOwners />} />
<Route
path="/owners/:slug"
element={
<EntityErrorBoundary entityType="owner">
<PropertyOwnerDetail />
</EntityErrorBoundary>
}
/>
<Route
path="/owners/:ownerSlug/parks"
element={
<EntityErrorBoundary entityType="owner">
<OwnerParks />
</EntityErrorBoundary>
}
/>
<Route path="/operators" element={<Operators />} />
<Route
path="/operators/:slug"
element={
<EntityErrorBoundary entityType="operator">
<OperatorDetail />
</EntityErrorBoundary>
}
/>
<Route
path="/operators/:operatorSlug/parks"
element={
<EntityErrorBoundary entityType="operator">
<OperatorParks />
</EntityErrorBoundary>
}
/>
<Route path="/blog" element={<BlogIndex />} />
<Route path="/blog/:slug" element={<BlogPost />} />
<Route path="/terms" element={<Terms />} />
<Route path="/privacy" element={<Privacy />} />
<Route path="/submission-guidelines" element={<SubmissionGuidelines />} />
<Route path="/contact" element={<Contact />} />
{/* User routes - lazy loaded */}
<Route path="/auth/callback" element={<AuthCallback />} />
<Route path="/profile" element={<Profile />} />
<Route path="/profile/:username" element={<Profile />} />
<Route path="/settings" element={<UserSettings />} />
{/* Admin routes with admin error boundaries */}
<Route
path="/admin"
element={
<AdminErrorBoundary section="Dashboard">
<AdminDashboard />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/moderation"
element={
<AdminErrorBoundary section="Moderation Queue">
<AdminModeration />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/reports"
element={
<AdminErrorBoundary section="Reports">
<AdminReports />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/system-log"
element={
<AdminErrorBoundary section="System Log">
<AdminSystemLog />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/users"
element={
<AdminErrorBoundary section="User Management">
<AdminUsers />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/blog"
element={
<AdminErrorBoundary section="Blog Management">
<AdminBlog />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/settings"
element={
<AdminErrorBoundary section="Settings">
<AdminSettings />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/contact"
element={
<AdminErrorBoundary section="Contact Management">
<AdminContact />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/email-settings"
element={
<AdminErrorBoundary section="Email Settings">
<AdminEmailSettings />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/error-monitoring"
element={
<AdminErrorBoundary section="Error Monitoring">
<ErrorMonitoring />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/approval-history"
element={
<AdminErrorBoundary section="Approval History">
<ApprovalHistory />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/error-lookup"
element={
<AdminErrorBoundary section="Error Lookup">
<ErrorLookup />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/trace-viewer"
element={
<AdminErrorBoundary section="Trace Viewer">
<TraceViewer />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/rate-limit-metrics"
element={
<AdminErrorBoundary section="Rate Limit Metrics">
<RateLimitMetrics />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/monitoring-overview"
element={
<AdminErrorBoundary section="Monitoring Overview">
<MonitoringOverview />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/database-stats"
element={
<AdminErrorBoundary section="Database Statistics">
<AdminDatabaseStats />
</AdminErrorBoundary>
}
/>
<Route
path="/admin/database-maintenance"
element={
<AdminErrorBoundary section="Database Maintenance">
<DatabaseMaintenance />
</AdminErrorBoundary>
}
/>
{/* Utility routes - lazy loaded */}
<Route path="/force-logout" element={<ForceLogout />} />
{/* Temporary test route - DELETE AFTER TESTING */}
<Route path="/test-error-logging" element={<TestErrorLogging />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>
</RouteErrorBoundary>
</PageTransition>
</Suspense>
</div>
<Footer />
</div>
</div>
</ResilienceProvider>
</TooltipProvider>
);
}
const App = (): React.JSX.Element => {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<AuthModalProvider>
<MFAStepUpProvider>
<APIConnectivityProvider>
<BrowserRouter>
<AppContent />
</BrowserRouter>
</APIConnectivityProvider>
</MFAStepUpProvider>
</AuthModalProvider>
</AuthProvider>
{import.meta.env.DEV && <ReactQueryDevtools initialIsOpen={false} position="bottom" />}
<AnalyticsWrapper />
</QueryClientProvider>
);
};
export default App;