import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { approveClanRequest, type ClanInfo, type ClanJoinRequest, denyClanRequest, fetchClanRequests, } from "../../ClanApi"; import { translateText } from "../../Utils"; import "../CopyButton"; import { filterRequestsBySearch, formatClanDate, renderLoadingSpinner, renderMemberSearchInput, renderServerPagination, showToast, } from "./ClanShared"; @customElement("clan-requests-view") export class ClanRequestsView extends LitElement { createRenderRoot() { return this; } @property() clanTag = ""; @property({ type: Object }) selectedClan: ClanInfo | null = null; @state() private requests: ClanJoinRequest[] = []; @state() private requestsTotal = 0; @state() private requestsPage = 1; @state() private requestsLimit = 20; @state() private memberActionPending = false; @state() private loading = false; private memberSearch = ""; private memberSearchDebounce: ReturnType | null = null; connectedCallback() { super.connectedCallback(); this.loadRequests(1); } disconnectedCallback() { if (this.memberSearchDebounce) clearTimeout(this.memberSearchDebounce); super.disconnectedCallback(); } private async loadRequests(page: number, showLoading = true) { if (showLoading) this.loading = true; else this.memberActionPending = true; try { const data = await fetchClanRequests(this.clanTag, page); if (!data) { if (showLoading) showToast(translateText("clan_modal.failed_to_load_requests"), "red"); return; } this.requests = data.results; this.requestsTotal = data.total; this.requestsLimit = data.limit; this.requestsPage = page; } finally { if (showLoading) this.loading = false; else this.memberActionPending = false; } } private async handleApprove(publicId: string) { if (this.memberActionPending) return; this.memberActionPending = true; try { const result = await approveClanRequest(this.clanTag, publicId); if (result !== true) { showToast(translateText(result.error), "red"); return; } this.requests = this.requests.filter((r) => r.publicId !== publicId); this.requestsTotal--; this.dispatchEvent( new CustomEvent("request-approved", { detail: { publicId }, bubbles: true, composed: true, }), ); showToast(translateText("clan_modal.request_approved"), "green"); } finally { this.memberActionPending = false; } } private async handleDeny(publicId: string) { if (this.memberActionPending) return; this.memberActionPending = true; try { const result = await denyClanRequest(this.clanTag, publicId); if (result !== true) { showToast(translateText(result.error), "red"); return; } this.requests = this.requests.filter((r) => r.publicId !== publicId); this.requestsTotal--; this.dispatchEvent( new CustomEvent("request-denied", { detail: { publicId }, bubbles: true, composed: true, }), ); showToast(translateText("clan_modal.request_denied"), "green"); } finally { this.memberActionPending = false; } } private onSearchInput(e: Event) { if (this.memberSearchDebounce) clearTimeout(this.memberSearchDebounce); this.memberSearchDebounce = setTimeout(() => { this.memberSearch = (e.target as HTMLInputElement).value; this.requestUpdate(); }, 200); } render() { if (this.loading) return renderLoadingSpinner(); const totalPages = Math.ceil(this.requestsTotal / this.requestsLimit); const filtered = filterRequestsBySearch(this.requests, this.memberSearch); return html`
${translateText("clan_modal.pending_requests_count", { count: this.requestsTotal, })}
${renderMemberSearchInput( (e) => this.onSearchInput(e), "clan_modal.search_requests_placeholder", )} ${filtered.length === 0 ? html`

${translateText("clan_modal.no_requests")}

` : html`
${filtered.map( (req) => html`
${translateText("clan_modal.requested_on", { tag: this.selectedClan?.tag ?? this.clanTag, date: formatClanDate(req.createdAt), })}
`, )}
`} ${totalPages > 1 ? renderServerPagination(this.requestsPage, totalPages, (p) => this.loadRequests(p, false), ) : ""}
`; } }