"use server"; import { redirect } from "next/navigation"; import { revalidatePath } from "next/cache"; import { z } from "zod"; import { eq } from "drizzle-orm"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { db } from "@/db"; import { clients, projects, payments } from "@/db/schema"; async function requireAdmin() { const session = await getServerSession(authOptions); if (!session) throw new Error("Non autorizzato"); } function randomAlpha(len: number): string { const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; return Array.from({ length: len }, () => chars[Math.floor(Math.random() * chars.length)]).join(""); } function toSlug(name: string): string { const base = name .toLowerCase() .normalize("NFD") .replace(/[̀-ͯ]/g, "") .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 44); return `${base}-${randomAlpha(4)}`; } async function uniqueSlug(base: string): Promise { if (base.length < 3) return null; const candidates = [base, ...Array.from({ length: 8 }, (_, i) => `${base.slice(0, 47)}-${i + 2}`)]; for (const candidate of candidates) { const rows = await db.select({ id: clients.id }).from(clients).where(eq(clients.slug, candidate)).limit(1); if (rows.length === 0) return candidate; } return null; } const createClientSchema = z.object({ name: z.string().min(1, "Nome richiesto"), brand_name: z.string().min(1, "Nome brand richiesto"), brief: z.string().min(1, "Brief richiesto"), }); export async function createClient(formData: FormData) { await requireAdmin(); const raw = { name: formData.get("name") as string, brand_name: formData.get("brand_name") as string, brief: formData.get("brief") as string, }; const parsed = createClientSchema.safeParse(raw); if (!parsed.success) { throw new Error( parsed.error.issues.map((i) => i.message).join(", ") ); } // Auto-generate slug from name (e.g. "Mario Rossi" → "mario-rossi") const slug = await uniqueSlug(toSlug(parsed.data.name)); // Insert client — token and id are auto-generated by $defaultFn(() => nanoid()) const [newClient] = await db .insert(clients) .values({ name: parsed.data.name, brand_name: parsed.data.brand_name, brief: parsed.data.brief, slug, }) .returning({ id: clients.id, token: clients.token, brand_name: clients.brand_name }); // Create a default project for the client — all work items are project-scoped const [newProject] = await db .insert(projects) .values({ client_id: newClient.id, name: newClient.brand_name, }) .returning({ id: projects.id }); // Always create two payment stubs per project — Acconto 50% and Saldo 50% await db.insert(payments).values([ { project_id: newProject.id, label: "Acconto 50%", amount: "0", status: "da_saldare", }, { project_id: newProject.id, label: "Saldo 50%", amount: "0", status: "da_saldare", }, ]); revalidatePath("/admin"); redirect(`/admin/clients/${newClient.id}`); }