Files
pokemon/src/pages/partials/cards.astro
2026-02-25 14:12:11 -05:00

87 lines
3.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
import { client } from '../../db/typesense.ts';
import { db } from '../../db';
import RarityIcon from '../../components/RarityIcon.astro';
export const prerender = false;
// get the query from post request using form data
const formData = await Astro.request.formData();
const query = formData.get('q')?.toString() || '';
const start = Number(formData.get('start')?.toString() || '0');
// use typesense to search for cards matching the query and return the productIds of the results
const searchResults = await client.collections('cards').documents().search({
q: query,
filter_by: 'sealed:false',
query_by: 'productLineName,productName,setName,number,rarityName,Artist',
per_page: 20,
page: Math.floor(start / 20) + 1,
});
const productIds = searchResults.hits?.map((hit: any) => hit.document.productId) ?? [];
const totalHits = searchResults.found;
// get pokemon data with prices and set info using searchResults and then query the database for each card to get the prices and set info
const pokemon = await db.query.cards.findMany({
where: { productId: { in: productIds, }, },
with: {
prices: true,
set: true,
}
});
// format price to 2 decimal places (or 0 if price >=100) and adds a $ sign, if the price is null it returns ""
const formatPrice = (price:any) => {
if (price === null) {
return "—";
}
price = Number(price);
if (price > 99.99) return `$${Math.round(price)}`;
return `$${price.toFixed(2)}`;
};
const conditionOrder = ["Near Mint", "Lightly Played", "Moderately Played", "Heavily Played", "Damaged"];
---
{(start === 0) &&
<script define:vars={{ totalHits }} is:inline>
let hits = totalHits;
</script>
}
{pokemon.map((card) => (
<div class="col">
<div class="inventory-button position-relative float-end shadow-filter text-center d-none">
<div class="inventory-label pt-2">+/-</div>
</div>
<div hx-get={`/partials/card-modal?cardId=${card.cardId}`} hx-target="#cardModal" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#cardModal" onclick="const cardTitle = this.querySelector('#cardImage').getAttribute('alt'); dataLayer.push({'event': 'virtualPageview', 'pageUrl': this.getAttribute('hx-get'), 'pageTitle': cardTitle, 'previousUrl': '/pokemon'});">
<img src={`/cards/${card.productId}.jpg`} alt={card.productName} id="cardImage" loading="lazy" decoding="async" class="img-fluid rounded-3 mb-2 card-image image-grow w-100" onerror="this.onerror=null;this.src='/cards/noImage.webp'"/>
</div>
<div class="row row-cols-5 gx-1 price-row mb-2">
{card.prices
.slice()
.sort((a, b) => conditionOrder.indexOf(a.condition) - conditionOrder.indexOf(b.condition))
.filter((price, index, arr) =>
arr.findIndex(p => p.condition === price.condition) === index
)
.map((price) => (
<div class="col price-label ps-1">
{price.condition.split(' ').map((w) => w[0]).join('')}
<br />{formatPrice(price.marketPrice)}
</div>
))}
</div>
<div class="h5 my-0">{card.productName}</div>
<div class="d-flex flex-row lh-1 mt-1">
<div class="text-secondary flex-grow-1">{card.set?.setCode}</div>
<div class="text-secondary">{card.number}</div>
<span class="ps-2 small-icon"><RarityIcon rarity={card.rarityName} /></span>
</div>
<div>{card.variant}<span class="d-none">{card.productId}</span></div>
</div>
))}
{start + 20 < totalHits &&
<div hx-post="/partials/cards" hx-trigger="revealed" hx-include="#searchform" hx-target="#cardGrid" hx-swap="beforeend" hx-on--after-request="afterUpdate(event)">
Loading...
</div>
}