feat: add admin share index

This commit is contained in:
2026-08-16 02:01:05 +07:00 Unverified
parent bdd8442e75
commit 0494de677d
2 changed files with 200 additions and 1 deletions
+185
View File
@@ -0,0 +1,185 @@
import Link from "next/link";
import { desc, sql } from "drizzle-orm";
import { ArrowUpRight, Plus } from "lucide-react";
import { AdminShellServer } from "@/components/admin-shell-server";
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 AdminSharePage() {
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 (
<AdminShellServer>
<main className="mx-auto flex w-full max-w-7xl flex-col gap-6 p-4 pb-12 md:p-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("en-US")} published links
</p>
</div>
<Button asChild>
<Link href="/share">
<Plus data-icon="inline-start" />
Create share
</Link>
</Button>
</header>
<Card>
<CardHeader>
<CardTitle>All shares</CardTitle>
<CardDescription>
Open a public page without loading the full text into this list.
</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"
>
No shares yet.
</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">
No shares yet.
</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}`}>
Open
<ArrowUpRight data-icon="inline-end" />
</Link>
</Button>
</div>
</CardContent>
</Card>
))
)}
</div>
</CardContent>
</Card>
</main>
</AdminShellServer>
);
}
+15 -1
View File
@@ -15,7 +15,15 @@ import {
SidebarInset,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { ClipboardList, KeyRound, Server, LayoutDashboard, Upload, LucideIcon } from "lucide-react";
import {
ClipboardList,
KeyRound,
LayoutDashboard,
LucideIcon,
Server,
Share2,
Upload,
} from "lucide-react";
import { ThemeToggle } from "@/components/theme-toggle";
import { DropdownMenuAvatar, DiscordMemberProfile } from "@/components/avatar";
@@ -40,6 +48,12 @@ const NAV_ITEMS: NavItem[] = [
icon: ClipboardList,
isActive: (pathname) => pathname === "/admin/form",
},
{
href: "/admin/share",
label: "Share",
icon: Share2,
isActive: (pathname) => pathname.startsWith("/admin/share"),
},
{
href: "/admin/backend",
label: "Backend",