Skip to content

🥂 feat: Toasts on All Save Actions#81

Merged
danny-avila merged 1 commit into
mainfrom
feat/save-action-toasts-v2
Jun 18, 2026
Merged

🥂 feat: Toasts on All Save Actions#81
danny-avila merged 1 commit into
mainfrom
feat/save-action-toasts-v2

Conversation

@dustinhealy

@dustinhealy dustinhealy commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Wires @clickhouse/click-ui's Toast into every save mutation in the admin panel so admins get visible feedback on both success and failure. Before this PR most save sites silently swallowed errors to console.error and showed nothing on success.

Depends on the Radix react-dismissable-layer dedupe in #80. Without that dedupe click-ui's toast auto-dismiss timer is killed by the shared-layer-set race as soon as any modal opens or closes.

  • Add notifySuccess(title) and notifyError(title) helpers in src/utils/toast.ts that delegate to click-ui's exported createToast. ClickUIProvider already mounts the matching ToastProvider, so no extra wiring is needed at the root.
  • Fire a success/error toast from every save site: config editor (per-field, bulk save, YAML import), groups create/edit/delete, roles create/edit/delete, users invite/delete, role + group assignment in UserDetailDialog, user-profile create/delete, system grants in EditCapabilitiesDialog, and per-field profile values in useProfileMutations.
  • Pass the affected resource through each mutation's variables rather than reading it from closed-over component state in onSuccess, so the toast still renders the correct name when the confirm dialog has cleared its target by the time the response lands.
  • Delete the unused .config-toast* CSS block and ToastState type left over from the old ConfigPage-local toast portal.
  • New i18n keys under com_toast_* for the per-resource success messages (com_toast_group_deleted, com_toast_role_updated, etc.).

Change Type

  • New feature (non-breaking change which adds functionality)

Implementation

  • src/utils/toast.ts: five-line module exporting notifySuccess and notifyError over createToast({ type: 'success' | 'danger', title }). Toasts inherit click-ui's brand colours, WCAG contrast, dismiss button, swipe-to-dismiss, ARIA live region, keyboard accessibility, and 5s auto-dismiss from the library.
  • Mutation variables carry the resource name. GroupsTab, RolesTab, UsersPage delete-mutation mutationFn now takes the full target object; UserDetailDialog's add/remove role/group mutations take { id, name }; EditCapabilitiesDialog's save passes { name: principalName }. Each onSuccess reads from the mutation's variables arg so the toast cannot read an empty string when the confirm dialog has already closed.
  • ConfigPage drops its local useState<ToastState> + portal and instead calls notifySuccess and notifyError directly. The notifySaving intermediate state is gone since the bulk save flow already shows pending state via ConfirmSaveDialog's saving prop and the StickyActionBar's disabled save button.

Testing

  • SESSION_SECRET=12345678901234567890123456789012 bun run test: 675 tests pass across 17 files.
  • bun run lint: clean.
  • SESSION_SECRET=12345678901234567890123456789012 bun run build: production build green.
  • Manual: triggered each save mutation listed above and confirmed a click-ui toast appears with the resource name and click-ui's brand styling, auto-dismisses after 5s, can be dismissed early via the close button or swipe-right.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • Local unit tests pass with my changes

Note

Medium Risk
Wide UI-only surface area across admin mutations (including capabilities and access control), but server behavior is largely unchanged aside from clearer client error handling and toast timing dependency on click-ui/Radix dismissable-layer behavior.

Overview
Adds shared notifySuccess / notifyError helpers that call click-ui createToast, and uses them across admin save/delete flows so successes and failures are visible instead of inline-only or silent errors.

Config drops the local ToastState portal and .config-toast* CSS in favor of toasts on save, validation, import, and scope errors; the in-flight “Saving…” toast is removed (pending state stays on the confirm dialog / action bar).

Access, users, grants, and profile field edits fire localized com_toast_* messages on create/update/delete and on role/group assignment changes. Mutations now pass the affected resource (full entity or { id, name }) through mutate variables so toast text stays correct after confirm dialogs close. Several onError handlers switch from setError to notifyError; role delete no longer uses ConfirmDialog’s error prop.

Edit save paths throw when group/role/principal is missing instead of no-op returns.

Reviewed by Cursor Bugbot for commit 2fa6042. Bugbot is set up for automated code reviews on this repo. Configure here.

* feat: success/error toasts on every admin panel save action

Wires click-ui's Toast component into every save mutation in the admin
panel: config editor (per-field, bulk, YAML import), groups (create /
edit / delete), roles (create / edit / delete), users (invite, delete,
role / group assignment, user profile create / delete), system grants
(EditCapabilitiesDialog), and per-field profile mutations
(useProfileMutations).

Uses click-ui's official createToast directly; ClickUIProvider already
mounts the matching ToastProvider, so the global createToast routes
through it. Toasts inherit click-ui brand colours, WCAG contrast,
keyboard accessibility, swipe-to-dismiss, close button, ARIA live
region, and 5s auto-dismiss from the library.

src/utils/toast.ts is a five-line wrapper exposing notifySuccess and
notifyError that delegate to click-ui's createToast. Call sites pass
the affected resource through mutation variables (not closed-over
component state) so the toast renders the correct name even when the
confirm dialog has already cleared its target by the time the response
lands.

Refs AI-1206.

* fix: throw instead of silently no-op when target missing in edit mutations

EditGroupDialog, EditRoleDialog and EditCapabilitiesDialog mutationFns
used to bail with an empty return when their target (group, role,
principal) was missing, which React Query treats as success and which
caused the new onSuccess handlers to fire a success toast and close
the dialog without anything having been persisted.

Throw a localised error in the unavailable case so onError fires
instead, and add a matching guard at each call site so mutate is
never invoked with a missing target in the first place.

* fix: capture submitted name in mutation variables for create/edit toasts

The five create and edit dialogs used to read the resource name from
component state inside onSuccess instead of from the value that was
submitted with the mutation. If the user edited the name field while
the request was in flight (or the dialog reset before the toast
fired), the toast could render an empty or wrong name even though the
server saved the original value.

Pass the submitted name through the mutation variables, use it for
the actual API call, and read it back from the mutation's data or
variables argument in onSuccess so the toast always reflects what
was persisted.
@danny-avila danny-avila merged commit 6bde5c3 into main Jun 18, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants