1- import React from 'react'
2- import { Controller } from 'react-hook-form'
3- import { Button , TextField } from '@mui/material'
1+ import React , { useState } from 'react'
2+ import { Controller , useFormContext } from 'react-hook-form'
3+ import { TextField , InputAdornment , Button } from '@mui/material'
44
55import { BridgeInputProps } from './bridge-input.types'
66import { useBridgeInfo } from '@/hooks/use-bridge-info'
77import { config } from '@/config'
8- import useTariAccount from '@/store/account'
8+ import { useTariAccountStore } from '@/store/account'
9+ import { toDecimals } from '@/utils/formatters'
910
10- export const BridgeInput : React . FC < BridgeInputProps > = ( {
11- fromNetwork,
12- control,
13- errors,
14- } ) => {
11+ export const BridgeInput = ( { fromNetwork, availableBalance, remainingDailyLimit, isTari } : BridgeInputProps ) => {
12+ const [ valueLength , setValueLength ] = useState ( 5 )
1513 const { fromToken } = useBridgeInfo ( fromNetwork )
16- const { available_balance } = useTariAccount ( )
14+ const {
15+ control,
16+ formState : { errors } ,
17+ setValue,
18+ } = useFormContext ( )
19+
20+ const account = useTariAccountStore ( ( state ) => state . tariAccount )
21+
22+ const handleMaxClick = ( ) => {
23+ let maxValue = availableBalance
24+
25+ if ( isTari ) {
26+ maxValue = toDecimals ( availableBalance , config . TARI_DECIMAL_PLACES )
27+ }
28+
29+ setValue ( 'amount' , maxValue . toString ( ) , { shouldValidate : true } )
30+ setValueLength ( maxValue . toString ( ) . length )
31+ }
32+
33+ // Helper to block invalid keys
34+ const handleKeyDown = ( e : React . KeyboardEvent < HTMLInputElement > ) => {
35+ const allowedKeys = [
36+ 'Backspace' ,
37+ 'Tab' ,
38+ 'ArrowLeft' ,
39+ 'ArrowRight' ,
40+ 'Delete' ,
41+ 'Home' ,
42+ 'End' ,
43+ '.' , // Allow decimal point
44+ ]
45+ // Allow Ctrl/Cmd + A,C,V,X for copy/paste/select all
46+ if ( ( e . ctrlKey || e . metaKey ) && [ 'a' , 'c' , 'v' , 'x' ] . includes ( e . key . toLowerCase ( ) ) ) {
47+ return
48+ }
49+ // Allow digits and allowed keys only
50+ if ( ! allowedKeys . includes ( e . key ) && ( e . key < '0' || e . key > '9' ) ) {
51+ e . preventDefault ( )
52+ }
53+ }
54+
55+ const handleChange = ( onChange : ( value : string ) => void ) => ( e : React . ChangeEvent < HTMLInputElement > ) => {
56+ let value = e . target . value
57+
58+ // Remove non-digit characters except decimal point
59+ const parts = value . split ( '.' )
60+ const integerPart = parts [ 0 ] . replace ( / \D / g, '' ) // digits only
61+ const decimalPart = parts [ 1 ] ? parts [ 1 ] . replace ( / \D / g, '' ) : ''
62+
63+ // Limit integer part to max 10 digits
64+ const limitedInteger = integerPart . slice ( 0 , 10 )
65+
66+ // Reconstruct value with decimal part if any
67+ if ( parts . length > 1 ) {
68+ value = limitedInteger + '.' + decimalPart
69+ } else {
70+ value = limitedInteger
71+ }
72+
73+ setValueLength ( value . length )
74+ onChange ( value )
75+ }
76+
77+ const getFontSize = ( length : number ) => {
78+ if ( length < 10 ) return '22px'
79+ if ( length < 14 ) return '18px'
80+ if ( length < 18 ) return '14px'
81+ return '10px'
82+ }
83+
84+ const helperText = errors . amount ?. message
1785
1886 return (
1987 < Controller
2088 name = "amount"
2189 control = { control }
2290 rules = { {
23- required : 'Amount is required' ,
2491 min : {
2592 value : config . MIN_BRIDGE_AMOUNT ,
26- message : `You must bridge at least ${ config . MIN_BRIDGE_AMOUNT . toLocaleString ( ) } ${ fromToken } ` ,
93+ message : `Min amount is ${ config . MIN_BRIDGE_AMOUNT . toLocaleString ( ) } ${ fromToken } ` ,
2794 } ,
2895 max : {
2996 value : config . MAX_BRIDGE_AMOUNT ,
30- message : `Maximum amount is ${ config . MAX_BRIDGE_AMOUNT } ${ fromToken } ` ,
97+ message : `Max amount is ${ config . MAX_BRIDGE_AMOUNT } ${ fromToken } ` ,
3198 } ,
3299 pattern : {
33100 value : / ^ \d + ( \. \d { 0 , 6 } ) ? $ / ,
34- message : 'Maximum 6 decimal places allowed' ,
101+ message : 'Max 6 decimal places allowed' ,
35102 } ,
36103 validate : ( value ) => {
104+ if ( value === '' || value === undefined ) {
105+ return 'Amount is required'
106+ }
37107 const amount = parseFloat ( value )
38108 if ( isNaN ( amount ) ) {
39109 return 'Amount must be a valid number'
40110 }
41111 if ( amount > config . MAX_BRIDGE_AMOUNT ) {
42- return `Maximum amount is ${ config . MAX_BRIDGE_AMOUNT } ${ fromToken } `
112+ return `Max amount is ${ config . MAX_BRIDGE_AMOUNT } ${ fromToken } `
113+ }
114+ if ( isTari && amount > toDecimals ( availableBalance , config . TARI_DECIMAL_PLACES ) ) {
115+ return `Not enough ${ fromToken } `
43116 }
44- if ( amount > available_balance ) {
45- return `Amount exceeds your wallet balance`
117+ if ( ! isTari && amount > availableBalance ) {
118+ return `Not enough ${ fromToken } `
119+ }
120+ if ( remainingDailyLimit !== undefined && amount > remainingDailyLimit ) {
121+ return `Daily limit exceeded. Remaining: ${ remainingDailyLimit . toLocaleString ( ) } XTM`
46122 }
47123 return true
48124 } ,
@@ -54,43 +130,45 @@ export const BridgeInput: React.FC<BridgeInputProps> = ({
54130 variant = "standard"
55131 placeholder = "0"
56132 error = { Boolean ( errors . amount ) }
57- helperText = { errors . amount ?. message }
133+ helperText = { helperText as React . ReactNode }
134+ onKeyDown = { handleKeyDown }
135+ onChange = { handleChange ( field . onChange ) }
58136 InputProps = { {
137+ disableUnderline : true ,
138+ inputMode : 'decimal' ,
139+ style : {
140+ fontSize : getFontSize ( valueLength ) ,
141+ fontWeight : 500 ,
142+ minWidth : '180px' ,
143+ width : '100%' ,
144+ padding : 0 ,
145+ appearance : 'textfield' ,
146+ } ,
59147 endAdornment : (
60- < Button
61- onClick = { ( ) => field . onChange ( 100 ) }
62- sx = { {
63- color : '#888' ,
64- fontSize : '14px' ,
65- minWidth : 'unset' ,
66- padding : '0 5px' ,
67- height : '30px' ,
68- } }
69- >
70- Max
71- </ Button >
148+ < InputAdornment position = "end" >
149+ < Button
150+ onClick = { handleMaxClick }
151+ sx = { {
152+ marginRight : '-10px' ,
153+ zIndex : 1 ,
154+ fontSize : '12px' ,
155+ fontWeight : 500 ,
156+ textTransform : 'none' ,
157+ minWidth : 'unset' ,
158+ padding : '2px 8px' ,
159+ backgroundColor : 'transparent' ,
160+ border : '1px solid #888' ,
161+ '&:hover' : {
162+ backgroundColor : 'transparent' ,
163+ border : '1px solid #888' ,
164+ } ,
165+ } }
166+ >
167+ Max
168+ </ Button >
169+ </ InputAdornment >
72170 ) ,
73171 } }
74- slotProps = { {
75- input : {
76- disableUnderline : true ,
77- inputProps : {
78- style : {
79- fontSize : '30px' ,
80- fontWeight : 500 ,
81- width : '130px' ,
82- padding : 0 ,
83- appearance : 'textfield' ,
84- } ,
85- } ,
86- style : {
87- display : 'flex' ,
88- alignItems : 'center' ,
89- height : '35px' ,
90- paddingTop : '10px' ,
91- } ,
92- } ,
93- } }
94172 sx = { {
95173 '& .MuiInputBase-input' : {
96174 appearance : 'textfield' ,
0 commit comments