Files
pokeshare/src/pages/friends/[username].astro
T
2026-07-28 08:58:44 +02:00

206 lines
8.0 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 BaseLayout from "../../layouts/BaseLayout.astro";
import {
priceCollectionCards,
type MyCardRecord,
} from "../../lib/collection";
const { pb } = Astro.locals;
if (!pb.authStore.isValid) {
return Astro.redirect("/login");
}
const { username } = Astro.params;
if (!username) {
return Astro.redirect("/friends");
}
// Find the target user by username (same public view the friends API uses)
let targetUser;
try {
targetUser = await pb
.collection("public_users")
.getFirstListItem(pb.filter("username = {:username}", { username }));
} catch {
return Astro.redirect("/friends");
}
// Check visibility — Private users' collections are never visible
if (targetUser.visibility === "Private") {
return Astro.redirect("/friends");
}
// Friends-only collections require an accepted friendship
if (targetUser.visibility !== "Public") {
const userId = pb.authStore.record!.id;
const friendship = await pb.collection("friendships").getList(1, 1, {
filter: pb.filter(
"((requester = {:userId} && addressee = {:targetId}) || (requester = {:targetId} && addressee = {:userId})) && status = 'accepted'",
{ userId, targetId: targetUser.id },
),
});
if (friendship.items.length === 0) {
return Astro.redirect("/friends");
}
}
// Fetch their collection
const myCards = (await pb.collection("mycards").getFullList({
filter: pb.filter("user = {:userId}", { userId: targetUser.id }),
sort: "-created",
})) as unknown as MyCardRecord[];
const cardsWithPrice = await priceCollectionCards(myCards);
const totalValue = cardsWithPrice.reduce((s, c) => s + c.totalPrice, 0);
const totalCards = cardsWithPrice.reduce((s, c) => s + c.quantity, 0);
---
<BaseLayout>
<div class="max-w-[1600px] mx-auto px-4 md:px-8 pb-12 mt-8">
<a
href="/friends"
class="inline-block no-underline opacity-70 hover:opacity-100 text-sm mb-4"
>← Amis</a
>
<div class="flex flex-wrap items-center justify-between gap-4 mb-6">
<div class="flex items-center gap-4">
<span class="avatar" data-size="lg">
<span
>{
(
targetUser.name ||
targetUser.username ||
targetUser.email ||
"??"
)
.slice(0, 2)
.toUpperCase()
}</span
>
</span>
<div>
<h1 class="m-0">{targetUser.name || "—"}</h1>
<p class="text-sm opacity-60 m-0">
@{targetUser.username || "—"}
</p>
</div>
</div>
<div class="flex gap-6">
<div
class="bg-muted rounded-lg px-6 py-3 flex flex-col items-center"
>
<span class="text-xs uppercase tracking-wide opacity-70"
>Cartes</span
>
<span class="text-2xl font-bold">{totalCards}</span>
</div>
<div
class="bg-muted rounded-lg px-6 py-3 flex flex-col items-center"
>
<span class="text-xs uppercase tracking-wide opacity-70"
>Valeur totale</span
>
<span class="text-2xl font-bold"
>{totalValue.toFixed(2)} €</span
>
</div>
</div>
</div>
{
cardsWithPrice.length === 0 ? (
<section class="empty">
<header>
<h3>Collection vide</h3>
<p>
{targetUser.name || targetUser.username} n'a pas
encore de cartes dans sa collection.
</p>
</header>
</section>
) : (
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
{cardsWithPrice.map((card) => (
<a
href={`/card/${card.card_id}`}
class="card no-underline hover:-translate-y-0.5 hover:shadow-lg transition-all duration-150 overflow-hidden"
>
<div class="aspect-2.5/3.5 overflow-hidden">
{card.card_image ? (
<img
src={`${card.card_image}/low.webp`}
alt={card.card_name}
class="w-full h-full object-cover"
/>
) : (
<img
src="/assets/placeholder_card.png"
alt={card.card_name}
class="w-full h-full object-cover"
/>
)}
</div>
<div class="p-3 flex flex-col gap-1.5">
<h3 class="m-0 text-sm font-semibold leading-tight truncate">
{card.card_name}
</h3>
{card.set_name && (
<small class="opacity-60 text-xs truncate">
{card.set_name}
</small>
)}
<div class="flex items-center justify-between">
<span
class="badge"
data-variant="secondary"
>
{card.variant}
</span>
<span class="font-semibold text-sm">
×{card.quantity}
</span>
</div>
{card.condition && (
<span
class="badge"
data-variant={
card.condition === "Neuf"
? "primary"
: card.condition === "Joué"
? "destructive"
: "outline"
}
>
{card.condition}
</span>
)}
<div class="flex items-center justify-between mt-2 pt-2 border-t border-border">
<span class="font-bold text-base">
{card.price.toFixed(2)} €
</span>
{card.quantity > 1 && (
<span class="text-xs opacity-70">
= {card.totalPrice.toFixed(2)} €
</span>
)}
</div>
</div>
</a>
))}
</div>
)
}
</div>
</BaseLayout>
<style is:global>
a.card {
text-decoration: none;
}
</style>