feat : update
This commit is contained in:
@@ -1,182 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { desc, sql } from "drizzle-orm";
|
||||
import { ArrowUpRight, Plus } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { db } from "@/db";
|
||||
import { shareTexts } from "@/db/schema";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
function formatBangkok(date: Date) {
|
||||
return new Intl.DateTimeFormat("en-GB", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
timeZone: "Asia/Bangkok",
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export default async function AllSharesPage() {
|
||||
const shares = await db
|
||||
.select({
|
||||
id: shareTexts.id,
|
||||
authorName: shareTexts.authorName,
|
||||
source: shareTexts.source,
|
||||
description: shareTexts.description,
|
||||
contentPreview: sql<string>`left(${shareTexts.content}, 160)`,
|
||||
createdAt: shareTexts.createdAt,
|
||||
})
|
||||
.from(shareTexts)
|
||||
.orderBy(desc(shareTexts.createdAt));
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<header className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Share</h1>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
{shares.length.toLocaleString("th-TH")} ลิงก์สาธารณะ
|
||||
</p>
|
||||
</div>
|
||||
<Button asChild>
|
||||
<Link href="/share">
|
||||
<Plus data-icon="inline-start" />
|
||||
สร้างลิงก์ใหม่
|
||||
</Link>
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>ข้อความที่แชร์ทั้งหมด</CardTitle>
|
||||
<CardDescription>
|
||||
เลือกข้อความเพื่อเปิดหน้าสาธารณะ
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="hidden border md:block">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Share</TableHead>
|
||||
<TableHead>Preview</TableHead>
|
||||
<TableHead>Author</TableHead>
|
||||
<TableHead>Source</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead className="text-right">Open</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{shares.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={6}
|
||||
className="h-24 text-center text-muted-foreground"
|
||||
>
|
||||
ยังไม่มีข้อความที่แชร์
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
shares.map((share) => (
|
||||
<TableRow key={share.id}>
|
||||
<TableCell className="font-mono font-medium">
|
||||
<Link
|
||||
href={`/share/${share.id}`}
|
||||
className="hover:underline"
|
||||
>
|
||||
{share.id}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<p className="max-w-md truncate text-muted-foreground">
|
||||
{share.description || share.contentPreview}
|
||||
</p>
|
||||
</TableCell>
|
||||
<TableCell>{share.authorName}</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={
|
||||
share.source === "bot" ? "secondary" : "outline"
|
||||
}
|
||||
>
|
||||
{share.source === "bot" ? "Bot" : "Web"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{formatBangkok(share.createdAt)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button variant="ghost" size="icon-sm" asChild>
|
||||
<Link
|
||||
href={`/share/${share.id}`}
|
||||
aria-label={`Open share ${share.id}`}
|
||||
>
|
||||
<ArrowUpRight />
|
||||
</Link>
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 md:hidden">
|
||||
{shares.length === 0 ? (
|
||||
<p className="py-8 text-center text-sm text-muted-foreground">
|
||||
ยังไม่มีข้อความที่แชร์
|
||||
</p>
|
||||
) : (
|
||||
shares.map((share) => (
|
||||
<Card key={share.id} size="sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="font-mono">
|
||||
<Link href={`/share/${share.id}`}>{share.id}</Link>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{share.authorName} · {formatBangkok(share.createdAt)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="line-clamp-2 text-sm text-muted-foreground">
|
||||
{share.description || share.contentPreview}
|
||||
</p>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<Badge
|
||||
variant={
|
||||
share.source === "bot" ? "secondary" : "outline"
|
||||
}
|
||||
>
|
||||
{share.source === "bot" ? "Bot" : "Web"}
|
||||
</Badge>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href={`/share/${share.id}`}>
|
||||
เปิด
|
||||
<ArrowUpRight data-icon="inline-end" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -69,6 +69,29 @@ export async function getXpLeaderboardPage(
|
||||
return buildXpLeaderboard(rows, requestedPage, searchQuery);
|
||||
}
|
||||
|
||||
export async function getMemberXpStats(userId: string) {
|
||||
const database = getLeaderboardDatabase();
|
||||
const [sourceRows, leaderboard] = await Promise.all([
|
||||
database
|
||||
.select({
|
||||
level: userLevels.level,
|
||||
totalXp: userLevels.xp,
|
||||
})
|
||||
.from(userLevels)
|
||||
.where(eq(userLevels.userId, userId))
|
||||
.limit(1),
|
||||
getXpLeaderboardPage(1, userId),
|
||||
]);
|
||||
const rankedRow = leaderboard.rows.find((row) => row.userId === userId);
|
||||
const sourceRow = sourceRows[0];
|
||||
|
||||
return {
|
||||
level: sourceRow?.level ?? 0,
|
||||
totalXp: sourceRow?.totalXp ?? "0",
|
||||
rank: rankedRow?.rank ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getVoiceLeaderboardPage(
|
||||
currentChannelIds: string[],
|
||||
requestedPage: number,
|
||||
@@ -108,3 +131,21 @@ export async function getVoiceLeaderboardPage(
|
||||
searchQuery,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getMemberVoiceStats(
|
||||
userId: string,
|
||||
currentChannelIds: string[],
|
||||
) {
|
||||
const leaderboard = await getVoiceLeaderboardPage(
|
||||
currentChannelIds,
|
||||
1,
|
||||
undefined,
|
||||
userId,
|
||||
);
|
||||
const rankedRow = leaderboard.rows.find((row) => row.userId === userId);
|
||||
|
||||
return {
|
||||
totalSeconds: rankedRow?.totalSeconds ?? "0",
|
||||
rank: rankedRow?.rank ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
+2
-14
@@ -11,7 +11,7 @@ const nextConfig: NextConfig = {
|
||||
root: __dirname,
|
||||
},
|
||||
serverExternalPackages: ["bun"],
|
||||
allowedDevOrigins: ['test.gunshiz.top'],
|
||||
allowedDevOrigins: ['test.gunshiz.top', 'astral.neko-piranha.ts.net'],
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
@@ -21,19 +21,7 @@ const nextConfig: NextConfig = {
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'cdn.discordapp.com',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'p16-sign-sg.tiktokcdn.com',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'p16-sign.tiktokcdn-us.com',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: '*.tiktokcdn.com',
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user