on master help sign in
login.js: add inline toggle input for ticket login
615749cLiam Fitzgerald
src/assets/secret-hidden.svgnew file+17
@@ -0,0 +1,17 @@
1+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2+<rect width="16" height="16" fill="white"/>
3+<path fill-rule="evenodd" clip-rule="evenodd" d="M8 13C13.0495 13 16 8 16 8C16 8 13.0495 3 8 3C2.95054 3 0 8 0 8C0 8 2.95054 13 8 13ZM8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10Z" fill="black"/>
4+<g filter="url(#filter0_d)">
5+<path d="M2 2.70588L2.70588 2L14 13.2941L13.2941 14L2 2.70588Z" fill="black"/>
6+</g>
7+<defs>
8+<filter id="filter0_d" x="1" y="2" width="13" height="12" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
9+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
10+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
11+<feOffset dx="-1"/>
12+<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
13+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
14+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
15+</filter>
16+</defs>
17+</svg>
src/assets/secret-show.svgnew file+4
@@ -0,0 +1,4 @@
1+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2+<rect width="16" height="16" fill="white"/>
3+<path fill-rule="evenodd" clip-rule="evenodd" d="M8 13C13.0495 13 16 8 16 8C16 8 13.0495 3 8 3C2.95054 3 0 8 0 8C0 8 2.95054 13 8 13ZM8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10Z" fill="#B1B2B3"/>
4+</svg>
src/form/Inputs.js+2−2
@@ -26,7 +26,7 @@ const PLACEHOLDER_EMAIL = 'email@example.com';
26
26
27const formatPat = buildFormatter([downcase, ensurePatFormat]);
27const formatPat = buildFormatter([downcase, ensurePatFormat]);
28
28
29−export function TicketInput({ name, ...rest }) {
29+export function TicketInput({ name, hidden, ...rest }) {
30 const {
30 const {
31 meta: { valid, error, validating, touched, active },
31 meta: { valid, error, validating, touched, active },
32 } = useField(name, {
32 } = useField(name, {
@@ -41,7 +41,7 @@ export function TicketInput({ name, ...rest }) {
41
41
42 return (
42 return (
43 <Input
43 <Input
44− type="text"
44+ type={hidden ? 'password' : 'text'}
45 name={name}
45 name={name}
46 obscure={value => value.replace(/[^~-]+/g, '••••••')}
46 obscure={value => value.replace(/[^~-]+/g, '••••••')}
47 placeholder={PLACEHOLDER_TICKET}
47 placeholder={PLACEHOLDER_TICKET}
src/indigo-react/components/CheckboxInput.js+14−4
@@ -9,6 +9,9 @@ export default function CheckboxInput({
9 name,
9 name,
10 label,
10 label,
11 className,
11 className,
12+ style,
13+ inline = false,
14+ white = false,
12
15
13 disabled,
16 disabled,
14}) {
17}) {
@@ -23,7 +26,8 @@ export default function CheckboxInput({
23 <Flex
26 <Flex
24 row
27 row
25 align="center"
28 align="center"
26− className={cn(className, 'mv2', {
29+ className={cn(className, {
30+ mv2: !inline,
27 black: !disabled,
31 black: !disabled,
28 gray4: disabled,
32 gray4: disabled,
29 })}
33 })}
@@ -39,19 +43,25 @@ export default function CheckboxInput({
39 <Flex.Item
43 <Flex.Item
40 flex
44 flex
41 as="label"
45 as="label"
46+ className={cn(
47+ { mr3: !inline },
42− className="f6 mr3 lh-tall us-none pointer flex-row align-center"
48+ 'f6 lh-tall us-none pointer flex-row align-center'
49+ )}
43 htmlFor={name}>
50 htmlFor={name}>
44 <Flex
51 <Flex
45 justify="center"
52 justify="center"
46 align="center"
53 align="center"
47− className={cn('b1 p1 mr3', {
54+ className={cn('b1 p1', {
55+ mr3: !inline,
56+ mr1: inline,
48 'bg-gray1': disabled,
57 'bg-gray1': disabled,
49− 'bg-black white b-black': !disabled && input.value,
58+ 'bg-black white b-black': !disabled && (input.value || white),
50 'bg-white black b-black': !disabled && !input.value,
59 'bg-white black b-black': !disabled && !input.value,
51 })}
60 })}
52 style={{
61 style={{
53 height: '14px',
62 height: '14px',
54 width: '14px',
63 width: '14px',
64+ ...style,
55 }}>
65 }}>
56 {input.value && '✓'}
66 {input.value && '✓'}
57 </Flex>
67 </Flex>
src/indigo-react/components/Input.js+9−4
@@ -11,6 +11,7 @@ export default function Input({
11 label,
11 label,
12 className,
12 className,
13 accessory,
13 accessory,
14+ labelAccessory,
14 disabled = false,
15 disabled = false,
15 mono = false,
16 mono = false,
16 obscure,
17 obscure,
@@ -65,13 +66,17 @@ export default function Input({
65 }),
66 }),
66 }}>
67 }}>
67 <Flex.Item
68 <Flex.Item
68− as="label"
69+ as={Flex}
70+ justify="between"
71+ align="center"
69 className={cn('f6 lh-tall', {
72 className={cn('f6 lh-tall', {
70 black: !disabled,
73 black: !disabled,
71 gray4: disabled,
74 gray4: disabled,
72− })}
75+ })}>
73− htmlFor={name}>
76+ <Flex.Item as="label" justify htmlFor={name}>
74− {label}
77+ {label}
78+ </Flex.Item>
79+ {labelAccessory && <Flex.Item>{labelAccessory}</Flex.Item>}
75 </Flex.Item>
80 </Flex.Item>
76 <Flex.Item as={Flex} row className="rel">
81 <Flex.Item as={Flex} row className="rel">
77 <Flex.Item
82 <Flex.Item
src/indigo-react/components/ToggleInput.js+4−1
@@ -11,6 +11,7 @@ export default function ToggleInput({
11 label,
11 label,
12 inverseLabel,
12 inverseLabel,
13 inverseColor = 'black',
13 inverseColor = 'black',
14+ small = false,
14 className,
15 className,
15
16
16 //
17 //
@@ -55,7 +56,9 @@ export default function ToggleInput({
55 htmlFor={name}>
56 htmlFor={name}>
56 <LinkButton
57 <LinkButton
57 disabled={disabled}
58 disabled={disabled}
58− className={cn('f5', {
59+ className={cn({
60+ f5: !small,
61+ f6: small,
59 black: !input.checked,
62 black: !input.checked,
60 [inverseColor]: input.checked,
63 [inverseColor]: input.checked,
61 })}>
64 })}>
src/views/Login.js+33−21
@@ -1,5 +1,5 @@
1import React, { useCallback, useState } from 'react';
1import React, { useCallback, useState } from 'react';
2−import { P, H4, H5, Grid, Text } from 'indigo-react';
2+import { P, H4, H5, Grid, Text, Button, Flex, LinkButton } from 'indigo-react';
3
3
4import { version } from '../../package.json';
4import { version } from '../../package.json';
5
5
@@ -59,41 +59,53 @@ export default function Login() {
59 return (
59 return (
60 <View pop={pop} inset>
60 <View pop={pop} inset>
61 <Grid>
61 <Grid>
62− <Grid.Item full as={Crumbs} routes={[{ text: 'Bridge' }]} />
63− <Grid.Item full as={Text} className="flex justify-center mt4">
62+ <Grid.Item full as={Text} className="flex justify-center mt9 mb7">
64− <Grid.Item as={Text} className="gray5">
63+ <Grid.Item as={Text} className="gray4">
65 Urbit ID /&nbsp;
64 Urbit ID /&nbsp;
66 </Grid.Item>
65 </Grid.Item>
67 <Grid.Item as={Text}>Login</Grid.Item>
66 <Grid.Item as={Text}>Login</Grid.Item>
68 </Grid.Item>
67 </Grid.Item>
69− {isOther && <Grid.Item full as={Other} />}
68+ {isOther && <Grid.Item full as={Other} goHome={goHome} />}
70− {!isOther && <Grid.Item full as={Ticket} />}
69+ {!isOther && <Grid.Item full as={Ticket} goHome={goHome} />}
71 {!isOther && (
70 {!isOther && (
71+ <>
72+ <Grid.Item full className="t-center mv4 gray4">
73+ or
74+ </Grid.Item>
75+ <Grid.Item
76+ full
77+ as={Button}
78+ className="b-solid b1 b-black"
79+ center
72− <Grid.Item full as={ForwardButton} onClick={() => setisOther(true)}>
80+ onClick={() => setisOther(true)}>
73− Metamask ledger, etc.
81+ Metamask, Mnemonic, Hardware Wallet, etc...
74− </Grid.Item>
82+ </Grid.Item>
83+ <Grid.Item full onClick={goToActivate} className="mv10 t-center f6">
84+ <span className="gray4">New Urbit ID? </span>
85+ <LinkButton>Activate</LinkButton>
86+ </Grid.Item>
87+ </>
75 )}
88 )}
76 {isOther && (
89 {isOther && (
77 <Grid.Item
90 <Grid.Item
78 onClick={() => setisOther(false)}
91 onClick={() => setisOther(false)}
92+ full
79− className="underline gray4">
93+ className="t-center underline mt8">
80 Back
94 Back
81 </Grid.Item>
95 </Grid.Item>
82 )}
96 )}
83− <Grid.Item full as={P} className="f6">
84− Version {version}
85− </Grid.Item>
86 </Grid>
97 </Grid>
87
98
88 <Footer>
99 <Footer>
89− <Grid>
100+ <Flex className="mb8 f6" justify="between">
90− <Grid.Divider />
101+ <Flex.Item
91− <Grid.Item full as={ForwardButton} onClick={goToActivate}>
102+ as="a"
92− Activate
103+ href="https://github.com/urbit/bridge/releases"
104+ className="us-none pointer">
105+ <span className="underline">Offline </span>↗
93− </Grid.Item>
106+ </Flex.Item>
94− <Grid.Divider />
107+ <Flex.Item className="gray4">Version {version}</Flex.Item>
95− <Grid.Item full as={OfflineButton} />
108+ </Flex>
96− </Grid>
97 </Footer>
109 </Footer>
98 </View>
110 </View>
99 );
111 );
src/views/Login/Other.js+13−9
@@ -1,4 +1,5 @@
1import React, { useState, useMemo } from 'react';
1import React, { useState, useMemo } from 'react';
2+import { Grid } from 'indigo-react';
2
3
3import useBreakpoints from 'lib/useBreakpoints';
4import useBreakpoints from 'lib/useBreakpoints';
4
5
@@ -55,14 +56,17 @@ export default function Other({ className, ...rest }) {
55 [hardwareAllowed]
56 [hardwareAllowed]
56 );
57 );
57 return (
58 return (
59+ <>
60+ <Grid.Divider />
58− <Accordion
61+ <Accordion
59− className={className}
62+ className={className}
60− views={VIEWS}
63+ views={VIEWS}
61− options={options}
64+ options={options}
62− currentTab={currentTab}
65+ currentTab={currentTab}
63− onTabChange={setCurrentTab}
66+ onTabChange={setCurrentTab}
64− //
67+ //
65− {...rest}
68+ {...rest}
66− />
69+ />
70+ </>
67 );
71 );
68}
72}
src/views/Login/Ticket.js+80−34
@@ -3,7 +3,13 @@ import { Just } from 'folktale/maybe';
3import cn from 'classnames';
3import cn from 'classnames';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
5import * as kg from 'urbit-key-generation';
5import * as kg from 'urbit-key-generation';
6+import {
7+ Grid,
8+ CheckboxInput,
9+ Flex,
10+ ToggleInput,
11+ AccessoryIcon,
6−import { Grid, CheckboxInput, Flex, ToggleInput } from 'indigo-react';
12+} from 'indigo-react';
7import { FORM_ERROR } from 'final-form';
13import { FORM_ERROR } from 'final-form';
8
14
9import { useNetwork } from 'store/network';
15import { useNetwork } from 'store/network';
@@ -31,6 +37,56 @@ import FormError from 'form/FormError';
31import SubmitButton from 'form/SubmitButton';
37import SubmitButton from 'form/SubmitButton';
32import { WARNING } from 'form/helpers';
38import { WARNING } from 'form/helpers';
33
39
40+import { ReactComponent as SecretShowIcon } from 'assets/secret-show.svg';
41+import { ReactComponent as SecretHideIcon } from 'assets/secret-hidden.svg';
42+
43+function AdvancedOptions() {
44+ return (
45+ <Flex.Item as={Flex}>
46+ <Condition when="useAdvanced" is={true}>
47+ <Flex.Item
48+ as={CheckboxInput}
49+ inline
50+ white
51+ className="mr4"
52+ name="usePassphrase"
53+ label="Passphrase"
54+ style={{ boxSizing: 'border-box' }}
55+ />
56+ <Flex.Item
57+ as={CheckboxInput}
58+ inline
59+ white
60+ className="mr4"
61+ name="useShards"
62+ label="Shards"
63+ style={{ boxSizing: 'border-box' }}
64+ />
65+ </Condition>
66+ <Flex.Item
67+ as={ToggleInput}
68+ name="useAdvanced"
69+ label="Settings"
70+ small
71+ inverseLabel="Close"
72+ />
73+ </Flex.Item>
74+ );
75+}
76+
77+function TicketInputAccessory({ name }) {
78+ return (
79+ <AccessoryIcon>
80+ <ToggleInput
81+ name={name}
82+ className="mt1"
83+ inverseLabel={<SecretHideIcon />}
84+ label={<SecretShowIcon />}
85+ />
86+ </AccessoryIcon>
87+ );
88+}
89+
34export default function Ticket({ className, goHome }) {
90export default function Ticket({ className, goHome }) {
35 useLoginView(WALLET_TYPES.TICKET);
91 useLoginView(WALLET_TYPES.TICKET);
36
92
@@ -124,7 +180,7 @@ export default function Ticket({ className, goHome }) {
124 shard1: buildPatqValidator(),
180 shard1: buildPatqValidator(),
125 shard2: buildPatqValidator(),
181 shard2: buildPatqValidator(),
126 shard3: buildPatqValidator(),
182 shard3: buildPatqValidator(),
127− showTicket: buildCheckboxValidator(),
183+ ticketHidden: buildCheckboxValidator(),
128 passphrase: buildPassphraseValidator(),
184 passphrase: buildPassphraseValidator(),
129 },
185 },
130 validateForm
186 validateForm
@@ -137,14 +193,14 @@ export default function Ticket({ className, goHome }) {
137 point: impliedPoint || '',
193 point: impliedPoint || '',
138 usePasshrase: false,
194 usePasshrase: false,
139 useShards: false,
195 useShards: false,
140− showTicket: true,
196+ ticketHidden: false,
141 useAdvanced: false,
197 useAdvanced: false,
142 }),
198 }),
143 [impliedPoint]
199 [impliedPoint]
144 );
200 );
145
201
146 return (
202 return (
147− <Grid className={cn('mt4', className)}>
203+ <Grid className={className}>
148 <BridgeForm
204 <BridgeForm
149 validate={validate}
205 validate={validate}
150 onSubmit={onSubmit}
206 onSubmit={onSubmit}
@@ -158,7 +214,10 @@ export default function Ticket({ className, goHome }) {
158 <Grid.Item
214 <Grid.Item
159 full
215 full
160 as={TicketInput}
216 as={TicketInput}
161− type={values.showTicket ? 'text' : 'password'}
217+ hidden={values.ticketHidden}
218+ labelAccessory={<AdvancedOptions />}
219+ accessory={<TicketInputAccessory name="ticketHidden" />}
220+ className="mt3"
162 name="ticket"
221 name="ticket"
163 label="Master Ticket"
222 label="Master Ticket"
164 />
223 />
@@ -168,32 +227,38 @@ export default function Ticket({ className, goHome }) {
168 <Grid.Item
227 <Grid.Item
169 full
228 full
170 as={TicketInput}
229 as={TicketInput}
171− type={values.showTicket ? 'text' : 'password'}
230+ accessory={<TicketInputAccessory name="ticketHidden" />}
231+ hidden={values.ticketHidden}
232+ labelAccessory={<AdvancedOptions />}
233+ className="mt3"
172 name="shard1"
234 name="shard1"
173 label="Shard 1"
235 label="Shard 1"
174 />
236 />
175 <Grid.Item
237 <Grid.Item
176 full
238 full
177 as={TicketInput}
239 as={TicketInput}
178− type={values.showTicket ? 'text' : 'password'}
240+ accessory={<TicketInputAccessory name="ticketHidden" />}
241+ hidden={values.ticketHidden}
242+ className="mt3"
179 name="shard2"
243 name="shard2"
180 label="Shard 2"
244 label="Shard 2"
181 />
245 />
182 <Grid.Item
246 <Grid.Item
183 full
247 full
184 as={TicketInput}
248 as={TicketInput}
185− type={values.showTicket ? 'text' : 'password'}
249+ className="mt3"
250+ accessory={<TicketInputAccessory name="ticketHidden" />}
251+ hidden={values.ticketHidden}
186 name="shard3"
252 name="shard3"
187 label="Shard 3"
253 label="Shard 3"
188 />
254 />
189 </Condition>
255 </Condition>
190
256
191− <Grid.Item full as={CheckboxInput} name="showTicket" label="Show" />
192−
193 <Condition when="usePassphrase" is={true}>
257 <Condition when="usePassphrase" is={true}>
194 <Grid.Item
258 <Grid.Item
195 full
259 full
196 as={PassphraseInput}
260 as={PassphraseInput}
261+ className="mt3"
197 name="passphrase"
262 name="passphrase"
198 label="Wallet Passphrase"
263 label="Wallet Passphrase"
199 />
264 />
@@ -201,7 +266,11 @@ export default function Ticket({ className, goHome }) {
201
266
202 <Grid.Item full as={FormError} />
267 <Grid.Item full as={FormError} />
203
268
269+ <Grid.Item
270+ full
271+ as={SubmitButton}
272+ center
204− <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
273+ handleSubmit={handleSubmit}>
205 {isWarning =>
274 {isWarning =>
206 submitting
275 submitting
207 ? 'Logging in...'
276 ? 'Logging in...'
@@ -210,29 +279,6 @@ export default function Ticket({ className, goHome }) {
210 : 'Continue'
279 : 'Continue'
211 }
280 }
212 </Grid.Item>
281 </Grid.Item>
213−
214− <Grid.Item full as={Flex} justify="between">
215− <Flex.Item
216− as={ToggleInput}
217− name="useAdvanced"
218− label="Advanced"
219− inverseLabel="Hide"
220− />
221− <Condition when="useAdvanced" is={true}>
222− <Flex.Item as={Flex}>
223− <Flex.Item
224− as={CheckboxInput}
225− name="usePassphrase"
226− label="Passphrase"
227− />
228− <Flex.Item
229− as={CheckboxInput}
230− name="useShards"
231− label="Shards"
232− />
233− </Flex.Item>
234− </Condition>
235− </Grid.Item>
236 </>
282 </>
237 )}
283 )}
238 </BridgeForm>
284 </BridgeForm>