ConnectSheet.tsx200 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { useParams } from 'common'
3import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs'
4import { useEffect, useMemo, useRef } from 'react'
5import { cn, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from 'ui'
6
7import type { ConnectMode, ProjectKeys } from './Connect.types'
8import { CONNECT_MODES } from './Connect.types'
9import { ConnectConfigSection, ModeSelector } from './ConnectConfigSection'
10import { ConnectStepsSection } from './ConnectStepsSection'
11import { useAvailableConnectModes } from './useAvailableConnectModes'
12import { useConnectState } from './useConnectState'
13import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
14import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
15import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
16import { useTrack } from '@/lib/telemetry/track'
17import { useAppStateSnapshot } from '@/state/app-state'
18
19function isConnectMode(value: string): value is ConnectMode {
20 return CONNECT_MODES.some((mode) => mode === value)
21}
22
23function mapConnectTabToMode(tab: string | null): ConnectMode | null {
24 if (!tab) return null
25 switch (tab) {
26 case 'frameworks':
27 case 'mobiles':
28 return 'framework'
29 case 'orms':
30 return 'orm'
31 default:
32 return isConnectMode(tab) ? tab : null
33 }
34}
35
36export const ConnectSheet = () => {
37 const { ref: projectRef } = useParams()
38
39 const availableModeIds = useAvailableConnectModes()
40
41 const [showConnect, setShowConnect] = useQueryState(
42 'showConnect',
43 parseAsBoolean.withDefault(false)
44 )
45 const [connectTab, setConnectTab] = useQueryState('connectTab', parseAsString)
46 const [queryFramework, setQueryFramework] = useQueryState('framework', parseAsString)
47 const [queryUsing, setQueryUsing] = useQueryState('using', parseAsString)
48 const [queryMethod, setQueryMethod] = useQueryState('method', parseAsString)
49 const [queryType, setQueryType] = useQueryState('type', parseAsString)
50 const [queryMcpClient, setQueryMcpClient] = useQueryState('mcpClient', parseAsString)
51 const { connectSheetSource, setConnectSheetSource } = useAppStateSnapshot()
52 const track = useTrack()
53 const prevShowConnect = useRef(false)
54
55 const { state, activeFields, resolvedSteps, schema, getFieldOptions, setMode, updateField } =
56 useConnectState()
57
58 useEffect(() => {
59 const justOpened = showConnect && !prevShowConnect.current
60 prevShowConnect.current = showConnect
61
62 if (!justOpened) return
63
64 track('connect_sheet_opened', { source: connectSheetSource })
65 setConnectSheetSource('header_button')
66
67 const mappedMode = mapConnectTabToMode(connectTab)
68 if (mappedMode && availableModeIds.includes(mappedMode)) {
69 setMode(mappedMode)
70 }
71
72 if (mappedMode === 'framework') {
73 if (queryFramework) {
74 updateField('framework', queryFramework)
75 if (queryUsing) updateField('frameworkVariant', queryUsing)
76 }
77 } else if (mappedMode === 'orm') {
78 if (queryFramework) updateField('orm', queryFramework)
79 } else if (mappedMode === 'direct') {
80 if (queryMethod) updateField('connectionMethod', queryMethod)
81 if (queryType) updateField('connectionType', queryType)
82 } else if (mappedMode === 'mcp') {
83 if (queryMcpClient) updateField('mcpClient', queryMcpClient)
84 }
85 }, [
86 showConnect,
87 connectSheetSource,
88 connectTab,
89 queryFramework,
90 queryUsing,
91 queryMethod,
92 queryType,
93 queryMcpClient,
94 availableModeIds,
95 track,
96 setConnectSheetSource,
97 setMode,
98 updateField,
99 ])
100
101 const clearAllQueryParams = () => {
102 setConnectTab(null)
103 setQueryFramework(null)
104 setQueryUsing(null)
105 setQueryMethod(null)
106 setQueryType(null)
107 setQueryMcpClient(null)
108 }
109
110 const handleOpenChange = (sheetOpen: boolean) => {
111 if (!sheetOpen) clearAllQueryParams()
112 setShowConnect(sheetOpen)
113 }
114
115 const { data: endpoint = '' } = useProjectApiUrl({ projectRef }, { enabled: showConnect })
116
117 const { can: canReadAPIKeys } = useAsyncCheckPermissions(
118 PermissionAction.READ,
119 'service_api_keys'
120 )
121 const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys })
122 const { anonKey, publishableKey } = canReadAPIKeys
123 ? getKeys(apiKeys)
124 : { anonKey: null, publishableKey: null }
125
126 const projectKeys: ProjectKeys = useMemo(() => {
127 return {
128 apiUrl: endpoint,
129 anonKey: anonKey?.api_key ?? null,
130 publishableKey: publishableKey?.api_key ?? null,
131 }
132 }, [endpoint, anonKey?.api_key, publishableKey?.api_key])
133
134 const availableModes = useMemo(
135 () => schema.modes.filter((m) => availableModeIds.includes(m.id)),
136 [schema.modes, availableModeIds]
137 )
138
139 const handleModeChange = (mode: ConnectMode) => {
140 setMode(mode)
141 setConnectTab(mode)
142 setQueryFramework(null)
143 setQueryUsing(null)
144 setQueryMethod(null)
145 setQueryType(null)
146 setQueryMcpClient(null)
147 }
148
149 const handleFieldChange = (fieldId: string, value: string | boolean | string[]) => {
150 updateField(fieldId, value)
151 const str = String(value)
152 if (fieldId === 'framework') {
153 setQueryFramework(str)
154 setQueryUsing(null)
155 } else if (fieldId === 'frameworkVariant') {
156 setQueryUsing(str)
157 } else if (fieldId === 'orm') {
158 setQueryFramework(str)
159 } else if (fieldId === 'connectionMethod') {
160 setQueryMethod(str)
161 setQueryType(null)
162 } else if (fieldId === 'connectionType') {
163 setQueryType(str)
164 } else if (fieldId === 'mcpClient') {
165 setQueryMcpClient(str)
166 }
167 }
168
169 return (
170 <Sheet open={showConnect} onOpenChange={handleOpenChange}>
171 <SheetContent size="lg" className="flex flex-col gap-0 p-0 space-y-0" tabIndex={undefined}>
172 <SheetHeader className={cn('text-left border-b shrink-0 py-6 px-8')}>
173 <SheetTitle>Connect to your project</SheetTitle>
174 <SheetDescription>Choose how you want to use Briven</SheetDescription>
175 </SheetHeader>
176
177 <div className="flex flex-1 flex-col overflow-y-auto divide-y">
178 <div className="p-8">
179 <ModeSelector
180 modes={availableModes}
181 selected={state.mode}
182 onChange={handleModeChange}
183 />
184 </div>
185
186 <div className="border-b p-8">
187 <ConnectConfigSection
188 state={state}
189 activeFields={activeFields}
190 onFieldChange={handleFieldChange}
191 getFieldOptions={getFieldOptions}
192 />
193 </div>
194
195 <ConnectStepsSection steps={resolvedSteps} state={state} projectKeys={projectKeys} />
196 </div>
197 </SheetContent>
198 </Sheet>
199 )
200}