mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-24 04:53:45 +00:00
feat(client): custom plutonium amount purchase in store (#4515)
## Summary Adds a **custom plutonium amount** purchase option to the Store, wiring the client to the new `POST /stripe/create-custom-currency-checkout` endpoint (infra [PR #400](https://github.com/openfrontio/infra/pull/400)). Players can buy any integer **20–2000** plutonium, priced inline server-side at **20 plutonium = \$1.00**. A new `<custom-currency-card>` renders as the **last tile in the Store "Packs" tab** — a slider + number input with a live price and a "Buy for \$X.XX" button that redirects to Stripe Checkout. ## Changes | File | Change | |---|---| | `src/client/Api.ts` | New `createCustomCurrencyCheckout(hardAmount)` — POSTs `{ hardAmount, hostname }`, returns the Stripe `url` (mirrors `createCheckoutSession`). | | `src/client/components/CustomCurrencyCard.ts` | New `<custom-currency-card>` component: plutonium slider + number input, live price, clamps to 20–2000 integer client-side, redirects to Stripe on buy. | | `src/client/Store.ts` | Renders the card after the fixed packs in the "Packs" tab; drops the now-unreachable "no packs" empty state. | | `src/client/Main.ts` | Handles `type=custom_currency` in the `#purchase-completed` redirect (success alert + strip hash; balance refetches from `/users/@me` on load). | | `src/client/LangSelector.ts` | Registers `custom-currency-card` for translation-reload re-render. | | `resources/lang/en.json` | Adds `store.buy_for`, `store.custom_amount`, `store.custom_currency_purchase_success`; removes the orphaned `store.no_packs`. | ## Notes - **No catalog product** — unlike currency packs, the custom amount is priced inline server-side, so the card is standalone (not a `resolveCosmetics` item). - **Async credit** — no optimistic balance bump; the balance reflects the Stripe webhook via `/users/@me` on return, matching the currency-pack flow. - Server is authoritative on bounds/rate; client-side clamping is UX-only. ## Testing - Full suite green: **1842 tests / 153 files**; `tsc --noEmit` and eslint clean. - Manually verified in the running app (headless Chromium): card renders in the Packs tab; amount/price stay in sync across slider + number field; clamping confirmed (5000→2000/\$100.00, 5→20/\$1.00, 750→\$37.50 — matches the pricing table). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+4
-8
@@ -6,6 +6,7 @@ import { Cosmetics } from "../core/CosmeticSchemas";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/CosmeticButton";
|
||||
import "./components/CustomCurrencyCard";
|
||||
import "./components/EffectsGrid";
|
||||
import "./components/NotLoggedInWarning";
|
||||
import { modalHeader } from "./components/ui/ModalHeader";
|
||||
@@ -163,14 +164,8 @@ export class StoreModal extends BaseModal {
|
||||
this.affiliateCode,
|
||||
).filter((r) => r.type === "pack" && r.relationship === "purchasable");
|
||||
|
||||
if (items.length === 0) {
|
||||
return html`<div
|
||||
class="text-white/40 text-sm font-bold uppercase tracking-wider text-center py-8"
|
||||
>
|
||||
${translateText("store.no_packs")}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// The custom-amount card is always purchasable (priced inline server-side,
|
||||
// no catalog entry), and follows the fixed packs at the end of the grid.
|
||||
return html`
|
||||
<div
|
||||
class="flex flex-wrap gap-4 p-8 justify-center items-stretch content-start"
|
||||
@@ -183,6 +178,7 @@ export class StoreModal extends BaseModal {
|
||||
></cosmetic-button>
|
||||
`,
|
||||
)}
|
||||
<custom-currency-card></custom-currency-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user