Role split and owner gating
All checks were successful
Build and Push / build (push) Successful in 1m24s
All checks were successful
Build and Push / build (push) Successful in 1m24s
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { getSessionUser } from "@/lib/session";
|
||||
import { getSessionUser, canMutate } from "@/lib/session";
|
||||
import { getTranslations, getFormatter } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { listTenants } from "@/lib/k8s";
|
||||
@@ -149,8 +149,39 @@ export default async function DashboardPage() {
|
||||
(r) => !r.tenantName || !orgTenants.some((t) => t.metadata.name === r.tenantName)
|
||||
);
|
||||
|
||||
// Slice 5: only owners (and platform users, who'd typically be using
|
||||
// the admin panel anyway) see the "Create new instance" link. A
|
||||
// `user`-role member sees the dashboard but not the create flow —
|
||||
// they need to ask an owner.
|
||||
const canCreate = canMutate(user);
|
||||
|
||||
// First-time user: empty company. Show the onboarding wizard inline.
|
||||
// Note: the registering user is always granted `owner` on their new
|
||||
// org by registerCustomer, so this branch is only reachable by an
|
||||
// owner — no role check needed here. But a customer-side `user`
|
||||
// promoted into a fresh empty org (Slice 7 invites) would also land
|
||||
// here without permission to submit. Belt-and-braces gate.
|
||||
if (orgTenants.length === 0 && inflightRequests.length === 0) {
|
||||
if (!canCreate) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-8 animate-in">
|
||||
<h1 className="font-display text-2xl font-semibold accent-rule mb-2">
|
||||
{t("title")}
|
||||
</h1>
|
||||
<p className="text-text-secondary text-sm mt-4">
|
||||
{t("welcome", { name: user.name || user.email })}
|
||||
</p>
|
||||
</div>
|
||||
<Card className="animate-in animate-in-delay-1">
|
||||
<p className="text-sm text-text-secondary text-center py-6">
|
||||
{t("noAccessNoInstances")}
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-8 animate-in">
|
||||
@@ -170,7 +201,7 @@ export default async function DashboardPage() {
|
||||
}
|
||||
|
||||
// Returning customer: list of tenants + in-flight requests, plus
|
||||
// a button to add another instance.
|
||||
// a button to add another instance (owners only).
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-8 animate-in flex items-start justify-between gap-4">
|
||||
@@ -183,12 +214,14 @@ export default async function DashboardPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/dashboard/new"
|
||||
className="shrink-0 inline-flex items-center gap-1.5 py-2 px-4 bg-accent text-white text-xs font-medium rounded-lg hover:bg-accent-dim transition-colors"
|
||||
>
|
||||
<span>+</span> {t("createInstance")}
|
||||
</Link>
|
||||
{canCreate && (
|
||||
<Link
|
||||
href="/dashboard/new"
|
||||
className="shrink-0 inline-flex items-center gap-1.5 py-2 px-4 bg-accent text-white text-xs font-medium rounded-lg hover:bg-accent-dim transition-colors"
|
||||
>
|
||||
<span>+</span> {t("createInstance")}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* In-flight (pending/approved/provisioning/rejected) requests */}
|
||||
|
||||
Reference in New Issue
Block a user