mirror of
https://github.com/supabase/supabase.git
synced 2026-06-06 14:15:24 +08:00
chore: migrate Integrations Modal to Dialog (#46380)
## Problem We still use the deprecated `Modal` for: - Deleting a wrapper - Updating a vault secret - Sending a queue message ## Solution - use `Dialog` instead <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Replaced several modal dialogs with updated dialog/alert patterns for sending messages and confirming deletions, improving visual consistency and content structure. * **Bug Fixes** * Prevent duplicate/accidental actions by disabling buttons and showing loading states during pending operations; confirmation dialogs now display relevant item details and close on success. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46380?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -4,6 +4,14 @@ import { useEffect } from 'react'
|
||||
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTitle,
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
@@ -11,7 +19,6 @@ import {
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
InputGroupText,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import z from 'zod'
|
||||
@@ -31,6 +38,7 @@ const FormSchema = z.object({
|
||||
(val) => {
|
||||
try {
|
||||
JSON.parse(val)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
@@ -80,69 +88,77 @@ export const SendMessageModal = ({ visible, onClose }: SendMessageModalProps) =>
|
||||
}, [visible])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="medium"
|
||||
alignFooter="right"
|
||||
header="Add a message to the queue"
|
||||
visible={visible}
|
||||
loading={isPending}
|
||||
onCancel={onClose}
|
||||
confirmText="Add"
|
||||
onConfirm={() => {
|
||||
const values = form.getValues()
|
||||
onSubmit(values)
|
||||
}}
|
||||
>
|
||||
<Modal.Content className="flex flex-col gap-y-4">
|
||||
<Form {...form}>
|
||||
<form
|
||||
id={FORM_ID}
|
||||
className="grow overflow-auto gap-2 flex flex-col"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
<Dialog open={visible} onOpenChange={onClose}>
|
||||
<DialogContent size="medium">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a message to the queue</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogSectionSeparator />
|
||||
<DialogSection className="flex flex-col gap-y-4">
|
||||
<Form {...form}>
|
||||
<form
|
||||
id={FORM_ID}
|
||||
className="grow overflow-auto gap-2 flex flex-col"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="delay"
|
||||
render={({ field: { ref, ...rest } }) => (
|
||||
<FormItemLayout
|
||||
label="Delay"
|
||||
layout="vertical"
|
||||
className="gap-1"
|
||||
description="Time in seconds before the message becomes available for reading."
|
||||
>
|
||||
<FormControl>
|
||||
<InputGroup>
|
||||
<InputGroupInput {...rest} type="number" placeholder="1" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>sec</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="payload"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout label="Message payload" layout="vertical" className="gap-1">
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
id="message-payload"
|
||||
language="json"
|
||||
autofocus={false}
|
||||
className="mb-0! h-32 overflow-hidden rounded-sm border"
|
||||
onInputChange={(e: string | undefined) => field.onChange(e)}
|
||||
options={{ wordWrap: 'off', contextmenu: false }}
|
||||
value={field.value}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogSection>
|
||||
<DialogFooter>
|
||||
<Button type="default" onClick={onClose} disabled={isPending}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
form={FORM_ID}
|
||||
disabled={isPending}
|
||||
loading={isPending}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="delay"
|
||||
render={({ field: { ref, ...rest } }) => (
|
||||
<FormItemLayout
|
||||
label="Delay"
|
||||
layout="vertical"
|
||||
className="gap-1"
|
||||
description="Time in seconds before the message becomes available for reading."
|
||||
>
|
||||
<FormControl>
|
||||
<InputGroup>
|
||||
<InputGroupInput {...rest} type="number" placeholder="1" />
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>sec</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="payload"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout label="Message payload" layout="vertical" className="gap-1">
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
id="message-payload"
|
||||
language="json"
|
||||
autofocus={false}
|
||||
className="mb-0! h-32 overflow-hidden rounded-sm border"
|
||||
onInputChange={(e: string | undefined) => field.onChange(e)}
|
||||
options={{ wordWrap: 'off', contextmenu: false }}
|
||||
value={field.value}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
Add
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { parseAsString, useQueryState } from 'nuqs'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { Modal } from 'ui'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from 'ui'
|
||||
|
||||
import { useVaultSecretDeleteMutation } from '@/data/vault/vault-secret-delete-mutation'
|
||||
import { useVaultSecretsQuery } from '@/data/vault/vault-secrets-query'
|
||||
@@ -18,11 +27,7 @@ export const DeleteSecretModal = () => {
|
||||
const [secretIdToDelete, setSelectedSecretToDelete] = useQueryState('delete', parseAsString)
|
||||
const selectedSecret = secrets.find((secret) => secret.id === secretIdToDelete)
|
||||
|
||||
const {
|
||||
mutate: deleteSecret,
|
||||
isPending: isDeleting,
|
||||
isSuccess: isSuccessDelete,
|
||||
} = useVaultSecretDeleteMutation({
|
||||
const { mutateAsync: deleteSecret, isSuccess: isSuccessDelete } = useVaultSecretDeleteMutation({
|
||||
onSuccess: () => {
|
||||
toast.success(`Successfully deleted secret ${selectedSecret?.name}`)
|
||||
setSelectedSecretToDelete(null)
|
||||
@@ -51,27 +56,30 @@ export const DeleteSecretModal = () => {
|
||||
}, [isSuccess, isSuccessDelete, secretIdToDelete, selectedSecret, setSelectedSecretToDelete])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="small"
|
||||
variant="danger"
|
||||
alignFooter="right"
|
||||
header="Confirm to delete secret"
|
||||
visible={!!selectedSecret}
|
||||
loading={isDeleting}
|
||||
onCancel={() => setSelectedSecretToDelete(null)}
|
||||
onConfirm={onConfirmDeleteSecret}
|
||||
>
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm">
|
||||
The following secret will be permanently removed and cannot be recovered. Are you sure?
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm">{selectedSecret?.description}</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
ID: <code className="text-code-inline">{selectedSecret?.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
<AlertDialog open={!!selectedSecret} onOpenChange={() => setSelectedSecretToDelete(null)}>
|
||||
<AlertDialogContent size="small">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Confirm to delete secret</AlertDialogTitle>
|
||||
<AlertDialogDescription className="space-y-4">
|
||||
<p className="text-sm">
|
||||
The following secret will be permanently removed and cannot be recovered. Are you
|
||||
sure?
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm">{selectedSecret?.description}</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
ID: <code className="text-code-inline">{selectedSecret?.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction variant="danger" onClick={onConfirmDeleteSecret}>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,16 @@ import { useParams } from 'common'
|
||||
import { parseAsString, useQueryState } from 'nuqs'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { Modal } from 'ui'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from 'ui'
|
||||
|
||||
import { INTEGRATIONS } from '../Landing/Integrations.constants'
|
||||
import { getWrapperMetaForWrapper, wrapperMetaComparator } from './Wrappers.utils'
|
||||
@@ -34,11 +43,7 @@ export const DeleteWrapperModal = () => {
|
||||
)
|
||||
const selectedWrapper = wrappers.find((x) => x.id.toString() === selectedWrapperIdToDelete)
|
||||
|
||||
const {
|
||||
mutate: deleteFDW,
|
||||
isPending: isDeleting,
|
||||
isSuccess: isSuccessDelete,
|
||||
} = useFDWDeleteMutation({
|
||||
const { mutateAsync: deleteFDW, isSuccess: isSuccessDelete } = useFDWDeleteMutation({
|
||||
onSuccess: () => {
|
||||
toast.success(`Successfully disabled ${selectedWrapper?.name} foreign data wrapper`)
|
||||
setSelectedWrapperToDelete(null)
|
||||
@@ -51,7 +56,7 @@ export const DeleteWrapperModal = () => {
|
||||
if (!selectedWrapper) return console.error('Wrapper is required')
|
||||
if (!wrapperMeta) return console.error('Wrapper meta is required')
|
||||
|
||||
deleteFDW({
|
||||
await deleteFDW({
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
wrapper: selectedWrapper,
|
||||
@@ -73,22 +78,25 @@ export const DeleteWrapperModal = () => {
|
||||
])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="medium"
|
||||
variant="danger"
|
||||
alignFooter="right"
|
||||
loading={isDeleting}
|
||||
visible={selectedWrapper !== undefined}
|
||||
onCancel={() => setSelectedWrapperToDelete(null)}
|
||||
onConfirm={() => onConfirmDelete()}
|
||||
header={`Confirm to disable ${selectedWrapper?.name}`}
|
||||
<AlertDialog
|
||||
open={selectedWrapper !== undefined}
|
||||
onOpenChange={() => setSelectedWrapperToDelete(null)}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
Are you sure you want to disable {selectedWrapper?.name}? This will also remove all tables
|
||||
created with this wrapper.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
<AlertDialogContent size="medium">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{`Confirm to disable ${selectedWrapper?.name}`}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to disable {selectedWrapper?.name}? This will also remove all
|
||||
tables created with this wrapper.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction variant="danger" onClick={onConfirmDelete}>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user