From fa20667ec1c2b95d17efc0bc1ab33ff7018d044f Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:43:31 +1000 Subject: [PATCH] fix(studio): migrate email template reset dialog to async AlertDialog (#47705) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What kind of change does this PR introduce? Bug fix / refactor. Resolves DEPR-573. ## What is the current behavior? `ResetTemplateDialog` (added in #45572) confirms the Auth email template reset using the old `AlertDialog` workaround: an `AlertDialogAction` with `asChild` + `event.preventDefault()` and a manual loading `Button`, driven by `mutate` plus inline callbacks. Reset failures are only reported via a toast from the mutation's default `onError`, so the error disappears from the dialog context. This predates #45960, which added first-class async handling to `AlertDialogAction` (promise-returning handlers, controlled `loading`, and `AlertDialogBody` for inline feedback). #45960 explicitly flagged `ResetTemplateDialog` as needing this follow-up migration. ## What is the new behavior? `ResetTemplateDialog` now uses the async `AlertDialogAction` pattern: - The confirm handler uses `mutateAsync` and returns the reset promise, so the dialog stays open with a loading state while the mutation is pending and closes only after it succeeds. - Reset failures surface inline via a destructive `Admonition` inside `AlertDialogBody`, and the mutation's toast-only error path is suppressed (`onError: () => {}`). The inline error clears when the dialog closes. - `Cancel` is disabled while the reset is in flight. - The `asChild` + `preventDefault()` workaround and the manual loading `Button` are removed; `loading={isResetting}` is retained for parent-controlled loading. This matches the established usage in `DisablePipelinesDialog` / `JitDbAccessDeleteDialog` and the design-system `alert-dialog-async-error` example. ## To test - [ ] Customise an Auth email template, click **Reset template**, confirm the dialog shows loading until the reset succeeds and then closes with the editor refreshed to the default subject/body. - [ ] In DevTools → Network, block `*/templates/*/reset`, click **Reset**, and confirm the dialog stays open with an inline destructive admonition and no toast. ## Summary by CodeRabbit * **Bug Fixes** * Improved email template reset error handling by showing reset failures inline in the confirmation dialog (with a destructive alert message). * The dialog remains open on reset failure so users can review the error and retry. * “Cancel” is disabled while resetting; success behavior and existing success toast behavior remain unchanged. * **Tests** * Updated reset mutation mock to use async behavior and added coverage for reset failure UI/error handling (including that error toasts are not triggered). --- .../EmailTemplates/ResetTemplateDialog.tsx | 56 +++++++++++++------ .../EmailTemplates/TemplateEditor.test.tsx | 23 +++++++- 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/ResetTemplateDialog.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/ResetTemplateDialog.tsx index 6c54b1dd732..233e993f042 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/ResetTemplateDialog.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/ResetTemplateDialog.tsx @@ -5,6 +5,7 @@ import { toast } from 'sonner' import { AlertDialog, AlertDialogAction, + AlertDialogBody, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, @@ -14,6 +15,7 @@ import { AlertDialogTrigger, Button, } from 'ui' +import { Admonition } from 'ui-patterns/admonition' import { type AuthTemplate } from './EmailTemplates.types' import { getAuthTemplateType } from './EmailTemplates.utils' @@ -32,6 +34,7 @@ export const ResetTemplateDialog = ({ }) => { const { ref: projectRef } = useParams() const [open, setOpen] = useState(false) + const [error, setError] = useState(null) const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' @@ -40,25 +43,40 @@ export const ResetTemplateDialog = ({ const { id } = template const templateType = getAuthTemplateType(id) - const { mutate: resetAuthTemplate, isPending: isResetting } = useAuthTemplateResetMutation() + const { mutateAsync: resetAuthTemplate, isPending: isResetting } = useAuthTemplateResetMutation({ + onSuccess: (config) => { + toast.success('Email template reset to default') + onResetSuccess(config) + }, + onError: () => {}, + }) const resetTemplateToDefault = async () => { if (!projectRef) throw new Error('Project ref is required') if (!templateType) throw new Error('Template type is required') - resetAuthTemplate( - { projectRef, template: templateType }, - { - onSuccess: (config) => { - toast.success('Email template reset to default') - onResetSuccess(config) - }, - } - ) + setError(null) + + try { + await resetAuthTemplate({ projectRef, template: templateType }) + } catch (error) { + setError( + error instanceof Error + ? error.message + : 'An unknown error occurred while resetting the template' + ) + throw error + } } return ( - + { + setOpen(next) + if (!next) setError(null) + }} + >