-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Goal
Enable editing, revoking/unrevoking, and deleting Access Keys from the list, using a reusable ConfirmDialog, and wiring row actions end-to-end with the API.
- Parent: Access Keys FE #64
- Depends on: Access Keys — Data hooks (CRUD) + mutation keys + API types #65, Access Keys — List view + Status badges + Sorting #66
Description
Add an Edit Key modal for name, description, and expiresAt (set/clear). Add Revoke/Unrevoke and Delete actions behind a reusable ConfirmDialog. Ensure consistent error handling (toast with envelope error.message) and that the list updates via React Query invalidation (or a small optimistic update).
Rules
- Clearing expiration sends
expiresAt: null. - Status precedence (view logic remains from Access Keys — List view + Status badges + Sorting #66): Expired > Revoked > Active.
- No copy affordance anywhere in these flows.
Tasks
A) Reusable ConfirmDialog
-
Create
src/app/(client)/components/common/ConfirmDialog.tsx-
Built from shadcn
Dialog+ minimal Button pair. -
Props (guide; do not over-specify):
type ConfirmDialogProps = { open: boolean; title: string; description?: string; confirmLabel?: string; // default: "Confirm" cancelLabel?: string; // default: "Cancel" variant?: 'default'|'destructive'; isLoading?: boolean; onConfirm: () => void | Promise<void>; onOpenChange: (v: boolean) => void; };
-
a11y: focus first actionable element;
aria-describedbyhooked to description.
-
B) Edit modal
-
Add
src/app/(client)/(core)/key-management/components/KeyEditModal.tsx-
RHF +
UpdateAccessKeySchema(@lib/validation). -
Fields:
name(optional, 1–100)description(optional, ≤500)expiresAt(optional date/time; include a Clear control)
-
Build a PATCH payload from dirty fields only; include
expiresAt: nullwhen cleared.// pseudo: const patch: UpdateAccessKeyPatch = {}; if (dirty.name) patch.name = values.name!; if (dirty.description) patch.description = values.description!; if (dirty.expiresAt) patch.expiresAt = values.expiresAt ?? null;
-
Submit via
useUpdateAccessKeyMutation(); on success close modal and let the hook invalidate the list. -
Errors →
handleFormError(err, { setError: form.setError }). -
Disable submit while pending.
-
C) Wire row actions in the list
-
Update
src/app/(client)/(core)/key-management/components/RowActions.tsx- Make the existing buttons enabled and accept callbacks.
-
Update
KeysTable.tsx-
Provide callbacks to
RowActions:- Edit → open
KeyEditModalwith selected row (local state:editingKeyIdor the whole row). - Revoke/Unrevoke → open
ConfirmDialogwith context; on confirm calluseRevokeAccessKeyMutation().mutate({ id, revoked: true|false }). - Delete → open
ConfirmDialog(destructive); on confirm calluseDeleteAccessKeyMutation().mutate({ id }).
- Edit → open
-
Optional: optimistic UI
- For revoke/unrevoke, you may optimistically flip
revokedinqueryClient.setQueryDataand roll back on error. Not required — invalidation is sufficient for Phase 2.
- For revoke/unrevoke, you may optimistically flip
-
D) UX polish
-
Button labels:
-
Row: Edit, Revoke/Unrevoke, Delete
-
ConfirmDialog copy:
- Revoke: “Revoke this Access Key?” — “Clients using this key will be rejected.”
- Unrevoke: “Re-enable this Access Key?”
- Delete: “Permanently delete this Access Key?” — “This cannot be undone.”
-
-
Loading states:
- Show button loading in dialogs during mutation.
- Disable actions while a mutation is in flight.
-
Keep date rendering consistent with Access Keys — List view + Status badges + Sorting #66 (
formatDateTime, ISO tooltip).
E) Cleanup & consistency
- Remove any remaining preview copy affordances (there should be none).
- Ensure all mutations invalidate
queryKeys.accessKeys.list()(hooks from Access Keys — Data hooks (CRUD) + mutation keys + API types #65 already do this). - Add light JSDoc to
ConfirmDialogandKeyEditModalto clarify usage.
Tests (manual)
Edit
- Open Edit on a row; change only
name→ PATCH sends only{"name": "…"}. - Set an expiration → saved and reflected; Clear expiration → sends
{"expiresAt": null}and shows “—” afterward. - Server errors (422/500) → toast shows
error.message; modal remains open; fields intact.
Revoke / Unrevoke
- Click Revoke → ConfirmDialog → confirm → row shows Revoked (unless already Expired).
- Click Unrevoke → ConfirmDialog → confirm → row status returns to Active/Expired as appropriate.
- Errors → toast; state re-syncs after invalidate.
Delete
- Click Delete → ConfirmDialog → confirm → row disappears (204).
- Errors → toast; table remains stable.
General
- Multiple dialogs can’t be open simultaneously; focus is restored sensibly.
- Actions disabled while pending; no duplicate requests on double-click.
- Accessibility: ConfirmDialog is keyboard operable;
aria-describedbyties to description; focus trap works.
Acceptance Criteria
- Editing, revoking/unrevoking, and deleting work end-to-end against the real API.
ConfirmDialogis reusable and adopted by both Revoke/Unrevoke and Delete actions.- PATCH sends only changed fields; clearing expiration uses
null. - List reflects changes via query invalidation (optimistic update optional).
- Errors and loading states match the app’s existing patterns (RHF + toasts).
- No preview copy functionality added.