From 7b4e06733f8b2b2dc68135c52f517a2399cfb225 Mon Sep 17 00:00:00 2001 From: Zach Harding Date: Tue, 17 Mar 2026 11:27:16 -0400 Subject: [PATCH] added a button group for quick filtering by productLine --- src/assets/css/_bootstrap.scss | 2 +- src/components/CardGrid.astro | 33 ++++++++++++++---------- src/components/Search.astro | 26 +++++++++++-------- src/pages/partials/cards.astro | 47 ++++++++++++++++++++-------------- 4 files changed, 65 insertions(+), 43 deletions(-) diff --git a/src/assets/css/_bootstrap.scss b/src/assets/css/_bootstrap.scss index b22f8d5..a93d3c6 100644 --- a/src/assets/css/_bootstrap.scss +++ b/src/assets/css/_bootstrap.scss @@ -22,7 +22,7 @@ @import 'bootstrap/scss/alert'; @import 'bootstrap/scss/badge'; // @import 'bootstrap/scss/breadcrumb'; -// @import 'bootstrap/scss/button-group'; +@import 'bootstrap/scss/button-group'; @import 'bootstrap/scss/buttons'; @import 'bootstrap/scss/card'; // @import 'bootstrap/scss/carousel'; diff --git a/src/components/CardGrid.astro b/src/components/CardGrid.astro index 38d06a0..4bb78c4 100644 --- a/src/components/CardGrid.astro +++ b/src/components/CardGrid.astro @@ -15,7 +15,7 @@ import BackToTop from "./BackToTop.astro"
-
+
@@ -48,12 +48,9 @@ import BackToTop from "./BackToTop.astro" (function () { // ── Sort dropdown ───────────────────────────────────────────────────────── - // Plain JS toggle — no dependency on Bootstrap's Dropdown JS initialising. - // Uses event delegation so it works after OOB swaps repopulate #sortBy. document.addEventListener('click', (e) => { const sortBy = document.getElementById('sortBy'); - // Toggle the menu when the button is clicked const btn = e.target.closest('#sortBy [data-bs-toggle="dropdown"]'); if (btn) { e.preventDefault(); @@ -64,7 +61,6 @@ import BackToTop from "./BackToTop.astro" return; } - // Handle sort option selection const opt = e.target.closest('#sortBy .sort-option'); if (opt) { e.preventDefault(); @@ -87,7 +83,6 @@ import BackToTop from "./BackToTop.astro" return; } - // Click outside — close any open sort menu const menu = document.querySelector('#sortBy .dropdown-menu.show'); if (menu) { menu.classList.remove('show'); @@ -96,6 +91,25 @@ import BackToTop from "./BackToTop.astro" } }); + // ── Language toggle ─────────────────────────────────────────────────────── + // Buttons live inside #sortBy which is OOB-swapped from the partial, so the + // listener is registered here on document (persistent shell) instead. + document.addEventListener('click', (e) => { + const btn = e.target.closest('.language-btn'); + if (!btn) return; + e.preventDefault(); + + const input = document.getElementById('languageInput'); + if (input) input.value = btn.dataset.lang; + + const start = document.getElementById('start'); + if (start) start.value = '0'; + + document.getElementById('searchform').dispatchEvent( + new Event('submit', { bubbles: true, cancelable: true }) + ); + }); + // ── Global helpers ──────────────────────────────────────────────────────── window.copyImage = async function(img) { try { @@ -201,7 +215,6 @@ import BackToTop from "./BackToTop.astro" nextBtn.classList.toggle('d-none', next === null); } - // ── Trigger infinite scroll sentinel ───────────────────────────────────── function tryTriggerSentinel() { const sentinel = cardGrid.querySelector('[hx-trigger="revealed"]'); if (!sentinel) return; @@ -212,7 +225,6 @@ import BackToTop from "./BackToTop.astro" } } - // ── Fire card-modal:swapped so the partial's script can init the chart ──── function initChartAfterSwap(modal) { const canvas = modal.querySelector('#priceHistoryChart'); if (!canvas) return; @@ -269,11 +281,9 @@ import BackToTop from "./BackToTop.astro" if (next) loadCard(next, 'next'); } - // ── Nav button clicks ───────────────────────────────────────────────────── document.getElementById('modalPrevBtn').addEventListener('click', navigatePrev); document.getElementById('modalNextBtn').addEventListener('click', navigateNext); - // ── Keyboard ────────────────────────────────────────────────────────────── document.addEventListener('keydown', (e) => { const modal = document.getElementById('cardModal'); if (!modal.classList.contains('show')) return; @@ -281,7 +291,6 @@ import BackToTop from "./BackToTop.astro" if (e.key === 'ArrowRight') { e.preventDefault(); navigateNext(); } }); - // ── Touch / swipe ───────────────────────────────────────────────────────── let touchStartX = 0; let touchStartY = 0; const SWIPE_THRESHOLD = 50; @@ -299,7 +308,6 @@ import BackToTop from "./BackToTop.astro" else navigatePrev(); }, { passive: true }); - // ── HTMX card-modal opens ───────────────────────────────────────────────── document.body.addEventListener('htmx:beforeRequest', async (e) => { if (e.detail.elt.getAttribute('hx-target') !== '#cardModal') return; @@ -363,7 +371,6 @@ import BackToTop from "./BackToTop.astro" } }); - // ── Bootstrap modal events ──────────────────────────────────────────────── const cardModal = document.getElementById('cardModal'); cardModal.addEventListener('shown.bs.modal', () => { updateNavButtons(cardModal); diff --git a/src/components/Search.astro b/src/components/Search.astro index 150f03e..457e8dd 100644 --- a/src/components/Search.astro +++ b/src/components/Search.astro @@ -26,15 +26,21 @@ import { Show } from '@clerk/astro/components' - - \ No newline at end of file + \ No newline at end of file diff --git a/src/pages/partials/cards.astro b/src/pages/partials/cards.astro index 665da0c..446ca68 100644 --- a/src/pages/partials/cards.astro +++ b/src/pages/partials/cards.astro @@ -18,11 +18,6 @@ const facetFields:any = { } // ── Allowed sort values ─────────────────────────────────────────────────── -// Maps the client-supplied key to the actual Typesense sort_by string. -// Never pass raw user input directly to sort_by. -// Note: price sorting uses nmMarketPrice — a field you need to denormalize -// onto your card document in your Typesense indexing step (NM market price -// as an integer in cents, e.g. nmMarketPrice: 499 = $4.99). const sortMap: Record = { 'releaseDate:desc,number:asc': '_text_match:asc,releaseDate:desc,number:asc', 'releaseDate:asc,number:asc': '_text_match:asc,releaseDate:asc,number:asc', @@ -40,8 +35,16 @@ const start = Number(formData.get('start')?.toString() || '0'); const sortKey = formData.get('sort')?.toString() || ''; const resolvedSort = sortMap[sortKey] ?? DEFAULT_SORT; +// ── Language filter ─────────────────────────────────────────────────────── +// Expects a `language` field on your card documents in Typesense. +// Valid values: 'en', 'jp' — anything else (or 'all') means no filter. +const language = formData.get('language')?.toString() || 'all'; +const languageFilter = language === 'en' ? " && productLineName:=`Pokemon`" + : language === 'jp' ? " && productLineName:=`Pokemon Japan`" + : ''; + const filters = Array.from(formData.entries()) - .filter(([key, value]) => key !== 'q' && key !== 'start' && key !== 'sort') + .filter(([key]) => key !== 'q' && key !== 'start' && key !== 'sort' && key !== 'language') .reduce((acc, [key, value]) => { if (!acc[key]) { acc[key] = []; @@ -63,14 +66,15 @@ const facetFilter = (facet:string) => { .filter(([field]) => field !== facet) .map(([field, values]) => `${field}:=[${values.map(v => '`'+v+'`').join(',')}]`) .join(' && '); - return `sealed:false${otherFilters ? ` && ${otherFilters}` : ''}`; + // Language filter is always included so facet counts stay accurate + return `sealed:false${languageFilter}${otherFilters ? ` && ${otherFilters}` : ''}`; }; // primary search values (for cards) let searchArray = [{ collection: 'cards', - filter_by: `sealed:false${filterBy ? ` && ${filterBy}` : ''}`, + filter_by: `sealed:false${languageFilter}${filterBy ? ` && ${filterBy}` : ''}`, per_page: 20, facet_by: '', max_facet_values: 0, @@ -135,8 +139,8 @@ const facetNames = (name:string) => { } const facets = searchResults.results.slice(1).map((result: any) => { - const facet = result.facet_counts[0]; - if (!facet) return facet; + const facet = result.facet_counts?.[0]; + if (!facet) return null; // Sort: checked items first, then alphabetically facet.counts = facet.counts.sort((a: any, b: any) => { @@ -148,7 +152,7 @@ const facets = searchResults.results.slice(1).map((result: any) => { }); return facet; -}); +}).filter(Boolean); --- @@ -178,19 +182,24 @@ const facets = searchResults.results.slice(1).map((result: any) => {
))}
-
+
+
+ + + +
- {sortKey ? ({"releaseDate:desc,number:asc":"Set: Newest to Oldest","releaseDate:asc,number:asc":"Set: Oldest to Newest","marketPrice:desc":"Price: High to Low","marketPrice:asc":"Price: Low to High","number:asc":"Card Number: Ascending","number:desc":"Card Number: Descending"}[sortKey] ?? '') : ''} + {sortKey ? ({"releaseDate:desc,number:asc":"Set: Newest to Oldest","releaseDate:asc,number:asc":"Set: Oldest to Newest","marketPrice:desc":"Price: High to Low","marketPrice:asc":"Price: Low to High","number:asc":"Card Number: Ascending","number:desc":"Card Number: Descending"}[sortKey] ?? '') : ''}
{totalHits} {totalHits === 1 ? ' result' : ' results'}