on master help sign in
chore: untested: update more form components
fdeb086Matt Condon (shrugs)
src/form/Inputs.js+24−2
@@ -13,6 +13,7 @@ import {
13 validateOneOf,
13 validateOneOf,
14 validateHexString,
14 validateHexString,
15 validateHexLength,
15 validateHexLength,
16+ validateEthereumAddress,
16} from 'lib/validators';
17} from 'lib/validators';
17import { compose } from 'lib/lib';
18import { compose } from 'lib/lib';
18import { prependSig } from 'lib/transformers';
19import { prependSig } from 'lib/transformers';
@@ -46,8 +47,10 @@ export const buildTicketValidator = (validators = []) =>
46 buildValidator([...validators, validateTicket, validateNotEmpty]);
47 buildValidator([...validators, validateTicket, validateNotEmpty]);
47export const buildMnemonicValidator = () =>
48export const buildMnemonicValidator = () =>
48 buildValidator([validateMnemonic, validateNotEmpty]);
49 buildValidator([validateMnemonic, validateNotEmpty]);
49−export const buildCheckboxValidator = () =>
50+export const buildCheckboxValidator = mustBe =>
50− buildValidator([validateOneOf([true, false])]);
51+ buildValidator([
52+ validateOneOf(mustBe !== undefined ? [mustBe] : [true, false]),
53+ ]);
51export const buildPassphraseValidator = () => buildValidator([]);
54export const buildPassphraseValidator = () => buildValidator([]);
52// TODO: validate hdpath format
55// TODO: validate hdpath format
53export const buildHdPathValidator = () => buildValidator([validateNotEmpty]);
56export const buildHdPathValidator = () => buildValidator([validateNotEmpty]);
@@ -66,6 +69,13 @@ export const buildHexValidator = length =>
66 validateNotEmpty,
69 validateNotEmpty,
67 ]);
70 ]);
68export const buildUploadValidator = () => buildValidator([validateNotEmpty]);
71export const buildUploadValidator = () => buildValidator([validateNotEmpty]);
72+export const buildAddressValidator = () =>
73+ buildValidator([
74+ validateEthereumAddress,
75+ validateHexLength(40),
76+ validateHexString,
77+ validateNotEmpty,
78+ ]);
69
79
70// the default form validator just returns field-level validations
80// the default form validator just returns field-level validations
71const kDefaultFormValidator = (values, errors) => errors;
81const kDefaultFormValidator = (values, errors) => errors;
@@ -205,3 +215,15 @@ export function HexInput({ ...rest }) {
205 />
215 />
206 );
216 );
207}
217}
218+
219+export function AddressInput({ ...rest }) {
220+ return (
221+ <Input
222+ type="text"
223+ placeholder={PLACEHOLDER_ADDRESS}
224+ autoComplete="off"
225+ mono
226+ {...rest}
227+ />
228+ );
229+}
src/lib/useInputs.js−89
@@ -57,64 +57,6 @@ function useFirstOf({ inputs, setValue, ...rest }, mapper = identity) {
57 ];
57 ];
58}
58}
59
59
60−const kMnemonicValidators = [validateMnemonic, validateNotEmpty];
61−export function useMnemonicInput(props) {
62− return useFirstOf(
63− useForm([
64− {
65− type: 'textarea',
66− autoComplete: 'off',
67− placeholder: PLACEHOLDER_MNEMONIC,
68− validators: kMnemonicValidators,
69− ...props,
70− },
71− ])
72− );
73−}
74−
75−export function useHdPathInput(props) {
76− return useFirstOf(
77− useForm([
78− {
79− type: 'text',
80− autoComplete: 'off',
81− placeholder: PLACEHOLDER_HD_PATH,
82− ...props,
83− },
84− ])
85− );
86−}
87−
88−const kTicketValidators = [validateTicket, validateNotEmpty];
89−//TODO needs to be fancier, displaying sig and dashes instead of •ing all
90−const kTicketTransformers = [prependSig];
91−export function useTicketInput({ validators = [], deriving = false, ...rest }) {
92− return useFirstOf(
93− useForm([
94− {
95− type: 'password',
96− label: 'Ticket',
97− placeholder: PLACEHOLDER_TICKET,
98− validators: useMemo(() => [...validators, ...kTicketValidators], [
99− validators,
100− ]),
101− transformers: kTicketTransformers,
102− mono: true,
103− ...rest,
104− },
105− ]),
106− ({ error, pass }) => ({
107− accessory: error ? (
108− <AccessoryIcon.Failure />
109− ) : deriving ? (
110− <AccessoryIcon.Pending />
111− ) : pass ? (
112− <AccessoryIcon.Success />
113− ) : null,
114− })
115− );
116−}
117−
118const kPointTransformers = [prependSig];
60const kPointTransformers = [prependSig];
119export function usePointInput({ size = 4, validators = [], ...rest }) {
61export function usePointInput({ size = 4, validators = [], ...rest }) {
120 const _validators = useMemo(
62 const _validators = useMemo(
@@ -162,37 +104,6 @@ export function useGalaxyInput(props) {
162 });
104 });
163}
105}
164
106
165−export function useCheckboxInput({ initialValue, ...rest }) {
166− return useFirstOf(
167− useForm([
168− {
169− type: 'checkbox',
170− ...rest,
171− },
172− ])
173− );
174−}
175−
176−export function useSelectInput({ initialValue, options, ...rest }) {
177− return useFirstOf(
178− useForm([
179− {
180− type: 'select',
181− validators: useMemo(
182− () => [
183− validateOneOf(options.map(option => option.value)),
184− validateNotEmpty,
185− ],
186− [options]
187− ),
188− options,
189− initialValue: initialValue || options[0].value,
190− ...rest,
191− },
192− ])
193− );
194−}
195−
196const kAddressValidators = [
107const kAddressValidators = [
197 validateEthereumAddress,
108 validateEthereumAddress,
198 validateNotNullAddress,
109 validateNotNullAddress,
src/views/Admin/AdminSetProxy.js+86−57
@@ -1,6 +1,6 @@
1−import React, { useCallback, useEffect } from 'react';
1+import React, { useCallback, useMemo } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3−import { Grid, Text, Input, Flex, ToggleInput } from 'indigo-react';
3+import { Grid, Text, Flex, ToggleInput } from 'indigo-react';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
5
5
6import { useNetwork } from 'store/network';
6import { useNetwork } from 'store/network';
@@ -16,12 +16,18 @@ import * as need from 'lib/need';
16import { useLocalRouter } from 'lib/LocalRouter';
16import { useLocalRouter } from 'lib/LocalRouter';
17import { ETH_ZERO_ADDR, eqAddr, isZeroAddress } from 'lib/wallet';
17import { ETH_ZERO_ADDR, eqAddr, isZeroAddress } from 'lib/wallet';
18import capitalize from 'lib/capitalize';
18import capitalize from 'lib/capitalize';
19−import { useAddressInput, useCheckboxInput } from 'lib/useInputs';
20import useEthereumTransaction from 'lib/useEthereumTransaction';
19import useEthereumTransaction from 'lib/useEthereumTransaction';
21
20
22import ViewHeader from 'components/ViewHeader';
21import ViewHeader from 'components/ViewHeader';
23import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
22import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
24import { GAS_LIMITS } from 'lib/constants';
23import { GAS_LIMITS } from 'lib/constants';
24+import {
25+ composeValidator,
26+ buildCheckboxValidator,
27+ buildAddressValidator,
28+ AddressInput,
29+} from 'form/Inputs';
30+import BridgeForm from 'form/BridgeForm';
25
31
26const proxyFromDetails = (details, contracts, proxyType) => {
32const proxyFromDetails = (details, contracts, proxyType) => {
27 switch (proxyType) {
33 switch (proxyType) {
@@ -107,41 +113,41 @@ export default function AdminSetProxy() {
107 bind,
113 bind,
108 } = useSetProxy(data.proxyType);
114 } = useSetProxy(data.proxyType);
109
115
110− const [unsetInput, { data: isUnsetting }] = useCheckboxInput({
116+ const validateForm = useCallback((values, errors) => {
111− name: 'unset',
117+ if (!values.unset && errors.address) {
112− label: 'Unset',
118+ return errors;
113− inverseLabel: 'Specify',
114− initialValue: false,
115− disabled: inputsLocked,
116− });
117− const [
118− addressInput,
119− { pass: validAddress, data: address },
120− { reset: resetAddress },
121− ] = useAddressInput({
122− name: 'address',
123− label: `New ${properProxyType} Address`,
124− disabled: inputsLocked || isUnsetting,
125− });
126−
127− useEffect(() => {
128− if (isUnsetting) {
129− unset();
130− resetAddress();
131− } else if (validAddress) {
132− construct(address);
133− } else {
134− unconstruct();
135 }
119 }
120+
121+ return {};
136− }, [
122+ }, []);
137− validAddress,
123+
124+ const validate = useMemo(
125+ () =>
126+ composeValidator(
127+ {
128+ unset: buildCheckboxValidator(),
138− address,
129+ address: buildAddressValidator(),
139− construct,
130+ },
140− isUnsetting,
131+ validateForm
132+ ),
133+ [validateForm]
134+ );
135+
136+ const onValues = useCallback(
137+ ({ valid, values, form }) => {
138+ if (valid) {
139+ if (values.unset) {
141− unset,
140+ unset();
141+ form.change('address', '');
142+ } else {
143+ construct(values.address);
144+ }
145+ } else {
142− unconstruct,
146+ unconstruct();
143− resetAddress,
147+ }
148+ },
149+ [construct, unconstruct, unset]
144− ]);
150+ );
145
151
146 const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
152 const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
147 const isProxySet = !isZeroAddress(proxyAddress);
153 const isProxySet = !isZeroAddress(proxyAddress);
@@ -169,27 +175,50 @@ export default function AdminSetProxy() {
169 {proxyAddressLabel}
175 {proxyAddressLabel}
170 </Grid.Item>
176 </Grid.Item>
171
177
178+ <BridgeForm
179+ validate={validate}
180+ onValues={onValues}
181+ onSubmit={() => {}}
182+ initialValues={{ unset: false }}>
183+ {({ handleSubmit, values }) => (
184+ <>
172− <Grid.Item full as={Flex} row justify="between" align="center">
185+ <Grid.Item full as={Flex} row justify="between" align="center">
173− <Flex.Item
186+ <Flex.Item
174− flex
187+ flex
175− as={Text}
188+ as={Text}
176− className={cn('mono', {
189+ className={cn('mono', {
177− black: !completed && isProxySet,
190+ black: !completed && isProxySet,
178− gray4: !completed && !isProxySet,
191+ gray4: !completed && !isProxySet,
179− green3: completed,
192+ green3: completed,
180− })}>
193+ })}>
181− {isProxySet ? proxyAddress : 'Unset'}
194+ {isProxySet ? proxyAddress : 'Unset'}
182− </Flex.Item>
195+ </Flex.Item>
183− {!completed && isProxySet && (
196+ {!completed && isProxySet && (
197+ <Flex.Item
184− <Flex.Item as={ToggleInput} {...unsetInput} />
198+ as={ToggleInput}
199+ name="unset"
200+ label="Unset"
201+ inverseLabel="Specify"
202+ disabled={inputsLocked}
203+ />
204+ )}
205+ </Grid.Item>
206+
207+ {completed ? (
208+ <Grid.Item full className="mb4" />
209+ ) : (
210+ <Grid.Item
211+ full
212+ as={AddressInput}
213+ className="mv4"
214+ name="address"
215+ label={`New ${properProxyType} Address`}
216+ disabled={inputsLocked || values.unset}
217+ />
218+ )}
219+ </>
185 )}
220 )}
186− </Grid.Item>
221+ </BridgeForm>
187−
188− {completed ? (
189− <Grid.Item full className="mb4" />
190− ) : (
191− <Grid.Item full as={Input} {...addressInput} className="mv4" />
192− )}
193
222
194 <Grid.Item
223 <Grid.Item
195 full
224 full
src/views/Admin/Reticket/ReticketVerify.js+35−21
@@ -1,12 +1,17 @@
1import React, { useCallback, useMemo } from 'react';
1import React, { useCallback, useMemo } from 'react';
2−import { Input, Text, Grid } from 'indigo-react';
2+import { Text, Grid } from 'indigo-react';
3
3
4−import { useTicketInput } from 'lib/useInputs';
5import { useLocalRouter } from 'lib/LocalRouter';
4import { useLocalRouter } from 'lib/LocalRouter';
6import { validateExactly } from 'lib/validators';
5import { validateExactly } from 'lib/validators';
7import { isDevelopment } from 'lib/flags';
6import { isDevelopment } from 'lib/flags';
8
7
9−import { ForwardButton } from 'components/Buttons';
8+import BridgeForm from 'form/BridgeForm';
9+import {
10+ TicketInput,
11+ composeValidator,
12+ buildTicketValidator,
13+} from 'form/Inputs';
14+import SubmitButton from 'form/SubmitButton';
10
15
11const STUB_VERIFY_TICKET = isDevelopment;
16const STUB_VERIFY_TICKET = isDevelopment;
12
17
@@ -14,17 +19,15 @@ export default function ReticketVerify({ newWallet }) {
14 const { push, names } = useLocalRouter();
19 const { push, names } = useLocalRouter();
15
20
16 const ticket = newWallet.value.wallet.ticket;
21 const ticket = newWallet.value.wallet.ticket;
17− const validators = useMemo(
22+ const validate = useMemo(
23+ () =>
24+ composeValidator({
25+ ticket: buildTicketValidator([
18− () => [validateExactly(ticket, 'Does not match expected master ticket.')],
26+ validateExactly(ticket, 'Does not match expected master ticket.'),
27+ ]),
28+ }),
19 [ticket]
29 [ticket]
20 );
30 );
21− const [ticketInput, { pass }] = useTicketInput({
22− name: 'ticket',
23− label: 'New master ticket',
24− initialValue: STUB_VERIFY_TICKET ? ticket : undefined,
25− autoFocus: true,
26− validators,
27− });
28
31
29 const goExecute = useCallback(() => push(names.EXECUTE), [push, names]);
32 const goExecute = useCallback(() => push(names.EXECUTE), [push, names]);
30
33
@@ -33,15 +36,26 @@ export default function ReticketVerify({ newWallet }) {
33 <Grid.Item full as={Text}>
36 <Grid.Item full as={Text}>
34 Verify New Master Ticket
37 Verify New Master Ticket
35 </Grid.Item>
38 </Grid.Item>
39+ <BridgeForm
40+ validate={validate}
41+ onSubmit={goExecute}
42+ initialVaues={{
43+ ticket: STUB_VERIFY_TICKET ? ticket : 'undefined',
44+ }}>
45+ {({ handleSubmit }) => (
46+ <>
36− <Grid.Item full as={Input} {...ticketInput} />
47+ <Grid.Item
37− <Grid.Item
48+ full
38− full
49+ as={TicketInput}
39− as={ForwardButton}
50+ name="ticket"
40− solid
51+ label="New master ticket"
41− disabled={!pass}
52+ />
42− onClick={goExecute}>
53+ <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
43− Verify & Reticket
54+ Verify & Reticket
44− </Grid.Item>
55+ </Grid.Item>
56+ </>
57+ )}
58+ </BridgeForm>
45 </Grid>
59 </Grid>
46 );
60 );
47}
61}
src/views/Disclaimer.js+25−16
@@ -1,24 +1,26 @@
1−import React, { useCallback } from 'react';
1+import React, { useCallback, useMemo } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Grid, H3, B, Text, CheckboxInput } from 'indigo-react';
3import { Grid, H3, B, Text, CheckboxInput } from 'indigo-react';
4
4
5−import { useCheckboxInput } from 'lib/useInputs';
6import { useHistory } from 'store/history';
5import { useHistory } from 'store/history';
7import useHasDisclaimed from 'lib/useHasDisclaimed';
6import useHasDisclaimed from 'lib/useHasDisclaimed';
8
7
9import View from 'components/View';
8import View from 'components/View';
10−import { ForwardButton } from 'components/Buttons';
11import WarningBox from 'components/WarningBox';
9import WarningBox from 'components/WarningBox';
10+import BridgeForm from 'form/BridgeForm';
11+import SubmitButton from 'form/SubmitButton';
12+import { composeValidator, buildCheckboxValidator } from 'form/Inputs';
12
13
13const TEXT_STYLE = 'f5';
14const TEXT_STYLE = 'f5';
14
15
15export default function ActivateDisclaimer() {
16export default function ActivateDisclaimer() {
16 const { pop } = useHistory();
17 const { pop } = useHistory();
17 const [, setHasDisclaimed] = useHasDisclaimed();
18 const [, setHasDisclaimed] = useHasDisclaimed();
18− const [understoodInput, { data: isUnderstood }] = useCheckboxInput({
19+
19− name: 'checkbox',
20+ const validate = useMemo(
20− label: 'I acknowledge and understand these rights',
21+ () => composeValidator({ checkbox: buildCheckboxValidator(true) }),
22+ []
21− });
23+ );
22
24
23 const goBack = useCallback(async () => {
25 const goBack = useCallback(async () => {
24 setHasDisclaimed(true);
26 setHasDisclaimed(true);
@@ -76,15 +78,22 @@ export default function ActivateDisclaimer() {
76 <Grid.Item full as={WarningBox}>
78 <Grid.Item full as={WarningBox}>
77 Warning: Nobody but you can restore or reset your Master Ticket
79 Warning: Nobody but you can restore or reset your Master Ticket
78 </Grid.Item>
80 </Grid.Item>
81+ <BridgeForm validate={validate} onSubmit={goBack}>
82+ {({ handleSubmit }) => (
83+ <>
84+ <Grid.Item
85+ full
79− <Grid.Item as={CheckboxInput} {...understoodInput} full />
86+ as={CheckboxInput}
80− <Grid.Item
87+ name="checkbox"
81− as={ForwardButton}
88+ label="I acknowledge and understand these rights"
82− disabled={!isUnderstood}
89+ />
83− onClick={goBack}
90+
84− solid
91+ <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
85− full>
86− Continue
92+ Continue
87− </Grid.Item>
93+ </Grid.Item>
94+ </>
95+ )}
96+ </BridgeForm>
88 </Grid>
97 </Grid>
89 </View>
98 </View>
90 );
99 );