import { attachArtworkRendition, createAlbum, createArtwork, createCategory, createGallery, createTag, linkArtworkToGrouping, listArtworks, listMediaAssets, listMediaFoundationGroups, } from "@cms/db" import { Button } from "@cms/ui/button" import { revalidatePath } from "next/cache" import { redirect } from "next/navigation" import { AdminShell } from "@/components/admin-shell" import { requirePermissionForRoute } from "@/lib/route-guards" export const dynamic = "force-dynamic" type SearchParamsInput = Record type GroupType = "gallery" | "album" | "category" | "tag" function readField(formData: FormData, key: string): string { const value = formData.get(key) return typeof value === "string" ? value.trim() : "" } function readOptionalField(formData: FormData, key: string): string | undefined { const value = readField(formData, key) return value.length > 0 ? value : undefined } function readFirstValue(value: string | string[] | undefined): string | null { if (Array.isArray(value)) { return value[0] ?? null } return value ?? null } function slugify(input: string): string { return input .toLowerCase() .trim() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 180) } function redirectWithState(params: { notice?: string; error?: string }) { const query = new URLSearchParams() if (params.notice) { query.set("notice", params.notice) } if (params.error) { query.set("error", params.error) } const value = query.toString() redirect(value ? `/portfolio?${value}` : "/portfolio") } async function requireWritePermission() { await requirePermissionForRoute({ nextPath: "/portfolio", permission: "media:write", scope: "team", }) } async function createArtworkAction(formData: FormData) { "use server" await requireWritePermission() const title = readField(formData, "title") const slug = slugify(readField(formData, "slug") || title) try { await createArtwork({ title, slug, description: readOptionalField(formData, "description"), medium: readOptionalField(formData, "medium"), dimensions: readOptionalField(formData, "dimensions"), framing: readOptionalField(formData, "framing"), availability: readOptionalField(formData, "availability"), year: (() => { const raw = readField(formData, "year") return raw ? Number(raw) : undefined })(), }) } catch { redirectWithState({ error: "Failed to create artwork." }) } revalidatePath("/portfolio") redirectWithState({ notice: "Artwork created." }) } async function createGroupAction(formData: FormData) { "use server" await requireWritePermission() const type = readField(formData, "groupType") as GroupType const name = readField(formData, "name") const slug = slugify(readField(formData, "slug") || name) try { if (type === "gallery") { await createGallery({ name, slug, description: readOptionalField(formData, "description"), }) } else if (type === "album") { await createAlbum({ name, slug, description: readOptionalField(formData, "description"), }) } else if (type === "category") { await createCategory({ name, slug, description: readOptionalField(formData, "description"), }) } else { await createTag({ name, slug, }) } } catch { redirectWithState({ error: "Failed to create grouping entity." }) } revalidatePath("/portfolio") redirectWithState({ notice: `${type} created.` }) } async function linkArtworkGroupAction(formData: FormData) { "use server" await requireWritePermission() const artworkId = readField(formData, "artworkId") const groupType = readField(formData, "groupType") as GroupType const groupId = readField(formData, "groupId") try { await linkArtworkToGrouping({ artworkId, groupType, groupId, }) } catch { redirectWithState({ error: "Failed to link artwork to grouping." }) } revalidatePath("/portfolio") redirectWithState({ notice: "Artwork linked to grouping." }) } async function attachRenditionAction(formData: FormData) { "use server" await requireWritePermission() try { await attachArtworkRendition({ artworkId: readField(formData, "artworkId"), mediaAssetId: readField(formData, "mediaAssetId"), slot: readField(formData, "slot"), width: (() => { const raw = readField(formData, "width") return raw ? Number(raw) : undefined })(), height: (() => { const raw = readField(formData, "height") return raw ? Number(raw) : undefined })(), isPrimary: readField(formData, "isPrimary") === "true", }) } catch { redirectWithState({ error: "Failed to attach artwork rendition." }) } revalidatePath("/portfolio") redirectWithState({ notice: "Rendition attached." }) } export default async function PortfolioPage({ searchParams, }: { searchParams: Promise }) { const role = await requirePermissionForRoute({ nextPath: "/portfolio", permission: "media:read", scope: "team", }) const [resolvedSearchParams, artworks, mediaAssets, groups] = await Promise.all([ searchParams, listArtworks(30), listMediaAssets(200), listMediaFoundationGroups(), ]) const notice = readFirstValue(resolvedSearchParams.notice) const error = readFirstValue(resolvedSearchParams.error) return ( {notice ? (
{notice}
) : null} {error ? (
{error}
) : null}

Create Artwork