11'use client'
22
3- import { useCallback , useMemo , useState } from 'react'
3+ import { useMemo , useState } from 'react'
44import { createLogger } from '@sim/logger'
55import { ArrowLeftRight , ExternalLink , Loader2 , RotateCcw } from 'lucide-react'
66import {
@@ -26,8 +26,8 @@ import { getSubscriptionAccessState } from '@/lib/billing/client'
2626import { ConnectorSelectorField } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field'
2727import { SYNC_INTERVALS } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/consts'
2828import { MaxBadge } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/max-badge'
29+ import { useConnectorConfigFields } from '@/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields'
2930import { isBillingEnabled } from '@/app/workspace/[workspaceId]/settings/navigation'
30- import { getDependsOnFields } from '@/blocks/utils'
3131import { CONNECTOR_REGISTRY } from '@/connectors/registry'
3232import type { ConnectorConfig , ConnectorConfigField } from '@/connectors/types'
3333import type { ConnectorData } from '@/hooks/queries/kb/connectors'
@@ -61,11 +61,14 @@ export function EditConnectorModal({
6161 const connectorConfig = CONNECTOR_REGISTRY [ connector . connectorType ] ?? null
6262
6363 const [ activeTab , setActiveTab ] = useState ( 'settings' )
64+ const [ syncInterval , setSyncInterval ] = useState ( connector . syncIntervalMinutes )
65+ const [ error , setError ] = useState < string | null > ( null )
66+
6467 /**
6568 * Seeds from the stored canonical config. For canonical-pair fields (selector +
6669 * manual input), both field IDs get the same value so toggling preserves it.
6770 */
68- const [ sourceConfig , setSourceConfig ] = useState < Record < string , string > > ( ( ) => {
71+ const initialSourceConfig = useMemo ( ( ) => {
6972 const config : Record < string , string > = { }
7073 if ( ! connectorConfig ) {
7174 for ( const [ key , value ] of Object . entries ( connector . sourceConfig ) ) {
@@ -80,118 +83,26 @@ export function EditConnectorModal({
8083 if ( rawValue !== undefined ) config [ field . id ] = String ( rawValue ?? '' )
8184 }
8285 return config
83- } )
84- const [ syncInterval , setSyncInterval ] = useState ( connector . syncIntervalMinutes )
85- const [ error , setError ] = useState < string | null > ( null )
86- const [ canonicalModes , setCanonicalModes ] = useState < Record < string , 'basic' | 'advanced' > > ( { } )
86+ // Seed once on mount; editing state is owned by the hook afterward
87+ // eslint-disable-next-line react-hooks/exhaustive-deps
88+ } , [ ] )
89+
90+ const {
91+ sourceConfig,
92+ canonicalModes,
93+ canonicalGroups,
94+ isFieldVisible,
95+ handleFieldChange,
96+ toggleCanonicalMode,
97+ resolveSourceConfig,
98+ } = useConnectorConfigFields ( { connectorConfig, initialSourceConfig } )
8799
88100 const { mutate : updateConnector , isPending : isSaving } = useUpdateConnector ( )
89101
90102 const { data : subscriptionResponse } = useSubscriptionData ( { enabled : isBillingEnabled } )
91103 const subscriptionAccess = getSubscriptionAccessState ( subscriptionResponse ?. data )
92104 const hasMaxAccess = ! isBillingEnabled || subscriptionAccess . hasUsableMaxAccess
93105
94- const canonicalGroups = useMemo ( ( ) => {
95- if ( ! connectorConfig ) return new Map < string , ConnectorConfigField [ ] > ( )
96- const groups = new Map < string , ConnectorConfigField [ ] > ( )
97- for ( const field of connectorConfig . configFields ) {
98- if ( field . canonicalParamId ) {
99- const existing = groups . get ( field . canonicalParamId )
100- if ( existing ) existing . push ( field )
101- else groups . set ( field . canonicalParamId , [ field ] )
102- }
103- }
104- return groups
105- } , [ connectorConfig ] )
106-
107- const dependentFieldIds = useMemo ( ( ) => {
108- if ( ! connectorConfig ) return new Map < string , string [ ] > ( )
109- const map = new Map < string , Set < string > > ( )
110- for ( const field of connectorConfig . configFields ) {
111- const deps = getDependsOnFields ( field . dependsOn )
112- for ( const dep of deps ) {
113- const existing = map . get ( dep ) ?? new Set < string > ( )
114- existing . add ( field . id )
115- if ( field . canonicalParamId ) {
116- for ( const sibling of canonicalGroups . get ( field . canonicalParamId ) ?? [ ] ) {
117- existing . add ( sibling . id )
118- }
119- }
120- map . set ( dep , existing )
121- }
122- }
123- for ( const group of canonicalGroups . values ( ) ) {
124- const allDependents = new Set < string > ( )
125- for ( const field of group ) {
126- for ( const dep of map . get ( field . id ) ?? [ ] ) {
127- allDependents . add ( dep )
128- const depField = connectorConfig . configFields . find ( ( f ) => f . id === dep )
129- if ( depField ?. canonicalParamId ) {
130- for ( const sibling of canonicalGroups . get ( depField . canonicalParamId ) ?? [ ] ) {
131- allDependents . add ( sibling . id )
132- }
133- }
134- }
135- }
136- if ( allDependents . size > 0 ) {
137- for ( const field of group ) map . set ( field . id , new Set ( allDependents ) )
138- }
139- }
140- const result = new Map < string , string [ ] > ( )
141- for ( const [ key , value ] of map ) result . set ( key , [ ...value ] )
142- return result
143- } , [ connectorConfig , canonicalGroups ] )
144-
145- const isFieldVisible = ( field : ConnectorConfigField ) : boolean => {
146- if ( ! field . canonicalParamId || ! field . mode ) return true
147- const activeMode = canonicalModes [ field . canonicalParamId ] ?? 'basic'
148- return field . mode === activeMode
149- }
150-
151- const handleFieldChange = ( fieldId : string , value : string ) => {
152- setSourceConfig ( ( prev ) => {
153- const next = { ...prev , [ fieldId ] : value }
154- const toClear = dependentFieldIds . get ( fieldId )
155- if ( toClear ) {
156- for ( const depId of toClear ) next [ depId ] = ''
157- }
158- return next
159- } )
160- }
161-
162- const toggleCanonicalMode = ( canonicalId : string ) => {
163- setCanonicalModes ( ( prev ) => ( {
164- ...prev ,
165- [ canonicalId ] : prev [ canonicalId ] === 'advanced' ? 'basic' : 'advanced' ,
166- } ) )
167- }
168-
169- /**
170- * Collapse the canonical-pair state back to a flat map keyed by canonical IDs
171- * (matching what's stored in `connector.sourceConfig`).
172- */
173- const resolveSourceConfig = useCallback ( ( ) : Record < string , string > => {
174- const resolved : Record < string , string > = { }
175- const processedCanonicals = new Set < string > ( )
176- if ( ! connectorConfig ) return resolved
177-
178- for ( const field of connectorConfig . configFields ) {
179- if ( field . canonicalParamId ) {
180- if ( processedCanonicals . has ( field . canonicalParamId ) ) continue
181- processedCanonicals . add ( field . canonicalParamId )
182- const group = canonicalGroups . get ( field . canonicalParamId )
183- if ( ! group ) continue
184- const activeMode = canonicalModes [ field . canonicalParamId ] ?? 'basic'
185- const activeField = group . find ( ( f ) => f . mode === activeMode ) ?? group [ 0 ]
186- const value = sourceConfig [ activeField . id ] ?? ''
187- resolved [ field . canonicalParamId ] = value
188- } else {
189- resolved [ field . id ] = sourceConfig [ field . id ] ?? ''
190- }
191- }
192- return resolved
193- } , [ connectorConfig , canonicalGroups , canonicalModes , sourceConfig ] )
194-
195106 const hasChanges = useMemo ( ( ) => {
196107 if ( syncInterval !== connector . syncIntervalMinutes ) return true
197108 const resolved = resolveSourceConfig ( )
0 commit comments