Files
Danny White fa20667ec1 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 -->
2026-07-08 14:43:31 +08:00

117 lines
3.5 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { useState } from 'react'
import { toast } from 'sonner'
import {
AlertDialog,
AlertDialogAction,
AlertDialogBody,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
Button,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { type AuthTemplate } from './EmailTemplates.types'
import { getAuthTemplateType } from './EmailTemplates.utils'
import { AuthConfigResponse } from '@/data/auth/auth-config-query'
import { useAuthTemplateResetMutation } from '@/data/auth/auth-template-reset-mutation'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
export const ResetTemplateDialog = ({
template,
hasUnsavedChanges,
onResetSuccess,
}: {
template: AuthTemplate
hasUnsavedChanges: boolean
onResetSuccess: (config: AuthConfigResponse) => void
}) => {
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'
)
const { id } = template
const templateType = getAuthTemplateType(id)
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')
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={(next) => {
setOpen(next)
if (!next) setError(null)
}}
>
<AlertDialogTrigger asChild>
<Button variant="default" type="button" disabled={!canUpdateConfig}>
Reset template
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Reset template to default</AlertDialogTitle>
<AlertDialogDescription>
{hasUnsavedChanges
? 'This will discard your unsaved changes and use the default subject line and email body content.'
: '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 disabled={isResetting}>Cancel</AlertDialogCancel>
<AlertDialogAction
variant="warning"
loading={isResetting}
onClick={resetTemplateToDefault}
>
Reset
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}