feat(web): confirmation flow for destructive actions#120
Conversation
…on skip Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove redundant setStopDialogOpen(false) from onConfirm (Radix AlertDialogAction already closes the dialog on click); add GameCard.test.tsx covering all four Stop confirmation branches. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a reusable confirmation-dialog flow for destructive actions in the web UI, including optional “type to confirm” and per-session “don’t ask again” suppression, and wires it into multiple existing destructive actions.
Changes:
- Added a shared
ConfirmDialogwrapper around shadcn/RadixAlertDialog(optional type-to-confirm + optional “don’t ask again this session”). - Added an in-memory session suppression store (
confirm-skip.ts) and integrated suppression checks for selected actions. - Updated Discord and Dashboard-related UI flows (Stop server, remove guild, clear permissions, remove admin chips) and added/expanded tests.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| app/packages/web/src/pages/DiscordPage.tsx | Adds confirmation dialogs for guild removal, admin ID removal (including chip backspace), and per-game permission clearing; uses suppression checks for selected actions. |
| app/packages/web/src/pages/DiscordPage.test.tsx | Adds tests covering the new confirmation flows for Clear, Remove guild (type-to-confirm), and Remove admin. |
| app/packages/web/src/lib/confirm-skip.ts | Introduces module-level in-memory suppression store (isSuppressed / suppress). |
| app/packages/web/src/components/GameCard.tsx | Adds Stop-server confirmation dialog with optional per-session suppression. |
| app/packages/web/src/components/GameCard.test.tsx | Adds tests for Stop confirmation behavior (suppressed vs. dialog confirm/cancel). |
| app/packages/web/src/components/ConfirmDialog.tsx | New reusable confirm dialog component (type-to-confirm + “don’t ask again” support). |
| app/packages/web/src/components/ConfirmDialog.test.tsx | Adds unit tests for rendering, cancel/confirm behavior, type-to-confirm disabling, and suppression. |
| onConfirm={() => { | ||
| if (pendingRemoveId) onRemove(pendingRemoveId); | ||
| setPendingRemoveId(null); |
There was a problem hiding this comment.
Fixed in 2f8423e — prefixed with void to explicitly discard the promise. wrap() already owns the busy state and error feedback, so discarding the returned promise is intentional.
Generated by Claude Code
…ction `onRemove` calls `wrap()` which is async; the returned Promise was silently discarded in the onConfirm handler, risking unhandled rejection noise if the API call failed. `void` makes the intent explicit — `wrap` already owns the busy state and error feedback. https://claude.ai/code/session_019cf4SN4tAs4R7R8mu2zHse
…onvention Merges the file naming convention from #119 (issue-114). Renames the three files introduced by this PR to match: ConfirmDialog.tsx → confirm-dialog.component.tsx ConfirmDialog.test.tsx → confirm-dialog.component.test.tsx GameCard.test.tsx → game-card.component.test.tsx confirm-skip.ts → confirm-skip.utils.ts Updates all internal imports in those files and in the two conflict sites (game-card.component.tsx, discord.page.tsx) to use the new paths. https://claude.ai/code/session_019cf4SN4tAs4R7R8mu2zHse
| <AlertDialogFooter> | ||
| <AlertDialogCancel onClick={handleCancel}>Cancel</AlertDialogCancel> | ||
| <AlertDialogAction onClick={handleConfirm} disabled={confirmDisabled}> |
There was a problem hiding this comment.
Fixed in 5dc1e08 — removed the handleCancel function and the onClick from AlertDialogCancel. The Radix controlled root already fires onOpenChange(false) on cancel, so the explicit handler was a duplicate call.
Generated by Claude Code
…ialog AlertDialogCancel already calls onOpenChange(false) via the controlled Radix AlertDialog root. The explicit handleCancel onClick was causing a double invocation on every cancel click. https://claude.ai/code/session_019cf4SN4tAs4R7R8mu2zHse
|
@copilot do one final check please |
Final check done:
No issues found — PR looks good to merge. |
Closes #67
Summary
ConfirmDialogcomponent wrapping shadcnAlertDialogwith two optional modes: type-to-confirm input (blocks Confirm until text matches) and "don't ask again" session checkboxlib/confirm-skip.tssession store:isSuppressed(key)/suppress(key)backed by a module-levelSet— resets on hard reload, persists across SPA navigationGameCard): simple confirm with "Don't ask again for this session"; suppressed path firesapi.stop()directlyGuildsSection): type-to-confirm — user must type the guild ID exactly to enable Confirm; no "don't ask again"PermissionRow): simple confirm with "don't ask again"AdminsSection): simple confirm with "don't ask again"; addedonRemoveChipprop toSnowflakeChipsInputso Backspace-to-pop also routes through confirmationConfirmDialog.test.tsx,GameCard.test.tsx, andDiscordPage.test.tsx; 335 total passingImplementation notes
Set) — notlocalStorage— so it resets on hard reload per the spec. SPA navigation leaves the module loaded, so suppression persists across routes.prefers-reduced-motionare handled automatically by the RadixAlertDialogprimitive — no custom code needed for AC#4.AdminsSectiondraft-editor design). The dialog description reflects this.issue-114(file naming convention) lands after this PR, the new files (confirm-skip.ts,ConfirmDialog.tsx,GameCard.test.tsx) will need to be renamed to the kebab-case + type-suffix convention.Test plan
npm run app:test— 335 tests passnpm run app:lint— exits cleanGenerated with Claude Code