feat: add reputation system with deals, ratings, level badges, and chat-widget deal confirmation
This commit is contained in:
@@ -9,6 +9,7 @@ import '../chat-widget.js'
|
||||
import '../location-map.js'
|
||||
import '../listing-card.js'
|
||||
import { categoriesService } from '../../services/categories.js'
|
||||
import { reputationService } from '../../services/reputation.js'
|
||||
|
||||
class PageListing extends HTMLElement {
|
||||
constructor() {
|
||||
@@ -20,6 +21,7 @@ class PageListing extends HTMLElement {
|
||||
this.rates = null
|
||||
this.isOwner = false
|
||||
this.hasPendingChats = false
|
||||
this.sellerReputation = null
|
||||
this.handleCurrencyChange = this.handleCurrencyChange.bind(this)
|
||||
}
|
||||
|
||||
@@ -69,6 +71,8 @@ class PageListing extends HTMLElement {
|
||||
if (this.listing?.user_created) {
|
||||
await this.loadSellerListings()
|
||||
}
|
||||
|
||||
await this.loadSellerReputation()
|
||||
} catch (e) {
|
||||
console.error('Failed to load listing:', e)
|
||||
this.listing = null
|
||||
@@ -179,6 +183,23 @@ class PageListing extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
async loadSellerReputation() {
|
||||
if (!this.listing?.id) return
|
||||
try {
|
||||
const convsResponse = await directus.get('/items/conversations', {
|
||||
filter: { listing_id: { _eq: this.listing.id } },
|
||||
fields: ['participant_hash_2'],
|
||||
limit: 1
|
||||
})
|
||||
const conv = (convsResponse.data || [])[0]
|
||||
if (conv?.participant_hash_2) {
|
||||
this.sellerReputation = await reputationService.getReputation(conv.participant_hash_2)
|
||||
}
|
||||
} catch (e) {
|
||||
// No conversations yet = no reputation to show
|
||||
}
|
||||
}
|
||||
|
||||
loadFavoriteState() {
|
||||
this.isFavorite = favoritesService.isFavorite(this.listingId)
|
||||
}
|
||||
@@ -443,10 +464,44 @@ class PageListing extends HTMLElement {
|
||||
<span>${t('listing.memberSince')} 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
${this.renderSellerReputation()}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
renderSellerReputation() {
|
||||
if (!this.sellerReputation) return ''
|
||||
|
||||
const rep = this.sellerReputation
|
||||
const levelInfo = reputationService.getLevelInfo(rep.level)
|
||||
const dealCount = rep.deals_completed
|
||||
const dealText = dealCount === 1 ? t('reputation.dealsSingular') : t('reputation.deals', { count: dealCount })
|
||||
|
||||
let ratingHtml = ''
|
||||
if (rep.avg_rating > 0) {
|
||||
const stars = '★'.repeat(Math.round(rep.avg_rating)) + '☆'.repeat(5 - Math.round(rep.avg_rating))
|
||||
ratingHtml = /* html */`<span class="seller-rating" title="${t('reputation.avgRating', { rating: rep.avg_rating.toFixed(1) })}">${stars}</span>`
|
||||
}
|
||||
|
||||
const showWarning = rep.level === 'new'
|
||||
|
||||
return /* html */`
|
||||
<div class="seller-reputation">
|
||||
<div class="seller-level">
|
||||
<span class="level-badge">${levelInfo.badge}</span>
|
||||
<span class="level-name">${t(levelInfo.i18nKey)}</span>
|
||||
${dealCount > 0 ? `<span class="deal-count">· ${dealText}</span>` : ''}
|
||||
</div>
|
||||
${ratingHtml}
|
||||
</div>
|
||||
${showWarning ? /* html */`
|
||||
<div class="seller-warning">
|
||||
${t('reputation.newWarning')}
|
||||
</div>
|
||||
` : ''}
|
||||
`
|
||||
}
|
||||
|
||||
renderListingCard(listing) {
|
||||
const imageId = listing.images?.[0]?.directus_files_id?.id || listing.images?.[0]?.directus_files_id
|
||||
const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 180) : ''
|
||||
@@ -961,6 +1016,52 @@ style.textContent = /* css */`
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
page-listing .seller-reputation {
|
||||
padding: var(--space-sm) 0 0;
|
||||
margin-top: var(--space-sm);
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
page-listing .seller-level {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
page-listing .level-badge {
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
page-listing .level-name {
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
page-listing .deal-count {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
page-listing .seller-rating {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
page-listing .seller-warning {
|
||||
margin-top: var(--space-sm);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Seller Listings */
|
||||
page-listing .seller-listings {
|
||||
margin-top: var(--space-3xl);
|
||||
|
||||
Reference in New Issue
Block a user