ConnectTabContent.tsx133 lines · main
1import { useParams } from 'common'
2import dynamic from 'next/dynamic'
3import { forwardRef, HTMLAttributes, useMemo } from 'react'
4import { cn } from 'ui'
5import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
6import { CopyCallbackContext } from 'ui-patterns/SimpleCodeBlock'
7
8import { getAddons } from '../Billing/Subscription/Subscription.utils'
9import type { projectKeys } from './Connect.types'
10import { getConnectionStrings } from './DatabaseSettings.utils'
11import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
12import { usePgbouncerConfigQuery } from '@/data/database/pgbouncer-config-query'
13import { useSupavisorConfigurationQuery } from '@/data/database/supavisor-configuration-query'
14import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query'
15import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
16import { pluckObjectFields } from '@/lib/helpers'
17import { useTrack } from '@/lib/telemetry/track'
18
19interface ConnectContentTabProps extends HTMLAttributes<HTMLDivElement> {
20 projectKeys: projectKeys
21 filePath: string
22 connectionTab: 'App Frameworks' | 'Mobile Frameworks' | 'ORMs'
23 selectedFrameworkOrTool: string
24 connectionStringPooler?: {
25 transactionShared: string
26 sessionShared: string
27 transactionDedicated?: string
28 sessionDedicated?: string
29 ipv4SupportedForDedicatedPooler: boolean
30 direct?: string
31 }
32}
33
34export const ConnectTabContent = forwardRef<HTMLDivElement, ConnectContentTabProps>(
35 ({ projectKeys, filePath, connectionTab, selectedFrameworkOrTool, ...props }, ref) => {
36 const { ref: projectRef } = useParams()
37 const track = useTrack()
38 const { hasAccess: allowPgBouncerSelection } = useCheckEntitlements('dedicated_pooler')
39
40 const handleCopy = () => {
41 const trackingProperties: {
42 connectionTab: 'App Frameworks' | 'Mobile Frameworks' | 'ORMs'
43 selectedItem: string
44 connectionType?: string
45 lang?: string
46 } = {
47 connectionTab,
48 selectedItem: selectedFrameworkOrTool,
49 }
50
51 // Only include connectionType and lang for App Frameworks and Mobile Frameworks
52 if (connectionTab !== 'ORMs') {
53 trackingProperties.connectionType = 'Framework snippet'
54 trackingProperties.lang = filePath.split('/').pop() ?? 'unknown'
55 }
56
57 track('connection_string_copied', trackingProperties)
58 }
59
60 const { data: settings } = useProjectSettingsV2Query({ projectRef })
61 const { data: pgbouncerConfig } = usePgbouncerConfigQuery({ projectRef })
62 const { data: supavisorConfig } = useSupavisorConfigurationQuery({ projectRef })
63 const { data: addons } = useProjectAddonsQuery({ projectRef })
64 const { ipv4: ipv4Addon } = getAddons(addons?.selected_addons ?? [])
65
66 const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
67 const emptyState = { db_user: '', db_host: '', db_port: '', db_name: '' }
68 const connectionInfo = pluckObjectFields(settings || emptyState, DB_FIELDS)
69 const poolingConfigurationShared = supavisorConfig?.find((x) => x.database_type === 'PRIMARY')
70 const poolingConfigurationDedicated = allowPgBouncerSelection ? pgbouncerConfig : undefined
71
72 const connectionStringsShared = getConnectionStrings({
73 connectionInfo,
74 poolingInfo: {
75 connectionString: poolingConfigurationShared?.connection_string ?? '',
76 db_host: poolingConfigurationShared?.db_host ?? '',
77 db_name: poolingConfigurationShared?.db_name ?? '',
78 db_port: poolingConfigurationShared?.db_port ?? 0,
79 db_user: poolingConfigurationShared?.db_user ?? '',
80 },
81 metadata: { projectRef },
82 })
83
84 const connectionStringsDedicated =
85 poolingConfigurationDedicated !== undefined
86 ? getConnectionStrings({
87 connectionInfo,
88 poolingInfo: {
89 connectionString: poolingConfigurationDedicated.connection_string,
90 db_host: poolingConfigurationDedicated.db_host,
91 db_name: poolingConfigurationDedicated.db_name,
92 db_port: poolingConfigurationDedicated.db_port,
93 db_user: poolingConfigurationDedicated.db_user,
94 },
95 metadata: { projectRef },
96 })
97 : undefined
98
99 const ContentFile = useMemo(() => {
100 return dynamic<ConnectContentTabProps>(() => import(`./content/${filePath}/content`), {
101 loading: () => (
102 <div className="p-4 min-h-[331px]">
103 <GenericSkeletonLoader />
104 </div>
105 ),
106 })
107 }, [filePath])
108
109 return (
110 <div ref={ref} {...props} className={cn('border rounded-lg', props.className)}>
111 <CopyCallbackContext.Provider value={handleCopy}>
112 <ContentFile
113 projectKeys={projectKeys}
114 filePath={filePath}
115 connectionTab={connectionTab}
116 selectedFrameworkOrTool={selectedFrameworkOrTool}
117 connectionStringPooler={{
118 transactionShared: connectionStringsShared.pooler.uri,
119 sessionShared: connectionStringsShared.pooler.uri.replace('6543', '5432'),
120 transactionDedicated: connectionStringsDedicated?.pooler.uri,
121 sessionDedicated: connectionStringsDedicated?.pooler.uri.replace('6543', '5432'),
122 ipv4SupportedForDedicatedPooler: !!ipv4Addon,
123 direct: connectionStringsShared.direct.uri,
124 }}
125 onCopy={handleCopy}
126 />
127 </CopyCallbackContext.Provider>
128 </div>
129 )
130 }
131)
132
133ConnectTabContent.displayName = 'ConnectTabContent'