import { t, i18n } from '../../i18n.js' import { directus } from '../../services/directus.js' import { auth } from '../../services/auth.js' import { listingsService } from '../../services/listings.js' import { escapeHTML } from '../../utils/helpers.js' import '../listing-card.js' import '../skeleton-card.js' class PageMyListings extends HTMLElement { constructor() { super() this.listings = [] this.loading = true this.error = null this.isLoggedIn = false this._handleClick = this.handleDelegatedClick.bind(this) } connectedCallback() { this.isLoggedIn = auth.isLoggedIn() if (!this.isLoggedIn) { window.location.hash = '#/' return } this.render() this.loadMyListings() this.addEventListener('click', this._handleClick) this._unsubs = [] this._unsubs.push(i18n.subscribe(() => this.render())) this._unsubs.push(auth.subscribe(() => { this.isLoggedIn = auth.isLoggedIn() if (!this.isLoggedIn) { window.location.hash = '#/' } })) } disconnectedCallback() { this._unsubs.forEach(fn => fn()) this._unsubs = [] this.removeEventListener('click', this._handleClick) this.stopPolling() } handleDelegatedClick(e) { const toggleBtn = e.target.closest('.btn-toggle-status') if (toggleBtn) { e.preventDefault() e.stopPropagation() this.toggleListingStatus(toggleBtn.dataset.id, toggleBtn.dataset.status) return } const deleteBtn = e.target.closest('.btn-delete-listing') if (deleteBtn) { e.preventDefault() e.stopPropagation() this.deleteListing(deleteBtn.dataset.id) } } startPolling() { this.stopPolling() const hasPending = this.listings.some(l => l.payment_status === 'processing' || l.payment_status === 'pending' ) if (hasPending) { this.pollInterval = setInterval(() => this.pollListings(), 15000) } } stopPolling() { if (this.pollInterval) { clearInterval(this.pollInterval) this.pollInterval = null } } async pollListings() { try { const user = await auth.getUser() if (!user) return const pendingIds = this.listings .filter(l => l.payment_status === 'processing' || l.payment_status === 'pending') .map(l => l.id) if (pendingIds.length === 0) { this.stopPolling() return } const response = await directus.getListings({ fields: ['id', 'status', 'payment_status'], filter: { id: { _in: pendingIds } }, limit: pendingIds.length }) const updated = response.items || [] let changed = false for (const item of updated) { const existing = this.listings.find(l => l.id === item.id) if (existing && (existing.payment_status !== item.payment_status || existing.status !== item.status)) { existing.payment_status = item.payment_status existing.status = item.status changed = true } } if (changed) { this.updateContent() } this.startPolling() } catch (err) { console.error('Polling failed:', err) } } async loadMyListings() { try { const user = await auth.getUser() if (!user) { this.loading = false this.updateContent() return } const response = await directus.getListings({ fields: [ 'id', 'status', 'title', 'slug', 'price', 'currency', 'condition', 'payment_status', 'paid_at', 'expires_at', 'date_created', 'date_updated', 'user_created', 'images.directus_files_id.id', 'location.id', 'location.name' ], filter: { user_created: { _eq: user.id } }, sort: ['-date_created'], limit: 50 }) const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000 this.listings = (response.items || []).filter(l => { if (l.status !== 'deleted') return true const updated = new Date(l.date_updated || l.date_created).getTime() return updated > thirtyDaysAgo }) this.loading = false this.updateContent() this.startPolling() } catch (err) { console.error('Failed to load my listings:', err) this.error = err.message this.loading = false this.updateContent() } } showAuthModal() { document.querySelector('auth-modal')?.show() } getStatusBadge(listing) { if (listing.status === 'archived') { return `${t('myListings.status.archived')}` } if (listing.status === 'draft' && listing.payment_status !== 'processing' && listing.payment_status !== 'pending' && !listingsService.isPaidAndActive(listing)) { return `${t('myListings.status.draft')}` } return '' } render() { this.innerHTML = /* html */`
${this.renderContent()}
` this.querySelector('#login-btn')?.addEventListener('click', () => this.showAuthModal()) } updateContent() { const container = this.querySelector('#my-listings-content') if (container) { container.innerHTML = this.renderContent() this.querySelector('#login-btn')?.addEventListener('click', () => this.showAuthModal()) } } renderContent() { if (!this.isLoggedIn) { return /* html */`
🔐

${t('myListings.loginRequired')}

${t('myListings.loginHint')}

` } if (this.loading) { return Array(4).fill(0).map(() => '').join('') } if (this.error) { return /* html */`
⚠️

${t('common.error')}

` } if (this.listings.length === 0) { return /* html */`
📦

${t('myListings.empty')}

${t('myListings.emptyHint')}

${t('myListings.create')}
` } const html = this.listings.map(listing => { const imageId = listing.images?.[0]?.directus_files_id?.id || listing.images?.[0]?.directus_files_id const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 180) : '' const locationName = listing.location?.name || '' const statusBadge = this.getStatusBadge(listing) const isPublished = listing.status === 'published' let toggleBtn = '' if (listingsService.canTogglePublish(listing)) { const label = isPublished ? t('myListings.unpublish') : t('myListings.republish') toggleBtn = /* html */` ` } let deleteBtn = '' if (listingsService.isSoftDeleted(listing)) { deleteBtn = /* html */`

${t('myListings.deletedHint')}

` } else if (listing.status !== 'archived') { deleteBtn = /* html */` ` } return /* html */`
${statusBadge} ${toggleBtn} ${deleteBtn}
` }).join('') return html } async toggleListingStatus(id, newStatus) { try { await directus.updateListing(id, { status: newStatus }) const listing = this.listings.find(l => l.id === id) if (listing) { listing.status = newStatus this.updateContent() } } catch (err) { console.error('Failed to toggle listing status:', err) } } async deleteListing(id) { if (!confirm(t('myListings.deleteConfirm'))) return try { await directus.updateListing(id, { status: 'deleted' }) const listing = this.listings.find(l => l.id === id) if (listing) { listing.status = 'deleted' this.updateContent() } } catch (err) { console.error('Failed to delete listing:', err) } } } customElements.define('page-my-listings', PageMyListings) const style = document.createElement('style') style.textContent = /* css */` page-my-listings .my-listings-page { padding: var(--space-lg) 0; } page-my-listings .page-header { margin-bottom: var(--space-xl); } page-my-listings .page-header h1 { margin: 0 0 var(--space-xs); } page-my-listings .page-subtitle { color: var(--color-text-muted); margin: 0; } page-my-listings .listing-wrapper { position: relative; min-width: 0; } page-my-listings .listing-wrapper.is-deleted { opacity: 0.5; filter: grayscale(1); pointer-events: none; } page-my-listings .deleted-hint { margin: var(--space-xs) 0 0; font-size: var(--font-size-xs); color: var(--color-text-muted); text-align: center; } page-my-listings .status-badge { position: absolute; top: var(--space-sm); left: var(--space-sm); padding: var(--space-xs) var(--space-sm); font-size: var(--font-size-xs); font-weight: var(--font-weight-medium); border-radius: var(--radius-sm); z-index: 2; } page-my-listings .status-draft { background: var(--color-bg-tertiary); color: var(--color-text-muted); } page-my-listings .status-archived { background: var(--color-bg-tertiary); color: var(--color-text-muted); text-decoration: line-through; } page-my-listings .btn-delete-listing { display: block; width: 100%; padding: var(--space-xs) var(--space-sm); margin-top: var(--space-xs); background: none; border: 1px solid var(--color-border); border-radius: var(--radius-sm); color: var(--color-text-muted); font-size: var(--font-size-sm); cursor: pointer; transition: all var(--transition-fast); } page-my-listings .btn-delete-listing:hover { border-color: var(--color-error, #b43c3c); color: var(--color-error, #b43c3c); } page-my-listings .btn-toggle-status { display: block; width: 100%; padding: var(--space-xs) var(--space-sm); margin-top: var(--space-xs); background: var(--color-bg-secondary); border: 1px solid var(--color-border); border-radius: var(--radius-sm); color: var(--color-text-secondary); font-size: var(--font-size-sm); cursor: pointer; transition: background-color var(--transition-fast); } page-my-listings .btn-toggle-status:hover { background: var(--color-bg-tertiary); } page-my-listings .empty-state { grid-column: 1 / -1; text-align: center; padding: var(--space-3xl); } page-my-listings .empty-icon { font-size: 4rem; margin-bottom: var(--space-md); filter: grayscale(1); } page-my-listings .empty-state h3 { margin: 0 0 var(--space-sm); } page-my-listings .empty-state p { color: var(--color-text-muted); margin: 0 0 var(--space-lg); } ` document.head.appendChild(style)