import { FolkShape } from "./folk-shape"; import { css, html } from "./tags"; const styles = css` :host { background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); min-width: 320px; min-height: 280px; } .header { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: #6d28d9; color: white; border-radius: 8px 8px 0 0; font-size: 12px; font-weight: 600; cursor: move; } .header-title { display: flex; align-items: center; gap: 6px; } .header-title .symbol { opacity: 0.8; font-weight: 400; font-size: 11px; } .header-actions button { background: transparent; border: none; color: white; cursor: pointer; padding: 2px 6px; border-radius: 4px; font-size: 14px; } .header-actions button:hover { background: rgba(255, 255, 255, 0.2); } .ledger-body { padding: 12px; } .summary-row { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 4px; } .summary-row .label { color: #64748b; } .summary-row .value { font-weight: 600; color: #1e293b; } .holder-list { max-height: 220px; overflow-y: auto; margin-top: 10px; } .holder-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; font-size: 12px; border-radius: 4px; margin-bottom: 3px; background: #f8fafc; } .holder-item:hover { background: #f1f5f9; } .holder-info { display: flex; align-items: center; gap: 6px; } .holder-icon { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: white; flex-shrink: 0; } .holder-name { color: #1e293b; font-weight: 500; } .holder-memo { font-size: 9px; color: #94a3b8; } .holder-amount { font-weight: 600; color: #6d28d9; white-space: nowrap; } .escrow-badge { font-size: 9px; background: #fef3c7; color: #92400e; padding: 1px 5px; border-radius: 3px; margin-left: 4px; } .empty-state { text-align: center; padding: 16px; color: #94a3b8; font-size: 12px; } .issue-form { display: flex; flex-direction: column; gap: 4px; padding: 8px 12px; border-top: 1px solid #e2e8f0; } .issue-row { display: flex; gap: 4px; } .issue-form input { flex: 1; padding: 6px 8px; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 12px; outline: none; } .issue-form input:focus { border-color: #6d28d9; } .issue-form input.amount-input { width: 80px; flex: 0; } .issue-btn { padding: 6px 12px; background: #6d28d9; color: white; border: none; border-radius: 4px; font-size: 11px; font-weight: 600; cursor: pointer; white-space: nowrap; } .issue-btn:hover { background: #5b21b6; } .issue-btn:disabled { background: #cbd5e1; cursor: not-allowed; } `; export interface LedgerEntry { id: string; holder: string; holderLabel: string; amount: number; issuedAt: string; issuedBy: string; memo: string; } declare global { interface HTMLElementTagNameMap { "folk-token-ledger": FolkTokenLedger; } } export class FolkTokenLedger extends FolkShape { static override tagName = "folk-token-ledger"; static { const sheet = new CSSStyleSheet(); const parentRules = Array.from(FolkShape.styles.cssRules) .map((r) => r.cssText) .join("\n"); const childRules = Array.from(styles.cssRules) .map((r) => r.cssText) .join("\n"); sheet.replaceSync(`${parentRules}\n${childRules}`); this.styles = sheet; } #mintId = ""; #entries: LedgerEntry[] = []; #headerEl: HTMLElement | null = null; #summaryEl: HTMLElement | null = null; #listEl: HTMLElement | null = null; #issueBtnEl: HTMLButtonElement | null = null; get mintId() { return this.#mintId; } set mintId(v: string) { this.#mintId = v; this.#render(); } get entries() { return this.#entries; } set entries(v: LedgerEntry[]) { this.#entries = v; this.#render(); this.#updateLinkedMint(); this.dispatchEvent(new CustomEvent("content-change")); } get totalIssued() { return this.#entries.reduce((sum, e) => sum + e.amount, 0); } addEntry(entry: LedgerEntry) { this.#entries.push(entry); this.#render(); this.#updateLinkedMint(); this.dispatchEvent(new CustomEvent("content-change")); } #getLinkedMint(): HTMLElement | null { if (!this.#mintId) return null; return document.getElementById(this.#mintId); } #getMintInfo(): { name: string; symbol: string; color: string; totalSupply: number } { const mint = this.#getLinkedMint() as any; if (mint) { return { name: mint.tokenName || "Token", symbol: mint.tokenSymbol || "TKN", color: mint.tokenColor || "#6d28d9", totalSupply: mint.totalSupply || 0, }; } return { name: "Token", symbol: "TKN", color: "#6d28d9", totalSupply: 0 }; } #updateLinkedMint() { const mint = this.#getLinkedMint() as any; if (mint && typeof mint.issuedSupply !== "undefined") { mint.issuedSupply = this.totalIssued; } } #isEmail(s: string): boolean { return s.includes("@") && !s.startsWith("did:"); } #holderColor(holder: string): string { let hash = 0; for (let i = 0; i < holder.length; i++) { hash = ((hash << 5) - hash + holder.charCodeAt(i)) | 0; } const hue = Math.abs(hash) % 360; return `hsl(${hue}, 55%, 50%)`; } override createRenderRoot() { const root = super.createRenderRoot(); const wrapper = document.createElement("div"); wrapper.innerHTML = html`