Repository navigation
Subscription pricing: free hour, monthly and yearly plans - #144
Open
alpha5611331 wants to merge 17 commits into
Open
alpha5611331 wants to merge 17 commits into
alpha5611331 wants to merge 17 commits into
Conversation
…on-plans AppState gains subscribed, subscriptionExpiresAt and subscriptionPlan. credits stays the free balance: balanceState reads free_credits when the backend sends it, since its own credits is, for a subscriber, a compatibility figure for clients that predate subscriptions. An older backend's credits is used as before. Plans now come from /api/payment/subscription-plans, and history rows carry days. Refs #143 Co-Authored-By: Claude Opus 5.5 <[email protected]>
The payment page becomes Subscription: the current plan (pass and end date, or free time left) and the monthly and yearly passes through the existing crypto checkout. The title bar, stealth status panel and home strip show the plan and end date or the free time left, warning in the last three days. Subscribers may always start and get no low-balance toasts; the mock setup says unlimited. Copy for the gate, 4402 and the trial notice points to the subscription, in English and Russian. Refs #143 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Refs #143 Co-Authored-By: Claude Opus 5.5 <[email protected]>
"Your free hour is used up" was shown to anyone unsubscribed at a zero balance, including a reseller's customer who never had a free hour and a subscriber whose pass lapsed. Say "No free time left" instead (en, ru). Co-Authored-By: Claude Opus 5.5 <[email protected]>
- isPro becomes isPopular: the highlighted card is whichever plan the backend marks popular, and no plan is called Pro any more. - One planNameOf helper replaces a cast repeated at the purchase summary. - The history column key is purchase, matching its label. - The balance fetch error says the plan failed to load instead of "credits". Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
… tab Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…accounts The renderer could write subscribed / subscriptionExpiresAt / subscriptionPlan through app:update-state, and subscribed overrides the free balance in the start gate. Sign-in, sign-out and an unanswered first ping now reset the whole balance slice, so a subscriber's plan no longer shows for the next account on the machine. Subscribed without an end date reads as not subscribed, so the gate and the display cannot disagree. Co-Authored-By: Claude Opus 5.5 <[email protected]>
A refused start said 'No free time left' while the title bar showed time left. One freeTimeLabel now serves the title bar and the home plan line, and the Plans tab and refusal copy say less than a minute. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Nothing called them: the renderer's usePayment().getCredits had no consumer, so the payment:get-credits IPC, its preload bridge and PaymentService.getCredits were reachable only from each other, and PaymentService.pollPaymentStatus had no caller at all. The status tab polls through getStatus, and the ping already refreshes the balance and subscription every few seconds. The PR had ported the dead path to the subscription balance; removing it instead, with the two error strings only it used. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Passes never renew, and the only warning was the title bar turning "Ends in N days" yellow, which nobody reads until an interview stops at zero free time. useSubscriptionExpiryWarning, mounted in MainFrame so it runs on every route, toasts once at each of 3, 2 and 1 days left (keyed by the expiry, so buying another pass re-arms it) and once when a subscription this session saw active lapses. A lapse before launch and the undefined in between two accounts on sign-out never read as one. Both offer "See plans". SPEC also stops naming /api/payment/credits, which the client no longer calls. Co-Authored-By: Claude Opus 5.5 <[email protected]>
… the free hour Co-Authored-By: Claude Opus 5.5 <[email protected]>
…nest Source-level, like interview-lock.test.mjs, because each condition reads like something to simplify and none breaks a type or a lint: "ended" fires only on an active-to-lapsed transition (not on launch, not across sign-out), "ending soon" is keyed by expiry and day count, and the hook stays mounted in MainFrame so a lapse on any route is announced. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This branch has not been deployed
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.
Closes #143
Moves the app to subscription pricing (PowerInterviewAI/backend#76): a free hour once on the free-tier model, then a $20 monthly or $96 yearly prepaid pass with unlimited interviews on the SOTA model.
Changes
subscribed,subscriptionExpiresAtandsubscriptionPlan.creditsstays the free balance:balanceState(src/main/utils/balance.ts) readsfree_creditswhen the backend sends it, because the backend's owncreditsis, for a subscriber, a compatibility figure for clients older than this one. Against an older backend it falls back tocreditsand reads as not subscribed./api/payment/subscription-plans, with "Save 60%" computed from the served prices. It goes through the existing crypto checkout and notes that passes never renew. Buying while subscribed says the days are added after the current end.useSubscriptionExpiryWarning, mounted inMainFrame, toasts at 3, 2 and 1 days left (re-armed when a new pass moves the end date) and once when a subscription seen active this session lapses, each with "See plans". Never for one that lapsed before launch, and never across a sign-out.useNowkeeps the clock in state, since the React Compiler refusesDate.now()in render.usePayment().getCredits, thepayment:get-creditsIPC and bridge,PaymentService.getCredits) and the never-calledpollPaymentStatus; the ping refreshes the balance every 5 seconds.Deploy
Ship after the backend, and confirm
/api/payment/subscription-plansis in prodopenapi.json. Against an older backend, the plan list fails with the plans error and everything else falls back to the credits view. This carries new features, so dispatch the release withminor.Testing
pnpm lint,tsc -p tsconfig.electron.json,tsc -b,vite build,pnpm test:main: all pass.test/balance.test.mjs(subscriber / free / old backend), plus subscriber cases incredit-gate.test.mjs./creditsand/subscription-plansfields this client reads match the backend branch's generated OpenAPI schema.days_granted; plans map to monthly $20 and yearly $96 at $8; sign-in and sign-out clear the balance.tsc, the build and the source-level tests only.🤖 Generated with Claude Code