feat: wire auth and CRUD UI to APIs

This commit is contained in:
2026-07-01 06:29:18 -07:00
parent 914f74c5cc
commit 86a8f54725
10 changed files with 1119 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
import { redirect } from "next/navigation";
import { getCurrentAuthContext } from "@/modules/core/server/auth";
import { readData } from "@/modules/core/server/store";
import { EldersClient } from "@/modules/elders/components/EldersClient";
export default async function EldersPage(): Promise<React.ReactElement> {
const context = await getCurrentAuthContext();
if (!context) {
redirect("/login");
}
const data = await readData();
return <EldersClient initialElders={data.elders} permissions={context.permissions} />;
}

26
app/(app)/app/layout.tsx Normal file
View File

@@ -0,0 +1,26 @@
import { redirect } from "next/navigation";
import { getBootstrapState, getCurrentAuthContext } from "@/modules/core/server/auth";
import { AppShell } from "@/modules/shared/components/AppShell";
type AppLayoutProps = {
children: React.ReactNode;
};
export default async function AppLayout({ children }: AppLayoutProps): Promise<React.ReactElement> {
const bootstrap = await getBootstrapState();
if (bootstrap.setupRequired) {
redirect("/setup");
}
const context = await getCurrentAuthContext();
if (!context) {
redirect("/login");
}
return (
<AppShell account={context.account} permissions={context.permissions}>
{children}
</AppShell>
);
}

View File

@@ -0,0 +1,26 @@
import { redirect } from "next/navigation";
import { getCurrentAuthContext, toPublicAccount } from "@/modules/core/server/auth";
import { hasPermission, ROLE_DEFINITIONS } from "@/modules/core/server/permissions";
import { readData } from "@/modules/core/server/store";
import { SettingsOverview } from "@/modules/settings/components/SettingsOverview";
export default async function SettingsPage(): Promise<React.ReactElement> {
const context = await getCurrentAuthContext();
if (!context) {
redirect("/login");
}
if (!hasPermission(context.account.role, "account:read")) {
redirect("/app/dashboard");
}
const data = await readData();
return (
<SettingsOverview
accounts={data.accounts.map((account) => toPublicAccount(account))}
roles={ROLE_DEFINITIONS}
auditLogs={data.auditLogs.slice(0, 100)}
/>
);
}

81
app/globals.css Normal file
View File

@@ -0,0 +1,81 @@
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.5rem;
--background: oklch(0.986 0.008 151);
--foreground: oklch(0.215 0.025 157);
--card: oklch(1 0 0);
--card-foreground: oklch(0.215 0.025 157);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.215 0.025 157);
--primary: oklch(0.48 0.11 155);
--primary-foreground: oklch(0.985 0.01 150);
--secondary: oklch(0.945 0.026 152);
--secondary-foreground: oklch(0.28 0.058 156);
--muted: oklch(0.95 0.012 151);
--muted-foreground: oklch(0.47 0.025 157);
--accent: oklch(0.88 0.06 150);
--accent-foreground: oklch(0.25 0.055 155);
--destructive: oklch(0.58 0.19 25);
--destructive-foreground: oklch(0.985 0.01 150);
--border: oklch(0.89 0.016 151);
--input: oklch(0.89 0.016 151);
--ring: oklch(0.54 0.11 155);
}
* {
border-color: var(--border);
}
html {
background: var(--background);
color: var(--foreground);
}
body {
min-height: 100svh;
background:
radial-gradient(circle at 12% 8%, color-mix(in oklch, var(--accent) 36%, transparent), transparent 30%),
linear-gradient(180deg, color-mix(in oklch, var(--background) 90%, white), var(--background));
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
letter-spacing: 0;
}
button,
a,
[role="button"] {
-webkit-tap-highlight-color: transparent;
}
::selection {
background: color-mix(in oklch, var(--primary) 22%, transparent);
}