Files
remco-vermeer 53bf667f54 Voeg Module 4 experts, waardebepalingspagina en projectverbeteringen toe
Experts (Module 4)
- Pagina Experts (CRM): experts uitnodigen per e-mail (zonder SMTP wordt de
  link getoond), rondes koppelen, opnieuw uitnodigen, verwijderen en
  "Inloggen als" (alleen lezen) om mee te kijken
- Uitnodigingspagina waar een expert zijn wachtwoord instelt (token 7 dagen
  geldig, eenmalig, alleen als hash opgeslagen)
- Expertpagina: projecten in waardebepaling uit de eigen rondes, project plan
  lezen en eigen beoordeling geven (3 scores, advies Bonus/Malus, toelichting)
- Rollen admin/expert: alle beheerpagina's en -acties controleren de rol
- Rikx calculatie gebruikt de beoordelingen van de experts in plaats van de
  handmatige expertkolommen; experts koppelen ook vanaf de rondepagina

Waardebepaling projecten
- Nieuwe pagina met projecten in waardebepaling op kosten/impact (laag naar
  hoog), filter op ronde, Rikx Waarde-veld dat live doorrekent en opslaat in
  de ronde, en een 75%-lijn bij een gekozen ronde

Projecten
- Rikx calculatie als tab, zichtbaar vanaf status Waardebepaling, met keuze
  rekenmethode (Arbeid; Jeugd nog niet beschikbaar) en beoordeling
  goedgekeurd/afgekeurd met reden
- Impactmaker gekoppeld aan een sociaal ondernemer
- Opslaan-knop bovenaan; ongeldige velden op een verborgen tab worden getoond
- Project toevoegen maakt direct een project aan; aparte pagina verwijderd
- Waarderingssystematiek Rikx juni 2023 toegevoegd aan _doc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:42:22 +02:00

79 lines
2.8 KiB
TypeScript

"use server";
import { redirect } from "next/navigation";
import { randomBytes, scryptSync } from "crypto";
import { getPlatformDb } from "../../lib/platform-db";
import { requireAdmin } from "../../lib/auth";
function requireAuth() {
// Called inside server actions — session check is synchronous-safe via cache
}
function hashPassword(password: string): string {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(password, salt, 64).toString("hex");
return `${salt}:${hash}`;
}
export async function createTenant(formData: FormData) {
const session = await requireAdmin();
const name = (formData.get("name") as string).trim();
const slug = (formData.get("slug") as string).trim().toLowerCase().replace(/[^a-z0-9-]/g, "-");
const adminEmail = (formData.get("admin_email") as string).trim();
const adminPassword = formData.get("admin_password") as string;
const adminName = (formData.get("admin_name") as string).trim();
if (!name || !slug || !adminEmail || !adminPassword || !adminName) {
redirect("/tenants?error=missing");
}
const db = getPlatformDb();
const existing = db.prepare("SELECT id FROM accounts WHERE slug = ?").get(slug);
if (existing) redirect("/tenants?error=slug");
db.prepare("INSERT INTO accounts (name, slug) VALUES (?, ?)").run(name, slug);
const account = db.prepare("SELECT id FROM accounts WHERE slug = ?").get(slug) as { id: number };
db.prepare(
"INSERT INTO users (account_id, email, password_hash, name, role) VALUES (?, ?, ?, ?, 'admin')"
).run(account.id, adminEmail, hashPassword(adminPassword), adminName);
redirect("/tenants");
}
export async function deleteTenant(formData: FormData) {
const session = await requireAdmin();
const accountId = Number(formData.get("account_id"));
if (!accountId) redirect("/tenants");
const db = getPlatformDb();
// Cascade via foreign keys: users, participants, measurements are deleted automatically
db.prepare("DELETE FROM accounts WHERE id = ?").run(accountId);
redirect("/tenants");
}
export async function updateTenant(formData: FormData) {
const session = await requireAdmin();
const accountId = Number(formData.get("account_id"));
const name = ((formData.get("name") as string) ?? "").trim();
const slug = ((formData.get("slug") as string) ?? "").trim().toLowerCase().replace(/[^a-z0-9-]/g, "-");
if (!accountId) redirect("/tenants");
if (!name || !slug) redirect(`/tenants/${accountId}?error=missing`);
const db = getPlatformDb();
const existing = db
.prepare("SELECT id FROM accounts WHERE slug = ? AND id != ?")
.get(slug, accountId);
if (existing) redirect(`/tenants/${accountId}?error=slug`);
db.prepare("UPDATE accounts SET name = ?, slug = ? WHERE id = ?").run(name, slug, accountId);
redirect("/tenants");
}