mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 10:23:18 +00:00
fix(store): specific rate-limit message on tier change + use shared dialogs for subscription flows (#4622)
## Summary - The change-tier API allows one tier change per minute per player (`playerRateLimit` on `/subscriptions/@me/change-tier`). The client previously lumped the 429 in with every other failure and showed "Couldn't update your subscription. Please try again." — retrying immediately just fails again. A 429 now gets its own message: *"You just changed tiers. Please wait a minute before changing again."* - Replaced all native `alert()` / `window.confirm()` calls in the subscription flows with the shared `showInGameAlert` / `showInGameConfirm` helpers (styled `<confirm-dialog>`, consistent with the rest of the store, and safe on CrazyGames): - Tier change: already-subscribed notice, upgrade/downgrade confirmation (warning variant with "Change Tier" heading), rate-limited/failure/success notices - Dollar checkout failures (first-time subscribe path) - Subscription panel: cancel confirmation (danger variant), cancel failure/success, portal-open failure The currency-purchase alerts in Cosmetics.ts (`login_required`, `purchase_failed`, `purchase_success`) are untouched — subscriptions can't reach that path. ## Test plan - [x] Typecheck, ESLint, Prettier - [ ] Manual: change tier twice within a minute against the real API and confirm the rate-limit dialog appears 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+5
-1
@@ -410,7 +410,7 @@ export async function cancelSubscription(): Promise<boolean> {
|
||||
|
||||
export async function changeSubscriptionTier(
|
||||
tierName: string,
|
||||
): Promise<boolean> {
|
||||
): Promise<boolean | "rate_limited"> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${getApiBase()}/subscriptions/@me/change-tier`,
|
||||
@@ -427,6 +427,10 @@ export async function changeSubscriptionTier(
|
||||
await logOut();
|
||||
return false;
|
||||
}
|
||||
// The API allows one tier change per minute per player.
|
||||
if (response.status === 429) {
|
||||
return "rate_limited";
|
||||
}
|
||||
if (!response.ok) {
|
||||
console.error(
|
||||
"changeSubscriptionTier: request failed",
|
||||
|
||||
+19
-8
@@ -29,6 +29,7 @@ import {
|
||||
invalidateUserMe,
|
||||
purchaseWithCurrency,
|
||||
} from "./Api";
|
||||
import { showInGameAlert, showInGameConfirm } from "./InGameModal";
|
||||
import { translateText } from "./Utils";
|
||||
|
||||
export const TEMP_FLARE_OFFSET = 1 * 60 * 1000; // 1 minute
|
||||
@@ -92,7 +93,7 @@ export async function purchaseCosmetic(
|
||||
|
||||
if (currentSub) {
|
||||
if (currentSub.tier === sub.name) {
|
||||
alert(translateText("store.already_subscribed"));
|
||||
await showInGameAlert(translateText("store.already_subscribed"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,17 +109,27 @@ export async function purchaseCosmetic(
|
||||
const confirmKey = isUpgrade
|
||||
? "store.confirm_upgrade"
|
||||
: "store.confirm_downgrade";
|
||||
const confirmed = window.confirm(
|
||||
const confirmed = await showInGameConfirm(
|
||||
translateText(confirmKey, { tier: targetName }),
|
||||
{
|
||||
heading: translateText("account_modal.change_tier"),
|
||||
variant: "warning",
|
||||
},
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
const ok = await changeSubscriptionTier(sub.name);
|
||||
if (!ok) {
|
||||
alert(translateText("store.change_tier_failed"));
|
||||
const result = await changeSubscriptionTier(sub.name);
|
||||
if (result === "rate_limited") {
|
||||
await showInGameAlert(translateText("store.change_tier_rate_limited"));
|
||||
return;
|
||||
}
|
||||
alert(translateText("store.change_tier_success", { tier: targetName }));
|
||||
if (!result) {
|
||||
await showInGameAlert(translateText("store.change_tier_failed"));
|
||||
return;
|
||||
}
|
||||
await showInGameAlert(
|
||||
translateText("store.change_tier_success", { tier: targetName }),
|
||||
);
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
@@ -126,7 +137,7 @@ export async function purchaseCosmetic(
|
||||
|
||||
if (method === "dollar") {
|
||||
if (!c.product) {
|
||||
alert(translateText("store.checkout_failed"));
|
||||
await showInGameAlert(translateText("store.checkout_failed"));
|
||||
return;
|
||||
}
|
||||
const url = await createCheckoutSession(
|
||||
@@ -134,7 +145,7 @@ export async function purchaseCosmetic(
|
||||
colorPaletteName,
|
||||
);
|
||||
if (url === false) {
|
||||
alert(translateText("store.checkout_failed"));
|
||||
await showInGameAlert(translateText("store.checkout_failed"));
|
||||
return;
|
||||
}
|
||||
window.location.href = url;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
openSubscriptionPortal,
|
||||
} from "../Api";
|
||||
import { translateCosmetic } from "../Cosmetics";
|
||||
import { showInGameAlert, showInGameConfirm } from "../InGameModal";
|
||||
import { translateText } from "../Utils";
|
||||
import "./baseComponents/Button";
|
||||
import "./CapIcon";
|
||||
@@ -28,7 +29,9 @@ export class SubscriptionPanel extends LitElement {
|
||||
private handleManage = async (): Promise<void> => {
|
||||
const url = await openSubscriptionPortal();
|
||||
if (url === false) {
|
||||
alert(translateText("account_modal.subscription_portal_failed"));
|
||||
await showInGameAlert(
|
||||
translateText("account_modal.subscription_portal_failed"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
@@ -39,16 +42,21 @@ export class SubscriptionPanel extends LitElement {
|
||||
};
|
||||
|
||||
private handleCancel = async (): Promise<void> => {
|
||||
const confirmed = window.confirm(
|
||||
const confirmed = await showInGameConfirm(
|
||||
translateText("account_modal.cancel_subscription_confirm"),
|
||||
{ heading: translateText("account_modal.cancel_subscription") },
|
||||
);
|
||||
if (!confirmed) return;
|
||||
const ok = await cancelSubscription();
|
||||
if (!ok) {
|
||||
alert(translateText("account_modal.cancel_subscription_failed"));
|
||||
await showInGameAlert(
|
||||
translateText("account_modal.cancel_subscription_failed"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
alert(translateText("account_modal.cancel_subscription_success"));
|
||||
await showInGameAlert(
|
||||
translateText("account_modal.cancel_subscription_success"),
|
||||
);
|
||||
invalidateUserMe();
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user