feat: wire auth and CRUD UI to APIs
This commit is contained in:
15
app/(app)/app/elders/page.tsx
Normal file
15
app/(app)/app/elders/page.tsx
Normal 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
26
app/(app)/app/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
26
app/(app)/app/settings/page.tsx
Normal file
26
app/(app)/app/settings/page.tsx
Normal 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
81
app/globals.css
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user