NewPublicationPanel.tsx162 lines · main
1import { zodResolver } from '@hookform/resolvers/zod'
2import { useParams } from 'common'
3import { useForm } from 'react-hook-form'
4import { toast } from 'sonner'
5import {
6 Button,
7 Form,
8 FormControl,
9 FormField,
10 Input,
11 Sheet,
12 SheetContent,
13 SheetDescription,
14 SheetFooter,
15 SheetHeader,
16 SheetSection,
17 SheetTitle,
18} from 'ui'
19import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
20import { MultiSelector } from 'ui-patterns/multi-select'
21import { z } from 'zod'
22
23import { useCreatePublicationMutation } from '@/data/replication/publication-create-mutation'
24import { useReplicationTablesQuery } from '@/data/replication/tables-query'
25import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
26
27interface NewPublicationPanelProps {
28 visible: boolean
29 sourceId?: number
30 onClose: () => void
31}
32
33export const NewPublicationPanel = ({ visible, sourceId, onClose }: NewPublicationPanelProps) => {
34 const { ref: projectRef } = useParams()
35 const { data: project } = useSelectedProjectQuery()
36
37 const { data: tables } = useReplicationTablesQuery({ projectRef, sourceId })
38
39 const { mutate: createPublication, isPending: creatingPublication } =
40 useCreatePublicationMutation({
41 onSuccess: () => {
42 toast.success('Successfully created publication')
43 form.reset(defaultValues)
44 onClose()
45 },
46 })
47
48 const formId = 'publication-editor'
49 const FormSchema = z.object({
50 name: z.string().min(1, 'Name is required'),
51 tables: z.array(z.string()).min(1, 'At least one table is required'),
52 })
53 const defaultValues = {
54 name: '',
55 tables: [],
56 }
57 const form = useForm<z.infer<typeof FormSchema>>({
58 mode: 'onBlur',
59 reValidateMode: 'onBlur',
60 resolver: zodResolver(FormSchema as any),
61 defaultValues,
62 })
63
64 const onSubmit = async (data: z.infer<typeof FormSchema>) => {
65 if (!projectRef) return console.error('Project ref is required')
66 if (!project) return console.error('Project is required')
67 if (!sourceId) return console.error('Source id is required')
68
69 const tables = data.tables.map((table) => {
70 const [schema, name] = table.split('.')
71 return { schema, name }
72 })
73
74 createPublication({
75 projectRef,
76 sourceId,
77 name: data.name,
78 tables,
79 connectionString: project.connectionString,
80 })
81 }
82
83 return (
84 <>
85 <Sheet open={visible} onOpenChange={onClose}>
86 <SheetContent size="default">
87 <div className="flex flex-col h-full">
88 <SheetHeader>
89 <SheetTitle>Create a new Publication</SheetTitle>
90 <SheetDescription>Replicate table changes to destinations</SheetDescription>
91 </SheetHeader>
92 <SheetSection className="grow overflow-auto">
93 <Form {...form}>
94 <form
95 id={formId}
96 onSubmit={form.handleSubmit(onSubmit)}
97 className="flex flex-col gap-y-4"
98 >
99 <FormField
100 control={form.control}
101 name="name"
102 render={({ field }) => (
103 <FormItemLayout label="Name" layout="vertical">
104 <FormControl>
105 <Input {...field} placeholder="Name" />
106 </FormControl>
107 </FormItemLayout>
108 )}
109 />
110 <FormField
111 control={form.control}
112 name="tables"
113 render={({ field }) => (
114 <FormItemLayout
115 label="Tables"
116 description="Which tables to replicate to destinations"
117 >
118 <FormControl>
119 <MultiSelector
120 values={field.value}
121 onValuesChange={field.onChange}
122 disabled={creatingPublication}
123 >
124 <MultiSelector.Trigger
125 badgeLimit="wrap"
126 label="Select tables..."
127 mode="inline-combobox"
128 />
129 <MultiSelector.Content>
130 <MultiSelector.List>
131 {tables?.map((table) => (
132 <MultiSelector.Item
133 key={`${table.schema}.${table.name}`}
134 value={`${table.schema}.${table.name}`}
135 >
136 {`${table.schema}.${table.name}`}
137 </MultiSelector.Item>
138 ))}
139 </MultiSelector.List>
140 </MultiSelector.Content>
141 </MultiSelector>
142 </FormControl>
143 </FormItemLayout>
144 )}
145 />
146 </form>
147 </Form>
148 </SheetSection>
149 <SheetFooter>
150 <Button type="default" disabled={creatingPublication} onClick={onClose}>
151 Cancel
152 </Button>
153 <Button type="primary" disabled={creatingPublication} form={formId} htmlType="submit">
154 Create publication
155 </Button>
156 </SheetFooter>
157 </div>
158 </SheetContent>
159 </Sheet>
160 </>
161 )
162}