diff --git a/app/dashboard/page-client.tsx b/app/dashboard/page-client.tsx
new file mode 100644
index 0000000..9cb5222
--- /dev/null
+++ b/app/dashboard/page-client.tsx
@@ -0,0 +1,247 @@
+"use client";
+
+import Image from "next/image";
+import { useRouter } from "next/navigation";
+import { useEffect, useRef, useState } from "react";
+import { useTheme } from "next-themes";
+import { useMutation } from "convex/react";
+import { useTranslations } from "next-intl";
+import {
+ ChevronDown,
+ Coins,
+ LayoutTemplate,
+ Monitor,
+ Moon,
+ Search,
+ Sun,
+} 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 { RecentTransactions } from "@/components/dashboard/recent-transactions";
+import CanvasCard from "@/components/dashboard/canvas-card";
+import { toast } from "@/lib/toast";
+import { useAuthQuery } from "@/hooks/use-auth-query";
+
+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 function DashboardPageClient() {
+ const t = useTranslations("toasts");
+ const router = useRouter();
+ const welcomeToastSentRef = useRef(false);
+ const { theme = "system", setTheme } = useTheme();
+ const { data: session, isPending: isSessionPending } = authClient.useSession();
+ const canvases = useAuthQuery(
+ api.canvases.list,
+ session?.user && !isSessionPending ? {} : "skip",
+ );
+ const createCanvas = useMutation(api.canvases.create);
+ const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
+ const [hasClientMounted, setHasClientMounted] = useState(false);
+
+ useEffect(() => {
+ setHasClientMounted(true);
+ }, []);
+
+ const displayName = session?.user.name?.trim() || session?.user.email || "Nutzer";
+ const initials = getInitials(displayName);
+
+ 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]);
+
+ 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}`)}
+ />
+ ))}
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index 4289c24..269319c 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -1,263 +1,15 @@
-"use client";
+import { redirect } from "next/navigation";
-import Image from "next/image";
-import { useRouter } from "next/navigation";
-import { useEffect, useRef, useState } from "react";
-import { useTheme } from "next-themes";
-import { useMutation } from "convex/react";
-import { useTranslations } from "next-intl";
-import {
- ChevronDown,
- Coins,
- LayoutTemplate,
- Monitor,
- Moon,
- Search,
- Sun,
-} from "lucide-react";
+import { isAuthenticated } from "@/lib/auth-server";
-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 { RecentTransactions } from "@/components/dashboard/recent-transactions";
-import CanvasCard from "@/components/dashboard/canvas-card";
-import { toast } from "@/lib/toast";
-import { useAuthQuery } from "@/hooks/use-auth-query";
+import { DashboardPageClient } from "./page-client";
+export default async function DashboardPage() {
+ const authenticated = await isAuthenticated();
-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();
+ if (!authenticated) {
+ redirect("/auth/sign-in");
}
- return normalized.slice(0, 2).toUpperCase();
-}
-
-export default function DashboardPage() {
- const t = useTranslations('toasts');
- const router = useRouter();
- const welcomeToastSentRef = useRef(false);
- const { theme = "system", setTheme } = useTheme();
- const { data: session, isPending: isSessionPending } = authClient.useSession();
- const canvases = useAuthQuery(
- api.canvases.list,
- session?.user && !isSessionPending ? {} : "skip",
- );
- const createCanvas = useMutation(api.canvases.create);
- const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false);
- const [hasClientMounted, setHasClientMounted] = useState(false);
-
- useEffect(() => {
- setHasClientMounted(true);
- }, []);
-
- useEffect(() => {
- if (!isSessionPending && !session?.user) {
- router.replace("/auth/sign-in");
- }
- }, [isSessionPending, router, session?.user]);
-
- const displayName = session?.user.name?.trim() || session?.user.email || "Nutzer";
- const initials = getInitials(displayName);
-
- 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]);
-
- 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 (
-
- {/* Header */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Account
-
-
- Theme
-
- setTheme(value)}
- >
-
-
- Light
-
-
-
- Dark
-
-
-
- System
-
-
-
- Einstellungen
- Abrechnung
-
- Abmelden
-
-
-
-
-
-
-
- {/* Greeting & Context */}
-
-
- Guten Tag, {displayName}
-
-
- Überblick über deine Credits und laufende Generierungen.
-
-
-
- {/* Credits Overview */}
-
-
-
- Credit-Übersicht
-
-
-
-
- {/* Workspaces */}
-
-
-
-
- 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}`)}
- />
- ))}
-
- )}
-
-
- {/* Recent Transactions */}
-
-
-
- );
+ return ;
}
diff --git a/app/layout.tsx b/app/layout.tsx
index af2e435..9282acb 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
+import Script from "next/script";
import { Manrope } from "next/font/google";
import * as Sentry from "@sentry/nextjs";
import "./globals.css";
@@ -11,8 +12,34 @@ import { getLocale, getMessages, getTimeZone } from "next-intl/server";
const manrope = Manrope({ subsets: ["latin"], variable: "--font-sans" });
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ metadataBase: new URL("https://app.lemonspace.io"),
+ title: {
+ default: "LemonSpace",
+ template: "%s | LemonSpace",
+ },
+ description: "LemonSpace is a platform for creating and sharing digital content with nodes.",
+ keywords: ["LemonSpace", "digital content", "platform", "nodes"],
+ authors: [{ name: "LemonSpace" }],
+ robots: {
+ index: true,
+ follow: true,
+ googleBot: {
+ index: true,
+ follow: true,
+ },
+ },
+ openGraph: {
+ type: "website",
+ url: "https://app.lemonspace.io",
+ siteName: "LemonSpace",
+ title: "LemonSpace",
+ description: "LemonSpace is a platform for creating and sharing digital content with nodes.",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "LemonSpace",
+ description: "LemonSpace is a platform for creating and sharing digital content with nodes.",
+ },
};
export default async function RootLayout({
@@ -41,25 +68,12 @@ export default async function RootLayout({
suppressHydrationWarning
className={cn("h-full", "antialiased", "font-sans", manrope.variable)}
>
-
-
-
-
-
-
-
-
-
-
-
-
+ strategy="afterInteractive"
+ />
- Laden...
-
- );
+export default async function Home() {
+ const authenticated = await isAuthenticated();
+
+ if (authenticated) {
+ redirect("/dashboard");
}
return (
🍋 LemonSpace
- {session?.user ? (
-
-
- Willkommen, {session.user.name}
-
-
- Zum Dashboard
-
-
-
- ) : (
-
-
- Anmelden
-
-
- Registrieren
-
-
- )}
+
+
+ Anmelden
+
+
+ Registrieren
+
+
);
}