feat: migrate from <GlobalSnackbar /> to sonner (#22258)

Replaces our custom `<GlobalSnackbar />` (MUI Snackbar + event emitter)
with [`sonner`](https://github.com/emilkowalski/sonner). Deletes
`GlobalSnackbar/`, the custom event emitter infra, and migrates ~80
source files to `toast.success()` / `toast.error()` from `sonner`.

- ~47 error toasts now surface API error detail via
`getErrorDetail(error)` in the toast description, not just a generic
message. Coincides with #22229.
- Toast messages follow an `{Action} "{entity}" {result}.` format (e.g.
`User "alice" suspended successfully.`) since toasts persist across
navigation now.
- 17 uses of `toast.promise()` for loading → success → error lifecycle.
- Some toasts include action buttons for quick navigation (e.g. "View
task", "View template").
- Multiple toasts can stack and display simultaneously.

---------

Co-authored-by: Kayla はな <mckayla@hey.com>
This commit is contained in:
Jake Howell
2026-02-26 02:42:34 +11:00
committed by GitHub
co-authored by Kayla はな
parent 1c4d8fafc7
commit 15a2bab1cd
129 changed files with 1292 additions and 1240 deletions
+7 -3
View File
@@ -8,7 +8,6 @@ import {
updateProfile as updateProfileOptions,
} from "api/queries/users";
import type { UpdateUserProfileRequest, User } from "api/typesGenerated";
import { displaySuccess } from "components/GlobalSnackbar/utils";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import { type Permissions, permissionChecks } from "modules/permissions";
import {
@@ -19,6 +18,7 @@ import {
useContext,
} from "react";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { toast } from "sonner";
export type AuthContextValue = {
isLoading: boolean;
@@ -64,7 +64,6 @@ export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
...updateProfileOptions("me"),
onSuccess: (user) => {
queryClient.setQueryData(meOptions.queryKey, user);
displaySuccess("Updated settings.");
},
});
@@ -96,7 +95,12 @@ export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
const updateProfile = useCallback(
(req: UpdateUserProfileRequest) => {
updateProfileMutation.mutate(req);
const mutation = updateProfileMutation.mutateAsync(req);
toast.promise(mutation, {
loading: "Updating profile...",
success: "Profile updated successfully.",
error: "Failed to update profile.",
});
},
[updateProfileMutation],
);