From f83c5eb4f4f995bff4f7f0265cb078cea5770a86 Mon Sep 17 00:00:00 2001 From: SomiVista Date: Wed, 2 Sep 2026 17:50:49 +0300 Subject: [PATCH] feat(ui): rebuild Subscription on subturtle-ui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the four standalone pricing cards with the design's tablist + detail panel: a current-plan usage strip, one tab per tier, and the selected tier's price, CTA and features in a single panel. All `st-` tokens, no raw hex and no `dark:` variants, so the screen reads correctly under both themes. No new components or icons were needed — StSwitch, StBadge, StButton, StCard, StSkeleton, StEmptyState, StModal and solar:check-circle-bold all already ship. Notable decisions, and where they depart from the prototype: - The usage strip serves every tier, so StarterUsageCard (used only here) is deleted and the paid "This month" block folds into the same four meters. - `bg-st-sunken` for the detail panel's left rail, not the design's `--ink-50`: the ink ramp inverts in dark, where ink-50 lands within one channel step of --surface-card and the rail disappears. - Meter fill stays rose at rest as drawn, but keeps the amber/red cap warning Council 004 specced — the prototype's all-zero demo never shows it. - An unlimited allowance renders an empty accent track; a full bar would read as "at your cap". A free tier never reads Unlimited (FREE_CAPS fallback), and a Reader with no voice budget keeps the "Upgrade to Learner" upsell instead of a dead 0 / 0. - The ribbon uses Stripe's `badge` metadata, falling back to the i18n string. - Manage/change/downgrade CTAs are preserved; goToPortal no longer branches on trialing (the off-ramp's own "continue" calls it, which would have recursed), manageSubscription owns that. - The strip waits on isSubscriptionFetching so its defaults never render as a real answer, and dead `isLoading` state is dropped. theme-tokens.css and CLAUDE.md carried a note saying the design system's subturtle-theme.css supersedes ui/'s dark layer wholesale. Per the screen handoff that note predates the theme PR; ui/'s is the one that ships (RGB channels for Tailwind, the .theme-switching rule, measured contrast). Corrected in both places — comments only, no palette change. The e2e spec asserted against the old card layout and VoiceMeter sub-line; its assertions are updated to the new structure. 8/8 pass. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 5 +- frontend/components/StarterUsageCard.vue | 75 - .../components/subscription/CheckoutPanel.vue | 284 ++-- frontend/locales/en.json | 25 +- frontend/pages/settings/subscription.vue | 1259 +++++++++-------- .../tests/e2e/specs/subscription-ui.spec.ts | 60 +- ui/src/styles/theme-tokens.css | 39 +- 7 files changed, 890 insertions(+), 857 deletions(-) delete mode 100644 frontend/components/StarterUsageCard.vue diff --git a/CLAUDE.md b/CLAUDE.md index ca5c7eaf..ea4d64d6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -181,8 +181,9 @@ promoted to `dev` in batches. While it is in progress: writing the `.dark` class and `plugins/theme.client.ts` mirrors the preference into pilotui's store, so the two never disagree, but they are not styled to match and will not be until each screen migrates. - ⚠️ `ui/src/styles/theme-tokens.css` still holds the EARLIER handoff's palette. The design - system's own `subturtle-theme.css` supersedes it wholesale — see the banner in that file. + `ui/src/styles/theme-tokens.css` is the dark layer that ships. The design system carries + its own `subturtle-theme.css`, but that is a reference for values only — it writes hex + where Tailwind needs RGB channels — so diff the two rather than swapping the file in. - `subturtle-ui` is a `link:../ui` dependency whose `dist/` is not committed, so the frontend's `postinstall` builds it. Any context that installs the frontend needs `ui/` present — the Dockerfile copies it in. diff --git a/frontend/components/StarterUsageCard.vue b/frontend/components/StarterUsageCard.vue deleted file mode 100644 index b80021a2..00000000 --- a/frontend/components/StarterUsageCard.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - diff --git a/frontend/components/subscription/CheckoutPanel.vue b/frontend/components/subscription/CheckoutPanel.vue index 92d5d63a..b42456c0 100644 --- a/frontend/components/subscription/CheckoutPanel.vue +++ b/frontend/components/subscription/CheckoutPanel.vue @@ -1,158 +1,150 @@ diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 3772431b..463eec87 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -204,6 +204,7 @@ "subscription": { "title": "Subscription", "subscription-plans": "Subscription Plans", + "overline": "Membership", "plans-price-details": "Plans & Price Details", "invoice-date": "Invoice Date:", "due-date": "Due Date:", @@ -268,7 +269,16 @@ "continue-free": "Continue with Free", "loading": "Loading plans…", "error": "Plans are temporarily unavailable due to a payment system issue. Please check back soon.", - "empty": "No plans available right now." + "empty": "No plans available right now.", + "popular": "Popular", + "current": "Current", + "trial": "Trial", + "canceling": "Canceling", + "billed-yearly": "Billed yearly. Save 20%.", + "billed-monthly": "Billed monthly.", + "footnote": "Stripe shows your local currency at checkout. Switch or cancel any time.", + "error-title": "Plans unavailable", + "empty-title": "No plans yet" }, "checkout": { "title": "Subscribe to {plan}", @@ -360,7 +370,6 @@ "ai-usage": "AI translations & explanations: {n} used" }, "starter-usage": { - "title": "Free plan", "saved-phrases": "Saved phrases", "text-chats": "Text chats", "live-sessions": "Live sessions", @@ -424,19 +433,19 @@ "no-account-cta": "Just sign in and you will get a new account.", "chrome-extension": "Chrome extension", "waiting": { - "label": "Waiting for Google\u2026", + "label": "Waiting for Google…", "hint": "A Google window should have opened. Nothing there?", "retry": "Open it again" }, "returning": { - "title": "Signing you in\u2026", + "title": "Signing you in…", "subtitle": "Taking you to your dashboard." }, "notice": { "expired": "Your session ended, so we brought you here. Nothing you saved was lost.", "failed": { "title": "That didn't go through", - "message": "The Google window closed before you finished. Nothing was saved \u2014 try once more." + "message": "The Google window closed before you finished. Nothing was saved — try once more." } }, "preview": { @@ -450,14 +459,14 @@ "start-review": "Start review", "time-left": "3 min left", "session-title": "Live session", - "session-body": "Say this week's phrases out loud with Coach. Money Heist \u2014 s1e4.", + "session-body": "Say this week's phrases out loud with Coach. Money Heist — s1e4.", "start-session": "Start session", "resting-title": "Resting", "resting-body": "Levels 4 and 5 are asleep. Their next cards land on Thursday.", "optional": "Optional practice", "phrase-1": "Je n'en reviens pas", "phrase-1-translation": "I can't believe it", - "phrase-2": "\u00c7a vaut le coup", + "phrase-2": "Ça vaut le coup", "phrase-2-translation": "It's worth it" } }, @@ -736,7 +745,7 @@ "light": "Light", "dark": "Dark", "system": "System", - "resolved": "{mode} \u00b7 {resolved}", + "resolved": "{mode} · {resolved}", "aria": "Theme: {current}. Switch to {next}." } } diff --git a/frontend/pages/settings/subscription.vue b/frontend/pages/settings/subscription.vue index c46a1b90..21cca56a 100644 --- a/frontend/pages/settings/subscription.vue +++ b/frontend/pages/settings/subscription.vue @@ -1,636 +1,729 @@