Saved boxes & public share pages¶
Customers who build a custom box can save it to their account and (optionally) share a public, read-only URL: https://fruitplug.co.uk/b/{slug}. Visitors with that link can preview the contents, add the whole box to cart in one tap, or remix it in the builder.
This is the social-proof flywheel: every saved box is a potential mini landing page.
Surfaces¶
/build-your-box/{template}— the builder. After save, a Share this box button copies the public URL to clipboard (only when the saver flagged the box as public)./b/{slug}— the public share page. Server-rendered (revalidate: 300),noindex(these are personal shares, not SEO targets).- Optional deep-link:
/build-your-box/{template}?from={savedSlug}— pre-populates the builder from the shared composition so a visitor can tweak it before checkout.
End-to-end flow¶
sequenceDiagram
participant C as Customer
participant PWA as Next.js PWA
participant WP as fruitplug-api
C->>PWA: Builds box at /build-your-box/{template}
C->>PWA: Click "Save this box"
PWA->>WP: POST /box/save (auth)
WP-->>PWA: { slug, ... }
PWA-->>C: "Share this box" button (clipboard copy)
C->>C: Sends /b/{slug} to a friend
Note over PWA,WP: Friend visits /b/{slug}
PWA->>WP: GET /box/{slug} (public, transient-cached)
WP-->>PWA: { title, items[], totals, author_display_name, ... }
PWA-->>C: Share page renders, "Add all to cart" CTA
REST shape¶
GET /wp-json/fruitplug/v1/box/{slug} — public, no auth, 5-minute server-side transient.
{
"slug": "aZ9bC0dEfG",
"title": "Mango lover's plug-in",
"template_slug": "plug-in-l",
"items": [
{ "slug": "baby-mango", "qty": 4, "section": "everyday", "credits": 32, "productId": 123 }
],
"totals": { "price_gbp": 125.0, "item_count": 12, "currency": "GBP" },
"author_display_name": "Moses",
"created_at": "2026-04-25 10:13:42",
"composition": { "...": "back-compat raw lines" }
}
The endpoint resolves each fruit slug to its Woo product ID in a single SQL hit so the PWA can render images and prices without an additional slug→id round trip.
Caching¶
| Layer | TTL | Key |
|---|---|---|
| WP transient (server) | 5 min | fp_saved_box_{slug} |
| Next fetch cache (RSC) | 5 min | saved-box:{slug} |
Next page cache (revalidate) |
5 min | /b/{slug} |
A saved box is immutable once written (saves create new rows + slugs), so a stale window is safe. If we later add edit-in-place, invalidate the transient on update.
Privacy & SEO¶
- The endpoint refuses to return private boxes (
is_public = 0) — a visitor with the URL of a private box still gets a 404. /b/{slug}pages arerobots: { index: false, follow: false }and excluded fromsitemap.ts. They're personal shares, not SEO targets — we don't want thin-content noise in the index.- The saver's
display_nameis exposed when present (it's already public on Woo orders / reviews). We never expose email.
Files¶
- PWA:
apps/web/app/b/[slug]/page.tsx,apps/web/components/saved-box/SavedBoxActions.tsx,apps/web/lib/saved-boxes.ts - Builder hook:
apps/web/components/box-builder/BoxBuilder.tsx(Share button +initialSelectionprop),apps/web/app/build-your-box/[slug]/page.tsx(?from=resolver) - Plugin:
wp-plugin/fruitplug-api/includes/Rest/BoxBuilderController.php(fetch_public)