Skip to content

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 are robots: { index: false, follow: false } and excluded from sitemap.ts. They're personal shares, not SEO targets — we don't want thin-content noise in the index.
  • The saver's display_name is 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 + initialSelection prop), apps/web/app/build-your-box/[slug]/page.tsx (?from= resolver)
  • Plugin: wp-plugin/fruitplug-api/includes/Rest/BoxBuilderController.php (fetch_public)