Skip to content

Access Keys — Edit / Revoke / Delete #68

@mahid797

Description

@mahid797

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.



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

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-describedby hooked 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: null when 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 KeyEditModal with selected row (local state: editingKeyId or the whole row).
      • Revoke/Unrevoke → open ConfirmDialog with context; on confirm call useRevokeAccessKeyMutation().mutate({ id, revoked: true|false }).
      • Delete → open ConfirmDialog (destructive); on confirm call useDeleteAccessKeyMutation().mutate({ id }).
    • Optional: optimistic UI

      • For revoke/unrevoke, you may optimistically flip revoked in queryClient.setQueryData and roll back on error. Not required — invalidation is sufficient for Phase 2.

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


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-describedby ties to description; focus trap works.

Acceptance Criteria

  • Editing, revoking/unrevoking, and deleting work end-to-end against the real API.
  • ConfirmDialog is 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ⚡ ImportantHigh-impact issue that needs to be resolved before the next release.🎨 FrontendFrontend related issue.🖌️ UX/UI ChangeUser experience or interface design change.🧩 CoreChanges to core system architecture or critical modules.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions