feat: share links visible on plants/containers; container image gallery

- Fix ShareButton silently swallowing errors from createShareLink; now
  shows inline error text so failures are visible to the user
- Add getShareLinksForEntity server action and EntityShareLink type to
  _core/share.ts
- Add ShareLinkList component — renders active share links per entity
  with per-row Revoke; renders nothing when empty
- Wire ShareLinkList into plant and container detail pages (loaded
  server-side in parallel with the entity fetch)
- Add images jsonb column to garden_containers schema + migration 0018
- Add addContainerImage / removeContainerImage / setContainerPrimaryImage
  server actions mirroring the plant image pattern (10-image cap, first
  upload auto-sets cover)
- Update ContainerDetailDto, listContainers, getContainer to include images
- Rewrite ContainerDetail with Info/Gallery tabs; Gallery tab mirrors
  plant gallery (3-col grid, star/X overlays, upload button, counter)
- Update ContainerShareData and container renderSharedView to show cover
  image hero and secondary image grid on public share pages
This commit is contained in:
ginnoir
2026-06-02 20:15:29 -05:00
parent 076e35aced
commit c6a34f7471
13 changed files with 463 additions and 103 deletions
+5
View File
@@ -10,6 +10,7 @@ export type ContainerDto = {
type: string;
locationNotes: string | null;
coverImageUrl: string | null;
images: string[];
plantCount: number;
createdAt: string;
updatedAt: string;
@@ -39,6 +40,7 @@ export async function listContainers(): Promise<ContainerDto[]> {
type: gardenContainers.type,
locationNotes: gardenContainers.locationNotes,
coverImageUrl: gardenContainers.coverImageUrl,
images: gardenContainers.images,
createdAt: gardenContainers.createdAt,
updatedAt: gardenContainers.updatedAt,
plantCount: sql<number>`count(${gardenPlants.id})::int`,
@@ -51,6 +53,7 @@ export async function listContainers(): Promise<ContainerDto[]> {
return rows.map((r) => ({
...r,
images: r.images ?? [],
plantCount: r.plantCount ?? 0,
createdAt: r.createdAt.toISOString(),
updatedAt: r.updatedAt.toISOString(),
@@ -68,6 +71,7 @@ export async function getContainer(id: string): Promise<ContainerDetailDto | nul
type: gardenContainers.type,
locationNotes: gardenContainers.locationNotes,
coverImageUrl: gardenContainers.coverImageUrl,
images: gardenContainers.images,
createdAt: gardenContainers.createdAt,
updatedAt: gardenContainers.updatedAt,
plantCount: sql<number>`(select count(*)::int from garden_plants where container_id = ${gardenContainers.id})`,
@@ -98,6 +102,7 @@ export async function getContainer(id: string): Promise<ContainerDetailDto | nul
type: container.type,
locationNotes: container.locationNotes,
coverImageUrl: container.coverImageUrl,
images: container.images ?? [],
plantCount: container.plantCount ?? 0,
createdAt: container.createdAt.toISOString(),
updatedAt: container.updatedAt.toISOString(),