"use client"; import Image from "next/image"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useRef, useState } from "react"; import { useTheme } from "next-themes"; import { useMutation } from "convex/react"; import { useTranslations } from "next-intl"; import { Box, ChevronDown, Coins, ImageIcon, LayoutTemplate, Loader2, Monitor, Moon, Search, Sun, Video, } from "lucide-react"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; 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 { api } from "@/convex/_generated/api"; import type { Doc } from "@/convex/_generated/dataModel"; import { authClient } from "@/lib/auth-client"; import { CreditOverview } from "@/components/dashboard/credit-overview"; import { CreditsActivityChart } from "@/components/dashboard/credits-activity-chart"; import { RecentTransactions } from "@/components/dashboard/recent-transactions"; import CanvasCard from "@/components/dashboard/canvas-card"; import { MediaLibraryDialog } from "@/components/media/media-library-dialog"; import { collectMediaStorageIdsForResolution, resolveMediaPreviewUrl, } from "@/components/media/media-preview-utils"; import { useDashboardSnapshot } from "@/hooks/use-dashboard-snapshot"; import { toast } from "@/lib/toast"; 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(); } function formatDimensions( width: number | undefined, height: number | undefined, unknownSizeLabel: string, ): string { if (typeof width === "number" && typeof height === "number") { return `${width} x ${height}px`; } return unknownSizeLabel; } function getMediaItemKey(item: NonNullable["snapshot"]>["mediaPreview"][number]): string { if (item.storageId) { return item.storageId; } if (item.originalUrl) { return `url:${item.originalUrl}`; } if (item.previewUrl) { return `preview:${item.previewUrl}`; } if (item.sourceUrl) { return `source:${item.sourceUrl}`; } return `${item.kind}:${item.createdAt}:${item.filename ?? "unnamed"}`; } function getMediaItemMeta( item: NonNullable["snapshot"]>["mediaPreview"][number], labels: { unknownSize: string; videoFile: string; }, ): string { if (item.kind === "video") { return labels.videoFile; } return formatDimensions(item.width, item.height, labels.unknownSize); } function getMediaItemLabel( item: NonNullable["snapshot"]>["mediaPreview"][number], labels: { untitledImage: string; untitledVideo: string; untitledAsset: string; }, ): string { if (item.filename) { return item.filename; } if (item.kind === "video") { return labels.untitledVideo; } if (item.kind === "asset") { return labels.untitledAsset; } return labels.untitledImage; } export function DashboardPageClient() { const t = useTranslations("toasts"); const tMediaCommon = useTranslations("mediaLibrary.common"); const tMediaDashboard = useTranslations("mediaLibrary.dashboard"); const tMediaDialog = useTranslations("mediaLibrary.dialog"); const router = useRouter(); const welcomeToastSentRef = useRef(false); const { theme = "system", setTheme } = useTheme(); const { data: session, isPending: isSessionPending } = authClient.useSession(); const { snapshot: dashboardSnapshot } = useDashboardSnapshot(session?.user?.id); const createCanvas = useMutation(api.canvases.create); const resolveMediaPreviewUrls = useMutation(api.storage.batchGetUrlsForUserMedia); const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false); const [isMediaLibraryDialogOpen, setIsMediaLibraryDialogOpen] = useState(false); const [mediaPreviewUrlMap, setMediaPreviewUrlMap] = useState>({}); const [isResolvingMediaPreview, setIsResolvingMediaPreview] = useState(false); const [mediaPreviewError, setMediaPreviewError] = useState(null); const [hasClientMounted, setHasClientMounted] = useState(false); useEffect(() => { setHasClientMounted(true); }, []); const displayName = session?.user.name?.trim() || session?.user.email || "Nutzer"; const initials = getInitials(displayName); const canvases = dashboardSnapshot?.canvases; const mediaPreview = dashboardSnapshot?.mediaPreview; const mediaPreviewStorageIds = useMemo(() => { const previewItems = mediaPreview ?? []; return collectMediaStorageIdsForResolution(previewItems); }, [mediaPreview]); useEffect(() => { if (!session?.user || welcomeToastSentRef.current) return; const key = `ls-dashboard-welcome-${session.user.id}`; if (typeof window !== "undefined" && sessionStorage.getItem(key)) return; welcomeToastSentRef.current = true; sessionStorage.setItem(key, "1"); toast.success(t("auth.welcomeOnDashboard")); }, [t, session?.user]); useEffect(() => { let isCancelled = false; async function run() { if (dashboardSnapshot === undefined) { setMediaPreviewUrlMap({}); setMediaPreviewError(null); setIsResolvingMediaPreview(false); return; } if (mediaPreviewStorageIds.length === 0) { setMediaPreviewUrlMap({}); setMediaPreviewError(null); setIsResolvingMediaPreview(false); return; } setIsResolvingMediaPreview(true); setMediaPreviewError(null); try { const resolved = await resolveMediaPreviewUrls({ storageIds: mediaPreviewStorageIds }); if (isCancelled) { return; } setMediaPreviewUrlMap(resolved); } catch (error) { if (isCancelled) { return; } setMediaPreviewUrlMap({}); setMediaPreviewError( error instanceof Error ? error.message : tMediaDialog("urlResolveError"), ); } finally { if (!isCancelled) { setIsResolvingMediaPreview(false); } } } void run(); return () => { isCancelled = true; }; }, [dashboardSnapshot, mediaPreviewStorageIds, resolveMediaPreviewUrls, tMediaDialog]); const handleSignOut = async () => { toast.info(t("auth.signedOut")); 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); } }; return (
Account Theme setTheme(value)} > Light Dark System Einstellungen Abrechnung Abmelden

Guten Tag, {displayName}

Überblick über deine Credits und laufende Generierungen.

Credit-Übersicht
Arbeitsbereiche
{isSessionPending || canvases === undefined ? (
Arbeitsbereiche werden geladen...
) : canvases.length === 0 ? (
Noch kein Arbeitsbereich vorhanden. Mit „Neuer Arbeitsbereich“ legst du den ersten an.
) : (
{canvases.map((canvas: Doc<"canvases">) => ( router.push(`/canvas/${id}`)} /> ))}
)}
{tMediaDashboard("sectionTitle")}
{dashboardSnapshot === undefined ? (
{tMediaDashboard("loading")}
) : mediaPreviewError ? (
{tMediaDashboard("previewError", { error: mediaPreviewError })}
) : !mediaPreview || mediaPreview.length === 0 ? (
{tMediaDashboard("empty")}
) : (
{(mediaPreview ?? []).map((item) => { const itemKey = getMediaItemKey(item); const previewUrl = resolveMediaPreviewUrl(item, mediaPreviewUrlMap); const itemLabel = getMediaItemLabel(item, { untitledImage: tMediaCommon("untitledImage"), untitledVideo: tMediaCommon("untitledVideo"), untitledAsset: tMediaCommon("untitledAsset"), }); const itemMeta = getMediaItemMeta(item, { unknownSize: tMediaCommon("unknownSize"), videoFile: tMediaCommon("videoFile"), }); return (
{previewUrl && item.kind === "video" ? (

{itemLabel}

{itemMeta}

); })}
)}
); }