-
Notifications
You must be signed in to change notification settings - Fork 1k
Add subscriptions: store tab, account panel, manage/cancel #3918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,14 @@ import { | |||||||||||||||||||||||||||
| UserMeResponse, | ||||||||||||||||||||||||||||
| } from "../core/ApiSchemas"; | ||||||||||||||||||||||||||||
| import { assetUrl } from "../core/AssetUrls"; | ||||||||||||||||||||||||||||
| import { fetchPlayerById, getUserMe } from "./Api"; | ||||||||||||||||||||||||||||
| import { Cosmetics } from "../core/CosmeticSchemas"; | ||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||
| cancelSubscription, | ||||||||||||||||||||||||||||
| fetchPlayerById, | ||||||||||||||||||||||||||||
| getUserMe, | ||||||||||||||||||||||||||||
| invalidateUserMe, | ||||||||||||||||||||||||||||
| openSubscriptionPortal, | ||||||||||||||||||||||||||||
| } from "./Api"; | ||||||||||||||||||||||||||||
| import { discordLogin, logOut, sendMagicLink } from "./Auth"; | ||||||||||||||||||||||||||||
| import "./components/baseComponents/stats/DiscordUserHeader"; | ||||||||||||||||||||||||||||
| import "./components/baseComponents/stats/GameList"; | ||||||||||||||||||||||||||||
|
|
@@ -17,7 +24,9 @@ import { BaseModal } from "./components/BaseModal"; | |||||||||||||||||||||||||||
| import "./components/CopyButton"; | ||||||||||||||||||||||||||||
| import "./components/CurrencyDisplay"; | ||||||||||||||||||||||||||||
| import "./components/Difficulties"; | ||||||||||||||||||||||||||||
| import "./components/SubscriptionPanel"; | ||||||||||||||||||||||||||||
| import { modalHeader } from "./components/ui/ModalHeader"; | ||||||||||||||||||||||||||||
| import { fetchCosmetics } from "./Cosmetics"; | ||||||||||||||||||||||||||||
| import { translateText } from "./Utils"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @customElement("account-modal") | ||||||||||||||||||||||||||||
|
|
@@ -28,6 +37,7 @@ export class AccountModal extends BaseModal { | |||||||||||||||||||||||||||
| private userMeResponse: UserMeResponse | null = null; | ||||||||||||||||||||||||||||
| private statsTree: PlayerStatsTree | null = null; | ||||||||||||||||||||||||||||
| private recentGames: PlayerGame[] = []; | ||||||||||||||||||||||||||||
| private cosmetics: Cosmetics | null = null; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| constructor() { | ||||||||||||||||||||||||||||
| super(); | ||||||||||||||||||||||||||||
|
|
@@ -157,6 +167,8 @@ export class AccountModal extends BaseModal { | |||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| ${this.renderSubscriptionPanel()} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| <!-- Middle Row: Stats Section --> | ||||||||||||||||||||||||||||
| ${this.hasAnyStats() | ||||||||||||||||||||||||||||
| ? html`<div | ||||||||||||||||||||||||||||
|
|
@@ -192,6 +204,46 @@ export class AccountModal extends BaseModal { | |||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private renderSubscriptionPanel(): TemplateResult | "" { | ||||||||||||||||||||||||||||
| const sub = this.userMeResponse?.player?.subscription; | ||||||||||||||||||||||||||||
| if (!sub) return ""; | ||||||||||||||||||||||||||||
| const cosmetic = this.cosmetics?.subscriptions?.[sub.tier] ?? null; | ||||||||||||||||||||||||||||
| return html`<subscription-panel | ||||||||||||||||||||||||||||
| .sub=${sub} | ||||||||||||||||||||||||||||
| .cosmetic=${cosmetic} | ||||||||||||||||||||||||||||
| @subscription-manage=${this.handleManageSubscription} | ||||||||||||||||||||||||||||
| @subscription-cancel=${this.handleCancelSubscription} | ||||||||||||||||||||||||||||
| ></subscription-panel>`; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private handleManageSubscription = async (): Promise<void> => { | ||||||||||||||||||||||||||||
| const url = await openSubscriptionPortal(); | ||||||||||||||||||||||||||||
| if (url === false) { | ||||||||||||||||||||||||||||
| alert(translateText("account_modal.subscription_portal_failed")); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| window.open(url, "_blank", "noopener,noreferrer"); | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private handleCancelSubscription = async (): Promise<void> => { | ||||||||||||||||||||||||||||
| const confirmed = window.confirm( | ||||||||||||||||||||||||||||
| translateText("account_modal.cancel_subscription_confirm"), | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| if (!confirmed) return; | ||||||||||||||||||||||||||||
| const ok = await cancelSubscription(); | ||||||||||||||||||||||||||||
| if (!ok) { | ||||||||||||||||||||||||||||
| alert(translateText("account_modal.cancel_subscription_failed")); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| alert(translateText("account_modal.cancel_subscription_success")); | ||||||||||||||||||||||||||||
| invalidateUserMe(); | ||||||||||||||||||||||||||||
| const userMe = await getUserMe(); | ||||||||||||||||||||||||||||
| if (userMe) { | ||||||||||||||||||||||||||||
| this.userMeResponse = userMe; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| this.requestUpdate(); | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private renderCurrency(): TemplateResult { | ||||||||||||||||||||||||||||
| const currency = this.userMeResponse?.player?.currency; | ||||||||||||||||||||||||||||
| if (!currency) return html``; | ||||||||||||||||||||||||||||
|
|
@@ -377,6 +429,11 @@ export class AccountModal extends BaseModal { | |||||||||||||||||||||||||||
| protected onOpen(): void { | ||||||||||||||||||||||||||||
| this.isLoadingUser = true; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void fetchCosmetics().then((cosmetics) => { | ||||||||||||||||||||||||||||
| this.cosmetics = cosmetics; | ||||||||||||||||||||||||||||
| this.requestUpdate(); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
Comment on lines
+432
to
+435
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for If 🛡️ Proposed fix- void fetchCosmetics().then((cosmetics) => {
- this.cosmetics = cosmetics;
- this.requestUpdate();
- });
+ void fetchCosmetics()
+ .then((cosmetics) => {
+ this.cosmetics = cosmetics;
+ this.requestUpdate();
+ })
+ .catch((err) => {
+ console.warn("Failed to fetch cosmetics in AccountModal:", err);
+ this.requestUpdate();
+ });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void getUserMe() | ||||||||||||||||||||||||||||
| .then((userMe) => { | ||||||||||||||||||||||||||||
| if (userMe) { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for
getUserMe()after successful cancellation.If
cancelSubscription()succeeds but the subsequentgetUserMe()call fails (line 240), the UI won't update to reflect the cancellation. The user will see the success alert but the subscription panel will continue to display.🛡️ Proposed fix
alert(translateText("account_modal.cancel_subscription_success")); invalidateUserMe(); - const userMe = await getUserMe(); - if (userMe) { - this.userMeResponse = userMe; - } - this.requestUpdate(); + try { + const userMe = await getUserMe(); + if (userMe) { + this.userMeResponse = userMe; + } + this.requestUpdate(); + } catch (err) { + console.warn("Failed to refresh user after cancel:", err); + this.requestUpdate(); + }🤖 Prompt for AI Agents