mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 11:31:14 -05:00
feat: Implement skeleton loading
This commit is contained in:
@@ -23,6 +23,7 @@ import { ReassignDialog } from './ReassignDialog';
|
|||||||
import { smartMergeArray } from '@/lib/smartStateUpdate';
|
import { smartMergeArray } from '@/lib/smartStateUpdate';
|
||||||
import { useDebounce } from '@/hooks/useDebounce';
|
import { useDebounce } from '@/hooks/useDebounce';
|
||||||
import { QueueItem } from './QueueItem';
|
import { QueueItem } from './QueueItem';
|
||||||
|
import { QueueSkeleton } from './QueueSkeleton';
|
||||||
|
|
||||||
interface ModerationItem {
|
interface ModerationItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -1678,11 +1679,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
|
|
||||||
const QueueContent = () => {
|
const QueueContent = () => {
|
||||||
if (isInitialLoad && loading) {
|
if (isInitialLoad && loading) {
|
||||||
return (
|
return <QueueSkeleton count={5} />;
|
||||||
<div className="flex items-center justify-center p-8">
|
|
||||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
|
|||||||
42
src/components/moderation/QueueItemSkeleton.tsx
Normal file
42
src/components/moderation/QueueItemSkeleton.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Card, CardHeader, CardContent } from '@/components/ui/card';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
|
||||||
|
export function QueueItemSkeleton() {
|
||||||
|
return (
|
||||||
|
<Card className="border-l-4 border-l-muted">
|
||||||
|
<CardHeader className="pb-4">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
{/* Left side: Entity type badge + title */}
|
||||||
|
<div className="flex-1 space-y-3">
|
||||||
|
<Skeleton className="h-5 w-24" /> {/* Badge */}
|
||||||
|
<Skeleton className="h-6 w-3/4" /> {/* Title */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Skeleton className="h-4 w-4 rounded-full" /> {/* Avatar */}
|
||||||
|
<Skeleton className="h-4 w-32" /> {/* Username */}
|
||||||
|
<Skeleton className="h-4 w-24" /> {/* Date */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right side: Status badge */}
|
||||||
|
<Skeleton className="h-6 w-20" />
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{/* Content area */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Skeleton className="h-4 w-full" />
|
||||||
|
<Skeleton className="h-4 w-5/6" />
|
||||||
|
<Skeleton className="h-4 w-4/6" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action buttons */}
|
||||||
|
<div className="flex gap-2 pt-4">
|
||||||
|
<Skeleton className="h-9 w-24" />
|
||||||
|
<Skeleton className="h-9 w-24" />
|
||||||
|
<Skeleton className="h-9 w-24" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
15
src/components/moderation/QueueSkeleton.tsx
Normal file
15
src/components/moderation/QueueSkeleton.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { QueueItemSkeleton } from './QueueItemSkeleton';
|
||||||
|
|
||||||
|
interface QueueSkeletonProps {
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QueueSkeleton({ count = 5 }: QueueSkeletonProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
{Array.from({ length: count }).map((_, i) => (
|
||||||
|
<QueueItemSkeleton key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ import { useModerationStats } from '@/hooks/useModerationStats';
|
|||||||
import { useAdminSettings } from '@/hooks/useAdminSettings';
|
import { useAdminSettings } from '@/hooks/useAdminSettings';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { QueueSkeleton } from '@/components/moderation/QueueSkeleton';
|
||||||
|
|
||||||
export default function AdminDashboard() {
|
export default function AdminDashboard() {
|
||||||
const { user, loading: authLoading } = useAuth();
|
const { user, loading: authLoading } = useAuth();
|
||||||
@@ -110,14 +112,37 @@ export default function AdminDashboard() {
|
|||||||
|
|
||||||
if (authLoading || roleLoading) {
|
if (authLoading || roleLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<AdminLayout>
|
||||||
<div className="flex items-center justify-center min-h-[400px]">
|
<div className="space-y-6">
|
||||||
<div className="text-center">
|
<div>
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
<h1 className="text-2xl font-bold tracking-tight">Admin Dashboard</h1>
|
||||||
<p className="text-muted-foreground">Loading admin panel...</p>
|
<p className="text-muted-foreground mt-1">Central hub for all moderation activity</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Skeleton for stat cards */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
{[1, 2, 3].map((i) => (
|
||||||
|
<Card key={i}>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Skeleton className="h-11 w-11 rounded-lg" />
|
||||||
|
<Skeleton className="h-4 w-32" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-10 w-16" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Skeleton for tabs */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Skeleton className="h-12 w-full" />
|
||||||
|
<QueueSkeleton count={3} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useUserRole } from '@/hooks/useUserRole';
|
|||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { AdminLayout } from '@/components/layout/AdminLayout';
|
import { AdminLayout } from '@/components/layout/AdminLayout';
|
||||||
import { ModerationQueue, ModerationQueueRef } from '@/components/moderation/ModerationQueue';
|
import { ModerationQueue, ModerationQueueRef } from '@/components/moderation/ModerationQueue';
|
||||||
|
import { QueueSkeleton } from '@/components/moderation/QueueSkeleton';
|
||||||
import { useAdminSettings } from '@/hooks/useAdminSettings';
|
import { useAdminSettings } from '@/hooks/useAdminSettings';
|
||||||
import { useModerationStats } from '@/hooks/useModerationStats';
|
import { useModerationStats } from '@/hooks/useModerationStats';
|
||||||
|
|
||||||
@@ -47,14 +48,23 @@ export default function AdminModeration() {
|
|||||||
|
|
||||||
if (authLoading || roleLoading) {
|
if (authLoading || roleLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<AdminLayout
|
||||||
<div className="flex items-center justify-center min-h-[400px]">
|
onRefresh={handleRefresh}
|
||||||
<div className="text-center">
|
refreshMode={refreshMode}
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
pollInterval={pollInterval}
|
||||||
<p className="text-muted-foreground">Loading moderation queue...</p>
|
lastUpdated={lastUpdated}
|
||||||
|
>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight">Moderation Queue</h1>
|
||||||
|
<p className="text-muted-foreground mt-1">
|
||||||
|
Review and manage pending content submissions
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<QueueSkeleton count={5} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useUserRole } from '@/hooks/useUserRole';
|
|||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { AdminLayout } from '@/components/layout/AdminLayout';
|
import { AdminLayout } from '@/components/layout/AdminLayout';
|
||||||
import { ReportsQueue, ReportsQueueRef } from '@/components/moderation/ReportsQueue';
|
import { ReportsQueue, ReportsQueueRef } from '@/components/moderation/ReportsQueue';
|
||||||
|
import { QueueSkeleton } from '@/components/moderation/QueueSkeleton';
|
||||||
import { useAdminSettings } from '@/hooks/useAdminSettings';
|
import { useAdminSettings } from '@/hooks/useAdminSettings';
|
||||||
import { useModerationStats } from '@/hooks/useModerationStats';
|
import { useModerationStats } from '@/hooks/useModerationStats';
|
||||||
|
|
||||||
@@ -48,14 +49,23 @@ export default function AdminReports() {
|
|||||||
|
|
||||||
if (authLoading || roleLoading) {
|
if (authLoading || roleLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<AdminLayout
|
||||||
<div className="flex items-center justify-center min-h-[400px]">
|
onRefresh={handleRefresh}
|
||||||
<div className="text-center">
|
refreshMode={refreshMode}
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
pollInterval={pollInterval}
|
||||||
<p className="text-muted-foreground">Loading reports...</p>
|
lastUpdated={lastUpdated}
|
||||||
|
>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight">User Reports</h1>
|
||||||
|
<p className="text-muted-foreground mt-1">
|
||||||
|
Review and resolve user-submitted reports
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<QueueSkeleton count={5} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { useAuth } from '@/hooks/useAuth';
|
|||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
import { AdminLayout } from '@/components/layout/AdminLayout';
|
import { AdminLayout } from '@/components/layout/AdminLayout';
|
||||||
import { SystemActivityLog } from '@/components/admin/SystemActivityLog';
|
import { SystemActivityLog } from '@/components/admin/SystemActivityLog';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
|
||||||
export default function AdminSystemLog() {
|
export default function AdminSystemLog() {
|
||||||
const { user, loading: authLoading } = useAuth();
|
const { user, loading: authLoading } = useAuth();
|
||||||
@@ -26,14 +28,30 @@ export default function AdminSystemLog() {
|
|||||||
|
|
||||||
if (authLoading || roleLoading) {
|
if (authLoading || roleLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<AdminLayout>
|
||||||
<div className="flex items-center justify-center min-h-[400px]">
|
<div className="space-y-6">
|
||||||
<div className="text-center">
|
<div>
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
<h1 className="text-2xl font-bold tracking-tight">System Activity Log</h1>
|
||||||
<p className="text-muted-foreground">Loading system log...</p>
|
<p className="text-muted-foreground mt-1">
|
||||||
|
Complete audit trail of all system changes and administrative actions
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-6 space-y-3">
|
||||||
|
{[1, 2, 3, 4, 5].map((i) => (
|
||||||
|
<div key={i} className="flex items-start gap-4 p-3 border-l-2 border-l-muted">
|
||||||
|
<div className="flex-1 space-y-2">
|
||||||
|
<Skeleton className="h-4 w-2/3" />
|
||||||
|
<Skeleton className="h-3 w-1/2" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-3 w-24" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { useUserRole } from '@/hooks/useUserRole';
|
|||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { AdminLayout } from '@/components/layout/AdminLayout';
|
import { AdminLayout } from '@/components/layout/AdminLayout';
|
||||||
import { UserManagement } from '@/components/admin/UserManagement';
|
import { UserManagement } from '@/components/admin/UserManagement';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
|
||||||
export default function AdminUsers() {
|
export default function AdminUsers() {
|
||||||
const { user, loading: authLoading } = useAuth();
|
const { user, loading: authLoading } = useAuth();
|
||||||
@@ -26,14 +28,31 @@ export default function AdminUsers() {
|
|||||||
|
|
||||||
if (authLoading || roleLoading) {
|
if (authLoading || roleLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<AdminLayout>
|
||||||
<div className="flex items-center justify-center min-h-[400px]">
|
<div className="space-y-6">
|
||||||
<div className="text-center">
|
<div>
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
<h1 className="text-2xl font-bold tracking-tight">User Management</h1>
|
||||||
<p className="text-muted-foreground">Loading user management...</p>
|
<p className="text-muted-foreground mt-1">
|
||||||
|
Manage user profiles, roles, and permissions
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-6 space-y-4">
|
||||||
|
{[1, 2, 3, 4].map((i) => (
|
||||||
|
<div key={i} className="flex items-center gap-4 p-4 border rounded-lg">
|
||||||
|
<Skeleton className="h-10 w-10 rounded-full" />
|
||||||
|
<div className="flex-1 space-y-2">
|
||||||
|
<Skeleton className="h-4 w-48" />
|
||||||
|
<Skeleton className="h-3 w-32" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-8 w-24" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user