Skip to content

feat(apps-nuvemshop): customer accounts, sitemap and skill - #631

Merged
JonasJesus42 merged 1 commit into
mainfrom
JonasJesus42/nuvemshop-account
Oct 7, 2026
Merged

JonasJesus42 merged 1 commit into
mainfrom
JonasJesus42/nuvemshop-account

Conversation

@JonasJesus42

@JonasJesus42 JonasJesus42 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What

Customer accounts, a sitemap, and the apps-nuvemshop skill. The Storefront API has no customer endpoints, so each piece uses whichever upstream works. All of it was validated against a live store.

Feature Upstream Why this one
actions/account/register Admin API POST /customers, with a custom-app token (adminToken) The store's own form needs a reCAPTCHA tied to its domain. Posting without it fails silently: a 302 back to the form and no account is created. The Admin API has no captcha, so this action requires Cloudflare Turnstile (turnstileSecret) unless allowUnverifiedRegistration is set (demos only). A duplicate email returns 422, mapped to email_taken.
actions/account/login / logout the store's /account/login/ and /account/logout/, called server-side Success is a 302 to /account/. On failure, the reason is read from the login page's markup: js-login-general-error for wrong credentials, js-account-validation-pending when the email hasn't been confirmed yet (the store requires that for every new account).
loaders/user the store's /account/ for LS.customer, then Admin API /customers/<id> Returns null when logged out. Exposes only id, name, email and phone.
nuvemshopSitemap() Storefront API, paginated Matches the store's own /sitemap.xml: 50/50 products, same categories. The only difference is theme-only pages (/contato, /produtos).

store.ts bridges the session between our domain and the store:

  • only the request's store_* cookies are sent upstream, never the site's own;
  • only store_* Set-Cookie headers are re-emitted, through RequestContext.responseHeaders, with Domain rewritten to our host. The store's Cloudflare cookies are dropped.

The block's admin form gains storeUrl, adminToken (secret), turnstileSecret (secret) and allowUnverifiedRegistration.

The new skill, .agents/skills/apps-nuvemshop, covers:

  • Storefront API fields and limits;
  • listing parity with the theme;
  • accounts;
  • cart and checkout findings, including that Nuvemshop starts returning 403 on every /comprar/ (add to cart) from one IP after about 15 carts created in a few minutes.

It also gets a row in the CLAUDE.md skills table.

Stacked on #630.

Verified

  • 90 tests pass (16 new account tests, 1 sitemap test), and tsc, biome, skills:check, skills:readme:check and bun install --frozen-lockfile are clean.
  • Against the live demo store, through the package inside a RequestContext:
    • anonymous user returns null;
    • a wrong password returns invalid_credentials;
    • login succeeds, and user then returns the customer;
    • logout works, and user returns null again;
    • registering a taken email returns email_taken.
  • After login, the browser's cookie jar holds only store_session_payload_* and store_login_session.

🤖 Generated with Claude Code


Summary by cubic

Adds customer accounts (register, login, logout, user), a sitemap, and the apps-nuvemshop skill to the Nuvemshop app. The Storefront API has no customer or sitemap endpoints, so each piece uses whichever upstream works, validated against a live store.

Accounts

  • register posts to the Admin API POST /customers with a custom-app token (adminToken). The store's form needs a domain-bound reCAPTCHA and silently drops unverified posts; the Admin API has no captcha, so the action requires Cloudflare Turnstile (turnstileSecret) unless allowUnverifiedRegistration is set. Duplicate emails map to email_taken.
  • login and logout call the store's own /account/login/ and /account/logout/ server-side. Login failure reasons are read from the login page markup: js-login-general-error for wrong credentials, js-account-validation-pending for unconfirmed email.
  • The user loader returns the customer from the session — id from the store's /account/ page, profile from the Admin API — and null when logged out, exposing only id, name, email, and phone.
  • store.ts bridges the session: only the request's store_* cookies go upstream, and only store_* Set-Cookie headers return, re-emitted on the site's domain with Domain rewritten.

Sitemap and skill

  • nuvemshopSitemap() builds product and category URLs from the paginated Storefront API, matching the store's own sitemap paths.
  • The block's admin form gains storeUrl, adminToken (secret), turnstileSecret (secret), and allowUnverifiedRegistration; store, admin, and Turnstile calls get operation names.
  • The new .agents/skills/apps-nuvemshop skill documents API fields and limits, listing parity with the theme, accounts, and cart/checkout findings, including the Nuvemshop /comprar/ 403 IP block.

Written for commit e0fff42. Summary will update on new commits.

Review in cubic Turn on auto-fix

@JonasJesus42
JonasJesus42 changed the base branch from JonasJesus42/nuvemshop-admin-props to main October 7, 2026 01:19
@JonasJesus42
JonasJesus42 requested a review from a team October 7, 2026 01:19
…op skill

The Storefront API has no customer endpoints. Validated against a live store:

- register: Admin API POST /customers (custom-app `adminToken`). The store's
  own form needs a domain-bound reCAPTCHA and silently drops posts without
  it; the Admin API has no captcha, so the action requires Cloudflare
  Turnstile (`turnstileSecret`) unless `allowUnverifiedRegistration`.
  Duplicate email maps to `email_taken`.
- login/logout: the store's /account/login/ and /account/logout/, called
  server-side. Failure reasons come from the login page markup
  (`js-login-general-error`, `js-account-validation-pending`).
- user loader: customer id from the store's /account/ page, profile from the
  Admin API; null when logged out.
- store.ts bridges the session: only `store_*` cookies go upstream, and only
  `store_*` Set-Cookie come back, re-emitted on our domain.
- nuvemshopSitemap(): products + categories from the API with our URLs;
  matches the store's own sitemap (50/50 products, same categories).
- Config/admin form: storeUrl, adminToken and turnstileSecret (secrets),
  allowUnverifiedRegistration. Operation names for store/admin/turnstile calls.
- New skill .agents/skills/apps-nuvemshop (API fields/limits, listing parity,
  accounts, cart/checkout findings incl. the /comprar/ IP block).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@JonasJesus42
JonasJesus42 force-pushed the JonasJesus42/nuvemshop-account branch from 49db110 to e0fff42 Compare October 7, 2026 01:20
@JonasJesus42
JonasJesus42 merged commit dc0606c into main Oct 7, 2026
2 checks passed
JonasJesus42 added a commit that referenced this pull request Oct 7, 2026
…632)

## What

The minicart data for `@decocms/apps-nuvemshop`, using the Storefront
API.

The cart stays in the browser (`useCart`, `localStorage`). Checkout goes
through `POST /checkouts`. We don't use the theme's classic `/comprar/`
endpoint: after about 15 carts created in a few minutes, Nuvemshop
returned 403 on every `/comprar/` from that IP for over 40 minutes. A
server-side proxy would send every buyer through the Worker's egress
IPs, so one spike could block add-to-cart for the whole store. Details
are in the `apps-nuvemshop` skill.

- **`loaders/cart`** turns the client items into minicart lines: name,
variant, image, price, list price, subtotal and `maxQuantity`, plus
totals and savings.
- Products are fetched by `ids`, up to 30 per call, through the existing
SWR cache.
- **Quantities are clamped to stock**, because `POST /checkouts` rejects
a line above it with `422 checkout_rejected`.
- Missing, hidden and sold-out variants go to `unavailable` and are left
out of the totals.
- **`createCheckout`** now returns `{ error, message }` instead of
throwing. The success shape (`{ checkoutUrl }`) is unchanged.

  | API response | `error` |
  |---|---|
  | `422 coupon_rejected` | `coupon_rejected` |
  | `422 checkout_rejected` | `out_of_stock` |
  | `404 resource_not_found` | `unavailable` |
  | `400 invalid_request` | `invalid` |

  `NuvemshopApiError` carries the API's status and code.
- **Hooks:** `fetchCartDetails` and `useCartDetails()` back the
minicart; `checkout()` throws a `CheckoutError` with the code, so the UI
can show the right message.
- There is no coupon preview before checkout, because the API has none.
The checkout validates the coupon.

Stacked on #631.

## Verified

- 103 tests pass; `tsc`, biome and `skills:check` are clean.
- Live against the demo store:
- a 50-unit line is clamped to the variant's stock of 17, and the same
line without the clamp is rejected as `out_of_stock`;
  - a promotional item shows R$169.90 against a list price of R$199.90;
  - a removed variant lands in `unavailable`;
  - an invalid coupon returns `coupon_rejected`;
- the created checkout's subtotal and total (R$3,698.10) equal the
loader's subtotal.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a minicart loader and typed checkout errors to
`@decocms/apps-nuvemshop`, using the Storefront API instead of the
classic `/comprar/` endpoint (which IP-blocks server-side proxies after
a handful of carts).

- New `loaders/cart` resolves client-held `useCart` items into minicart
lines (name, variant, image, prices, subtotal) with totals and savings,
fetching products by `ids` (≤30 per call, cached like the checkout
proxy).
- Quantities are clamped to stock so `POST /checkouts` doesn't reject
lines with `422 checkout_rejected`; missing, hidden, and sold-out
variants are moved to `unavailable` and excluded from totals.
- `createCheckout` now returns `{ error, message }` on failures instead
of throwing, mapping API responses (`coupon_rejected`,
`checkout_rejected` → `out_of_stock`, `resource_not_found` →
`unavailable`, `invalid_request` → `invalid`); the success shape `{
checkoutUrl }` is unchanged.
- New `fetchCartDetails`/`useCartDetails` hooks back the minicart;
`checkout()` throws a `CheckoutError` carrying the code so the UI can
react.
- There is no coupon preview before checkout because the API has none.

<sup>Written for commit f767ad2.
Summary will update on new commits.</sup>

<a href="https://cubic.dev/pr/decocms/blocks/pull/632?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<a
href="https://www.cubic.dev/action/auto-fix/pr/decocms/blocks/632?returnTo=https%3A%2F%2Fgithub.com%2Fdecocms%2Fblocks%2Fpull%2F632&source=description"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img
alt="Turn on auto-fix"
src="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
@JonasJesus42
JonasJesus42 deleted the JonasJesus42/nuvemshop-account branch October 7, 2026 01:20
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.76.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant