diff --git a/apps/community/app/api/auth/[...all]/route.ts b/apps/community/app/api/auth/[...all]/route.ts deleted file mode 100644 index a2cd0932..00000000 --- a/apps/community/app/api/auth/[...all]/route.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Better Auth API route handler - * Handles all /api/auth/* routes for authentication - */ - -import { toNextJsHandler } from 'better-auth/next-js' -import { auth } from '@/lib/auth' - -const { GET, POST } = toNextJsHandler(auth) - -export { GET, POST } diff --git a/apps/community/app/api/presets/[id]/route.ts b/apps/community/app/api/presets/[id]/route.ts deleted file mode 100644 index 450ed884..00000000 --- a/apps/community/app/api/presets/[id]/route.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { headers } from 'next/headers' -import { auth } from '@/lib/auth' -import { supabaseAdmin } from '@/lib/supabase/server' - -// PUT /api/presets/[id] -// Accepts any subset of: name, data, is_community -export async function PUT( - req: NextRequest, - { params }: { params: Promise<{ id: string }> }, -) { - const session = await auth.api.getSession({ headers: await headers() }) - if (!session?.user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - const { id } = await params - const body = await req.json() - const { name, data, is_community } = body - - if (name === undefined && data === undefined && is_community === undefined) { - return NextResponse.json({ error: 'Nothing to update' }, { status: 400 }) - } - - const existingResult = await supabaseAdmin - .from('presets') - .select('user_id') - .eq('id', id) - .single() - const existing = existingResult.data as { user_id: string | null } | null - - if (!existing || existing.user_id !== session.user.id) { - return NextResponse.json({ error: 'Not found or forbidden' }, { status: 403 }) - } - - const updates: Record = {} - if (name !== undefined) updates.name = name - if (data !== undefined) updates.data = data - if (is_community !== undefined) updates.is_community = is_community - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { data: preset, error } = await (supabaseAdmin as any) - .from('presets') - .update(updates) - .eq('id', id) - .select() - .single() - - if (error) return NextResponse.json({ error: error.message }, { status: 500 }) - return NextResponse.json({ preset }) -} - -// DELETE /api/presets/[id] -export async function DELETE( - req: NextRequest, - { params }: { params: Promise<{ id: string }> }, -) { - const session = await auth.api.getSession({ headers: await headers() }) - if (!session?.user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - const { id } = await params - - const existingResult = await supabaseAdmin - .from('presets') - .select('user_id, thumbnail_url') - .eq('id', id) - .single() - const existing = existingResult.data as { user_id: string | null; thumbnail_url: string | null } | null - - if (!existing || existing.user_id !== session.user.id) { - return NextResponse.json({ error: 'Not found or forbidden' }, { status: 403 }) - } - - // Delete thumbnail from storage if present - if (existing.thumbnail_url) { - const url = existing.thumbnail_url as string - const match = url.match(/preset-thumbnails\/(.+)$/) - if (match?.[1]) { - await supabaseAdmin.storage.from('preset-thumbnails').remove([match[1]]) - } - } - - const { error } = await supabaseAdmin.from('presets').delete().eq('id', id) - - if (error) return NextResponse.json({ error: error.message }, { status: 500 }) - return NextResponse.json({ success: true }) -} diff --git a/apps/community/app/api/presets/[id]/thumbnail/route.ts b/apps/community/app/api/presets/[id]/thumbnail/route.ts deleted file mode 100644 index 4f3a69f0..00000000 --- a/apps/community/app/api/presets/[id]/thumbnail/route.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { headers } from 'next/headers' -import { auth } from '@/lib/auth' -import { supabaseAdmin } from '@/lib/supabase/server' - -// POST /api/presets/[id]/thumbnail -// Accepts a raw PNG blob, uploads to preset-thumbnails bucket, updates thumbnail_url -export async function POST( - req: NextRequest, - { params }: { params: Promise<{ id: string }> }, -) { - const session = await auth.api.getSession({ headers: await headers() }) - if (!session?.user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - const { id } = await params - - const existingResult = await supabaseAdmin - .from('presets') - .select('user_id') - .eq('id', id) - .single() - const existing = existingResult.data as { user_id: string | null } | null - - if (!existing || existing.user_id !== session.user.id) { - return NextResponse.json({ error: 'Not found or forbidden' }, { status: 403 }) - } - - const blob = await req.blob() - - const filename = `${id}/thumbnail.png` - const { data: uploadData, error: uploadError } = await supabaseAdmin.storage - .from('preset-thumbnails') - .upload(filename, blob, { - contentType: 'image/png', - upsert: true, - }) - - if (uploadError) { - return NextResponse.json({ error: uploadError.message }, { status: 500 }) - } - - const { data: urlData } = supabaseAdmin.storage - .from('preset-thumbnails') - .getPublicUrl(uploadData.path) - - const thumbnailUrl = `${urlData.publicUrl}?t=${Date.now()}` - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { error: updateError } = await (supabaseAdmin as any) - .from('presets') - .update({ thumbnail_url: thumbnailUrl }) - .eq('id', id) - - if (updateError) { - return NextResponse.json({ error: updateError.message }, { status: 500 }) - } - - return NextResponse.json({ thumbnail_url: thumbnailUrl }) -} diff --git a/apps/community/app/api/presets/route.ts b/apps/community/app/api/presets/route.ts deleted file mode 100644 index f8bb6a54..00000000 --- a/apps/community/app/api/presets/route.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { headers } from 'next/headers' -import { auth } from '@/lib/auth' -import { supabaseAdmin } from '@/lib/supabase/server' -import { createId } from '@pascal-app/db' - -// GET /api/presets?type=door|window&tab=community|mine -export async function GET(req: NextRequest) { - const { searchParams } = req.nextUrl - const type = searchParams.get('type') - const tab = searchParams.get('tab') ?? 'community' - - if (!type || (type !== 'door' && type !== 'window')) { - return NextResponse.json({ error: 'Invalid type' }, { status: 400 }) - } - - if (tab === 'mine') { - const session = await auth.api.getSession({ headers: await headers() }) - if (!session?.user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - const { data, error } = await supabaseAdmin - .from('presets') - .select('*') - .eq('type', type) - .eq('user_id', session.user.id) - .order('created_at', { ascending: false }) - - if (error) return NextResponse.json({ error: error.message }, { status: 500 }) - return NextResponse.json({ presets: data }) - } - - // community tab - const { data, error } = await supabaseAdmin - .from('presets') - .select('*') - .eq('type', type) - .eq('is_community', true) - .order('created_at', { ascending: false }) - - if (error) return NextResponse.json({ error: error.message }, { status: 500 }) - return NextResponse.json({ presets: data }) -} - -// POST /api/presets -export async function POST(req: NextRequest) { - const session = await auth.api.getSession({ headers: await headers() }) - if (!session?.user) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - const body = await req.json() - const { type, name, data, thumbnailUrl } = body - - if (!type || !name || !data) { - return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }) - } - - if (type !== 'door' && type !== 'window') { - return NextResponse.json({ error: 'Invalid type' }, { status: 400 }) - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { data: preset, error } = await (supabaseAdmin as any) - .from('presets') - .insert({ - id: createId('preset'), - type, - name, - data, - thumbnail_url: thumbnailUrl ?? null, - user_id: session.user.id, - is_community: false, - }) - .select() - .single() - - if (error) return NextResponse.json({ error: error.message }, { status: 500 }) - return NextResponse.json({ preset }, { status: 201 }) -} diff --git a/apps/community/app/editor/[projectId]/error.tsx b/apps/community/app/editor/[projectId]/error.tsx deleted file mode 100644 index 7ca2858b..00000000 --- a/apps/community/app/editor/[projectId]/error.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client' - -import Link from 'next/link' -import { useEffect } from 'react' - -export default function EditorRouteError({ - error, - reset, -}: Readonly<{ - error: Error & { digest?: string } - reset: () => void -}>) { - useEffect(() => { - console.error('[editor-route] Unhandled editor error:', error) - }, [error]) - - return ( -
-
-

Editor error

-

- We couldn't load this editor route. You can retry or return home. -

-
- - - Back to home - -
-
-
- ) -} diff --git a/apps/community/app/editor/[projectId]/layout.tsx b/apps/community/app/editor/[projectId]/layout.tsx deleted file mode 100644 index 61badebf..00000000 --- a/apps/community/app/editor/[projectId]/layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Metadata } from 'next' - -export const metadata: Metadata = { - title: 'Editor Workspace', - description: 'Edit your Pascal projects in a full 3D workspace.', - robots: { - index: false, - follow: false, - }, -} - -export default function EditorProjectLayout({ - children, -}: Readonly<{ - children: React.ReactNode -}>) { - return ( -
- {children} -
- ) -} diff --git a/apps/community/app/editor/[projectId]/page.tsx b/apps/community/app/editor/[projectId]/page.tsx deleted file mode 100644 index fb66cdc4..00000000 --- a/apps/community/app/editor/[projectId]/page.tsx +++ /dev/null @@ -1,110 +0,0 @@ -'use client' - -import { useParams, useRouter } from 'next/navigation' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { Editor, SceneLoader } from '@pascal-app/editor' -import type { SceneGraph } from '@pascal-app/editor' -import { createApiPresetsAdapter } from '@/lib/presets-adapter' -import { CommunityAppMenu } from '@/features/community/components/community-app-menu' -import { ProjectHeader } from '@/features/community/components/project-header' -import { useAuth } from '@/features/community/lib/auth/hooks' -import { getProjectModel, saveProjectModel } from '@/features/community/lib/models/actions' -import { uploadProjectThumbnail, updateProjectVisibility } from '@/features/community/lib/projects/actions' -import { useProjectStore } from '@/features/community/lib/projects/store' -import { uploadAssetWithProgress } from '@/lib/upload-asset' -import { deleteProjectAssetByUrl } from '@/features/community/lib/assets/actions' - -export default function EditorPage() { - const params = useParams() - const projectId = params.projectId as string - const { isAuthenticated, isLoading } = useAuth() - const setActiveProject = useProjectStore((state) => state.setActiveProject) - const setAutosaveStatus = useProjectStore((state) => state.setAutosaveStatus) - const isProjectLoading = useProjectStore((state) => state.isLoading) - const isVersionPreviewMode = useProjectStore((state) => state.isVersionPreviewMode) - const activeProject = useProjectStore((state) => state.activeProject) - const router = useRouter() - const [mounted, setMounted] = useState(false) - - useEffect(() => { - setMounted(true) - }, []) - - useEffect(() => { - if (isLoading) return - if (!isAuthenticated) { - router.replace('/') - return - } - if (projectId) { - setActiveProject(projectId) - } - }, [projectId, isAuthenticated, isLoading, setActiveProject, router]) - - const onLoad = useCallback(async (): Promise => { - const result = await getProjectModel(projectId) - return result.success ? (result.data?.model?.scene_graph ?? null) : null - }, [projectId]) - - const onSave = useCallback(async (scene: SceneGraph) => { - await saveProjectModel(projectId, scene) - }, [projectId]) - - const apiPresetsAdapter = useMemo( - () => createApiPresetsAdapter(isAuthenticated), - [isAuthenticated], - ) - - const onThumbnailCapture = useCallback(async (blob: Blob) => { - const result = await uploadProjectThumbnail(projectId, blob) - if (result.success) { - useProjectStore.getState().updateActiveThumbnail(result.data.thumbnail_url) - } - }, [projectId]) - - if (!mounted || isLoading) { - return - } - - if (!isAuthenticated) { - return null - } - - return ( -
-
- } - sidebarTop={} - onLoad={onLoad} - onSave={onSave} - onSaveStatusChange={setAutosaveStatus} - isVersionPreviewMode={isVersionPreviewMode} - isLoading={isProjectLoading} - onThumbnailCapture={onThumbnailCapture} - presetsAdapter={apiPresetsAdapter} - settingsPanelProps={{ - projectId, - projectVisibility: activeProject ? { - isPrivate: activeProject.is_private ?? false, - showScansPublic: activeProject.show_scans_public ?? true, - showGuidesPublic: activeProject.show_guides_public ?? true, - } : undefined, - onVisibilityChange: async (field, value) => { - await updateProjectVisibility(projectId, { [field]: value }) - }, - }} - sitePanelProps={{ - projectId, - onUploadAsset: (pid, levelId, file, type) => { - uploadAssetWithProgress(pid, levelId, file, type) - }, - onDeleteAsset: (pid, url) => { - deleteProjectAssetByUrl(pid, url) - }, - }} - /> -
-
- ) -} diff --git a/apps/community/app/favicon.ico b/apps/community/app/favicon.ico deleted file mode 100644 index e9f36729..00000000 Binary files a/apps/community/app/favicon.ico and /dev/null differ diff --git a/apps/community/app/fonts/GeistMonoVF.woff b/apps/community/app/fonts/GeistMonoVF.woff deleted file mode 100644 index f2ae185c..00000000 Binary files a/apps/community/app/fonts/GeistMonoVF.woff and /dev/null differ diff --git a/apps/community/app/fonts/GeistVF.woff b/apps/community/app/fonts/GeistVF.woff deleted file mode 100644 index 1b62daac..00000000 Binary files a/apps/community/app/fonts/GeistVF.woff and /dev/null differ diff --git a/apps/community/app/globals.css b/apps/community/app/globals.css deleted file mode 100644 index 4908f5a6..00000000 --- a/apps/community/app/globals.css +++ /dev/null @@ -1,256 +0,0 @@ -@import "tailwindcss"; -@import "tw-animate-css"; -@source "../../../packages/editor/src"; - -@custom-variant dark (&:is(.dark *)); - -@theme { - --font-sans: var(--font-barlow), var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; - --font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - --font-barlow: var(--font-barlow), var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-barlow), sans-serif; - --font-mono: var(--font-geist-mono), monospace; - --font-barlow: var(--font-barlow), sans-serif; - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); -} - -:root { - --radius: 0.625rem; - --background: oklch(0.998 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(0.998 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(0.998 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); -} - -.dark { - --background: oklch(0.205 0 0); /* ~171717 */ - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.235 0 0); /* slightly lighter than background (0.205) but darker than previous (0.269) */ - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.235 0 0); /* matching accent */ - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } - button, - [role="button"], - a { - cursor: pointer; - } -} - -/* Apple-style smooth corners (squircle) — progressive enhancement */ -.rounded-smooth { - border-radius: var(--radius-lg); - corner-shape: squircle; -} -.rounded-smooth-xl { - border-radius: var(--radius-xl); - corner-shape: squircle; -} - -.no-scrollbar::-webkit-scrollbar { - display: none; -} - -.no-scrollbar { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ -} - -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} - -/* Loaders */ -.pascal-loader-1 { - width: 45px; - aspect-ratio: 1; - --c:no-repeat linear-gradient(currentColor 0 0); - background: var(--c), var(--c), var(--c); - animation: - pascal-l1-1 1s infinite, - pascal-l1-2 1s infinite; -} -@keyframes pascal-l1-1 { - 0%,100% {background-size:20% 100%} - 33%,66% {background-size:20% 20%} -} -@keyframes pascal-l1-2 { - 0%,33% {background-position: 0 0,50% 50%,100% 100%} - 66%,100% {background-position: 100% 0,50% 50%,0 100%} -} - -.pascal-loader-2 { - width: 45px; - aspect-ratio: .75; - --c: no-repeat linear-gradient(currentColor 0 0); - background: - var(--c) 0% 50%, - var(--c) 50% 50%, - var(--c) 100% 50%; - background-size: 20% 50%; - animation: pascal-l2 1s infinite linear; -} -@keyframes pascal-l2 { - 20% {background-position: 0% 0% ,50% 50% ,100% 50% } - 40% {background-position: 0% 100%,50% 0% ,100% 50% } - 60% {background-position: 0% 50% ,50% 100%,100% 0% } - 80% {background-position: 0% 50% ,50% 50% ,100% 100%} -} - -.pascal-loader-3 { - width: 45px; - aspect-ratio: .75; - --c:no-repeat linear-gradient(currentColor 0 0); - background: - var(--c) 0% 100%, - var(--c) 50% 100%, - var(--c) 100% 100%; - background-size: 20% 65%; - animation: pascal-l3 1s infinite linear; -} -@keyframes pascal-l3 { - 16.67% {background-position: 0% 0% ,50% 100%,100% 100%} - 33.33% {background-position: 0% 0% ,50% 0% ,100% 100%} - 50% {background-position: 0% 0% ,50% 0% ,100% 0% } - 66.67% {background-position: 0% 100%,50% 0% ,100% 0% } - 83.33% {background-position: 0% 100%,50% 100%,100% 0% } -} - -.pascal-loader-4 { - width: 45px; - aspect-ratio: 1; - --c:no-repeat linear-gradient(currentColor 0 0); - background: var(--c), var(--c), var(--c); - animation: - pascal-l4-1 1s infinite, - pascal-l4-2 1s infinite; -} -@keyframes pascal-l4-1 { - 0%,100% {background-size:20% 100%} - 33%,66% {background-size:20% 40%} -} -@keyframes pascal-l4-2 { - 0%,33% {background-position: 0 0,50% 100%,100% 100%} - 66%,100% {background-position: 100% 0,0 100%,50% 100%} -} - -.pascal-loader-5 { - width: 45px; - aspect-ratio: 1; - --c:no-repeat linear-gradient(currentColor 0 0); - background: var(--c), var(--c), var(--c); - animation: - pascal-l5-1 1s infinite, - pascal-l5-2 1s infinite; -} -@keyframes pascal-l5-1 { - 0%,100% {background-size:20% 100%} - 33%,66% {background-size:20% 40%} -} -@keyframes pascal-l5-2 { - 0%,33% {background-position: 0 0 ,50% 100%,100% 0} - 66%,100% {background-position: 0 100%,50% 0 ,100% 100%} -} diff --git a/apps/community/app/layout.tsx b/apps/community/app/layout.tsx deleted file mode 100644 index ef3b645c..00000000 --- a/apps/community/app/layout.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import type { Metadata } from 'next' -import Script from 'next/script' -import localFont from 'next/font/local' -import { Barlow } from 'next/font/google' -import { Analytics } from '@vercel/analytics/react' -import { SpeedInsights } from '@vercel/speed-insights/next' -import { VercelToolbar } from '@vercel/toolbar/next' -import { UsernameGate } from '@/features/community/components/username-gate' -import { siteConfig } from './seo' -import './globals.css' - -const geistSans = localFont({ - src: './fonts/GeistVF.woff', - variable: '--font-geist-sans', -}) -const geistMono = localFont({ - src: './fonts/GeistMonoVF.woff', - variable: '--font-geist-mono', -}) - -const barlow = Barlow({ - subsets: ['latin'], - weight: ['400', '500', '600', '700'], - variable: '--font-barlow', - display: 'swap', -}) - -export const metadata: Metadata = { - metadataBase: new URL(siteConfig.url), - title: { - default: siteConfig.name, - template: '%s | Pascal Editor', - }, - description: siteConfig.description, - applicationName: siteConfig.name, - keywords: [...siteConfig.keywords], - authors: [{ name: 'Pascal', url: 'https://pascal.app' }], - creator: 'Pascal', - publisher: 'Pascal', - alternates: { - canonical: '/', - }, - icons: [{ rel: 'icon', url: '/favicon.ico' }], - openGraph: { - title: siteConfig.name, - description: siteConfig.description, - url: siteConfig.url, - siteName: siteConfig.name, - images: [{ url: siteConfig.ogImage, alt: 'Pascal Editor' }], - locale: 'en_US', - type: 'website', - }, - twitter: { - card: 'summary_large_image', - title: siteConfig.name, - description: siteConfig.description, - creator: siteConfig.twitterHandle, - images: [siteConfig.ogImage], - }, - robots: { - index: true, - follow: true, - googleBot: { - index: true, - follow: true, - 'max-image-preview': 'large', - 'max-snippet': -1, - 'max-video-preview': -1, - }, - }, -} - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode -}>) { - const shouldShowToolbar = process.env.NODE_ENV === 'development' - - return ( - - - {process.env.NODE_ENV === 'development' && ( - <> -