Enhance admin dashboard UI and tables with new styles
All checks were successful
Build Frontend / build (push) Successful in 1m4s
All checks were successful
Build Frontend / build (push) Successful in 1m4s
Refactors admin dashboard, users, vendors, shipping, and stock pages to improve UI consistency and visual clarity. Adds new icons, animated transitions, and card styles for stats and tables. Updates table row rendering with framer-motion for smooth animations, improves badge and button styling, and enhances search/filter inputs. Refines loading skeletons and overall layout for a more modern, accessible admin experience.
This commit is contained in:
@@ -37,7 +37,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error(`Error loading ${this.props.componentName || 'component'}:`, error, errorInfo);
|
||||
|
||||
|
||||
// Log to error tracking service if available
|
||||
if (typeof window !== 'undefined' && (window as any).gtag) {
|
||||
(window as any).gtag('event', 'exception', {
|
||||
@@ -105,31 +105,31 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
}
|
||||
|
||||
// Lazy load admin components with error handling
|
||||
const AdminAnalytics = lazy(() =>
|
||||
const AdminAnalytics = lazy(() =>
|
||||
import("@/components/admin/AdminAnalytics").catch((err) => {
|
||||
console.error("Failed to load AdminAnalytics:", err);
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
const InviteVendorCard = lazy(() =>
|
||||
const InviteVendorCard = lazy(() =>
|
||||
import("@/components/admin/InviteVendorCard").catch((err) => {
|
||||
console.error("Failed to load InviteVendorCard:", err);
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
const BanUserCard = lazy(() =>
|
||||
const BanUserCard = lazy(() =>
|
||||
import("@/components/admin/BanUserCard").catch((err) => {
|
||||
console.error("Failed to load BanUserCard:", err);
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
const InvitationsListCard = lazy(() =>
|
||||
const InvitationsListCard = lazy(() =>
|
||||
import("@/components/admin/InvitationsListCard").catch((err) => {
|
||||
console.error("Failed to load InvitationsListCard:", err);
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
const VendorsCard = lazy(() =>
|
||||
const VendorsCard = lazy(() =>
|
||||
import("@/components/admin/VendorsCard").catch((err) => {
|
||||
console.error("Failed to load VendorsCard:", err);
|
||||
throw err;
|
||||
@@ -139,7 +139,7 @@ const VendorsCard = lazy(() =>
|
||||
// Loading skeleton with timeout warning
|
||||
function AdminComponentSkeleton({ showSlowWarning = false }: { showSlowWarning?: boolean }) {
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
className="space-y-6 animate-in fade-in duration-500 relative"
|
||||
role="status"
|
||||
aria-label="Loading analytics dashboard"
|
||||
@@ -156,7 +156,7 @@ function AdminComponentSkeleton({ showSlowWarning = false }: { showSlowWarning?:
|
||||
)}
|
||||
{/* Subtle loading indicator */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-muted overflow-hidden rounded-full">
|
||||
<div className="h-full bg-primary w-1/3 animate-[shimmer_2s_ease-in-out_infinite]"
|
||||
<div className="h-full bg-primary w-1/3 animate-[shimmer_2s_ease-in-out_infinite]"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, transparent, hsl(var(--primary)), transparent)',
|
||||
backgroundSize: '200% 100%',
|
||||
@@ -164,7 +164,7 @@ function AdminComponentSkeleton({ showSlowWarning = false }: { showSlowWarning?:
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Header skeleton */}
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="space-y-2">
|
||||
@@ -181,9 +181,9 @@ function AdminComponentSkeleton({ showSlowWarning = false }: { showSlowWarning?:
|
||||
{/* Metric cards grid skeleton */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
|
||||
<Card
|
||||
key={i}
|
||||
className="overflow-hidden animate-in fade-in slide-in-from-bottom-4"
|
||||
<Card
|
||||
key={i}
|
||||
className="overflow-hidden animate-in fade-in slide-in-from-bottom-4 border-border/40 bg-background/50 backdrop-blur-sm shadow-sm"
|
||||
style={{
|
||||
animationDelay: `${i * 50}ms`,
|
||||
animationDuration: '400ms',
|
||||
@@ -227,14 +227,14 @@ function AdminComponentSkeleton({ showSlowWarning = false }: { showSlowWarning?:
|
||||
}
|
||||
|
||||
// Suspense wrapper with timeout
|
||||
function SuspenseWithTimeout({
|
||||
children,
|
||||
fallback,
|
||||
function SuspenseWithTimeout({
|
||||
children,
|
||||
fallback,
|
||||
timeout = 5000,
|
||||
timeoutFallback
|
||||
}: {
|
||||
children: ReactNode;
|
||||
fallback: ReactNode;
|
||||
timeoutFallback
|
||||
}: {
|
||||
children: ReactNode;
|
||||
fallback: ReactNode;
|
||||
timeout?: number;
|
||||
timeoutFallback?: ReactNode;
|
||||
}) {
|
||||
@@ -258,7 +258,7 @@ function SuspenseWithTimeout({
|
||||
// Loading skeleton for management cards
|
||||
function ManagementCardsSkeleton({ showSlowWarning = false }: { showSlowWarning?: boolean }) {
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
className="grid gap-4 lg:gap-6 sm:grid-cols-2 lg:grid-cols-3 items-stretch relative"
|
||||
role="status"
|
||||
aria-label="Loading management tools"
|
||||
@@ -277,7 +277,7 @@ function ManagementCardsSkeleton({ showSlowWarning = false }: { showSlowWarning?
|
||||
)}
|
||||
{/* Subtle loading indicator */}
|
||||
<div className="absolute -top-6 left-0 right-0 h-1 bg-muted overflow-hidden rounded-full">
|
||||
<div className="h-full bg-primary w-1/3"
|
||||
<div className="h-full bg-primary w-1/3"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, transparent, hsl(var(--primary)), transparent)',
|
||||
backgroundSize: '200% 100%',
|
||||
@@ -286,9 +286,9 @@ function ManagementCardsSkeleton({ showSlowWarning = false }: { showSlowWarning?
|
||||
/>
|
||||
</div>
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<Card
|
||||
key={i}
|
||||
className="overflow-hidden animate-in fade-in slide-in-from-bottom-4"
|
||||
<Card
|
||||
key={i}
|
||||
className="overflow-hidden animate-in fade-in slide-in-from-bottom-4 border-border/40 bg-background/50 backdrop-blur-sm shadow-sm"
|
||||
style={{
|
||||
animationDelay: `${i * 75}ms`,
|
||||
animationDuration: '400ms',
|
||||
@@ -311,8 +311,8 @@ function ManagementCardsSkeleton({ showSlowWarning = false }: { showSlowWarning?
|
||||
/* List items skeleton for list cards */
|
||||
<>
|
||||
{[1, 2, 3].map((j) => (
|
||||
<div
|
||||
key={j}
|
||||
<div
|
||||
key={j}
|
||||
className="space-y-2 p-3 rounded border border-border/50 animate-in fade-in"
|
||||
style={{
|
||||
animationDelay: `${(i - 2) * 75 + j * 50}ms`,
|
||||
@@ -345,9 +345,9 @@ export default function AdminPage() {
|
||||
const prefetchTabComponents = (tab: string) => {
|
||||
// Avoid prefetching if already done
|
||||
if (prefetchedTabs.has(tab)) return;
|
||||
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
|
||||
if (tab === "analytics") {
|
||||
// Prefetch analytics component
|
||||
import("@/components/admin/AdminAnalytics")
|
||||
@@ -355,7 +355,7 @@ export default function AdminPage() {
|
||||
const loadTime = performance.now() - startTime;
|
||||
console.log(`[Performance] AdminAnalytics prefetched in ${loadTime.toFixed(2)}ms`);
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
} else if (tab === "management") {
|
||||
// Prefetch management components
|
||||
Promise.all([
|
||||
@@ -368,9 +368,9 @@ export default function AdminPage() {
|
||||
const loadTime = performance.now() - startTime;
|
||||
console.log(`[Performance] Management components prefetched in ${loadTime.toFixed(2)}ms`);
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
}
|
||||
|
||||
|
||||
setPrefetchedTabs(prev => new Set(prev).add(tab));
|
||||
};
|
||||
|
||||
@@ -392,26 +392,26 @@ export default function AdminPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between animate-in fade-in slide-in-from-top-2 duration-300">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Admin Dashboard</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight text-foreground">Admin Dashboard</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">Platform analytics and vendor management</p>
|
||||
</div>
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Button asChild variant="outline" size="sm" className="border-border/50 bg-background/50 backdrop-blur-sm hover:bg-background/80 transition-all">
|
||||
<Link href="/dashboard">Back to Dashboard</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
||||
<TabsList>
|
||||
<TabsTrigger
|
||||
<TabsTrigger
|
||||
value="analytics"
|
||||
onMouseEnter={() => handleTabHover("analytics")}
|
||||
onFocus={() => handleTabFocus("analytics")}
|
||||
>
|
||||
Analytics
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
<TabsTrigger
|
||||
value="management"
|
||||
onMouseEnter={() => handleTabHover("management")}
|
||||
onFocus={() => handleTabFocus("management")}
|
||||
@@ -422,7 +422,7 @@ export default function AdminPage() {
|
||||
|
||||
<TabsContent value="analytics" className="space-y-6 relative">
|
||||
<ErrorBoundary componentName="Analytics Dashboard">
|
||||
<SuspenseWithTimeout
|
||||
<SuspenseWithTimeout
|
||||
fallback={<AdminComponentSkeleton />}
|
||||
timeout={5000}
|
||||
timeoutFallback={<AdminComponentSkeleton showSlowWarning={true} />}
|
||||
@@ -436,7 +436,7 @@ export default function AdminPage() {
|
||||
|
||||
<TabsContent value="management" className="space-y-6 relative">
|
||||
<ErrorBoundary componentName="Management Tools">
|
||||
<SuspenseWithTimeout
|
||||
<SuspenseWithTimeout
|
||||
fallback={<ManagementCardsSkeleton />}
|
||||
timeout={5000}
|
||||
timeoutFallback={<ManagementCardsSkeleton showSlowWarning={true} />}
|
||||
|
||||
Reference in New Issue
Block a user