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 { RetryStatusIndicator } from "@/components/ui/retry-status-indicator";
import { APIStatusBanner } from "@/components/ui/api-status-banner";
import { useAdminRoutePreload } from "@/hooks/useAdminRoutePreload";
import { useVersionCheck } from "@/hooks/useVersionCheck";
import { cn } from "@/lib/utils";
// 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
const { isAPIReachable, isBannerDismissed } = useAPIConnectivity();
const showBanner = !isAPIReachable && !isBannerDismissed;
// Preload admin routes for moderators/admins
useAdminRoutePreload();
// Monitor for new deployments
useVersionCheck();
return (
<TooltipProvider>

View File

@@ -71,6 +71,32 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
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 = () => {
window.location.href = '/';
};
@@ -90,12 +116,23 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
<AlertTriangle className="w-8 h-8 text-destructive" />
</div>
<CardTitle className="text-2xl">
{isChunkError ? 'New Version Available' : 'Something Went Wrong'}
{isChunkError ? 'App Update Required' : 'Something Went Wrong'}
</CardTitle>
<CardDescription className="mt-2">
{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."}
<CardDescription className="mt-2 space-y-2">
{isChunkError ? (
<>
<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>
</CardHeader>
<CardContent className="space-y-4">
@@ -114,23 +151,35 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
</div>
)}
<div className="flex flex-col sm:flex-row gap-2">
<Button
variant="default"
onClick={this.handleReload}
className="flex-1 gap-2"
>
<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 className="flex flex-col gap-2">
{isChunkError && (
<Button
variant="default"
onClick={this.handleClearCacheAndReload}
className="w-full gap-2"
>
<RefreshCw className="w-4 h-4" />
Clear Cache & Reload
</Button>
)}
<div className="flex flex-col sm:flex-row gap-2">
<Button
variant={isChunkError ? "outline" : "default"}
onClick={this.handleReload}
className="flex-1 gap-2"
>
<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>
<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 { RideModelForm } from '@/components/admin/RideModelForm';
import { Save, X, Edit } from 'lucide-react';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
interface ItemEditDialogProps {
item?: SubmissionItemWithDeps | null;
@@ -131,66 +132,70 @@ export function ItemEditDialog({ item, items, open, onOpenChange, onComplete }:
switch (editItem.item_type) {
case 'park':
return (
<ParkForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
// Convert Json to form-compatible object (null → undefined)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialData={jsonToFormData(editItem.item_data) as any}
isEditing
/>
<SubmissionErrorBoundary submissionId={editItem.id}>
<ParkForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any}
isEditing
/>
</SubmissionErrorBoundary>
);
case 'ride':
return (
<RideForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
// Convert Json to form-compatible object (null → undefined)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialData={jsonToFormData(editItem.item_data) as any}
isEditing
/>
<SubmissionErrorBoundary submissionId={editItem.id}>
<RideForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any}
isEditing
/>
</SubmissionErrorBoundary>
);
case 'manufacturer':
return (
<ManufacturerForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialData={jsonToFormData(editItem.item_data) as any}
/>
<SubmissionErrorBoundary submissionId={editItem.id}>
<ManufacturerForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any}
/>
</SubmissionErrorBoundary>
);
case 'designer':
return (
<DesignerForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialData={jsonToFormData(editItem.item_data) as any}
/>
<SubmissionErrorBoundary submissionId={editItem.id}>
<DesignerForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any}
/>
</SubmissionErrorBoundary>
);
case 'operator':
return (
<OperatorForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialData={jsonToFormData(editItem.item_data) as any}
/>
<SubmissionErrorBoundary submissionId={editItem.id}>
<OperatorForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any}
/>
</SubmissionErrorBoundary>
);
case 'property_owner':
return (
<PropertyOwnerForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialData={jsonToFormData(editItem.item_data) as any}
/>
<SubmissionErrorBoundary submissionId={editItem.id}>
<PropertyOwnerForm
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={jsonToFormData(editItem.item_data) as any}
/>
</SubmissionErrorBoundary>
);
case 'ride_model':
@@ -201,14 +206,15 @@ export function ItemEditDialog({ item, items, open, onOpenChange, onComplete }:
? itemData.manufacturer_id
: '';
return (
<RideModelForm
manufacturerName={manufacturerName}
manufacturerId={manufacturerId}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialData={itemData as any}
/>
<SubmissionErrorBoundary submissionId={editItem.id}>
<RideModelForm
manufacturerName={manufacturerName}
manufacturerId={manufacturerId}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
initialData={itemData as any}
/>
</SubmissionErrorBoundary>
);
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 { useOpenGraph } from '@/hooks/useOpenGraph';
import { handleNonCriticalError } from '@/lib/errorHandler';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function DesignerRides() {
const { designerSlug } = useParams<{ designerSlug: string }>();
@@ -329,10 +330,12 @@ export default function DesignerRides() {
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<RideForm
onSubmit={handleCreateSubmit}
onCancel={() => setIsCreateModalOpen(false)}
/>
<SubmissionErrorBoundary>
<RideForm
onSubmit={handleCreateSubmit}
onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent>
</Dialog>
</div>

View File

@@ -26,6 +26,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { getErrorMessage } from '@/lib/errorHandler';
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
export default function Designers() {
useDocumentTitle('Designers');
@@ -359,7 +360,9 @@ export default function Designers() {
</main>
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<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>
</Dialog>
</div>

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { getErrorMessage } from '@/lib/errorHandler';
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { useOpenGraph } from '@/hooks/useOpenGraph';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
const Operators = () => {
useDocumentTitle('Operators');
@@ -377,10 +378,12 @@ const Operators = () => {
{/* Create Modal */}
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<OperatorForm
onSubmit={handleCreateSubmit}
onCancel={() => setIsCreateModalOpen(false)}
/>
<SubmissionErrorBoundary>
<OperatorForm
onSubmit={handleCreateSubmit}
onCancel={() => setIsCreateModalOpen(false)}
/>
</SubmissionErrorBoundary>
</DialogContent>
</Dialog>
</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 ParkForm = lazy(() => import('@/components/admin/ParkForm').then(m => ({ default: m.ParkForm })));
import { getErrorMessage } from '@/lib/errorHandler';
import { SubmissionErrorBoundary } from '@/components/error/SubmissionErrorBoundary';
import { useUserRole } from '@/hooks/useUserRole';
import { Edit } from 'lucide-react';
import { VersionIndicator } from '@/components/versioning/VersionIndicator';
@@ -610,10 +611,13 @@ export default function ParkDetail() {
</DialogDescription>
</DialogHeader>
<Suspense fallback={<AdminFormSkeleton />}>
<RideForm
onSubmit={handleRideSubmit}
onCancel={() => setIsAddRideModalOpen(false)}
/>
<SubmissionErrorBoundary>
<RideForm
onSubmit={handleRideSubmit}
onCancel={() => setIsAddRideModalOpen(false)}
initialData={{ park_id: park.id }}
/>
</SubmissionErrorBoundary>
</Suspense>
</DialogContent>
</Dialog>
@@ -628,28 +632,30 @@ export default function ParkDetail() {
</DialogDescription>
</DialogHeader>
<Suspense fallback={<AdminFormSkeleton />}>
<ParkForm
onSubmit={handleEditParkSubmit}
onCancel={() => setIsEditParkModalOpen(false)}
initialData={{
id: park?.id,
name: park?.name,
slug: park?.slug,
description: park?.description ?? undefined,
park_type: park?.park_type,
status: park?.status,
opening_date: park?.opening_date ?? undefined,
closing_date: park?.closing_date ?? undefined,
website_url: park?.website_url ?? undefined,
phone: park?.phone ?? undefined,
email: park?.email ?? undefined,
operator_id: park?.operator?.id,
property_owner_id: park?.property_owner?.id,
banner_image_url: park?.banner_image_url ?? undefined,
card_image_url: park?.card_image_url ?? undefined
}}
isEditing={true}
/>
<SubmissionErrorBoundary>
<ParkForm
onSubmit={handleEditParkSubmit}
onCancel={() => setIsEditParkModalOpen(false)}
initialData={{
id: park?.id,
name: park?.name,
slug: park?.slug,
description: park?.description ?? undefined,
park_type: park?.park_type,
status: park?.status,
opening_date: park?.opening_date ?? undefined,
closing_date: park?.closing_date ?? undefined,
website_url: park?.website_url ?? undefined,
phone: park?.phone ?? undefined,
email: park?.email ?? undefined,
operator_id: park?.operator?.id,
property_owner_id: park?.property_owner?.id,
banner_image_url: park?.banner_image_url ?? undefined,
card_image_url: park?.card_image_url ?? undefined
}}
isEditing={true}
/>
</SubmissionErrorBoundary>
</Suspense>
</DialogContent>
</Dialog>

View File

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

View File

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

View File

@@ -39,6 +39,7 @@ import { useAuthModal } from "@/hooks/useAuthModal";
import { useOpenGraph } from "@/hooks/useOpenGraph";
import { useParks } from "@/hooks/parks/useParks";
import { Pagination } from "@/components/common/Pagination";
import { SubmissionErrorBoundary } from "@/components/error/SubmissionErrorBoundary";
export interface FilterState {
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.
</DialogDescription>
</DialogHeader>
<ParkForm onSubmit={handleParkSubmit} onCancel={() => setIsAddParkModalOpen(false)} isEditing={false} />
<SubmissionErrorBoundary>
<ParkForm onSubmit={handleParkSubmit} onCancel={() => setIsAddParkModalOpen(false)} isEditing={false} />
</SubmissionErrorBoundary>
</DialogContent>
</Dialog>
</main>

View File

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

View File

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