Replace spinners with skeleton loading states for faster perceived UX.
Add reusable Skeleton components and route-level loading files across public pages and admin lists so layouts stay stable while data loads. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useLanguage } from '@/context/LanguageContext';
|
||||
import { faqApi, FaqItem } from '@/lib/api';
|
||||
import { Skeleton, FaqListSkeleton } from '@/components/ui/Skeleton';
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
||||
import Link from 'next/link';
|
||||
import clsx from 'clsx';
|
||||
@@ -23,7 +24,20 @@ export default function HomepageFaqSection() {
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
|
||||
if (loading || faqs.length === 0) {
|
||||
if (loading) {
|
||||
return (
|
||||
<section className="section-padding bg-secondary-gray" aria-labelledby="homepage-faq-title">
|
||||
<div className="container-page">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<Skeleton className="h-9 w-72 max-w-full mx-auto mb-8" />
|
||||
<FaqListSkeleton count={4} compact />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (faqs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
||||
import { useLanguage } from '@/context/LanguageContext';
|
||||
import { eventsApi, Event } from '@/lib/api';
|
||||
import { formatPrice, formatDateLong, formatTime } from '@/lib/utils';
|
||||
import { FeaturedEventSkeleton } from '@/components/ui/Skeleton';
|
||||
import { CalendarIcon, MapPinIcon, ClockIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
interface NextEventSectionProps {
|
||||
@@ -51,11 +52,7 @@ export default function NextEventSection({ initialEvent }: NextEventSectionProps
|
||||
: '';
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full mx-auto" />
|
||||
</div>
|
||||
);
|
||||
return <FeaturedEventSkeleton />;
|
||||
}
|
||||
|
||||
if (!nextEvent) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
UserPayment,
|
||||
} from '@/lib/api';
|
||||
import toast from 'react-hot-toast';
|
||||
import { CardListSkeleton } from '@/components/ui/Skeleton';
|
||||
|
||||
import OverviewTab from './components/OverviewTab';
|
||||
import TicketsTab from './components/TicketsTab';
|
||||
@@ -104,9 +105,7 @@ export default function DashboardPage() {
|
||||
|
||||
{/* Tab content */}
|
||||
{loading ? (
|
||||
<div className="flex justify-center py-12">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-primary-yellow" />
|
||||
</div>
|
||||
<CardListSkeleton count={3} />
|
||||
) : (
|
||||
<>
|
||||
{activeTab === 'overview' && (
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Skeleton, ArticleSkeleton } from '@/components/ui/Skeleton';
|
||||
|
||||
// Route-level skeleton for the event detail page: back link, banner, article
|
||||
// body on the left and the booking card in the sidebar.
|
||||
export default function EventDetailLoading() {
|
||||
return (
|
||||
<div className="section-padding">
|
||||
<div className="container-page">
|
||||
<Skeleton className="h-5 w-36" />
|
||||
<div className="mt-6 grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2">
|
||||
<ArticleSkeleton withBanner />
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-white rounded-card shadow-card p-6" aria-hidden="true">
|
||||
<div className="text-center">
|
||||
<Skeleton className="h-4 w-16 mx-auto" />
|
||||
<Skeleton className="mt-3 h-10 w-32 mx-auto" />
|
||||
</div>
|
||||
<Skeleton className="mt-6 h-12 w-full rounded-btn" />
|
||||
<Skeleton className="mt-3 h-4 w-3/4 mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Skeleton, EventGridSkeleton } from '@/components/ui/Skeleton';
|
||||
|
||||
// Route-level skeleton shown during navigation while the server fetches the
|
||||
// event list. Mirrors the EventsClient shell: title, filter tabs, card grid.
|
||||
export default function EventsLoading() {
|
||||
return (
|
||||
<div className="section-padding">
|
||||
<div className="container-page">
|
||||
<Skeleton className="h-10 w-64" />
|
||||
<div className="mt-8 flex gap-2">
|
||||
<Skeleton className="h-10 w-32 rounded-btn" />
|
||||
<Skeleton className="h-10 w-28 rounded-btn" />
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
<EventGridSkeleton count={6} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Skeleton, FaqListSkeleton } from '@/components/ui/Skeleton';
|
||||
|
||||
// Route-level skeleton for the FAQ page: centered header + accordion rows.
|
||||
export default function FaqLoading() {
|
||||
return (
|
||||
<div className="section-padding">
|
||||
<div className="container-page max-w-3xl">
|
||||
<div className="text-center mb-12">
|
||||
<Skeleton className="h-10 w-80 max-w-full mx-auto" />
|
||||
<Skeleton className="mt-4 h-4 w-96 max-w-full mx-auto" />
|
||||
</div>
|
||||
<FaqListSkeleton count={6} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Skeleton, SkeletonGroup, SkeletonText } from '@/components/ui/Skeleton';
|
||||
|
||||
// Route-level skeleton for legal pages: back link, bordered header, prose body.
|
||||
export default function LegalPageLoading() {
|
||||
return (
|
||||
<div className="section-padding">
|
||||
<div className="container-page max-w-4xl">
|
||||
<SkeletonGroup>
|
||||
<Skeleton className="h-5 w-32 mb-8" />
|
||||
<div className="mb-8 pb-6 border-b border-gray-200">
|
||||
<Skeleton className="h-10 w-2/3" />
|
||||
<Skeleton className="mt-3 h-4 w-44" />
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<SkeletonText lines={4} />
|
||||
<Skeleton className="h-6 w-1/3" />
|
||||
<SkeletonText lines={5} />
|
||||
<Skeleton className="h-6 w-2/5" />
|
||||
<SkeletonText lines={4} />
|
||||
</div>
|
||||
</SkeletonGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user