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
+42
View File
@@ -100,6 +100,48 @@ export async function revokeShareLink(id: string): Promise<void> {
export type ActiveShareLink = typeof shareLinks.$inferSelect;
export type EntityShareLink = {
id: string;
createdAt: string;
expiresAt: string | null;
capabilities: ShareLinkCapabilities;
};
export async function getShareLinksForEntity(
entityType: string,
entityId: string,
): Promise<EntityShareLink[]> {
const { household } = await getCurrentSession();
const now = new Date();
const rows = await db
.select({
id: shareLinks.id,
createdAt: shareLinks.createdAt,
expiresAt: shareLinks.expiresAt,
capabilities: shareLinks.capabilities,
})
.from(shareLinks)
.where(
and(
eq(shareLinks.householdId, household.id),
eq(shareLinks.entityType, entityType),
eq(shareLinks.entityId, entityId),
isNull(shareLinks.revokedAt),
),
)
.orderBy(shareLinks.createdAt);
return rows
.filter((r) => !r.expiresAt || r.expiresAt > now)
.map((r) => ({
id: r.id,
createdAt: r.createdAt.toISOString(),
expiresAt: r.expiresAt?.toISOString() ?? null,
capabilities: r.capabilities,
}));
}
export async function getActiveShareLinks(): Promise<ActiveShareLink[]> {
const { household } = await getCurrentSession();
const now = new Date();