yap-blog/src/app/admin/(panel)/posts/[id]/preview/page.tsx
matt 6d84ae1224 Add author accounts with per-tag posting rights
The seeded account is the single admin; it can create author accounts
on the new /admin/users page (username + password) and grant each one
access to specific tags. Authors sign in to a Posts-only panel where
they can write, edit, publish, and unpublish their own posts — every
post must carry at least one granted tag, tags outside the grants are
rejected server-side, and only the admin can create tags or delete
posts (or anything else: pages, comments, settings, and backups stay
admin-only). Admin-only URLs bounce authors to their post list, and
foreign post editors 404.

posts.author_id records ownership; deleting an account keeps its posts
as unowned, admin-managed rows and signs the account out everywhere.
Backups (export v3) store the owner's username per post and re-attach
ownership on import when the account still exists.

Also fixes a latent form bug: a missing newTags field (author forms
don't render it) failed zod validation with an invisible error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:10:02 -04:00

45 lines
1.5 KiB
TypeScript

import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { StatusBadge } from "@/components/admin/StatusBadge";
import { PostArticle } from "@/components/public/PostArticle";
import { requireUser } from "@/lib/auth/dal";
import { parseIdParam } from "@/lib/params";
import { getPostById } from "@/lib/services/posts";
export const metadata: Metadata = { title: "Preview post" };
/** Renders the post exactly as the public site would — drafts included. */
export default async function PostPreviewPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const user = await requireUser();
const { id: rawId } = await params;
const id = parseIdParam(rawId);
if (id === null) notFound();
const post = await getPostById(id);
if (!post) notFound();
if (user.role !== "admin" && post.authorId !== user.id) notFound();
return (
<div>
<div className="mb-8 flex flex-wrap items-center justify-between gap-3 rounded-lg border border-warning/40 bg-warning/10 px-4 py-3 text-sm">
<p className="flex items-center gap-2 text-warning">
<span className="font-medium">Preview</span>
<StatusBadge status={post.status} />
</p>
<Link
href={`/admin/posts/${post.id}/edit`}
className="font-medium text-warning underline underline-offset-4"
>
Back to editor
</Link>
</div>
<PostArticle post={post} />
</div>
);
}