Refactor requests, refactor users, add home dashboard
This commit is contained in:
@ -1,9 +0,0 @@
|
||||
import { CommissionRequestsTable } from "@/components/commissions/CommissionRequestsTable";
|
||||
|
||||
export default function CommissionPage() {
|
||||
return (
|
||||
<div>
|
||||
<CommissionRequestsTable />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -12,15 +12,16 @@ export default async function CommissionRequestPage({
|
||||
if (!request) notFound();
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-5xl space-y-6 p-4 md:p-8">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Commission Request</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Submitted: {new Date(request.createdAt).toLocaleString()} · ID: {request.id}
|
||||
</p>
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">Commission Request</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Submitted: {new Date(request.createdAt).toLocaleString()} · ID: {request.id}
|
||||
</p>
|
||||
</div>
|
||||
<CommissionRequestEditor request={request as any} />
|
||||
</div>
|
||||
|
||||
<CommissionRequestEditor request={request as any} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
25
src/app/(admin)/commissions/requests/page.tsx
Normal file
25
src/app/(admin)/commissions/requests/page.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import RequestsTable from "@/components/commissions/requests/RequestsTable";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export default async function CommissionPage() {
|
||||
const items = await prisma.commissionRequest.findMany({
|
||||
include: {
|
||||
_count: { select: { files: true } },
|
||||
},
|
||||
orderBy: { index: "desc" },
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">Commission Requests</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
List of all incomming requests via website.
|
||||
</p>
|
||||
</div>
|
||||
<RequestsTable requests={items} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,228 @@
|
||||
export default function HomePage() {
|
||||
import Link from "next/link";
|
||||
|
||||
import { getAdminDashboard } from "@/actions/home/getDashboard";
|
||||
import { StatCard } from "@/components/home/StatCard";
|
||||
import { StatusPill } from "@/components/home/StatusPill";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
function fmtDate(d: Date) {
|
||||
return new Intl.DateTimeFormat("de-DE", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
}).format(d);
|
||||
}
|
||||
|
||||
export default async function HomePage() {
|
||||
const data = await getAdminDashboard();
|
||||
|
||||
return (
|
||||
<div>
|
||||
ADMIN HOME
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">Dashboard</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Quick status of content, commissions, and user hygiene.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* <Button asChild variant="secondary">
|
||||
<Link href="/artworks/new">Add artwork</Link>
|
||||
</Button> */}
|
||||
<Button asChild>
|
||||
<Link href="/commissions">Review requests</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top stats */}
|
||||
<section className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<StatCard
|
||||
title="Artworks"
|
||||
value={data.artworks.total}
|
||||
hint={
|
||||
<>
|
||||
{data.artworks.published} published · {data.artworks.unpublished}{" "}
|
||||
unpublished
|
||||
</>
|
||||
}
|
||||
href="/artworks"
|
||||
/>
|
||||
<StatCard
|
||||
title="Needs work"
|
||||
value={data.artworks.needsWork}
|
||||
hint="Artwork items flagged for review"
|
||||
href="/artworks?needsWork=true"
|
||||
/>
|
||||
<StatCard
|
||||
title="Commission requests"
|
||||
value={data.commissions.total}
|
||||
hint={
|
||||
<>
|
||||
{data.commissions.new7d} new (7d) · {data.commissions.new30d} new
|
||||
(30d)
|
||||
</>
|
||||
}
|
||||
href="/commissions"
|
||||
/>
|
||||
<StatCard
|
||||
title="Users"
|
||||
value={data.users.total}
|
||||
hint={
|
||||
<>
|
||||
{data.users.unverified} unverified · {data.users.banned} banned
|
||||
</>
|
||||
}
|
||||
href="/users"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 lg:grid-cols-3">
|
||||
{/* Artwork status */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Artwork status</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<StatusPill label="Published" value={data.artworks.published} />
|
||||
<StatusPill label="Unpublished" value={data.artworks.unpublished} />
|
||||
<StatusPill label="NSFW" value={data.artworks.nsfw} />
|
||||
<StatusPill label="Set as header" value={data.artworks.header} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Color pipeline */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Color pipeline</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<StatusPill
|
||||
label="Pending"
|
||||
value={data.artworks.colorStatus.PENDING}
|
||||
/>
|
||||
<StatusPill
|
||||
label="Processing"
|
||||
value={data.artworks.colorStatus.PROCESSING}
|
||||
/>
|
||||
<StatusPill
|
||||
label="Ready"
|
||||
value={data.artworks.colorStatus.READY}
|
||||
/>
|
||||
<StatusPill
|
||||
label="Failed"
|
||||
value={data.artworks.colorStatus.FAILED}
|
||||
/>
|
||||
<div className="pt-2 text-sm text-muted-foreground">
|
||||
Tip: keep “Failed” near zero—those typically need a re-run or file
|
||||
fix.
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Commissions status */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Commission pipeline</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<StatusPill label="New" value={data.commissions.status.NEW} />
|
||||
<StatusPill
|
||||
label="Reviewing"
|
||||
value={data.commissions.status.REVIEWING}
|
||||
/>
|
||||
<StatusPill
|
||||
label="Accepted"
|
||||
value={data.commissions.status.ACCEPTED}
|
||||
/>
|
||||
<StatusPill
|
||||
label="Rejected"
|
||||
value={data.commissions.status.REJECTED}
|
||||
/>
|
||||
<StatusPill label="Spam" value={data.commissions.status.SPAM} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
{/* Recent activity */}
|
||||
<section className="grid gap-4 lg:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle className="text-base">Recent artworks</CardTitle>
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/artworks">Open</Link>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{data.artworks.recent.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">No artworks yet.</div>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{data.artworks.recent.map((a) => (
|
||||
<li
|
||||
key={a.id}
|
||||
className="flex items-center justify-between gap-3 rounded-md border px-3 py-2"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-medium">{a.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{fmtDate(a.createdAt)} · {a.colorStatus}
|
||||
{a.published ? " · published" : " · draft"}
|
||||
{a.needsWork ? " · needs work" : ""}
|
||||
</div>
|
||||
</div>
|
||||
<Button asChild variant="secondary" size="sm">
|
||||
<Link href={`/artworks/${a.slug}`}>Open</Link>
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle className="text-base">Recent commission requests</CardTitle>
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href="/commissions/requests">Open</Link>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{data.commissions.recent.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
No commission requests yet.
|
||||
</div>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{data.commissions.recent.map((r) => (
|
||||
<li
|
||||
key={r.id}
|
||||
className="flex items-center justify-between gap-3 rounded-md border px-3 py-2"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-medium">
|
||||
{r.customerName}{" "}
|
||||
<span className="text-muted-foreground">
|
||||
({r.customerEmail})
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{fmtDate(r.createdAt)} · {r.status}
|
||||
</div>
|
||||
</div>
|
||||
<Button asChild variant="secondary" size="sm">
|
||||
<Link href={`/commissions/requests/${r.id}`}>Open</Link>
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,21 @@
|
||||
import { ResetPasswordForm } from "@/components/auth/ResetPasswordForm";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function ResetPasswordPage({
|
||||
export default async function ResetPasswordPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { token?: string };
|
||||
}) {
|
||||
const { token } = await searchParams;
|
||||
|
||||
if (!token) {
|
||||
return (
|
||||
<div className="mx-auto max-w-md p-6">
|
||||
<p>No valid token, please try again or get back to <Link href="/">Home</Link></p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md p-6">
|
||||
<h1 className="text-xl font-semibold">Reset password</h1>
|
||||
@ -12,7 +23,7 @@ export default function ResetPasswordPage({
|
||||
Choose a new password.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<ResetPasswordForm token={searchParams.token ?? ""} />
|
||||
<ResetPasswordForm token={token ?? ""} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user