Subscription card + resize dispatch + WS plumbing (closes #20)#22
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
<b-card title="Subscription">in Settings with five render states (Trial / Interstitial / Active / Active+pending / Grace), driven by a singlesubscriptionStatecomputed.resizeShard()now redirects to the PayPalapproval_urlwhen present, otherwise falls through to the existing/restartnavigation. Size buttons disable while apending_vm_sizeis in flight.Subscribe/Reactivatebutton POSTs the subscribe endpoint and redirects on the returnedapproval_url.handle_websocket_messagedispatchesforce_query_profile_dataon thesubscription_updatedWS event so the UI converges without a reload. Component-local 3s/60s polling fallback covers WS-missed deliveries during the post-PayPal interstitial.src/lib/pricing.jsmirrors the landing-page formula for the Subscribe-button label only. Active / Grace renderprice_centsfrom the controller directly so existing subscribers stay grandfathered.Depends on
Profile.volume_size_gb, which the pricing helper uses for the Subscribe-button label. Defensivenullfallback (€—) remains in place for legacy shards wherevolume_size_gbis unset upstream.Recommended reading order
src/lib/pricing.js— pure helper.src/store.js— WS dispatch.src/views/Settings.vue— UI states + polling + subscribe / resize dispatch.tests/unit/pricing.spec.js,tests/unit/store.spec.js— unit tests.package.json— Jest devDeps +test:unitscript + Jest preset.babel.config.js— unchanged (@vue/cli-plugin-babel/presetalready covers Jest's babel-jest transform).Manual QA checklist
€X/month?sub=cancel→ warning banner, dismissible, URL cleared on dismiss/settings?sub=returnwith null subscription → spinner + "Activating…"pending_vm_size→ upgrade-pending alert + Size buttons disabledwindow.locationset to approval URLapproval_url) → redirects to PayPalapproval_url) → navigates to/restart/core/management/notify) → profile refetchesVerification run locally
npm install— OK (Jest 26 +@vue/cli-plugin-unit-jest@~4.5.0added).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
Closes #20
🤖 Generated with Claude Code