Skip to content

feat(billing): paid-seat paywall UI + i18n - #5167

Open
rafavalls wants to merge 1 commit into
mainfrom
rafavalls/paid-seat-paywall
Open

feat(billing): paid-seat paywall UI + i18n#5167
rafavalls wants to merge 1 commit into
mainfrom
rafavalls/paid-seat-paywall

Conversation

@rafavalls

@rafavalls rafavalls commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

When a free seat attempts a paid action, the backend returns a [PAID_SEAT_REQUIRED] 403 (same convention as [CREDITS]). This PR turns that into a role-aware paywall experience instead of a generic error toast.

Scope: frontend only (UI + i18n). The checkout / seat wiring is intentionally left as a single seam (SubscribeCta) for a billing follow-up.

What's included

  • is-paid-seat-error.ts — pure [PAID_SEAT_REQUIRED] prefix detector mirroring is-credit-error.ts, with unit tests.
  • paid-seat-store.ts — tiny useSyncExternalStore so the global MutationCache can open the paywall from outside React.
  • Triggers (both already wired):
    • Non-chat mutations (connection/agent creation, etc.) via a global MutationCache.onError → root-mounted PaidSeatPaywallHost.
    • Chat streaming errors, inline via the chat highlight stack (mirrors the existing credits-exhausted path).
  • PaidSeatDialog — dark, branded surface (deco-green spotlight), role-aware:
    • owner/admin → Subscribe (placeholder)
    • other members → "ask an admin"
    • responsive / mobile-safe, design-system tokens + @deco/ui Button.
  • SubscribeCta — standalone component, the single seam for the billing follow-up.
  • i18n — new paywall domain, en + pt-BR mirrored (typecheck enforces completeness).

Left for follow-up (dev to wire)

The Subscribe button currently shows a "coming soon" toast. Wire SubscribeCta.onClick to:

  • ORGANIZATION_BILLING_CHECKOUT_START when the org has no subscription (first paid seat), or
  • the seat-add flow (ORGANIZATION_SEATS_SET, members page) when the org is already subscribed (2nd+ seat).

Read ORGANIZATION_SEATS_GET to pick between the two and swap the CTA label accordingly.

Testing

  • bun test (paywall + chat highlight): green.
  • bun run check (typecheck, incl. i18n en/pt-BR mirror): green.
  • bun run fmt / bun run lint: clean (no new findings in touched files).
  • Verified live (owner + member variants, light + dark, and mobile viewport — no overflow).

Screenshots

Owner (dark) and mobile were verified locally; the surface is a dark spotlight card with the deco-green badge, a capability list, and a white Subscribe CTA.

🤖 Generated with Claude Code


Summary by cubic

Adds a role-aware paid-seat paywall shown when the backend returns [PAID_SEAT_REQUIRED], replacing the generic error with a clear dialog. Frontend-only (UI + i18n); checkout wiring is left to a single SubscribeCta seam.

  • New Features

    • Detects [PAID_SEAT_REQUIRED] and opens a paywall via global @tanstack/react-query MutationCache.onError; chat streaming errors open it inline in the highlight stack.
    • Role-aware dialog: owner/admin see a SubscribeCta; members get guidance. Responsive, dark surface using @deco/ui.
    • Added is-paid-seat-error, a tiny external store + PaidSeatPaywallHost, and a new paywall i18n domain (en, pt-BR) with tests.
  • Migration

    • Wire SubscribeCta.onClick to ORGANIZATION_BILLING_CHECKOUT_START (no subscription) or to the seat-add flow when already subscribed.

Written for commit 6b56eef. Summary will update on new commits.

Review in cubic

When a free seat attempts a paid action the backend returns a
`[PAID_SEAT_REQUIRED]` 403. This turns that into a role-aware paywall
instead of a generic error toast.

Frontend only (UI + i18n). The checkout/seat wiring is left as a single
seam in SubscribeCta for a follow-up.

- is-paid-seat-error: pure `[PAID_SEAT_REQUIRED]` prefix detector
  (mirrors is-credit-error) + unit tests
- paid-seat-store: tiny external store so the global MutationCache can
  open the paywall from outside React
- Triggers: global MutationCache.onError (connection/agent creation etc.)
  → root-mounted PaidSeatPaywallHost; and chat streaming errors inline via
  the chat highlight stack
- PaidSeatDialog: dark branded surface (deco-green spotlight), role-aware —
  owner/admin see Subscribe (placeholder), members are pointed at an admin;
  responsive/mobile-safe, design-system tokens + Button
- SubscribeCta: standalone slot for the billing follow-up
- i18n: new `paywall` domain, en + pt-BR mirrored

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant