Skip to content

Commit adb3c29

Browse files
committed
fix: address AI review - fix unit conversion, network token logic, and store naming
1 parent 948946d commit adb3c29

4 files changed

Lines changed: 133 additions & 53 deletions

File tree

components/bridge-input/bridge-input.tsx

Lines changed: 128 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,124 @@
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

55
import { BridgeInputProps } from './bridge-input.types'
66
import { useBridgeInfo } from '@/hooks/use-bridge-info'
77
import { 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',

components/bridge-input/bridge-input.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export type BridgeInputProps = {
88
fromNetwork: Network
99
availableBalance: number
1010
remainingDailyLimit?: number
11+
isTari: boolean
1112
}

config/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export const config = {
1212
MAINNET_EXPLORER_URL: 'https://etherscan.io',
1313
WXTM_CONTRACT_ADDRESS: '0xfD36fA88bb3feA8D1264fc89d70723b6a2B56958',
1414
MICRO_XTM_PER_XTM: 1_000_000,
15+
TARI_DECIMAL_PLACES: 6,
1516
}

utils/formatters.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ export enum FormatPreset {
88
COMPACT = 'compact',
99
}
1010

11-
const removeDecimals = (value: number, decimals: number) => {
11+
export const toDecimals = (value: number, decimals: number) => {
1212
return value / Math.pow(10, decimals)
1313
}
1414

1515
const removeCryptoDecimals = (value: number, decimals: number) => {
16-
return removeDecimals(value, decimals)
16+
return toDecimals(value, decimals)
1717
}
1818

1919
/**
@@ -130,4 +130,4 @@ export function formatHashrate(hashrate: number, joinUnit = true): string {
130130
}
131131
}
132132

133-
export { formatDecimalCompact, roundToTwoDecimals, removeDecimals }
133+
export { formatDecimalCompact, roundToTwoDecimals }

0 commit comments

Comments
 (0)