diff --git a/css/base.css b/css/base.css index 8a4d54b..d4d43c7 100644 --- a/css/base.css +++ b/css/base.css @@ -42,6 +42,19 @@ button, input, textarea, select { color: inherit; } +/* Override browser autofill styles */ +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +input:-webkit-autofill:active, +textarea:-webkit-autofill, +select:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px var(--color-bg) inset !important; + -webkit-text-fill-color: var(--color-text) !important; + box-shadow: 0 0 0 1000px var(--color-bg) inset !important; + transition: background-color 5000s ease-in-out 0s; +} + button { cursor: pointer; border: none; diff --git a/js/components/pages/page-home.js b/js/components/pages/page-home.js index b33922f..29b10b4 100644 --- a/js/components/pages/page-home.js +++ b/js/components/pages/page-home.js @@ -10,44 +10,95 @@ class PageHome extends HTMLElement { this.listings = [] this.loading = true this.error = null + this.currentFilters = {} } connectedCallback() { this.render() + this.setupEventListeners() this.loadListings() - this.unsubscribe = i18n.subscribe(() => this.render()) + this.unsubscribe = i18n.subscribe(() => { + this.render() + this.setupEventListeners() + }) } disconnectedCallback() { if (this.unsubscribe) this.unsubscribe() } + setupEventListeners() { + const searchBox = this.querySelector('search-box') + + // Listen for filter changes (live update) + searchBox?.addEventListener('filter-change', (e) => { + this.currentFilters = e.detail + this.loadListings() + }) + + // Listen for search submit (with query) + searchBox?.addEventListener('search', (e) => { + e.preventDefault() + e.stopPropagation() + this.currentFilters = e.detail + this.loadListings() + return false + }) + } + async loadListings() { try { this.loading = true this.error = null - this.render() + this.updateListingsSection() - const response = await listingsService.getRecentListings(12) + const filters = { + search: this.currentFilters.query || undefined, + category: this.currentFilters.category || undefined, + limit: 20 + } + + const response = await listingsService.getListingsWithFilters(filters) this.listings = response.items || [] this.loading = false - this.render() + this.updateListingsSection() } catch (err) { console.error('Failed to load listings:', err) this.error = err.message this.loading = false - this.render() + this.updateListingsSection() } } + updateListingsSection() { + const listingsGrid = this.querySelector('.listings-grid') + const sectionTitle = this.querySelector('.listings-title') + + if (listingsGrid) { + listingsGrid.innerHTML = this.renderListings() + } + + if (sectionTitle) { + sectionTitle.textContent = this.getListingsTitle() + } + } + + getListingsTitle() { + if (this.currentFilters.query || this.currentFilters.category) { + const count = this.listings.length + return t('search.resultsCount', { count }) || `${count} Ergebnisse` + } + return t('home.recentListings') + } + render() { this.innerHTML = /* html */`
- +
-

${t('home.recentListings')}

+

${this.getListingsTitle()}

${this.renderListings()}
@@ -57,21 +108,21 @@ class PageHome extends HTMLElement { renderListings() { if (this.loading) { - return /* html */`

${t('common.loading') || 'Laden...'}

` + return /* html */`

${t('common.loading')}

` } if (this.error) { - return /* html */`

${t('common.error') || 'Fehler beim Laden'}

` + return /* html */`

${t('common.error')}

` } if (this.listings.length === 0) { - return /* html */`

${t('home.noListings') || 'Keine Anzeigen gefunden'}

` + return /* html */`

${t('home.noListings')}

` } return this.listings.map(listing => { const imageId = listing.images?.[0]?.directus_files_id?.id || listing.images?.[0]?.directus_files_id const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 300) : '' - const locationName = listing.location?.name || listing.location || '' + const locationName = listing.location?.name || '' return /* html */` ${this.listing.condition ? `${this.getConditionLabel(this.listing.condition)}` : ''} ${this.listing.shipping ? `📦 ${t('listing.shippingAvailable')}` : ''} + 👁 ${this.formatViews(this.listing.views || 0)} ${t('listing.postedOn')} ${createdDate} @@ -442,6 +446,13 @@ class PageListing extends HTMLElement { return labels[condition] || condition } + formatViews(count) { + if (count === 1) { + return `1 ${t('listing.viewSingular')}` + } + return `${count} ${t('listing.viewPlural')}` + } + formatDescription(text) { if (!text) return '' return text @@ -604,6 +615,10 @@ style.textContent = /* css */` page-listing .meta-date { margin-left: auto; } + + page-listing .views-icon { + filter: grayscale(1); + } page-listing .listing-description, page-listing .listing-location-section { diff --git a/js/components/search-box.js b/js/components/search-box.js index 3dc2f6b..ecb4741 100644 --- a/js/components/search-box.js +++ b/js/components/search-box.js @@ -337,8 +337,8 @@ class SearchBox extends HTMLElement { this.selectedCategory = '' this.selectedSubcategory = '' this._expandedCategory = '' - this.saveFiltersToStorage() categoryMenu?.classList.remove('open') + this.emitFilterChange() this.render() this.setupEventListeners() }) @@ -350,8 +350,8 @@ class SearchBox extends HTMLElement { this.selectedCategory = item.dataset.category this.selectedSubcategory = item.dataset.subcategory this._expandedCategory = '' - this.saveFiltersToStorage() categoryMenu?.classList.remove('open') + this.emitFilterChange() this.render() this.setupEventListeners() }) @@ -366,7 +366,7 @@ class SearchBox extends HTMLElement { this.useCurrentLocation = false this.selectedCountry = e.target.value } - this.saveFiltersToStorage() + this.emitFilterChange() this.render() this.setupEventListeners() } @@ -377,7 +377,7 @@ class SearchBox extends HTMLElement { // Radius select handler (both desktop and mobile) const handleRadiusChange = (e) => { this.selectedRadius = parseInt(e.target.value) - this.saveFiltersToStorage() + this.emitFilterChange() } radiusSelect?.addEventListener('change', handleRadiusChange) @@ -410,6 +410,28 @@ class SearchBox extends HTMLElement { measurer.remove() } + getFilterDetails() { + return { + query: this.searchQuery, + category: this.selectedCategory, + subcategory: this.selectedSubcategory, + country: this.selectedCountry, + useCurrentLocation: this.useCurrentLocation, + lat: this.currentLat, + lng: this.currentLng, + radius: this.selectedRadius + } + } + + emitFilterChange() { + this.saveFiltersToStorage() + + this.dispatchEvent(new CustomEvent('filter-change', { + bubbles: true, + detail: this.getFilterDetails() + })) + } + handleSearch() { const params = new URLSearchParams() @@ -432,14 +454,7 @@ class SearchBox extends HTMLElement { bubbles: true, cancelable: true, detail: { - query: this.searchQuery, - category: this.selectedCategory, - subcategory: this.selectedSubcategory, - country: this.selectedCountry, - useCurrentLocation: this.useCurrentLocation, - lat: this.currentLat, - lng: this.currentLng, - radius: this.selectedRadius, + ...this.getFilterDetails(), params: params.toString() } }) diff --git a/js/services/directus.js b/js/services/directus.js index 9d5b44a..9306f10 100644 --- a/js/services/directus.js +++ b/js/services/directus.js @@ -331,6 +331,34 @@ class DirectusService { return response.data } + async incrementListingViews(id) { + // Check if user already viewed this listing (session-based) + const viewedKey = `dgray_viewed_${id}` + if (sessionStorage.getItem(viewedKey)) { + return false + } + + try { + // Get current views + const listing = await this.get(`/items/listings/${id}`, { + fields: ['views'] + }) + const currentViews = listing.data?.views || 0 + + // Increment views + await this.patch(`/items/listings/${id}`, { + views: currentViews + 1 + }) + + // Mark as viewed for this session + sessionStorage.setItem(viewedKey, 'true') + return true + } catch (error) { + console.error('Failed to increment views:', error) + return false + } + } + async deleteListing(id) { return this.delete(`/items/listings/${id}`) } diff --git a/locales/de.json b/locales/de.json index 08d6438..9c16552 100644 --- a/locales/de.json +++ b/locales/de.json @@ -116,6 +116,8 @@ "contactHint": "Kopiere die Adresse und sende den Betrag über dein Monero-Wallet.", "priceOnRequest": "Preis auf Anfrage", "shippingAvailable": "Versand verfügbar", + "viewSingular": "Aufruf", + "viewPlural": "Aufrufe", "share": "Teilen", "report": "Melden", "moreFromSeller": "Weitere Anzeigen des Anbieters" diff --git a/locales/en.json b/locales/en.json index a21ec97..2375b87 100644 --- a/locales/en.json +++ b/locales/en.json @@ -116,6 +116,8 @@ "contactHint": "Copy the address and send the amount using your Monero wallet.", "priceOnRequest": "Price on request", "shippingAvailable": "Shipping available", + "viewSingular": "view", + "viewPlural": "views", "share": "Share", "report": "Report", "moreFromSeller": "More from this seller" diff --git a/locales/fr.json b/locales/fr.json index 71b8565..06e2988 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -116,6 +116,8 @@ "contactHint": "Copiez l'adresse et envoyez le montant via votre portefeuille Monero.", "priceOnRequest": "Prix sur demande", "shippingAvailable": "Livraison disponible", + "viewSingular": "vue", + "viewPlural": "vues", "share": "Partager", "report": "Signaler", "moreFromSeller": "Autres annonces du vendeur"