Skip to content

Let sites show their own sample products before a store connects - #1528

Open
sbolinger-godaddy wants to merge 4 commits into
mainfrom
feat/storefront-custom-samples
Open

sbolinger-godaddy wants to merge 4 commits into
mainfrom
feat/storefront-custom-samples

Conversation

@sbolinger-godaddy

@sbolinger-godaddy sbolinger-godaddy commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A template can show its own sample products before a store is connected, with real images. Then it can keep the same customized card once the store's products load. The design follows a compound-component approach: the package adds sample data and hooks, not new display props. Elements that are only display, such as a "Bestseller" label or a category line, are the site's own components, built with the hooks.

interface RoutineSample extends SampleProduct { category: string }

<CatalogRoot sampleProducts={samples}>          {/* or `true` for the six packaged samples */}
  <CatalogProducts>
    <ProductCardRoot>
      <ProductCardImage />
      <CategoryLine />                          {/* site one-off: useProductCard<RoutineSample>() */}
      <ProductCardTitle />
      <ProductCardDescription />
      <ProductCardPrice />
    </ProductCardRoot>
  </CatalogProducts>
</CatalogRoot>
  • sampleProducts takes an array as well as true. A sample has key, name and price (minor units, USD), and optionally compareAtPrice, description and imageUrl. An image is any URL the browser can load, such as a file in the site's public folder. The package doesn't upload or check it, and a broken image shows the usual placeholder. Fields of the site's own are kept as they are. CatalogRoot throws for an empty list, duplicate or empty keys, or invalid prices.
  • The existing parts render samples fully: image (unlinked, since samples have no page), description, price, Sale badge and struck-through compare-at price.
  • useProductCard() and useCartItem() return sample, typed with the site's sample type (useProductCard<MySample>()). One host component can then work on a card and in the cart.
  • The sample cart keeps each sample as it was added, so drawer lines show the image and the site's fields.
  • New useCatalog() returns { count, isSample, status } for a custom product count.
  • New --commerce-card-image-aspect theme property sets the ratio of card images and their loading placeholders. It defaults to square.
  • Unchanged: samples appear only while the server reports unbound, never reach Commerce and can't check out. true still shows the six packaged samples with no images. There are no new action, link or badge parts.

Changeset

  • Changeset added (@godaddy/gd-commerce-storefront minor)

Test Plan

  • Unit tests (Vitest): 14 new tests in extensibility.test.tsx:
    • host samples render through the same parts, with image, description, sale price and host fields, and make only /config requests
    • the sample cart keeps the image and host fields, and sends the expected onEvent
    • the same card shows live products once a store is connected
    • useCatalog reports loading and error states
    • the aspect property applies, square by default
    • the built-in samples have no images
    • a sample's image is read by screen readers only when the card has no title, so the name is read once
    • validation errors, including non-string keys, empty names, packaged keys, non-object entries and non-string imageUrl/description
    • invalid samples are logged, not thrown, once a store is connected, and the live catalog still renders, including when an entry later becomes undefined
    • an inline sample list rebuilt with the same values doesn't re-render the cards
    • cart lines pick up a mounted catalog's changed sample, and a cart image that fails to load is hidden
    • packaged samples are listed in the cart in catalog order
    • All 182 package tests pass, along with build, lint and typecheck.
  • The default design is unchanged: the local component test app's smoke, parts, walkthrough and boutique suites ran against main and against this branch. All 59 screenshots are pixel-identical, including the packaged sample catalog and sample drawer. (One canvas-tote product image sometimes differs by a single pixel between runs; reruns match, so this is rendering noise.)
  • New template page in the test app:
    • routine.html is modeled on a skincare template, built only from public parts and hooks, with images in the app's public/ folder.
    • The category line, merchandising badge, "View" link and count are the site's own one-offs.
    • npm run routine passes 5 checks: template samples, sample cart, live products in the same card tree, toggling the store without a reload, and phone width.
No store connected: the template's samples Sample cart
Template samples Sample cart
Store connected: same card tree, live products Phone
Live products Phone

Photos are from Unsplash. The connected store is the test app's mock catalog.

For review

  • Live categories aren't in the product query yet. On the connected page, the category line comes from a map in the site's code. Returning categories or tags from the server fits with the filtering work.
  • Samples have no product page, so a "View" link only makes sense for live products. The template adds instead.

🤖 Generated with Claude Code

`sampleProducts` takes the site's own samples as well as `true`. Card and
cart parts show a sample's image, description, price and Sale badge, and
`useProductCard()` and `useCartItem()` return the sample with the site's own
fields, so display-only one-offs such as a category line work before and
after a store connects. `useCatalog()` gives custom parts the product count,
and `--commerce-card-image-aspect` sets the card image ratio. The packaged
samples and default design are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sbolinger-godaddy
sbolinger-godaddy requested a review from a team as a code owner November 2, 2026 19:15
@changeset-bot

changeset-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 640a8d9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@godaddy/gd-commerce-storefront Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ck sample names

- Close the custom card example's code fence on its own line, so the rest of the README renders.
- Hide a sample's image from screen readers when the card has a title, as live cards do, so its name is read once.
- Reject non-string keys and empty names in host samples.
- Validate samples with a plain call instead of a side-effect useMemo.
- Document that the sample type on useProductCard and useCartItem is unchecked, that sample lists should be stable,
  and that a failed refetch keeps the last page's count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pbennett1-godaddy pbennett1-godaddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things I noticed reading through this. None are reproduced yet, so please check my reasoning. I'd look at the key clash and the validation-in-production ones first.

…ow, and keep cart lines current

- Reject host samples that use a packaged sample's key, so they never share a sample cart line with one.
- Throw for invalid samples only while no store is connected; once one is, log in development and keep the live
  catalog.
- Report entries that are not objects, and a description or imageUrl that is not a string, with the CatalogRoot error.
- Compare an inline sample list by value, so an unchanged list does not rebuild every card.
- Recognize packaged samples by key instead of object identity, so a copy keeps its localized name.
- Refresh cart lines from a mounted catalog's sample of the same key, and list packaged samples in catalog order.
- Render nothing from CartItemImage when its image fails to load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pbennett1-godaddy pbennett1-godaddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've re-reviewed 1632408, and all 7 earlier points are addressed (replied on each thread). There's one new edge case below.

…of throwing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pbennett1-godaddy pbennett1-godaddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All review points are addressed through 640a8d9, and CI is green. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants