wip: refactored create queue sheet

This commit is contained in:
Ali Waseem committed 2026-03-30 11:40:07 -06:00
1 parent bf8b49fc9e
commit 30eb3ac4ef
9 files changed
+429 -365

No files matched your search

@@ -1,365 +0,0 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { EnableExtensionModal } from 'components/interfaces/Database/Extensions/EnableExtensionModal'
import { Markdown } from 'components/interfaces/Markdown'
import { DiscardChangesConfirmationDialog } from 'components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import { useDatabaseQueueCreateMutation } from 'data/database-queues/database-queues-create-mutation'
import { useQueuesExposePostgrestStatusQuery } from 'data/database-queues/database-queues-expose-postgrest-status-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useConfirmOnClose } from 'hooks/ui/useConfirmOnClose'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
Badge,
Button,
Checkbox_Shadcn_,
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
Input,
Input_Shadcn_,
RadioGroupStacked,
RadioGroupStackedItem,
Separator,
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetSection,
SheetTitle,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import z from 'zod'
import { QUEUE_TYPES } from './Queues.constants'
import { QueryNameSchema } from './Queues.utils'
import { usePgPartmanStatus } from './usePgPartmanStatus'
export interface CreateQueueSheetProps {
visible: boolean
onClose: () => void
}
const normalQueueSchema = z.object({
type: z.literal('basic'),
})
const partitionedQueueSchema = z.object({
type: z.literal('partitioned'),
partitionInterval: z.coerce.number(),
retentionInterval: z.coerce.number(),
})
const unloggedQueueSchema = z.object({
type: z.literal('unlogged'),
})
const FormSchema = z.object({
name: QueryNameSchema,
enableRls: z.boolean(),
values: z.discriminatedUnion('type', [
normalQueueSchema,
partitionedQueueSchema,
unloggedQueueSchema,
]),
})
export type CreateQueueForm = z.infer<typeof FormSchema>
export type QueueType = CreateQueueForm['values']
const FORM_ID = 'create-queue-sidepanel'
export const CreateQueueSheet = ({ visible, onClose }: CreateQueueSheetProps) => {
const router = useRouter()
const { data: project } = useSelectedProjectQuery()
const { data: isExposed } = useQueuesExposePostgrestStatusQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const { mutate: createQueue, isPending } = useDatabaseQueueCreateMutation()
const {
pgPartmanExtension,
isAvailable: pgPartmanAvailable,
isInstalled: pgPartmanInstalled,
} = usePgPartmanStatus()
const defaultValues: CreateQueueForm = pgPartmanInstalled
? {
name: '',
enableRls: true,
values: { type: 'partitioned', partitionInterval: 0, retentionInterval: 0 },
}
: { name: '', enableRls: true, values: { type: 'basic' } }
const form = useForm<CreateQueueForm>({
resolver: zodResolver(FormSchema),
defaultValues,
})
useEffect(() => {
if (visible) {
form.reset(defaultValues)
}
}, [
form,
pgPartmanInstalled,
// end of stable references
visible,
])
const checkIsDirty = () => form.formState.isDirty
const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
checkIsDirty,
onClose,
})
const onSubmit: SubmitHandler<CreateQueueForm> = async ({ name, enableRls, values }) => {
createQueue(
{
projectRef: project!.ref,
connectionString: project?.connectionString,
name,
enableRls,
type: values.type,
configuration:
values.type === 'partitioned'
? {
partitionInterval: values.partitionInterval,
retentionInterval: values.retentionInterval,
}
: undefined,
},
{
onSuccess: () => {
toast.success(`Successfully created queue ${name}`)
router.push(`/project/${project?.ref}/integrations/queues/queues/${name}`)
onClose()
},
}
)
}
const [showEnableExtensionModal, setShowEnableExtensionModal] = useState(false)
const queueType = form.watch('values.type')
return (
<Sheet open={visible} onOpenChange={handleOpenChange}>
<SheetContent size="default" className="w-[35%]" tabIndex={undefined}>
<div className="flex flex-col h-full" tabIndex={-1}>
<SheetHeader>
<SheetTitle>Create a new queue</SheetTitle>
</SheetHeader>
<div className="overflow-auto flex-grow">
<Form_Shadcn_ {...form}>
<form
id={FORM_ID}
className="flex-grow overflow-auto"
onSubmit={form.handleSubmit(onSubmit)}
>
<SheetSection>
<FormField_Shadcn_
control={form.control}
name="name"
render={({ field }) => (
<FormItemLayout label="Name" layout="vertical" className="gap-1 relative">
<FormControl_Shadcn_>
<Input_Shadcn_ {...field} />
</FormControl_Shadcn_>
<span className="text-foreground-lighter text-xs absolute top-0 right-0">
Must be all lowercase letters
</span>
</FormItemLayout>
)}
/>
</SheetSection>
<Separator />
<SheetSection>
<FormField_Shadcn_
control={form.control}
name="values.type"
render={({ field }) => (
<FormItemLayout label="Type" layout="vertical" className="gap-1">
<FormControl_Shadcn_>
<RadioGroupStacked
id="queue_type"
name="queue_type"
value={field.value}
disabled={field.disabled}
onValueChange={field.onChange}
>
{QUEUE_TYPES.filter(
(definition) =>
definition.value !== 'partitioned' || pgPartmanInstalled
).map((definition) => {
const isPartitioned = definition.value === 'partitioned'
return (
<RadioGroupStackedItem
key={definition.value}
id={definition.value}
value={definition.value}
label=""
showIndicator={false}
>
<div className="flex items-start gap-x-5">
<div className="text-foreground">{definition.icon}</div>
<div className="flex flex-col gap-y-1">
<div className="flex items-center gap-x-2">
<p className="text-foreground text-left">
{definition.label}
</p>
{isPartitioned && (
<Badge variant="success">Recommended</Badge>
)}
</div>
<p className="text-foreground-lighter text-left">
{isPartitioned
? 'Automatically manages data retention and improves performance for high-volume queues via pg_partman.'
: definition.description}
</p>
</div>
</div>
</RadioGroupStackedItem>
)
})}
</RadioGroupStacked>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
{pgPartmanAvailable && !pgPartmanInstalled && (
<Admonition
className="my-4"
type="default"
title="Partitioned queues require the pg_partman extension"
description="Enable pg_partman to unlock partitioned queues, which automatically manage data retention and improve performance for high-volume queues."
>
<Button
type="default"
size="tiny"
className="mt-2"
onClick={() => setShowEnableExtensionModal(true)}
>
Enable pg_partman
</Button>
</Admonition>
)}
</SheetSection>
<Separator />
{queueType === 'partitioned' && (
<>
<SheetSection className="flex flex-col gap-3">
<FormField_Shadcn_
control={form.control}
name="values.partitionInterval"
render={({ field: { ref, ...rest } }) => (
<FormItemLayout label="Partition interval" className="gap-1">
<Input
{...rest}
type="number"
placeholder="1000"
actions={<p className="text-foreground-light pr-2">ms</p>}
/>
</FormItemLayout>
)}
/>
<FormField_Shadcn_
control={form.control}
name="values.retentionInterval"
render={({ field: { ref, ...rest } }) => (
<FormItemLayout label="Retention interval" className="gap-1">
<Input
{...rest}
type="number"
placeholder="1000"
actions={<p className="text-foreground-light pr-2">ms</p>}
/>
</FormItemLayout>
)}
/>
</SheetSection>
<Separator />
</>
)}
<SheetSection className="flex flex-col gap-y-2">
<FormField_Shadcn_
control={form.control}
name="enableRls"
render={({ field }) => (
<FormItemLayout
layout="flex"
label={
<div className="flex items-center gap-x-2">
<p>Enable Row Level Security (RLS)</p>
<Badge variant="success">Recommended</Badge>
</div>
}
description="Restrict access to your queue by enabling RLS and writing Postgres policies to control access for each role."
>
<FormControl_Shadcn_>
<Checkbox_Shadcn_
checked={field.value}
onCheckedChange={field.onChange}
disabled={field.disabled || isExposed}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
{!isExposed ? (
<Admonition
type="default"
title="Row Level Security for queues is only relevant if exposure through PostgREST has been enabled"
>
<Markdown
className="[&>p]:!leading-normal"
content={`You may opt to manage your queues via any Supabase client libraries or PostgREST
endpoints by enabling this in the [queues settings](/project/${project?.ref}/integrations/queues/settings).`}
/>
</Admonition>
) : (
<Admonition
type="default"
title="RLS must be enabled as queues are exposed via PostgREST"
description="This is to prevent anonymous access to any of your queues"
/>
)}
</SheetSection>
</form>
</Form_Shadcn_>
</div>
<SheetFooter>
<Button
size="tiny"
type="default"
htmlType="button"
onClick={confirmOnClose}
disabled={isPending}
>
Cancel
</Button>
<Button size="tiny" type="primary" form={FORM_ID} htmlType="submit" loading={isPending}>
Create queue
</Button>
</SheetFooter>
</div>
<DiscardChangesConfirmationDialog {...modalProps} />
{pgPartmanExtension && !pgPartmanInstalled && (
<EnableExtensionModal
visible={showEnableExtensionModal}
extension={pgPartmanExtension}
onCancel={() => setShowEnableExtensionModal(false)}
/>
)}
</SheetContent>
</Sheet>
)
}
@@ -0,0 +1,30 @@
import z from 'zod'
import { QueryNameSchema } from '../Queues.utils'
const normalQueueSchema = z.object({
type: z.literal('basic'),
})
const partitionedQueueSchema = z.object({
type: z.literal('partitioned'),
partitionInterval: z.coerce.number(),
retentionInterval: z.coerce.number(),
})
const unloggedQueueSchema = z.object({
type: z.literal('unlogged'),
})
export const FormSchema = z.object({
name: QueryNameSchema,
enableRls: z.boolean(),
values: z.discriminatedUnion('type', [
normalQueueSchema,
partitionedQueueSchema,
unloggedQueueSchema,
]),
})
export type CreateQueueForm = z.infer<typeof FormSchema>
export type QueueType = CreateQueueForm['values']
@@ -0,0 +1,149 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { DiscardChangesConfirmationDialog } from 'components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import { useDatabaseQueueCreateMutation } from 'data/database-queues/database-queues-create-mutation'
import { useQueuesExposePostgrestStatusQuery } from 'data/database-queues/database-queues-expose-postgrest-status-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useConfirmOnClose } from 'hooks/ui/useConfirmOnClose'
import { useRouter } from 'next/router'
import { useEffect, useMemo } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
Form_Shadcn_,
Separator,
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetTitle,
} from 'ui'
import { usePgPartmanStatus } from '../usePgPartmanStatus'
import { CreateQueueForm, FormSchema } from './CreateQueueSheet.schema'
import { PartitionConfigFields } from './PartitionConfigFields'
import { PgPartmanCallout } from './PgPartmanCallout'
import { QueueNameField } from './QueueNameField'
import { QueueTypeSelector } from './QueueTypeSelector'
import { RlsSection } from './RlsSection'
export interface CreateQueueSheetProps {
visible: boolean
onClose: () => void
}
const FORM_ID = 'create-queue-sidepanel'
export const CreateQueueSheet = ({ visible, onClose }: CreateQueueSheetProps) => {
const router = useRouter()
const { data: project } = useSelectedProjectQuery()
const { data: isExposed } = useQueuesExposePostgrestStatusQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const { mutate: createQueue, isPending } = useDatabaseQueueCreateMutation()
const { isInstalled: pgPartmanInstalled } = usePgPartmanStatus()
const defaultValues: CreateQueueForm = useMemo(
() =>
pgPartmanInstalled
? {
name: '',
enableRls: true,
values: { type: 'partitioned', partitionInterval: 0, retentionInterval: 0 },
}
: { name: '', enableRls: true, values: { type: 'basic' } },
[pgPartmanInstalled]
)
const form = useForm<CreateQueueForm>({
resolver: zodResolver(FormSchema),
defaultValues,
})
useEffect(() => {
if (visible) {
form.reset(defaultValues)
}
}, [form, defaultValues, visible])
const checkIsDirty = () => form.formState.isDirty
const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
checkIsDirty,
onClose,
})
const onSubmit: SubmitHandler<CreateQueueForm> = async ({ name, enableRls, values }) => {
createQueue(
{
projectRef: project!.ref,
connectionString: project?.connectionString,
name,
enableRls,
type: values.type,
configuration:
values.type === 'partitioned'
? {
partitionInterval: values.partitionInterval,
retentionInterval: values.retentionInterval,
}
: undefined,
},
{
onSuccess: () => {
toast.success(`Successfully created queue ${name}`)
router.push(`/project/${project?.ref}/integrations/queues/queues/${name}`)
onClose()
},
}
)
}
return (
<Sheet open={visible} onOpenChange={handleOpenChange}>
<SheetContent size="default" className="w-[35%]" tabIndex={undefined}>
<div className="flex flex-col h-full" tabIndex={-1}>
<SheetHeader>
<SheetTitle>Create a new queue</SheetTitle>
</SheetHeader>
<div className="overflow-auto flex-grow">
<Form_Shadcn_ {...form}>
<form
id={FORM_ID}
className="flex-grow overflow-auto"
onSubmit={form.handleSubmit(onSubmit)}
>
<QueueNameField form={form} />
<Separator />
<PgPartmanCallout />
<QueueTypeSelector form={form} />
<Separator />
<PartitionConfigFields form={form} />
<RlsSection form={form} isExposed={isExposed} projectRef={project?.ref} />
</form>
</Form_Shadcn_>
</div>
<SheetFooter>
<Button
size="tiny"
type="default"
htmlType="button"
onClick={confirmOnClose}
disabled={isPending}
>
Cancel
</Button>
<Button size="tiny" type="primary" form={FORM_ID} htmlType="submit" loading={isPending}>
Create queue
</Button>
</SheetFooter>
</div>
<DiscardChangesConfirmationDialog {...modalProps} />
</SheetContent>
</Sheet>
)
}
@@ -0,0 +1,47 @@
import { UseFormReturn } from 'react-hook-form'
import { FormField_Shadcn_, Input, Separator, SheetSection } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateQueueForm } from './CreateQueueSheet.schema'
export function PartitionConfigFields({ form }: { form: UseFormReturn<CreateQueueForm> }) {
const queueType = form.watch('values.type')
if (queueType !== 'partitioned') return null
return (
<>
<SheetSection className="flex flex-col gap-3">
<FormField_Shadcn_
control={form.control}
name="values.partitionInterval"
render={({ field: { ref, ...rest } }) => (
<FormItemLayout label="Partition interval" className="gap-1">
<Input
{...rest}
type="number"
placeholder="1000"
actions={<p className="text-foreground-light pr-2">ms</p>}
/>
</FormItemLayout>
)}
/>
<FormField_Shadcn_
control={form.control}
name="values.retentionInterval"
render={({ field: { ref, ...rest } }) => (
<FormItemLayout label="Retention interval" className="gap-1">
<Input
{...rest}
type="number"
placeholder="1000"
actions={<p className="text-foreground-light pr-2">ms</p>}
/>
</FormItemLayout>
)}
/>
</SheetSection>
<Separator />
</>
)
}
@@ -0,0 +1,39 @@
import { EnableExtensionModal } from 'components/interfaces/Database/Extensions/EnableExtensionModal'
import { useState } from 'react'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns'
import { usePgPartmanStatus } from '../usePgPartmanStatus'
export function PgPartmanCallout() {
const { pgPartmanExtension, isAvailable, isInstalled } = usePgPartmanStatus()
const [showEnableModal, setShowEnableModal] = useState(false)
if (!isAvailable || isInstalled) return null
return (
<div className="mx-5 my-2">
<Admonition
type="tip"
title="pg_partman is now available"
description="Unlock partitioned queues for automatic data retention, lower storage costs, and faster performance at scale."
>
<Button
type="default"
size="tiny"
className="mt-2"
onClick={() => setShowEnableModal(true)}
>
Enable pg_partman
</Button>
</Admonition>
{pgPartmanExtension && (
<EnableExtensionModal
visible={showEnableModal}
extension={pgPartmanExtension}
onCancel={() => setShowEnableModal(false)}
/>
)}
</div>
)
}
@@ -0,0 +1,26 @@
import { UseFormReturn } from 'react-hook-form'
import { FormControl_Shadcn_, FormField_Shadcn_, Input_Shadcn_, SheetSection } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateQueueForm } from './CreateQueueSheet.schema'
export function QueueNameField({ form }: { form: UseFormReturn<CreateQueueForm> }) {
return (
<SheetSection>
<FormField_Shadcn_
control={form.control}
name="name"
render={({ field }) => (
<FormItemLayout label="Name" layout="vertical" className="gap-1 relative">
<FormControl_Shadcn_>
<Input_Shadcn_ {...field} />
</FormControl_Shadcn_>
<span className="text-foreground-lighter text-xs absolute top-0 right-0">
Must be all lowercase letters
</span>
</FormItemLayout>
)}
/>
</SheetSection>
)
}
@@ -0,0 +1,71 @@
import { UseFormReturn } from 'react-hook-form'
import {
Badge,
FormControl_Shadcn_,
FormField_Shadcn_,
RadioGroupStacked,
RadioGroupStackedItem,
SheetSection,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { QUEUE_TYPES } from '../Queues.constants'
import { usePgPartmanStatus } from '../usePgPartmanStatus'
import { CreateQueueForm } from './CreateQueueSheet.schema'
export function QueueTypeSelector({ form }: { form: UseFormReturn<CreateQueueForm> }) {
const { isInstalled } = usePgPartmanStatus()
return (
<SheetSection>
<FormField_Shadcn_
control={form.control}
name="values.type"
render={({ field }) => (
<FormItemLayout label="Type" layout="vertical" className="gap-1">
<FormControl_Shadcn_>
<RadioGroupStacked
id="queue_type"
name="queue_type"
value={field.value}
disabled={field.disabled}
onValueChange={field.onChange}
>
{QUEUE_TYPES.filter(
(definition) => definition.value !== 'partitioned' || isInstalled
).map((definition) => {
const isPartitioned = definition.value === 'partitioned'
return (
<RadioGroupStackedItem
key={definition.value}
id={definition.value}
value={definition.value}
label=""
showIndicator={false}
>
<div className="flex items-start gap-x-5">
<div className="text-foreground">{definition.icon}</div>
<div className="flex flex-col gap-y-1">
<div className="flex items-center gap-x-2">
<p className="text-foreground text-left">{definition.label}</p>
{isPartitioned && <Badge variant="success">Recommended</Badge>}
</div>
<p className="text-foreground-lighter text-left">
{isPartitioned
? 'Automatically manages data retention and improves performance for high-volume queues via pg_partman.'
: definition.description}
</p>
</div>
</div>
</RadioGroupStackedItem>
)
})}
</RadioGroupStacked>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</SheetSection>
)
}
@@ -0,0 +1,64 @@
import { Markdown } from 'components/interfaces/Markdown'
import { UseFormReturn } from 'react-hook-form'
import { Badge, Checkbox_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, SheetSection } from 'ui'
import { Admonition } from 'ui-patterns'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateQueueForm } from './CreateQueueSheet.schema'
export function RlsSection({
form,
isExposed,
projectRef,
}: {
form: UseFormReturn<CreateQueueForm>
isExposed: boolean | undefined
projectRef: string | undefined
}) {
return (
<SheetSection className="flex flex-col gap-y-2">
<FormField_Shadcn_
control={form.control}
name="enableRls"
render={({ field }) => (
<FormItemLayout
layout="flex"
label={
<div className="flex items-center gap-x-2">
<p>Enable Row Level Security (RLS)</p>
<Badge variant="success">Recommended</Badge>
</div>
}
description="Restrict access to your queue by enabling RLS and writing Postgres policies to control access for each role."
>
<FormControl_Shadcn_>
<Checkbox_Shadcn_
checked={field.value}
onCheckedChange={field.onChange}
disabled={field.disabled || isExposed}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
{!isExposed ? (
<Admonition
type="default"
title="Row Level Security for queues is only relevant if exposure through PostgREST has been enabled"
>
<Markdown
className="[&>p]:!leading-normal"
content={`You may opt to manage your queues via any Supabase client libraries or PostgREST
endpoints by enabling this in the [queues settings](/project/${projectRef}/integrations/queues/settings).`}
/>
</Admonition>
) : (
<Admonition
type="default"
title="RLS must be enabled as queues are exposed via PostgREST"
description="This is to prevent anonymous access to any of your queues"
/>
)}
</SheetSection>
)
}
@@ -0,0 +1,3 @@
export { CreateQueueSheet } from './CreateQueueSheet'
export type { CreateQueueSheetProps } from './CreateQueueSheet'
export type { CreateQueueForm, QueueType } from './CreateQueueSheet.schema'