/** * — Canvas-embeddable listing card shape. * * Shows: cover photo placeholder, title, type icon, host name + trust badge, * capacity, location, economy badge, availability dot, endorsement count. */ const ECONOMY_COLORS: Record = { gift: { bg: 'rgba(52,211,153,0.12)', fg: '#34d399', label: 'Gift', icon: '\u{1F49A}' }, exchange: { bg: 'rgba(96,165,250,0.12)', fg: '#60a5fa', label: 'Exchange', icon: '\u{1F91D}' }, sliding_scale: { bg: 'rgba(245,158,11,0.12)', fg: '#f59e0b', label: 'Sliding Scale', icon: '\u{2696}' }, suggested: { bg: 'rgba(167,139,250,0.12)', fg: '#a78bfa', label: 'Suggested', icon: '\u{1F4AD}' }, fixed: { bg: 'rgba(148,163,184,0.12)', fg: '#94a3b8', label: 'Fixed', icon: '\u{1F3F7}' }, }; const TYPE_ICONS: Record = { couch: '\u{1F6CB}', room: '\u{1F6CF}', apartment: '\u{1F3E2}', cabin: '\u{1F3E1}', tent_site: '\u{26FA}', land: '\u{1F333}', studio: '\u{1F3A8}', loft: '\u{1F3D7}', house: '\u{1F3E0}', other: '\u{1F3E8}', }; class FolkListing extends HTMLElement { static observedAttributes = ['listing-id', 'space']; #shadow: ShadowRoot; #data: any = null; #endorsementCount = 0; #isAvailable = true; constructor() { super(); this.#shadow = this.attachShadow({ mode: 'open' }); } connectedCallback() { this.#fetchAndRender(); } attributeChangedCallback() { this.#fetchAndRender(); } set listingData(data: { listing: any; endorsementCount?: number; isAvailable?: boolean }) { this.#data = data.listing; this.#endorsementCount = data.endorsementCount ?? 0; this.#isAvailable = data.isAvailable ?? true; this.#render(); } async #fetchAndRender() { const space = this.getAttribute('space') || 'demo'; const listingId = this.getAttribute('listing-id'); if (!listingId) return; try { const res = await fetch(`/${space}/rbnb/api/listings/${listingId}`); if (res.ok) { this.#data = await res.json(); this.#render(); } } catch { /* offline */ } } #render() { if (!this.#data) { this.#shadow.innerHTML = `
Loading listing...
`; return; } const d = this.#data; const eco = ECONOMY_COLORS[d.economy] || ECONOMY_COLORS.gift; const typeIcon = TYPE_ICONS[d.type] || '\u{1F3E8}'; const typeLabel = (d.type || 'room').replace(/_/g, ' '); this.#shadow.innerHTML = `
${d.cover_photo ? `${d.title}` : typeIcon}
${this.#esc(d.title)}
${this.#esc(d.host_name)} ${d.instant_accept ? '\u{26A1} Auto-accept' : ''}
${eco.icon} ${eco.label} ${typeIcon} ${typeLabel} \u{1F465} ${d.guest_capacity}
\u{1F4CD} ${this.#esc(d.location_name)}
`; } #esc(s: string): string { const el = document.createElement('span'); el.textContent = s || ''; return el.innerHTML; } } if (!customElements.get('folk-listing')) { customElements.define('folk-listing', FolkListing); } export { FolkListing };