Files
supabase/apps/studio/components/interfaces/APIKeys/CreateNewAPIKeysButton.tsx
Danny White 331278bfe4 feat(studio): add async handling to AlertDialog actions (#45960)
## What kind of change does this PR introduce?

Feature, bug fix, and docs update. Addresses the AlertDialog async
action behaviour discussed in Slack and follow-up PR feedback.

## What is the current behavior?

`AlertDialogAction` delegates directly to Radix, so confirm actions
close the dialog immediately on click. Async mutation flows have to use
`asChild` with `event.preventDefault()` and a custom loading button to
keep the dialog open while work is in flight.

## What is the new behavior?

- `AlertDialogAction` now accepts async handlers and a controlled
`loading` prop. Promise-returning actions keep the dialog open, show the
existing Button loading state, disable cancel/dismissal while pending,
close on success, and stay open on rejection.
- Existing workaround usages in Studio have been migrated to the direct
action API (see 'To test' callsite list below)
- design-system docs now include async action examples and
`AlertDialogBody` guidance for inline feedback


https://github.com/user-attachments/assets/1af66410-e9f9-4231-9c6d-fe650bd717a4


## Additional context

- [ ] Once #45572 is rebased onto this change, `ResetTemplateDialog`
should use `AlertDialogAction loading={isResettingTemplate}` with a
promise-returning reset handler instead of a plain loading `Button` in
`AlertDialogFooter`.

## To test

- [x] On Studio API Keys settings, use a project with no publishable or
secret API keys, click the “Create API keys” banner action, and confirm
the Alert Dialog stays open with loading until the default publishable
and secret keys are created.
- [x] Delete a JIT database access rule and confirm the Alert Dialog
stays open with loading until deletion succeeds, and stays open with
inline feedback if it fails.
- [x] With temporary access disabled and existing rules configured,
enable temporary access and confirm the “This will activate existing
rules” Alert Dialog stays open with loading until the configuration
update succeeds, and stays open with inline feedback if it fails.
- [x] Disable external replication and confirm the Alert Dialog stays
open with loading until the mutation succeeds.
- [x] Enable Index Advisor and confirm the Alert Dialog stays open with
loading until the mutation succeeds, and stays open with inline feedback
if it fails.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Alert dialogs support async actions with built-in loading, dismissal
blocking while pending, and preserved dialog on error.
* Two interactive examples demonstrating async success and error flows.

* **Improvements**
* Dialogs now surface inline error messages and consistent
loading/confirm behavior across flows (create keys, replication, JIT DB
access, index advisor).
  * Minor UI refinements for action controls.

* **Documentation**
* Docs updated with async-action guidance and inline-error
recommendations.

* **Tests**
  * New test suite validating async dialog behaviors.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45960)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com>
2026-05-22 11:44:45 +10:00

85 lines
2.7 KiB
TypeScript

import { useParams } from 'common'
import { useState } from 'react'
import { toast } from 'sonner'
import {
AlertDialog,
AlertDialogAction,
AlertDialogBody,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
Button,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { useAPIKeyCreateMutation } from '@/data/api-keys/api-key-create-mutation'
export const CreateNewAPIKeysButton = () => {
const { ref: projectRef } = useParams()
const [createKeysDialogOpen, setCreateKeysDialogOpen] = useState(false)
const [error, setError] = useState<string | null>(null)
const { mutateAsync: createAPIKey } = useAPIKeyCreateMutation({ onError: () => {} })
const handleCreateNewApiKeys = async () => {
if (!projectRef) return
try {
setError(null)
// Create publishable key
try {
await createAPIKey({ projectRef, type: 'publishable', name: 'default' })
} catch (error: any) {
setError(`Failed to create the default publishable key: ${error.message}`)
throw error
}
// Create secret key
try {
await createAPIKey({ projectRef, type: 'secret', name: 'default' })
} catch (error: any) {
setError(
`The default publishable key was created, but the default secret key failed: ${error.message}`
)
throw error
}
setCreateKeysDialogOpen(false)
toast.success('Successfully created a new set of API keys!')
} catch (error) {
console.error('Failed to create API keys:', error)
throw error
}
}
return (
<AlertDialog open={createKeysDialogOpen} onOpenChange={setCreateKeysDialogOpen}>
<Button onClick={() => setCreateKeysDialogOpen(true)}>Create new API keys</Button>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Create new API keys</AlertDialogTitle>
<AlertDialogDescription>
This will create a default publishable key and a default secret key both named{' '}
<code className="break-keep! text-code-inline">default</code>. These keys are required
to connect your application to your Supabase project.
</AlertDialogDescription>
</AlertDialogHeader>
{error && (
<AlertDialogBody>
<Admonition type="destructive" title="Unable to create API keys" description={error} />
</AlertDialogBody>
)}
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleCreateNewApiKeys}>Create keys</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}