"use server"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { api, ApiError, unwrap } from "@/lib/api"; import { attempt, field, type ActionState } from "@/lib/actions"; const done = (id: string) => { revalidatePath(`/products/${id}`); revalidatePath("/library"); }; /** The person's library and a product: get it, remove it, stay on a revision or follow the latest. */ export async function libraryAction(_: ActionState, form: FormData): Promise { const id = field(form, "id"); return attempt(async () => { const lib = api.library({ id }); switch (field(form, "intent")) { case "get": unwrap(await lib.put()); break; case "remove": unwrap(await lib.delete()); break; case "stay": unwrap(await lib.patch({ stayOn: Number(field(form, "revision")) })); break; case "follow": unwrap(await lib.patch({ stayOn: null })); break; } done(id); }); } /** Its creator deletes the product. */ export async function deleteProduct(_: ActionState, form: FormData): Promise { const id = field(form, "id"); const state = await attempt(async () => { unwrap(await api.products({ id }).delete()); done(id); revalidatePath("/products"); }); if (state?.error) return state; redirect(`/products/${id}`); } /** Moderation removes the product. */ export async function removeProduct(_: ActionState, form: FormData): Promise { const id = field(form, "id"); const state = await attempt(async () => { unwrap(await api.products({ id }).remove.post({ reason: field(form, "reason") })); done(id); revalidatePath("/products"); }); if (state?.error) return state; redirect("/products"); } export async function retract(_: ActionState, form: FormData): Promise { const id = field(form, "id"); return attempt(async () => { unwrap(await api.products({ id }).revisions({ n: Number(field(form, "revision")) }).retract.post()); done(id); }); } /** Saves a product's info. Fields left out of the form (locked ones are disabled) aren't changed. */ export async function saveProduct(_: ActionState, form: FormData): Promise { const id = field(form, "id"); const has = (name: string) => form.has(name); const rating = field(form, "rating"); const listed = field(form, "listed"); return attempt(async () => { const { fields } = unwrap( await api.products({ id }).patch({ ...(has("name") ? { name: field(form, "name") } : {}), ...(has("description") ? { description: String(form.get("description") ?? "") } : {}), ...(rating === "adult" || rating === "general" ? { rating } : {}), ...(has("tags") ? { tags: field(form, "tags") .split(",") .map((t) => t.trim()) .filter(Boolean), } : {}), ...(listed === "yes" || listed === "no" ? { listed: listed === "yes" } : {}), ...(has("category") ? { category: Number(field(form, "category")) || null } : {}), ...(field(form, "reason") ? { reason: field(form, "reason") } : {}), }), ); done(id); revalidatePath("/products"); revalidatePath(`/products/${id}/edit`); return fields.length ? "Saved." : "Nothing changed."; }); } export async function publishProduct(_: ActionState, form: FormData): Promise { const id = field(form, "id"); const state = await attempt(async () => { unwrap(await api.products({ id }).publish.post()); done(id); revalidatePath("/products"); revalidatePath("/console/products"); }); if (state?.error) return state; redirect(`/products/${id}`); } /** Moderators lock fields: the checked ones are locked, the rest unlocked. */ export async function saveLocks(_: ActionState, form: FormData): Promise { const id = field(form, "id"); const fields = form .getAll("locked") .filter((v): v is "name" | "description" | "rating" | "tags" | "listed" | "category" => ["name", "description", "rating", "tags", "listed", "category"].includes(String(v)), ); return attempt(async () => { unwrap(await api.products({ id }).locks.put({ fields, reason: field(form, "reason") })); done(id); revalidatePath(`/products/${id}/edit`); return "Locks saved."; }); } /** Puts back what a history entry changed. */ export async function revertInfo(_: ActionState, form: FormData): Promise { const id = field(form, "id"); return attempt(async () => { const reason = field(form, "reason"); unwrap( await api .products({ id }) .history({ entry: Number(field(form, "entry")) }) .revert.post(reason ? { reason } : {}), ); done(id); revalidatePath(`/products/${id}/edit`); return "Undone."; }); } /** An upload token and the catalog's upload address, for the browser to send the file there itself. */ export async function startUpload( catalog: string, product?: string, ): Promise<{ token: string; url: string } | { error: string }> { try { return unwrap(await api.products.upload.post({ catalog, ...(product ? { product } : {}) })); } catch (e) { if (e instanceof ApiError) return { error: e.message }; throw e; } }