/** * — Stay request detail view. * * Shows: status banner, listing info, dates, message thread, * action buttons (accept/decline/cancel/complete), endorsement prompt. */ const STATUS_STYLES: Record = { pending: { bg: 'rgba(245,158,11,0.15)', fg: '#f59e0b', label: 'Pending' }, accepted: { bg: 'rgba(52,211,153,0.15)', fg: '#34d399', label: 'Accepted' }, declined: { bg: 'rgba(239,68,68,0.15)', fg: '#ef4444', label: 'Declined' }, cancelled: { bg: 'rgba(148,163,184,0.15)', fg: '#94a3b8', label: 'Cancelled' }, completed: { bg: 'rgba(96,165,250,0.15)', fg: '#60a5fa', label: 'Completed' }, endorsed: { bg: 'rgba(167,139,250,0.15)', fg: '#a78bfa', label: 'Endorsed' }, }; class FolkStayRequest extends HTMLElement { static observedAttributes = ['stay-id', 'space']; #shadow: ShadowRoot; #data: any = null; #currentDid: string = ''; constructor() { super(); this.#shadow = this.attachShadow({ mode: 'open' }); } connectedCallback() { this.#fetchAndRender(); } attributeChangedCallback() { this.#fetchAndRender(); } set stayData(data: any) { this.#data = data; this.#render(); } set currentDid(did: string) { this.#currentDid = did; this.#render(); } async #fetchAndRender() { const space = this.getAttribute('space') || 'demo'; const stayId = this.getAttribute('stay-id'); if (!stayId) return; try { const res = await fetch(`/${space}/rbnb/api/stays/${stayId}`); if (res.ok) { this.#data = await res.json(); this.#render(); } } catch { /* offline */ } } #render() { if (!this.#data) { this.#shadow.innerHTML = `
Loading stay request...
`; return; } const d = this.#data; const status = STATUS_STYLES[d.status] || STATUS_STYLES.pending; const isHost = this.#currentDid === d.host_did; const isGuest = this.#currentDid === d.guest_did; const checkIn = d.check_in ? new Date(d.check_in).toLocaleDateString() : '—'; const checkOut = d.check_out ? new Date(d.check_out).toLocaleDateString() : '—'; const nights = d.check_in && d.check_out ? Math.ceil((new Date(d.check_out).getTime() - new Date(d.check_in).getTime()) / 86400000) : 0; // Build action buttons based on status and role let actions = ''; if (d.status === 'pending' && isHost) { actions = ` `; } else if (d.status === 'pending' && isGuest) { actions = ``; } else if (d.status === 'accepted') { actions = ``; } else if (d.status === 'completed') { actions = ``; } // Build message thread const messages = (d.messages || []).map((m: any) => { const isSent = m.sender_did === this.#currentDid; const time = m.sent_at ? new Date(m.sent_at).toLocaleString() : ''; return `
${this.#esc(m.sender_name)}
${this.#esc(m.body)}
${time}
`; }).join(''); this.#shadow.innerHTML = `
${status.label} ${checkIn} \u{2192} ${checkOut} (${nights} night${nights !== 1 ? 's' : ''})

${this.#esc(d.guest_name)} \u{2192} Stay Request

\u{1F465} ${d.guest_count} guest${d.guest_count !== 1 ? 's' : ''} ${d.offered_amount ? `\u{1F4B0} ${d.offered_currency || ''} ${d.offered_amount}` : ''} ${d.offered_exchange ? `\u{1F91D} ${this.#esc(d.offered_exchange)}` : ''}
${messages || '
No messages yet
'}
${actions ? `
${actions}
` : ''}
`; // Wire up event listeners this.#shadow.getElementById('send-btn')?.addEventListener('click', () => this.#sendMessage()); this.#shadow.getElementById('msg-input')?.addEventListener('keydown', (e: Event) => { if ((e as KeyboardEvent).key === 'Enter') this.#sendMessage(); }); for (const btn of this.#shadow.querySelectorAll('.action[data-action]')) { btn.addEventListener('click', () => { const action = (btn as HTMLElement).dataset.action; this.dispatchEvent(new CustomEvent('stay-action', { detail: { stayId: this.#data.id, action }, bubbles: true, })); }); } } #sendMessage() { const input = this.#shadow.getElementById('msg-input') as HTMLInputElement; const body = input?.value?.trim(); if (!body) return; this.dispatchEvent(new CustomEvent('stay-message', { detail: { stayId: this.#data.id, body }, bubbles: true, })); input.value = ''; } #esc(s: string): string { const el = document.createElement('span'); el.textContent = s || ''; return el.innerHTML; } } if (!customElements.get('folk-stay-request')) { customElements.define('folk-stay-request', FolkStayRequest); } export { FolkStayRequest };