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.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 */`
`
}
if (this.listings.length === 0) {
return /* html */`
`
}
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)