feat(app): Next.js 16 + Auth.js v5 portal skeleton
Next.js 16 + Auth.js v5 skeleton: host→slug middleware, tenant-context layout, OIDC sign-in flow against breakpilot-dev realm. 100% coverage on src/lib. Bumps next to 16.2.6 to clear trivy CVEs in 15.0.3.
This commit was merged in pull request #4.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { auth, signIn, signOut } from "@/auth";
|
||||
|
||||
export default async function Dashboard({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const session = await auth();
|
||||
|
||||
if (!session) {
|
||||
async function login() {
|
||||
"use server";
|
||||
await signIn("keycloak", { redirectTo: `/${slug}/dashboard` });
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1>Sign in to {slug}</h1>
|
||||
<form action={login}>
|
||||
<button type="submit">Sign in with Keycloak</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
"use server";
|
||||
await signOut({ redirectTo: `/${slug}/dashboard` });
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>
|
||||
Welcome, {session.user?.name ?? session.user?.email ?? "user"}. This is the{" "}
|
||||
<code>{slug}</code> dashboard. Real product tiles, settings, billing — land
|
||||
in M5.2 / M10.1.
|
||||
</p>
|
||||
<form action={logout} style={{ marginTop: 24 }}>
|
||||
<button type="submit">Sign out</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import type { ReactNode } from "react";
|
||||
import { fetchTenantBySlug } from "@/lib/tenant-registry";
|
||||
|
||||
export default async function TenantLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const tenant = await fetchTenantBySlug(slug);
|
||||
if (!tenant) notFound();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header
|
||||
style={{
|
||||
padding: "12px 24px",
|
||||
borderBottom: "1px solid #eaeaea",
|
||||
background: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<strong>{tenant.name}</strong>
|
||||
<span style={{ fontSize: 12, color: "#666" }}>
|
||||
{tenant.plan} · {tenant.status}
|
||||
</span>
|
||||
</header>
|
||||
<main style={{ padding: 24 }}>{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function TenantRoot({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
redirect(`/${slug}/dashboard`);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { handlers } from "@/auth";
|
||||
|
||||
export const { GET, POST } = handlers;
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Metadata } from "next";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Breakpilot",
|
||||
description: "Breakpilot Platform — customer portal",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
style={{
|
||||
margin: 0,
|
||||
fontFamily:
|
||||
'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
|
||||
background: "#fafafa",
|
||||
color: "#111",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Apex landing page. In dev this is what loads at http://localhost:3000.
|
||||
// Tenant portals live at http://<slug>.localhost:3000 (middleware rewrites).
|
||||
|
||||
export default function Apex() {
|
||||
return (
|
||||
<main style={{ maxWidth: 720, margin: "10vh auto", padding: "0 24px" }}>
|
||||
<h1 style={{ fontSize: 32, marginBottom: 16 }}>Breakpilot</h1>
|
||||
<p>
|
||||
Customer portals live at <code><tenant>.localhost:3000</code> in dev,{" "}
|
||||
<code><tenant>.breakpilot.com</code> in prod. Backstage lives at{" "}
|
||||
<code>backstage.<apex></code>.
|
||||
</p>
|
||||
<p style={{ marginTop: 24 }}>
|
||||
Try: <a href="http://acme.localhost:3000">http://acme.localhost:3000</a>
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user