import type { TemplateResult } from "lit"; import { html } from "lit"; import { customElement, state } from "lit/decorators.js"; import { UserMeResponse } from "../core/ApiSchemas"; import { Cosmetics } from "../core/CosmeticSchemas"; import { UserSettings } from "../core/game/UserSettings"; import { BaseModal } from "./components/BaseModal"; import "./components/CosmeticButton"; import "./components/NotLoggedInWarning"; import { modalHeader } from "./components/ui/ModalHeader"; import { fetchCosmetics, purchaseCosmetic, resolveCosmetics, } from "./Cosmetics"; import { translateText } from "./Utils"; @customElement("store-modal") export class StoreModal extends BaseModal { @state() private activeTab: "patterns" | "flags" | "packs" = "patterns"; private cosmetics: Cosmetics | null = null; private isActive = false; private affiliateCode: string | null = null; private userMeResponse: UserMeResponse | false = false; connectedCallback() { super.connectedCallback(); document.addEventListener( "userMeResponse", (event: CustomEvent) => { this.onUserMe(event.detail); }, ); } async onUserMe(userMeResponse: UserMeResponse | false) { this.userMeResponse = userMeResponse; this.cosmetics = await fetchCosmetics(); this.refresh(); } private renderHeader(): TemplateResult { return modalHeader({ title: translateText("store.title"), onBack: () => this.close(), ariaLabel: translateText("common.back"), rightContent: html``, }); } private renderPatternGrid(): TemplateResult { const items = resolveCosmetics( this.cosmetics, this.userMeResponse, this.affiliateCode, ).filter( (r) => r.type === "pattern" && r.relationship !== "blocked" && r.relationship !== "owned", ); if (items.length === 0) { return html`
${translateText("store.no_skins")}
`; } return html`
${items.map( (r) => html` `, )}
`; } private renderFlagGrid(): TemplateResult { const items = resolveCosmetics( this.cosmetics, this.userMeResponse, this.affiliateCode, ).filter( (r) => r.type === "flag" && r.relationship !== "blocked" && r.relationship !== "owned", ); if (items.length === 0) { return html`
${translateText("store.no_flags")}
`; } const selectedFlag = new UserSettings().getFlag() ?? ""; return html`
${items.map( (r) => html` `, )}
`; } private renderPackGrid(): TemplateResult { const items = resolveCosmetics( this.cosmetics, this.userMeResponse, this.affiliateCode, ).filter((r) => r.type === "pack" && r.relationship === "purchasable"); if (items.length === 0) { return html`
${translateText("store.no_packs")}
`; } return html`
${items.map( (r) => html` `, )}
`; } render() { if (!this.isActive && !this.inline) return html``; const tabs = [ { key: "packs", label: translateText("store.packs") }, { key: "patterns", label: translateText("store.patterns") }, { key: "flags", label: translateText("store.flags") }, ]; const grid = this.activeTab === "patterns" ? this.renderPatternGrid() : this.activeTab === "flags" ? this.renderFlagGrid() : this.renderPackGrid(); return html` (this.activeTab = key as "patterns" | "flags" | "packs")} >
${this.renderHeader()}
${grid}
`; } public async open(options?: string | { affiliateCode?: string }) { if (this.isModalOpen) return; this.isActive = true; if (typeof options === "string") { this.affiliateCode = options; } else if ( options !== null && typeof options === "object" && !Array.isArray(options) ) { this.affiliateCode = options.affiliateCode ?? null; } else { this.affiliateCode = null; } this.cosmetics ??= await fetchCosmetics(); await this.refresh(); super.open(); } public close() { this.isActive = false; this.affiliateCode = null; super.close(); } public async refresh() { this.requestUpdate(); } }