132 lines
4.9 KiB
TypeScript
132 lines
4.9 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import { useCallback, useEffect, useState } from "react";
|
|||
|
|
import { ChevronLeft, ChevronRight, HeartHandshake } from "lucide-react";
|
|||
|
|
import { Card } from "@/components/ui/Card";
|
|||
|
|
import { Avatar } from "@/components/ui/Avatar";
|
|||
|
|
import { Button } from "@/components/ui/Button";
|
|||
|
|
import { EmptyState } from "@/components/ui/EmptyState";
|
|||
|
|
import { FullScreenLoader } from "@/components/ui/FullScreenLoader";
|
|||
|
|
import { ErrorBanner } from "@/components/ui/ErrorBanner";
|
|||
|
|
import { formatApiError } from "@/lib/hooks";
|
|||
|
|
import { AdminMatchView, listAllMatches } from "@/lib/api";
|
|||
|
|
|
|||
|
|
const PAGE_SIZE = 20;
|
|||
|
|
|
|||
|
|
export default function MatchesPage() {
|
|||
|
|
const [items, setItems] = useState<AdminMatchView[] | null>(null);
|
|||
|
|
const [total, setTotal] = useState(0);
|
|||
|
|
const [page, setPage] = useState(1);
|
|||
|
|
const [error, setError] = useState<string | null>(null);
|
|||
|
|
|
|||
|
|
const load = useCallback(async (p: number) => {
|
|||
|
|
setError(null);
|
|||
|
|
try {
|
|||
|
|
const res = await listAllMatches(p, PAGE_SIZE);
|
|||
|
|
setItems(res.items);
|
|||
|
|
setTotal(res.total);
|
|||
|
|
} catch (err) {
|
|||
|
|
setError(formatApiError(err));
|
|||
|
|
}
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|||
|
|
load(page);
|
|||
|
|
}, [page, load]);
|
|||
|
|
|
|||
|
|
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="flex flex-col gap-6 max-w-3xl">
|
|||
|
|
<div>
|
|||
|
|
<h1 className="font-[var(--font-display)] font-extrabold text-[24px]">
|
|||
|
|
رفقا (مچها) 💞
|
|||
|
|
</h1>
|
|||
|
|
<p className="text-[14px] text-(--color-content-secondary) mt-1">
|
|||
|
|
همهی جفتهایی که با هم مچ شدن، جدیدترین اول.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{error && <ErrorBanner message={error} />}
|
|||
|
|
|
|||
|
|
{!items ? (
|
|||
|
|
<FullScreenLoader />
|
|||
|
|
) : items.length === 0 ? (
|
|||
|
|
<Card>
|
|||
|
|
<EmptyState
|
|||
|
|
icon={<HeartHandshake size={32} />}
|
|||
|
|
title="هنوز هیچ مچی اتفاق نیفتاده"
|
|||
|
|
description="وقتی دو نفر همدیگه رو لایک کنن، اینجا نشون داده میشن."
|
|||
|
|
/>
|
|||
|
|
</Card>
|
|||
|
|
) : (
|
|||
|
|
<>
|
|||
|
|
<Card padded={false} className="overflow-hidden">
|
|||
|
|
{items.map((m, i) => (
|
|||
|
|
<div
|
|||
|
|
key={m.id}
|
|||
|
|
className={`flex items-center gap-4 px-4 py-3.5 ${
|
|||
|
|
i !== items.length - 1 ? "border-b border-(--color-border-subtle)" : ""
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<div className="flex items-center -space-x-3 space-x-reverse shrink-0">
|
|||
|
|
<Avatar name={m.user_one_name || m.user_one_phone} />
|
|||
|
|
<Avatar name={m.user_two_name || m.user_two_phone} />
|
|||
|
|
</div>
|
|||
|
|
<div className="flex-1 min-w-0 grid grid-cols-2 gap-3">
|
|||
|
|
<div className="min-w-0">
|
|||
|
|
<p className="font-bold text-[13.5px] text-(--color-content-primary) truncate">
|
|||
|
|
{m.user_one_name || "بدون نام"}
|
|||
|
|
</p>
|
|||
|
|
<p dir="ltr" className="text-[11.5px] text-(--color-content-tertiary) text-right">
|
|||
|
|
{m.user_one_phone}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="min-w-0">
|
|||
|
|
<p className="font-bold text-[13.5px] text-(--color-content-primary) truncate">
|
|||
|
|
{m.user_two_name || "بدون نام"}
|
|||
|
|
</p>
|
|||
|
|
<p dir="ltr" className="text-[11.5px] text-(--color-content-tertiary) text-right">
|
|||
|
|
{m.user_two_phone}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<p className="text-[11.5px] text-(--color-content-tertiary) shrink-0">
|
|||
|
|
{new Date(m.created_at).toLocaleDateString("fa-IR")}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<p className="text-[12px] text-(--color-content-tertiary)">
|
|||
|
|
صفحه {page.toLocaleString("fa-IR")} از {totalPages.toLocaleString("fa-IR")} · {total.toLocaleString("fa-IR")} مچ
|
|||
|
|
</p>
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<Button
|
|||
|
|
variant="tertiary"
|
|||
|
|
size="sm"
|
|||
|
|
icon={<ChevronRight size={14} />}
|
|||
|
|
disabled={page <= 1}
|
|||
|
|
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
|||
|
|
>
|
|||
|
|
قبلی
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
variant="tertiary"
|
|||
|
|
size="sm"
|
|||
|
|
icon={<ChevronLeft size={14} />}
|
|||
|
|
disabled={page >= totalPages}
|
|||
|
|
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
|||
|
|
>
|
|||
|
|
بعدی
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|