Skip to content

Replace self-service checkout with a needs form and booking step - #1011

Merged
spashii merged 4 commits into
mainfrom
feat/pricing-configurator
Aug 25, 2026
Merged

Replace self-service checkout with a needs form and booking step#1011
spashii merged 4 commits into
mainfrom
feat/pricing-configurator

Conversation

@spashii

@spashii spashii commented Aug 25, 2026

Copy link
Copy Markdown
Member

Self-service checkout is replaced by a needs form and a booking step. A person who reaches a plan limit answers a few questions and books a call; existing subscribers keep managing their subscription.

Draft. Translations, analytics capture and a final walkthrough are still open. See the list at the end.

What changes for a person

  • Blocked controls stay visible. Clicking one opens a small popover that names the feature, with one button, Tell us what you need. The standing "Available on a paid plan" boxes are gone.
  • One modal from every mount point. Step 1 names what the person tried and says a few questions follow and end in a booking step. Then: what for, when, recordings in the first year, conversations at the busiest moment, anything else you might need. Nothing is required; skip is real; a reload restores the answers; the step lives in the url so the back button undoes a step.
  • Every free text box has voice input through the existing composer where a transcription context exists.
  • The booking step embeds cal.com with a reference and a plain summary of the answers prefilled. If the embed does not load within 8 seconds, a plain link takes over with the same information.
  • The confirmation says the call is booked only after cal.com confirms it; otherwise it asks the person to check their mail. The mail link carries the reference in its subject.
  • Billing page: an existing subscriber keeps plan, amounts, invoices, cancel and resume. A managed account keeps its panel. A free account sees no checkout and the same form entry. No new subscription can be started from the app.
  • Documentation describes plan capabilities and directs pricing questions to the contact flow.
  • User-facing copy was refreshed.

What changes underneath

  • Directus, additive only: one new collection pricing_configuration, one idempotent migration in echo/directus/migrations/, the snapshot carries it. No existing field or collection is changed.
  • API: POST /api/v2/pricing-configurations, upsert on config_session_id. Identity fields are derived server side; answers are saved before optional audio, and storage failures are reported separately. 16 tests.
  • CSP (frontend/vercel.json): app.cal.com and cal.com in frame-src, script-src, connect-src and font-src, for the embed.
  • Events: analytics hooks are wired; capture is not enabled in this PR.

Deployment

Apply the additive schema change before deploying. The frontend calls the new endpoint on its final step; if the frontend lands before the API, that step retries until the API is up, and nothing is lost.

Checks

  • Frontend: tsc, biome lint, vite build, vitest for the pricing, workspace and billing components. Two failures in useChunkAnchorScroll.test.tsx predate this branch (missing jsdom directive).
  • Server: ruff, tests/test_pricing_configurations.py. The migration is idempotent (second run is a no-op); upsert and access guards verified against a local stack.
  • Existing billing surfaces verified unchanged for paying, managed, free and cancelled accounts.
  • Catalogues extracted and compiled.

Still open before this leaves draft

  • Dutch translations for the new strings remain under review; other locales fall back to English.
  • Analytics capture.
  • Voice input where no transcription context is present.
  • The create-workspace access step still uses the old disabled state instead of the popover.
  • A live check of the cal.com embed on the preview deployment.

Comment thread echo/frontend/src/components/pricing/PricingConfigurator.tsx Fixed
Comment thread echo/frontend/src/components/pricing/PricingConfigurator.tsx Fixed
Comment thread echo/frontend/src/components/pricing/PricingConfigurator.tsx Fixed
Comment thread echo/frontend/src/components/pricing/PricingConfigurator.tsx Fixed
@spashii spashii changed the title Kill the SaaS billing page: the pricing intake replaces the plan picker (phase 1) Replace self-service checkout with a needs form and booking step Aug 25, 2026
@spashii
spashii force-pushed the feat/pricing-configurator branch 3 times, most recently from 0cc836a to eb66a77 Compare August 25, 2026 07:15
A person who reaches a plan limit answers a few questions and books a
call; existing subscribers keep managing their subscription. Blocked
controls stay visible and open a popover that names the feature; one
modal serves every mount point, with the step in the url, nothing
required, a real skip, and answers restored after a reload. Free text
takes voice through the existing composer where a transcription context
exists. The booking step embeds cal.com with a reference and a plain
summary prefilled, and falls back to a plain link when the embed does
not load. The confirmation reports a booking only after cal.com confirms
it.

Underneath: one additive Directus collection with an idempotent
migration and its snapshot; an upsert endpoint keyed on the session id
with identity derived server side and answers saved before optional
audio; the content security policy admits the cal.com embed; analytics
hooks are wired and not yet captured; the billing surface shows no
checkout to accounts without a subscription and is unchanged for
subscribers and managed accounts; the tier price components and the
personal booking link are removed; documentation describes plan
capabilities and points pricing questions to the contact flow;
catalogues are extracted and compiled.
@spashii
spashii force-pushed the feat/pricing-configurator branch from eb66a77 to 8afa9f3 Compare August 25, 2026 07:16
The stateless transcription endpoint accepts purpose=pricing_intake from
any signed-in session with no project: nothing is metered, the size cap
stays, a tighter per-user rate limit applies, and every existing caller
path is unchanged. The intake's free text boxes use it where no project
exists, so the record button renders on organisation and workspace
mounts as well. The create-workspace access step opens the same popover
as the other gated controls instead of showing disabled options. The
Dutch catalogue carries every new intake string.
When cal.com confirms a booking inside the intake, the client reports
the uid, status and start time to the same endpoint under the same
session id, fire and forget with one retry; the confirmation never
waits. The server writes the uid and status onto the row and keeps the
start time in the row's json; a booking write never lowers the row's
status, the same uid merges, a different uid wins and re-arms the
notification. A scheduled forwarder, shaped like the support one, posts
one message per unnotified booking to the support webhook with
kind pricing_booking, the reference, the booking, the contact fields and
a short summary of the answers, stamps the row only on a 2xx, and is a
no-op without the webhook settings. One new field, booking_notified_at,
in the migration and the snapshot.
@spashii
spashii marked this pull request as ready for review August 25, 2026 10:37
@spashii
spashii enabled auto-merge August 25, 2026 10:37
@spashii
spashii merged commit b97052c into main Aug 25, 2026
14 of 16 checks passed
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