initial commit after split

This commit is contained in:
2026-06-24 01:43:10 +02:00
parent b1d6c0150f
commit 30c11a6dcf
173 changed files with 25380 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
import { useEffect, useState, type ReactNode } from "react";
import { Navigate } from "react-router-dom";
import type { AuthInfo } from "../types";
import { isApiError } from "../api/client";
import DismissibleAlert from "./DismissibleAlert";
import { hasAnyScope } from "../utils/permissions";
type PermissionBoundaryProps = {
auth: AuthInfo;
anyOf: string[];
fallback: string;
children: ReactNode;
};
export function PermissionBoundary({ auth, anyOf, fallback, children }: PermissionBoundaryProps) {
return hasAnyScope(auth, anyOf) ? <>{children}</> : <Navigate to={fallback} replace />;
}
type ResourceAccessBoundaryProps = {
probe: () => Promise<unknown>;
resetKey: string;
fallback: string;
children: ReactNode;
};
/**
* Keeps resource routes tenant-agnostic. After a tenant switch the current URL
* is probed in the new tenant context; inaccessible or missing resources fall
* back to their parent collection instead of requiring switch-specific routing.
*/
export function ResourceAccessBoundary({ probe, resetKey, fallback, children }: ResourceAccessBoundaryProps) {
const [state, setState] = useState<"checking" | "allowed" | "denied" | "error">("checking");
const [message, setMessage] = useState("");
useEffect(() => {
let cancelled = false;
setState("checking");
setMessage("");
probe()
.then(() => { if (!cancelled) setState("allowed"); })
.catch((error) => {
if (cancelled) return;
if (isApiError(error, 403, 404)) {
setState("denied");
return;
}
setMessage(error instanceof Error ? error.message : String(error));
setState("error");
});
return () => { cancelled = true; };
}, [probe, resetKey]);
if (state === "denied") return <Navigate to={fallback} replace />;
if (state === "error") return <div className="content-pad"><DismissibleAlert tone="danger" dismissible={false}>{message || "The resource could not be loaded."}</DismissibleAlert></div>;
if (state === "checking") return <div className="content-pad"><p className="muted">Checking access</p></div>;
return <>{children}</>;
}