Files
buzz/app/(ui)/donate/top/page.tsx
T
dmgnr f6f8f79238
Build and Push Image / build-backend (push) Successful in 52s
Build and Push Image / build-frontend (push) Successful in 2m38s
Build and Push Image / push (push) Successful in 1m1s
fix(don): filter out anonymous from top page
2026-06-28 21:18:10 +07:00

97 lines
2.7 KiB
TypeScript

import type { SQL } from "drizzle-orm";
import { asc, desc, max, ne, sql, sum } from "drizzle-orm";
import type { Metadata } from "next";
import { Suspense } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { db } from "@/lib/db";
import { donations } from "@/lib/db/schema";
import { fileToDataUrl } from "@/lib/utils";
import { Podium } from "./podium";
import { TopDonateTable } from "./table";
type NumberString = `${number}`;
type NumberLike = number | NumberString;
export const metadata: Metadata = {
title: "โดเนทขึ้นจอ",
description: "ท็อป 10 อันดับคนโดเนท",
};
export default function Page() {
return (
<Suspense fallback={<PageSkeleton />}>
<Content />
</Suspense>
);
}
function PageSkeleton() {
return (
<div className="flex min-h-svh flex-col justify-center">
<div className="mx-auto flex w-full max-w-4xl flex-col gap-4 p-4">
<div className="mt-6 flex items-end justify-center gap-4">
<Skeleton className="h-36 w-36 rounded-xl" />
<Skeleton className="h-48 w-36 rounded-xl" />
<Skeleton className="h-32 w-36 rounded-xl" />
</div>
<Skeleton className="h-64 w-full rounded-xl" />
</div>
</div>
);
}
async function Content() {
const list = await db
.select({
i: sql<NumberLike>`ROW_NUMBER() OVER
(ORDER BY ${desc(sum(donations.amount))}, ${asc(max(donations.id))})`,
name: donations.name,
amount: sum(donations.amount) as unknown as SQL<number>,
})
.from(donations)
.where(ne(donations.name, "Anonymous"))
.offset(3)
.limit(7)
.groupBy(donations.name)
.orderBy(desc(sum(donations.amount)), asc(max(donations.id)));
const podium = await db
.select({
name: donations.name,
amount: sum(donations.amount).as("amount"),
image: sql<Buffer | null>`
(
array_agg(${donations.image}
ORDER BY ${donations.id} DESC
)
)[1]
`,
})
.from(donations)
.where(ne(donations.name, "Anonymous"))
.groupBy(donations.name)
.orderBy(desc(sum(donations.amount)), asc(max(donations.id)))
.limit(3)
.then((r) =>
Promise.all(
r.map(async (d) => ({
...d,
image: d.image
? await fileToDataUrl(new File([Buffer.from(d.image)], "image.jpg"))
: undefined,
})),
),
);
return (
<div className="flex min-h-svh flex-col justify-center">
<div className="mx-auto flex max-w-4xl flex-col gap-4 p-4">
<Podium data={podium} />
<TopDonateTable data={list} />
</div>
</div>
);
}
export const dynamic = "force-dynamic";