import { t, i18n } from '../i18n.js'
import { escapeHTML } from '../utils/helpers.js'
import { getXmrRates, formatPrice as formatCurrencyPrice } from '../services/currency.js'
import { auth } from '../services/auth.js'
import { favoritesService } from '../services/favorites.js'
class ListingCard extends HTMLElement {
static get observedAttributes() {
return ['listing-id', 'title', 'price', 'currency', 'location', 'image', 'owner-id', 'payment-status', 'status', 'priority']
}
constructor() {
super()
this.isFavorite = false
this.rates = null
this.isOwner = false
this.handleCurrencyChange = this.handleCurrencyChange.bind(this)
}
async connectedCallback() {
this.loadFavoriteState()
await this.loadRates()
await this.checkOwnership()
this.render()
this.setupEventListeners()
window.addEventListener('currency-changed', this.handleCurrencyChange)
}
disconnectedCallback() {
window.removeEventListener('currency-changed', this.handleCurrencyChange)
}
handleCurrencyChange() {
this.render()
this.setupEventListeners()
}
async checkOwnership() {
const ownerId = this.getAttribute('owner-id')
if (!ownerId || !auth.isLoggedIn()) {
this.isOwner = false
return
}
try {
const user = await auth.getUser()
this.isOwner = user?.id === ownerId
} catch {
this.isOwner = false
}
}
async loadRates() {
this.rates = await getXmrRates()
}
attributeChangedCallback() {
if (this.isConnected) {
this.render()
this.setupEventListeners()
}
}
loadFavoriteState() {
const id = this.getAttribute('listing-id')
if (id) {
this.isFavorite = favoritesService.isFavorite(id)
}
}
render() {
const id = this.getAttribute('listing-id') || ''
const title = this.getAttribute('title') || t('home.placeholderTitle')
const price = this.getAttribute('price')
const currency = this.getAttribute('currency') || 'EUR'
const location = this.getAttribute('location') || t('home.placeholderLocation')
const image = this.getAttribute('image')
let priceDisplay = '–'
let secondaryPrice = null
if (price && this.rates) {
const listing = { price: parseFloat(price), currency, price_mode: currency === 'XMR' ? 'xmr' : 'fiat' }
const formatted = formatCurrencyPrice(listing, this.rates)
priceDisplay = formatted.primary
secondaryPrice = formatted.secondary
} else if (price) {
priceDisplay = currency === 'XMR'
? `${parseFloat(price).toFixed(4)} XMR`
: `€ ${parseFloat(price).toFixed(2)}`
}
const favoriteLabel = this.isFavorite ? t('home.removeFavorite') : t('home.addFavorite')
const placeholderSvg = /* html */`
`
const paymentStatus = this.getAttribute('payment-status')
const status = this.getAttribute('status')
const isDeleted = status === 'deleted'
const ownerBadge = (this.isOwner && !isDeleted) ? /* html */`
` : ''
let paymentBadge = ''
if (status === 'deleted') {
paymentBadge = /* html */`${t('myListings.status.deleted')}`
} else if (status === 'archived') {
paymentBadge = /* html */`${t('myListings.status.expired')}`
} else if (paymentStatus === 'processing' || paymentStatus === 'pending') {
paymentBadge = /* html */`${t('myListings.status.processing')}`
} else if (paymentStatus === 'expired') {
paymentBadge = /* html */`${t('myListings.status.expired')}`
} else if (paymentStatus === 'paid' && status === 'draft') {
paymentBadge = /* html */`${t('myListings.status.unpublished')}`
} else if (paymentStatus === 'paid') {
paymentBadge = /* html */`${t('myListings.status.published')}`
}
const linkTag = isDeleted ? 'div' : 'a'
const linkAttr = isDeleted ? '' : `href="#/listing/${escapeHTML(id)}"`
this.innerHTML = /* html */`
${ownerBadge}
<${linkTag} ${linkAttr} class="listing-link">
${image
? `
})
`
: placeholderSvg}
${paymentBadge}
${escapeHTML(title)}
${priceDisplay}
${secondaryPrice ? `
${secondaryPrice}
` : ''}
${escapeHTML(location)}
${linkTag}>
${!isDeleted ? /* html */`
` : ''}
`
}
setupEventListeners() {
const btn = this.querySelector('.favorite-btn')
btn?.addEventListener('click', (e) => {
e.preventDefault()
e.stopPropagation()
this.toggleFavorite()
})
}
toggleFavorite() {
const id = this.getAttribute('listing-id')
if (!id) return
this.isFavorite = !this.isFavorite
favoritesService.toggle(id)
const btn = this.querySelector('.favorite-btn')
btn?.classList.toggle('active', this.isFavorite)
btn?.setAttribute('aria-pressed', this.isFavorite)
btn?.setAttribute('aria-label', this.isFavorite ? t('home.removeFavorite') : t('home.addFavorite'))
btn?.classList.add('animate__animated', 'animate__heartBeat')
btn?.addEventListener('animationend', () => {
btn?.classList.remove('animate__animated', 'animate__heartBeat')
}, { once: true })
this.dispatchEvent(new CustomEvent('favorite-toggle', {
bubbles: true,
detail: { id: this.getAttribute('listing-id'), isFavorite: this.isFavorite }
}))
}
}
customElements.define('listing-card', ListingCard)
const style = document.createElement('style')
style.textContent = /* css */`
listing-card {
display: block;
position: relative;
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
overflow: hidden;
transition: box-shadow var(--transition-fast);
min-width: 0;
}
listing-card:hover {
box-shadow: var(--shadow-md);
}
listing-card .listing-link {
display: block;
text-decoration: none;
color: inherit;
}
listing-card .listing-image {
aspect-ratio: 1;
background: var(--color-bg-tertiary);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
listing-card .payment-badge {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-xs) var(--space-sm);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
text-align: center;
}
listing-card .payment-processing {
background: rgba(230, 167, 0, 0.9);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
listing-card .payment-published {
background: var(--color-accent);
color: var(--color-accent-text, #fff);
}
listing-card .payment-expired {
background: rgba(180, 60, 60, 0.85);
color: #fff;
}
listing-card .payment-unpublished {
background: rgba(120, 120, 120, 0.85);
color: #fff;
}
listing-card .pulse-dot {
width: 6px;
height: 6px;
border-radius: var(--radius-full);
background: #fff;
animation: card-pulse 1.5s ease-in-out infinite;
}
@keyframes card-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
listing-card .listing-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
listing-card:hover .listing-image img {
transform: scale(1.08);
}
listing-card .listing-image .placeholder-icon {
width: 48px;
height: 48px;
color: var(--color-border);
}
listing-card .listing-info {
padding: var(--space-sm);
}
listing-card .listing-title {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
margin: 0 0 var(--space-xs);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
listing-card .listing-price-wrapper {
margin: 0 0 var(--space-xs);
}
listing-card .listing-price {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-bold);
color: var(--color-accent);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
listing-card .listing-price-secondary {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
listing-card .listing-location {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
listing-card .favorite-btn {
position: absolute;
top: var(--space-sm);
right: var(--space-sm);
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--color-bg);
border: none;
border-radius: var(--radius-full);
cursor: pointer;
box-shadow: var(--shadow-sm);
transition: all var(--transition-fast);
z-index: 1;
}
listing-card .favorite-btn:hover {
transform: scale(1.1);
}
listing-card .favorite-btn .heart-icon {
color: var(--color-text-muted);
transition: all var(--transition-fast);
}
listing-card .favorite-btn.active .heart-icon {
fill: var(--color-accent);
stroke: var(--color-accent);
color: var(--color-accent);
}
listing-card .favorite-btn:hover .heart-icon {
color: var(--color-accent);
}
listing-card .owner-badge {
position: absolute;
top: var(--space-sm);
left: var(--space-sm);
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--color-bg);
border: none;
border-radius: var(--radius-full);
box-shadow: var(--shadow-sm);
z-index: 2;
color: var(--color-text-muted);
transition: all var(--transition-fast);
text-decoration: none;
}
listing-card .owner-badge:hover {
background: var(--color-primary);
color: white;
transform: scale(1.1);
}
`
document.head.appendChild(style)