feat(einladungen): admin erzeugt einmal gueltige registrierungslinks
Nach dem ersten Nutzer bleibt die Registrierung geschlossen; weitere Konten entstehen nur ueber einen Einladungslink. Der Klartext-Token wird genau einmal angezeigt, in der Datenbank liegt sein SHA-256-Hash. Optional laesst sich eine Einladung an eine E-Mail-Adresse binden, die Rolle und die Gueltigkeit sind waehlbar. Die Freigabe zum Anlegen eines Kontos laeuft ueber AsyncLocalStorage: Der Server-Action, der den Token geprueft hat, umschliesst damit den signUp-Aufruf, und nur in diesem Kontext laesst der user-create-Hook von better-auth die Registrierung zu. Die Rolle kommt aus der Einladung und kann nicht ueber das Formular gesetzt werden. Der Smoke-Test deckt den ganzen Weg ab: Admin erstellt die Einladung, der zweite Elternteil registriert sich damit, bekommt die Rolle "Mitglied" (und wird von der Einladungsverwaltung weggeleitet), und der Link funktioniert kein zweites Mal. Refs Phase 2 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,17 +5,19 @@ import { usePathname } from 'next/navigation';
|
||||
import { cn } from 'cn';
|
||||
|
||||
const tabs = [
|
||||
{ href: '/verwaltung/hersteller', label: 'Hersteller' },
|
||||
{ href: '/verwaltung/themen', label: 'Themen' },
|
||||
{ href: '/verwaltung/standorte', label: 'Standorte' },
|
||||
{ href: '/verwaltung/hersteller', label: 'Hersteller', adminOnly: false },
|
||||
{ href: '/verwaltung/themen', label: 'Themen', adminOnly: false },
|
||||
{ href: '/verwaltung/standorte', label: 'Standorte', adminOnly: false },
|
||||
{ href: '/verwaltung/einladungen', label: 'Einladungen', adminOnly: true },
|
||||
] as const;
|
||||
|
||||
export function VerwaltungNav() {
|
||||
export function VerwaltungNav({ isAdmin }: { isAdmin: boolean }) {
|
||||
const pathname = usePathname();
|
||||
const visible = tabs.filter((tab) => !tab.adminOnly || isAdmin);
|
||||
|
||||
return (
|
||||
<nav className="flex gap-1 overflow-x-auto rounded-lg bg-muted p-1" aria-label="Stammdaten">
|
||||
{tabs.map((tab) => {
|
||||
{visible.map((tab) => {
|
||||
const active = pathname === tab.href;
|
||||
return (
|
||||
<Link
|
||||
|
||||
Reference in New Issue
Block a user