@@ -136,15 +136,14 @@ const ExpiryDateField = ({
136136
137137 return (
138138 < FormItem className = "flex flex-1 flex-col" >
139- < FormLabel > { label } </ FormLabel >
139+ < FormLabel className = 'mb-0.5' > { label } </ FormLabel >
140140 < div className = "relative" >
141141 < DatePicker
142142 mode = "single"
143143 date = { displayDate }
144144 onDateChange = { handleDateChange }
145145 showTime = { true }
146146 useUtcTimestamp = { useUtcTimestamp }
147- label = ""
148147 placeholder = { t ( 'userDialog.expireDate' , { defaultValue : 'Expire date' } ) }
149148 minDate = { now }
150149 maxDate = { maxDate }
@@ -154,7 +153,7 @@ const ExpiryDateField = ({
154153 onFieldChange = { handleFieldChange }
155154 />
156155 { expireInfo && (
157- < p className = { cn ( 'absolute top-full mt-1 whitespace-nowrap text-xs text-muted-foreground' , ! expireInfo . time && 'hidden' , dir === 'rtl' ? 'right-0' : 'left-0' ) } >
156+ < p className = { cn ( 'absolute top-full text-end right-0 mt-1 whitespace-nowrap text-xs text-muted-foreground' , ! expireInfo . time && 'hidden' , dir === 'rtl' ? 'right-0' : 'left-0' ) } >
158157 { expireInfo . time !== '0' && expireInfo . time !== '0s'
159158 ? t ( 'expires' , { time : expireInfo . time , defaultValue : 'Expires in {{time}}' } )
160159 : t ( 'expired' , { time : expireInfo . time , defaultValue : 'Expired in {{time}}' } ) }
@@ -282,6 +281,8 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
282281 } , [ isDialogOpen ] )
283282 const [ touchedFields , setTouchedFields ] = useState < Record < string , boolean > > ( { } )
284283 const [ isFormValid , setIsFormValid ] = useState ( false )
284+ // Ref to store raw input value for data_limit to allow typing decimals
285+ const dataLimitInputRef = React . useRef < string > ( '' )
285286
286287 const handleModalOpenChange = React . useCallback (
287288 ( open : boolean ) => {
@@ -291,6 +292,7 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
291292 setIsFormValid ( false )
292293 setActiveTab ( 'groups' )
293294 setSelectedTemplateId ( null )
295+ dataLimitInputRef . current = ''
294296 }
295297 onOpenChange ( open )
296298 } ,
@@ -1267,35 +1269,84 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
12671269 < FormField
12681270 control = { form . control }
12691271 name = "data_limit"
1270- render = { ( { field } ) => (
1271- < FormItem className = "h-full flex-1" >
1272- < FormLabel > { t ( 'userDialog.dataLimit' , { defaultValue : 'Data Limit (GB)' } ) } </ FormLabel >
1273- < FormControl >
1274- < Input
1275- type = "number"
1276- step = "any"
1277- min = "0"
1278- placeholder = { t ( 'userDialog.dataLimit' , { defaultValue : 'e.g. 1' } ) }
1279- { ...field }
1280- value = { field . value ? field . value : '' }
1281- onChange = { e => {
1282- const value = e . target . value === '' ? 0 : parseFloat ( e . target . value )
1283- if ( ! isNaN ( value ) && value >= 0 ) {
1284- field . onChange ( value )
1285- handleFieldChange ( 'data_limit' , value )
1286- }
1287- } }
1288- onBlur = { ( ) => {
1289- handleFieldChange ( 'data_limit' , field . value || 0 )
1290- } }
1291- />
1292- </ FormControl >
1293- { field . value !== null && field . value !== undefined && field . value > 0 && field . value < 1 && (
1294- < p className = "mt-1 text-xs text-muted-foreground" > { formatBytes ( Math . round ( field . value * 1024 * 1024 * 1024 ) ) } </ p >
1295- ) }
1296- < FormMessage />
1297- </ FormItem >
1298- ) }
1272+ render = { ( { field } ) => {
1273+ if ( dataLimitInputRef . current === '' && field . value !== null && field . value !== undefined && field . value > 0 ) {
1274+ dataLimitInputRef . current = String ( field . value )
1275+ } else if ( ( field . value === null || field . value === undefined ) && dataLimitInputRef . current !== '' ) {
1276+ dataLimitInputRef . current = ''
1277+ }
1278+
1279+ const displayValue = dataLimitInputRef . current !== ''
1280+ ? dataLimitInputRef . current
1281+ : ( field . value !== null && field . value !== undefined && field . value > 0 ? String ( field . value ) : '' )
1282+
1283+ return (
1284+ < FormItem className = "h-full flex-1 relative" >
1285+ < FormLabel > { t ( 'userDialog.dataLimit' , { defaultValue : 'Data Limit (GB)' } ) } </ FormLabel >
1286+ < FormControl >
1287+ < Input
1288+ type = "text"
1289+ inputMode = "decimal"
1290+ placeholder = { t ( 'userDialog.dataLimit' , { defaultValue : 'e.g. 1' } ) }
1291+ value = { displayValue }
1292+ onChange = { e => {
1293+ const rawValue = e . target . value . trim ( )
1294+
1295+ dataLimitInputRef . current = rawValue
1296+
1297+ if ( rawValue === '' ) {
1298+ field . onChange ( 0 )
1299+ handleFieldChange ( 'data_limit' , 0 )
1300+ return
1301+ }
1302+
1303+ const validNumberPattern = / ^ - ? \d * \. ? \d * $ /
1304+ if ( validNumberPattern . test ( rawValue ) ) {
1305+ if ( rawValue . endsWith ( '.' ) && rawValue . length > 1 ) {
1306+ const prevValue = field . value !== null && field . value !== undefined ? field . value : 0
1307+ field . onChange ( prevValue )
1308+ handleFieldChange ( 'data_limit' , prevValue )
1309+ } else if ( rawValue === '.' ) {
1310+ field . onChange ( 0 )
1311+ handleFieldChange ( 'data_limit' , 0 )
1312+ } else {
1313+ const numValue = parseFloat ( rawValue )
1314+ if ( ! isNaN ( numValue ) && numValue >= 0 ) {
1315+ field . onChange ( numValue )
1316+ handleFieldChange ( 'data_limit' , numValue )
1317+ }
1318+ }
1319+ }
1320+ } }
1321+ onBlur = { ( ) => {
1322+ const rawValue = dataLimitInputRef . current . trim ( )
1323+ if ( rawValue === '' || rawValue === '.' || rawValue === '0' ) {
1324+ dataLimitInputRef . current = ''
1325+ field . onChange ( 0 )
1326+ handleFieldChange ( 'data_limit' , 0 )
1327+ } else {
1328+ const numValue = parseFloat ( rawValue )
1329+ if ( ! isNaN ( numValue ) && numValue >= 0 ) {
1330+ const finalValue = numValue
1331+ dataLimitInputRef . current = finalValue > 0 ? String ( finalValue ) : ''
1332+ field . onChange ( finalValue )
1333+ handleFieldChange ( 'data_limit' , finalValue )
1334+ } else {
1335+ dataLimitInputRef . current = ''
1336+ field . onChange ( 0 )
1337+ handleFieldChange ( 'data_limit' , 0 )
1338+ }
1339+ }
1340+ } }
1341+ />
1342+ </ FormControl >
1343+ { field . value !== null && field . value !== undefined && field . value > 0 && field . value < 1 && (
1344+ < p className = "mt-1 text-end right-0 absolute top-full text-xs text-muted-foreground" > { formatBytes ( Math . round ( field . value * 1024 * 1024 * 1024 ) ) } </ p >
1345+ ) }
1346+ < FormMessage />
1347+ </ FormItem >
1348+ )
1349+ } }
12991350 />
13001351 { form . watch ( 'data_limit' ) !== undefined && form . watch ( 'data_limit' ) !== null && Number ( form . watch ( 'data_limit' ) ) > 0 && (
13011352 < FormField
@@ -1386,9 +1437,6 @@ export default function UserModal({ isDialogOpen, onOpenChange, form, editingUse
13861437 />
13871438 </ FormControl >
13881439 < FormMessage />
1389- { isTouched && isZeroOrEmpty && ! hasError && (
1390- < p className = "text-sm text-destructive" > { t ( 'validation.required' , { field : t ( 'userDialog.onHoldExpireDuration' , { defaultValue : 'On Hold Expire Duration' } ) } ) } </ p >
1391- ) }
13921440 </ FormItem >
13931441 )
13941442 } }
0 commit comments