"use client"; // ============================================================ // Smart Gallery — the tenant's media library, powered by @photo-gallery/sdk embedded // inside the dashboard shell. The SDK owns the gallery experience (grid, lightbox, // photo + video editors, map, people, versions, comments, AI); this file owns the // LynkedUp chrome around it: page head, demo-mode banner, sizing, and failure // containment so a gallery fault can never take the dashboard down. // // Storage + identity come from `@/lib/gallery-api` (be-crm data door when the Shell is // configured, device-local otherwise). Theming comes from GALLERY_THEME_TOKENS, which // maps the SDK's tokens onto this dashboard's own CSS variables. // ============================================================ import { Component, type ErrorInfo, type ReactNode } from "react"; import dynamic from "next/dynamic"; import { Icon } from "./ui"; import { useGalleryFeatures, useGalleryStorage } from "@/lib/gallery-api"; const SmartGalleryMount = dynamic(() => import("./smart-gallery-mount"), { ssr: false, loading: () => , }); export function SmartGallery({ theme }: { theme: "dark" | "light" }) { const { live } = useGalleryStorage(); // Whole-view gate. `media.view` with the same permissive fallback the feature toggles use, so a // superadmin/owner and the demo always pass, and a member is only blocked if they hold some Media // perms but not `media.view`. The sidebar keeps the row visible (see sidebar.tsx §4) — the real // gate is here. const { canView } = useGalleryFeatures(); return (
{/* Slim header — the big PageHead ate vertical space the gallery needs. The CRM topbar already shows the "Smart Gallery" title; this compact row (~40px) just adds context and the icon. */}

Smart Gallery

Every photo and video for your jobs — searchable, editable and shareable.
{!live && (
Demo mode — stored on this device only. It goes live once the Shell + be-crm are connected.
)} {canView ? (
) : (

You don't have access to the gallery

Ask a workspace admin to grant you the “View Smart Gallery” permission.

)}
); } /* ------------------------------------------------------------------ */ function GalleryPlaceholder({ label }: { label: string }) { return (

{label}

); } interface BoundaryState { error: Error | null; } /** * The gallery is a large third-party surface with canvas, workers and optional ML models. A render * fault inside it must degrade to a message rather than blanking the whole dashboard, so it gets its * own error boundary. (Error boundaries still require a class component in React 19.) */ class GalleryBoundary extends Component<{ children: ReactNode }, BoundaryState> { state: BoundaryState = { error: null }; static getDerivedStateFromError(error: Error): BoundaryState { return { error }; } componentDidCatch(error: Error, info: ErrorInfo): void { console.error("[smart-gallery] render failed", error, info.componentStack); } render(): ReactNode { const { error } = this.state; if (!error) return this.props.children; return (

The gallery could not be displayed

{error.message || "An unexpected error occurred."}

); } }