feat: rustic dashboard redesign
This commit is contained in:
@@ -1,7 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
ArrowRight,
|
||||
BadgeDollarSign,
|
||||
Bird,
|
||||
Clock3,
|
||||
Loader2,
|
||||
PartyPopper,
|
||||
RefreshCw,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -27,6 +37,35 @@ interface WalletData {
|
||||
totalSpent: number;
|
||||
}
|
||||
|
||||
function StatTile({
|
||||
label,
|
||||
value,
|
||||
hint,
|
||||
icon: Icon,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
hint: string;
|
||||
icon: any;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-primary/10 bg-card/80 p-4 shadow-[0_12px_30px_-20px_rgba(0,0,0,0.45)] backdrop-blur-sm">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-xs uppercase tracking-[0.25em] text-muted-foreground">
|
||||
{label}
|
||||
</div>
|
||||
<div className="mt-2 text-3xl font-bold text-foreground">{value}</div>
|
||||
<div className="mt-1 text-sm text-muted-foreground">{hint}</div>
|
||||
</div>
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-primary/10 text-primary">
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const router = useRouter();
|
||||
const { isAuthenticated, logout } = useStore();
|
||||
@@ -79,100 +118,192 @@ export default function DashboardPage() {
|
||||
logout();
|
||||
router.push("/login");
|
||||
};
|
||||
const handleRefresh = () => {
|
||||
loadWalletAndPending();
|
||||
syncPending();
|
||||
};
|
||||
|
||||
if (isLoading) return <div className="p-8">Loading...</div>;
|
||||
if (isLoading)
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-6">
|
||||
<div className="flex items-center justify-end">
|
||||
<Button variant="outline" onClick={handleLogout}>
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Coop</CardTitle>
|
||||
<CardDescription>DB-only credits</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground">Nest Egg</div>
|
||||
<div className="text-3xl font-bold">
|
||||
{formatNumber(wallet?.balance || 0)} $COOP
|
||||
<div className="relative min-h-screen overflow-hidden px-4 py-6 sm:px-6 lg:px-8">
|
||||
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(185,28,28,0.12),_transparent_30%),radial-gradient(circle_at_top_right,_rgba(217,119,6,0.10),_transparent_28%),radial-gradient(circle_at_bottom,_rgba(120,53,15,0.06),_transparent_40%)]" />
|
||||
<div className="mx-auto max-w-7xl space-y-6">
|
||||
<div className="overflow-hidden rounded-[2rem] border border-primary/15 bg-gradient-to-br from-primary via-primary to-amber-700 text-primary-foreground shadow-[0_30px_80px_-40px_rgba(127,29,29,0.7)]">
|
||||
<div className="grid gap-8 p-6 sm:p-8 lg:grid-cols-[1.4fr_0.9fr] lg:p-10">
|
||||
<div className="space-y-5">
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-4 py-2 text-xs font-semibold uppercase tracking-[0.3em] text-white/90">
|
||||
<Bird className="h-4 w-4" />
|
||||
Join flock
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="font-display text-4xl leading-none sm:text-5xl lg:text-6xl">
|
||||
Barnyard coop
|
||||
</h1>
|
||||
<p className="mt-4 max-w-2xl text-sm/6 text-white/85 sm:text-base">
|
||||
Keep nest full, request feed, track harvest. Warm barn light,
|
||||
quick lookup, no fluff.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button
|
||||
onClick={() => setIsSearchModalOpen(true)}
|
||||
className="bg-amber-200 text-amber-950 hover:bg-amber-100"
|
||||
>
|
||||
Request content <ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleRefresh}
|
||||
className="border-white/25 bg-white/10 text-white hover:bg-white/20"
|
||||
>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Check requests
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleLogout}
|
||||
className="border-white/25 bg-white/10 text-white hover:bg-white/20"
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Total Gathered
|
||||
<div className="grid gap-3 rounded-[1.5rem] border border-white/15 bg-white/10 p-4 backdrop-blur-md">
|
||||
<div className="rounded-2xl bg-white/15 p-4">
|
||||
<div className="flex items-center justify-between text-white/80">
|
||||
<span className="text-xs uppercase tracking-[0.3em]">
|
||||
Nest Egg
|
||||
</span>
|
||||
<ShieldCheck className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="mt-3 text-4xl font-bold">
|
||||
{formatNumber(wallet?.balance || 0)}{" "}
|
||||
<span className="text-xl">$COOP</span>
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-white/75">
|
||||
Ready to spend on requests.
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-3xl font-bold">
|
||||
{formatNumber(wallet?.totalEarned || 0)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground">Spent</div>
|
||||
<div className="text-3xl font-bold">
|
||||
{formatNumber(wallet?.totalSpent || 0)}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="rounded-2xl bg-black/10 p-4">
|
||||
<div className="text-[11px] uppercase tracking-[0.25em] text-white/70">
|
||||
Gathered
|
||||
</div>
|
||||
<div className="mt-2 text-2xl font-bold">
|
||||
{formatNumber(wallet?.totalEarned || 0)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-black/10 p-4">
|
||||
<div className="text-[11px] uppercase tracking-[0.25em] text-white/70">
|
||||
Spent
|
||||
</div>
|
||||
<div className="mt-2 text-2xl font-bold">
|
||||
{formatNumber(wallet?.totalSpent || 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button onClick={() => setIsSearchModalOpen(true)}>
|
||||
Request content
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<StatTile
|
||||
label="Pending requests"
|
||||
value={String(pendingRequests.length)}
|
||||
hint="Waiting in coop"
|
||||
icon={Clock3}
|
||||
/>
|
||||
<StatTile
|
||||
label="Movie cost"
|
||||
value={`${requestCosts.movie}`}
|
||||
hint="One fetch"
|
||||
icon={BadgeDollarSign}
|
||||
/>
|
||||
<StatTile
|
||||
label="TV cost"
|
||||
value={`${requestCosts.tv}`}
|
||||
hint="Season hangout"
|
||||
icon={PartyPopper}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-6 lg:grid-cols-[1.35fr_0.65fr]">
|
||||
<Card className="border-primary/10 bg-card/80 shadow-[0_18px_50px_-30px_rgba(0,0,0,0.45)] backdrop-blur-sm">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="font-display text-2xl">
|
||||
Harvest board
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Watch, requests, leaderboard. All feed, no smoke.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="history" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3 bg-secondary/60">
|
||||
<TabsTrigger value="history">History</TabsTrigger>
|
||||
<TabsTrigger value="requests">Requests</TabsTrigger>
|
||||
<TabsTrigger value="leaderboard">Leaderboard</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="history" className="mt-6">
|
||||
<WatchHistory />
|
||||
</TabsContent>
|
||||
<TabsContent value="requests" className="mt-6">
|
||||
<RequestHistory onRefresh={handleRefresh} />
|
||||
</TabsContent>
|
||||
<TabsContent value="leaderboard" className="mt-6">
|
||||
<Leaderboard />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="space-y-6">
|
||||
<Card className="border-primary/10 bg-card/80 shadow-[0_18px_50px_-30px_rgba(0,0,0,0.45)] backdrop-blur-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="font-display text-xl">
|
||||
Quick request
|
||||
</CardTitle>
|
||||
<CardDescription>Grab content fast.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() => setIsSearchModalOpen(true)}
|
||||
>
|
||||
Request content
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="mt-3 w-full"
|
||||
onClick={handleRefresh}
|
||||
>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Sync requests
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-primary/10 bg-card/80 shadow-[0_18px_50px_-30px_rgba(0,0,0,0.45)] backdrop-blur-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="font-display text-xl">
|
||||
Recent activity
|
||||
</CardTitle>
|
||||
<CardDescription>Newest barn noise.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ActivityFeed />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Pending requests</CardTitle>
|
||||
<CardDescription>My requests waiting</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-2xl font-bold">
|
||||
{pendingRequests.length}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Movie cost</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-2xl font-bold">
|
||||
{requestCosts.movie}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>TV cost</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-2xl font-bold">
|
||||
{requestCosts.tv}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<SearchRequestModal
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
costs={requestCosts}
|
||||
/>
|
||||
</div>
|
||||
<Tabs defaultValue="history">
|
||||
<TabsList>
|
||||
<TabsTrigger value="history">History</TabsTrigger>
|
||||
<TabsTrigger value="requests">Requests</TabsTrigger>
|
||||
<TabsTrigger value="leaderboard">Leaderboard</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="history">
|
||||
<WatchHistory />
|
||||
</TabsContent>
|
||||
<TabsContent value="requests">
|
||||
<RequestHistory onRefresh={syncPending} />
|
||||
</TabsContent>
|
||||
<TabsContent value="leaderboard">
|
||||
<Leaderboard />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<ActivityFeed />
|
||||
<SearchRequestModal
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
costs={requestCosts}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user