ConnectSheet.tsx 6.6 KB

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