@@ -309,19 +309,43 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
309309 { id : 'groups' , label : 'groups' , icon : Users } ,
310310 { id : 'templates' , label : 'templates.title' , icon : Layers } ,
311311 ]
312- const [ nextPlanEnabled , setNextPlanEnabled ] = useState ( ( ) => {
313- const nextPlan = form . watch ( 'next_plan' )
314- return nextPlan !== undefined && nextPlan !== null && Object . keys ( nextPlan ) . length > 0
315- } )
312+ const [ nextPlanEnabled , setNextPlanEnabled ] = useState ( false )
313+ const [ nextPlanManuallyDisabled , setNextPlanManuallyDisabled ] = useState ( false )
316314 const [ selectedTemplateId , setSelectedTemplateId ] = useState < number | null > ( null )
317315 const [ expireCalendarOpen , setExpireCalendarOpen ] = useState ( false )
318316 const [ onHoldCalendarOpen , setOnHoldCalendarOpen ] = useState ( false )
319317
320- // Reset calendar state when modal opens/closes
318+ const hasNextPlanValues = React . useCallback ( ( nextPlan : any ) : boolean => {
319+ if ( ! nextPlan || typeof nextPlan !== 'object' ) return false
320+
321+ const hasAnyValue = ! ! (
322+ ( nextPlan . user_template_id !== undefined && nextPlan . user_template_id !== null ) ||
323+ ( nextPlan . expire !== undefined && nextPlan . expire !== null ) ||
324+ ( nextPlan . data_limit !== undefined && nextPlan . data_limit !== null ) ||
325+ nextPlan . add_remaining_traffic !== undefined
326+ )
327+
328+ return hasAnyValue
329+ } , [ ] )
330+
331+ const nextPlanValue = React . useMemo ( ( ) => ( {
332+ user_template_id : form . watch ( 'next_plan.user_template_id' ) ,
333+ expire : form . watch ( 'next_plan.expire' ) ,
334+ data_limit : form . watch ( 'next_plan.data_limit' ) ,
335+ add_remaining_traffic : form . watch ( 'next_plan.add_remaining_traffic' ) ,
336+ } ) , [
337+ form . watch ( 'next_plan.user_template_id' ) ,
338+ form . watch ( 'next_plan.expire' ) ,
339+ form . watch ( 'next_plan.data_limit' ) ,
340+ form . watch ( 'next_plan.add_remaining_traffic' ) ,
341+ ] )
342+
321343 useEffect ( ( ) => {
322344 if ( ! isDialogOpen ) {
323345 setExpireCalendarOpen ( false )
324346 setOnHoldCalendarOpen ( false )
347+ setNextPlanEnabled ( false )
348+ setNextPlanManuallyDisabled ( false )
325349 }
326350 } , [ isDialogOpen ] )
327351 const [ touchedFields , setTouchedFields ] = useState < Record < string , boolean > > ( { } )
@@ -332,16 +356,21 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
332356 const handleModalOpenChange = React . useCallback (
333357 ( open : boolean ) => {
334358 if ( ! open ) {
335- form . reset ( )
359+ // Only reset form if not editing (for create mode)
360+ // When editing, parent component will repopulate the form
361+ if ( ! editingUser ) {
362+ form . reset ( )
363+ }
336364 setTouchedFields ( { } )
337365 setIsFormValid ( false )
338366 setActiveTab ( 'groups' )
339367 setSelectedTemplateId ( null )
368+ setNextPlanEnabled ( false )
340369 dataLimitInputRef . current = ''
341370 }
342371 onOpenChange ( open )
343372 } ,
344- [ form , onOpenChange ] ,
373+ [ form , onOpenChange , editingUser ] ,
345374 )
346375
347376 const handleFieldChange = React . useCallback (
@@ -429,6 +458,7 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
429458 gcTime : 0 ,
430459 refetchOnMount : true ,
431460 refetchOnReconnect : false ,
461+ enabled : isDialogOpen ,
432462 } ,
433463 } )
434464
@@ -569,34 +599,32 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
569599 form . setValue ( 'on_hold_timeout' , undefined )
570600 form . clearErrors ( 'on_hold_timeout' )
571601 }
572- } , [ status , form , t , handleFieldChange ] )
602+ } , [ status , form , t , handleFieldChange , touchedFields ] )
573603
574604 useEffect ( ( ) => {
575605 if ( ! nextPlanEnabled ) {
576- // Clear all next_plan data when disabled
577606 form . setValue ( 'next_plan' , undefined )
578607 handleFieldChange ( 'next_plan' , undefined )
579- } else if ( ! form . watch ( 'next_plan' ) || form . watch ( 'next_plan' ) === null ) {
580- form . setValue ( 'next_plan' , { } )
581- handleFieldChange ( 'next_plan' , { } )
608+ } else {
609+ setNextPlanManuallyDisabled ( false )
610+ const isEmpty = ! nextPlanValue . user_template_id && ! nextPlanValue . expire && ! nextPlanValue . data_limit && nextPlanValue . add_remaining_traffic === undefined
611+ if ( isEmpty ) {
612+ form . setValue ( 'next_plan' , { } )
613+ handleFieldChange ( 'next_plan' , { } )
614+ }
582615 }
583- // eslint-disable-next-line
584- } , [ nextPlanEnabled ] )
616+ } , [ nextPlanEnabled , nextPlanValue ] )
585617
586- // Sync switch state when next_plan value changes (e.g., when editing a user)
587- // Only sync when the form has data but the switch is off (initial load scenario)
588618 useEffect ( ( ) => {
589- const nextPlan = form . watch ( 'next_plan' )
590- const shouldBeEnabled = nextPlan !== undefined && nextPlan !== null && Object . keys ( nextPlan ) . length > 0
619+ if ( ! isDialogOpen || ! editingUser || nextPlanManuallyDisabled ) return
620+
621+ const shouldBeEnabled = hasNextPlanValues ( nextPlanValue )
591622
592- // Only sync if:
593- // 1. The form has data (shouldBeEnabled is true)
594- // 2. The switch is currently off (nextPlanEnabled is false)
595- // 3. We're not in the middle of a user manually disabling it
596623 if ( shouldBeEnabled && ! nextPlanEnabled ) {
597624 setNextPlanEnabled ( true )
598625 }
599- } , [ form . watch ( 'next_plan' ) , nextPlanEnabled ] )
626+ // Don't automatically disable - let user control it via the toggle
627+ } , [ nextPlanValue , nextPlanEnabled , isDialogOpen , editingUser , hasNextPlanValues , nextPlanManuallyDisabled ] )
600628
601629 // Helper to convert expire field to needed schema using the same logic as other components
602630 function normalizeExpire ( expire : Date | string | number | null | undefined , useUtcTimestamp : boolean = false ) : string | number | undefined {
@@ -920,12 +948,6 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
920948 status : values . status ,
921949 }
922950
923- // Remove next_plan.data_limit and next_plan.expire if next_plan.user_template_id is set
924- if ( preparedValues . next_plan && preparedValues . next_plan . user_template_id ) {
925- delete preparedValues . next_plan . data_limit
926- delete preparedValues . next_plan . expire
927- }
928-
929951 // Check if proxy settings are filled
930952 const hasProxySettings = values . proxy_settings && Object . values ( values . proxy_settings ) . some ( settings => settings && Object . values ( settings ) . some ( value => value !== undefined && value !== '' ) )
931953
@@ -950,18 +972,37 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
950972 }
951973 : undefined
952974
953- // Convert data_limit from GB to bytes
975+ let nextPlanData = undefined
976+ if ( nextPlanEnabled ) {
977+ const nextPlanFromValues = values . next_plan
978+ const hasValues = nextPlanFromValues && hasNextPlanValues ( nextPlanFromValues )
979+
980+ if ( hasValues ) {
981+ nextPlanData = { ...nextPlanFromValues }
982+
983+ if ( nextPlanData . user_template_id ) {
984+ delete nextPlanData . data_limit
985+ delete nextPlanData . expire
986+ }
987+ } else {
988+ nextPlanData = {
989+ expire : 0 ,
990+ data_limit : 0 ,
991+ }
992+ }
993+ }
994+
954995 const sendValues = {
955996 ...preparedValues ,
956997 data_limit : gbToBytes ( preparedValues . data_limit as any ) ,
957998 expire : preparedValues . expire ,
958- // Only include proxy_settings if they are filled
959999 ...( hasProxySettings ? { proxy_settings : cleanedProxySettings } : { } ) ,
960- // Force send undefined when Next Plan is disabled
961- next_plan : nextPlanEnabled ? preparedValues . next_plan : undefined ,
9621000 }
9631001
964- // Remove proxy_settings from the payload if it's empty or undefined
1002+ if ( nextPlanEnabled ) {
1003+ sendValues . next_plan = nextPlanData
1004+ }
1005+
9651006 if ( ! hasProxySettings ) {
9661007 delete sendValues . proxy_settings
9671008 }
@@ -1149,8 +1190,7 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
11491190 }
11501191 }
11511192 }
1152- // eslint-disable-next-line
1153- } , [ isDialogOpen , editingUser , generalSettings ] )
1193+ } , [ isDialogOpen , editingUser , generalSettings , form ] )
11541194
11551195
11561196 return (
@@ -1817,15 +1857,27 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
18171857 { activeTab === 'groups' && editingUser && (
18181858 < div className = "rounded-[--radius] border border-border p-4" >
18191859 < div className = "flex items-center justify-between" >
1820- < div className = "flex items-center gap-2" >
1860+ < div className = "flex items-center gap-2 cursor-pointer" onClick = { ( ) => {
1861+ const newValue = ! nextPlanEnabled
1862+ setNextPlanEnabled ( newValue )
1863+ if ( ! newValue ) {
1864+ setNextPlanManuallyDisabled ( true )
1865+ } else {
1866+ setNextPlanManuallyDisabled ( false )
1867+ }
1868+ } } >
18211869 < ListStart className = "h-4 w-4" />
18221870 < div > { t ( 'userDialog.nextPlanTitle' , { defaultValue : 'Next Plan' } ) } </ div >
18231871 </ div >
18241872 < Switch
18251873 checked = { nextPlanEnabled }
18261874 onCheckedChange = { value => {
18271875 setNextPlanEnabled ( value )
1828- // Trigger validation when Next Plan toggle changes
1876+ if ( ! value ) {
1877+ setNextPlanManuallyDisabled ( true )
1878+ } else {
1879+ setNextPlanManuallyDisabled ( false )
1880+ }
18291881 const currentValues = form . getValues ( )
18301882 const isValid = validateAllFields ( currentValues , touchedFields )
18311883 setIsFormValid ( isValid )
0 commit comments