Skip to content

fix(showcase): configure the Fiat checkout panel on the deployed site (runtime embed base) - #75

Merged
aaitor merged 2 commits into
mainfrom
fix/fiat-panel-runtime-embed-base
Sep 11, 2026
Merged

aaitor merged 2 commits into
mainfrom
fix/fiat-panel-runtime-embed-base

Conversation

@aaitor

@aaitor aaitor commented Sep 11, 2026

Copy link
Copy Markdown
Member

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.tsx is statically prerendered (generateStaticParams, no dynamic/revalidate). It read process.env.NVM_EMBED_BASE_URL in the server component and passed it to the panel as a prop — so the value was captured at next 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/orders route 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/agent reads WEATHER_AGENT_URL in a route handler):

  • new app/api/embed-base/route.tsforce-dynamic, returns { embedBase } from NVM_EMBED_BASE_URL at request time.
  • FiatRunPanel — fetches /api/embed-base on mount; pick buttons disabled until it loads; on failure falls back to "" (the notice).
  • page.tsx — drops the build-time embedBase prop.

Every tutorial page stays SSG; only the tiny config route is dynamic.

Test plan

  • npm run build clean; route table shows /t/[slug] (SSG) preserved, /api/embed-base ƒ (dynamic).
  • Standalone server with NVM_EMBED_BASE_URL=https://embed.nevermined.appGET /api/embed-base returns {"embedBase":"https://embed.nevermined.app"}; SSG page does not inline the URL (panel fetches it).
  • After deploy: panel on tutorials.nevermined.app mounts the checkout iframe (no notice); full 4242 payment → nvm:success → booked.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GYjpDX5ZRMGrpSZ8zAMLg3

…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
@aaitor
aaitor requested a review from r-marques September 11, 2026 13:41
@r-marques

Copy link
Copy Markdown
Member

👀 Reviewing

@r-marques r-marques left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 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, head c16411f85.
  • No panel — a 39-line diff, verified directly. Saying so rather than implying coverage I do not have.
  • The new null state does not open a fail-open window. The nvm:success listener is if (!embedOrigin || e.origin !== embedOrigin) return;, and embedOrigin derives to "" while embedBase is null — so during the fetch, every inbound postMessage is rejected. pick() is guarded the same way.
  • A malformed NVM_EMBED_BASE_URL also fails closednew URL(embedBase).origin is wrapped in try/catch returning "", so an operator typo (a host with no scheme, say) yields the notice rather than an unhandled TypeError in render. I went looking for that crash specifically; it is not there.
  • force-dynamic is 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

Comment thread showcase/components/FiatRunPanel.tsx Outdated
Comment thread showcase/app/api/embed-base/route.ts Outdated
Comment thread showcase/components/FiatRunPanel.tsx Outdated

@r-marques r-marques left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟡 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
@aaitor
aaitor merged commit 0c8f3d1 into main Sep 11, 2026
2 checks passed
@aaitor
aaitor deleted the fix/fiat-panel-runtime-embed-base branch September 11, 2026 14:16
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