318 lines
9.6 KiB
JavaScript
318 lines
9.6 KiB
JavaScript
import { t, i18n } from '../../i18n.js'
|
|
import { auth } from '../../services/auth.js'
|
|
import { notificationsService } from '../../services/notifications.js'
|
|
import { router } from '../../router.js'
|
|
|
|
class PageNotifications extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.loading = true
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.render()
|
|
this.loadNotifications()
|
|
this.unsubscribe = i18n.subscribe(() => this.render())
|
|
this.notifUnsubscribe = notificationsService.subscribe(() => {
|
|
this.loading = false
|
|
this.updateContent()
|
|
})
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
if (this.unsubscribe) this.unsubscribe()
|
|
if (this.notifUnsubscribe) this.notifUnsubscribe()
|
|
}
|
|
|
|
async loadNotifications() {
|
|
if (!auth.isLoggedIn()) {
|
|
this.loading = false
|
|
this.updateContent()
|
|
return
|
|
}
|
|
await notificationsService.refresh()
|
|
}
|
|
|
|
render() {
|
|
this.innerHTML = /* html */`
|
|
<div class="notifications-page">
|
|
<header class="page-header">
|
|
<div class="page-header-row">
|
|
<div>
|
|
<h1>${t('notifications.title')}</h1>
|
|
</div>
|
|
${auth.isLoggedIn() && notificationsService.notifications.length > 0 ? `
|
|
<button class="btn btn-outline btn-sm" id="mark-all-read">
|
|
${t('notifications.markAllRead')}
|
|
</button>
|
|
` : ''}
|
|
</div>
|
|
</header>
|
|
|
|
<div id="notifications-content">
|
|
${this.renderContent()}
|
|
</div>
|
|
</div>
|
|
`
|
|
this.setupEventListeners()
|
|
}
|
|
|
|
updateContent() {
|
|
const container = this.querySelector('#notifications-content')
|
|
if (container) {
|
|
container.innerHTML = this.renderContent()
|
|
this.setupEventListeners()
|
|
}
|
|
}
|
|
|
|
renderContent() {
|
|
if (!auth.isLoggedIn()) {
|
|
return /* html */`
|
|
<div class="empty-state">
|
|
<div class="empty-icon">🔔</div>
|
|
<h3>${t('auth.loginRequired')}</h3>
|
|
<button class="btn btn-primary" id="login-btn">${t('auth.login')}</button>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
if (this.loading) {
|
|
return /* html */`
|
|
<div class="notifications-list">
|
|
${Array(3).fill(0).map(() => `
|
|
<div class="notification-skeleton">
|
|
<div class="skeleton-line skeleton-line-short"></div>
|
|
<div class="skeleton-line"></div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
`
|
|
}
|
|
|
|
const notifications = notificationsService.notifications
|
|
if (notifications.length === 0) {
|
|
return /* html */`
|
|
<div class="empty-state">
|
|
<div class="empty-icon">🔔</div>
|
|
<h3>${t('notifications.empty')}</h3>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
return /* html */`
|
|
<div class="notifications-list">
|
|
${notifications.map(n => this.renderNotification(n)).join('')}
|
|
</div>
|
|
`
|
|
}
|
|
|
|
renderNotification(n) {
|
|
const typeText = t(`notifications.${n.type}`)
|
|
const date = new Date(n.date_created)
|
|
const timeAgo = this.formatTimeAgo(date)
|
|
|
|
return /* html */`
|
|
<div class="notification-item ${n.read ? '' : 'unread'}" data-id="${n.id}" data-type="${n.type}" data-ref="${n.reference_id || ''}">
|
|
<div class="notification-icon">${this.getIcon(n.type)}</div>
|
|
<div class="notification-body">
|
|
<p class="notification-text">${typeText}</p>
|
|
<span class="notification-time">${timeAgo}</span>
|
|
</div>
|
|
<button class="btn btn-icon notification-delete" data-delete="${n.id}" title="${t('common.remove')}">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
getIcon(type) {
|
|
const icons = {
|
|
listing_created: '📝',
|
|
listing_published: '✅',
|
|
listing_expired: '⏰',
|
|
new_message: '💬',
|
|
favorite_added: '❤️'
|
|
}
|
|
return icons[type] || '🔔'
|
|
}
|
|
|
|
formatTimeAgo(date) {
|
|
const now = new Date()
|
|
const diffMs = now - date
|
|
const diffMin = Math.floor(diffMs / 60000)
|
|
const diffHours = Math.floor(diffMs / 3600000)
|
|
const diffDays = Math.floor(diffMs / 86400000)
|
|
|
|
if (diffMin < 1) return t('messages.today')
|
|
if (diffMin < 60) return `${diffMin} min`
|
|
if (diffHours < 24) return `${diffHours}h`
|
|
if (diffDays === 1) return t('messages.yesterday')
|
|
return t('messages.daysAgo', { days: diffDays })
|
|
}
|
|
|
|
setupEventListeners() {
|
|
this.querySelector('#mark-all-read')?.addEventListener('click', async () => {
|
|
await notificationsService.markAllRead()
|
|
this.render()
|
|
})
|
|
|
|
this.querySelector('#login-btn')?.addEventListener('click', () => {
|
|
document.querySelector('auth-modal')?.show('login')
|
|
})
|
|
|
|
this.querySelectorAll('.notification-item').forEach(item => {
|
|
item.addEventListener('click', async (e) => {
|
|
if (e.target.closest('.notification-delete')) return
|
|
|
|
const id = item.dataset.id
|
|
const type = item.dataset.type
|
|
const ref = item.dataset.ref
|
|
|
|
await notificationsService.markRead(id)
|
|
|
|
if (ref) {
|
|
if (type === 'new_message') {
|
|
router.navigate(`/messages`)
|
|
} else {
|
|
router.navigate(`/listing/${ref}`)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
this.querySelectorAll('.notification-delete').forEach(btn => {
|
|
btn.addEventListener('click', async (e) => {
|
|
e.stopPropagation()
|
|
await notificationsService.remove(btn.dataset.delete)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
customElements.define('page-notifications', PageNotifications)
|
|
|
|
const style = document.createElement('style')
|
|
style.textContent = /* css */`
|
|
page-notifications .notifications-page {
|
|
padding: var(--space-lg) 0;
|
|
}
|
|
|
|
page-notifications .page-header {
|
|
margin-bottom: var(--space-xl);
|
|
}
|
|
|
|
page-notifications .page-header-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
page-notifications .page-header h1 {
|
|
margin: 0;
|
|
}
|
|
|
|
page-notifications .notifications-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1px;
|
|
background-color: var(--color-border);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
page-notifications .notification-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-md);
|
|
padding: var(--space-md) var(--space-lg);
|
|
background-color: var(--color-bg);
|
|
cursor: pointer;
|
|
transition: background-color var(--transition-fast);
|
|
}
|
|
|
|
page-notifications .notification-item:hover {
|
|
background-color: var(--color-bg-secondary);
|
|
}
|
|
|
|
page-notifications .notification-item.unread {
|
|
background-color: var(--color-bg-secondary);
|
|
}
|
|
|
|
page-notifications .notification-item.unread .notification-text {
|
|
font-weight: var(--font-weight-semibold);
|
|
}
|
|
|
|
page-notifications .notification-icon {
|
|
font-size: 1.5rem;
|
|
flex-shrink: 0;
|
|
filter: grayscale(1);
|
|
}
|
|
|
|
page-notifications .notification-body {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
page-notifications .notification-text {
|
|
margin: 0 0 var(--space-xs);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
page-notifications .notification-time {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
page-notifications .notification-delete {
|
|
flex-shrink: 0;
|
|
opacity: 0;
|
|
transition: opacity var(--transition-fast);
|
|
}
|
|
|
|
page-notifications .notification-item:hover .notification-delete {
|
|
opacity: 1;
|
|
}
|
|
|
|
page-notifications .empty-state {
|
|
text-align: center;
|
|
padding: var(--space-3xl);
|
|
}
|
|
|
|
page-notifications .empty-icon {
|
|
font-size: 4rem;
|
|
margin-bottom: var(--space-md);
|
|
filter: grayscale(1);
|
|
}
|
|
|
|
page-notifications .empty-state h3 {
|
|
margin: 0 0 var(--space-sm);
|
|
}
|
|
|
|
page-notifications .notification-skeleton {
|
|
padding: var(--space-md) var(--space-lg);
|
|
background-color: var(--color-bg);
|
|
}
|
|
|
|
page-notifications .skeleton-line {
|
|
height: 14px;
|
|
background-color: var(--color-bg-tertiary);
|
|
border-radius: var(--radius-sm);
|
|
margin-bottom: var(--space-xs);
|
|
}
|
|
|
|
page-notifications .skeleton-line-short {
|
|
width: 60%;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
page-notifications .notification-delete {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
`
|
|
document.head.appendChild(style)
|