fix(showcase): configure the Fiat checkout panel on the deployed site (runtime embed base) - #75
Conversation
…onfigures on the deployed site
The /t/[slug] tutorial pages are statically prerendered (generateStaticParams),
so reading process.env.NVM_EMBED_BASE_URL in the server component froze the value
at `next build` (unset → "" → a permanent "checkout isn't configured" notice) no
matter what env the pod carries. That's why the deployed Fiat panel stayed
unconfigured even after the ArgoCD env was set (argocd#628).
Move the read to a dynamic route (GET /api/embed-base) and have FiatRunPanel
fetch it on mount — the same "runtime env in a route handler" pattern the live
weather panel already uses (app/api/agent). Keeps every tutorial page SSG.
- new: showcase/app/api/embed-base/route.ts (force-dynamic, returns { embedBase })
- FiatRunPanel: fetch embedBase on mount; pick buttons disabled until loaded
- page.tsx: drop the build-time embedBase prop
Verified: build clean, /t/[slug] stays ● SSG, /api/embed-base is ƒ; running the
standalone server with NVM_EMBED_BASE_URL set returns it from the route.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYjpDX5ZRMGrpSZ8zAMLg3
|
👀 Reviewing |
r-marques
left a comment
There was a problem hiding this comment.
🤖 Automated PR review — 🟡 Mergeable with nits
Moves the hosted-checkout origin off the SSG page and behind a dynamic route handler the panel fetches at mount. The diagnosis is right and is the non-obvious part: /t/[slug] is statically generated, so process.env.NVM_EMBED_BASE_URL read there is frozen at next build — unset at build time means a permanent "not configured" notice no amount of redeploying fixes. No blockers.
What I verified
- Diff range
origin/main...HEAD— 3 files, +39/−12, headc16411f85. - No panel — a 39-line diff, verified directly. Saying so rather than implying coverage I do not have.
- The new
nullstate does not open a fail-open window. Thenvm:successlistener isif (!embedOrigin || e.origin !== embedOrigin) return;, andembedOriginderives to""whileembedBaseisnull— so during the fetch, every inboundpostMessageis rejected.pick()is guarded the same way. - A malformed
NVM_EMBED_BASE_URLalso fails closed —new URL(embedBase).originis wrapped intry/catchreturning"", so an operator typo (a host with no scheme, say) yields the notice rather than an unhandledTypeErrorin render. I went looking for that crash specifically; it is not there. force-dynamicis correct for this version.next@^15.5.25, where route handlers are already dynamic by default — so the comment's "handlers are always dynamic" is accurate here and the export is defence in depth, not a contradiction.
One thing worth reporting back across repos. Reviewing argocd#628 earlier today I raised a should-fix that argocd cannot check the claim "org key stays server-side", since the guard lives in this repo. It does hold: NVM_ORDER_API_KEY is read only in showcase/app/api/orders/route.ts — a route handler — and sent as Authorization: Bearer. FiatRunPanel.tsx is "use client" and mentions the name only in a comment and an error string; it never reads it from process.env, and the unprefixed name means Next would not inline it regardless. That finding can be considered answered.
Verdict: 🟡 Mergeable with nits — 0 blockers, 1 should-fix, 2 nits. The should-fix is a missing timeout whose failure mode is a permanently inert panel; the pattern to copy is already in the same file.
🧭 Inline comments (3)
Each is posted on its line in Files changed.
- 🟡 SHOULD FIX — This fetch has no timeout, and a hang disables every button permanently —
showcase/components/FiatRunPanel.tsx:58 - 💡 NIT — Confirm the response is not cacheable by the CDN in front of the site —
showcase/app/api/embed-base/route.ts:14 - 💡 NIT — Disabled-while-loading has no affordance saying so —
showcase/components/FiatRunPanel.tsx:207
r-marques
left a comment
There was a problem hiding this comment.
🟡 Approved with comments — no blockers; 1 should-fix, 2 nit(s) left inline.
Inline review: #75 (review)
- embed-base fetch: bound with AbortSignal.timeout (EMBED_CFG_TIMEOUT_MS=8s) so a never-settling request falls back to "" (the notice) instead of leaving every package button disabled forever - /api/embed-base: send Cache-Control: no-store so the Cloudflare edge can't cache one env value and re-freeze it (force-dynamic governs Next, not the CDN) - picking UI: show a "connecting to secure checkout…" spinner + button title while embedBase is still loading, so the disabled row isn't silent Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYjpDX5ZRMGrpSZ8zAMLg3
Why this matters
The Fiat Checkout tutorial's "See it run" panel on tutorials.nevermined.app still showed "Checkout isn't configured here (NVM_EMBED_BASE_URL is unset)" even after the deploy config was wired up (argocd#628). Visitors couldn't try the card-checkout flow the tutorial teaches. This makes the panel pick up the deployed configuration so it actually runs — pick a trip, pay with a test card, see the booking confirmed.
Root cause
app/t/[slug]/page.tsxis statically prerendered (generateStaticParams, nodynamic/revalidate). It readprocess.env.NVM_EMBED_BASE_URLin the server component and passed it to the panel as a prop — so the value was captured atnext build(where the var is unset →""→ the permanent "not configured" notice). No runtime env on the pod could change an already-prerendered page. The/api/ordersroute worked only because route handlers are dynamic and read env at request time.Fix
Read the origin at request time, matching the pattern the live weather panel already uses (
app/api/agentreadsWEATHER_AGENT_URLin a route handler):app/api/embed-base/route.ts—force-dynamic, returns{ embedBase }fromNVM_EMBED_BASE_URLat request time.FiatRunPanel— fetches/api/embed-baseon mount; pick buttons disabled until it loads; on failure falls back to""(the notice).page.tsx— drops the build-timeembedBaseprop.Every tutorial page stays SSG; only the tiny config route is dynamic.
Test plan
npm run buildclean; route table shows/t/[slug]●(SSG) preserved,/api/embed-baseƒ(dynamic).NVM_EMBED_BASE_URL=https://embed.nevermined.app→GET /api/embed-basereturns{"embedBase":"https://embed.nevermined.app"}; SSG page does not inline the URL (panel fetches it).4242payment →nvm:success→ booked.🤖 Generated with Claude Code
https://claude.ai/code/session_01GYjpDX5ZRMGrpSZ8zAMLg3