@@ -54,9 +54,33 @@ import {
5454 getLabel ,
5555 getProtocolDefaultConfig ,
5656 protocols as PROTOCOLS ,
57+ type ProtocolType ,
5758 useProtocolFields ,
5859} from "./form-schema" ;
5960
61+ function getFieldLabel ( field : FieldConfig ) {
62+ return (
63+ < >
64+ { field . label }
65+ { field . required ? (
66+ < span aria-hidden = "true" className = "ml-1 text-destructive" >
67+ *
68+ </ span >
69+ ) : null }
70+ </ >
71+ ) ;
72+ }
73+
74+ function getVisibleRequiredFields (
75+ fields : FieldConfig [ ] ,
76+ protocolData : Record < string , any >
77+ ) {
78+ return fields . filter (
79+ ( field ) =>
80+ field . required && ( ! field . condition || field . condition ( protocolData , { } ) )
81+ ) ;
82+ }
83+
6084function DynamicField ( {
6185 field,
6286 control,
@@ -88,7 +112,7 @@ function DynamicField({
88112 { ...commonProps }
89113 render = { ( { field : fieldProps } ) => (
90114 < FormItem >
91- < FormLabel > { field . label } </ FormLabel >
115+ < FormLabel > { getFieldLabel ( field ) } </ FormLabel >
92116 < FormControl >
93117 < EnhancedInput
94118 { ...fieldProps }
@@ -178,7 +202,7 @@ function DynamicField({
178202 { ...commonProps }
179203 render = { ( { field : fieldProps } ) => (
180204 < FormItem >
181- < FormLabel > { field . label } </ FormLabel >
205+ < FormLabel > { getFieldLabel ( field ) } </ FormLabel >
182206 < FormControl >
183207 < EnhancedInput
184208 { ...fieldProps }
@@ -207,7 +231,7 @@ function DynamicField({
207231 { ...commonProps }
208232 render = { ( { field : fieldProps } ) => (
209233 < FormItem >
210- < FormLabel > { field . label } </ FormLabel >
234+ < FormLabel > { getFieldLabel ( field ) } </ FormLabel >
211235 < FormControl >
212236 < Select
213237 onValueChange = { ( v ) => fieldProps . onChange ( v ) }
@@ -239,7 +263,7 @@ function DynamicField({
239263 { ...commonProps }
240264 render = { ( { field : fieldProps } ) => (
241265 < FormItem >
242- < FormLabel > { field . label } </ FormLabel >
266+ < FormLabel > { getFieldLabel ( field ) } </ FormLabel >
243267 < FormControl >
244268 < div className = "pt-2" >
245269 < Switch
@@ -260,7 +284,7 @@ function DynamicField({
260284 { ...commonProps }
261285 render = { ( { field : fieldProps } ) => (
262286 < FormItem className = "col-span-2" >
263- < FormLabel > { field . label } </ FormLabel >
287+ < FormLabel > { getFieldLabel ( field ) } </ FormLabel >
264288 < FormControl >
265289 < textarea
266290 { ...fieldProps }
@@ -399,12 +423,100 @@ export default function ServerForm(props: {
399423 // eslint-disable-next-line react-hooks/exhaustive-deps
400424 } , [ initialValues ] ) ;
401425
426+ function validateEnabledProtocols ( values : Record < string , any > ) {
427+ let firstInvalidProtocol : ProtocolType | undefined ;
428+
429+ for ( const [ index , protocol ] of ( values ?. protocols || [ ] ) . entries ( ) ) {
430+ if ( ! protocol ?. enable ) continue ;
431+
432+ const protocolType = protocol . type as ProtocolType ;
433+ const fields = PROTOCOL_FIELDS [ protocolType ] || [ ] ;
434+ const requiredFields = getVisibleRequiredFields ( fields , protocol ) ;
435+
436+ for ( const field of requiredFields ) {
437+ const value = protocol [ field . name ] ;
438+ const fieldPath = `protocols.${ index } .${ field . name } ` as any ;
439+
440+ if ( field . type === "number" ) {
441+ const numericValue =
442+ typeof value === "number" ? value : Number ( value ?? Number . NaN ) ;
443+ const hasValue = Number . isFinite ( numericValue ) ;
444+ const inMinRange =
445+ field . min === undefined || numericValue >= field . min ;
446+ const inMaxRange =
447+ field . max === undefined || numericValue <= field . max ;
448+
449+ if ( ! ( hasValue && inMinRange && inMaxRange ) ) {
450+ form . setError ( fieldPath , {
451+ type : "manual" ,
452+ message : t (
453+ "validation.requiredNumberField" ,
454+ "{{field}} is required and must be between {{min}} and {{max}}" ,
455+ {
456+ field : field . label ,
457+ min : field . min ?? 0 ,
458+ max : field . max ?? 65_535 ,
459+ }
460+ ) ,
461+ } ) ;
462+ firstInvalidProtocol ??= protocolType ;
463+ }
464+ continue ;
465+ }
466+
467+ if ( field . type === "select" ) {
468+ const hasValue = typeof value === "string" && value . trim ( ) . length > 0 ;
469+ const isAllowed =
470+ ! field . options || ( hasValue && field . options . includes ( value ) ) ;
471+
472+ if ( ! ( hasValue && isAllowed ) ) {
473+ form . setError ( fieldPath , {
474+ type : "manual" ,
475+ message : t (
476+ "validation.requiredSelectField" ,
477+ "{{field}} is required" ,
478+ { field : field . label }
479+ ) ,
480+ } ) ;
481+ firstInvalidProtocol ??= protocolType ;
482+ }
483+ continue ;
484+ }
485+
486+ const hasValue =
487+ typeof value === "string"
488+ ? value . trim ( ) . length > 0
489+ : value !== null && value !== undefined ;
490+
491+ if ( ! hasValue ) {
492+ form . setError ( fieldPath , {
493+ type : "manual" ,
494+ message : t ( "validation.requiredField" , "{{field}} is required" , {
495+ field : field . label ,
496+ } ) ,
497+ } ) ;
498+ firstInvalidProtocol ??= protocolType ;
499+ }
500+ }
501+ }
502+
503+ if ( firstInvalidProtocol ) {
504+ setAccordionValue ( firstInvalidProtocol ) ;
505+ return false ;
506+ }
507+
508+ return true ;
509+ }
510+
402511 async function handleSubmit ( values : Record < string , any > ) {
512+ form . clearErrors ( ) ;
513+
514+ if ( ! validateEnabledProtocols ( values ) ) {
515+ return ;
516+ }
517+
403518 const filteredProtocols = ( values ?. protocols || [ ] ) . filter (
404- ( protocol : any ) => {
405- const port = Number ( protocol ?. port ) ;
406- return protocol && Number . isFinite ( port ) && port > 0 && port <= 65_535 ;
407- }
519+ ( protocol : any ) => protocol ?. enable
408520 ) ;
409521
410522 const result = {
@@ -605,6 +717,15 @@ export default function ServerForm(props: {
605717 ) }
606718 onCheckedChange = { ( checked ) => {
607719 form . setValue ( `protocols.${ i } .enable` , checked ) ;
720+ if ( checked ) {
721+ setAccordionValue ( type ) ;
722+ return ;
723+ }
724+
725+ if ( accordionValue === type ) {
726+ setAccordionValue ( undefined ) ;
727+ }
728+ form . clearErrors ( `protocols.${ i } ` as any ) ;
608729 } }
609730 onClick = { ( e ) => e . stopPropagation ( ) }
610731 />
0 commit comments