Skip to content

Commit c6a444d

Browse files
authored
Advanced filter: validate address input (#3295)
1 parent 67ce6ae commit c6a444d

6 files changed

+42
-10
lines changed

ui/advancedFilter/FilterByColumn.pw.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ const filters = {
2222
methods: [ '0xa9059cbb' ],
2323
age: '7d' as const,
2424
address_relation: 'or' as const,
25-
from_address_hashes_to_include: [ '0x123' ],
26-
to_address_hashes_to_include: [ '0x456' ],
25+
from_address_hashes_to_include: [ '0x1230000000000000000000000000000000000000' ],
26+
to_address_hashes_to_include: [ '0x4560000000000000000000000000000000000000' ],
2727
amount_from: '100',
2828
token_contract_symbols_to_include: [ 'ETH' ],
2929
token_contract_address_hashes_to_include: [ 'native' ],
138 Bytes
Loading
173 Bytes
Loading
175 Bytes
Loading
150 Bytes
Loading

ui/advancedFilter/filters/AddressFilter.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import React from 'react';
55

66
import type { AdvancedFilterParams } from 'types/api/advancedFilter';
77

8+
import { Field } from 'toolkit/chakra/field';
89
import { Input } from 'toolkit/chakra/input';
910
import { InputGroup } from 'toolkit/chakra/input-group';
1011
import { Select } from 'toolkit/chakra/select';
1112
import AddButton from 'toolkit/components/buttons/AddButton';
1213
import { ClearButton } from 'toolkit/components/buttons/ClearButton';
14+
import { ADDRESS_REGEXP } from 'toolkit/utils/regexp';
1315
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
1416

1517
const FILTER_PARAM_TO_INCLUDE = 'to_address_hashes_to_include';
@@ -42,8 +44,10 @@ type InputProps = {
4244
isLast: boolean;
4345
onModeChange: ({ value }: { value: Array<string> }) => void;
4446
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
47+
onBlur: () => void;
4548
onClear: () => void;
4649
onAddFieldClick: () => void;
50+
isInvalid: boolean;
4751
};
4852

4953
type AddressFilter = {
@@ -55,9 +59,9 @@ function addressFilterToKey(filter: AddressFilter) {
5559
return `${ filter.address.toLowerCase() }-${ filter.mode }`;
5660
}
5761

58-
const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, isLast, onAddFieldClick }: InputProps) => {
62+
const AddressFilterInput = ({ address, mode, onModeChange, onChange, onBlur, onClear, isLast, onAddFieldClick, isInvalid }: InputProps) => {
5963
return (
60-
<Flex alignItems="center" w="100%">
64+
<Flex alignItems="flex-start" w="100%">
6165
<Select
6266
collection={ collection }
6367
placeholder="Select mode"
@@ -68,12 +72,17 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is
6872
flexShrink={ 0 }
6973
mr={ 3 }
7074
/>
71-
<InputGroup
75+
<Field
7276
flexGrow={ 1 }
73-
endElement={ <ClearButton onClick={ onClear } mx={ 2 } disabled={ !address }/> }
77+
invalid={ isInvalid }
78+
errorText="Invalid address format"
7479
>
75-
<Input value={ address } onChange={ onChange } placeholder="Smart contract / Address (0x...)*" size="sm" autoComplete="off"/>
76-
</InputGroup>
80+
<InputGroup
81+
endElement={ <ClearButton onClick={ onClear } mx={ 2 } disabled={ !address }/> }
82+
>
83+
<Input value={ address } onChange={ onChange } onBlur={ onBlur } placeholder="Smart contract / Address (0x...)*" size="sm" autoComplete="off"/>
84+
</InputGroup>
85+
</Field>
7786
{ isLast && (
7887
<AddButton
7988
ml={ 2 }
@@ -89,6 +98,7 @@ const emptyItem = { address: '', mode: 'include' as AddressFilterMode };
8998
const AddressFilter = ({ type, value = [], handleFilterChange }: Props) => {
9099
const [ currentValue, setCurrentValue ] =
91100
React.useState<Array<AddressFilter>>([ ...value, emptyItem ]);
101+
const [ touched, setTouched ] = React.useState<Array<boolean>>(value.map(() => true).concat(false));
92102

93103
const handleModeSelectChange = React.useCallback((index: number) => ({ value }: { value: Array<string> }) => {
94104
setCurrentValue(prev => {
@@ -103,6 +113,11 @@ const AddressFilter = ({ type, value = [], handleFilterChange }: Props) => {
103113
newVal[index] = { ...newVal[index], address: '' };
104114
return newVal;
105115
});
116+
setTouched(prev => {
117+
const newTouched = [ ...prev ];
118+
newTouched[index] = false;
119+
return newTouched;
120+
});
106121
}, []);
107122

108123
const handleAddressChange = React.useCallback((index: number) => (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -115,11 +130,23 @@ const AddressFilter = ({ type, value = [], handleFilterChange }: Props) => {
115130
});
116131
}, []);
117132

133+
const handleAddressBlur = React.useCallback((index: number) => () => {
134+
setTouched(prev => {
135+
const newTouched = [ ...prev ];
136+
newTouched[index] = true;
137+
return newTouched;
138+
});
139+
}, []);
140+
118141
const onAddFieldClick = React.useCallback(() => {
119142
setCurrentValue(prev => [ ...prev, emptyItem ]);
143+
setTouched(prev => [ ...prev, false ]);
120144
}, []);
121145

122-
const onReset = React.useCallback(() => setCurrentValue([ emptyItem ]), []);
146+
const onReset = React.useCallback(() => {
147+
setCurrentValue([ emptyItem ]);
148+
setTouched([ false ]);
149+
}, []);
123150

124151
const onFilter = React.useCallback(() => {
125152
const includeFilterParam = type === 'from' ? FILTER_PARAM_FROM_INCLUDE : FILTER_PARAM_TO_INCLUDE;
@@ -131,11 +158,14 @@ const AddressFilter = ({ type, value = [], handleFilterChange }: Props) => {
131158
handleFilterChange(excludeFilterParam, excludeValue.length ? excludeValue : undefined);
132159
}, [ handleFilterChange, currentValue, type ]);
133160

161+
const hasErrors = currentValue.some(i => Boolean(i.address) && !ADDRESS_REGEXP.test(i.address));
162+
const isTouched = !isEqual(currentValue.filter(i => i.address).map(addressFilterToKey).sort(), value.map(addressFilterToKey).sort());
163+
134164
return (
135165
<TableColumnFilter
136166
title={ type === 'from' ? 'From address' : 'To address' }
137167
isFilled={ Boolean(currentValue[0].address) }
138-
isTouched={ !isEqual(currentValue.filter(i => i.address).map(addressFilterToKey).sort(), value.map(addressFilterToKey).sort()) }
168+
isTouched={ isTouched && !hasErrors }
139169
onFilter={ onFilter }
140170
onReset={ onReset }
141171
hasReset
@@ -149,8 +179,10 @@ const AddressFilter = ({ type, value = [], handleFilterChange }: Props) => {
149179
isLast={ index === currentValue.length - 1 }
150180
onModeChange={ handleModeSelectChange(index) }
151181
onChange={ handleAddressChange(index) }
182+
onBlur={ handleAddressBlur(index) }
152183
onClear={ handleAddressClear(index) }
153184
onAddFieldClick={ onAddFieldClick }
185+
isInvalid={ Boolean(touched[index]) && Boolean(item.address) && !ADDRESS_REGEXP.test(item.address) }
154186
/>
155187
)) }
156188
</VStack>

0 commit comments

Comments
 (0)