NewPublicationPanel.tsx 5.5 KB

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