226 lines
7.2 KiB
JavaScript
226 lines
7.2 KiB
JavaScript
import { t, i18n } from '../i18n.js';
|
||
import { escapeHTML, formatPrice } from '../utils/helpers.js';
|
||
|
||
class ListingCard extends HTMLElement {
|
||
static get observedAttributes() {
|
||
return ['listing-id', 'title', 'price', 'currency', 'location', 'image'];
|
||
}
|
||
|
||
constructor() {
|
||
super();
|
||
this.isFavorite = false;
|
||
}
|
||
|
||
connectedCallback() {
|
||
this.loadFavoriteState();
|
||
this.render();
|
||
this.setupEventListeners();
|
||
}
|
||
|
||
attributeChangedCallback() {
|
||
if (this.isConnected) {
|
||
this.render();
|
||
this.setupEventListeners();
|
||
}
|
||
}
|
||
|
||
loadFavoriteState() {
|
||
const id = this.getAttribute('listing-id');
|
||
if (id) {
|
||
const favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
|
||
this.isFavorite = favorites.includes(id);
|
||
}
|
||
}
|
||
|
||
saveFavoriteState() {
|
||
const id = this.getAttribute('listing-id');
|
||
if (!id) return;
|
||
|
||
let favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
|
||
|
||
if (this.isFavorite) {
|
||
if (!favorites.includes(id)) favorites.push(id);
|
||
} else {
|
||
favorites = favorites.filter(f => f !== id);
|
||
}
|
||
|
||
localStorage.setItem('favorites', JSON.stringify(favorites));
|
||
}
|
||
|
||
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');
|
||
|
||
const priceDisplay = price ? formatPrice(parseFloat(price), currency) : '–';
|
||
const favoriteLabel = this.isFavorite ? t('home.removeFavorite') : t('home.addFavorite');
|
||
|
||
const placeholderSvg = /* html */`
|
||
<svg class="placeholder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||
<polyline points="21 15 16 10 5 21"></polyline>
|
||
</svg>
|
||
`;
|
||
|
||
this.innerHTML = /* html */`
|
||
<a href="#/listing/${escapeHTML(id)}" class="listing-link">
|
||
<div class="listing-image" ${image ? `style="background-image: url('${escapeHTML(image)}')"` : ''}>
|
||
${!image ? placeholderSvg : ''}
|
||
</div>
|
||
<div class="listing-info">
|
||
<h3 class="listing-title">${escapeHTML(title)}</h3>
|
||
<p class="listing-price">${priceDisplay}</p>
|
||
<p class="listing-location">${escapeHTML(location)}</p>
|
||
</div>
|
||
</a>
|
||
<button
|
||
class="favorite-btn ${this.isFavorite ? 'active' : ''}"
|
||
aria-label="${favoriteLabel}"
|
||
aria-pressed="${this.isFavorite}"
|
||
>
|
||
<svg class="heart-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
|
||
</svg>
|
||
</button>
|
||
`;
|
||
}
|
||
|
||
setupEventListeners() {
|
||
const btn = this.querySelector('.favorite-btn');
|
||
btn?.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
this.toggleFavorite();
|
||
});
|
||
}
|
||
|
||
toggleFavorite() {
|
||
this.isFavorite = !this.isFavorite;
|
||
this.saveFavoriteState();
|
||
|
||
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);
|
||
}
|
||
|
||
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);
|
||
background-size: cover;
|
||
background-position: center;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
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 {
|
||
font-size: var(--font-size-sm);
|
||
font-weight: var(--font-weight-bold);
|
||
color: var(--color-primary);
|
||
margin: 0;
|
||
}
|
||
|
||
listing-card .listing-location {
|
||
font-size: var(--font-size-xs);
|
||
color: var(--color-text-muted);
|
||
margin: 0;
|
||
}
|
||
|
||
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);
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|