diff --git a/app/(app)/canvas/[canvasId]/page.tsx b/app/(app)/canvas/[canvasId]/page.tsx
index c1b586d..b987d52 100644
--- a/app/(app)/canvas/[canvasId]/page.tsx
+++ b/app/(app)/canvas/[canvasId]/page.tsx
@@ -17,7 +17,24 @@ export default async function CanvasPage({
}
const { canvasId } = await params;
- const typedCanvasId = canvasId as Id<"canvases">;
+ let typedCanvasId: Id<"canvases">;
+
+ if (/^\d+$/.test(canvasId)) {
+ const oneBasedIndex = Number(canvasId);
+ if (!Number.isSafeInteger(oneBasedIndex) || oneBasedIndex < 1) {
+ notFound();
+ }
+
+ const canvases = await fetchAuthQuery(api.canvases.list, {});
+ const selectedCanvas = canvases[oneBasedIndex - 1];
+ if (!selectedCanvas) {
+ notFound();
+ }
+
+ typedCanvasId = selectedCanvas._id;
+ } else {
+ typedCanvasId = canvasId as Id<"canvases">;
+ }
try {
const canvas = await fetchAuthQuery(api.canvases.get, {
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index 9f59305..03d907c 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -1,14 +1,21 @@
"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";
@@ -19,11 +26,15 @@ import {
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) =>
@@ -71,12 +82,6 @@ const mockRuns = [
},
];
-const mockWorkspaces = [
- { name: "Sommer-Kampagne", nodes: 24, frames: 3, initial: "S" },
- { name: "Produktfotos", nodes: 11, frames: 2, initial: "P" },
- { name: "Social Variants", nodes: 8, frames: 1, initial: "V" },
-];
-
function StatusDot({ status }: { status: (typeof mockRuns)[0]["status"] }) {
const base = "inline-block size-2 rounded-full";
switch (status) {
@@ -109,7 +114,54 @@ function statusLabel(status: (typeof mockRuns)[0]["status"]) {
}
}
+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;
@@ -151,22 +203,43 @@ export default function DashboardPage() {
Überblick über deine Credits und laufende Generierungen. @@ -268,35 +341,47 @@ export default function DashboardPage() { variant="ghost" size="sm" className="text-muted-foreground" - disabled + type="button" + onClick={handleCreateWorkspace} + disabled={isCreatingWorkspace || isSessionPending || !session?.user} > - Neuer Workspace + {isCreatingWorkspace ? "Erstelle..." : "Neuer Workspace"}