@@ -14,8 +14,9 @@ export function CustomConnectionForm({
1414 onSuccess,
1515 onCancel,
1616} : CustomConnectionFormProps ) {
17- const [ mode , setMode ] = useState < "fields" | "url" > ( "fields" ) ;
17+ const hasConnectionString = ! ! config . parseConnectionString ;
1818 const [ connectionString , setConnectionString ] = useState ( "" ) ;
19+ const [ fieldsExpanded , setFieldsExpanded ] = useState ( ! hasConnectionString ) ;
1920 const [ values , setValues ] = useState < Record < string , string > > ( ( ) => {
2021 const defaults : Record < string , string > = { } ;
2122 for ( const field of config . fields ) {
@@ -26,16 +27,19 @@ export function CustomConnectionForm({
2627 const [ error , setError ] = useState < string | null > ( null ) ;
2728 const [ submitting , setSubmitting ] = useState ( false ) ;
2829
30+ // Detect which mode is active based on what the user has filled in
31+ const usingConnectionString = hasConnectionString && connectionString . trim ( ) . length > 0 ;
32+
2933 const setValue = useCallback ( ( name : string , value : string ) => {
3034 setValues ( ( prev ) => ( { ...prev , [ name ] : value } ) ) ;
3135 } , [ ] ) ;
3236
3337 const handleSubmit = useCallback ( async ( ) => {
3438 setError ( null ) ;
3539
36- // If in URL mode, parse first
3740 let finalValues = { ...values } ;
38- if ( mode === "url" && connectionString . trim ( ) ) {
41+
42+ if ( usingConnectionString ) {
3943 if ( ! config . parseConnectionString ) return ;
4044 try {
4145 const parsed = config . parseConnectionString ( connectionString . trim ( ) ) ;
@@ -44,13 +48,13 @@ export function CustomConnectionForm({
4448 setError ( "Invalid connection string format" ) ;
4549 return ;
4650 }
47- }
48-
49- // Validate required fields (skip nickname which is optional)
50- for ( const field of config . fields ) {
51- if ( field . required && ! finalValues [ field . name ] ?. trim ( ) ) {
52- setError ( ` ${ field . label } is required` ) ;
53- return ;
51+ } else {
52+ // Validate required fields (skip nickname which is optional)
53+ for ( const field of config . fields ) {
54+ if ( field . required && ! finalValues [ field . name ] ?. trim ( ) ) {
55+ setError ( ` ${ field . label } is required` ) ;
56+ return ;
57+ }
5458 }
5559 }
5660
@@ -93,79 +97,82 @@ export function CustomConnectionForm({
9397 } finally {
9498 setSubmitting ( false ) ;
9599 }
96- } , [ values , mode , connectionString , config , integrationId , onSuccess ] ) ;
100+ } , [ values , usingConnectionString , connectionString , config , integrationId , onSuccess ] ) ;
101+
102+ // Fields that aren't nickname (nickname is shown outside the collapsible section)
103+ const connectionFields = config . fields . filter ( ( f ) => f . name !== "nickname" ) ;
104+ const usingFields = connectionFields . some ( ( f ) => {
105+ const val = ( values [ f . name ] ?? "" ) . trim ( ) ;
106+ return val . length > 0 && val !== ( f . defaultValue ?? "" ) ;
107+ } ) ;
97108
98109 const inputClass =
99110 "w-full text-[12px] px-2.5 py-1.5 rounded-md border border-[#e8e4df] bg-white text-[#1a1a1a] placeholder:text-[#c4bfb8] focus:outline-none focus:border-[#a8a099] transition-colors" ;
111+ const disabledInputClass =
112+ "w-full text-[12px] px-2.5 py-1.5 rounded-md border border-[#e8e4df] bg-[#f5f3f0] text-[#a8a099] placeholder:text-[#d4d0cb] cursor-not-allowed" ;
100113
101114 return (
102115 < div className = "flex flex-col gap-3 pt-1" >
103- { /* Mode toggle */ }
104- < div className = "flex gap-1" >
116+ { /* Nickname (always on top) */ }
117+ < div >
118+ < label className = "text-[10px] text-[#a8a099] block mb-0.5" > Nickname (optional)</ label >
119+ < input
120+ type = "text"
121+ value = { values . nickname ?? "" }
122+ onChange = { ( e ) => setValue ( "nickname" , e . target . value ) }
123+ placeholder = "e.g. Production DB"
124+ className = { inputClass }
125+ />
126+ </ div >
127+
128+ { /* Connection string input (always visible when available) */ }
129+ { hasConnectionString && (
130+ < div >
131+ < label className = "text-[10px] text-[#a8a099] block mb-0.5" >
132+ Connection String{ ! usingFields && < span className = "text-red-400 ml-0.5" > *</ span > }
133+ </ label >
134+ < input
135+ type = "password"
136+ value = { connectionString }
137+ onChange = { ( e ) => setConnectionString ( e . target . value ) }
138+ placeholder = "postgresql://user:pass@host:5432/db?sslmode=require"
139+ disabled = { usingFields }
140+ className = { usingFields ? disabledInputClass : inputClass }
141+ />
142+ </ div >
143+ ) }
144+
145+ { /* Expandable fields section */ }
146+ { hasConnectionString && (
105147 < button
106148 type = "button"
107- onClick = { ( ) => setMode ( "fields" ) }
108- className = { `text-[10px] px-2 py-0.5 rounded-full transition-colors cursor-pointer ${
109- mode === "fields"
110- ? "bg-[#1a1a1a] text-white"
111- : "bg-[#f0ece7] text-[#787068] hover:bg-[#e8e4df]"
112- } `}
149+ onClick = { ( ) => setFieldsExpanded ( ( prev ) => ! prev ) }
150+ className = "flex items-center gap-2 w-full cursor-pointer group"
113151 >
114- Fields
152+ < div className = "flex-1 h-px bg-[#e8e4df]" />
153+ < span className = "text-[10px] text-[#a8a099] group-hover:text-[#787068] underline decoration-dotted underline-offset-2 transition-colors whitespace-nowrap" >
154+ { fieldsExpanded ? "hide fields" : "or enter fields individually" }
155+ </ span >
156+ < div className = "flex-1 h-px bg-[#e8e4df]" />
115157 </ button >
116- { config . parseConnectionString && (
117- < button
118- type = "button"
119- onClick = { ( ) => setMode ( "url" ) }
120- className = { `text-[10px] px-2 py-0.5 rounded-full transition-colors cursor-pointer ${
121- mode === "url"
122- ? "bg-[#1a1a1a] text-white"
123- : "bg-[#f0ece7] text-[#787068] hover:bg-[#e8e4df]"
124- } `}
125- >
126- Connection String
127- </ button >
128- ) }
129- </ div >
158+ ) }
130159
131- { mode === "url" ? (
132- /* Connection string mode */
133- < div className = "flex flex-col gap-2" >
134- < div >
135- < label className = "text-[10px] text-[#a8a099] block mb-0.5" > Connection String< span className = "text-red-400 ml-0.5" > *</ span > </ label >
136- < input
137- type = "password"
138- value = { connectionString }
139- onChange = { ( e ) => setConnectionString ( e . target . value ) }
140- placeholder = "postgresql://user:pass@host:5432/db?sslmode=require"
141- className = { inputClass }
142- />
143- </ div >
144- < div >
145- < label className = "text-[10px] text-[#a8a099] block mb-0.5" > Nickname</ label >
146- < input
147- type = "text"
148- value = { values . nickname ?? "" }
149- onChange = { ( e ) => setValue ( "nickname" , e . target . value ) }
150- placeholder = "e.g. Production DB"
151- className = { inputClass }
152- />
153- </ div >
154- </ div >
155- ) : (
156- /* Individual fields mode */
160+ { fieldsExpanded && (
157161 < div className = "flex flex-col gap-2" >
158- { config . fields . map ( ( field ) => (
162+ { connectionFields . map ( ( field ) => (
159163 < div key = { field . name } >
160164 < label className = "text-[10px] text-[#a8a099] block mb-0.5" >
161165 { field . label }
162- { field . required && < span className = "text-red-400 ml-0.5" > *</ span > }
166+ { field . required && ! usingConnectionString && (
167+ < span className = "text-red-400 ml-0.5" > *</ span >
168+ ) }
163169 </ label >
164170 { field . type === "select" ? (
165171 < select
166172 value = { values [ field . name ] ?? field . defaultValue ?? "" }
167173 onChange = { ( e ) => setValue ( field . name , e . target . value ) }
168- className = { inputClass }
174+ disabled = { usingConnectionString }
175+ className = { usingConnectionString ? disabledInputClass : inputClass }
169176 >
170177 { field . options ?. map ( ( opt ) => (
171178 < option key = { opt } value = { opt } >
@@ -179,7 +186,8 @@ export function CustomConnectionForm({
179186 value = { values [ field . name ] ?? "" }
180187 onChange = { ( e ) => setValue ( field . name , e . target . value ) }
181188 placeholder = { field . placeholder }
182- className = { inputClass }
189+ disabled = { usingConnectionString }
190+ className = { usingConnectionString ? disabledInputClass : inputClass }
183191 />
184192 ) }
185193 </ div >
0 commit comments