fix(devices): confirm before revoking a display
Revoke acted on a single click, yet it strands the screen until someone re-pairs it in person. Explain the consequence in the shared in-product Dialog first (DESIGN 21.4), with an obvious Cancel. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4bcdb578e6
commit
85b1377fd3
@@ -1,5 +1,5 @@
|
||||
import { designToken } from "@ekklesia/design-tokens";
|
||||
import { Button, Field, TextInput } from "@ekklesia/ui";
|
||||
import { Button, Dialog, Field, TextInput } from "@ekklesia/ui";
|
||||
import { type FormEvent, type ReactElement, useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
approveDevicePairing,
|
||||
@@ -61,6 +61,10 @@ export function DevicePanel(props: DevicePanelProps): ReactElement | undefined {
|
||||
const [code, setCode] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [pairing, setPairing] = useState<PairingStep>({ step: "idle" });
|
||||
/** The device an administrator asked to revoke and has not yet confirmed. */
|
||||
const [confirming, setConfirming] = useState<HostDeviceSummary | undefined>(undefined);
|
||||
const [revoking, setRevoking] = useState(false);
|
||||
const dismissConfirmation = useCallback(() => setConfirming(undefined), []);
|
||||
|
||||
const refresh = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
@@ -106,13 +110,15 @@ export function DevicePanel(props: DevicePanelProps): ReactElement | undefined {
|
||||
};
|
||||
|
||||
const revoke = async (deviceId: string): Promise<void> => {
|
||||
setRevoking(true);
|
||||
try {
|
||||
await revokeHostDevice(config, deviceId);
|
||||
await refresh();
|
||||
} catch {
|
||||
// Left to the next refresh rather than asserting a failure the operator cannot act on.
|
||||
}
|
||||
await refresh();
|
||||
setRevoking(false);
|
||||
setConfirming(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -195,7 +201,7 @@ export function DevicePanel(props: DevicePanelProps): ReactElement | undefined {
|
||||
{device.revoked ? undefined : (
|
||||
<Button
|
||||
data-testid={`device-revoke-${device.id}`}
|
||||
onClick={() => void revoke(device.id)}
|
||||
onClick={() => setConfirming(device)}
|
||||
size="sm"
|
||||
>
|
||||
Revoke
|
||||
@@ -203,6 +209,38 @@ export function DevicePanel(props: DevicePanelProps): ReactElement | undefined {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Revoking strands the screen until someone re-pairs it in person, so the consequence is
|
||||
spelled out before it happens — never a browser-native confirm. */}
|
||||
<Dialog
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
data-testid="device-revoke-cancel"
|
||||
disabled={revoking}
|
||||
onClick={dismissConfirmation}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="device-revoke-confirm"
|
||||
disabled={revoking}
|
||||
onClick={() => (confirming ? void revoke(confirming.id) : undefined)}
|
||||
variant="danger"
|
||||
>
|
||||
Revoke display
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
onDismiss={dismissConfirmation}
|
||||
open={confirming !== undefined}
|
||||
title={`Revoke ${confirming?.name ?? "this display"}?`}
|
||||
>
|
||||
<p style={{ margin: 0 }}>
|
||||
The screen stops showing content within a few seconds and returns to its pairing code.
|
||||
This cannot be undone: to use it again, pair it from the screen with a new code.
|
||||
</p>
|
||||
</Dialog>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user