import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { ExternalLink, Plug } from 'lucide-react' import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' import { useEffect, useMemo, useState } from 'react' import { DatabaseConnectionString } from 'components/interfaces/Connect/DatabaseConnectionString' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import Panel from 'components/ui/Panel' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useCustomContent } from 'hooks/custom-content/useCustomContent' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' import { Button, DIALOG_PADDING_X, DIALOG_PADDING_Y, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, TabsContent_Shadcn_, TabsList_Shadcn_, TabsTrigger_Shadcn_, Tabs_Shadcn_, cn, } from 'ui' import { CONNECTION_TYPES, ConnectionType, FRAMEWORKS, MOBILES, ORMS } from './Connect.constants' import { getContentFilePath } from './Connect.utils' import { ConnectDropdown } from './ConnectDropdown' import { ConnectTabContent } from './ConnectTabContent' import { ConnectTabContentCustom } from './ConnectTabContentCustom' export const Connect = () => { const { ref: projectRef } = useParams() const { data: selectedProject } = useSelectedProjectQuery() const isActiveHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY const { connectFrameworks } = useCustomContent(['connect:frameworks']) const connectionTypes = !connectFrameworks ? CONNECTION_TYPES : [ { key: 'direct', label: 'Connection String', obj: [] }, connectFrameworks, { key: 'orms', label: 'ORMs', obj: ORMS }, ] const frameworks = !connectFrameworks ? FRAMEWORKS : connectFrameworks.obj const [showConnect, setShowConnect] = useQueryState( 'showConnect', parseAsBoolean.withDefault(false) ) const [connectTab, setConnectTab] = useQueryState( 'connectTab', parseAsString.withDefault('direct').withOptions({ clearOnDefault: true }) ) const [prefFramework] = useQueryState( 'framework', parseAsString.withDefault('').withOptions({ clearOnDefault: true }) ) const [connectionObject, setConnectionObject] = useState(frameworks) const [selectedParent, setSelectedParent] = useState(connectionObject[0].key) // aka nextjs const [selectedChild, setSelectedChild] = useState( connectionObject.find((item) => item.key === selectedParent)?.children[0]?.key ?? '' ) const [selectedGrandchild, setSelectedGrandchild] = useState( connectionObject .find((item) => item.key === selectedParent) ?.children.find((child) => child.key === selectedChild)?.children[0]?.key || '' ) const isFrameworkSelected = frameworks.some((x) => x.key === selectedParent) const { data: settings } = useProjectSettingsV2Query({ projectRef }, { enabled: showConnect }) const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions( PermissionAction.READ, 'service_api_keys' ) const handleParentChange = (value: string) => { setSelectedParent(value) // check if parent has children setSelectedChild(connectionObject.find((item) => item.key === value)?.children[0]?.key ?? '') // check if child has grandchildren setSelectedGrandchild( connectionObject.find((item) => item.key === value)?.children[0]?.children[0]?.key ?? '' ) } const handleChildChange = (value: string) => { setSelectedChild(value) const parent = connectionObject.find((item) => item.key === selectedParent) const child = parent?.children.find((child) => child.key === value) if (child && child.children.length > 0) { setSelectedGrandchild(child.children[0].key) } else { setSelectedGrandchild('') } } const handleGrandchildChange = (value: string) => { setSelectedGrandchild(value) } // reset the parent/child/grandchild when the connection type (tab) changes function handleConnectionTypeChange(connections: ConnectionType[]) { setSelectedParent(connections[0].key) if (connections[0]?.children.length > 0) { setSelectedChild(connections[0].children[0].key) if (connections[0].children[0]?.children.length > 0) { setSelectedGrandchild(connections[0].children[0].children[0].key) } else { setSelectedGrandchild('') } } else { setSelectedChild('') setSelectedGrandchild('') } } function handleConnectionType(type: string) { if (type === 'frameworks') { setConnectionObject(frameworks) handleConnectionTypeChange(frameworks) } if (type === 'mobiles') { setConnectionObject(MOBILES) handleConnectionTypeChange(MOBILES) } if (type === 'orms') { setConnectionObject(ORMS) handleConnectionTypeChange(ORMS) } } const getChildOptions = () => { const parent = connectionObject.find((item) => item.key === selectedParent) if (parent && parent.children.length > 0) { return parent.children } return [] } const getGrandchildrenOptions = () => { const parent = connectionObject.find((item) => item.key === selectedParent) const subCategory = parent?.children.find((child) => child.key === selectedChild) if (subCategory && subCategory.children.length > 0) { return subCategory.children } return [] } const { data: apiKeys } = useAPIKeysQuery({ projectRef }) const { anonKey, publishableKey } = canReadAPIKeys ? getKeys(apiKeys) : { anonKey: null, publishableKey: null } const projectKeys = useMemo(() => { const protocol = settings?.app_config?.protocol ?? 'https' const endpoint = settings?.app_config?.endpoint ?? '' const apiHost = canReadAPIKeys ? `${protocol}://${endpoint ?? '-'}` : '' return { apiUrl: apiHost ?? null, anonKey: anonKey?.api_key ?? null, publishableKey: publishableKey?.api_key ?? null, } }, [ settings?.app_config?.protocol, settings?.app_config?.endpoint, canReadAPIKeys, anonKey?.api_key, publishableKey?.api_key, ]) const filePath = getContentFilePath({ connectionObject, selectedParent, selectedChild, selectedGrandchild, }) // Sync initial tab and framework from query params useEffect(() => { if (connectTab === 'frameworks') { setConnectionObject(frameworks) handleConnectionTypeChange(frameworks) if (prefFramework) { const exists = frameworks.find((f) => f.key === prefFramework) if (exists) { setSelectedParent(prefFramework) // Reset children/grandchildren based on the new parent const firstChild = exists.children?.[0]?.key ?? '' setSelectedChild(firstChild) const firstGrand = exists.children?.[0]?.children?.[0]?.key ?? '' setSelectedGrandchild(firstGrand) } } } if (connectTab === 'mobiles') { setConnectionObject(MOBILES) handleConnectionTypeChange(MOBILES) } if (connectTab === 'orms') { setConnectionObject(ORMS) handleConnectionTypeChange(ORMS) } }, [connectTab, prefFramework]) if (!isActiveHealthy) { return ( } tooltip={{ content: { side: 'bottom', text: 'Project is currently not active and cannot be connected', }, }} > Connect ) } return ( setShowConnect(!open ? null : open)}> Connect to your project Get the connection strings and environment variables for your app { handleConnectionType(value) setConnectTab(value) }} > {connectionTypes.map((type) => ( {type.label} ))} {connectionTypes.map((type) => { const hasChildOptions = (connectionObject.find((parent) => parent.key === selectedParent)?.children.length || 0) > 0 const hasGrandChildOptions = (connectionObject .find((parent) => parent.key === selectedParent) ?.children.find((child) => child.key === selectedChild)?.children.length || 0) > 0 if (type.key === 'direct') { return (
) } return (
{selectedParent && hasChildOptions && ( )} {selectedChild && hasGrandChildOptions && ( )}
{connectionObject.find((item) => item.key === selectedParent)?.guideLink && ( )}

Add the following files below to your application

{!!connectFrameworks && isFrameworkSelected ? ( x.key === selectedParent)} /> ) : ( )}
) })}
) }