diff --git a/app/layout.tsx b/app/layout.tsx
index 9852c15..7ee2ba3 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Rikx Beheer",
+ description: "Rikx platform beheer",
};
export default function RootLayout({ children }: LayoutProps<"/">) {
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000..d37fb66
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,74 @@
+"use server";
+
+import { redirect } from "next/navigation";
+import { getUserByEmail, verifyPassword, createSession } from "../../lib/auth";
+import { getSession } from "../../lib/auth";
+
+export default async function LoginPage({
+ searchParams,
+}: {
+ searchParams: Promise<{ error?: string }>;
+}) {
+ const session = await getSession();
+ if (session) redirect("/tenants");
+
+ const { error } = await searchParams;
+
+ return (
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
index c887311..fe337f2 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,69 +1,7 @@
-import Image from "next/image";
+import { redirect } from "next/navigation";
+import { getSession } from "../lib/auth";
-export default function Home() {
- return (
-
-
-
-
-
- To get started, edit the{" "}
-
- page.tsx
- {" "}
- file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
-
-
- );
+export default async function Home() {
+ const session = await getSession();
+ redirect(session ? "/tenants" : "/login");
}
diff --git a/app/tenants/actions.ts b/app/tenants/actions.ts
new file mode 100644
index 0000000..2196dd9
--- /dev/null
+++ b/app/tenants/actions.ts
@@ -0,0 +1,59 @@
+"use server";
+
+import { redirect } from "next/navigation";
+import { randomBytes, scryptSync } from "crypto";
+import { getPlatformDb } from "../../lib/platform-db";
+import { getSession } 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 getSession();
+ if (!session) redirect("/login");
+
+ 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 getSession();
+ if (!session) redirect("/login");
+
+ 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");
+}
diff --git a/app/tenants/page.tsx b/app/tenants/page.tsx
new file mode 100644
index 0000000..07962d5
--- /dev/null
+++ b/app/tenants/page.tsx
@@ -0,0 +1,192 @@
+import { redirect } from "next/navigation";
+import { getSession, destroySession } from "../../lib/auth";
+import { listAccounts } from "../../lib/platform-db";
+import { createTenant, deleteTenant } from "./actions";
+
+export default async function TenantsPage({
+ searchParams,
+}: {
+ searchParams: Promise<{ error?: string }>;
+}) {
+ const session = await getSession();
+ if (!session) redirect("/login");
+
+ const { error } = await searchParams;
+ const accounts = listAccounts();
+
+ const errorMsg =
+ error === "slug"
+ ? "Slug is al in gebruik."
+ : error === "missing"
+ ? "Vul alle velden in."
+ : null;
+
+ return (
+
+
+
+
+ {/* Overzicht */}
+
+
+ Tenants ({accounts.length})
+
+ {accounts.length === 0 ? (
+ Nog geen tenants aangemaakt.
+ ) : (
+
+
+
+
+ | Naam |
+ Slug |
+ URL |
+ Gebruikers |
+ Aangemaakt |
+ |
+
+
+
+ {accounts.map((a) => (
+
+ | {a.name} |
+ {a.slug} |
+
+
+ {a.slug}.rikxplatform.nl
+
+ |
+ {a.user_count} |
+
+ {new Date(a.created_at).toLocaleDateString("nl-NL")}
+ |
+
+
+ |
+
+ ))}
+
+
+
+ )}
+
+
+ {/* Nieuw tenant formulier */}
+
+
+ Nieuwe tenant aanmaken
+
+ {errorMsg && (
+ {errorMsg}
+ )}
+
+
+
+
+ );
+}
diff --git a/lib/platform-db.ts b/lib/platform-db.ts
new file mode 100644
index 0000000..7336fde
--- /dev/null
+++ b/lib/platform-db.ts
@@ -0,0 +1,42 @@
+import { DatabaseSync } from "node:sqlite";
+
+const DB_PATH = process.env.RIKX_PLATFORM_DB_PATH || "/rikx-platform-data/app.db";
+
+let _db: DatabaseSync | null = null;
+
+export function getPlatformDb(): DatabaseSync {
+ if (!_db) {
+ _db = new DatabaseSync(DB_PATH);
+ _db.exec("PRAGMA journal_mode = WAL");
+ _db.exec("PRAGMA foreign_keys = ON");
+ }
+ return _db;
+}
+
+export type PlatformAccount = {
+ id: number;
+ name: string;
+ slug: string;
+ created_at: string;
+};
+
+export type PlatformUser = {
+ id: number;
+ account_id: number;
+ email: string;
+ name: string;
+ role: string;
+ created_at: string;
+};
+
+export function listAccounts(): (PlatformAccount & { user_count: number })[] {
+ return getPlatformDb()
+ .prepare(
+ `SELECT a.*, COUNT(u.id) as user_count
+ FROM accounts a
+ LEFT JOIN users u ON u.account_id = a.id
+ GROUP BY a.id
+ ORDER BY a.created_at DESC`
+ )
+ .all() as (PlatformAccount & { user_count: number })[];
+}