import { html } from "lit"; import { customElement, state } from "lit/decorators.js"; import { getUserMe, invalidateUserMe } from "./Api"; import { type ClanInfo, type ClanMember, type ClanStats } from "./ClanApi"; import { BaseModal } from "./components/BaseModal"; import "./components/clan/ClanBansView"; import "./components/clan/ClanBrowseView"; import type { BrowseState } from "./components/clan/ClanBrowseView"; import "./components/clan/ClanCard"; import "./components/clan/ClanDetailView"; import "./components/clan/ClanManageView"; import "./components/clan/ClanMyRequestsView"; import "./components/clan/ClanRequestsView"; import type { ClanRole } from "./components/clan/ClanShared"; import "./components/clan/ClanTransferView"; import "./components/ConfirmDialog"; import "./components/CopyButton"; import { modalHeader } from "./components/ui/ModalHeader"; import { translateText } from "./Utils"; type View = | "list" | "detail" | "manage" | "transfer" | "requests" | "bans" | "my-requests"; @customElement("clan-modal") export class ClanModal extends BaseModal { protected routerName = "clan"; @state() private view: View = "list"; @state() private loading = false; @state() private myClans: ClanInfo[] = []; @state() private myPendingRequests: { tag: string; name: string; createdAt: string; }[] = []; @state() private selectedClanTag = ""; @state() private selectedClan: ClanInfo | null = null; @state() private myRole: ClanRole | null = null; private myPublicId: string | null = null; @state() private myClanRoles = new Map(); // Lifted browse state — survives tab switches private browseCache: BrowseState | null = null; // Lifted detail cache — survives sub-view navigation private detailCache: { tag: string; members: ClanMember[]; membersTotal: number; pendingRequestCount: number; stats: ClanStats | null; } | null = null; private get onListView(): boolean { return this.view === "list" && !this.selectedClanTag; } protected modalConfig() { return { tabs: this.onListView ? [ { key: "my-clans", label: translateText("clan_modal.my_clans") }, { key: "browse", label: translateText("clan_modal.browse") }, ] : [], }; } protected renderHeaderSlot() { return this.onListView ? modalHeader({ title: translateText("clan_modal.title"), onBack: () => this.close(), ariaLabel: translateText("common.back"), }) : this.renderSubViewHeader(); } protected renderBody() { return html`
${this.renderInner()}
`; } protected onTabEnter(tab: string): void { this.view = "list"; this.selectedClan = null; this.selectedClanTag = ""; if (tab === "my-clans") { this.loadMyClans(); } } private tagPill(tag: string) { return html`[${tag}]`; } private renderSubViewHeader() { const clan = this.selectedClan; const ariaLabel = translateText("common.back"); if (this.view === "my-requests") { return modalHeader({ title: translateText("clan_modal.pending_applications"), onBack: () => (this.view = "list"), ariaLabel, }); } if (this.view === "manage") { return modalHeader({ title: translateText("clan_modal.manage_clan"), onBack: () => (this.view = "detail"), ariaLabel, rightContent: clan ? this.tagPill(clan.tag) : undefined, }); } if (this.view === "transfer") { return modalHeader({ title: translateText("clan_modal.transfer_leadership"), onBack: () => (this.view = "manage"), ariaLabel, }); } if (this.view === "requests") { return modalHeader({ title: translateText("clan_modal.join_requests"), onBack: () => (this.view = "detail"), ariaLabel, }); } if (this.view === "bans") { return modalHeader({ title: translateText("clan_modal.banned_players"), onBack: () => (this.view = "manage"), ariaLabel, }); } // Default: detail return modalHeader({ title: clan?.name ?? translateText("clan_modal.title"), onBack: () => { this.view = "list"; this.selectedClan = null; this.selectedClanTag = ""; this.myRole = null; this.detailCache = null; }, ariaLabel, rightContent: clan ? this.tagPill(clan.tag) : undefined, }); } protected onOpen(): void { this.loadMyClans(); } protected onClose(): void { this.activeTab = "my-clans"; this.view = "list"; this.selectedClan = null; this.selectedClanTag = ""; this.myRole = null; this.browseCache = null; this.detailCache = null; } private async loadMyClans() { this.loading = true; try { const me = await getUserMe(); if (!this.isModalOpen) return; if (!me || Object.keys(me.user).length === 0) { window.dispatchEvent( new CustomEvent("show-message", { detail: { message: translateText("clan_modal.sign_in_for_clans"), color: "red", duration: 3000, }, }), ); this.close(); window.showPage?.("page-account"); return; } this.myPublicId = me.player.publicId; this.myPendingRequests = me.player.clanRequests ?? []; const roles = new Map(); const clans: ClanInfo[] = []; for (const c of me.player.clans ?? []) { roles.set(c.tag, c.role); clans.push({ tag: c.tag, name: c.name, description: "", isOpen: false, memberCount: c.memberCount, }); } this.myClanRoles = roles; this.myClans = clans; } finally { this.loading = false; } } private renderInner() { if (this.loading) { return this.renderLoadingSpinner(); } if (this.view === "my-requests") { return html` (this.view = "list")} @request-withdrawn=${(e: CustomEvent<{ tag: string }>) => { this.myPendingRequests = this.myPendingRequests.filter( (r) => r.tag !== e.detail.tag, ); if (this.myPendingRequests.length === 0) this.view = "list"; }} >`; } if (this.selectedClanTag) { if (this.view === "manage") { return html` (this.view = "detail")} @navigate-bans=${() => (this.view = "bans")} @navigate-transfer=${() => (this.view = "transfer")} @clan-updated=${(e: CustomEvent>) => { if (this.selectedClan) { this.selectedClan = { ...this.selectedClan, ...e.detail }; } this.detailCache = null; invalidateUserMe(); }} @clan-disbanded=${(e: CustomEvent<{ tag: string }>) => { const roles = new Map(this.myClanRoles); roles.delete(e.detail.tag); this.myClanRoles = roles; this.myClans = this.myClans.filter((c) => c.tag !== e.detail.tag); this.selectedClan = null; this.selectedClanTag = ""; this.myRole = null; this.detailCache = null; this.view = "list"; this.loadMyClans(); }} >`; } if (this.view === "transfer") { return html` (this.view = "manage")} @leadership-transferred=${() => { this.loadMyClans().then(() => this.openDetail(this.selectedClanTag), ); }} >`; } if (this.view === "requests") { return html` (this.view = "detail")} @request-approved=${() => { if (this.selectedClan) { this.selectedClan = { ...this.selectedClan, memberCount: (this.selectedClan.memberCount ?? 0) + 1, }; } this.detailCache = null; }} >`; } if (this.view === "bans") { return html` (this.view = "manage")} >`; } // Default: detail view return html` { this.view = "list"; this.selectedClan = null; this.selectedClanTag = ""; this.myRole = null; this.detailCache = null; }} @detail-loaded=${( e: CustomEvent<{ clan: ClanInfo; myRole: ClanRole | null; members: ClanMember[]; membersTotal: number; pendingRequestCount: number; stats: ClanStats | null; }>, ) => { this.selectedClan = e.detail.clan; this.myRole = e.detail.myRole; this.detailCache = { tag: e.detail.clan.tag, members: e.detail.members, membersTotal: e.detail.membersTotal, pendingRequestCount: e.detail.pendingRequestCount, stats: e.detail.stats, }; }} @navigate-manage=${() => (this.view = "manage")} @navigate-requests=${() => (this.view = "requests")} @clan-joined=${(e: CustomEvent<{ tag: string }>) => { this.myClanRoles = new Map([ ...this.myClanRoles, [e.detail.tag, "member" as ClanRole], ]); this.openDetail(e.detail.tag); }} @clan-left=${(e: CustomEvent<{ tag: string }>) => { const roles = new Map(this.myClanRoles); roles.delete(e.detail.tag); this.myClanRoles = roles; this.selectedClan = null; this.selectedClanTag = ""; this.myRole = null; this.detailCache = null; this.view = "list"; this.loadMyClans(); }} @request-sent=${(e: CustomEvent<{ tag: string; name: string }>) => { this.myPendingRequests = [ ...this.myPendingRequests, { tag: e.detail.tag, name: e.detail.name, createdAt: new Date().toISOString(), }, ]; }} >`; } // List view (my clans / browse) — header + tabs are rendered by o-modal return html` ${this.activeTab === "my-clans" ? this.renderMyClans() : html`) => { this.browseCache = e.detail; }} @clan-select=${(e: CustomEvent<{ tag: string }>) => this.openDetail(e.detail.tag)} >`} `; } private openDetail(tag: string) { this.selectedClanTag = tag; this.view = "detail"; } private renderMyClans() { const hasClans = this.myClans.length > 0; const hasRequests = this.myPendingRequests.length > 0; if (!hasClans && !hasRequests) { return html`

${translateText("clan_modal.no_clans")}

`; } return html`
${hasRequests ? this.renderPendingRequestsButton() : ""} ${this.myClans.map( (clan) => html` ) => this.openDetail(e.detail.tag)} > `, )}
`; } private renderPendingRequestsButton() { const count = this.myPendingRequests.length; return html` `; } }