improve page create and page listing
This commit is contained in:
@@ -205,10 +205,10 @@ class PageCreate extends HTMLElement {
|
||||
id="location"
|
||||
name="location"
|
||||
value="${this.escapeHtml(this.formData.location)}"
|
||||
required
|
||||
data-i18n-placeholder="create.locationPlaceholder"
|
||||
placeholder="${t('create.locationPlaceholder')}"
|
||||
>
|
||||
<p class="field-hint">${t('create.locationHint') || 'Stadt oder PLZ eingeben'}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -413,13 +413,17 @@ class PageCreate extends HTMLElement {
|
||||
if (this.formData.price_mode) listingData.price_mode = this.formData.price_mode
|
||||
if (this.formData.category) listingData.category = this.formData.category
|
||||
if (this.formData.condition) listingData.condition = this.formData.condition
|
||||
if (this.formData.location) listingData.location = this.formData.location
|
||||
if (this.formData.shipping) listingData.shipping = this.formData.shipping
|
||||
if (this.formData.moneroAddress) listingData.monero_address = this.formData.moneroAddress
|
||||
|
||||
// Add images if uploaded
|
||||
// Add images in junction table format
|
||||
if (imageIds.length > 0) {
|
||||
listingData.images = imageIds
|
||||
listingData.images = {
|
||||
create: imageIds.map((id, index) => ({
|
||||
directus_files_id: id,
|
||||
sort: index
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Creating listing:', listingData)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t, i18n } from '../../i18n.js'
|
||||
import { getListingById } from '../../data/mock-listings.js'
|
||||
import { directus } from '../../services/directus.js'
|
||||
import '../chat-widget.js'
|
||||
|
||||
class PageListing extends HTMLElement {
|
||||
@@ -21,9 +21,12 @@ class PageListing extends HTMLElement {
|
||||
}
|
||||
|
||||
async loadListing() {
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
|
||||
this.listing = getListingById(this.listingId)
|
||||
try {
|
||||
this.listing = await directus.getListing(this.listingId)
|
||||
} catch (e) {
|
||||
console.error('Failed to load listing:', e)
|
||||
this.listing = null
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
this.render()
|
||||
@@ -50,7 +53,9 @@ class PageListing extends HTMLElement {
|
||||
return
|
||||
}
|
||||
|
||||
const hasImages = this.listing.images && this.listing.images.length > 0
|
||||
const images = this.listing.images || []
|
||||
const hasImages = images.length > 0
|
||||
const firstImage = hasImages ? this.getImageUrl(images[0]) : null
|
||||
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>
|
||||
@@ -59,38 +64,63 @@ class PageListing extends HTMLElement {
|
||||
</svg>
|
||||
`
|
||||
|
||||
const categoryName = this.listing.category?.name || this.listing.category?.slug || ''
|
||||
const locationName = this.listing.location?.name || ''
|
||||
const price = this.formatPrice(this.listing.price, this.listing.currency)
|
||||
const createdDate = this.listing.date_created
|
||||
? new Date(this.listing.date_created).toLocaleDateString()
|
||||
: ''
|
||||
|
||||
this.innerHTML = /* html */`
|
||||
<article class="listing-detail">
|
||||
<div class="listing-gallery">
|
||||
<div class="listing-image-main">
|
||||
${!hasImages ? placeholderSvg : ''}
|
||||
${firstImage
|
||||
? `<img src="${firstImage}" alt="${this.escapeHtml(this.listing.title)}">`
|
||||
: placeholderSvg}
|
||||
</div>
|
||||
${images.length > 1 ? `
|
||||
<div class="listing-thumbnails">
|
||||
${images.map((img, i) => `
|
||||
<button class="thumbnail ${i === 0 ? 'active' : ''}" data-index="${i}">
|
||||
<img src="${this.getImageUrl(img, 100)}" alt="">
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="listing-info">
|
||||
<header>
|
||||
<span class="badge badge-primary">${t(`categories.${this.listing.category}`)}</span>
|
||||
${categoryName ? `<span class="badge badge-primary">${this.escapeHtml(categoryName)}</span>` : ''}
|
||||
<h1>${this.escapeHtml(this.listing.title)}</h1>
|
||||
<p class="listing-price">€ ${this.listing.price}</p>
|
||||
<p class="listing-location">📍 ${this.escapeHtml(this.listing.location)}</p>
|
||||
<p class="listing-price">${price}</p>
|
||||
${locationName ? `<p class="listing-location">📍 ${this.escapeHtml(locationName)}</p>` : ''}
|
||||
${this.listing.condition ? `<p class="listing-condition">${this.getConditionLabel(this.listing.condition)}</p>` : ''}
|
||||
</header>
|
||||
|
||||
<section class="listing-description">
|
||||
<h2 data-i18n="listing.description">${t('listing.description')}</h2>
|
||||
<p>${this.escapeHtml(this.listing.description)}</p>
|
||||
<div class="description-text">${this.formatDescription(this.listing.description)}</div>
|
||||
</section>
|
||||
|
||||
<section class="listing-seller">
|
||||
<h2 data-i18n="listing.seller">${t('listing.seller')}</h2>
|
||||
<div class="seller-card">
|
||||
<div class="seller-avatar">${this.listing.seller.name.charAt(0)}</div>
|
||||
<div class="seller-avatar">?</div>
|
||||
<div class="seller-info">
|
||||
<strong>${this.escapeHtml(this.listing.seller.name)}</strong>
|
||||
<span>${t('listing.memberSince')} ${this.listing.seller.memberSince}</span>
|
||||
<strong>${t('listing.anonymousSeller')}</strong>
|
||||
${createdDate ? `<span>${t('listing.postedOn')} ${createdDate}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
${this.listing.shipping ? `
|
||||
<section class="listing-shipping">
|
||||
<span class="shipping-badge">📦 ${t('listing.shippingAvailable')}</span>
|
||||
</section>
|
||||
` : ''}
|
||||
|
||||
<div class="listing-actions">
|
||||
<button class="btn btn-primary btn-lg" id="contact-btn">
|
||||
${t('listing.contactSeller')}
|
||||
@@ -116,9 +146,9 @@ class PageListing extends HTMLElement {
|
||||
<div class="tab-content" id="tab-chat">
|
||||
<chat-widget
|
||||
listing-id="${this.listing.id}"
|
||||
recipient-id="${this.listing.seller.name}"
|
||||
recipient-key="${this.listing.seller.publicKey || 'demo-key-' + this.listing.id}"
|
||||
recipient-name="${this.escapeHtml(this.listing.seller.name)}"
|
||||
recipient-id="${this.listing.user_created?.id || ''}"
|
||||
recipient-key=""
|
||||
recipient-name="${t('listing.anonymousSeller')}"
|
||||
></chat-widget>
|
||||
</div>
|
||||
|
||||
@@ -128,13 +158,15 @@ class PageListing extends HTMLElement {
|
||||
<div class="monero-section">
|
||||
<label>${t('listing.moneroAddress')}</label>
|
||||
<div class="monero-address">
|
||||
<code id="monero-addr">${this.listing.seller.moneroAddress || '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H'}</code>
|
||||
<button class="btn btn-outline btn-copy" id="copy-btn" title="${t('listing.copyAddress')}">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<code id="monero-addr">${this.listing.monero_address || t('listing.noMoneroAddress')}</code>
|
||||
${this.listing.monero_address ? `
|
||||
<button class="btn btn-outline btn-copy" id="copy-btn" title="${t('listing.copyAddress')}">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -190,7 +222,45 @@ class PageListing extends HTMLElement {
|
||||
})
|
||||
}
|
||||
|
||||
getImageUrl(image, size = null) {
|
||||
const fileId = image?.directus_files_id?.id || image?.directus_files_id || image
|
||||
if (!fileId) return null
|
||||
return size
|
||||
? directus.getThumbnailUrl(fileId, size)
|
||||
: directus.getFileUrl(fileId)
|
||||
}
|
||||
|
||||
formatPrice(price, currency = 'EUR') {
|
||||
if (price === null || price === undefined) return t('listing.priceOnRequest')
|
||||
|
||||
if (currency === 'XMR') {
|
||||
return `${parseFloat(price).toFixed(4)} XMR`
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat('de-DE', {
|
||||
style: 'currency',
|
||||
currency: currency
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
formatDescription(desc) {
|
||||
if (!desc) return ''
|
||||
return this.escapeHtml(desc).replace(/\n/g, '<br>')
|
||||
}
|
||||
|
||||
getConditionLabel(condition) {
|
||||
const labels = {
|
||||
new: t('create.conditionNew'),
|
||||
like_new: t('create.conditionLikeNew'),
|
||||
good: t('create.conditionGood'),
|
||||
fair: t('create.conditionFair'),
|
||||
poor: t('create.conditionPoor')
|
||||
}
|
||||
return labels[condition] || condition
|
||||
}
|
||||
|
||||
escapeHtml(text) {
|
||||
if (!text) return ''
|
||||
const div = document.createElement('div')
|
||||
div.textContent = text
|
||||
return div.innerHTML
|
||||
|
||||
@@ -158,7 +158,11 @@ class DirectusService {
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value === undefined || value === null) continue
|
||||
|
||||
if (typeof value === 'object') {
|
||||
if (key === 'sort' && Array.isArray(value)) {
|
||||
searchParams.set(key, value.join(','))
|
||||
} else if (key === 'fields' && Array.isArray(value)) {
|
||||
searchParams.set(key, value.join(','))
|
||||
} else if (typeof value === 'object') {
|
||||
searchParams.set(key, JSON.stringify(value))
|
||||
} else {
|
||||
searchParams.set(key, value)
|
||||
@@ -258,8 +262,7 @@ class DirectusService {
|
||||
'category.icon',
|
||||
'location.id',
|
||||
'location.name',
|
||||
'location.region',
|
||||
'user_created.id'
|
||||
'location.region'
|
||||
],
|
||||
filter: options.filter || { status: { _eq: 'published' } },
|
||||
sort: options.sort || ['-date_created'],
|
||||
@@ -285,9 +288,7 @@ class DirectusService {
|
||||
'images.directus_files_id.*',
|
||||
'category.*',
|
||||
'category.translations.*',
|
||||
'location.*',
|
||||
'user_created.id',
|
||||
'user_created.date_created'
|
||||
'location.*'
|
||||
]
|
||||
})
|
||||
return response.data
|
||||
@@ -309,11 +310,11 @@ class DirectusService {
|
||||
|
||||
async getMyListings() {
|
||||
const response = await this.get('/items/listings', {
|
||||
fields: ['*', 'images.directus_files_id.id', 'category.name', 'location.name'],
|
||||
fields: ['*', 'images.directus_files_id.id', 'category.id', 'category.name', 'location.name'],
|
||||
filter: { user_created: { _eq: '$CURRENT_USER' } },
|
||||
sort: ['-date_created']
|
||||
})
|
||||
return response.data
|
||||
return response.data || []
|
||||
}
|
||||
|
||||
async searchListings(query, options = {}) {
|
||||
@@ -356,12 +357,12 @@ class DirectusService {
|
||||
|
||||
async getCategories() {
|
||||
const response = await this.get('/items/categories', {
|
||||
fields: ['*', 'translations.*', 'parent.id', 'parent.name'],
|
||||
fields: ['*', 'translations.*'],
|
||||
filter: { status: { _eq: 'published' } },
|
||||
sort: ['sort', 'name'],
|
||||
limit: -1
|
||||
})
|
||||
return response.data
|
||||
return response.data || []
|
||||
}
|
||||
|
||||
async getCategory(idOrSlug) {
|
||||
|
||||
@@ -99,12 +99,17 @@
|
||||
"backHome": "Zur Startseite",
|
||||
"description": "Beschreibung",
|
||||
"seller": "Anbieter",
|
||||
"anonymousSeller": "Anonymer Anbieter",
|
||||
"memberSince": "Mitglied seit",
|
||||
"postedOn": "Eingestellt am",
|
||||
"contactSeller": "Anbieter kontaktieren",
|
||||
"paymentInfo": "Bezahlung erfolgt direkt über Monero (XMR).",
|
||||
"moneroAddress": "Monero-Adresse des Anbieters",
|
||||
"noMoneroAddress": "Keine Monero-Adresse angegeben",
|
||||
"copyAddress": "Adresse kopieren",
|
||||
"contactHint": "Kopiere die Adresse und sende den Betrag über dein Monero-Wallet."
|
||||
"contactHint": "Kopiere die Adresse und sende den Betrag über dein Monero-Wallet.",
|
||||
"priceOnRequest": "Preis auf Anfrage",
|
||||
"shippingAvailable": "Versand verfügbar"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Nachricht senden",
|
||||
|
||||
@@ -99,12 +99,17 @@
|
||||
"backHome": "Back to Home",
|
||||
"description": "Description",
|
||||
"seller": "Seller",
|
||||
"anonymousSeller": "Anonymous Seller",
|
||||
"memberSince": "Member since",
|
||||
"postedOn": "Posted on",
|
||||
"contactSeller": "Contact Seller",
|
||||
"paymentInfo": "Payment is made directly via Monero (XMR).",
|
||||
"moneroAddress": "Seller's Monero Address",
|
||||
"noMoneroAddress": "No Monero address provided",
|
||||
"copyAddress": "Copy address",
|
||||
"contactHint": "Copy the address and send the amount using your Monero wallet."
|
||||
"contactHint": "Copy the address and send the amount using your Monero wallet.",
|
||||
"priceOnRequest": "Price on request",
|
||||
"shippingAvailable": "Shipping available"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Send Message",
|
||||
|
||||
@@ -99,12 +99,17 @@
|
||||
"backHome": "Retour à l'accueil",
|
||||
"description": "Description",
|
||||
"seller": "Vendeur",
|
||||
"anonymousSeller": "Vendeur anonyme",
|
||||
"memberSince": "Membre depuis",
|
||||
"postedOn": "Publié le",
|
||||
"contactSeller": "Contacter le vendeur",
|
||||
"paymentInfo": "Le paiement s'effectue directement via Monero (XMR).",
|
||||
"moneroAddress": "Adresse Monero du vendeur",
|
||||
"noMoneroAddress": "Aucune adresse Monero fournie",
|
||||
"copyAddress": "Copier l'adresse",
|
||||
"contactHint": "Copiez l'adresse et envoyez le montant via votre portefeuille Monero."
|
||||
"contactHint": "Copiez l'adresse et envoyez le montant via votre portefeuille Monero.",
|
||||
"priceOnRequest": "Prix sur demande",
|
||||
"shippingAvailable": "Livraison disponible"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Envoyer un message",
|
||||
|
||||
Reference in New Issue
Block a user