Compare commits

...

2 Commits

Author SHA1 Message Date
gpt-engineer-app[bot]
524f6a65e8 Wrap forms with error boundaries 2025-11-05 21:33:14 +00:00
gpt-engineer-app[bot]
fa3dfcfdee Fix: Improve chunk load error handling 2025-11-05 21:23:09 +00:00
17 changed files with 355 additions and 138 deletions

View File

@@ -20,6 +20,8 @@ import { breadcrumb } from "@/lib/errorBreadcrumbs";
import { handleError } from "@/lib/errorHandler"; import { handleError } from "@/lib/errorHandler";
import { RetryStatusIndicator } from "@/components/ui/retry-status-indicator"; import { RetryStatusIndicator } from "@/components/ui/retry-status-indicator";
import { APIStatusBanner } from "@/components/ui/api-status-banner"; import { APIStatusBanner } from "@/components/ui/api-status-banner";
import { useAdminRoutePreload } from "@/hooks/useAdminRoutePreload";
import { useVersionCheck } from "@/hooks/useVersionCheck";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
// Core routes (eager-loaded for best UX) // Core routes (eager-loaded for best UX)
@@ -136,6 +138,12 @@ function AppContent(): React.JSX.Element {
// Check if API status banner is visible to add padding // Check if API status banner is visible to add padding
const { isAPIReachable, isBannerDismissed } = useAPIConnectivity(); const { isAPIReachable, isBannerDismissed } = useAPIConnectivity();
const showBanner = !isAPIReachable && !isBannerDismissed; const showBanner = !isAPIReachable && !isBannerDismissed;
// Preload admin routes for moderators/admins
useAdminRoutePreload();
// Monitor for new deployments
useVersionCheck();
return ( return (
<TooltipProvider> <TooltipProvider>

View File

@@ -71,6 +71,32 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
window.location.reload(); window.location.reload();
}; };
handleClearCacheAndReload = async () => {
try {
// Clear all caches
if ('caches' in window) {
const cacheNames = await caches.keys();
await Promise.all(cacheNames.map(name => caches.delete(name)));
}
// Unregister service workers
if ('serviceWorker' in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations();
await Promise.all(registrations.map(reg => reg.unregister()));
}
// Clear session storage chunk reload flag
sessionStorage.removeItem('chunk-load-reload');
// Force reload bypassing cache
window.location.reload();
} catch (error) {
// Fallback to regular reload if cache clearing fails
console.error('Failed to clear cache:', error);
window.location.reload();
}
};
handleGoHome = () => { handleGoHome = () => {
window.location.href = '/'; window.location.href = '/';
}; };
@@ -90,12 +116,23 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
<AlertTriangle className="w-8 h-8 text-destructive" /> <AlertTriangle className="w-8 h-8 text-destructive" />
</div> </div>
<CardTitle className="text-2xl"> <CardTitle className="text-2xl">
{isChunkError ? 'New Version Available' : 'Something Went Wrong'} {isChunkError ? 'App Update Required' : 'Something Went Wrong'}
</CardTitle> </CardTitle>
<CardDescription className="mt-2"> <CardDescription className="mt-2 space-y-2">
{isChunkError {isChunkError ? (
? "The app has been updated. Please reload the page to get the latest version." <>
: "We encountered an unexpected error. This has been logged and we'll look into it."} <p>The app has been updated with new features and improvements.</p>
<p className="text-sm font-medium">
To continue, please clear your browser cache and reload:
</p>
<ul className="text-sm list-disc list-inside space-y-1 ml-2">
<li>Click "Clear Cache & Reload" below, or</li>
<li>Press <kbd className="px-1.5 py-0.5 text-xs font-semibold bg-muted rounded">Ctrl+Shift+R</kbd> (Windows/Linux) or <kbd className="px-1.5 py-0.5 text-xs font-semibold bg-muted rounded">+Shift+R</kbd> (Mac)</li>
</ul>
</>
) : (
"We encountered an unexpected error. This has been logged and we'll look into it."
)}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
@@ -114,23 +151,35 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
</div> </div>
)} )}
<div className="flex flex-col sm:flex-row gap-2"> <div className="flex flex-col gap-2">
<Button {isChunkError && (
variant="default" <Button
onClick={this.handleReload} variant="default"
className="flex-1 gap-2" onClick={this.handleClearCacheAndReload}
> className="w-full gap-2"
<RefreshCw className="w-4 h-4" /> >
Reload Page <RefreshCw className="w-4 h-4" />
</Button> Clear Cache & Reload
<Button </Button>
variant="outline" )}
onClick={this.handleGoHome} <div className="flex flex-col sm:flex-row gap-2">
className="flex-1 gap-2" <Button
> variant={isChunkError ? "outline" : "default"}
<Home className="w-4 h-4" /> onClick={this.handleReload}
Go Home className="flex-1 gap-2"
</Button> >
<RefreshCw className="w-4 h-4" />
Reload Page
</Button>
<Button
variant="outline"
onClick={this.handleGoHome}
className="flex-1 gap-2"
>
<Home className="w-4 h-4" />
Go Home
</Button>
</div>
</div> </div>
<p className="text-xs text-center text-muted-foreground"> <p className="text-xs text-center text-muted-foreground">

View File

@@ -22,6 +22,7 @@ import { jsonToFormData } from '@/lib/typeConversions';
import { PropertyOwnerForm } from '@/components/admin/PropertyOwnerForm'; import { PropertyOwnerForm } from '@/components/admin/PropertyOwnerForm';
import { RideModelForm } from '@/components/admin/RideModelForm'; import { RideModelForm } from '@/components/admin/RideModelForm';
import { Save, X, Edit } from 'lucide-react'; import { Save, X, Edit } from 'lucide-react';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
interface ItemEditDialogProps { interface ItemEditDialogProps {
item?: SubmissionItemWithDeps | null; item?: SubmissionItemWithDeps | null;
@@ -131,66 +132,70 @@ export function ItemEditDialog({ item, items, open, onOpenChange, onComplete }:
switch (editItem.item_type) { switch (editItem.item_type) {
case 'park': case 'park':
return ( return (
<ParkForm <SubmissionErrorBoundary submissionId={editItem.id}>
onSubmit={handleSubmit} <ParkForm
onCancel={() => onOpenChange(false)} onSubmit={handleSubmit}
// Convert Json to form-compatible object (null → undefined) onCancel={() => onOpenChange(false)}
// eslint-disable-next-line @typescript-eslint/no-explicit-any initialData={jsonToFormData(editItem.item_data) as any}
initialData={jsonToFormData(editItem.item_data) as any} isEditing
isEditing />
/> </SubmissionErrorBoundary>
); );
case 'ride': case 'ride':
return ( return (
<RideForm <SubmissionErrorBoundary submissionId={editItem.id}>
onSubmit={handleSubmit} <RideForm
onCancel={() => onOpenChange(false)} onSubmit={handleSubmit}
// Convert Json to form-compatible object (null → undefined) onCancel={() => onOpenChange(false)}
// eslint-disable-next-line @typescript-eslint/no-explicit-any initialData={jsonToFormData(editItem.item_data) as any}
initialData={jsonToFormData(editItem.item_data) as any} isEditing
isEditing />
/> </SubmissionErrorBoundary>
); );
case 'manufacturer': case 'manufacturer':
return ( return (
<ManufacturerForm <SubmissionErrorBoundary submissionId={editItem.id}>
onSubmit={handleSubmit} <ManufacturerForm
onCancel={() => onOpenChange(false)} onSubmit={handleSubmit}
// eslint-disable-next-line @typescript-eslint/no-explicit-any onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any} initialData={jsonToFormData(editItem.item_data) as any}
/> />
</SubmissionErrorBoundary>
); );
case 'designer': case 'designer':
return ( return (
<DesignerForm <SubmissionErrorBoundary submissionId={editItem.id}>
onSubmit={handleSubmit} <DesignerForm
onCancel={() => onOpenChange(false)} onSubmit={handleSubmit}
// eslint-disable-next-line @typescript-eslint/no-explicit-any onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any} initialData={jsonToFormData(editItem.item_data) as any}
/> />
</SubmissionErrorBoundary>
); );
case 'operator': case 'operator':
return ( return (
<OperatorForm <SubmissionErrorBoundary submissionId={editItem.id}>
onSubmit={handleSubmit} <OperatorForm
onCancel={() => onOpenChange(false)} onSubmit={handleSubmit}
// eslint-disable-next-line @typescript-eslint/no-explicit-any onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any} initialData={jsonToFormData(editItem.item_data) as any}
/> />
</SubmissionErrorBoundary>
); );
case 'property_owner': case 'property_owner':
return ( return (
<PropertyOwnerForm <SubmissionErrorBoundary submissionId={editItem.id}>
onSubmit={handleSubmit} <PropertyOwnerForm
onCancel={() => onOpenChange(false)} onSubmit={handleSubmit}
// eslint-disable-next-line @typescript-eslint/no-explicit-any onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any} initialData={jsonToFormData(editItem.item_data) as any}
/> />
</SubmissionErrorBoundary>
); );
case 'ride_model': case 'ride_model':
@@ -201,14 +206,15 @@ export function ItemEditDialog({ item, items, open, onOpenChange, onComplete }:
? itemData.manufacturer_id ? itemData.manufacturer_id
: ''; : '';
return ( return (
<RideModelForm <SubmissionErrorBoundary submissionId={editItem.id}>
manufacturerName={manufacturerName} <RideModelForm
manufacturerId={manufacturerId} manufacturerName={manufacturerName}
onSubmit={handleSubmit} manufacturerId={manufacturerId}
onCancel={() => onOpenChange(false)} onSubmit={handleSubmit}
// eslint-disable-next-line @typescript-eslint/no-explicit-any onCancel={() => onOpenChange(false)}
initialData={itemData as any} initialData={itemData as any}
/> />
</SubmissionErrorBoundary>
); );
case 'photo': case 'photo':

View File

@@ -0,0 +1,39 @@
import { useEffect } from 'react';
import { useAuth } from './useAuth';
import { useUserRole } from './useUserRole';
/**
* Preloads admin route chunks for authenticated moderators/admins
* This reduces chunk load failures by warming up the browser cache
*/
export function useAdminRoutePreload() {
const { user } = useAuth();
const { isModerator, isAdmin } = useUserRole();
useEffect(() => {
// Only preload if user has admin access
if (!user || (!isModerator && !isAdmin)) {
return;
}
// Preload admin chunks after a short delay to avoid blocking initial page load
const preloadTimer = setTimeout(() => {
// Preload critical admin routes
const adminRoutes = [
() => import('../pages/AdminDashboard'),
() => import('../pages/AdminModeration'),
() => import('../pages/AdminReports'),
];
// Start preloading (but don't await - let it happen in background)
adminRoutes.forEach(route => {
route().catch(err => {
// Silently fail - preloading is a performance optimization
console.debug('Admin route preload failed:', err);
});
});
}, 2000); // Wait 2 seconds after auth to avoid blocking initial render
return () => clearTimeout(preloadTimer);
}, [user, isModerator, isAdmin]);
}

View File

@@ -0,0 +1,76 @@
import { useEffect, useState } from 'react';
import { toast } from 'sonner';
// App version - automatically updated during build
const APP_VERSION = import.meta.env.VITE_APP_VERSION || 'dev';
const VERSION_CHECK_INTERVAL = 5 * 60 * 1000; // Check every 5 minutes
/**
* Monitors for new app deployments and prompts user to refresh
*/
export function useVersionCheck() {
const [newVersionAvailable, setNewVersionAvailable] = useState(false);
useEffect(() => {
// Don't run in development
if (import.meta.env.DEV) {
return;
}
const checkVersion = async () => {
try {
// Fetch the current index.html with cache bypass
const response = await fetch('/', {
method: 'HEAD',
cache: 'no-cache',
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
},
});
// Check ETag or Last-Modified to detect changes
const etag = response.headers.get('ETag');
const lastModified = response.headers.get('Last-Modified');
const currentFingerprint = `${etag}-${lastModified}`;
const storedFingerprint = sessionStorage.getItem('app-version-fingerprint');
if (storedFingerprint && storedFingerprint !== currentFingerprint) {
// New version detected
setNewVersionAvailable(true);
toast.info('New version available', {
description: 'A new version of ThrillWiki is available. Please refresh to update.',
duration: 30000, // Show for 30 seconds
action: {
label: 'Refresh Now',
onClick: () => window.location.reload(),
},
});
}
// Store current fingerprint
if (!storedFingerprint) {
sessionStorage.setItem('app-version-fingerprint', currentFingerprint);
}
} catch (error) {
// Silently fail - version check is non-critical
console.debug('Version check failed:', error);
}
};
// Initial check after 1 minute (give time for user to settle in)
const initialTimer = setTimeout(checkVersion, 60000);
// Then check periodically
const interval = setInterval(checkVersion, VERSION_CHECK_INTERVAL);
return () => {
clearTimeout(initialTimer);
clearInterval(interval);
};
}, []);
return { newVersionAvailable };
}

View File

@@ -18,6 +18,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { handleNonCriticalError } from '@/lib/errorHandler'; import { handleNonCriticalError } from '@/lib/errorHandler';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function DesignerRides() { export default function DesignerRides() {
const { designerSlug } = useParams<{ designerSlug: string }>(); const { designerSlug } = useParams<{ designerSlug: string }>();
@@ -329,10 +330,12 @@ export default function DesignerRides() {
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<RideForm <SubmissionErrorBoundary>
onSubmit={handleCreateSubmit} <RideForm
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit}
/> onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>

View File

@@ -26,6 +26,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function Designers() { export default function Designers() {
useDocumentTitle('Designers'); useDocumentTitle('Designers');
@@ -359,7 +360,9 @@ export default function Designers() {
</main> </main>
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DesignerForm onSubmit={handleCreateSubmit} onCancel={() => setIsCreateModalOpen(false)} /> <SubmissionErrorBoundary>
<DesignerForm onSubmit={handleCreateSubmit} onCancel={() => setIsCreateModalOpen(false)} />
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>

View File

@@ -18,6 +18,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { handleNonCriticalError } from '@/lib/errorHandler'; import { handleNonCriticalError } from '@/lib/errorHandler';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
interface RideModelWithCount extends RideModel { interface RideModelWithCount extends RideModel {
ride_count: number; ride_count: number;
@@ -295,11 +296,13 @@ export default function ManufacturerModels() {
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<RideModelForm <SubmissionErrorBoundary>
manufacturerName={manufacturer.name} <RideModelForm
onSubmit={handleCreateSubmit} manufacturerName={manufacturer.name}
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit}
/> onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>

View File

@@ -18,6 +18,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { handleNonCriticalError } from '@/lib/errorHandler'; import { handleNonCriticalError } from '@/lib/errorHandler';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function ManufacturerRides() { export default function ManufacturerRides() {
const { manufacturerSlug } = useParams<{ manufacturerSlug: string }>(); const { manufacturerSlug } = useParams<{ manufacturerSlug: string }>();
@@ -329,10 +330,12 @@ export default function ManufacturerRides() {
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<RideForm <SubmissionErrorBoundary>
onSubmit={handleCreateSubmit} <RideForm
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit}
/> onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>

View File

@@ -26,6 +26,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function Manufacturers() { export default function Manufacturers() {
useDocumentTitle('Manufacturers'); useDocumentTitle('Manufacturers');
@@ -374,10 +375,12 @@ export default function Manufacturers() {
{/* Create Modal */} {/* Create Modal */}
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<ManufacturerForm <SubmissionErrorBoundary>
onSubmit={handleCreateSubmit} <ManufacturerForm
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit}
/> onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>

View File

@@ -26,6 +26,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
const Operators = () => { const Operators = () => {
useDocumentTitle('Operators'); useDocumentTitle('Operators');
@@ -377,10 +378,12 @@ const Operators = () => {
{/* Create Modal */} {/* Create Modal */}
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<OperatorForm <SubmissionErrorBoundary>
onSubmit={handleCreateSubmit} <OperatorForm
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit}
/> onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</main> </main>

View File

@@ -26,6 +26,7 @@ import { usePhotoCount } from '@/hooks/photos/usePhotoCount';
const RideForm = lazy(() => import('@/components/admin/RideForm').then(m => ({ default: m.RideForm }))); const RideForm = lazy(() => import('@/components/admin/RideForm').then(m => ({ default: m.RideForm })));
const ParkForm = lazy(() => import('@/components/admin/ParkForm').then(m => ({ default: m.ParkForm }))); const ParkForm = lazy(() => import('@/components/admin/ParkForm').then(m => ({ default: m.ParkForm })));
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
import { useUserRole } from '@/hooks/useUserRole'; import { useUserRole } from '@/hooks/useUserRole';
import { Edit } from 'lucide-react'; import { Edit } from 'lucide-react';
import { VersionIndicator } from '@/components/versioning/VersionIndicator'; import { VersionIndicator } from '@/components/versioning/VersionIndicator';
@@ -610,10 +611,13 @@ export default function ParkDetail() {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<Suspense fallback={<AdminFormSkeleton />}> <Suspense fallback={<AdminFormSkeleton />}>
<RideForm <SubmissionErrorBoundary>
onSubmit={handleRideSubmit} <RideForm
onCancel={() => setIsAddRideModalOpen(false)} onSubmit={handleRideSubmit}
/> onCancel={() => setIsAddRideModalOpen(false)}
initialData={{ park_id: park.id }}
/>
</SubmissionErrorBoundary>
</Suspense> </Suspense>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
@@ -628,28 +632,30 @@ export default function ParkDetail() {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<Suspense fallback={<AdminFormSkeleton />}> <Suspense fallback={<AdminFormSkeleton />}>
<ParkForm <SubmissionErrorBoundary>
onSubmit={handleEditParkSubmit} <ParkForm
onCancel={() => setIsEditParkModalOpen(false)} onSubmit={handleEditParkSubmit}
initialData={{ onCancel={() => setIsEditParkModalOpen(false)}
id: park?.id, initialData={{
name: park?.name, id: park?.id,
slug: park?.slug, name: park?.name,
description: park?.description ?? undefined, slug: park?.slug,
park_type: park?.park_type, description: park?.description ?? undefined,
status: park?.status, park_type: park?.park_type,
opening_date: park?.opening_date ?? undefined, status: park?.status,
closing_date: park?.closing_date ?? undefined, opening_date: park?.opening_date ?? undefined,
website_url: park?.website_url ?? undefined, closing_date: park?.closing_date ?? undefined,
phone: park?.phone ?? undefined, website_url: park?.website_url ?? undefined,
email: park?.email ?? undefined, phone: park?.phone ?? undefined,
operator_id: park?.operator?.id, email: park?.email ?? undefined,
property_owner_id: park?.property_owner?.id, operator_id: park?.operator?.id,
banner_image_url: park?.banner_image_url ?? undefined, property_owner_id: park?.property_owner?.id,
card_image_url: park?.card_image_url ?? undefined banner_image_url: park?.banner_image_url ?? undefined,
}} card_image_url: park?.card_image_url ?? undefined
isEditing={true} }}
/> isEditing={true}
/>
</SubmissionErrorBoundary>
</Suspense> </Suspense>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@@ -20,6 +20,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { getErrorMessage } from '@/lib/errorHandler'; import { getErrorMessage } from '@/lib/errorHandler';
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
const ParkOwners = () => { const ParkOwners = () => {
useDocumentTitle('Property Owners'); useDocumentTitle('Property Owners');
@@ -242,10 +243,12 @@ const ParkOwners = () => {
{/* Create Modal */} {/* Create Modal */}
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<PropertyOwnerForm <SubmissionErrorBoundary>
onSubmit={handleCreateSubmit} <PropertyOwnerForm
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit}
/> onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</main> </main>

View File

@@ -18,6 +18,7 @@ import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler';
import { useAuthModal } from '@/hooks/useAuthModal'; import { useAuthModal } from '@/hooks/useAuthModal';
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function ParkRides() { export default function ParkRides() {
const { parkSlug } = useParams<{ parkSlug: string }>(); const { parkSlug } = useParams<{ parkSlug: string }>();
@@ -353,12 +354,14 @@ export default function ParkRides() {
{/* Create Modal */} {/* Create Modal */}
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<RideForm <SubmissionErrorBoundary>
onSubmit={handleCreateSubmit as any} <RideForm
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit as any}
isEditing={false} onCancel={() => setIsCreateModalOpen(false)}
initialData={{ park_id: park.id }} isEditing={false}
/> initialData={{ park_id: park.id }}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</main> </main>

View File

@@ -39,6 +39,7 @@ import { useAuthModal } from "@/hooks/useAuthModal";
import { useOpenGraph } from "@/hooks/useOpenGraph"; import { useOpenGraph } from "@/hooks/useOpenGraph";
import { useParks } from "@/hooks/parks/useParks"; import { useParks } from "@/hooks/parks/useParks";
import { Pagination } from "@/components/common/Pagination"; import { Pagination } from "@/components/common/Pagination";
import { SubmissionErrorBoundary } from "@/components/error/SubmissionErrorBoundary";
export interface FilterState { export interface FilterState {
search: string; search: string;
@@ -583,7 +584,9 @@ export default function Parks() {
Add a new park to the database. Your submission will be reviewed before being published. Add a new park to the database. Your submission will be reviewed before being published.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<ParkForm onSubmit={handleParkSubmit} onCancel={() => setIsAddParkModalOpen(false)} isEditing={false} /> <SubmissionErrorBoundary>
<ParkForm onSubmit={handleParkSubmit} onCancel={() => setIsAddParkModalOpen(false)} isEditing={false} />
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</main> </main>

View File

@@ -17,6 +17,7 @@ import { toast } from '@/hooks/use-toast';
import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler'; import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler';
import type { Ride, Company, RideModel } from "@/types/database"; import type { Ride, Company, RideModel } from "@/types/database";
import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function RideModelRides() { export default function RideModelRides() {
const { manufacturerSlug, modelSlug } = useParams<{ manufacturerSlug: string; modelSlug: string }>(); const { manufacturerSlug, modelSlug } = useParams<{ manufacturerSlug: string; modelSlug: string }>();
@@ -293,10 +294,12 @@ export default function RideModelRides() {
{/* Create Modal */} {/* Create Modal */}
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<RideForm <SubmissionErrorBoundary>
onSubmit={handleCreateSubmit} <RideForm
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit}
/> onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>

View File

@@ -26,6 +26,7 @@ import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useOpenGraph } from '@/hooks/useOpenGraph';
import { useRides } from '@/hooks/rides/useRides'; import { useRides } from '@/hooks/rides/useRides';
import { Pagination } from '@/components/common/Pagination'; import { Pagination } from '@/components/common/Pagination';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function Rides() { export default function Rides() {
useDocumentTitle('Rides & Attractions'); useDocumentTitle('Rides & Attractions');
@@ -529,11 +530,13 @@ export default function Rides() {
{/* Create Modal */} {/* Create Modal */}
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}> <Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<RideForm <SubmissionErrorBoundary>
onSubmit={handleCreateSubmit} <RideForm
onCancel={() => setIsCreateModalOpen(false)} onSubmit={handleCreateSubmit}
isEditing={false} onCancel={() => setIsCreateModalOpen(false)}
/> isEditing={false}
/>
</SubmissionErrorBoundary>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</main> </main>