Skip to content

Subscription card + resize dispatch + WS plumbing (closes #20)#22

Merged
max-tet merged 1 commit into
mainfrom
issue/20-subscription-card-resize-ws
May 17, 2026
Merged

Subscription card + resize dispatch + WS plumbing (closes #20)#22
max-tet merged 1 commit into
mainfrom
issue/20-subscription-card-resize-ws

Conversation

@ClaydeCode
Copy link
Copy Markdown
Contributor

@ClaydeCode ClaydeCode commented May 16, 2026

Summary

  • New <b-card title="Subscription"> in Settings with five render states (Trial / Interstitial / Active / Active+pending / Grace), driven by a single subscriptionState computed.
  • resizeShard() now redirects to the PayPal approval_url when present, otherwise falls through to the existing /restart navigation. Size buttons disable while a pending_vm_size is in flight.
  • Subscribe / Reactivate button POSTs the subscribe endpoint and redirects on the returned approval_url.
  • Vuex handle_websocket_message dispatches force_query_profile_data on the subscription_updated WS event so the UI converges without a reload. Component-local 3s/60s polling fallback covers WS-missed deliveries during the post-PayPal interstitial.
  • New pure helper src/lib/pricing.js mirrors the landing-page formula for the Subscribe-button label only. Active / Grace render price_cents from the controller directly so existing subscribers stay grandfathered.

Depends on

Recommended reading order

  1. src/lib/pricing.js — pure helper.
  2. src/store.js — WS dispatch.
  3. src/views/Settings.vue — UI states + polling + subscribe / resize dispatch.
  4. tests/unit/pricing.spec.js, tests/unit/store.spec.js — unit tests.
  5. package.json — Jest devDeps + test:unit script + Jest preset.
  6. babel.config.js — unchanged (@vue/cli-plugin-babel/preset already covers Jest's babel-jest transform).

Manual QA checklist

  • Trial state: subscription=null, delete_after set → banner shows date + Subscribe button with €X/month
  • Trial + ?sub=cancel → warning banner, dismissible, URL cleared on dismiss
  • Interstitial: navigate to /settings?sub=return with null subscription → spinner + "Activating…"
  • Interstitial timeout: wait 60s (or stub) → Refresh button visible
  • Active: subscription with status=active → plan/amount/next-charge/payer + "Manage on PayPal" opens new tab
  • Active+pending: set pending_vm_size → upgrade-pending alert + Size buttons disabled
  • Grace: status=grace + delete_after → "Subscription ended" + Reactivate button
  • Subscribe click → POSTs subscribe endpoint → window.location set to approval URL
  • Resize subscribed (response has approval_url) → redirects to PayPal
  • Resize unsubscribed (no approval_url) → navigates to /restart
  • WS event injected (via shard-core /core/management/notify) → profile refetches
  • PayPal return: status converges to active → query param cleared without manual reload

Verification run locally

  • npm install — OK (Jest 26 + @vue/cli-plugin-unit-jest@~4.5.0 added).
  • npm run lint — clean.
  • npm run test:unit — 13/13 pass (10 pricing + 3 store).
  • npm run build — production build OK (pre-existing bundle-size warnings only).
  • npm run serve — dev server boots, GET / returns 200 with the SPA shell.

Notes

  • Component-level render tests for the five UI states are intentionally skipped (per scope decision). Manual QA above covers them.

Closes #20

🤖 Generated with Claude Code

Adds the PayPal billing UI surface to the Settings view:

- New `<b-card title="Subscription">` with five render states (trial /
  interstitial / active / active-pending / grace) driven by a single
  `subscriptionState` computed.
- `resizeShard()` now branches on `response.data.approval_url`: if
  present, redirects to PayPal; otherwise falls through to the existing
  `/restart` navigation. Size buttons disable while a `pending_vm_size`
  is in flight (avoids the controller's 409 path).
- `subscribe()` POSTs the subscribe endpoint and redirects on the
  returned approval URL. Reused for the Grace-state "Reactivate" CTA.
- `handle_websocket_message` dispatches `force_query_profile_data` on
  the `subscription_updated` WS event so the UI converges without a
  reload.
- Component-local 3s/60s polling fallback for the post-PayPal
  interstitial state to cover WS-missed deliveries; cleared on
  unmount and on watcher-observed convergence.
- New pure helper `src/lib/pricing.js` mirroring the formula in
  `landing-page/src/components/Pricing.astro` for the Subscribe button
  label only. Active / Grace render `price_cents` from the controller
  directly so existing subscribers stay grandfathered.

Why now: PayPal-billing UI work. Depends on Profile.volume_size_gb
exposure (FreeshardBase/freeshard#84) for the Subscribe price label —
degrades gracefully to `€—` until that ships.

Tests: Jest infra added (no prior test setup). 13 unit tests cover
the pricing helper and the WS-dispatch contract. UI render states get
manual QA per the PR description (component tests intentionally
skipped per scope decision).

Closes #20

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@max-tet max-tet merged commit 65ace3c into main May 17, 2026
2 checks passed
@max-tet max-tet deleted the issue/20-subscription-card-resize-ws branch May 17, 2026 11:04
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.

Settings.vue Subscription card + resize endpoint dispatch

2 participants