on master help sign in
feat: implement dropdowns, tidy up hardware login
ceff015Matt Condon (shrugs)
src/components/Inputs.js+24−1
@@ -14,6 +14,7 @@ import {
14 validateEmail,
14 validateEmail,
15 validateLength,
15 validateLength,
16 validateHexString,
16 validateHexString,
17+ validateOneOf,
17} from 'lib/validators';
18} from 'lib/validators';
18import { prependSig } from 'lib/transformers';
19import { prependSig } from 'lib/transformers';
19
20
@@ -187,7 +188,7 @@ export function usePointInput(rest) {
187 accessory: lastValidPoint ? (
188 accessory: lastValidPoint ? (
188 <InputSigil
189 <InputSigil
189 patp={lastValidPoint}
190 patp={lastValidPoint}
190− size={68}
191+ size={44}
191 margin={8}
192 margin={8}
192 pass={pass}
193 pass={pass}
193 focused={focused}
194 focused={focused}
@@ -209,6 +210,28 @@ export function useCheckboxInput({ initialValue, ...rest }) {
209 );
210 );
210}
211}
211
212
213+export function useSelectInput({ initialValue, options, ...rest }) {
214+ return useFirstOf(
215+ useForm([
216+ {
217+ type: 'select',
218+ validators: useMemo(
219+ () => [
220+ validateOneOf(
221+ options.map(option => option.value),
222+ validateNotEmpty
223+ ),
224+ ],
225+ [options]
226+ ),
227+ options,
228+ initialValue: initialValue || options[0].value,
229+ ...rest,
230+ },
231+ ])
232+ );
233+}
234+
212const kEmailValidators = [validateEmail, validateNotEmpty];
235const kEmailValidators = [validateEmail, validateNotEmpty];
213export const buildEmailInputConfig = extra => ({
236export const buildEmailInputConfig = extra => ({
214 type: 'email',
237 type: 'email',
src/indigo-react/components/CheckboxInput.js−4
@@ -12,9 +12,6 @@ export default function CheckboxInput({
12
12
13 // callbacks
13 // callbacks
14 onValue,
14 onValue,
15− onPass,
16− onError,
17− onFocus,
18 onEnter,
15 onEnter,
19
16
20 // state from hook
17 // state from hook
@@ -31,7 +28,6 @@ export default function CheckboxInput({
31 initialValue,
28 initialValue,
32 validators,
29 validators,
33 transformers,
30 transformers,
34− setValue,
35
31
36 // extra
32 // extra
37 ...rest
33 ...rest
src/indigo-react/components/Flex.js+21−13
@@ -1,21 +1,25 @@
1import React from 'react';
1import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3
3
4−function Flex({
4+const Flex = React.forwardRef(function Flex(
5+ {
5− as: As = 'div',
6+ as: As = 'div',
6− row = false,
7+ row = false,
7− wrap = false,
8+ wrap = false,
8− col = false,
9+ col = false,
9− align,
10+ align,
10− justify,
11+ justify,
11− className,
12+ className,
12− ...rest
13+ ...rest
14+ },
15+ ref
13−}) {
16+) {
14 if (row && col) {
17 if (row && col) {
15 throw new Error('Only one of row or col must be true, not both.');
18 throw new Error('Only one of row or col must be true, not both.');
16 }
19 }
17 return (
20 return (
18 <As
21 <As
22+ ref={ref}
19 className={cn(
23 className={cn(
20 'flex',
24 'flex',
21 {
25 {
@@ -32,16 +36,20 @@ function Flex({
32 {...rest}
36 {...rest}
33 />
37 />
34 );
38 );
35−}
39+});
36
40
37// flex can be boolean {true} or integer flex
41// flex can be boolean {true} or integer flex
42+Flex.Item = React.forwardRef(function FlexItem(
38−Flex.Item = function FlexItem({ as: As = 'div', flex, className, ...rest }) {
43+ { as: As = 'div', flex, className, ...rest },
44+ ref
45+) {
39 if (flex === true) {
46 if (flex === true) {
40 flex = 1;
47 flex = 1;
41 }
48 }
42
49
43 return (
50 return (
44 <As
51 <As
52+ ref={ref}
45 className={cn(
53 className={cn(
46 {
54 {
47 [`flex${flex}`]: flex,
55 [`flex${flex}`]: flex,
@@ -51,6 +59,6 @@ Flex.Item = function FlexItem({ as: As = 'div', flex, className, ...rest }) {
51 {...rest}
59 {...rest}
52 />
60 />
53 );
61 );
54−};
62+});
55
63
56export default Flex;
64export default Flex;
src/indigo-react/components/Grid.js+24−16
@@ -1,9 +1,13 @@
1import React from 'react';
1import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3
3
4+const Grid = React.forwardRef(function Grid(
4−function Grid({ as: As = 'div', gap = 0, align, justify, className, ...rest }) {
5+ { as: As = 'div', gap = 0, align, justify, className, ...rest },
6+ ref
7+) {
5 return (
8 return (
6 <As
9 <As
10+ ref={ref}
7 className={cn(
11 className={cn(
8 'grid12',
12 'grid12',
9 gap && `gap${gap}`,
13 gap && `gap${gap}`,
@@ -16,23 +20,27 @@ function Grid({ as: As = 'div', gap = 0, align, justify, className, ...rest }) {
16 {...rest}
20 {...rest}
17 />
21 />
18 );
22 );
19−}
23+});
20
24
21−Grid.Item = function GridItem({
25+Grid.Item = React.forwardRef(function GridItem(
26+ {
22− as: As = 'div',
27+ as: As = 'div',
23− full = false,
28+ full = false,
24− half = 0,
29+ half = 0,
25− third = 0,
30+ third = 0,
26− fourth = 0,
31+ fourth = 0,
27− rows = [],
32+ rows = [],
28− cols = [],
33+ cols = [],
29− justifySelf,
34+ justifySelf,
30− alignSelf,
35+ alignSelf,
31− className,
36+ className,
32− ...rest
37+ ...rest
38+ },
39+ ref
33−}) {
40+) {
34 return (
41 return (
35 <As
42 <As
43+ ref={ref}
36 className={cn(
44 className={cn(
37 {
45 {
38 full,
46 full,
@@ -53,7 +61,7 @@ Grid.Item = function GridItem({
53 {...rest}
61 {...rest}
54 />
62 />
55 );
63 );
56−};
64+});
57
65
58Grid.Divider = function GridDivider({
66Grid.Divider = function GridDivider({
59 vertical = false,
67 vertical = false,
src/indigo-react/components/Input.js+3−19
@@ -14,9 +14,6 @@ export default function Input({
14
14
15 // callbacks
15 // callbacks
16 onValue,
16 onValue,
17− onPass,
18− onError,
19− onFocus,
20 onEnter,
17 onEnter,
21
18
22 // state from hook
19 // state from hook
@@ -33,7 +30,6 @@ export default function Input({
33 initialValue,
30 initialValue,
34 validators,
31 validators,
35 transformers,
32 transformers,
36− setValue,
37
33
38 // extra
34 // extra
39 ...rest
35 ...rest
@@ -50,25 +46,13 @@ export default function Input({
50 );
46 );
51
47
52 // notify parent of value only when passing
48 // notify parent of value only when passing
49+ // TODO: deprecate/remove this
50+ // keeping it around for now to avoid refactoring InviteEmail.js, but this
51+ // feels like a hack
53 useEffect(() => {
52 useEffect(() => {
54 pass && onValue && onValue(data);
53 pass && onValue && onValue(data);
55 }, [pass, data]); // eslint-disable-line react-hooks/exhaustive-deps
54 }, [pass, data]); // eslint-disable-line react-hooks/exhaustive-deps
56
55
57− // notify parent of pass
58− useEffect(() => {
59− onPass && onPass(pass);
60− }, [pass]); // eslint-disable-line react-hooks/exhaustive-deps
61−
62− // notify parent of error whenever error changes
63− useEffect(() => {
64− onError && onError(error);
65− }, [error]); // eslint-disable-line react-hooks/exhaustive-deps
66−
67− // notify parent of focus
68− useEffect(() => {
69− onFocus && onFocus(focused);
70− }, [focused]); // eslint-disable-line react-hooks/exhaustive-deps
71−
72 return (
56 return (
73 <Flex
57 <Flex
74 className={cn(className, 'mb1')}
58 className={cn(className, 'mb1')}
src/indigo-react/components/SelectInput.jsnew file+147
@@ -0,0 +1,147 @@
1+import React, { useCallback, useRef, useState } from 'react';
2+import cn from 'classnames';
3+
4+import Flex from './Flex';
5+import { ErrorText } from './Typography';
6+import useOnClickOutside from 'indigo-react/lib/useOnClickOutside';
7+import AccessoryIcon from './AccessoryIcon';
8+
9+export default function SelectInput({
10+ // visuals
11+ name,
12+ label,
13+ className,
14+ accessory,
15+ mono = false,
16+ placeholder,
17+
18+ // callbacks
19+ onValue,
20+ onEnter,
21+
22+ // state from hook
23+ focused,
24+ pass,
25+ visiblyPassed,
26+ error,
27+ data,
28+ bind,
29+ autoFocus,
30+ disabled,
31+ options,
32+
33+ // ignored
34+ initialValue,
35+ validators,
36+ transformers,
37+
38+ // extra
39+ ...rest
40+}) {
41+ const [isOpen, setIsOpen] = useState(false);
42+ const ref = useRef();
43+
44+ // close select on outside clicks
45+ useOnClickOutside(ref, useCallback(() => setIsOpen(false), [setIsOpen]));
46+
47+ const toggleOpen = useCallback(() => setIsOpen(isOpen => !isOpen), [
48+ setIsOpen,
49+ ]);
50+
51+ const onChange = value => {
52+ // TODO: provide setValue here?
53+ // construct a pseudo event that sets the value correctly
54+ bind.onChange({ target: { value } });
55+ setIsOpen(false);
56+ };
57+
58+ // redefine accessory because we still want to ignore it from the ..rest above
59+ accessory = <AccessoryIcon>{isOpen ? '⬆' : '⬇'}</AccessoryIcon>;
60+
61+ const text = options.find(o => o.value === data).text;
62+
63+ return (
64+ <Flex
65+ ref={ref}
66+ className={cn(className, 'mb1 us-none')}
67+ col
68+ style={{
69+ ...(disabled && {
70+ pointerEvents: 'none',
71+ cursor: 'not-allowed',
72+ }),
73+ }}>
74+ <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
75+ {label}
76+ </Flex.Item>
77+ <Flex.Item as={Flex} row className="rel clickable" onClick={toggleOpen}>
78+ <Flex.Item
79+ // as="select"
80+ className={cn(
81+ 'b b1 p3 outline-none',
82+ { mono },
83+ {
84+ 'bg-white': !disabled,
85+ 'bg-gray1': disabled,
86+ },
87+ {
88+ gray4: isOpen,
89+ black: !isOpen,
90+ },
91+ {
92+ 'b-green3': visiblyPassed,
93+ 'b-black': focused && !visiblyPassed,
94+ 'b-yellow3': !focused && error,
95+ 'b-gray3': !focused && !error && !visiblyPassed,
96+ }
97+ )}
98+ id={name}
99+ name={name}
100+ {...bind}
101+ flex>
102+ {isOpen ? placeholder : text}
103+ </Flex.Item>
104+ <div
105+ className="abs"
106+ style={{
107+ top: 0,
108+ right: 0,
109+ height: '100%',
110+ width: '44px',
111+ overflow: 'hidden',
112+ }}>
113+ {accessory}
114+ </div>
115+ {isOpen && (
116+ <Flex
117+ col
118+ className="abs bg-white b-black b1 z10"
119+ style={{
120+ top: 'calc(100% + 4px)',
121+ left: 0,
122+ right: 0,
123+ overflow: 'auto',
124+ maxHeight: '30vh',
125+ }}>
126+ {options.map(option => (
127+ <Flex.Item
128+ key={option.value}
129+ className="pv2 ph3 hover-bg-grey3"
130+ onClick={e => {
131+ e.stopPropagation();
132+ onChange(option.value);
133+ }}>
134+ {option.text}
135+ </Flex.Item>
136+ ))}
137+ </Flex>
138+ )}
139+ </Flex.Item>
140+ {error && (
141+ <Flex.Item as={ErrorText} className="mv1">
142+ {error}
143+ </Flex.Item>
144+ )}
145+ </Flex>
146+ );
147+}
src/indigo-react/lib/useForm.js+27−21
@@ -38,7 +38,9 @@ export default function useForm(inputConfigs = []) {
38
38
39 // track values
39 // track values
40 const [values, _setValues] = useState(() =>
40 const [values, _setValues] = useState(() =>
41− defaultsFor(configs, config => config.initialValue || '')
41+ defaultsFor(configs, config =>
42+ config.initialValue === undefined ? '' : config.initialValue
43+ )
42 );
44 );
43 // ^ NB(shrugs): because we're not syncing the initialValue of _additional_
45 // ^ NB(shrugs): because we're not syncing the initialValue of _additional_
44 // configs that are added, we won't be able to set an initialValue for a
46 // configs that are added, we won't be able to set an initialValue for a
@@ -164,27 +166,31 @@ export default function useForm(inputConfigs = []) {
164 const inputs = useMemo(
166 const inputs = useMemo(
165 () =>
167 () =>
166 configs.map(
168 configs.map(
167− ({ name, error, autoFocus, disabled, initialValue, ...rest }, i) => ({
169+ ({ name, error, autoFocus, disabled, initialValue, ...rest }, i) => {
170+ const value =
171+ values[name] || (initialValue === undefined ? '' : initialValue);
172+ return {
168− // Input props
173+ // Input props
169− name,
174+ name,
170− data: datas[i],
175+ data: datas[i],
171− pass: passes[i],
176+ pass: passes[i],
172− visiblyPassed: visiblePasses[i],
177+ visiblyPassed: visiblePasses[i],
173− error: errors[i],
178+ error: errors[i],
174− focused: focuses[i],
179+ focused: focuses[i],
175− autoFocus: autoFocus && !disabled,
176− disabled,
177− ...rest,
178− // dom properties below:
179− bind: {
180− value: values[name] || initialValue || '',
181− checked: !!values[name],
182− onChange: onChange(name),
183− onFocus: onFocus(name),
184− onBlur: onBlur(name),
185 autoFocus: autoFocus && !disabled,
180 autoFocus: autoFocus && !disabled,
181+ disabled,
182+ ...rest,
183+ // dom properties below:
184+ bind: {
185+ value,
186+ checked: !!values[name],
187+ onChange: onChange(name),
188+ onFocus: onFocus(name),
189+ onBlur: onBlur(name),
190+ autoFocus: autoFocus && !disabled,
191+ },
186− },
192+ };
187− })
193+ }
188 ),
194 ),
189 [
195 [
190 configs,
196 configs,
src/indigo-react/lib/useOnClickOutside.jsnew file+24
@@ -0,0 +1,24 @@
1+import { useEffect } from 'react';
2+
3+//via https:// usehooks.com/useOnClickOutside/
4+
5+export default function useOnClickOutside(ref, handler) {
6+ useEffect(() => {
7+ const listener = event => {
8+ // Do nothing if clicking ref's element or descendent elements
9+ if (!ref.current || ref.current.contains(event.target)) {
10+ return;
11+ }
12+
13+ handler(event);
14+ };
15+
16+ document.addEventListener('mousedown', listener);
17+ document.addEventListener('touchstart', listener);
18+
19+ return () => {
20+ document.removeEventListener('mousedown', listener);
21+ document.removeEventListener('touchstart', listener);
22+ };
23+ }, [ref, handler]);
24+}
src/lib/validators.js+8−1
@@ -1,6 +1,6 @@
1import * as bip39 from 'bip39';
1import * as bip39 from 'bip39';
2import * as ob from 'urbit-ob';
2import * as ob from 'urbit-ob';
3−import { identity } from 'lodash';
3+import { identity, includes } from 'lodash';
4
4
5import { isValidAddress } from './wallet';
5import { isValidAddress } from './wallet';
6import patp2dec from './patp2dec';
6import patp2dec from './patp2dec';
@@ -149,6 +149,13 @@ export const validateLength = l => m =>
149 error: `Must be exactly ${l} characters`,
149 error: `Must be exactly ${l} characters`,
150 });
150 });
151
151
152+export const validateOneOf = (options = []) => m =>
153+ simpleValidatorWrapper({
154+ prevMessage: m,
155+ validator: d => includes(options, d),
156+ error: `Is not a valid option.`,
157+ });
158+
152export const validateHexString = m =>
159export const validateHexString = m =>
153 simpleValidatorWrapper({
160 simpleValidatorWrapper({
154 prevMessage: m,
161 prevMessage: m,
src/style/indigo-proposals.scss+4
@@ -39,3 +39,7 @@
39.auto-rows-min {
39.auto-rows-min {
40 grid-auto-rows: min-content;
40 grid-auto-rows: min-content;
41}
41}
42+
43+.hover-bg-grey3:hover {
44+ background-color: #e6e6e6;
45+}
src/views/Login/Ledger.js+38−36
@@ -1,4 +1,4 @@
1−import React, { useCallback, useEffect, useState } from 'react';
1+import React, { useCallback, useEffect } from 'react';
2import Maybe from 'folktale/maybe';
2import Maybe from 'folktale/maybe';
3import { P, Text, Input, Grid, H5, CheckboxInput } from 'indigo-react';
3import { P, Text, Input, Grid, H5, CheckboxInput } from 'indigo-react';
4import { times } from 'lodash';
4import { times } from 'lodash';
@@ -8,8 +8,11 @@ import Eth from '@ledgerhq/hw-app-eth';
8import * as secp256k1 from 'secp256k1';
8import * as secp256k1 from 'secp256k1';
9
9
10import { ForwardButton } from 'components/Buttons';
10import { ForwardButton } from 'components/Buttons';
11−import { InnerLabelDropdown } from 'components/old/Base';
11+import {
12+ useCheckboxInput,
13+ useHdPathInput,
14+ useSelectInput,
12−import { useCheckboxInput, useHdPathInput } from 'components/Inputs';
15+} from 'components/Inputs';
13
16
14import { useWallet } from 'store/wallet';
17import { useWallet } from 'store/wallet';
15
18
@@ -21,14 +24,15 @@ import {
21} from 'lib/ledger';
24} from 'lib/ledger';
22import { WALLET_TYPES } from 'lib/wallet';
25import { WALLET_TYPES } from 'lib/wallet';
23import useLoginView from 'lib/useLoginView';
26import useLoginView from 'lib/useLoginView';
27+import SelectInput from 'indigo-react/components/SelectInput';
24
28
25−const pathOptions = [
29+const PATH_OPTIONS = [
26− { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
30+ { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
27− { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
31+ { text: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
28];
32];
29
33
30−const accountOptions = times(20, i => ({
34+const ACCOUNT_OPTIONS = times(20, i => ({
31− title: `Account #${i + 1}`,
35+ text: `Account #${i + 1}`,
32 value: i,
36 value: i,
33}));
37}));
34
38
@@ -37,10 +41,21 @@ export default function Ledger({ className }) {
37
41
38 const { setWallet, setWalletHdPath } = useWallet();
42 const { setWallet, setWalletHdPath } = useWallet();
39
43
40− // ledger has two derivation path _patterns_
44+ // derivation path input
41− const [basePathPattern, setBasePathPattern] = useState(LEDGER_LIVE_PATH);
45+ const [derivationPathInput, { data: basePathPattern }] = useSelectInput({
42− // and then we want to be able to select an index within that path
46+ name: 'derivationpath',
43− const [accountIndex, setAccountIndex] = useState(0);
47+ label: 'Derivation Path',
48+ placeholder: 'Choose path pattern...',
49+ options: PATH_OPTIONS,
50+ });
51+
52+ // account input
53+ const [accountInput, { data: accountIndex }] = useSelectInput({
54+ name: 'account',
55+ label: 'Account',
56+ placeholder: 'Choose account...',
57+ options: ACCOUNT_OPTIONS,
58+ });
44
59
45 // custom toggle
60 // custom toggle
46 const [customPathInput, { data: useCustomPath }] = useCheckboxInput({
61 const [customPathInput, { data: useCustomPath }] = useCheckboxInput({
@@ -90,11 +105,6 @@ export default function Ledger({ className }) {
90 }
105 }
91 }, [useCustomPath, setHdPath, basePathPattern, accountIndex]);
106 }, [useCustomPath, setHdPath, basePathPattern, accountIndex]);
92
107
93− const basePathPatternTitle = pathOptions.find(
94− o => o.value === basePathPattern
95− ).title;
96− const accountTitle = accountOptions.find(o => o.value === accountIndex).title;
97−
98 const isHTTPS = document.location.protocol === 'http:';
108 const isHTTPS = document.location.protocol === 'http:';
99
109
100 // when not on https, tell user how to get there
110 // when not on https, tell user how to get there
@@ -141,41 +151,33 @@ export default function Ledger({ className }) {
141 <Grid.Item full as={H5}>
151 <Grid.Item full as={H5}>
142 Authenticate With Your Ledger
152 Authenticate With Your Ledger
143 </Grid.Item>
153 </Grid.Item>
144− <Grid.Item full as={Text} className="f6">
154+ <Grid.Item full as={Text} className="f6 mb3">
145 Connect and authenticate to your Ledger, and then open the "Ethereum"
155 Connect and authenticate to your Ledger, and then open the "Ethereum"
146 application. If you're running on older firmware, make sure the "browser
156 application. If you're running on older firmware, make sure the "browser
147 support" option is turned on. To sign transactions, you'll also need to
157 support" option is turned on. To sign transactions, you'll also need to
148 enable the "contract data" option.
158 enable the "contract data" option.
149 </Grid.Item>
159 </Grid.Item>
150
160
151− {useCustomPath && (
152− <Grid.Item full as={Input} className="mv3" {...hdPathInput} />
161+ {useCustomPath && <Grid.Item full as={Input} {...hdPathInput} />}
153− )}
154
162
155 {!useCustomPath && (
163 {!useCustomPath && (
156 <>
164 <>
157 <Grid.Item
165 <Grid.Item
158− full
166+ half={1}
159− as={InnerLabelDropdown}
167+ as={SelectInput}
160− className="mv4"
168+ className="pr1"
161− title="Derivation path"
169+ {...derivationPathInput}
162− options={pathOptions}
163− handleUpdate={setBasePathPattern}
164− currentSelectionTitle={basePathPatternTitle}
165 />
170 />
166 <Grid.Item
171 <Grid.Item
167− full
172+ half={2}
168− as={InnerLabelDropdown}
173+ as={SelectInput}
169− className="mt4"
174+ className="pl1"
170− title="Account"
175+ {...accountInput}
171− options={accountOptions}
172− handleUpdate={setAccountIndex}
173− currentSelectionTitle={accountTitle}
174 />
176 />
175 </>
177 </>
176 )}
178 )}
177
179
178− <Grid.Item full as={CheckboxInput} {...customPathInput} />
180+ <Grid.Item full as={CheckboxInput} className="mv3" {...customPathInput} />
179
181
180 <Grid.Item
182 <Grid.Item
181 full
183 full
src/views/Login/Mnemonic.js+1−1
@@ -38,7 +38,7 @@ export default function Mnemonic({ className }) {
38
38
39 const [passphraseInput, { data: passphrase }] = usePassphraseInput({
39 const [passphraseInput, { data: passphrase }] = usePassphraseInput({
40 name: 'passphrase',
40 name: 'passphrase',
41− label: '(Optional) Wallet Passphrase',
41+ label: 'Wallet Passphrase',
42 });
42 });
43
43
44 const [hdPathInput, { data: hdPath }] = useHdPathInput({
44 const [hdPathInput, { data: hdPath }] = useHdPathInput({
src/views/Login/Ticket.js+1−1
@@ -32,7 +32,7 @@ export default function Ticket({ className }) {
32 // point
32 // point
33 const [pointInput, { data: pointName }] = usePointInput({
33 const [pointInput, { data: pointName }] = usePointInput({
34 name: 'point',
34 name: 'point',
35− initialValue: impliedPoint,
35+ initialValue: impliedPoint || '',
36 autoFocus: true,
36 autoFocus: true,
37 });
37 });
38
38
src/views/Login/Trezor.js+24−28
@@ -1,4 +1,4 @@
1−import React, { useEffect, useState } from 'react';
1+import React, { useEffect } from 'react';
2import Maybe from 'folktale/maybe';
2import Maybe from 'folktale/maybe';
3import * as bip32 from 'bip32';
3import * as bip32 from 'bip32';
4import { times } from 'lodash';
4import { times } from 'lodash';
@@ -7,17 +7,21 @@ import * as secp256k1 from 'secp256k1';
7import { Text, Input, Grid, H5, CheckboxInput } from 'indigo-react';
7import { Text, Input, Grid, H5, CheckboxInput } from 'indigo-react';
8
8
9import { ForwardButton } from 'components/Buttons';
9import { ForwardButton } from 'components/Buttons';
10−import { InnerLabelDropdown } from 'components/old/Base';
11
10
12import { TREZOR_PATH } from 'lib/trezor';
11import { TREZOR_PATH } from 'lib/trezor';
13import { WALLET_TYPES } from 'lib/wallet';
12import { WALLET_TYPES } from 'lib/wallet';
14
13
15import { useWallet } from 'store/wallet';
14import { useWallet } from 'store/wallet';
16import useLoginView from 'lib/useLoginView';
15import useLoginView from 'lib/useLoginView';
16+import {
17+ useHdPathInput,
18+ useCheckboxInput,
19+ useSelectInput,
17−import { useHdPathInput, useCheckboxInput } from 'components/Inputs';
20+} from 'components/Inputs';
18−
21+import SelectInput from 'indigo-react/components/SelectInput';
22+
19−const accountOptions = times(20, i => ({
23+const ACCOUNT_OPTIONS = times(20, i => ({
20− title: `Account #${i + 1}`,
24+ text: `Account #${i + 1}`,
21 value: i,
25 value: i,
22}));
26}));
23
27
@@ -27,8 +31,6 @@ export default function Trezor({ className }) {
27
31
28 const { setWallet, setWalletHdPath } = useWallet();
32 const { setWallet, setWalletHdPath } = useWallet();
29
33
30− const [accountIndex, setAccountIndex] = useState(0);
31−
32 // custom toggle
34 // custom toggle
33 const [customPathInput, { data: useCustomPath }] = useCheckboxInput({
35 const [customPathInput, { data: useCustomPath }] = useCheckboxInput({
34 name: 'customPath',
36 name: 'customPath',
@@ -37,6 +39,14 @@ export default function Trezor({ className }) {
37 initialValue: false,
39 initialValue: false,
38 });
40 });
39
41
42+ // account input
43+ const [accountInput, { data: accountIndex }] = useSelectInput({
44+ name: 'account',
45+ label: 'Account',
46+ placeholder: 'Choose account...',
47+ options: ACCOUNT_OPTIONS,
48+ });
49+
40 // hd path input
50 // hd path input
41 const [
51 const [
42 hdPathInput,
52 hdPathInput,
@@ -79,36 +89,22 @@ export default function Trezor({ className }) {
79 }
89 }
80 }, [useCustomPath, setHdPath, accountIndex]);
90 }, [useCustomPath, setHdPath, accountIndex]);
81
91
82− const accountTitle = accountOptions.find(o => o.value === accountIndex).title;
83−
84 return (
92 return (
85 <Grid className={className}>
93 <Grid className={className}>
86 <Grid.Item full as={H5}>
94 <Grid.Item full as={H5}>
87 Authenticate With Your Trezor
95 Authenticate With Your Trezor
88 </Grid.Item>
96 </Grid.Item>
89
97
90− <Grid.Item full as={Text} className="f6">
98+ <Grid.Item full as={Text} className="f6 mb3">
91 Connect and authenticate to your Trezor. If you'd like to use a custom
99 Connect and authenticate to your Trezor. If you'd like to use a custom
92 derivation path, you may enter it below.
100 derivation path, you may enter it below.
93 </Grid.Item>
101 </Grid.Item>
94
102
95− {useCustomPath && (
96− <Grid.Item full as={Input} className="mv3" {...hdPathInput} />
103+ {useCustomPath && <Grid.Item full as={Input} {...hdPathInput} />}
97− )}
104+
98−
99− {!useCustomPath && (
105+ {!useCustomPath && <Grid.Item full as={SelectInput} {...accountInput} />}
100− <Grid.Item
106+
101− full
102− as={InnerLabelDropdown}
103− className="mv4"
104− title="Account"
105− options={accountOptions}
106− handleUpdate={setAccountIndex}
107− currentSelectionTitle={accountTitle}
108− />
109− )}
110−
111− <Grid.Item full as={CheckboxInput} {...customPathInput} />
107+ <Grid.Item full as={CheckboxInput} className="mv3" {...customPathInput} />
112
108
113 <Grid.Item
109 <Grid.Item
114 full
110 full