feat: complete shared platform UI contracts
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { Link } from "react-router";
|
||||
|
||||
import type { AuthInfo, WizardDirectoryEntry } from "../types";
|
||||
import { usePlatformLanguage } from "../i18n/LanguageContext";
|
||||
import { hasAnyScope, hasScope } from "../utils/permissions";
|
||||
import Card from "./Card";
|
||||
|
||||
export type WizardDirectoryProps = {
|
||||
entries: WizardDirectoryEntry[];
|
||||
auth: AuthInfo;
|
||||
emptyText?: string;
|
||||
};
|
||||
|
||||
function canOpenWizard(auth: AuthInfo, entry: WizardDirectoryEntry): boolean {
|
||||
if (entry.available === false) return false;
|
||||
if (entry.allOf?.some((scope) => !hasScope(auth, scope))) return false;
|
||||
if (entry.anyOf?.length && !hasAnyScope(auth, entry.anyOf)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export default function WizardDirectory({
|
||||
entries,
|
||||
auth,
|
||||
emptyText = "No guided workflows are available."
|
||||
}: WizardDirectoryProps) {
|
||||
const { translateText } = usePlatformLanguage();
|
||||
const visibleEntries = entries.filter(
|
||||
(entry) =>
|
||||
!entry.anyOf?.length
|
||||
|| hasAnyScope(auth, entry.anyOf)
|
||||
|| entry.available === false
|
||||
);
|
||||
|
||||
if (!visibleEntries.length) {
|
||||
return <p className="muted">{translateText(emptyText)}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wizard-directory-grid">
|
||||
{visibleEntries.map((entry) => {
|
||||
const available = canOpenWizard(auth, entry);
|
||||
return (
|
||||
<Card
|
||||
key={`${entry.moduleId}:${entry.id}`}
|
||||
title={entry.label}
|
||||
actions={
|
||||
available
|
||||
? (
|
||||
<Link className="btn btn-primary" to={entry.to}>
|
||||
{translateText(entry.actionLabel ?? "Open")}
|
||||
</Link>
|
||||
)
|
||||
: null
|
||||
}
|
||||
>
|
||||
{entry.description && <p>{translateText(entry.description)}</p>}
|
||||
{!available && entry.unavailableReason && (
|
||||
<p className="muted small-note">
|
||||
{translateText(entry.unavailableReason)}
|
||||
</p>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user