import { html, LitElement, TemplateResult } from "lit"; import { customElement, query, state } from "lit/decorators.js"; import { PlayerGame, PlayerStatsTree, UserMeResponse, } from "../core/ApiSchemas"; import { fetchPlayerById, getUserMe } from "./Api"; import { discordLogin, logOut, sendMagicLink } from "./Auth"; import "./components/baseComponents/stats/DiscordUserHeader"; import "./components/baseComponents/stats/GameList"; import "./components/baseComponents/stats/PlayerStatsTable"; import "./components/baseComponents/stats/PlayerStatsTree"; import "./components/Difficulties"; import "./components/PatternButton"; import { isInIframe, translateText } from "./Utils"; @customElement("account-modal") export class AccountModal extends LitElement { @query("o-modal") private modalEl!: HTMLElement & { open: () => void; close: () => void; }; @state() private email: string = ""; @state() private isLoadingUser: boolean = false; private userMeResponse: UserMeResponse | null = null; private statsTree: PlayerStatsTree | null = null; private recentGames: PlayerGame[] = []; constructor() { super(); document.addEventListener("userMeResponse", (event: Event) => { const customEvent = event as CustomEvent; if (customEvent.detail) { this.userMeResponse = customEvent.detail as UserMeResponse; if (this.userMeResponse?.player?.publicId === undefined) { this.statsTree = null; this.recentGames = []; } } else { this.statsTree = null; this.recentGames = []; this.requestUpdate(); } }); } createRenderRoot() { return this; } render() { return html` ${this.isLoadingUser ? html`

${translateText("account_modal.fetching_account")}

` : this.renderInner()}
`; } private renderInner() { if (this.userMeResponse?.user) { return this.renderAccountInfo(); } else { return this.renderLoginOptions(); } } private renderAccountInfo() { return html`

${translateText("account_modal.player_id", { id: this.userMeResponse?.player?.publicId ?? translateText("account_modal.not_found"), })}

${this.renderLoggedInAs()}

${this.renderPlayerStats()}
`; } private renderLoggedInAs(): TemplateResult { const me = this.userMeResponse?.user; if (me?.discord) { return html`

${translateText("account_modal.linked_account", { account_name: me.discord.global_name ?? "", })}

${this.renderLogoutButton()}`; } else if (me?.email) { return html`

${translateText("account_modal.linked_account", { account_name: me.email, })}

${this.renderLogoutButton()}`; } return this.renderLoginOptions(); } private renderPlayerStats(): TemplateResult { return html`
this.viewGame(id)} > `; } private viewGame(gameId: string): void { this.close(); const path = location.pathname; const { search } = location; const hash = `#join=${encodeURIComponent(gameId)}`; const newUrl = `${path}${search}${hash}`; history.pushState({ join: gameId }, "", newUrl); window.dispatchEvent(new HashChangeEvent("hashchange")); } private renderLogoutButton(): TemplateResult { return html` `; } private renderLoginOptions() { return html`
or
`; } private handleEmailInput(e: Event) { const target = e.target as HTMLInputElement; this.email = target.value; } private async handleSubmit() { if (!this.email) { alert(translateText("account_modal.enter_email_address")); return; } const success = await sendMagicLink(this.email); if (success) { alert( translateText("account_modal.recovery_email_sent", { email: this.email, }), ); } else { alert(translateText("account_modal.failed_to_send_recovery_email")); } } private handleDiscordLogin() { discordLogin(); } public open() { this.modalEl?.open(); this.isLoadingUser = true; void getUserMe() .then((userMe) => { if (userMe) { this.userMeResponse = userMe; if (this.userMeResponse?.player?.publicId) { this.loadPlayerProfile(this.userMeResponse.player.publicId); } } this.isLoadingUser = false; this.requestUpdate(); }) .catch((err) => { console.warn("Failed to fetch user info in AccountModal.open():", err); this.isLoadingUser = false; this.requestUpdate(); }); this.requestUpdate(); } public close() { this.modalEl?.close(); } private async handleLogout() { await logOut(); this.close(); // Refresh the page after logout to update the UI state window.location.reload(); } private async loadPlayerProfile(publicId: string): Promise { try { const data = await fetchPlayerById(publicId); if (!data) { this.requestUpdate(); return; } this.recentGames = data.games; this.statsTree = data.stats; this.requestUpdate(); } catch (err) { console.warn("Failed to load player data:", err); this.requestUpdate(); } } } @customElement("account-button") export class AccountButton extends LitElement { @state() private loggedInEmail: string | null = null; @state() private loggedInDiscord: string | null = null; private isVisible = true; @query("account-modal") private recoveryModal: AccountModal; constructor() { super(); document.addEventListener("userMeResponse", (event: Event) => { const customEvent = event as CustomEvent; if (customEvent.detail) { const userMeResponse = customEvent.detail as UserMeResponse; if (userMeResponse.user.email) { this.loggedInEmail = userMeResponse.user.email; this.requestUpdate(); } else if (userMeResponse.user.discord) { this.loggedInDiscord = userMeResponse.user.discord.id; this.requestUpdate(); } } else { // Clear the logged in states when user logs out this.loggedInEmail = null; this.loggedInDiscord = null; this.requestUpdate(); } }); } createRenderRoot() { return this; } render() { if (isInIframe()) { return html``; } if (!this.isVisible) { return html``; } let buttonTitle = ""; if (this.loggedInEmail) { buttonTitle = translateText("account_modal.linked_account", { account_name: this.loggedInEmail, }); } else if (this.loggedInDiscord) { buttonTitle = translateText("account_modal.linked_account"); } return html`
`; } private renderIcon() { if (this.loggedInDiscord) { return html`Discord`; } else if (this.loggedInEmail) { return html`Email`; } return html`Logged Out`; } private open() { this.recoveryModal?.open(); } public close() { this.isVisible = false; this.recoveryModal?.close(); this.requestUpdate(); } }