import { useEffect, useMemo, useRef, useState } from 'react' import { Link, getRouteApi } from '@tanstack/react-router' import { Brand } from '@/components/layout/Nav' import { ThemeButton } from '@/components/layout/ThemeButton' import { Alert } from '@/components/ui/Alert' import { IconButton } from '@/components/ui/Button' import { Tag } from '@/components/ui/Tag' import { Canvas, type CanvasHandle } from '@/editor/Canvas' import { PHONE_BREAKPOINT } from '@/editor/shared' import { useEditorStore } from '@/editor/store' import type { EditorGarden } from '@/editor/types' import { cn } from '@/lib/cn' import { monogramMap } from '@/lib/monogram' import { toEditorObject } from '@/lib/objects' import { toEditorPlanting } from '@/lib/plantings' import { usePublicGarden } from '@/lib/publicGarden' import { usePageTitle } from '@/lib/usePageTitle' const routeApi = getRouteApi('/g/$token') /** * The public, read-only garden behind a share token — the unauthenticated * `/g/$token` route (no auth guard), so a logged-out visitor never hits /login * or OIDC. The same canvas, with every mutation entry point off. */ export function PublicGardenPage() { const { token } = routeApi.useParams() const full = usePublicGarden(token) usePageTitle(full.data?.garden.name ?? 'Shared garden') const canvasRef = useRef(null) const rootRef = useRef(null) const [vw, setVw] = useState(() => (typeof window !== 'undefined' ? window.innerWidth : 1024)) useEffect(() => { useEditorStore.getState().reset() }, [token]) useEffect(() => { const el = rootRef.current if (!el) return const ro = new ResizeObserver(([entry]) => setVw(entry.contentRect.width)) ro.observe(el) return () => ro.disconnect() }, []) const isMobile = vw < PHONE_BREAKPOINT const objects = useMemo(() => full.data?.objects.map(toEditorObject) ?? [], [full.data?.objects]) const plantings = useMemo(() => full.data?.plantings.map(toEditorPlanting) ?? [], [full.data?.plantings]) const plants = useMemo(() => full.data?.plants ?? [], [full.data?.plants]) const plantsById = useMemo(() => new Map(plants.map((p) => [p.id, p])), [plants]) const letters = useMemo(() => monogramMap(plants), [plants]) if (full.isPending) return

Loading the garden…

if (full.isError) { return (
This shared link isn't available. The owner may have turned it off or changed it.
) } const g = full.data.garden const garden: EditorGarden = { id: g.id, name: g.name, widthCm: g.widthCm, heightCm: g.heightCm, unitPref: g.unitPref, gridSizeCm: g.gridSizeCm, snapToGrid: g.snapToGrid } return (
{g.name} shared · read-only
canvasRef.current?.zoomFit()} />
) }