Skip to content
Merged
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
61 changes: 19 additions & 42 deletions app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { RecommendationProvenance, type RecommendationSignalKey } from "@/compon
import { Skeleton } from "@/components/ui/skeleton"
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"
import { RecommendationsStrip } from "@/components/dashboard/RecommendationsStrip"
import { ActiveBets } from "@/components/active-bets/ActiveBets"
import { ActivityTimeline } from "@/components/activity-timeline"
import { useEffect, useState } from "react"

interface Stat {
Expand Down Expand Up @@ -97,7 +99,8 @@ export default function DashboardPage() {
}

const renderStatCard = (stat: Stat, idx: number) => {
return <StatCard key={idx} stat={stat} index={idx} />
const variants: any[] = ['volume', 'predictions', 'win-rate', 'leaderboard'];
return <StatCard key={idx} stat={stat} index={idx} emptyVariant={variants[idx % 4]} />
}

const renderCards = () => {
Expand All @@ -112,13 +115,10 @@ export default function DashboardPage() {
)
case 'empty':
return (
<div className="flex flex-col items-center justify-center py-8 text-center">
<AlertCircle className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-lg font-medium mb-2">No KPI data available.</p>
<p className="text-sm text-muted-foreground mb-4">Create events to generate statistics.</p>
<Button asChild>
<Link href="/events/new">Create New Event</Link>
</Button>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{(['volume', 'predictions', 'win-rate', 'leaderboard'] as const).map((variant, idx) => (
<StatCard key={idx} index={idx} status="empty" emptyVariant={variant} />
))}
</div>
)
case 'error':
Expand Down Expand Up @@ -359,6 +359,11 @@ export default function DashboardPage() {
</TabsList>
<TabsContent value="overview" className="space-y-4">
{renderCards()}
<ActiveBets
bets={status === 'empty' ? [] : []}
isLoading={status === 'loading'}
onAddBet={() => console.log('Add bet')}
/>
{renderRecommendationStrip()}
<RecommendationsStrip />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
Expand All @@ -375,42 +380,14 @@ export default function DashboardPage() {
</Card>
<Card className="col-span-3">
<CardHeader>
<CardTitle>Pending Tasks</CardTitle>
<CardDescription>Tasks requiring admin attention</CardDescription>
<CardTitle>Recent Activity</CardTitle>
<CardDescription>Your latest actions on Predictify</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center gap-4">
<CheckCircle className="h-5 w-5 text-amber-500" />
<div className="space-y-1">
<p className="text-sm font-medium leading-none">8 events need outcome verification</p>
<p className="text-sm text-muted-foreground">Deadline: Today</p>
</div>
<Button variant="outline" size="sm" className="ml-auto">
Verify
</Button>
</div>
<div className="flex items-center gap-4">
<HelpCircle className="h-5 w-5 text-red-500" />
<div className="space-y-1">
<p className="text-sm font-medium leading-none">12 disputes need resolution</p>
<p className="text-sm text-muted-foreground">3 high priority</p>
</div>
<Button variant="outline" size="sm" className="ml-auto">
Resolve
</Button>
</div>
<div className="flex items-center gap-4">
<TrendingUp className="h-5 w-5 text-green-500" />
<div className="space-y-1">
<p className="text-sm font-medium leading-none">Monthly financial report ready</p>
<p className="text-sm text-muted-foreground">April 2023</p>
</div>
<Button variant="outline" size="sm" className="ml-auto">
View
</Button>
</div>
</div>
<ActivityTimeline
activities={status === 'empty' ? [] : []}
isLoading={status === 'loading'}
/>
</CardContent>
</Card>
</div>
Expand Down
20 changes: 12 additions & 8 deletions components/active-bets/ActiveBets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,22 @@ const ActiveBetCardSkeleton = () => (

// Empty state component
const EmptyState = ({ onAddBet }: { onAddBet?: () => void }) => (
<div className="flex flex-col items-center justify-center py-12 px-4 text-center">
<div className="w-16 h-16 rounded-full bg-muted/50 flex items-center justify-center mb-4">
<Plus className="w-8 h-8 text-muted-foreground" />
<div className="flex flex-col items-center justify-center py-12 px-4 text-center bg-card/20 backdrop-blur-sm border border-border/50 rounded-2xl">
<div className="w-24 h-24 mb-6 relative">
<img
src="/assets/empty-states/dashboard/active-bets.svg"
alt="No active bets"
className="w-full h-full object-contain ml-auto mr-auto"
/>
</div>
<h3 className="text-lg font-semibold text-foreground mb-2">No Active Bets</h3>
<p className="text-muted-foreground mb-4 max-w-sm">
You don't have any active bets at the moment. Start by placing your first bet!
<h3 className="text-xl font-bold text-foreground mb-2">No Active Bets</h3>
<p className="text-muted-foreground mb-6 max-w-sm">
You haven't placed any bets yet. Explore live markets and start predicting today!
</p>
{onAddBet && (
<Button onClick={onAddBet} className="bg-primary hover:bg-primary/90">
<Button onClick={onAddBet} className="bg-primary hover:bg-primary/90 shadow-lg shadow-primary/20 transition-all hover:scale-105 active:scale-95">
<Plus className="w-4 h-4 mr-2" />
Add Bet
Explore Markets
</Button>
)}
</div>
Expand Down
58 changes: 50 additions & 8 deletions components/cards/stat-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import Link from "next/link";

export type StatEmptyVariant = 'volume' | 'predictions' | 'win-rate' | 'leaderboard';

interface StatCardProps {
stat?: Stat;
index: number;
Expand All @@ -15,23 +17,63 @@ interface StatCardProps {
* - "error": shows an error alert with a retry button.
*/
status?: "loading" | "empty" | "error";
/** The variant of empty state to show, which determines illustration and copy */
emptyVariant?: StatEmptyVariant;
/** Callback invoked when the retry button is clicked in error state */
onRetry?: () => void;
}

export function StatCard({ stat, index, status, onRetry }: StatCardProps) {
const EMPTY_CONFIGS: Record<StatEmptyVariant, { illustration: string; title: string; description: string; cta: string; href: string }> = {
volume: {
illustration: "/assets/empty-states/dashboard/volume.svg",
title: "No Volume",
description: "Start trading to see activity.",
cta: "Explore Markets",
href: "/events"
},
predictions: {
illustration: "/assets/empty-states/dashboard/predictions.svg",
title: "No Predictions",
description: "Make your first prediction.",
cta: "View Markets",
href: "/events"
},
"win-rate": {
illustration: "/assets/empty-states/dashboard/win-rate.svg",
title: "0% Win Rate",
description: "Win bets to see your rate.",
cta: "Place a Bet",
href: "/events"
},
leaderboard: {
illustration: "/assets/empty-states/dashboard/leaderboard.svg",
title: "Unranked",
description: "Compete to climb the ranks.",
cta: "Leaderboard",
href: "/leaderboard"
}
};

export function StatCard({ stat, index, status, emptyVariant = 'volume', onRetry }: StatCardProps) {
if (status === "loading") {
return <Skeleton className="h-32 w-full rounded-xl" />;
return <Skeleton className="h-44 w-full rounded-xl" />;
}

if (status === "empty") {
const config = EMPTY_CONFIGS[emptyVariant];
return (
<div className="flex flex-col items-center justify-center py-8 text-center">
<AlertCircle className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-lg font-medium mb-2">No data available.</p>
<p className="text-sm text-muted-foreground mb-4">Create events to generate statistics.</p>
<Button asChild>
<Link href="/events/new">Create New Event</Link>
<div className="flex flex-col items-center justify-center p-6 text-center bg-slate-900/40 backdrop-blur-sm border border-slate-800 rounded-2xl group transition-all duration-300 hover:border-cyan-500/30">
<div className="relative w-16 h-16 mb-3">
<img
src={config.illustration}
alt={config.title}
className="w-full h-full object-contain filter group-hover:scale-110 transition-transform duration-300"
/>
</div>
<p className="text-base font-semibold text-slate-100 mb-1">{config.title}</p>
<p className="text-xs text-slate-400 mb-4">{config.description}</p>
<Button asChild size="sm" variant="outline" className="h-8 text-xs border-cyan-500/30 hover:bg-cyan-500/10 hover:text-cyan-400">
<Link href={config.href}>{config.cta}</Link>
</Button>
</div>
);
Expand Down
13 changes: 13 additions & 0 deletions public/assets/empty-states/dashboard/active-bets.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions public/assets/empty-states/dashboard/leaderboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions public/assets/empty-states/dashboard/predictions.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions public/assets/empty-states/dashboard/volume.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions public/assets/empty-states/dashboard/win-rate.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.