Redesign auth pages and enhance analytics UI with motion
All checks were successful
Build Frontend / build (push) Successful in 1m17s
All checks were successful
Build Frontend / build (push) Successful in 1m17s
Refactored login and registration pages for a modern, consistent look with animated backgrounds and improved form feedback. Enhanced analytics dashboard and metrics cards with framer-motion animations and visual polish. Updated MotionWrapper for flexible motion props and improved transitions. Minor UI/UX improvements and code cleanup throughout auth and analytics components.
This commit is contained in:
@@ -46,6 +46,8 @@ import { DateRangePicker } from "@/components/ui/date-picker";
|
||||
import { DateRange } from "react-day-picker";
|
||||
import { addDays, startOfDay, endOfDay } from "date-fns";
|
||||
import type { DateRange as ProfitDateRange } from "@/lib/services/profit-analytics-service";
|
||||
import { MotionWrapper } from "@/components/ui/motion-wrapper";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
// Lazy load chart components - already handled individually below
|
||||
|
||||
@@ -195,7 +197,7 @@ export default function AnalyticsDashboard({
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-10">
|
||||
{/* Header with Privacy Toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -241,197 +243,215 @@ export default function AnalyticsDashboard({
|
||||
</div>
|
||||
|
||||
{/* Key Metrics Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 lg:gap-6">
|
||||
{isLoading
|
||||
? [...Array(4)].map((_, i) => <MetricsCardSkeleton key={i} />)
|
||||
: metrics.map((metric) => (
|
||||
<MotionWrapper className="space-y-10">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 lg:gap-8">
|
||||
{isLoading
|
||||
? [...Array(4)].map((_, i) => <MetricsCardSkeleton key={i} />)
|
||||
: metrics.map((metric) => (
|
||||
<MetricsCard key={metric.title} {...metric} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Completion Rate Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5" />
|
||||
Order Completion Rate
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Percentage of orders that have been successfully completed
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="h-12 w-16 bg-muted/20 rounded animate-pulse" />
|
||||
<div className="flex-1">
|
||||
<div className="w-full bg-muted/20 rounded-full h-2 animate-pulse" />
|
||||
</div>
|
||||
<div className="h-6 w-16 bg-muted/20 rounded animate-pulse" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-3xl font-bold">
|
||||
{hideNumbers ? "**%" : `${data.orders.completionRate}%`}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="w-full bg-secondary rounded-full h-2">
|
||||
<div
|
||||
className="bg-primary h-2 rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: hideNumbers
|
||||
? "0%"
|
||||
: `${data.orders.completionRate}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="secondary">
|
||||
{hideNumbers
|
||||
? "** / **"
|
||||
: `${data.orders.completed} / ${data.orders.total}`}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Time Period Selector */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Time Period</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Revenue, Profit, and Orders tabs use time filtering. Products and
|
||||
Customers show all-time data.
|
||||
</p>
|
||||
</div>
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="7">Last 7 days</SelectItem>
|
||||
<SelectItem value="30">Last 30 days</SelectItem>
|
||||
<SelectItem value="90">Last 90 days</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Analytics Tabs */}
|
||||
<div className="space-y-6">
|
||||
<Tabs defaultValue="growth" className="space-y-6">
|
||||
<TabsList className="grid w-full grid-cols-2 sm:grid-cols-3 lg:grid-cols-7">
|
||||
<TabsTrigger value="growth" className="flex items-center gap-2">
|
||||
<Activity className="h-4 w-4" />
|
||||
Growth
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="revenue" className="flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Revenue
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="profit" className="flex items-center gap-2">
|
||||
<Calculator className="h-4 w-4" />
|
||||
Profit
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="products" className="flex items-center gap-2">
|
||||
<Package className="h-4 w-4" />
|
||||
Products
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="customers" className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4" />
|
||||
Customers
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="orders" className="flex items-center gap-2">
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
Orders
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="predictions" className="flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Predictions
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="growth" className="space-y-6">
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<GrowthAnalyticsChart hideNumbers={hideNumbers} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="revenue" className="space-y-6">
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<RevenueChart timeRange={timeRange} hideNumbers={hideNumbers} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="profit" className="space-y-6">
|
||||
{/* Date Range Selector for Profit Calculator */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Date Range</CardTitle>
|
||||
<CardDescription>
|
||||
Select a custom date range for profit calculations
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:items-center">
|
||||
<DateRangePicker
|
||||
dateRange={profitDateRange}
|
||||
onDateRangeChange={setProfitDateRange}
|
||||
placeholder="Select date range"
|
||||
showPresets={true}
|
||||
className="w-full sm:w-auto"
|
||||
/>
|
||||
{profitDateRange?.from && profitDateRange?.to && (
|
||||
<div className="text-sm text-muted-foreground flex items-center">
|
||||
<span>
|
||||
{profitDateRange.from.toLocaleDateString()} -{" "}
|
||||
{profitDateRange.to.toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Completion Rate Card */}
|
||||
<motion.div>
|
||||
<Card className="hover:shadow-xl hover:border-indigo-500/30 transition-all duration-300">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5" />
|
||||
Order Completion Rate
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Percentage of orders that have been successfully completed
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="h-12 w-16 bg-muted/20 rounded animate-pulse" />
|
||||
<div className="flex-1">
|
||||
<div className="w-full bg-muted/20 rounded-full h-2 animate-pulse" />
|
||||
</div>
|
||||
<div className="h-6 w-16 bg-muted/20 rounded animate-pulse" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<ProfitAnalyticsChart
|
||||
dateRange={
|
||||
profitDateRange?.from && profitDateRange?.to
|
||||
? {
|
||||
from: profitDateRange.from,
|
||||
to: profitDateRange.to,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
hideNumbers={hideNumbers}
|
||||
/>
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
) : (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-3xl font-bold">
|
||||
{hideNumbers ? "**%" : `${data.orders.completionRate}%`}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="w-full bg-secondary rounded-full h-2">
|
||||
<div
|
||||
className="bg-primary h-2 rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: hideNumbers
|
||||
? "0%"
|
||||
: `${data.orders.completionRate}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="secondary">
|
||||
{hideNumbers
|
||||
? "** / **"
|
||||
: `${data.orders.completed} / ${data.orders.total}`}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<TabsContent value="products" className="space-y-6">
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<ProductPerformanceChart />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
{/* Time Period Selector */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Time Period</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Revenue, Profit, and Orders tabs use time filtering. Products and
|
||||
Customers show all-time data.
|
||||
</p>
|
||||
</div>
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="7">Last 7 days</SelectItem>
|
||||
<SelectItem value="30">Last 30 days</SelectItem>
|
||||
<SelectItem value="90">Last 90 days</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<TabsContent value="customers" className="space-y-6">
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<CustomerInsightsChart />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
{/* Analytics Tabs */}
|
||||
<div className="space-y-8">
|
||||
<Tabs defaultValue="growth" className="space-y-8">
|
||||
<TabsList className="grid w-full grid-cols-2 sm:grid-cols-3 lg:grid-cols-7">
|
||||
<TabsTrigger value="growth" className="flex items-center gap-2">
|
||||
<Activity className="h-4 w-4" />
|
||||
Growth
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="revenue" className="flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Revenue
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="profit" className="flex items-center gap-2">
|
||||
<Calculator className="h-4 w-4" />
|
||||
Profit
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="products" className="flex items-center gap-2">
|
||||
<Package className="h-4 w-4" />
|
||||
Products
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="customers" className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4" />
|
||||
Customers
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="orders" className="flex items-center gap-2">
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
Orders
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="predictions" className="flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Predictions
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="orders" className="space-y-6">
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<OrderAnalyticsChart timeRange={timeRange} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
<TabsContent value="growth" className="space-y-6">
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<GrowthAnalyticsChart hideNumbers={hideNumbers} />
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="predictions" className="space-y-6">
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<PredictionsChart timeRange={parseInt(timeRange)} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<TabsContent value="revenue" className="space-y-6">
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<RevenueChart timeRange={timeRange} hideNumbers={hideNumbers} />
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="profit" className="space-y-6">
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
{/* Date Range Selector for Profit Calculator */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Date Range</CardTitle>
|
||||
<CardDescription>
|
||||
Select a custom date range for profit calculations
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:items-center">
|
||||
<DateRangePicker
|
||||
dateRange={profitDateRange}
|
||||
onDateRangeChange={setProfitDateRange}
|
||||
placeholder="Select date range"
|
||||
showPresets={true}
|
||||
className="w-full sm:w-auto"
|
||||
/>
|
||||
{profitDateRange?.from && profitDateRange?.to && (
|
||||
<div className="text-sm text-muted-foreground flex items-center">
|
||||
<span>
|
||||
{profitDateRange.from.toLocaleDateString()} -{" "}
|
||||
{profitDateRange.to.toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<ProfitAnalyticsChart
|
||||
dateRange={
|
||||
profitDateRange?.from && profitDateRange?.to
|
||||
? {
|
||||
from: profitDateRange.from,
|
||||
to: profitDateRange.to,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
hideNumbers={hideNumbers}
|
||||
/>
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="products" className="space-y-6">
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<ProductPerformanceChart />
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="customers" className="space-y-6">
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<CustomerInsightsChart />
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="orders" className="space-y-6">
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<OrderAnalyticsChart timeRange={timeRange} />
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="predictions" className="space-y-6">
|
||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<PredictionsChart timeRange={parseInt(timeRange)} />
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</MotionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
|
||||
import { LucideIcon } from "lucide-react";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface MetricsCardProps {
|
||||
title: string;
|
||||
@@ -13,13 +14,13 @@ interface MetricsCardProps {
|
||||
trendValue: string;
|
||||
}
|
||||
|
||||
export default function MetricsCard({
|
||||
title,
|
||||
value,
|
||||
description,
|
||||
icon: Icon,
|
||||
trend,
|
||||
trendValue
|
||||
export default function MetricsCard({
|
||||
title,
|
||||
value,
|
||||
description,
|
||||
icon: Icon,
|
||||
trend,
|
||||
trendValue
|
||||
}: MetricsCardProps) {
|
||||
const getTrendIcon = () => {
|
||||
switch (trend) {
|
||||
@@ -44,23 +45,25 @@ export default function MetricsCard({
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
{title}
|
||||
</CardTitle>
|
||||
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{value}</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">{description}</p>
|
||||
<div className="flex items-center gap-1 mt-2">
|
||||
{getTrendIcon()}
|
||||
<span className={`text-xs ${getTrendColor()}`}>
|
||||
{trendValue}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<motion.div>
|
||||
<Card className="hover:shadow-xl hover:border-indigo-500/30 transition-all duration-300">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
{title}
|
||||
</CardTitle>
|
||||
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{value}</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">{description}</p>
|
||||
<div className="flex items-center gap-1 mt-2">
|
||||
{getTrendIcon()}
|
||||
<span className={`text-xs ${getTrendColor()}`}>
|
||||
{trendValue}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user