Files
pansy/web/src/pages/PublicGardenPage.tsx
T
steve e74fb308c1
Build image / build-and-push (push) Successful in 7s
Configurable grid + snap-to-grid in the layout system (#45)
Closes #44. Two independent grids (garden + per-bed), each with a size and a snap toggle; snapping defaults off. See PR #45 for details.

Co-authored-by: Steve Dudenhoeffer <[email protected]>
2026-07-19 07:07:14 +00:00

75 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useMemo } from 'react'
import { getRouteApi } from '@tanstack/react-router'
import { Alert } from '@/components/ui/Alert'
import { GardenCanvas } from '@/editor/GardenCanvas'
import { useEditorStore } from '@/editor/store'
import type { EditorGarden } from '@/editor/types'
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 view behind a share token. Rendered on the
* unauthenticated `/g/$token` route (no auth guard), so a logged-out visitor
* never hits /login or OIDC. Reuses GardenCanvas with canEdit=false — pan/zoom
* to explore, but no editing chrome.
*/
export function PublicGardenPage() {
const { token } = routeApi.useParams()
const full = usePublicGarden(token)
usePageTitle(full.data?.garden.name ?? 'Shared garden')
// Start from a clean canvas: if a logged-in user reaches here from their own
// editor, stale focus/selection state would otherwise dim or highlight things.
useEffect(() => {
useEditorStore.getState().resetTransient()
}, [token])
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])
if (full.isPending) return <p className="p-6 text-sm text-muted">Loading garden</p>
if (full.isError)
return (
<div className="p-6">
<Alert>This shared link isnt available. The owner may have turned it off or changed it.</Alert>
</div>
)
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 (
<div className="flex h-[calc(100vh-8rem)] flex-col gap-3">
<div className="flex flex-wrap items-center gap-2">
<h1 className="truncate text-lg font-semibold tracking-tight" title={garden.name}>
{garden.name}
</h1>
<span className="rounded-md bg-border/40 px-2 py-1 text-xs text-muted">👁 Shared · read-only</span>
</div>
<div className="min-h-0 flex-1">
<GardenCanvas
garden={garden}
objects={objects}
plantings={plantings}
plantsById={plantsById}
canEdit={false}
/>
</div>
</div>
)
}