Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions frontend/src/components/skeletons/AdminStatsSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

export const AdminStatsSkeleton: React.FC = () => {
return (
<div className="grid md:grid-cols-3 gap-6">
<div className="bg-gradient-to-br from-green-500 to-green-600 rounded-2xl p-6 text-white shadow-xl animate-pulse">
<div className="flex items-center justify-between">
<div className="space-y-2">
<div className="h-4 w-24 bg-green-200/40 rounded"></div>
<div className="h-9 w-20 bg-green-200/30 rounded"></div>
</div>
<div className="h-12 w-12 bg-green-200/20 rounded-full"></div>
</div>
<div className="mt-4 flex items-center gap-2">
<div className="h-3 w-3 bg-white/20 rounded"></div>
<div className="h-4 w-24 bg-white/20 rounded"></div>
</div>
</div>
<div className="bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-2xl p-6 text-white shadow-xl animate-pulse">
<div className="flex items-center justify-between">
<div className="space-y-2">
<div className="h-4 w-28 bg-indigo-200/40 rounded"></div>
<div className="h-9 w-20 bg-indigo-200/30 rounded"></div>
</div>
<div className="h-12 w-12 bg-indigo-200/20 rounded-full"></div>
</div>
<div className="mt-4 flex items-center gap-2">
<div className="h-3 w-3 bg-white/20 rounded"></div>
<div className="h-4 w-32 bg-white/20 rounded"></div>
</div>
</div>
<div className="bg-gradient-to-br from-red-500 to-red-600 rounded-2xl p-6 text-white shadow-xl animate-pulse">
<div className="flex items-center justify-between">
<div className="space-y-2">
<div className="h-4 w-24 bg-red-200/40 rounded"></div>
<div className="h-9 w-20 bg-red-200/30 rounded"></div>
</div>
<div className="h-12 w-12 bg-red-200/20 rounded-full"></div>
</div>
<div className="mt-4 flex items-center gap-2">
<div className="h-3 w-3 bg-white/20 rounded"></div>
<div className="h-4 w-20 bg-white/20 rounded"></div>
</div>
</div>
</div>
);
};

export default AdminStatsSkeleton;
67 changes: 67 additions & 0 deletions frontend/src/components/skeletons/AdminTableSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';

interface AdminTableSkeletonProps {
rows?: number;
}

const AdminTableSkeleton: React.FC<AdminTableSkeletonProps> = ({ rows = 5 }) => {
return (
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-6 animate-pulse">
<div className="flex items-center gap-3 mb-6">
<div className="h-6 w-6 bg-gray-300 dark:bg-gray-700 rounded"></div>
<div className="h-7 bg-gray-300 dark:bg-gray-700 rounded w-40"></div>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700">
{['Batch ID', 'Farmer', 'Crop Type', 'Quantity', 'Stage', 'Tx Value', 'Status'].map((col) => (
<th key={col} className="text-left py-4 px-6 font-semibold text-gray-500 dark:text-gray-400 text-sm">
<div className="h-4 bg-gray-300 dark:bg-gray-700 rounded w-20"></div>
</th>
))}
</tr>
</thead>
<tbody>
{Array.from({ length: rows }).map((_, rowIdx) => (
<tr key={rowIdx} className="border-b border-gray-100 dark:border-gray-700">
<td className="py-4 px-6">
<div className="flex items-center gap-2">
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded w-24"></div>
<div className="h-4 w-4 bg-gray-200 dark:bg-gray-700 rounded"></div>
</div>
</td>
<td className="py-4 px-6">
<div className="space-y-1">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-24"></div>
<div className="h-3 bg-gray-200 dark:bg-gray-600 rounded w-16"></div>
</div>
</td>
<td className="py-4 px-6">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-16"></div>
</td>
<td className="py-4 px-6">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-16"></div>
</td>
<td className="py-4 px-6">
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded-full w-14"></div>
</td>
<td className="py-4 px-6">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-16"></div>
</td>
<td className="py-4 px-6">
<div className="flex items-center gap-2">
<div className="h-2 w-2 bg-green-400 rounded-full"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-12"></div>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};

export default AdminTableSkeleton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';

const VerificationStatsSkeleton: React.FC<{ color: 'orange' | 'green'; label: string }> = ({ color, label }) => {
const bgClass = color === 'orange' ? 'from-orange-500 to-orange-600' : 'from-green-500 to-green-600';
const iconBgClass = color === 'orange' ? 'bg-orange-100 dark:bg-orange-900/50' : 'bg-green-100 dark:bg-green-900/50';
const iconTextClass = color === 'orange' ? 'text-orange-500' : 'text-green-500';

return (
<div className={`bg-gradient-to-br ${bgClass} rounded-xl p-6 text-white shadow-lg animate-pulse`}>
<div className="flex items-center justify-between">
<div className="space-y-2">
<div className="h-4 w-32 bg-white/20 rounded"></div>
<div className="h-9 w-12 bg-white/20 rounded"></div>
</div>
<div className={`h-12 w-12 ${iconBgClass} rounded-full flex items-center justify-center`}>
<div className={`h-6 w-6 ${iconTextClass} opacity-50`}></div>
</div>
</div>
</div>
);
};

const VerificationTableSkeleton: React.FC = () => {
return (
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 dark:bg-gray-700">
<tr>
{['User', 'Role', 'Wallet', 'Status', 'Actions'].map((col) => (
<th key={col} className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-16"></div>
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
{[1, 2, 3, 4, 5].map((row) => (
<tr key={row}>
<td className="px-6 py-4 whitespace-nowrap">
<div className="space-y-1">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-24"></div>
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-32"></div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="px-2 py-1 h-5 bg-gray-200 dark:bg-gray-700 rounded-full w-12"></div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-20"></div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="h-5 w-20 bg-gray-200 dark:bg-gray-700 rounded-full"></div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-16"></div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};

export const VerificationDashboardSkeleton: React.FC = () => {
return (
<div className="space-y-8 animate-pulse">
{/* Header */}
<div>
<div className="flex items-center gap-3 mb-2">
<div className="h-8 w-8 bg-gray-300 dark:bg-gray-700 rounded"></div>
<div className="h-8 bg-gray-300 dark:bg-gray-700 rounded w-64"></div>
</div>
<div className="h-4 bg-gray-300 dark:bg-gray-700 rounded w-80"></div>
</div>

{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<VerificationStatsSkeleton color="orange" label="Unverified Users" />
<VerificationStatsSkeleton color="green" label="Verified Users" />
</div>

{/* Tabs */}
<div className="flex gap-4">
{[1, 2].map((i) => (
<div key={i} className="h-10 bg-gray-200 dark:bg-gray-700 rounded-lg w-32"></div>
))}
</div>

{/* Table */}
<VerificationTableSkeleton />
</div>
);
};

export default VerificationDashboardSkeleton;
15 changes: 9 additions & 6 deletions frontend/src/components/skeletons/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export { default as FormSkeleton } from './FormSkeleton';
export { default as StatsCardSkeleton } from './StatsCardSkeleton';
export { default as TableSkeleton } from './TableSkeleton';
export { default as TrackBatchSkeleton } from './TrackBatchSkeleton';
export { default as ChartSkeleton } from './ChartSkeleton';
export { default as BatchInfoSkeleton } from './BatchInfoSkeleton';
export { default as BatchInfoSkeleton } from './BatchInfoSkeleton';
export { default as ChartSkeleton } from './ChartSkeleton';
export { default as FormSkeleton } from './FormSkeleton';
export { default as StatsCardSkeleton } from './StatsCardSkeleton';
export { default as TableSkeleton } from './TableSkeleton';
export { default as TrackBatchSkeleton } from './TrackBatchSkeleton';
export { default as AdminStatsSkeleton } from './AdminStatsSkeleton';
export { default as AdminTableSkeleton } from './AdminTableSkeleton';
export { VerificationDashboardSkeleton } from './VerificationDashboardSkeleton';
24 changes: 14 additions & 10 deletions frontend/src/pages/AdminDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import React, { useState, useEffect } from 'react';
import { Shield, Package, Coins, Activity, TrendingUp, Check, Copy } from 'lucide-react';
import { realCropBatchService } from '../services/realCropBatchService';
import { usePriceConverter } from '../hooks/usePriceConverter';
import { AdminStatsSkeleton } from '../components/skeletons';
import AdminTableSkeleton from '../components/skeletons/AdminTableSkeleton';
import ChartSkeleton from '../components/skeletons/ChartSkeleton';

const AdminDashboard: React.FC = () => {
const [stats, setStats] = useState({
Expand Down Expand Up @@ -52,18 +55,19 @@ const AdminDashboard: React.FC = () => {

if (isLoading) {
return (
<div className="max-w-7xl mx-auto space-y-8">
<div className="max-w-7xl mx-auto space-y-8 animate-pulse">
<div className="text-center">
<div className="h-12 bg-gray-300 dark:bg-gray-700 rounded w-64 mx-auto mb-4 animate-pulse"></div>
<div className="h-6 bg-gray-300 dark:bg-gray-700 rounded w-96 mx-auto animate-pulse"></div>
<div className="h-12 bg-gray-300 dark:bg-gray-700 rounded w-64 mx-auto mb-4"></div>
<div className="h-6 bg-gray-300 dark:bg-gray-700 rounded w-96 mx-auto"></div>
</div>
<div className="grid md:grid-cols-3 gap-6">
{[1, 2, 3].map((i) => (
<div key={i} className="bg-white dark:bg-gray-800 rounded-2xl p-6 animate-pulse">
<div className="h-20 bg-gray-300 dark:bg-gray-700 rounded mb-4"></div>
<div className="h-8 bg-gray-300 dark:bg-gray-700 rounded"></div>
</div>
))}

<AdminStatsSkeleton />

<AdminTableSkeleton rows={5} />

<div className="grid md:grid-cols-2 gap-6">
<ChartSkeleton />
<ChartSkeleton />
</div>
</div>
);
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/pages/VerificationDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { motion } from 'framer-motion';
import { Shield, UserCheck, UserX, AlertCircle } from 'lucide-react';
import { verificationService, UnverifiedUser, VerifiedUser } from '../services/verificationService';
import VerificationBadge from '../components/VerificationBadge';
import { VerificationDashboardSkeleton } from '../components/skeletons';
import { useAuth } from '../context/AuthContext';
import { useToast } from '../context/ToastContext';

Expand Down Expand Up @@ -176,10 +177,8 @@ const VerificationDashboard: React.FC = () => {

{/* Loading */}
{loading ? (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto"></div>
</div>
) : (
<VerificationDashboardSkeleton />
) : (
<>
{/* Unverified Users */}
{activeTab === 'unverified' && (
Expand Down