refactor: event delegation, unified subscription cleanup, centralized listing status helpers
This commit is contained in:
@@ -2,12 +2,14 @@ import { getCurrentLanguage, i18n } from '../../i18n.js'
|
|||||||
|
|
||||||
class PageAbout extends HTMLElement {
|
class PageAbout extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
this._unsubs = []
|
||||||
this.render()
|
this.render()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
getContent(lang) {
|
getContent(lang) {
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { getCurrentLanguage, i18n } from '../../i18n.js'
|
|||||||
|
|
||||||
class PageContact extends HTMLElement {
|
class PageContact extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
this._unsubs = []
|
||||||
this.render()
|
this.render()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
getContent(lang) {
|
getContent(lang) {
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class PageCreate extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async connectedCallback() {
|
async connectedCallback() {
|
||||||
|
this._unsubs = []
|
||||||
|
|
||||||
// Check if logged in
|
// Check if logged in
|
||||||
if (!auth.isLoggedIn()) {
|
if (!auth.isLoggedIn()) {
|
||||||
this.showLoginRequired()
|
this.showLoginRequired()
|
||||||
@@ -90,7 +92,7 @@ class PageCreate extends HTMLElement {
|
|||||||
await this.loadCategories()
|
await this.loadCategories()
|
||||||
await this.checkAccountStatus()
|
await this.checkAccountStatus()
|
||||||
this.render()
|
this.render()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadExistingListing() {
|
async loadExistingListing() {
|
||||||
@@ -165,7 +167,7 @@ class PageCreate extends HTMLElement {
|
|||||||
this.hasDraft = !!localStorage.getItem(STORAGE_KEY)
|
this.hasDraft = !!localStorage.getItem(STORAGE_KEY)
|
||||||
await this.loadCategories()
|
await this.loadCategories()
|
||||||
this.render()
|
this.render()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
}, { once: true })
|
}, { once: true })
|
||||||
authModal.addEventListener('close', () => {
|
authModal.addEventListener('close', () => {
|
||||||
// If closed without login, go back
|
// If closed without login, go back
|
||||||
@@ -206,7 +208,8 @@ class PageCreate extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -15,15 +15,16 @@ class PageFavorites extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
this._unsubs = []
|
||||||
this.render()
|
this.render()
|
||||||
this.loadFavorites()
|
this.loadFavorites()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
this.favUnsubscribe = favoritesService.subscribe(() => this.loadFavorites())
|
this._unsubs.push(favoritesService.subscribe(() => this.loadFavorites()))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
if (this.favUnsubscribe) this.favUnsubscribe()
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadFavorites() {
|
async loadFavorites() {
|
||||||
|
|||||||
@@ -40,25 +40,26 @@ class PageHome extends HTMLElement {
|
|||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
this.setupPullToRefresh()
|
this.setupPullToRefresh()
|
||||||
this.loadListings()
|
this.loadListings()
|
||||||
this.unsubscribe = i18n.subscribe(() => {
|
this._unsubs = []
|
||||||
|
this._unsubs.push(i18n.subscribe(() => {
|
||||||
this.updateTextContent()
|
this.updateTextContent()
|
||||||
})
|
}))
|
||||||
|
|
||||||
// Re-render listings on auth change to show owner badges
|
// 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')
|
const container = this.querySelector('#listings-container')
|
||||||
if (container) {
|
if (container) {
|
||||||
container.innerHTML = this.renderListings()
|
container.innerHTML = this.renderListings()
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
// Listen for URL changes (back/forward navigation)
|
// Listen for URL changes (back/forward navigation)
|
||||||
window.addEventListener('hashchange', this._onHashChange)
|
window.addEventListener('hashchange', this._onHashChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
if (this.authUnsubscribe) this.authUnsubscribe()
|
this._unsubs = []
|
||||||
window.removeEventListener('hashchange', this._onHashChange)
|
window.removeEventListener('hashchange', this._onHashChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,16 @@ class PageListing extends HTMLElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.listingId = this.dataset.id
|
this.listingId = this.dataset.id
|
||||||
|
this._unsubs = []
|
||||||
this.render()
|
this.render()
|
||||||
this.loadListing()
|
this.loadListing()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
window.addEventListener('currency-changed', this.handleCurrencyChange)
|
window.addEventListener('currency-changed', this.handleCurrencyChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
|
this._unsubs = []
|
||||||
window.removeEventListener('currency-changed', this.handleCurrencyChange)
|
window.removeEventListener('currency-changed', this.handleCurrencyChange)
|
||||||
this.resetMetaTags()
|
this.resetMetaTags()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,19 +23,20 @@ class PageMessages extends HTMLElement {
|
|||||||
this.render()
|
this.render()
|
||||||
this.loadConversations()
|
this.loadConversations()
|
||||||
|
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs = []
|
||||||
this.authUnsubscribe = auth.subscribe(() => {
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
|
this._unsubs.push(auth.subscribe(() => {
|
||||||
this.isLoggedIn = auth.isLoggedIn()
|
this.isLoggedIn = auth.isLoggedIn()
|
||||||
|
|
||||||
if (!this.isLoggedIn) {
|
if (!this.isLoggedIn) {
|
||||||
window.location.hash = '#/'
|
window.location.hash = '#/'
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
if (this.authUnsubscribe) this.authUnsubscribe()
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadConversations() {
|
async loadConversations() {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class PageMyListings extends HTMLElement {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
this.error = null
|
this.error = null
|
||||||
this.isLoggedIn = false
|
this.isLoggedIn = false
|
||||||
|
this._handleClick = this.handleDelegatedClick.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -25,23 +26,42 @@ class PageMyListings extends HTMLElement {
|
|||||||
|
|
||||||
this.render()
|
this.render()
|
||||||
this.loadMyListings()
|
this.loadMyListings()
|
||||||
|
this.addEventListener('click', this._handleClick)
|
||||||
|
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs = []
|
||||||
this.authUnsubscribe = auth.subscribe(() => {
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
|
this._unsubs.push(auth.subscribe(() => {
|
||||||
this.isLoggedIn = auth.isLoggedIn()
|
this.isLoggedIn = auth.isLoggedIn()
|
||||||
|
|
||||||
if (!this.isLoggedIn) {
|
if (!this.isLoggedIn) {
|
||||||
window.location.hash = '#/'
|
window.location.hash = '#/'
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
if (this.authUnsubscribe) this.authUnsubscribe()
|
this._unsubs = []
|
||||||
|
this.removeEventListener('click', this._handleClick)
|
||||||
this.stopPolling()
|
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() {
|
startPolling() {
|
||||||
this.stopPolling()
|
this.stopPolling()
|
||||||
const hasPending = this.listings.some(l =>
|
const hasPending = this.listings.some(l =>
|
||||||
@@ -221,11 +241,9 @@ class PageMyListings extends HTMLElement {
|
|||||||
const locationName = listing.location?.name || ''
|
const locationName = listing.location?.name || ''
|
||||||
const statusBadge = this.getStatusBadge(listing)
|
const statusBadge = this.getStatusBadge(listing)
|
||||||
|
|
||||||
const paidActive = listingsService.isPaidAndActive(listing)
|
|
||||||
const isPublished = listing.status === 'published'
|
const isPublished = listing.status === 'published'
|
||||||
const isDraftPaid = listing.status === 'draft' && paidActive
|
|
||||||
let toggleBtn = ''
|
let toggleBtn = ''
|
||||||
if (isPublished || isDraftPaid) {
|
if (listingsService.canTogglePublish(listing)) {
|
||||||
const label = isPublished ? t('myListings.unpublish') : t('myListings.republish')
|
const label = isPublished ? t('myListings.unpublish') : t('myListings.republish')
|
||||||
toggleBtn = /* html */`
|
toggleBtn = /* html */`
|
||||||
<button class="btn-toggle-status" data-id="${listing.id}" data-status="${isPublished ? 'draft' : 'published'}">
|
<button class="btn-toggle-status" data-id="${listing.id}" data-status="${isPublished ? 'draft' : 'published'}">
|
||||||
@@ -235,7 +253,7 @@ class PageMyListings extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let deleteBtn = ''
|
let deleteBtn = ''
|
||||||
if (listing.status === 'deleted') {
|
if (listingsService.isSoftDeleted(listing)) {
|
||||||
deleteBtn = /* html */`
|
deleteBtn = /* html */`
|
||||||
<p class="deleted-hint">${t('myListings.deletedHint')}</p>
|
<p class="deleted-hint">${t('myListings.deletedHint')}</p>
|
||||||
`
|
`
|
||||||
@@ -248,7 +266,7 @@ class PageMyListings extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return /* html */`
|
return /* html */`
|
||||||
<div class="listing-wrapper${listing.status === 'deleted' ? ' is-deleted' : ''}">
|
<div class="listing-wrapper${listingsService.isSoftDeleted(listing) ? ' is-deleted' : ''}">
|
||||||
${statusBadge}
|
${statusBadge}
|
||||||
<listing-card
|
<listing-card
|
||||||
listing-id="${listing.id}"
|
listing-id="${listing.id}"
|
||||||
@@ -267,30 +285,9 @@ class PageMyListings extends HTMLElement {
|
|||||||
`
|
`
|
||||||
}).join('')
|
}).join('')
|
||||||
|
|
||||||
setTimeout(() => this.setupToggleListeners(), 0)
|
|
||||||
return html
|
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) {
|
async toggleListingStatus(id, newStatus) {
|
||||||
try {
|
try {
|
||||||
await directus.updateListing(id, { status: newStatus })
|
await directus.updateListing(id, { status: newStatus })
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { t, i18n } from '../../i18n.js'
|
|||||||
|
|
||||||
class PageNotFound extends HTMLElement {
|
class PageNotFound extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
this._unsubs = []
|
||||||
this.render()
|
this.render()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -10,18 +10,19 @@ class PageNotifications extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
this._unsubs = []
|
||||||
this.render()
|
this.render()
|
||||||
this.loadNotifications()
|
this.loadNotifications()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
this.notifUnsubscribe = notificationsService.subscribe(() => {
|
this._unsubs.push(notificationsService.subscribe(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.updateContent()
|
this.updateContent()
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
if (this.notifUnsubscribe) this.notifUnsubscribe()
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNotifications() {
|
async loadNotifications() {
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { getCurrentLanguage, i18n } from '../../i18n.js'
|
|||||||
|
|
||||||
class PagePrivacy extends HTMLElement {
|
class PagePrivacy extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
this._unsubs = []
|
||||||
this.render()
|
this.render()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
getContent(lang) {
|
getContent(lang) {
|
||||||
|
|||||||
@@ -23,23 +23,24 @@ class PageSettings extends HTMLElement {
|
|||||||
this.render()
|
this.render()
|
||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
|
|
||||||
this.unsubscribe = i18n.subscribe(() => {
|
this._unsubs = []
|
||||||
|
this._unsubs.push(i18n.subscribe(() => {
|
||||||
this.render()
|
this.render()
|
||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
})
|
}))
|
||||||
|
|
||||||
this.authUnsubscribe = auth.subscribe(() => {
|
this._unsubs.push(auth.subscribe(() => {
|
||||||
this.isLoggedIn = auth.isLoggedIn()
|
this.isLoggedIn = auth.isLoggedIn()
|
||||||
|
|
||||||
if (!this.isLoggedIn) {
|
if (!this.isLoggedIn) {
|
||||||
window.location.hash = '#/'
|
window.location.hash = '#/'
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
if (this.authUnsubscribe) this.authUnsubscribe()
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { getCurrentLanguage, i18n } from '../../i18n.js'
|
|||||||
|
|
||||||
class PageTerms extends HTMLElement {
|
class PageTerms extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
this._unsubs = []
|
||||||
this.render()
|
this.render()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this._unsubs.push(i18n.subscribe(() => this.render()))
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.unsubscribe) this.unsubscribe()
|
this._unsubs.forEach(fn => fn())
|
||||||
|
this._unsubs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -75,6 +75,18 @@ class ListingsService {
|
|||||||
&& listing.expires_at
|
&& listing.expires_at
|
||||||
&& new Date(listing.expires_at) > new Date()
|
&& new Date(listing.expires_at) > new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canTogglePublish(listing) {
|
||||||
|
return listing.status === 'published' || (listing.status === 'draft' && this.isPaidAndActive(listing))
|
||||||
|
}
|
||||||
|
|
||||||
|
isSoftDeleted(listing) {
|
||||||
|
return listing.status === 'deleted'
|
||||||
|
}
|
||||||
|
|
||||||
|
canPublicView(listing) {
|
||||||
|
return listing.status === 'published'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listingsService = new ListingsService()
|
export const listingsService = new ListingsService()
|
||||||
|
|||||||
Reference in New Issue
Block a user