"use client"; import Image from "next/image"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useTheme } from "next-themes"; import { useMutation, useQuery } from "convex/react"; import { Activity, ArrowUpRight, ChevronDown, Coins, LayoutTemplate, Monitor, Moon, Search, Sparkles, Sun, } from "lucide-react"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Progress } from "@/components/ui/progress"; import { api } from "@/convex/_generated/api"; import { authClient } from "@/lib/auth-client"; import { cn } from "@/lib/utils"; const formatEurFromCents = (cents: number) => new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", }).format(cents / 100); const mockRuns = [ { id: "run-8841", workspace: "Sommer-Kampagne", node: "KI-Bild", model: "flux-pro", status: "done" as const, credits: 42, updated: "vor 12 Min.", }, { id: "run-8839", workspace: "Produktfotos", node: "KI-Bild", model: "flux-schnell", status: "executing" as const, credits: 18, updated: "gerade eben", }, { id: "run-8832", workspace: "Social Variants", node: "Compare", model: "—", status: "idle" as const, credits: 0, updated: "vor 1 Std.", }, { id: "run-8828", workspace: "Sommer-Kampagne", node: "KI-Bild", model: "flux-pro", status: "error" as const, credits: 0, updated: "vor 2 Std.", }, ]; function StatusDot({ status }: { status: (typeof mockRuns)[0]["status"] }) { const base = "inline-block size-2 rounded-full"; switch (status) { case "done": return ; case "executing": return ( ); case "idle": return ; case "error": return ; } } function statusLabel(status: (typeof mockRuns)[0]["status"]) { switch (status) { case "done": return "Fertig"; case "executing": return "Läuft"; case "idle": return "Bereit"; case "error": return "Fehler"; } } function getInitials(nameOrEmail: string) { const normalized = nameOrEmail.trim(); if (!normalized) return "U"; const parts = normalized.split(/\s+/).filter(Boolean); if (parts.length >= 2) { return `${parts[0][0]}${parts[1][0]}`.toUpperCase(); } return normalized.slice(0, 2).toUpperCase(); } export default function DashboardPage() { const router = useRouter(); const { theme = "system", setTheme } = useTheme(); const { data: session, isPending: isSessionPending } = authClient.useSession(); const canvases = useQuery( api.canvases.list, session?.user && !isSessionPending ? {} : "skip", ); const createCanvas = useMutation(api.canvases.create); const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false); const displayName = session?.user.name?.trim() || session?.user.email || "Nutzer"; const initials = getInitials(displayName); const handleSignOut = async () => { await authClient.signOut(); router.replace("/auth/sign-in"); router.refresh(); }; const handleCreateWorkspace = async () => { if (isCreatingWorkspace) return; if (!session?.user) return; setIsCreatingWorkspace(true); try { const canvasId = await createCanvas({ name: "Neuer Workspace", description: "", }); router.push(`/canvas/${canvasId}`); } finally { setIsCreatingWorkspace(false); } }; const balanceCents = 4320; const reservedCents = 180; const monthlyPoolCents = 5000; const usagePercent = Math.round( ((monthlyPoolCents - balanceCents) / monthlyPoolCents) * 100, ); return (
{/* Header */}
Account Theme setTheme(value)} > Light Dark System Einstellungen Abrechnung Abmelden
{/* Greeting & Context */}

Guten Tag, {displayName}

Überblick über deine Credits und laufende Generierungen.

{/* Credits & Active Generation — asymmetric two-column */}
{/* Credits Section */}
Credit-Guthaben
{formatEurFromCents(balanceCents)}
Reserviert {formatEurFromCents(reservedCents)}
Monatskontingent {usagePercent}%

Bei fehlgeschlagenen Jobs werden reservierte Credits automatisch freigegeben.

{/* Active Generation */}
Aktive Generierung
Läuft

Produktfotos — Variante 3/4

Fortschritt 62%

Step 2 von 4 —{" "} flux-schnell

{/* Workspaces */}
Workspaces
{isSessionPending || canvases === undefined ? (
Workspaces werden geladen...
) : canvases.length === 0 ? (
Noch kein Workspace vorhanden. Mit "Neuer Workspace" legst du den ersten an.
) : (
{canvases.map((canvas) => ( ))}
)}
{/* Recent Activity */}
Letzte Aktivität
{mockRuns.map((run) => (
{run.workspace} {run.node}
{run.model !== "—" && ( {run.model} )} {run.credits > 0 && ( <> · {run.credits} ct )}
{statusLabel(run.status)}

{run.updated}

))}
); }