DestinationPanel.tsx166 lines · main
1import { ArrowUpRight } from 'lucide-react'
2import Link from 'next/link'
3import { parseAsInteger, parseAsStringEnum, useQueryState } from 'nuqs'
4import { useEffect } from 'react'
5import { toast } from 'sonner'
6import {
7 Button,
8 cn,
9 DialogSectionSeparator,
10 Sheet,
11 SheetContent,
12 SheetDescription,
13 SheetHeader,
14 SheetSection,
15 SheetTitle,
16} from 'ui'
17
18import { EnableReplicationCallout } from '../EnableReplicationCallout'
19import { PipelineStatusName } from '../Replication.constants'
20import { useDestinationInformation } from '../useDestinationInformation'
21import { useIsETLPrivateAlpha } from '../useIsETLPrivateAlpha'
22import { DestinationForm } from './DestinationForm'
23import { DestinationType } from './DestinationPanel.types'
24import { DestinationTypeSelection } from './DestinationTypeSelection'
25import { ReadReplicaForm } from './ReadReplicaForm'
26import { DocsButton } from '@/components/ui/DocsButton'
27import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
28import { DOCS_URL } from '@/lib/constants'
29
30interface DestinationPanelProps {
31 onSuccessCreateReadReplica?: () => void
32}
33
34export const DestinationPanel = ({ onSuccessCreateReadReplica }: DestinationPanelProps) => {
35 const enablePgReplicate = useIsETLPrivateAlpha()
36 const { hasAccess: hasETLReplicationAccess } = useCheckEntitlements('replication.etl')
37
38 const [urlDestinationType, setDestinationType] = useQueryState(
39 'destinationType',
40 parseAsStringEnum<DestinationType>([
41 'Read Replica',
42 'BigQuery',
43 'Analytics Bucket',
44 'DuckLake',
45 ]).withOptions({
46 history: 'push',
47 clearOnDefault: true,
48 })
49 )
50
51 const [edit, setEdit] = useQueryState(
52 'edit',
53 parseAsInteger.withOptions({
54 history: 'push',
55 clearOnDefault: true,
56 })
57 )
58
59 const visible = urlDestinationType !== null || edit !== null
60 const editMode = edit !== null
61
62 const {
63 sourceId,
64 pipeline,
65 statusName,
66 replicationNotEnabled,
67 type: existingDestinationType,
68 destinationFetcher,
69 } = useDestinationInformation({ id: edit })
70 const destinationType = existingDestinationType ?? urlDestinationType
71 const invalidExistingDestination = destinationFetcher.error?.code === 404
72
73 const existingDestination = editMode
74 ? {
75 sourceId,
76 destinationId: edit,
77 pipelineId: pipeline?.id,
78 statusName,
79 enabled:
80 statusName === PipelineStatusName.STARTED || statusName === PipelineStatusName.FAILED,
81 }
82 : undefined
83
84 const onClose = () => {
85 setDestinationType(null)
86 setEdit(null)
87 }
88
89 useEffect(() => {
90 if (edit !== null && invalidExistingDestination) {
91 toast(`Unable to find destination ID ${edit}`)
92 setEdit(null)
93 }
94 }, [edit, invalidExistingDestination, setEdit])
95
96 return (
97 <>
98 <Sheet open={visible} onOpenChange={onClose}>
99 <SheetContent size="default" showClose={false} className="md:w-[850px]!">
100 <div className="flex flex-col h-full" tabIndex={-1}>
101 <SheetHeader>
102 <SheetTitle>{editMode ? 'Edit destination' : 'Create a new destination'}</SheetTitle>
103 <SheetDescription>
104 {editMode
105 ? 'Update the configuration for this destination'
106 : 'A destination can be a read replica or an external platform that receives your database changes in real time.'}
107 </SheetDescription>
108 </SheetHeader>
109
110 <DestinationTypeSelection />
111
112 <DialogSectionSeparator />
113
114 {destinationType === 'Read Replica' ? (
115 <ReadReplicaForm onClose={onClose} onSuccess={() => onSuccessCreateReadReplica?.()} />
116 ) : !enablePgReplicate ? (
117 <SheetSection>
118 <div className={cn('border rounded-md p-6 flex flex-col gap-y-4')}>
119 <div className="flex flex-col gap-y-1">
120 <h4>Replicate data to external destinations in real-time</h4>
121 <p className="text-sm text-foreground-light">
122 We are currently in <span className="text-foreground">private alpha</span> and
123 slowly onboarding new customers to ensure stable data pipelines. Request
124 access below to join the waitlist. Read replicas are available now.
125 </p>
126 </div>
127 <div className="flex gap-x-2">
128 <Button
129 asChild
130 type="secondary"
131 iconRight={<ArrowUpRight size={16} strokeWidth={1.5} />}
132 >
133 <Link
134 href="https://forms.supabase.com/pg_replicate"
135 target="_blank"
136 rel="noreferrer"
137 >
138 Request alpha access
139 </Link>
140 </Button>
141 <DocsButton href={`${DOCS_URL}/guides/database/replication#replication`} />
142 </div>
143 </div>
144 </SheetSection>
145 ) : replicationNotEnabled ? (
146 <SheetSection>
147 <EnableReplicationCallout
148 className="p-6!"
149 type={destinationType}
150 hasAccess={hasETLReplicationAccess}
151 />
152 </SheetSection>
153 ) : (
154 <DestinationForm
155 visible={visible}
156 selectedType={destinationType ?? 'Read Replica'}
157 existingDestination={existingDestination}
158 onClose={onClose}
159 />
160 )}
161 </div>
162 </SheetContent>
163 </Sheet>
164 </>
165 )
166}