Files
emdash-patch-imageupload/packages/admin/src/components/CapabilityConsentDialog.tsx
kunthawat 2d1be52177 Emdash source with visual editor image upload fix
Fixes:
1. media.ts: wrap placeholder generation in try-catch
2. toolbar.ts: check r.ok, display error message in popover
2026-05-03 10:44:54 +07:00

157 lines
4.4 KiB
TypeScript

/**
* Capability Consent Dialog
*
* Shown before installing or updating a marketplace plugin.
* Lists each requested capability with a human-readable explanation.
* User must explicitly confirm before the action proceeds.
*/
import { Button } from "@cloudflare/kumo";
import { useLingui } from "@lingui/react/macro";
import { ShieldCheck, ShieldWarning, Warning } from "@phosphor-icons/react";
import * as React from "react";
import { describeCapability } from "../lib/api/marketplace.js";
import { cn } from "../lib/utils.js";
import { DialogError } from "./DialogError.js";
export interface CapabilityConsentDialogProps {
/** Dialog mode */
mode?: "install" | "update";
/** Plugin display name */
pluginName: string;
/** Capabilities the plugin requests */
capabilities: string[];
/** Allowed network hosts (for network:fetch capability) */
allowedHosts?: string[];
/** New capabilities added in an update (highlighted differently) */
newCapabilities?: string[];
/** Audit verdict badge */
auditVerdict?: "pass" | "warn" | "fail";
/** Whether the action is in progress */
isPending?: boolean;
/** Error message to display inline */
error?: string | null;
/** Called when user confirms */
onConfirm: () => void;
/** Called when user cancels */
onCancel: () => void;
}
export function CapabilityConsentDialog({
mode,
pluginName,
capabilities,
allowedHosts,
newCapabilities = [],
auditVerdict,
isPending = false,
error,
onConfirm,
onCancel,
}: CapabilityConsentDialogProps) {
const { t } = useLingui();
const newSet = new Set(newCapabilities);
const isUpdate = mode === "update" || newCapabilities.length > 0;
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center"
role="dialog"
aria-modal="true"
aria-label={t`Capability consent`}
>
{/* Backdrop */}
<div className="absolute inset-0 bg-black/50" onClick={() => !isPending && onCancel()} />
{/* Dialog */}
<div className="relative w-full max-w-md rounded-lg border bg-kumo-base shadow-lg">
{/* Header */}
<div className="border-b px-6 py-4">
<h2 className="text-lg font-semibold">
{isUpdate ? t`Review New Permissions` : t`Plugin Permissions`}
</h2>
<p className="mt-1 text-sm text-kumo-subtle">
{isUpdate
? t`${pluginName} is requesting additional permissions:`
: t`${pluginName} requires the following permissions:`}
</p>
</div>
{/* Capabilities list */}
<div className="px-6 py-4 space-y-3">
{capabilities.map((cap) => {
const isNew = newSet.has(cap);
return (
<div
key={cap}
className={cn(
"flex items-start gap-3 rounded-md p-2 text-sm",
isNew ? "bg-warning/10 border border-warning/30" : "bg-kumo-tint/50",
)}
>
<ShieldCheck
className={cn(
"mt-0.5 h-4 w-4 shrink-0",
isNew ? "text-warning" : "text-kumo-subtle",
)}
/>
<div>
<span className={cn(isNew && "font-medium")}>
{describeCapability(cap, allowedHosts)}
</span>
{isNew && <span className="ms-2 text-xs text-warning font-medium">{t`NEW`}</span>}
</div>
</div>
);
})}
{/* Audit verdict banner */}
{auditVerdict && auditVerdict !== "pass" && (
<div
className={cn(
"flex items-center gap-2 rounded-md p-3 text-sm mt-2",
auditVerdict === "warn"
? "bg-warning/10 text-warning"
: "bg-kumo-danger/10 text-kumo-danger",
)}
>
{auditVerdict === "warn" ? (
<Warning className="h-4 w-4 shrink-0" />
) : (
<ShieldWarning className="h-4 w-4 shrink-0" />
)}
<span>
{auditVerdict === "warn"
? t`Security audit flagged potential concerns with this plugin.`
: t`Security audit flagged this plugin as potentially unsafe.`}
</span>
</div>
)}
</div>
{/* Error */}
<DialogError message={error} className="mx-6" />
{/* Actions */}
<div className="flex justify-end gap-3 border-t px-6 py-4">
<Button variant="ghost" onClick={onCancel} disabled={isPending}>
{t`Cancel`}
</Button>
<Button onClick={onConfirm} disabled={isPending}>
{isPending
? isUpdate
? t`Updating...`
: t`Installing...`
: isUpdate
? t`Accept & Update`
: t`Accept & Install`}
</Button>
</div>
</div>
</div>
);
}
export default CapabilityConsentDialog;