Skip to content

Commit 0ca1f8b

Browse files
committed
fix(FR-2819): use BAIConfirmModalWithInput for irreversible delete actions (#7370)
Resolves #7253(FR-2819) ## Summary Sweep all permanent-delete flows that still used `modal.confirm` / `Popconfirm` / ad-hoc dialogs and move them onto the project's typed-confirmation pattern (`.claude/rules/destructive-confirmation.md`). In the process, **consolidate two overlapping components** — `BAIConfirmModalWithInput` and `BAIDeleteConfirmModal` — into a single API so every irreversible delete in the app looks and behaves the same. ## What changed ### Component consolidation - **Remove `BAIConfirmModalWithInput`** (component, stories, exports). All 16 call sites are migrated to `BAIDeleteConfirmModal`. - **Extend `BAIDeleteConfirmModal`**: - Add `requireConfirmInput` to force typed confirmation for single-item deletes. - Add `target` prop — when set (and no `description` is provided) the modal renders the localized "Are you sure you want to permanently delete {target}?" copy. Centralizes the most common irreversible-delete sentence so call sites don't redeclare it. - Reinstall-confirm flows (`ManageImageResourceLimitModal`, `ManageAppsModal`) — which are confirmations but **not deletes** — are moved to plain `BAIModal` instead of a delete-shaped modal. They were only on `BAIConfirmModalWithInput` for the typed-input UX, which they don't actually need. ### Bug fixes (the issue's scope) - `UserCredentialList.tsx` — permanent keypair deletion was a single-click `modal.confirm`. Now opens `BAIDeleteConfirmModal` with `requireConfirmInput` and the keypair's `access_key` as the confirm string. - `AutoScalingRuleListLegacy.tsx` — permanent rule deletion was a `Popconfirm`. Now opens `BAIDeleteConfirmModal` requiring the rule's identifier to be typed. Card is migrated to `BAICard` in the same change per project convention. ### Sweep — other irreversible flows migrated Single-click confirms / ad-hoc patterns replaced with `BAIDeleteConfirmModal` (+ `requireConfirmInput` where the item is sensitive): - `AutoScalingRuleList.tsx` (V2) - `ContainerRegistryList.tsx` - `CustomizedImageList.tsx` - `DeleteForeverVFolderModalV2.tsx` - `DeploymentAccessTokensTab.tsx`, `DeploymentConfigurationSection.tsx`, `DeploymentList.tsx`, `EndpointList.tsx` - `KeypairResourcePolicyList.tsx`, `ProjectResourcePolicyList.tsx`, `UserResourcePolicyList.tsx` - `MyKeypairManagementModal.tsx` - `PrometheusPresetTab.tsx` - `PurgeUsersModal.tsx` - `ResourceGroupList.tsx`, `ResourcePresetList.tsx` - `RoleAssignmentTab.tsx`, `RolePermissionTab.tsx`, `RBACManagementPage.tsx` - `ShellScriptEditModal.tsx` - `UserCredentialList.tsx` - `VFolderNodes.tsx` - `AIAgentPage.tsx` - `FileExplorer/DeleteSelectedItemsModal.tsx` - `fragments/BAIProjectTable.tsx` ### `confirmText` policy Standardized so each call site picks the **safer** of two options: - **User-readable IDs** (project name, vfolder name, keypair `access_key`, role name, …) → use the resource's name as the confirm string. The user has it on screen and can copy-type it unambiguously. - **Cryptic / long IDs** (image full path, autoscaling metric name, bulk purge across N users) → use the literal `"Permanently Delete"` string. Asking the user to type a 90-char Docker reference is worse UX, not better. Activate / deactivate (reversible) flows are intentionally **left as-is** — those belong to FR-2825. ### i18n - New keys across all 22 locales: - `general.*` resource-type labels (9 new entries) for the `target` prop. - `dialog.title.DeleteSomething` - `comp:BAIDeleteConfirmModal.AreYouSureToPermanentlyDeleteTarget` - Filled in previously missing keys across non-English locales (translations were already drifting; this PR brings them back in sync). - Fix one Korean typo: `environment.ModifyImageResourceLimitReinstallRequired`. ### Storybook - Remove `BAIConfirmModalWithInput.stories.tsx`. - Add a Storybook case for the new `target` prop on `BAIDeleteConfirmModal`. ## Why one API, not two Before: `BAIConfirmModalWithInput` (typed-input shape) and `BAIDeleteConfirmModal` (item-list shape) covered overlapping use cases — the typed-input modal was being used for deletes, and call sites were duplicating copy and layout that already existed in `BAIDeleteConfirmModal`. After: a single component covers both the single-item and N-item delete cases, with `requireConfirmInput` and `target` handling the variants. Less surface area, less drift, fewer translation keys to keep aligned. ## Checklist - [x] Documentation — N/A (no user-facing manual change; convention rule unchanged) - [x] Minimum required manager version — N/A - [x] Specific setting for review — none - [x] Minimum requirements to check during review — `bash scripts/verify.sh` passes; manually exercise a few migrated flows (keypair delete, autoscaling rule delete, vfolder permanent delete, bulk user purge) to confirm the OK button stays disabled until the right string is typed - [x] Test case(s) — see Storybook stories and the migrated call sites; behavior across all 16 sites is now driven by one component
1 parent 674da9d commit 0ca1f8b

77 files changed

Lines changed: 1036 additions & 1296 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/backend.ai-ui/src/components/BAIConfirmModalWithInput.stories.tsx

Lines changed: 0 additions & 169 deletions
This file was deleted.

packages/backend.ai-ui/src/components/BAIConfirmModalWithInput.tsx

Lines changed: 0 additions & 109 deletions
This file was deleted.

packages/backend.ai-ui/src/components/BAIDeleteConfirmModal.stories.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const meta: Meta<typeof BAIDeleteConfirmModal> = {
2727
## Key Features
2828
- Accepts \`React.ReactNode\` for item labels (icons, tags, custom rendering)
2929
- Scrollable item list for multi-item selections
30+
- \`target\` prop produces a resource-type-aware default description ("Are you sure you want to permanently delete {target}?")
3031
- \`extraContent\` slot for domain-specific additions (checkboxes, warnings)
3132
- Built on \`BAIModal\`
3233
`,
@@ -93,6 +94,39 @@ export const SingleItemWithInput: Story = {
9394
},
9495
};
9596

97+
export const WithTarget: Story = {
98+
parameters: {
99+
docs: {
100+
description: {
101+
story:
102+
'Resource-typed deletion using the `target` prop. The default description becomes "Are you sure you want to permanently delete {target}?", surfacing the resource type (e.g. "Resource Preset", "Resource Policy") in the dialog copy. Typically paired with `requireConfirmInput` for irreversible deletes.',
103+
},
104+
},
105+
},
106+
render: () => {
107+
const [open, setOpen] = useState(false);
108+
const itemName = 'gpu-large-preset';
109+
return (
110+
<>
111+
<BAIButton danger icon={<DeleteFilled />} onClick={() => setOpen(true)}>
112+
Delete Resource Preset
113+
</BAIButton>
114+
<BAIDeleteConfirmModal
115+
open={open}
116+
title="Delete Resource Preset"
117+
target="Resource Preset"
118+
items={[{ key: itemName, label: itemName }]}
119+
confirmText={itemName}
120+
requireConfirmInput
121+
inputProps={{ placeholder: itemName }}
122+
onOk={() => setOpen(false)}
123+
onCancel={() => setOpen(false)}
124+
/>
125+
</>
126+
);
127+
},
128+
};
129+
96130
export const MultipleItems: Story = {
97131
parameters: {
98132
docs: {

packages/backend.ai-ui/src/components/BAIDeleteConfirmModal.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ export interface BAIDeleteConfirmModalProps extends Omit<
2323
items: BAIDeleteConfirmModalItem[];
2424
/** Custom modal title. Defaults to "Delete" / "Delete N items". */
2525
title?: React.ReactNode;
26-
/** Description shown above the item list. Defaults to "Are you sure you want to delete?" */
26+
/** Description shown above the item list. If omitted, falls back to a `target`-based or generic default. */
2727
description?: React.ReactNode;
28+
/**
29+
* Resource type label (e.g. "Credential", "Project"). When provided and `description` is not,
30+
* the default description becomes "Are you sure you want to permanently delete {target}?".
31+
*/
32+
target?: React.ReactNode;
2833
/** Force text-input confirmation even for a single item. Default: false */
2934
requireConfirmInput?: boolean;
3035
/**
@@ -54,6 +59,7 @@ const BAIDeleteConfirmModal: React.FC<BAIDeleteConfirmModalProps> = ({
5459
items,
5560
title,
5661
description,
62+
target,
5763
requireConfirmInput = false,
5864
confirmText: confirmTextProp,
5965
inputLabel,
@@ -83,15 +89,20 @@ const BAIDeleteConfirmModal: React.FC<BAIDeleteConfirmModalProps> = ({
8389
})
8490
: t('comp:BAIDeleteConfirmModal.DeleteItem'));
8591

86-
const resolvedDescription =
87-
description ?? t('comp:BAIDeleteConfirmModal.AreYouSureToDelete');
88-
8992
const resolvedConfirmText =
9093
confirmTextProp ??
9194
(items.length === 1
9295
? (extractTextFromNode(items[0]?.label) ?? t('general.button.Delete'))
9396
: t('general.button.Delete'));
9497

98+
const resolvedDescription =
99+
description ??
100+
(target
101+
? t('comp:BAIDeleteConfirmModal.AreYouSureToPermanentlyDeleteTarget', {
102+
target,
103+
})
104+
: t('comp:BAIDeleteConfirmModal.AreYouSureToDelete'));
105+
95106
const resolvedOkText = okText ?? t('general.button.Delete');
96107

97108
const resolvedInputLabel = inputLabel ?? (
@@ -159,7 +170,7 @@ const BAIDeleteConfirmModal: React.FC<BAIDeleteConfirmModalProps> = ({
159170
}}
160171
>
161172
<BAIFlex direction="column" align="stretch" gap="xs">
162-
<Text>{resolvedDescription}</Text>
173+
{resolvedDescription && <Text>{resolvedDescription}</Text>}
163174
{items.length > 1 && itemListContent}
164175
<Form
165176
form={form}
@@ -199,7 +210,7 @@ const BAIDeleteConfirmModal: React.FC<BAIDeleteConfirmModalProps> = ({
199210
onCancel={onCancel}
200211
>
201212
<BAIFlex direction="column" align="stretch" gap="xs">
202-
<Text>{resolvedDescription}</Text>
213+
{resolvedDescription && <Text>{resolvedDescription}</Text>}
203214
{itemListContent}
204215
<Text type="danger">
205216
{t('comp:BAIDeleteConfirmModal.CannotBeUndone')}

0 commit comments

Comments
 (0)