mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 22:18:00 +08:00
## Context Adds an admonition in the Connect sheet to inform users about the IPv4 addon if direct connection is selected and project doesn't have the IPv4 addon Decided to place it below the copy prompt CTA since it's technically a secondary action (users with IPv6 networks wouldn't need this) <img width="755" height="707" alt="image" src="https://github.com/user-attachments/assets/f1d29a56-db5f-4807-9545-a862434fea8f" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Displays contextual guidance in direct connection mode when the IPv4 add-on is not enabled, including quick-access links to configure IPv4 settings and to open IPv4 documentation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
216 lines
7.7 KiB
TypeScript
216 lines
7.7 KiB
TypeScript
import { useParams } from 'common'
|
|
import dynamic from 'next/dynamic'
|
|
import Link from 'next/link'
|
|
import { useMemo, useRef } from 'react'
|
|
import { Button } from 'ui'
|
|
import { Admonition } from 'ui-patterns'
|
|
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import type {
|
|
ConnectionStringPooler,
|
|
ConnectState,
|
|
ProjectKeys,
|
|
ResolvedStep,
|
|
StepContentProps,
|
|
} from './Connect.types'
|
|
import { ConnectSheetStep } from './ConnectSheetStep'
|
|
import { CopyPromptAdmonition } from './CopyPromptAdmonition'
|
|
import { getConnectionStrings } from './DatabaseSettings.utils'
|
|
import { getAddons } from '@/components/interfaces/Billing/Subscription/Subscription.utils'
|
|
import { DocsButton } from '@/components/ui/DocsButton'
|
|
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
|
|
import { usePgbouncerConfigQuery } from '@/data/database/pgbouncer-config-query'
|
|
import { useSupavisorConfigurationQuery } from '@/data/database/supavisor-configuration-query'
|
|
import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query'
|
|
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
|
|
import { DOCS_URL } from '@/lib/constants'
|
|
import { pluckObjectFields } from '@/lib/helpers'
|
|
|
|
interface ConnectStepsSectionProps {
|
|
steps: ResolvedStep[]
|
|
state: ConnectState
|
|
projectKeys: ProjectKeys
|
|
}
|
|
|
|
/**
|
|
* Resolves a content path template by replacing {{key}} placeholders with state values.
|
|
* Empty segments are filtered out to handle optional state values like frameworkVariant.
|
|
*
|
|
* Examples:
|
|
* - '{{framework}}/{{frameworkVariant}}/{{library}}' with state {framework: 'nextjs', frameworkVariant: 'app', library: 'supabasejs'}
|
|
* → 'nextjs/app/supabasejs'
|
|
* - '{{orm}}' with state {orm: 'prisma'}
|
|
* → 'prisma'
|
|
* - 'steps/install' (no templates)
|
|
* → 'steps/install'
|
|
*/
|
|
function resolveContentPath(template: string, state: ConnectState): string {
|
|
return template
|
|
.replace(/\{\{(\w+)\}\}/g, (_, key) => String(state[key] ?? ''))
|
|
.split('/')
|
|
.filter(Boolean)
|
|
.join('/')
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch and prepare connection strings for step content.
|
|
*/
|
|
function useConnectionStringPooler(): ConnectionStringPooler {
|
|
const { ref: projectRef } = useParams()
|
|
const { hasAccess: allowPgBouncerSelection } = useCheckEntitlements('dedicated_pooler')
|
|
|
|
const { data: settings } = useProjectSettingsV2Query({ projectRef })
|
|
const { data: pgbouncerConfig } = usePgbouncerConfigQuery({ projectRef })
|
|
const { data: supavisorConfig } = useSupavisorConfigurationQuery({ projectRef })
|
|
const { data: addons } = useProjectAddonsQuery({ projectRef })
|
|
const { ipv4: ipv4Addon } = getAddons(addons?.selected_addons ?? [])
|
|
|
|
const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
|
|
const emptyState = { db_user: '', db_host: '', db_port: '', db_name: '' }
|
|
const connectionInfo = pluckObjectFields(settings || emptyState, DB_FIELDS)
|
|
const poolingConfigurationShared = supavisorConfig?.find((x) => x.database_type === 'PRIMARY')
|
|
const poolingConfigurationDedicated = allowPgBouncerSelection ? pgbouncerConfig : undefined
|
|
|
|
const connectionStringsShared = getConnectionStrings({
|
|
connectionInfo,
|
|
poolingInfo: {
|
|
connectionString: poolingConfigurationShared?.connection_string ?? '',
|
|
db_host: poolingConfigurationShared?.db_host ?? '',
|
|
db_name: poolingConfigurationShared?.db_name ?? '',
|
|
db_port: poolingConfigurationShared?.db_port ?? 0,
|
|
db_user: poolingConfigurationShared?.db_user ?? '',
|
|
},
|
|
metadata: { projectRef },
|
|
})
|
|
|
|
const connectionStringsDedicated =
|
|
poolingConfigurationDedicated !== undefined
|
|
? getConnectionStrings({
|
|
connectionInfo,
|
|
poolingInfo: {
|
|
connectionString: poolingConfigurationDedicated.connection_string,
|
|
db_host: poolingConfigurationDedicated.db_host,
|
|
db_name: poolingConfigurationDedicated.db_name,
|
|
db_port: poolingConfigurationDedicated.db_port,
|
|
db_user: poolingConfigurationDedicated.db_user,
|
|
},
|
|
metadata: { projectRef },
|
|
})
|
|
: undefined
|
|
|
|
return useMemo(
|
|
() => ({
|
|
transactionShared: connectionStringsShared.pooler.uri,
|
|
sessionShared: connectionStringsShared.pooler.uri.replace('6543', '5432'),
|
|
transactionDedicated: connectionStringsDedicated?.pooler.uri,
|
|
sessionDedicated: connectionStringsDedicated?.pooler.uri.replace('6543', '5432'),
|
|
ipv4SupportedForDedicatedPooler: !!ipv4Addon,
|
|
direct: connectionStringsShared.direct.uri,
|
|
}),
|
|
[connectionStringsShared, connectionStringsDedicated, ipv4Addon]
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Dynamically loads and renders a content component from the content directory.
|
|
* All step content uses this unified loader - no built-in component registry needed.
|
|
*/
|
|
function StepContent({
|
|
contentId,
|
|
state,
|
|
projectKeys,
|
|
connectionStringPooler,
|
|
}: {
|
|
contentId: string
|
|
state: ConnectState
|
|
projectKeys: ProjectKeys
|
|
connectionStringPooler: ConnectionStringPooler
|
|
}) {
|
|
// Resolve any template placeholders in the content path
|
|
const filePath = useMemo(() => resolveContentPath(contentId, state), [contentId, state])
|
|
|
|
// Dynamically import the content component
|
|
const ContentComponent = useMemo(() => {
|
|
return dynamic<StepContentProps>(() => import(`./content/${filePath}/content`), {
|
|
loading: () => (
|
|
<div className="p-4 min-h-[200px]">
|
|
<GenericSkeletonLoader />
|
|
</div>
|
|
),
|
|
})
|
|
}, [filePath])
|
|
|
|
return (
|
|
<ContentComponent
|
|
state={state}
|
|
projectKeys={projectKeys}
|
|
connectionStringPooler={connectionStringPooler}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export function ConnectStepsSection({ steps, state, projectKeys }: ConnectStepsSectionProps) {
|
|
const { ref } = useParams()
|
|
const stepsContainerRef = useRef<HTMLDivElement | null>(null)
|
|
const connectionStringPooler = useConnectionStringPooler()
|
|
|
|
const { data: ipv4Addon } = useProjectAddonsQuery(
|
|
{ projectRef: ref },
|
|
{
|
|
select: (data) => {
|
|
const selectedAddons = data?.selected_addons ?? []
|
|
return selectedAddons.find((addon) => addon.type === 'ipv4')
|
|
},
|
|
}
|
|
)
|
|
const showIpv4AddonNotice =
|
|
state.mode === 'direct' &&
|
|
!ipv4Addon &&
|
|
(state.connectionMethod === 'direct' ||
|
|
(state.connectionMethod === 'transaction' && !state.useSharedPooler))
|
|
|
|
if (steps.length === 0) return null
|
|
|
|
return (
|
|
<div className="bg-muted/50 flex-1">
|
|
<div className="p-8 flex flex-col gap-y-6">
|
|
<h3>Connect your app</h3>
|
|
|
|
<CopyPromptAdmonition stepsContainerRef={stepsContainerRef} />
|
|
|
|
{showIpv4AddonNotice && (
|
|
<Admonition
|
|
type="default"
|
|
title={`${state.connectionMethod === 'direct' ? 'Direct connections use' : 'Transaction pooler uses'} IPv6 by default`}
|
|
description="Enable the dedicated IPv4 address add-on to connect from IPv4-only networks"
|
|
actions={[
|
|
<Button asChild key="addon" type="default">
|
|
<Link href={`/project/${ref}/settings/addons?panel=ipv4`}>Enable IPv4 add-on</Link>
|
|
</Button>,
|
|
<DocsButton key="docs" href={`${DOCS_URL}/guides/platform/ipv4-address`} />,
|
|
]}
|
|
/>
|
|
)}
|
|
|
|
<div className="mt-6" ref={stepsContainerRef}>
|
|
{steps.map((step, index) => (
|
|
<ConnectSheetStep
|
|
key={step.id}
|
|
number={index + 1}
|
|
title={step.title}
|
|
description={step.description}
|
|
>
|
|
<StepContent
|
|
contentId={step.content}
|
|
state={state}
|
|
projectKeys={projectKeys}
|
|
connectionStringPooler={connectionStringPooler}
|
|
/>
|
|
</ConnectSheetStep>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|