"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.
);
}
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 (