- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
335 lines
12 KiB
TypeScript
335 lines
12 KiB
TypeScript
/**
|
|
* Lazy-loaded route components for code splitting
|
|
* These components are loaded on-demand to improve initial bundle size
|
|
*/
|
|
|
|
import { lazy } from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import {
|
|
Calendar,
|
|
MapPin,
|
|
Users,
|
|
Settings,
|
|
Shield,
|
|
CreditCard,
|
|
QrCode,
|
|
Scan
|
|
} from 'lucide-react';
|
|
|
|
import { Card, CardHeader, CardBody } from '@/components/ui/Card';
|
|
import { Badge } from '@/components/ui/Badge';
|
|
import { Skeleton } from '@/components/loading/Skeleton';
|
|
|
|
// Lazy-loaded components
|
|
export const EventDetailPage = lazy(() => import('@/pages/EventDetailPage'));
|
|
export const GateOpsPage = lazy(() => import('@/pages/GateOpsPage').then(module => ({ default: module.GateOpsPage })));
|
|
export const PaymentSettings = lazy(() => import('@/features/org/PaymentSettings').then(module => ({ default: module.PaymentSettings })));
|
|
export const ScannerPage = lazy(() => import('@/features/scanner/ScannerPage').then(module => ({ default: module.ScannerPage })));
|
|
|
|
// Skeleton components for Suspense fallbacks
|
|
|
|
/**
|
|
* Event detail page skeleton with event header, stats, and ticket types
|
|
*/
|
|
export function EventDetailPageSkeleton() {
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="space-y-6"
|
|
data-testid="event-detail-skeleton"
|
|
>
|
|
{/* Event header */}
|
|
<Card className="overflow-hidden">
|
|
<div className="bg-gradient-to-r from-primary-500 to-accent-500 p-6">
|
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between">
|
|
<div className="space-y-3 text-white">
|
|
<Skeleton.Base className="h-8 w-64 bg-white/20" />
|
|
<div className="flex items-center space-x-4">
|
|
<div className="flex items-center space-x-2">
|
|
<Calendar className="w-4 h-4" />
|
|
<Skeleton.Base className="h-4 w-32 bg-white/20" />
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<MapPin className="w-4 h-4" />
|
|
<Skeleton.Base className="h-4 w-24 bg-white/20" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 md:mt-0 flex space-x-3">
|
|
<Skeleton.Button size="md" className="bg-white/20" />
|
|
<Skeleton.Button size="md" className="bg-white/20" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Event stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
{Array.from({ length: 4 }).map((_, index) => (
|
|
<Card key={index} className="p-6">
|
|
<div className="space-y-3">
|
|
<div className="flex items-center space-x-2">
|
|
<Users className="w-5 h-5 text-text-secondary" />
|
|
<Skeleton.Base className="h-4 w-20" />
|
|
</div>
|
|
<Skeleton.Base className="h-8 w-16" />
|
|
<Skeleton.Base className="h-3 w-32" />
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
{/* Ticket types section */}
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<Skeleton.Base className="h-6 w-32" />
|
|
<Skeleton.Button size="md" />
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div className="space-y-4">
|
|
{Array.from({ length: 3 }).map((_, index) => (
|
|
<div key={index} className="flex items-center justify-between p-4 bg-glass-bg border border-glass-border rounded-lg">
|
|
<div className="flex-1 space-y-2">
|
|
<Skeleton.Base className="h-5 w-40" />
|
|
<Skeleton.Base className="h-4 w-24" />
|
|
</div>
|
|
<div className="flex items-center space-x-4">
|
|
<Badge variant="neutral" className="bg-glass-bg">
|
|
<Skeleton.Base className="h-4 w-12" />
|
|
</Badge>
|
|
<Skeleton.Base className="h-6 w-16" />
|
|
<Skeleton.Button size="sm" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gate operations page skeleton with live scanning interface
|
|
*/
|
|
export function GateOpsPageSkeleton() {
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="space-y-6"
|
|
data-testid="gate-ops-skeleton"
|
|
>
|
|
{/* Status header */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
{Array.from({ length: 3 }).map((_, index) => (
|
|
<Card key={index} className="p-6">
|
|
<div className="space-y-3">
|
|
<div className="flex items-center space-x-2">
|
|
<Shield className="w-5 h-5 text-text-secondary" />
|
|
<Skeleton.Base className="h-4 w-24" />
|
|
</div>
|
|
<Skeleton.Base className="h-8 w-20" />
|
|
<div className="flex items-center space-x-2">
|
|
<Badge variant="neutral" className="bg-glass-bg">
|
|
<Skeleton.Base className="h-3 w-12" />
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
{/* Control panel */}
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<Skeleton.Base className="h-6 w-32" />
|
|
<div className="flex items-center space-x-3">
|
|
<Badge variant="neutral" className="bg-glass-bg">
|
|
<Skeleton.Base className="h-4 w-16" />
|
|
</Badge>
|
|
<Skeleton.Button size="sm" />
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div className="space-y-4">
|
|
{Array.from({ length: 5 }).map((_, index) => (
|
|
<div key={index} className="flex items-center justify-between p-3 bg-glass-bg border border-glass-border rounded">
|
|
<div className="flex items-center space-x-3">
|
|
<Skeleton.Avatar size="sm" />
|
|
<div className="space-y-1">
|
|
<Skeleton.Base className="h-4 w-24" />
|
|
<Skeleton.Base className="h-3 w-16" />
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Badge variant="neutral" className="bg-glass-bg">
|
|
<Skeleton.Base className="h-3 w-8" />
|
|
</Badge>
|
|
<Skeleton.Base className="h-4 w-12" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Payment settings page skeleton with Stripe integration status
|
|
*/
|
|
export function PaymentSettingsPageSkeleton() {
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="space-y-6"
|
|
data-testid="payment-settings-skeleton"
|
|
>
|
|
{/* Connection status card */}
|
|
<Card className="p-6">
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-3">
|
|
<CreditCard className="w-6 h-6 text-text-secondary" />
|
|
<Skeleton.Base className="h-6 w-48" />
|
|
</div>
|
|
<Badge variant="neutral" className="bg-glass-bg">
|
|
<Skeleton.Base className="h-4 w-16" />
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="bg-glass-bg border border-glass-border rounded-lg p-6">
|
|
<div className="space-y-4">
|
|
<Skeleton.Base className="h-5 w-56" />
|
|
<Skeleton.Text lines={3} />
|
|
<div className="flex space-x-3 pt-4">
|
|
<Skeleton.Button size="md" />
|
|
<Skeleton.Button size="md" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Payment configuration */}
|
|
<Card>
|
|
<CardHeader>
|
|
<Skeleton.Base className="h-6 w-40" />
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div className="space-y-6">
|
|
{/* Form fields */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{Array.from({ length: 4 }).map((_, index) => (
|
|
<div key={index} className="space-y-2">
|
|
<Skeleton.Base className="h-4 w-24" />
|
|
<Skeleton.Base className="h-10 w-full" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Settings toggles */}
|
|
<div className="space-y-4 pt-6 border-t border-glass-border">
|
|
{Array.from({ length: 3 }).map((_, index) => (
|
|
<div key={index} className="flex items-center justify-between p-4 bg-glass-bg border border-glass-border rounded">
|
|
<div className="space-y-1">
|
|
<Skeleton.Base className="h-4 w-32" />
|
|
<Skeleton.Base className="h-3 w-48" />
|
|
</div>
|
|
<Skeleton.Base className="h-6 w-12 rounded-full" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Scanner page skeleton with camera interface
|
|
*/
|
|
export function ScannerPageSkeleton() {
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="min-h-screen bg-glass-bg"
|
|
data-testid="scanner-skeleton"
|
|
>
|
|
{/* Scanner header */}
|
|
<div className="bg-gradient-to-r from-primary-500 to-accent-500 p-6 text-white">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-3">
|
|
<QrCode className="w-6 h-6" />
|
|
<Skeleton.Base className="h-6 w-32 bg-white/20" />
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Badge variant="neutral" className="bg-white/20 border-white/30">
|
|
<Skeleton.Base className="h-3 w-12 bg-white/20" />
|
|
</Badge>
|
|
<Skeleton.Button size="sm" className="bg-white/20" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="container mx-auto px-6 py-8 space-y-6">
|
|
{/* Camera viewport */}
|
|
<Card className="overflow-hidden">
|
|
<div className="aspect-video bg-background-secondary border-2 border-dashed border-glass-border flex items-center justify-center">
|
|
<div className="text-center space-y-3">
|
|
<Scan className="w-12 h-12 text-text-secondary mx-auto" />
|
|
<Skeleton.Base className="h-4 w-32 mx-auto" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Scanner controls */}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
{Array.from({ length: 4 }).map((_, index) => (
|
|
<Card key={index} className="p-4">
|
|
<div className="text-center space-y-2">
|
|
<Settings className="w-5 h-5 text-text-secondary mx-auto" />
|
|
<Skeleton.Base className="h-4 w-16 mx-auto" />
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
{/* Recent scans */}
|
|
<Card>
|
|
<CardHeader>
|
|
<Skeleton.Base className="h-6 w-32" />
|
|
</CardHeader>
|
|
<CardBody>
|
|
<div className="space-y-3">
|
|
{Array.from({ length: 5 }).map((_, index) => (
|
|
<div key={index} className="flex items-center justify-between p-3 bg-glass-bg border border-glass-border rounded">
|
|
<div className="flex items-center space-x-3">
|
|
<Skeleton.Avatar size="sm" />
|
|
<div className="space-y-1">
|
|
<Skeleton.Base className="h-4 w-20" />
|
|
<Skeleton.Base className="h-3 w-16" />
|
|
</div>
|
|
</div>
|
|
<Badge variant="neutral" className="bg-glass-bg">
|
|
<Skeleton.Base className="h-3 w-8" />
|
|
</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
} |