feat: share shopping lists via a public link

Mirrors the existing recipe public-share pattern (/r/[id], gated by a
visibility flag, no auth required) — shopping lists previously only
supported private per-user collaborator invites (email + viewer/editor
role), with no way to hand someone a link who isn't already a member.

- shoppingLists.isPublic (owner-only toggle, same access-control pattern
  already used for renaming/deleting the list).
- New public /s/[id] route added to PUBLIC_PATHS — read-only aisle-grouped
  view with a signup CTA for logged-out visitors, styled after /r/[id].
- The existing collaborator-invite dialog (ShareShoppingListButton) now
  also has a "Public link" toggle + copy-link button at the top, so
  there's one Share entry point for both private and public sharing.

Verified locally: toggling public via the real dialog UI persists
correctly (confirmed via DB + a fresh curl PATCH to rule out a client
race in my own test script), and the public link loads with zero auth
for a logged-out browser context.
This commit is contained in:
Arnaud
2026-07-12 16:11:57 +02:00
parent e98a9c3bb7
commit a1a11ff5e5
9 changed files with 4875 additions and 4 deletions
@@ -0,0 +1 @@
ALTER TABLE "shopping_lists" ADD COLUMN "is_public" boolean DEFAULT false NOT NULL;
File diff suppressed because it is too large Load Diff
@@ -225,6 +225,20 @@
"when": 1783841772890,
"tag": "0031_brave_arclight",
"breakpoints": true
},
{
"idx": 32,
"version": "7",
"when": 1783863921901,
"tag": "0032_boring_layla_miller",
"breakpoints": true
},
{
"idx": 33,
"version": "7",
"when": 1783864805460,
"tag": "0033_graceful_jetstream",
"breakpoints": true
}
]
}
+1
View File
@@ -60,6 +60,7 @@ export const shoppingLists = pgTable("shopping_lists", {
id: text("id").primaryKey(),
userId: text("user_id").references(() => users.id, { onDelete: "cascade" }),
name: text("name").notNull(),
isPublic: boolean("is_public").notNull().default(false),
generatedAt: timestamp("generated_at"),
createdAt: timestamp("created_at").notNull().defaultNow(),
});