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:
Michilis
2026-07-12 23:14:11 +00:00
co-authored by Cursor
parent d8c207c995
commit 75e317d73e
20 changed files with 397 additions and 57 deletions
+2 -5
View File
@@ -6,6 +6,7 @@ import { ticketsApi, eventsApi, paymentsApi, Ticket, Event } from '@/lib/api';
import { parseDate } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { AdminPageSkeleton } from '@/components/ui/Skeleton';
import { BottomSheet, MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents';
import {
TicketIcon,
@@ -246,11 +247,7 @@ export default function AdminBookingsPage() {
};
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
return <AdminPageSkeleton cols={7} />;
}
return (
+16 -2
View File
@@ -5,6 +5,7 @@ import { useLanguage } from '@/context/LanguageContext';
import { contactsApi, Contact } from '@/lib/api';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { Skeleton, SkeletonText, CardListSkeleton } from '@/components/ui/Skeleton';
import { EnvelopeIcon, EnvelopeOpenIcon, CheckIcon } from '@heroicons/react/24/outline';
import toast from 'react-hot-toast';
import { parseDate } from '@/lib/utils';
@@ -65,8 +66,21 @@ export default function AdminContactsPage() {
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
<div>
<div className="flex items-center justify-between mb-6">
<Skeleton className="h-8 w-48" />
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-1">
<CardListSkeleton count={5} />
</div>
<div className="lg:col-span-2">
<div className="bg-white rounded-card shadow-card p-6">
<Skeleton className="h-6 w-1/3" />
<SkeletonText lines={4} className="mt-6" />
</div>
</div>
</div>
</div>
);
}
+2 -5
View File
@@ -6,6 +6,7 @@ import { emailsApi, EmailTemplate, EmailLog, EmailStats } from '@/lib/api';
import { parseDate } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { AdminPageSkeleton } from '@/components/ui/Skeleton';
import Input from '@/components/ui/Input';
import { MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents';
import {
@@ -418,11 +419,7 @@ export default function AdminEmailsPage() {
};
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
return <AdminPageSkeleton cols={5} />;
}
return (
+7 -2
View File
@@ -8,6 +8,7 @@ import { ticketsApi, emailsApi, adminApi, paymentsApi, siteSettingsApi, Ticket }
import { formatDateLong, formatDateCompact, formatTime } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { Skeleton, TableSkeleton } from '@/components/ui/Skeleton';
import { Dropdown, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents';
import {
ArrowLeftIcon,
@@ -420,8 +421,12 @@ export default function AdminEventDetailPage() {
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
<div>
<div className="flex items-center justify-between mb-6">
<Skeleton className="h-8 w-64" />
<Skeleton className="h-10 w-36 rounded-btn hidden md:block" />
</div>
<TableSkeleton rows={5} cols={4} />
</div>
);
}
+2 -5
View File
@@ -7,6 +7,7 @@ import { useLanguage } from '@/context/LanguageContext';
import { eventsApi, siteSettingsApi, Event } from '@/lib/api';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { AdminPageSkeleton } from '@/components/ui/Skeleton';
import { MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents';
import { PlusIcon, PencilIcon, TrashIcon, EyeIcon, PhotoIcon, DocumentDuplicateIcon, ArchiveBoxIcon, StarIcon, LinkIcon } from '@heroicons/react/24/outline';
import { StarIcon as StarIconSolid } from '@heroicons/react/24/solid';
@@ -148,11 +149,7 @@ export default function AdminEventsPage() {
};
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
return <AdminPageSkeleton cols={5} />;
}
return (
+2 -5
View File
@@ -5,6 +5,7 @@ import { useLanguage } from '@/context/LanguageContext';
import { faqApi, FaqItemAdmin } from '@/lib/api';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { AdminPageSkeleton } from '@/components/ui/Skeleton';
import Input from '@/components/ui/Input';
import { MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents';
import toast from 'react-hot-toast';
@@ -185,11 +186,7 @@ export default function AdminFaqPage() {
const handleDragEnd = () => { setDraggedId(null); setDragOverId(null); };
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
return <AdminPageSkeleton cols={5} />;
}
return (
+7 -2
View File
@@ -6,6 +6,7 @@ import { mediaApi, Media } from '@/lib/api';
import { parseDate } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { Skeleton, ImageGridSkeleton } from '@/components/ui/Skeleton';
import {
PhotoIcon,
TrashIcon,
@@ -126,8 +127,12 @@ export default function AdminGalleryPage() {
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
<div>
<div className="flex items-center justify-between mb-6">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-10 w-36 rounded-btn hidden md:block" />
</div>
<ImageGridSkeleton />
</div>
);
}
+2 -5
View File
@@ -7,6 +7,7 @@ import { legalPagesApi, LegalPage } from '@/lib/api';
import { parseDate } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { AdminPageSkeleton } from '@/components/ui/Skeleton';
import Input from '@/components/ui/Input';
import toast from 'react-hot-toast';
import clsx from 'clsx';
@@ -173,11 +174,7 @@ export default function AdminLegalPagesPage() {
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
return <AdminPageSkeleton cols={5} />;
}
// Editor view
+2 -5
View File
@@ -6,6 +6,7 @@ import { paymentsApi, adminApi, eventsApi, PaymentWithDetails, Event, ExportedPa
import { parseDate } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { AdminPageSkeleton } from '@/components/ui/Skeleton';
import Input from '@/components/ui/Input';
import { BottomSheet, MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents';
import {
@@ -364,11 +365,7 @@ export default function AdminPaymentsPage() {
const onHoldBookingsCount = getUniqueBookingsCount(onHoldPayments);
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
return <AdminPageSkeleton cols={7} />;
}
return (
+2 -5
View File
@@ -6,6 +6,7 @@ import { ticketsApi, eventsApi, Ticket, Event } from '@/lib/api';
import { parseDate } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { AdminPageSkeleton } from '@/components/ui/Skeleton';
import Input from '@/components/ui/Input';
import { BottomSheet, MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents';
import { CheckCircleIcon, XCircleIcon, PlusIcon, FunnelIcon, XMarkIcon } from '@heroicons/react/24/outline';
@@ -135,11 +136,7 @@ export default function AdminTicketsPage() {
};
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
return <AdminPageSkeleton cols={5} />;
}
return (
+2 -5
View File
@@ -6,6 +6,7 @@ import { usersApi, eventsApi, User, Event } from '@/lib/api';
import { parseDate } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { AdminPageSkeleton } from '@/components/ui/Skeleton';
import Input from '@/components/ui/Input';
import { MoreMenu, DropdownItem, BottomSheet, AdminMobileStyles } from '@/components/admin/MobileComponents';
import { TrashIcon, PencilSquareIcon, FunnelIcon, XMarkIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
@@ -166,11 +167,7 @@ export default function AdminUsersPage() {
};
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
return <AdminPageSkeleton cols={6} />;
}
return (