'use client' import { initSpatialGridSync, useScene } from '@pascal-app/core' import { InteractiveSystem, useViewer, Viewer } from '@pascal-app/viewer' import Link from 'next/link' import { useParams } from 'next/navigation' import { useEffect, useState } from 'react' import { ErrorBoundary } from '@/components/ui/primitives/error-boundary' import { SceneLoader } from '@pascal-app/editor' import { getProjectModelPublic, incrementProjectViews, } from '@/features/community/lib/projects/actions' import type { ProjectOwner } from '@/features/community/lib/projects/types' import { ViewerCameraControls } from './viewer-camera-controls' import { ViewerGuestCTA } from './viewer-guest-cta' import { ViewerOverlay } from './viewer-overlay' import { ViewerZoneSystem } from './viewer-zone-system' function ViewerSceneCrashFallback({ projectName }: { projectName?: string | null }) { return (

The 3D scene failed to render

{projectName ? `"${projectName}" ` : ''} hit a rendering error. The rest of the app is still available.

Back to home
) } export default function ViewerPage() { const params = useParams() const id = params.id as string const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [projectId, setProjectId] = useState(null) const [projectName, setProjectName] = useState(null) const [owner, setOwner] = useState(null) const [canShowScans, setCanShowScans] = useState(true) const [canShowGuides, setCanShowGuides] = useState(true) const setScene = useScene((state) => state.setScene) useEffect(() => { useViewer.getState().setProjectId(projectId) }, [projectId]) useEffect(() => { let cancelled = false setLoading(true) setError(null) setProjectId(null) setProjectName(null) setOwner(null) setCanShowScans(true) setCanShowGuides(true) useViewer.getState().setShowScans(true) useViewer.getState().setShowGuides(true) const loadContent = async () => { try { // Check if it's a demo file (starts with 'demo_') if (id.startsWith('demo_')) { const response = await fetch(`/demos/${id}.json`) if (cancelled) return if (!response.ok) { throw new Error(`Demo "${id}" not found`) } const data = await response.json() if (cancelled) return if (data.nodes && data.rootNodeIds) { setScene(data.nodes, data.rootNodeIds) initSpatialGridSync() } setProjectName('Demo') } else { // Load from database (public project) const result = await getProjectModelPublic(id) if (cancelled) return if (result.success && result.data) { const { project, model, isOwner } = result.data const projectData = project as any setProjectId(project.id) setProjectName(project.name) setOwner(projectData.owner ?? null) // Apply public visibility settings for scans/guides (only for non-owners) if (!isOwner) { const scansAllowed = projectData.show_scans_public !== false const guidesAllowed = projectData.show_guides_public !== false setCanShowScans(scansAllowed) setCanShowGuides(guidesAllowed) if (!scansAllowed) { useViewer.getState().setShowScans(false) } if (!guidesAllowed) { useViewer.getState().setShowGuides(false) } } if (model?.scene_graph) { const { nodes, rootNodeIds } = model.scene_graph setScene(nodes, rootNodeIds) initSpatialGridSync() } // Increment view count await incrementProjectViews(id) if (cancelled) return } else { throw new Error(result.error || 'Project not found') } } if (!cancelled) { setLoading(false) } } catch (err) { if (!cancelled) { setError(err instanceof Error ? err.message : 'Failed to load content') setLoading(false) } } } loadContent() return () => { cancelled = true } }, [id, setScene]) if (error) { return (

{error}

) } return (
{loading && } {!loading && ( <> }> )}
) }