Files
kashilo/js/components/pages/page-messages.js

328 lines
9.4 KiB
JavaScript

import { t, i18n } from '../../i18n.js'
import { auth } from '../../services/auth.js'
import { directus } from '../../services/directus.js'
class PageMessages extends HTMLElement {
constructor() {
super()
this.conversations = []
this.loading = true
this.error = null
this.isLoggedIn = false
}
connectedCallback() {
this.isLoggedIn = auth.isLoggedIn()
if (!this.isLoggedIn) {
window.location.hash = '#/'
return
}
this.render()
this.loadConversations()
this.unsubscribe = i18n.subscribe(() => this.render())
this.authUnsubscribe = auth.subscribe(() => {
this.isLoggedIn = auth.isLoggedIn()
if (!this.isLoggedIn) {
window.location.hash = '#/'
}
})
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
if (this.authUnsubscribe) this.authUnsubscribe()
}
async loadConversations() {
try {
const user = auth.getUser()
if (!user) {
this.loading = false
this.updateContent()
return
}
// Load conversations for current user (matched by participant hash)
const userHash = await auth.hashString(user.id)
const response = await directus.get('/items/conversations', {
fields: [
'id',
'date_created',
'date_updated',
'listing_id.id',
'listing_id.title',
'listing_id.images.directus_files_id.id',
'status'
],
filter: {
_or: [
{ participant_hash_1: { _eq: userHash } },
{ participant_hash_2: { _eq: userHash } }
]
},
sort: ['-date_updated'],
limit: 50
})
this.conversations = response.data || []
this.loading = false
this.updateContent()
} catch (err) {
console.error('Failed to load conversations:', err)
this.error = err.message
this.loading = false
this.updateContent()
}
}
showAuthModal() {
document.querySelector('auth-modal')?.show()
}
render() {
this.innerHTML = /* html */`
<div class="messages-page">
<header class="page-header">
<h1>${t('messages.title')}</h1>
<p class="page-subtitle">${t('messages.subtitle')}</p>
</header>
<div id="messages-content" class="conversations-list">
${this.renderContent()}
</div>
</div>
`
this.querySelector('#login-btn')?.addEventListener('click', () => this.showAuthModal())
}
updateContent() {
const container = this.querySelector('#messages-content')
if (container) {
container.innerHTML = this.renderContent()
this.querySelector('#login-btn')?.addEventListener('click', () => this.showAuthModal())
}
}
renderContent() {
if (!this.isLoggedIn) {
return /* html */`
<div class="empty-state">
<div class="empty-icon">🔐</div>
<h3>${t('messages.loginRequired')}</h3>
<p>${t('messages.loginHint')}</p>
<button id="login-btn" class="btn btn-primary">${t('messages.login')}</button>
</div>
`
}
if (this.loading) {
return /* html */`
<div class="loading-state">
<div class="spinner"></div>
<p>${t('common.loading')}</p>
</div>
`
}
if (this.error) {
return /* html */`
<div class="empty-state">
<div class="empty-icon">⚠️</div>
<p>${t('common.error')}</p>
</div>
`
}
if (this.conversations.length === 0) {
return /* html */`
<div class="empty-state">
<div class="empty-icon">💬</div>
<h3>${t('messages.empty')}</h3>
<p>${t('messages.emptyHint')}</p>
<a href="#/" class="btn btn-primary">${t('messages.browse')}</a>
</div>
`
}
return this.conversations.map(conv => {
const listing = conv.listing_id
const imageId = listing?.images?.[0]?.directus_files_id?.id
const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 80) : ''
const title = listing?.title || t('messages.unknownListing')
const dateStr = this.formatDate(conv.date_updated || conv.date_created)
return /* html */`
<a href="#/listing/${listing?.id}" class="conversation-item">
<div class="conversation-image">
${imageUrl
? `<img src="${imageUrl}" alt="" loading="lazy">`
: `<div class="image-placeholder">📦</div>`}
</div>
<div class="conversation-info">
<h3 class="conversation-title">${this.escapeHtml(title)}</h3>
<p class="conversation-date">${dateStr}</p>
</div>
<div class="conversation-arrow">→</div>
</a>
`
}).join('')
}
formatDate(dateStr) {
if (!dateStr) return ''
const date = new Date(dateStr)
const now = new Date()
const diffMs = now - date
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
if (diffDays === 0) return t('messages.today')
if (diffDays === 1) return t('messages.yesterday')
if (diffDays < 7) return t('messages.daysAgo', { days: diffDays })
return date.toLocaleDateString()
}
escapeHtml(text) {
const div = document.createElement('div')
div.textContent = text
return div.innerHTML
}
}
customElements.define('page-messages', PageMessages)
const style = document.createElement('style')
style.textContent = /* css */`
page-messages .messages-page {
padding: var(--space-lg) 0;
}
page-messages .page-header {
margin-bottom: var(--space-xl);
}
page-messages .page-header h1 {
margin: 0 0 var(--space-xs);
}
page-messages .page-subtitle {
color: var(--color-text-muted);
margin: 0;
}
page-messages .conversations-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
page-messages .conversation-item {
display: flex;
align-items: center;
gap: var(--space-md);
padding: var(--space-md);
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
text-decoration: none;
color: inherit;
transition: all var(--transition-fast);
}
page-messages .conversation-item:hover {
border-color: var(--color-primary);
box-shadow: var(--shadow-sm);
}
page-messages .conversation-image {
width: 60px;
height: 60px;
border-radius: var(--radius-sm);
overflow: hidden;
background: var(--color-bg-tertiary);
flex-shrink: 0;
}
page-messages .conversation-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
page-messages .image-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
filter: grayscale(1);
}
page-messages .conversation-info {
flex: 1;
min-width: 0;
}
page-messages .conversation-title {
margin: 0 0 var(--space-xs);
font-size: var(--font-size-base);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
page-messages .conversation-date {
margin: 0;
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
page-messages .conversation-arrow {
color: var(--color-text-muted);
font-size: var(--font-size-lg);
}
page-messages .loading-state,
page-messages .empty-state {
text-align: center;
padding: var(--space-3xl);
}
page-messages .spinner {
width: 40px;
height: 40px;
border: 3px solid var(--color-border);
border-top-color: var(--color-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto var(--space-md);
}
@keyframes spin {
to { transform: rotate(360deg); }
}
page-messages .empty-icon {
font-size: 4rem;
margin-bottom: var(--space-md);
filter: grayscale(1);
}
page-messages .empty-state h3 {
margin: 0 0 var(--space-sm);
}
page-messages .empty-state p {
color: var(--color-text-muted);
margin: 0 0 var(--space-lg);
}
`
document.head.appendChild(style)