mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 03:15:06 +03:00
## Context PR here mainly breaks up the files under `ConnectSheet` to separate the functional logic so that we can write unit tests. No behavior changes intended beyond the bug fixes ## Changes involved - **Test organization:** moved all root-level `ConnectSheet` test files into `ConnectSheet/__tests__/` for consistency with other parts of the codebase that use this convention. - **Bug fix:** read replica label had a stray `}` / missing `)`, rendering as e.g. `Read Replica (us-east-1 - abc123})` instead of `Read Replica (us-east-1 - abc123)`. - **`ConnectSheet.tsx`:** extracted the "hydrate sheet state on open" `useEffect` logic (mode/field/URL param resolution from URL vs. localStorage) into a new `ConnectSheet.utils.ts`, with unit tests - **`useConnectServerEnv.ts`:** fixed two race conditions in the secret reveal/hide flow: - `toggle()` and `getValue()` could each fire a separate reveal request if triggered close together — now deduped to share one in-flight request. - `getValue()` could hide a secret that had just been explicitly revealed by a concurrent `toggle()`, due to reading a stale closure value — now reads the live state via `useLatest`. - Also stopped swallowing the original error on reveal failure (now attached via `cause`). - Added tests for the above, plus the 10s auto-hide timer (previously untested). - **`ConnectStepsSection.tsx`:** extracted `resolveContentPath` and the three inline "show notice" booleans (IPv4 addon, session pooler, self-hosted MCP) into `ConnectStepsSection.utils.ts`, matching the existing pattern for the Data API notice. Added unit tests for all of them. ## To test - [ ] Just a basic smoke test of the Connect sheet should do <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved connect setup hydration so saved preferences and URL values are applied more consistently when opening the sheet, including automatic URL backfilling where needed. * Refreshed connection guidance notices (IPv4 add-on, session pooler, and self-hosted MCP) with more consistent logic. * **Bug Fixes** * Fixed secret reveal behavior to keep concurrent reveal actions in sync, handle failures more safely, and ensure auto-hide works reliably. * Corrected the read-replica option label formatting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
194 lines
6.1 KiB
TypeScript
194 lines
6.1 KiB
TypeScript
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { useParams } from 'common'
|
|
import { parseAsBoolean, useQueryState } from 'nuqs'
|
|
import { useEffect, useMemo, useRef } from 'react'
|
|
import { cn, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from 'ui'
|
|
|
|
import type { ConnectMode, ProjectKeys } from './Connect.types'
|
|
import { ConnectConfigSection, ModeSelector } from './ConnectConfigSection'
|
|
import { resolveConnectSheetHydration } from './ConnectSheet.utils'
|
|
import { ConnectStepsSection } from './ConnectStepsSection'
|
|
import { useAvailableConnectModes } from './useAvailableConnectModes'
|
|
import { useConnectSheetParams } from './useConnectSheetParams'
|
|
import { useConnectSheetShortcut } from './useConnectSheetShortcut'
|
|
import { useConnectState } from './useConnectState'
|
|
import { useAPIKeys } from '@/data/api-keys/api-keys-query'
|
|
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
|
|
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
import { useAppStateSnapshot } from '@/state/app-state'
|
|
|
|
export const ConnectSheet = () => {
|
|
const track = useTrack()
|
|
const prevShowConnect = useRef(false)
|
|
const { ref: projectRef } = useParams()
|
|
|
|
useConnectSheetShortcut()
|
|
|
|
const availableModeIds = useAvailableConnectModes()
|
|
const { connectSheetSource, setConnectSheetSource } = useAppStateSnapshot()
|
|
const { state, activeFields, resolvedSteps, schema, getFieldOptions, setMode, updateField } =
|
|
useConnectState()
|
|
|
|
const [showConnect, setShowConnect] = useQueryState(
|
|
'showConnect',
|
|
parseAsBoolean.withDefault(false)
|
|
)
|
|
// URL params have no defaults: a `null` value signals "not in URL" so we can
|
|
// fall back to the user's last-used selections from localStorage.
|
|
const { params, storedPrefs, setConnectParams, setQueryParams } = useConnectSheetParams()
|
|
const {
|
|
connectTab,
|
|
framework: queryFramework,
|
|
using: queryUsing,
|
|
method: queryMethod,
|
|
type: queryType,
|
|
mcpClient: queryMcpClient,
|
|
} = params
|
|
|
|
useEffect(() => {
|
|
const justOpened = showConnect && !prevShowConnect.current
|
|
prevShowConnect.current = showConnect
|
|
|
|
if (!justOpened) return
|
|
|
|
track('connect_sheet_opened', { source: connectSheetSource })
|
|
setConnectSheetSource('header_button')
|
|
|
|
const { mode, fieldUpdates, urlUpdates } = resolveConnectSheetHydration(
|
|
{
|
|
connectTab,
|
|
framework: queryFramework,
|
|
using: queryUsing,
|
|
method: queryMethod,
|
|
type: queryType,
|
|
mcpClient: queryMcpClient,
|
|
},
|
|
storedPrefs,
|
|
availableModeIds
|
|
)
|
|
|
|
if (mode) setMode(mode)
|
|
fieldUpdates.forEach(({ fieldId, value }) => updateField(fieldId, value))
|
|
if (Object.keys(urlUpdates).length > 0) setQueryParams(urlUpdates)
|
|
}, [
|
|
showConnect,
|
|
connectSheetSource,
|
|
connectTab,
|
|
queryFramework,
|
|
queryUsing,
|
|
queryMethod,
|
|
queryType,
|
|
queryMcpClient,
|
|
storedPrefs,
|
|
availableModeIds,
|
|
track,
|
|
setConnectSheetSource,
|
|
setMode,
|
|
updateField,
|
|
setQueryParams,
|
|
])
|
|
|
|
const clearAllQueryParams = () => {
|
|
setQueryParams({
|
|
connectTab: null,
|
|
framework: null,
|
|
using: null,
|
|
method: null,
|
|
type: null,
|
|
mcpClient: null,
|
|
})
|
|
}
|
|
|
|
const handleOpenChange = (sheetOpen: boolean) => {
|
|
if (!sheetOpen) clearAllQueryParams()
|
|
setShowConnect(sheetOpen)
|
|
}
|
|
|
|
const { data: endpoint = '' } = useProjectApiUrl({ projectRef }, { enabled: showConnect })
|
|
|
|
const { can: canReadAPIKeys } = useAsyncCheckPermissions(
|
|
PermissionAction.READ,
|
|
'service_api_keys'
|
|
)
|
|
const { data: apiKeysData } = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
|
|
|
|
const projectKeys: ProjectKeys = useMemo(() => {
|
|
const { anonKey, publishableKey } = apiKeysData ?? {}
|
|
return {
|
|
apiUrl: endpoint,
|
|
anonKey: anonKey?.api_key ?? null,
|
|
publishableKey: publishableKey?.api_key ?? null,
|
|
}
|
|
}, [endpoint, apiKeysData])
|
|
|
|
const availableModes = useMemo(
|
|
() => schema.modes.filter((m) => availableModeIds.includes(m.id)),
|
|
[schema.modes, availableModeIds]
|
|
)
|
|
|
|
const handleModeChange = (mode: ConnectMode) => {
|
|
setMode(mode)
|
|
setConnectParams({
|
|
connectTab: mode,
|
|
framework: null,
|
|
using: null,
|
|
method: null,
|
|
type: null,
|
|
mcpClient: null,
|
|
})
|
|
}
|
|
|
|
const handleFieldChange = (fieldId: string, value: string | boolean | string[]) => {
|
|
updateField(fieldId, value)
|
|
const str = String(value)
|
|
if (fieldId === 'framework') {
|
|
setConnectParams({ framework: str, using: null })
|
|
} else if (fieldId === 'frameworkVariant') {
|
|
setConnectParams({ using: str })
|
|
} else if (fieldId === 'orm') {
|
|
setConnectParams({ framework: str })
|
|
} else if (fieldId === 'connectionMethod') {
|
|
setConnectParams({ method: str, type: null })
|
|
} else if (fieldId === 'connectionType') {
|
|
setConnectParams({ type: str })
|
|
} else if (fieldId === 'mcpClient') {
|
|
setConnectParams({ mcpClient: str })
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Sheet open={showConnect} onOpenChange={handleOpenChange}>
|
|
<SheetContent size="lg" className="flex flex-col gap-0 p-0 space-y-0" tabIndex={undefined}>
|
|
<SheetHeader className={cn('text-left border-b shrink-0 py-6 px-8')}>
|
|
<SheetTitle>Connect to your project</SheetTitle>
|
|
<SheetDescription>Choose how you want to use Supabase</SheetDescription>
|
|
</SheetHeader>
|
|
|
|
<div className="flex flex-1 flex-col overflow-y-auto divide-y">
|
|
<div className="p-8">
|
|
<ModeSelector
|
|
modes={availableModes}
|
|
selected={state.mode}
|
|
onChange={handleModeChange}
|
|
/>
|
|
</div>
|
|
|
|
{activeFields.length > 0 && (
|
|
<div className="border-b p-8">
|
|
<ConnectConfigSection
|
|
state={state}
|
|
activeFields={activeFields}
|
|
onFieldChange={handleFieldChange}
|
|
getFieldOptions={getFieldOptions}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<ConnectStepsSection steps={resolvedSteps} state={state} projectKeys={projectKeys} />
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
)
|
|
}
|