refactor: event delegation, unified subscription cleanup, centralized listing status helpers

This commit is contained in:
2026-02-08 10:50:11 +01:00
parent 088db52258
commit 45e7f9dde7
14 changed files with 101 additions and 72 deletions

View File

@@ -2,12 +2,14 @@ import { getCurrentLanguage, i18n } from '../../i18n.js'
class PageAbout extends HTMLElement {
connectedCallback() {
this._unsubs = []
this.render()
this.unsubscribe = i18n.subscribe(() => this.render())
this._unsubs.push(i18n.subscribe(() => this.render()))
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
getContent(lang) {

View File

@@ -2,12 +2,14 @@ import { getCurrentLanguage, i18n } from '../../i18n.js'
class PageContact extends HTMLElement {
connectedCallback() {
this._unsubs = []
this.render()
this.unsubscribe = i18n.subscribe(() => this.render())
this._unsubs.push(i18n.subscribe(() => this.render()))
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
getContent(lang) {

View File

@@ -72,6 +72,8 @@ class PageCreate extends HTMLElement {
}
async connectedCallback() {
this._unsubs = []
// Check if logged in
if (!auth.isLoggedIn()) {
this.showLoginRequired()
@@ -90,7 +92,7 @@ class PageCreate extends HTMLElement {
await this.loadCategories()
await this.checkAccountStatus()
this.render()
this.unsubscribe = i18n.subscribe(() => this.render())
this._unsubs.push(i18n.subscribe(() => this.render()))
}
async loadExistingListing() {
@@ -165,7 +167,7 @@ class PageCreate extends HTMLElement {
this.hasDraft = !!localStorage.getItem(STORAGE_KEY)
await this.loadCategories()
this.render()
this.unsubscribe = i18n.subscribe(() => this.render())
this._unsubs.push(i18n.subscribe(() => this.render()))
}, { once: true })
authModal.addEventListener('close', () => {
// If closed without login, go back
@@ -206,7 +208,8 @@ class PageCreate extends HTMLElement {
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
render() {

View File

@@ -15,15 +15,16 @@ class PageFavorites extends HTMLElement {
}
connectedCallback() {
this._unsubs = []
this.render()
this.loadFavorites()
this.unsubscribe = i18n.subscribe(() => this.render())
this.favUnsubscribe = favoritesService.subscribe(() => this.loadFavorites())
this._unsubs.push(i18n.subscribe(() => this.render()))
this._unsubs.push(favoritesService.subscribe(() => this.loadFavorites()))
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
if (this.favUnsubscribe) this.favUnsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
async loadFavorites() {

View File

@@ -40,25 +40,26 @@ class PageHome extends HTMLElement {
this.setupEventListeners()
this.setupPullToRefresh()
this.loadListings()
this.unsubscribe = i18n.subscribe(() => {
this._unsubs = []
this._unsubs.push(i18n.subscribe(() => {
this.updateTextContent()
})
}))
// Re-render listings on auth change to show owner badges
this.authUnsubscribe = auth.subscribe(() => {
this._unsubs.push(auth.subscribe(() => {
const container = this.querySelector('#listings-container')
if (container) {
container.innerHTML = this.renderListings()
}
})
}))
// Listen for URL changes (back/forward navigation)
window.addEventListener('hashchange', this._onHashChange)
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
if (this.authUnsubscribe) this.authUnsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
window.removeEventListener('hashchange', this._onHashChange)
}

View File

@@ -23,14 +23,16 @@ class PageListing extends HTMLElement {
connectedCallback() {
this.listingId = this.dataset.id
this._unsubs = []
this.render()
this.loadListing()
this.unsubscribe = i18n.subscribe(() => this.render())
this._unsubs.push(i18n.subscribe(() => this.render()))
window.addEventListener('currency-changed', this.handleCurrencyChange)
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
window.removeEventListener('currency-changed', this.handleCurrencyChange)
this.resetMetaTags()
}

View File

@@ -23,19 +23,20 @@ class PageMessages extends HTMLElement {
this.render()
this.loadConversations()
this.unsubscribe = i18n.subscribe(() => this.render())
this.authUnsubscribe = auth.subscribe(() => {
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() {
if (this.unsubscribe) this.unsubscribe()
if (this.authUnsubscribe) this.authUnsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
async loadConversations() {

View File

@@ -13,6 +13,7 @@ class PageMyListings extends HTMLElement {
this.loading = true
this.error = null
this.isLoggedIn = false
this._handleClick = this.handleDelegatedClick.bind(this)
}
connectedCallback() {
@@ -25,23 +26,42 @@ class PageMyListings extends HTMLElement {
this.render()
this.loadMyListings()
this.addEventListener('click', this._handleClick)
this.unsubscribe = i18n.subscribe(() => this.render())
this.authUnsubscribe = auth.subscribe(() => {
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() {
if (this.unsubscribe) this.unsubscribe()
if (this.authUnsubscribe) this.authUnsubscribe()
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 =>
@@ -221,11 +241,9 @@ class PageMyListings extends HTMLElement {
const locationName = listing.location?.name || ''
const statusBadge = this.getStatusBadge(listing)
const paidActive = listingsService.isPaidAndActive(listing)
const isPublished = listing.status === 'published'
const isDraftPaid = listing.status === 'draft' && paidActive
let toggleBtn = ''
if (isPublished || isDraftPaid) {
if (listingsService.canTogglePublish(listing)) {
const label = isPublished ? t('myListings.unpublish') : t('myListings.republish')
toggleBtn = /* html */`
<button class="btn-toggle-status" data-id="${listing.id}" data-status="${isPublished ? 'draft' : 'published'}">
@@ -235,7 +253,7 @@ class PageMyListings extends HTMLElement {
}
let deleteBtn = ''
if (listing.status === 'deleted') {
if (listingsService.isSoftDeleted(listing)) {
deleteBtn = /* html */`
<p class="deleted-hint">${t('myListings.deletedHint')}</p>
`
@@ -248,7 +266,7 @@ class PageMyListings extends HTMLElement {
}
return /* html */`
<div class="listing-wrapper${listing.status === 'deleted' ? ' is-deleted' : ''}">
<div class="listing-wrapper${listingsService.isSoftDeleted(listing) ? ' is-deleted' : ''}">
${statusBadge}
<listing-card
listing-id="${listing.id}"
@@ -267,30 +285,9 @@ class PageMyListings extends HTMLElement {
`
}).join('')
setTimeout(() => this.setupToggleListeners(), 0)
return html
}
setupToggleListeners() {
this.querySelectorAll('.btn-toggle-status').forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault()
e.stopPropagation()
const id = btn.dataset.id
const newStatus = btn.dataset.status
this.toggleListingStatus(id, newStatus)
})
})
this.querySelectorAll('.btn-delete-listing').forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault()
e.stopPropagation()
const id = btn.dataset.id
this.deleteListing(id)
})
})
}
async toggleListingStatus(id, newStatus) {
try {
await directus.updateListing(id, { status: newStatus })

View File

@@ -2,12 +2,14 @@ import { t, i18n } from '../../i18n.js'
class PageNotFound extends HTMLElement {
connectedCallback() {
this._unsubs = []
this.render()
this.unsubscribe = i18n.subscribe(() => this.render())
this._unsubs.push(i18n.subscribe(() => this.render()))
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
render() {

View File

@@ -10,18 +10,19 @@ class PageNotifications extends HTMLElement {
}
connectedCallback() {
this._unsubs = []
this.render()
this.loadNotifications()
this.unsubscribe = i18n.subscribe(() => this.render())
this.notifUnsubscribe = notificationsService.subscribe(() => {
this._unsubs.push(i18n.subscribe(() => this.render()))
this._unsubs.push(notificationsService.subscribe(() => {
this.loading = false
this.updateContent()
})
}))
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
if (this.notifUnsubscribe) this.notifUnsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
async loadNotifications() {

View File

@@ -2,12 +2,14 @@ import { getCurrentLanguage, i18n } from '../../i18n.js'
class PagePrivacy extends HTMLElement {
connectedCallback() {
this._unsubs = []
this.render()
this.unsubscribe = i18n.subscribe(() => this.render())
this._unsubs.push(i18n.subscribe(() => this.render()))
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
getContent(lang) {

View File

@@ -23,23 +23,24 @@ class PageSettings extends HTMLElement {
this.render()
this.setupEventListeners()
this.unsubscribe = i18n.subscribe(() => {
this._unsubs = []
this._unsubs.push(i18n.subscribe(() => {
this.render()
this.setupEventListeners()
})
}))
this.authUnsubscribe = auth.subscribe(() => {
this._unsubs.push(auth.subscribe(() => {
this.isLoggedIn = auth.isLoggedIn()
if (!this.isLoggedIn) {
window.location.hash = '#/'
}
})
}))
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
if (this.authUnsubscribe) this.authUnsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
setupEventListeners() {

View File

@@ -2,12 +2,14 @@ import { getCurrentLanguage, i18n } from '../../i18n.js'
class PageTerms extends HTMLElement {
connectedCallback() {
this._unsubs = []
this.render()
this.unsubscribe = i18n.subscribe(() => this.render())
this._unsubs.push(i18n.subscribe(() => this.render()))
}
disconnectedCallback() {
if (this.unsubscribe) this.unsubscribe()
this._unsubs.forEach(fn => fn())
this._unsubs = []
}
render() {