mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
## What kind of change does this PR introduce?
UX consistency improvement. Updates DEPR-355.
## What is the current behavior?
Discard-confirm close behavioir is implemented inconsistently across
Studio forms:
- some sheets/dialogs used `useConfirmOnClose`
- some duplicated local `CloseConfirmationModal` components
- some (e.g. `CreateHookSheet`) closed unconditionally and could lose
unsaved changes
## What is the new behavior?
Extracts and validates a reusable discard-close pattern for
dialogs/sheets
- enhances `useConfirmOnClose` with `handleOpenChange(open)` for
`Dialog`/`Sheet` `onOpenChange`
- adds shared `DiscardChangesConfirmationDialog` (`AlertDialog`-based,
override-able copy)
- migrates:
- `InviteMemberButton`
- `CreateHookSheet`
- `EditSecretSheet`
This standardizes close-guard behavior for
backdrop/escape/close-button/cancel-button flows without trying to block
route changes or arbitrary unmounts.
## Additional context
`CreateHookSheet` now also marks the generated secret action as dirty
(`setValue(..., { shouldDirty: true })`) so the discard guard behaves
correctly.
- Added tests for `useConfirmOnClose` covering:
- clean vs dirty close
- handleOpenChange(true|false)
- confirm/cancel behavior
- latest callback ref behavior
A follow-up PR is needed to migrate remaining duplicated
`CloseConfirmationModal` usages and older `useConfirmOnClose` call sites
to the shared `DiscardChangesConfirmationDialog` + `handleOpenChange`
pattern.
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
79 lines
2.0 KiB
TypeScript
79 lines
2.0 KiB
TypeScript
'use client'
|
|
|
|
import { useCallback, useEffect, useRef, type ReactNode } from 'react'
|
|
|
|
import { type ConfirmOnCloseModalProps } from 'hooks/ui/useConfirmOnClose'
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from 'ui'
|
|
|
|
interface DiscardChangesConfirmationDialogProps extends ConfirmOnCloseModalProps {
|
|
title?: ReactNode
|
|
description?: ReactNode
|
|
confirmLabel?: ReactNode
|
|
cancelLabel?: ReactNode
|
|
}
|
|
|
|
export const DiscardChangesConfirmationDialog = ({
|
|
visible,
|
|
onClose,
|
|
onCancel,
|
|
title = 'Discard changes?',
|
|
description = 'Are you sure you want to discard your changes? Any unsaved changes will be lost.',
|
|
confirmLabel = 'Discard changes',
|
|
cancelLabel = 'Keep editing',
|
|
}: DiscardChangesConfirmationDialogProps) => {
|
|
const isConfirmingRef = useRef(false)
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
isConfirmingRef.current = false
|
|
}
|
|
}, [visible])
|
|
|
|
const handleConfirm = useCallback(() => {
|
|
isConfirmingRef.current = true
|
|
onClose()
|
|
}, [onClose])
|
|
|
|
const handleOpenChange = useCallback(
|
|
(open: boolean) => {
|
|
if (open) return
|
|
|
|
if (isConfirmingRef.current) {
|
|
isConfirmingRef.current = false
|
|
return
|
|
}
|
|
|
|
onCancel()
|
|
},
|
|
[onCancel]
|
|
)
|
|
|
|
return (
|
|
<AlertDialog open={visible} onOpenChange={handleOpenChange}>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
|
{description !== undefined && description !== null && (
|
|
<AlertDialogDescription>{description}</AlertDialogDescription>
|
|
)}
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>{cancelLabel}</AlertDialogCancel>
|
|
<AlertDialogAction variant="danger" onClick={handleConfirm}>
|
|
{confirmLabel}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
)
|
|
}
|