Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { isEmpty, noop } from 'lodash'
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { toast } from 'sonner'

import { useFeaturePreviewModal } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import useLatest from 'hooks/misc/useLatest'
import { useConfirmOnClose, type ConfirmOnCloseModalProps } from 'hooks/ui/useConfirmOnClose'
import { Modal } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { POLICY_MODAL_VIEWS } from '../Policies.constants'
Expand Down Expand Up @@ -71,29 +73,46 @@ const PolicyEditorModal = ({
)
const [policyStatementForReview, setPolicyStatementForReview] = useState<any>('')
const [isDirty, setIsDirty] = useState(false)
const [isClosingPolicyEditorModal, setIsClosingPolicyEditorModal] = useState(false)

const { confirmOnClose, modalProps: closeConfirmationModalProps } = useConfirmOnClose({
checkIsDirty: () => isDirty,
onClose: () => {
onSelectCancel()
setIsDirty(false)
},
})

const onViewIntro = useCallback(() => setView(POLICY_MODAL_VIEWS.SELECTION), [])
const onViewEditor = useCallback(() => setView(POLICY_MODAL_VIEWS.EDITOR), [])
const onViewTemplates = () => {
setPreviousView(view)
setView(POLICY_MODAL_VIEWS.TEMPLATES)
}
const onReviewPolicy = () => setView(POLICY_MODAL_VIEWS.REVIEW)
const onSelectBackFromTemplates = () => setView(previousView)

const isNewPolicyRef = useLatest(isNewPolicy)
const initializedPolicyFormFieldsRef = useLatest(initializedPolicyFormFields)
useEffect(() => {
if (visible) {
if (isNewPolicy) {
if (isNewPolicyRef.current) {
onViewIntro()
} else {
onViewEditor()
}
setPolicyFormFields(initializedPolicyFormFields)
setPolicyFormFields(initializedPolicyFormFieldsRef.current)
}
}, [visible])
}, [
onViewIntro,
onViewEditor,
isNewPolicyRef,
initializedPolicyFormFieldsRef,
// end of stable references
visible,
])

/* Methods that are for the UI */

const onViewIntro = () => setView(POLICY_MODAL_VIEWS.SELECTION)
const onViewEditor = () => setView(POLICY_MODAL_VIEWS.EDITOR)
const onViewTemplates = () => {
setPreviousView(view)
setView(POLICY_MODAL_VIEWS.TEMPLATES)
}
const onReviewPolicy = () => setView(POLICY_MODAL_VIEWS.REVIEW)
const onSelectBackFromTemplates = () => setView(previousView)

const onToggleFeaturePreviewModal = () => {
toggleFeaturePreviewModal()
onSelectCancel()
Expand Down Expand Up @@ -158,10 +177,6 @@ const PolicyEditorModal = ({
hasError ? onViewEditor() : onSaveSuccess()
}

const isClosingPolicyEditor = () => {
isDirty ? setIsClosingPolicyEditorModal(true) : onSelectCancel()
}

return (
<Modal
hideFooter
Expand All @@ -180,25 +195,10 @@ const PolicyEditorModal = ({
onToggleFeaturePreviewModal={onToggleFeaturePreviewModal}
/>,
]}
onCancel={isClosingPolicyEditor}
onCancel={confirmOnClose}
>
<div>
<ConfirmationModal
visible={isClosingPolicyEditorModal}
title="Discard changes"
confirmLabel="Discard"
onCancel={() => setIsClosingPolicyEditorModal(false)}
onConfirm={() => {
onSelectCancel()
setIsClosingPolicyEditorModal(false)
setIsDirty(false)
}}
>
<p className="text-sm text-foreground-light">
There are unsaved changes. Are you sure you want to close the editor? Your changes will
be lost.
</p>
</ConfirmationModal>
<CloseConfirmationModal {...closeConfirmationModalProps} />
{view === POLICY_MODAL_VIEWS.SELECTION ? (
<PolicySelection
description="Write rules with PostgreSQL's policies to fit your unique business needs."
Expand Down Expand Up @@ -233,4 +233,19 @@ const PolicyEditorModal = ({
)
}

const CloseConfirmationModal = ({ visible, onClose, onCancel }: ConfirmOnCloseModalProps) => (
<ConfirmationModal
visible={visible}
title="Discard changes"
confirmLabel="Discard"
onCancel={onCancel}
onConfirm={onClose}
>
<p className="text-sm text-foreground-light">
There are unsaved changes. Are you sure you want to close the editor? Your changes will be
lost.
</p>
</ConfirmationModal>
)

export default PolicyEditorModal
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const QueryError = ({
setOpen: Dispatch<SetStateAction<boolean>>
}) => {
const formattedError =
(error?.formattedError?.split('\n') ?? [])?.filter((x: string) => x.length > 0) ?? []
(error?.message?.split('\n') ?? [])?.filter((x: string) => x.length > 0) ?? []

return (
<div className="flex flex-col gap-y-3 px-5">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { PostgresPolicy } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useQueryClient } from '@tanstack/react-query'
import { isEqual } from 'lodash'
import { memo, useEffect, useRef, useState } from 'react'
import { memo, useCallback, useEffect, useRef, useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import * as z from 'zod'
Expand All @@ -17,6 +17,7 @@ import { databasePoliciesKeys } from 'data/database-policies/keys'
import { QueryResponseError, useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useConfirmOnClose, type ConfirmOnCloseModalProps } from 'hooks/ui/useConfirmOnClose'
import {
Button,
Checkbox_Shadcn_,
Expand Down Expand Up @@ -94,7 +95,6 @@ export const PolicyEditorPanel = memo(function ({
const [selectedDiff, setSelectedDiff] = useState<string>()

const [showTools, setShowTools] = useState<boolean>(false)
const [isClosingPolicyEditorPanel, setIsClosingPolicyEditorPanel] = useState<boolean>(false)

const formId = 'rls-editor'
const FormSchema = z.object({
Expand Down Expand Up @@ -139,15 +139,18 @@ export const PolicyEditorPanel = memo(function ({
},
})

const onClosingPanel = () => {
const hasUnsavedChanges = useCallback(() => {
const editorOneValue = editorOneRef.current?.getValue().trim() ?? null
const editorOneFormattedValue = !editorOneValue ? null : editorOneValue
const editorTwoValue = editorTwoRef.current?.getValue().trim() ?? null
const editorTwoFormattedValue = !editorTwoValue ? null : editorTwoValue

const policyCreateUnsaved =
selectedPolicy === undefined &&
(name.length > 0 || roles.length > 0 || editorOneFormattedValue || editorTwoFormattedValue)
(name.length > 0 ||
roles.length > 0 ||
!!editorOneFormattedValue ||
!!editorTwoFormattedValue)
const policyUpdateUnsaved =
selectedPolicy !== undefined
? checkIfPolicyHasChanged(selectedPolicy, {
Expand All @@ -158,12 +161,13 @@ export const PolicyEditorPanel = memo(function ({
})
: false

if (policyCreateUnsaved || policyUpdateUnsaved) {
setIsClosingPolicyEditorPanel(true)
} else {
onSelectCancel()
}
}
return policyCreateUnsaved || policyUpdateUnsaved
}, [command, name, roles, selectedPolicy])

const { confirmOnClose, modalProps: closeConfirmationModalProps } = useConfirmOnClose({
checkIsDirty: hasUnsavedChanges,
onClose: onSelectCancel,
})

const onSubmit = (data: z.infer<typeof FormSchema>) => {
const { name, table, behavior, command, roles } = data
Expand Down Expand Up @@ -242,7 +246,6 @@ export const PolicyEditorPanel = memo(function ({
editorOneRef.current?.setValue('')
editorTwoRef.current?.setValue('')
setShowTools(false)
setIsClosingPolicyEditorPanel(false)
setError(undefined)
setShowDetails(false)
setSelectedDiff(undefined)
Expand Down Expand Up @@ -288,7 +291,7 @@ export const PolicyEditorPanel = memo(function ({
<>
<Form_Shadcn_ {...form}>
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
<Sheet open={visible} onOpenChange={() => onClosingPanel()}>
<Sheet open={visible} onOpenChange={confirmOnClose}>
<SheetContent
showClose={false}
size={showTools ? 'lg' : 'default'}
Expand Down Expand Up @@ -481,7 +484,7 @@ export const PolicyEditorPanel = memo(function ({
<Button
type="default"
disabled={isExecuting || isUpdating}
onClick={() => onClosingPanel()}
onClick={confirmOnClose}
>
Cancel
</Button>
Expand Down Expand Up @@ -569,23 +572,24 @@ export const PolicyEditorPanel = memo(function ({
</form>
</Form_Shadcn_>

<ConfirmationModal
visible={isClosingPolicyEditorPanel}
title="Discard changes"
confirmLabel="Discard"
onCancel={() => setIsClosingPolicyEditorPanel(false)}
onConfirm={() => {
onSelectCancel()
setIsClosingPolicyEditorPanel(false)
}}
>
<p className="text-sm text-foreground-light">
Are you sure you want to close the editor? Any unsaved changes on your policy and
conversations with the Assistant will be lost.
</p>
</ConfirmationModal>
<CloseConfirmationModal {...closeConfirmationModalProps} />
</>
)
})

PolicyEditorPanel.displayName = 'PolicyEditorPanel'

const CloseConfirmationModal = ({ visible, onClose, onCancel }: ConfirmOnCloseModalProps) => (
<ConfirmationModal
visible={visible}
title="Discard changes"
confirmLabel="Discard"
onCancel={onCancel}
onConfirm={onClose}
>
<p className="text-sm text-foreground-light">
Are you sure you want to close the editor? Any unsaved changes on your policy and
conversations with the Assistant will be lost.
</p>
</ConfirmationModal>
)
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,11 @@ export const CreateBranchModal = () => {
description="Clone production data into this branch"
>
<FormControl_Shadcn_>
{hasPitrEnabled && (
<Switch checked={field.value} onCheckedChange={field.onChange} />
)}
<Switch
disabled={!hasPitrEnabled}
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useDatabaseFunctionCreateMutation } from 'data/database-functions/datab
import { DatabaseFunction } from 'data/database-functions/database-functions-query'
import { useDatabaseFunctionUpdateMutation } from 'data/database-functions/database-functions-update-mutation'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useConfirmOnClose, type ConfirmOnCloseModalProps } from 'hooks/ui/useConfirmOnClose'
import { useProtectedSchemas } from 'hooks/useProtectedSchemas'
import type { FormSchema } from 'types'
import {
Expand Down Expand Up @@ -76,7 +77,6 @@ export const CreateFunction = ({
onClose,
}: CreateFunctionProps) => {
const { data: project } = useSelectedProjectQuery()
const [isClosingPanel, setIsClosingPanel] = useState(false)
const [advancedSettingsShown, setAdvancedSettingsShown] = useState(false)
const [focusedEditor, setFocusedEditor] = useState(false)

Expand All @@ -87,15 +87,16 @@ export const CreateFunction = ({
})
const language = form.watch('language')

const { confirmOnClose, modalProps: closeConfirmationModalProps } = useConfirmOnClose({
checkIsDirty: () => form.formState.isDirty,
onClose,
})

const { mutate: createDatabaseFunction, isLoading: isCreating } =
useDatabaseFunctionCreateMutation()
const { mutate: updateDatabaseFunction, isLoading: isUpdating } =
useDatabaseFunctionUpdateMutation()

function isClosingSidePanel() {
form.formState.isDirty ? setIsClosingPanel(true) : onClose()
}

const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (data) => {
if (!project) return console.error('Project is required')
const payload = {
Expand Down Expand Up @@ -156,7 +157,7 @@ export const CreateFunction = ({
const { data: protectedSchemas } = useProtectedSchemas()

return (
<Sheet open={visible} onOpenChange={() => isClosingSidePanel()}>
<Sheet open={visible} onOpenChange={confirmOnClose}>
<SheetContent
showClose={false}
size={'default'}
Expand Down Expand Up @@ -388,7 +389,7 @@ export const CreateFunction = ({
</form>
</Form_Shadcn_>
<SheetFooter>
<Button disabled={isCreating || isUpdating} type="default" onClick={isClosingSidePanel}>
<Button disabled={isCreating || isUpdating} type="default" onClick={confirmOnClose}>
Cancel
</Button>
<Button
Expand All @@ -401,26 +402,27 @@ export const CreateFunction = ({
</Button>
</SheetFooter>
</div>
<ConfirmationModal
visible={isClosingPanel}
title="Discard changes"
confirmLabel="Discard"
onCancel={() => setIsClosingPanel(false)}
onConfirm={() => {
setIsClosingPanel(false)
onClose()
}}
>
<p className="text-sm text-foreground-light">
There are unsaved changes. Are you sure you want to close the panel? Your changes will
be lost.
</p>
</ConfirmationModal>
<CloseConfirmationModal {...closeConfirmationModalProps} />
</SheetContent>
</Sheet>
)
}

const CloseConfirmationModal = ({ visible, onClose, onCancel }: ConfirmOnCloseModalProps) => (
<ConfirmationModal
visible={visible}
title="Discard changes"
confirmLabel="Discard"
onCancel={onCancel}
onConfirm={onClose}
>
<p className="text-sm text-foreground-light">
There are unsaved changes. Are you sure you want to close the panel? Your changes will be
lost.
</p>
</ConfirmationModal>
)

interface FormFieldConfigParamsProps {
readonly?: boolean
}
Expand Down
Loading
Loading