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`
`;
}
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`
`;
} else if (this.loggedInEmail) {
return html`
`;
}
return html`
`;
}
private open() {
this.recoveryModal?.open();
}
public close() {
this.isVisible = false;
this.recoveryModal?.close();
this.requestUpdate();
}
}