mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
fix(studio): migrate email template reset dialog to async AlertDialog (#47705)
## 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. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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). <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
511a7806de
commit
fa20667ec1
2 files changed
+62
-17
No files matched your search
@@ -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<string | null>(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 (
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<AlertDialog
|
||||
open={open}
|
||||
onOpenChange={(next) => {
|
||||
setOpen(next)
|
||||
if (!next) setError(null)
|
||||
}}
|
||||
>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="default" type="button" disabled={!canUpdateConfig}>
|
||||
Reset template
|
||||
@@ -73,15 +91,21 @@ export const ResetTemplateDialog = ({
|
||||
: 'This will remove your custom subject line and email body content. The default values will be used instead.'}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
{error && (
|
||||
<AlertDialogBody>
|
||||
<Admonition
|
||||
type="destructive"
|
||||
title="Unable to reset email template"
|
||||
description={error}
|
||||
/>
|
||||
</AlertDialogBody>
|
||||
)}
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogCancel disabled={isResetting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
variant="warning"
|
||||
loading={isResetting}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
resetTemplateToDefault()
|
||||
}}
|
||||
onClick={resetTemplateToDefault}
|
||||
>
|
||||
Reset
|
||||
</AlertDialogAction>
|
||||
|
||||
@@ -124,7 +124,10 @@ const renderTemplateEditor = ({
|
||||
})
|
||||
useAsyncCheckPermissionsMock.mockReturnValue({ can: canUpdateConfig })
|
||||
useAuthConfigUpdateMutationMock.mockReturnValue({ mutate: updateAuthConfigMock })
|
||||
useAuthTemplateResetMutationMock.mockReturnValue({ mutate: resetTemplateMock, isPending: false })
|
||||
useAuthTemplateResetMutationMock.mockImplementation((options = {}) => ({
|
||||
mutateAsync: (vars: unknown) => resetTemplateMock(vars, options),
|
||||
isPending: false,
|
||||
}))
|
||||
|
||||
return render(<TemplateEditor template={confirmationTemplate} />)
|
||||
}
|
||||
@@ -253,4 +256,22 @@ describe('TemplateEditor reset to default', () => {
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Reset template' })).toBeDisabled()
|
||||
})
|
||||
|
||||
it('keeps the dialog open and shows an inline error when reset fails', async () => {
|
||||
const user = userEvent.setup()
|
||||
resetTemplateMock.mockImplementation(async () => {
|
||||
throw new Error('Reset endpoint unavailable')
|
||||
})
|
||||
|
||||
renderTemplateEditor({ hasCustomBody: true })
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Reset template' }))
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
await user.click(within(dialog).getByRole('button', { name: 'Reset' }))
|
||||
|
||||
expect(await within(dialog).findByText('Reset endpoint unavailable')).toBeInTheDocument()
|
||||
expect(within(dialog).getByText('Unable to reset email template')).toBeInTheDocument()
|
||||
expect(within(dialog).getByRole('button', { name: 'Reset' })).toBeInTheDocument()
|
||||
expect(toast.error).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user