feat: Lazy load admin forms

This commit is contained in:
gpt-engineer-app[bot]
2025-10-21 18:43:38 +00:00
parent 70a8534da7
commit 6a70267a57
9 changed files with 358 additions and 222 deletions

View File

@@ -31,15 +31,6 @@ export const RideCardGridSkeleton = () => (
</div>
);
export const AdminFormSkeleton = () => (
<div className="space-y-4 p-6">
<Skeleton className="h-10 w-full" />
<Skeleton className="h-32 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-24" />
</div>
);
export const EditorSkeleton = () => (
<div className="space-y-2">
<Skeleton className="h-10 w-full" />
@@ -70,3 +61,49 @@ export const DialogSkeleton = () => (
</CardContent>
</Card>
);
export const AdminFormSkeleton = () => (
<div className="space-y-6 p-6">
{/* Name field */}
<div className="space-y-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-10 w-full" />
</div>
{/* Slug field */}
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
{/* Description textarea */}
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-32 w-full" />
</div>
{/* Two column fields */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-10 w-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-28" />
<Skeleton className="h-10 w-full" />
</div>
</div>
{/* Image upload section */}
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-40 w-full rounded-lg" />
</div>
{/* Action buttons */}
<div className="flex gap-2 justify-end pt-4">
<Skeleton className="h-10 w-24" />
<Skeleton className="h-10 w-32" />
</div>
</div>
);

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, lazy, Suspense } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '@/components/layout/Header';
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
@@ -7,11 +7,14 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { AdminFormSkeleton } from '@/components/loading/PageSkeletons';
import { ArrowLeft, MapPin, Star, Globe, Calendar, Edit, Ruler } from 'lucide-react';
import { Company } from '@/types/database';
import { supabase } from '@/integrations/supabase/client';
import { DesignerForm } from '@/components/admin/DesignerForm';
import { DesignerPhotoGallery } from '@/components/companies/DesignerPhotoGallery';
// Lazy load admin form
const DesignerForm = lazy(() => import('@/components/admin/DesignerForm').then(m => ({ default: m.DesignerForm })));
import { useAuth } from '@/hooks/useAuth';
import { useUserRole } from '@/hooks/useUserRole';
import { toast } from '@/hooks/use-toast';
@@ -337,23 +340,25 @@ export default function DesignerDetail() {
{/* Edit Modal */}
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DesignerForm
initialData={{
id: designer.id,
name: designer.name,
slug: designer.slug,
description: designer.description,
company_type: 'designer',
person_type: (designer.person_type || 'company') as 'company' | 'individual' | 'firm' | 'organization',
website_url: designer.website_url,
founded_year: designer.founded_year,
headquarters_location: designer.headquarters_location,
banner_image_url: designer.banner_image_url,
card_image_url: designer.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
<Suspense fallback={<AdminFormSkeleton />}>
<DesignerForm
initialData={{
id: designer.id,
name: designer.name,
slug: designer.slug,
description: designer.description,
company_type: 'designer',
person_type: (designer.person_type || 'company') as 'company' | 'individual' | 'firm' | 'organization',
website_url: designer.website_url,
founded_year: designer.founded_year,
headquarters_location: designer.headquarters_location,
banner_image_url: designer.banner_image_url,
card_image_url: designer.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
</Suspense>
</DialogContent>
</Dialog>
</div>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, lazy, Suspense } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '@/components/layout/Header';
import { trackPageView } from '@/lib/viewTracking';
@@ -8,11 +8,14 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { AdminFormSkeleton } from '@/components/loading/PageSkeletons';
import { ArrowLeft, MapPin, Star, Globe, Calendar, Edit, Factory, FerrisWheel } from 'lucide-react';
import { Company } from '@/types/database';
import { supabase } from '@/integrations/supabase/client';
import { ManufacturerForm } from '@/components/admin/ManufacturerForm';
import { ManufacturerPhotoGallery } from '@/components/companies/ManufacturerPhotoGallery';
// Lazy load admin form
const ManufacturerForm = lazy(() => import('@/components/admin/ManufacturerForm').then(m => ({ default: m.ManufacturerForm })));
import { useAuth } from '@/hooks/useAuth';
import { useUserRole } from '@/hooks/useUserRole';
import { toast } from '@/hooks/use-toast';
@@ -371,23 +374,25 @@ export default function ManufacturerDetail() {
{/* Edit Modal */}
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<ManufacturerForm
initialData={{
id: manufacturer.id,
name: manufacturer.name,
slug: manufacturer.slug,
description: manufacturer.description,
company_type: 'manufacturer',
person_type: (manufacturer.person_type || 'company') as 'company' | 'individual' | 'firm' | 'organization',
website_url: manufacturer.website_url,
founded_year: manufacturer.founded_year,
headquarters_location: manufacturer.headquarters_location,
banner_image_url: manufacturer.banner_image_url,
card_image_url: manufacturer.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
<Suspense fallback={<AdminFormSkeleton />}>
<ManufacturerForm
initialData={{
id: manufacturer.id,
name: manufacturer.name,
slug: manufacturer.slug,
description: manufacturer.description,
company_type: 'manufacturer',
person_type: (manufacturer.person_type || 'company') as 'company' | 'individual' | 'firm' | 'organization',
website_url: manufacturer.website_url,
founded_year: manufacturer.founded_year,
headquarters_location: manufacturer.headquarters_location,
banner_image_url: manufacturer.banner_image_url,
card_image_url: manufacturer.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
</Suspense>
</DialogContent>
</Dialog>
</div>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, lazy, Suspense } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '@/components/layout/Header';
import { trackPageView } from '@/lib/viewTracking';
@@ -8,12 +8,15 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { AdminFormSkeleton } from '@/components/loading/PageSkeletons';
import { ArrowLeft, MapPin, Star, Globe, Calendar, Edit, FerrisWheel, Gauge } from 'lucide-react';
import { Company, Park } from '@/types/database';
import { supabase } from '@/integrations/supabase/client';
import { OperatorForm } from '@/components/admin/OperatorForm';
import { OperatorPhotoGallery } from '@/components/companies/OperatorPhotoGallery';
import { ParkCard } from '@/components/parks/ParkCard';
// Lazy load admin form
const OperatorForm = lazy(() => import('@/components/admin/OperatorForm').then(m => ({ default: m.OperatorForm })));
import { useAuth } from '@/hooks/useAuth';
import { useUserRole } from '@/hooks/useUserRole';
import { toast } from '@/hooks/use-toast';
@@ -423,23 +426,25 @@ export default function OperatorDetail() {
{/* Edit Modal */}
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<OperatorForm
initialData={{
id: operator.id,
name: operator.name,
slug: operator.slug,
description: operator.description,
company_type: 'operator',
person_type: (operator.person_type || 'company') as 'company' | 'individual' | 'firm' | 'organization',
website_url: operator.website_url,
founded_year: operator.founded_year,
headquarters_location: operator.headquarters_location,
banner_image_url: operator.banner_image_url,
card_image_url: operator.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
<Suspense fallback={<AdminFormSkeleton />}>
<OperatorForm
initialData={{
id: operator.id,
name: operator.name,
slug: operator.slug,
description: operator.description,
company_type: 'operator',
person_type: (operator.person_type || 'company') as 'company' | 'individual' | 'firm' | 'organization',
website_url: operator.website_url,
founded_year: operator.founded_year,
headquarters_location: operator.headquarters_location,
banner_image_url: operator.banner_image_url,
card_image_url: operator.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
</Suspense>
</DialogContent>
</Dialog>
</div>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, lazy, Suspense } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '@/components/layout/Header';
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
@@ -17,9 +17,12 @@ import { ParkLocationMap } from '@/components/maps/ParkLocationMap';
import { EntityPhotoGallery } from '@/components/upload/EntityPhotoGallery';
import { supabase } from '@/integrations/supabase/client';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { RideForm } from '@/components/admin/RideForm';
import { ParkForm } from '@/components/admin/ParkForm';
import { AdminFormSkeleton } from '@/components/loading/PageSkeletons';
import { toast } from '@/hooks/use-toast';
// Lazy load admin forms
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 { useUserRole } from '@/hooks/useUserRole';
import { Edit } from 'lucide-react';
@@ -640,10 +643,12 @@ export default function ParkDetail() {
Submit a new ride for moderation. All submissions are reviewed before being published.
</DialogDescription>
</DialogHeader>
<RideForm
onSubmit={handleRideSubmit}
onCancel={() => setIsAddRideModalOpen(false)}
/>
<Suspense fallback={<AdminFormSkeleton />}>
<RideForm
onSubmit={handleRideSubmit}
onCancel={() => setIsAddRideModalOpen(false)}
/>
</Suspense>
</DialogContent>
</Dialog>
@@ -656,28 +661,30 @@ export default function ParkDetail() {
Make changes to the park information. {isModerator() ? 'Changes will be applied immediately.' : 'Your changes will be submitted for review.'}
</DialogDescription>
</DialogHeader>
<ParkForm
onSubmit={handleEditParkSubmit}
onCancel={() => setIsEditParkModalOpen(false)}
initialData={{
id: park?.id,
name: park?.name,
slug: park?.slug,
description: park?.description,
park_type: park?.park_type,
status: park?.status,
opening_date: park?.opening_date,
closing_date: park?.closing_date,
website_url: park?.website_url,
phone: park?.phone,
email: park?.email,
operator_id: park?.operator?.id,
property_owner_id: park?.property_owner?.id,
banner_image_url: park?.banner_image_url,
card_image_url: park?.card_image_url
}}
isEditing={true}
/>
<Suspense fallback={<AdminFormSkeleton />}>
<ParkForm
onSubmit={handleEditParkSubmit}
onCancel={() => setIsEditParkModalOpen(false)}
initialData={{
id: park?.id,
name: park?.name,
slug: park?.slug,
description: park?.description,
park_type: park?.park_type,
status: park?.status,
opening_date: park?.opening_date,
closing_date: park?.closing_date,
website_url: park?.website_url,
phone: park?.phone,
email: park?.email,
operator_id: park?.operator?.id,
property_owner_id: park?.property_owner?.id,
banner_image_url: park?.banner_image_url,
card_image_url: park?.card_image_url
}}
isEditing={true}
/>
</Suspense>
</DialogContent>
</Dialog>
</main>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, lazy, Suspense } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '@/components/layout/Header';
import { trackPageView } from '@/lib/viewTracking';
@@ -8,12 +8,15 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { AdminFormSkeleton } from '@/components/loading/PageSkeletons';
import { ArrowLeft, MapPin, Star, Globe, Calendar, Edit, Building2, Gauge } from 'lucide-react';
import { Company, Park } from '@/types/database';
import { supabase } from '@/integrations/supabase/client';
import { PropertyOwnerForm } from '@/components/admin/PropertyOwnerForm';
import { PropertyOwnerPhotoGallery } from '@/components/companies/PropertyOwnerPhotoGallery';
import { ParkCard } from '@/components/parks/ParkCard';
// Lazy load admin form
const PropertyOwnerForm = lazy(() => import('@/components/admin/PropertyOwnerForm').then(m => ({ default: m.PropertyOwnerForm })));
import { useAuth } from '@/hooks/useAuth';
import { useUserRole } from '@/hooks/useUserRole';
import { toast } from '@/hooks/use-toast';
@@ -423,23 +426,25 @@ export default function PropertyOwnerDetail() {
{/* Edit Modal */}
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<PropertyOwnerForm
initialData={{
id: owner.id,
name: owner.name,
slug: owner.slug,
description: owner.description,
company_type: 'property_owner',
person_type: (owner.person_type || 'company') as 'company' | 'individual' | 'firm' | 'organization',
website_url: owner.website_url,
founded_year: owner.founded_year,
headquarters_location: owner.headquarters_location,
banner_image_url: owner.banner_image_url,
card_image_url: owner.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
<Suspense fallback={<AdminFormSkeleton />}>
<PropertyOwnerForm
initialData={{
id: owner.id,
name: owner.name,
slug: owner.slug,
description: owner.description,
company_type: 'property_owner',
person_type: (owner.person_type || 'company') as 'company' | 'individual' | 'firm' | 'organization',
website_url: owner.website_url,
founded_year: owner.founded_year,
headquarters_location: owner.headquarters_location,
banner_image_url: owner.banner_image_url,
card_image_url: owner.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
</Suspense>
</DialogContent>
</Dialog>
</div>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, lazy, Suspense } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '@/components/layout/Header';
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
@@ -9,6 +9,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Separator } from '@/components/ui/separator';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { AdminFormSkeleton } from '@/components/loading/PageSkeletons';
import {
MapPin,
Star,
@@ -41,8 +42,10 @@ import { RecentPhotosPreview } from '@/components/rides/RecentPhotosPreview';
import { ParkLocationMap } from '@/components/maps/ParkLocationMap';
import { Ride } from '@/types/database';
import { supabase } from '@/integrations/supabase/client';
import { RideForm } from '@/components/admin/RideForm';
import { useAuth } from '@/hooks/useAuth';
// Lazy load admin forms
const RideForm = lazy(() => import('@/components/admin/RideForm').then(m => ({ default: m.RideForm })));
import { useUserRole } from '@/hooks/useUserRole';
import { toast } from '@/hooks/use-toast';
import { getErrorMessage } from '@/lib/errorHandler';
@@ -729,41 +732,43 @@ export default function RideDetail() {
: "Submit changes to this ride for review. A moderator will review your submission."}
</DialogDescription>
</DialogHeader>
{ride && (
<RideForm
initialData={{
id: ride.id,
name: ride.name,
slug: ride.slug,
description: ride.description,
category: ride.category,
ride_sub_type: ride.ride_sub_type,
status: ride.status as "operating" | "closed_permanently" | "closed_temporarily" | "under_construction" | "relocated" | "stored" | "demolished",
opening_date: ride.opening_date,
closing_date: ride.closing_date,
height_requirement: ride.height_requirement,
age_requirement: ride.age_requirement,
capacity_per_hour: ride.capacity_per_hour,
duration_seconds: ride.duration_seconds,
max_speed_kmh: ride.max_speed_kmh,
max_height_meters: ride.max_height_meters,
length_meters: ride.length_meters,
inversions: ride.inversions,
coaster_type: ride.coaster_type,
seating_type: ride.seating_type,
intensity_level: ride.intensity_level,
drop_height_meters: ride.drop_height_meters,
max_g_force: ride.max_g_force,
manufacturer_id: ride.manufacturer?.id,
ride_model_id: ride.ride_model?.id,
banner_image_url: ride.banner_image_url,
card_image_url: ride.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
isEditing={true}
/>
)}
<Suspense fallback={<AdminFormSkeleton />}>
{ride && (
<RideForm
initialData={{
id: ride.id,
name: ride.name,
slug: ride.slug,
description: ride.description,
category: ride.category,
ride_sub_type: ride.ride_sub_type,
status: ride.status as "operating" | "closed_permanently" | "closed_temporarily" | "under_construction" | "relocated" | "stored" | "demolished",
opening_date: ride.opening_date,
closing_date: ride.closing_date,
height_requirement: ride.height_requirement,
age_requirement: ride.age_requirement,
capacity_per_hour: ride.capacity_per_hour,
duration_seconds: ride.duration_seconds,
max_speed_kmh: ride.max_speed_kmh,
max_height_meters: ride.max_height_meters,
length_meters: ride.length_meters,
inversions: ride.inversions,
coaster_type: ride.coaster_type,
seating_type: ride.seating_type,
intensity_level: ride.intensity_level,
drop_height_meters: ride.drop_height_meters,
max_g_force: ride.max_g_force,
manufacturer_id: ride.manufacturer?.id,
ride_model_id: ride.ride_model?.id,
banner_image_url: ride.banner_image_url,
card_image_url: ride.card_image_url
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
isEditing={true}
/>
)}
</Suspense>
</DialogContent>
</Dialog>
</main>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, lazy, Suspense } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Header } from '@/components/layout/Header';
import { Button } from '@/components/ui/button';
@@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { AdminFormSkeleton } from '@/components/loading/PageSkeletons';
import { ArrowLeft, FerrisWheel, Building2, Edit } from 'lucide-react';
import { RideModel, Ride, Company } from '@/types/database';
import { supabase } from '@/integrations/supabase/client';
@@ -15,8 +16,10 @@ import { useAuthModal } from '@/hooks/useAuthModal';
import { useAuth } from '@/hooks/useAuth';
import { toast } from '@/hooks/use-toast';
import { getErrorMessage } from '@/lib/errorHandler';
import { RideModelForm } from '@/components/admin/RideModelForm';
import { ManufacturerPhotoGallery } from '@/components/companies/ManufacturerPhotoGallery';
// Lazy load admin form
const RideModelForm = lazy(() => import('@/components/admin/RideModelForm').then(m => ({ default: m.RideModelForm })));
import { VersionIndicator } from '@/components/versioning/VersionIndicator';
import { EntityVersionHistory } from '@/components/versioning/EntityVersionHistory';
@@ -335,22 +338,24 @@ export default function RideModelDetail() {
{/* Edit Modal */}
<Dialog open={isEditModalOpen} onOpenChange={setIsEditModalOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<RideModelForm
manufacturerName={manufacturer.name}
manufacturerId={manufacturer.id}
initialData={{
id: model.id,
name: model.name,
slug: model.slug,
category: model.category,
ride_type: model.ride_type,
description: model.description,
banner_image_url: model.banner_image_url,
card_image_url: model.card_image_url,
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
<Suspense fallback={<AdminFormSkeleton />}>
<RideModelForm
manufacturerName={manufacturer.name}
manufacturerId={manufacturer.id}
initialData={{
id: model.id,
name: model.name,
slug: model.slug,
category: model.category,
ride_type: model.ride_type,
description: model.description,
banner_image_url: model.banner_image_url,
card_image_url: model.card_image_url,
}}
onSubmit={handleEditSubmit}
onCancel={() => setIsEditModalOpen(false)}
/>
</Suspense>
</DialogContent>
</Dialog>
</main>