on master help sign in
feat: implement double-confirm when warnings exist
0791f78Matt Condon (shrugs)
src/form/helpers.jsnew file+4
@@ -0,0 +1,4 @@
1+import { some } from 'lodash';
2+
3+// we have warnings if some of the values are truthy
4+export const hasWarnings = warnings => some(warnings, v => !!v);
src/views/Activate/ActivateCode.js+62−27
@@ -1,35 +1,41 @@
1−import React, { useCallback, useMemo } from 'react';
1+import React, { useCallback, useMemo, useRef } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
4import { Grid, H4 } from 'indigo-react';
4import { Grid, H4 } from 'indigo-react';
5+import { FORM_ERROR } from 'final-form';
5
6
6import View from 'components/View';
7import View from 'components/View';
7import { ForwardButton } from 'components/Buttons';
8import { ForwardButton } from 'components/Buttons';
8import Passport from 'components/Passport';
9import Passport from 'components/Passport';
10+import WarningBox from 'components/WarningBox';
11+import FooterButton from 'components/FooterButton';
9
12
13+import { useNetwork } from 'store/network';
10import { useHistory } from 'store/history';
14import { useHistory } from 'store/history';
11
15
12import * as need from 'lib/need';
16import * as need from 'lib/need';
13import { ROUTE_NAMES } from 'lib/routeNames';
17import { ROUTE_NAMES } from 'lib/routeNames';
14−import FooterButton from 'components/FooterButton';
15import { DEFAULT_HD_PATH, walletFromMnemonic } from 'lib/wallet';
18import { DEFAULT_HD_PATH, walletFromMnemonic } from 'lib/wallet';
16−import { useNetwork } from 'store/network';
17import { generateWallet } from 'lib/invite';
19import { generateWallet } from 'lib/invite';
18import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
20import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
19−import { useActivateFlow } from './ActivateFlow';
20import { useLocalRouter } from 'lib/LocalRouter';
21import { useLocalRouter } from 'lib/LocalRouter';
21import useImpliedTicket from 'lib/useImpliedTicket';
22import useImpliedTicket from 'lib/useImpliedTicket';
22import timeout from 'lib/timeout';
23import timeout from 'lib/timeout';
23import useHasDisclaimed from 'lib/useHasDisclaimed';
24import useHasDisclaimed from 'lib/useHasDisclaimed';
24import useBreakpoints from 'lib/useBreakpoints';
25import useBreakpoints from 'lib/useBreakpoints';
25−import WarningBox from 'components/WarningBox';
26
26
27import BridgeForm from 'form/BridgeForm';
27import BridgeForm from 'form/BridgeForm';
28import SubmitButton from 'form/SubmitButton';
28import SubmitButton from 'form/SubmitButton';
29import { TicketInput } from 'form/Inputs';
29import { TicketInput } from 'form/Inputs';
30+import {
31+ composeValidator,
30−import { composeValidator, buildPatqValidator } from 'form/validators';
32+ buildPatqValidator,
33+ hasErrors,
34+} from 'form/validators';
31import FormError from 'form/FormError';
35import FormError from 'form/FormError';
36+
37+import { useActivateFlow } from './ActivateFlow';
32−import { FORM_ERROR } from 'final-form';
38+import { hasWarnings } from 'form/helpers';
33
39
34export default function ActivateCode() {
40export default function ActivateCode() {
35 const history = useHistory();
41 const history = useHistory();
@@ -37,6 +43,7 @@ export default function ActivateCode() {
37 const { contracts } = useNetwork();
43 const { contracts } = useNetwork();
38 const impliedTicket = useImpliedTicket();
44 const impliedTicket = useImpliedTicket();
39 const [hasDisclaimed] = useHasDisclaimed();
45 const [hasDisclaimed] = useHasDisclaimed();
46+ const warnings = useRef({});
40
47
41 const {
48 const {
42 setDerivedWallet,
49 setDerivedWallet,
@@ -61,11 +68,19 @@ export default function ActivateCode() {
61 if (!hasDisclaimed) {
68 if (!hasDisclaimed) {
62 push(names.DISCLAIMER);
69 push(names.DISCLAIMER);
63 }
70 }
64− }, [names, push, hasDisclaimed]);
71+ }, [hasDisclaimed, names.DISCLAIMER, names.PASSPORT, push]);
72+
73+ const validateForm = useCallback((values, errors) => {
74+ warnings.current.ticket = null;
75+
76+ if (hasErrors(errors)) {
77+ return errors;
78+ }
79+ }, []);
65
80
66 const validate = useMemo(
81 const validate = useMemo(
67− () => composeValidator({ ticket: buildPatqValidator() }),
82+ () => composeValidator({ ticket: buildPatqValidator() }, validateForm),
68− []
83+ [validateForm]
69 );
84 );
70
85
71 // set our state on submission
86 // set our state on submission
@@ -92,10 +107,9 @@ export default function ActivateCode() {
92
107
93 if (incoming.length > 0) {
108 if (incoming.length > 0) {
94 if (incoming.length > 1) {
109 if (incoming.length > 1) {
95− // TODO: putting a warning here doesn't make sense since the user
110+ warnings.current.ticket =
96− // will be immediately redirected away — what do?
97− // 'This invite code has multiple points available.\n' +
111+ 'This invite code has multiple points available.\n' +
98− // "Once you've activated this point, activate the next with the same process.";
112+ "Once you've activated this point, activate the next with the same process.";
99 }
113 }
100
114
101 const point = parseInt(incoming[0], 10);
115 const point = parseInt(incoming[0], 10);
@@ -114,6 +128,14 @@ export default function ActivateCode() {
114 [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
128 [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
115 );
129 );
116
130
131+ const afterSubmit = useCallback(() => {
132+ if (hasWarnings(warnings.current)) {
133+ return;
134+ }
135+
136+ goToPassport();
137+ }, [goToPassport]);
138+
117 const initialValues = useMemo(() => ({ ticket: impliedTicket || '' }), [
139 const initialValues = useMemo(() => ({ ticket: impliedTicket || '' }), [
118 impliedTicket,
140 impliedTicket,
119 ]);
141 ]);
@@ -128,9 +150,9 @@ export default function ActivateCode() {
128 <BridgeForm
150 <BridgeForm
129 validate={validate}
151 validate={validate}
130 onSubmit={onSubmit}
152 onSubmit={onSubmit}
131− afterSubmit={goToPassport}
153+ afterSubmit={afterSubmit}
132 initialValues={initialValues}>
154 initialValues={initialValues}>
133− {({ validating, submitting, handleSubmit }) => (
155+ {({ validating, submitting, submitSucceeded, handleSubmit }) => (
134 <>
156 <>
135 <Grid.Item
157 <Grid.Item
136 full
158 full
@@ -138,21 +160,33 @@ export default function ActivateCode() {
138 name="ticket"
160 name="ticket"
139 label="Activation Code"
161 label="Activation Code"
140 disabled={!activationAllowed}
162 disabled={!activationAllowed}
163+ warning={warnings.current.ticket}
141 />
164 />
142
165
143 <Grid.Item full as={FormError} />
166 <Grid.Item full as={FormError} />
144
167
168+ {submitSucceeded ? (
145− <Grid.Item
169+ <Grid.Item
146− full
170+ full
171+ as={ForwardButton}
172+ solid
173+ className="mt4"
174+ onClick={goToPassport}>
175+ Continue Activation
176+ </Grid.Item>
177+ ) : (
178+ <Grid.Item
179+ full
147− as={SubmitButton}
180+ as={SubmitButton}
148− className="mt4"
181+ className="mt4"
149− handleSubmit={handleSubmit}>
182+ handleSubmit={handleSubmit}>
150− {validating
183+ {validating
151− ? 'Deriving...'
184+ ? 'Deriving...'
152− : submitting
185+ : submitting
153− ? 'Generating...'
186+ ? 'Generating...'
154− : 'Go'}
187+ : 'Go'}
155− </Grid.Item>
188+ </Grid.Item>
189+ )}
156
190
157 {!activationAllowed && (
191 {!activationAllowed && (
158 <Grid.Item full as={WarningBox} className="mt4">
192 <Grid.Item full as={WarningBox} className="mt4">
@@ -163,6 +197,7 @@ export default function ActivateCode() {
163 )}
197 )}
164 </BridgeForm>
198 </BridgeForm>
165 </Grid>
199 </Grid>
200+
166 <FooterButton as={ForwardButton} onClick={goToLogin}>
201 <FooterButton as={ForwardButton} onClick={goToLogin}>
167 Login
202 Login
168 </FooterButton>
203 </FooterButton>
src/views/Login/Ticket.js+41−32
@@ -1,4 +1,4 @@
1−import React, { useCallback, useMemo } from 'react';
1+import React, { useCallback, useMemo, useRef } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3import cn from 'classnames';
3import cn from 'classnames';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
@@ -28,9 +28,9 @@ import {
28 buildPointValidator,
28 buildPointValidator,
29} from 'form/validators';
29} from 'form/validators';
30import FormError from 'form/FormError';
30import FormError from 'form/FormError';
31−
32import SubmitButton from 'form/SubmitButton';
31import SubmitButton from 'form/SubmitButton';
33−import useSetState from 'lib/useSetState';
32+import { hasWarnings } from 'form/helpers';
33+import { ForwardButton } from 'components/Buttons';
34
34
35export default function Ticket({ className, goHome }) {
35export default function Ticket({ className, goHome }) {
36 useLoginView(WALLET_TYPES.TICKET);
36 useLoginView(WALLET_TYPES.TICKET);
@@ -39,27 +39,25 @@ export default function Ticket({ className, goHome }) {
39 const { setUrbitWallet } = useWallet();
39 const { setUrbitWallet } = useWallet();
40 const { setPointCursor } = usePointCursor();
40 const { setPointCursor } = usePointCursor();
41 const impliedPoint = useImpliedPoint();
41 const impliedPoint = useImpliedPoint();
42− const [warnings, addWarning] = useSetState();
42+ const warnings = useRef({});
43+
44+ const validateForm = useCallback((values, errors) => {
45+ warnings.current.point = null;
46+
47+ if (errors.point) {
48+ return errors;
49+ }
43
50
44− const validateForm = useCallback(
45− (values, errors) => {
51+ if (values.useShards) {
46− if (errors.point) {
52+ if (errors.shard1 || errors.shard2 || errors.shard3) {
47− addWarning({ point: null });
48 return errors;
53 return errors;
49 }
54 }
50−
51− if (values.useShards) {
52− if (errors.shard1 || errors.shard2 || errors.shard3) {
53− return errors;
54− }
55− } else {
55+ } else {
56− if (errors.ticket) {
56+ if (errors.ticket) {
57− return errors;
57+ return errors;
58− }
59 }
58 }
60− },
59+ }
61− [addWarning]
62− );
60+ }, []);
63
61
64 const onSubmit = useCallback(
62 const onSubmit = useCallback(
65 async values => {
63 async values => {
@@ -93,12 +91,9 @@ export default function Ticket({ className, goHome }) {
93
91
94 const noPermissions = !isOwner && !isTransferProxy;
92 const noPermissions = !isOwner && !isTransferProxy;
95 // notify the user, but allow login regardless
93 // notify the user, but allow login regardless
96− // TODO: how should the warning work now that we generate onSubmit?
97 if (noPermissions) {
94 if (noPermissions) {
98− addWarning({
95+ warnings.current.point =
99− point:
100− 'This wallet is not the owner or transfer proxy for this point.',
96+ 'This wallet is not the owner or transfer proxy for this point.';
101− });
102 }
97 }
103
98
104 setUrbitWallet(Just(urbitWallet));
99 setUrbitWallet(Just(urbitWallet));
@@ -112,9 +107,17 @@ export default function Ticket({ className, goHome }) {
112 };
107 };
113 }
108 }
114 },
109 },
115− [addWarning, contracts, setPointCursor, setUrbitWallet]
110+ [contracts, setPointCursor, setUrbitWallet]
116 );
111 );
117
112
113+ const afterSubmit = useCallback(() => {
114+ if (hasWarnings(warnings.current)) {
115+ return;
116+ }
117+
118+ goHome();
119+ }, [goHome]);
120+
118 const validate = useMemo(
121 const validate = useMemo(
119 () =>
122 () =>
120 composeValidator(
123 composeValidator(
@@ -147,15 +150,15 @@ export default function Ticket({ className, goHome }) {
147 <BridgeForm
150 <BridgeForm
148 validate={validate}
151 validate={validate}
149 onSubmit={onSubmit}
152 onSubmit={onSubmit}
150− afterSubmit={goHome}
153+ afterSubmit={afterSubmit}
151 initialValues={initialValues}>
154 initialValues={initialValues}>
152− {({ handleSubmit }) => (
155+ {({ handleSubmit, submitSucceeded }) => (
153 <>
156 <>
154 <Grid.Item
157 <Grid.Item
155 full
158 full
156 as={PointInput}
159 as={PointInput}
157 name="point"
160 name="point"
158− warning={warnings.point}
161+ warning={warnings.current.point}
159 />
162 />
160
163
161 <Condition when="useShards" is={false}>
164 <Condition when="useShards" is={false}>
@@ -197,9 +200,15 @@ export default function Ticket({ className, goHome }) {
197
200
198 <Grid.Item full as={FormError} />
201 <Grid.Item full as={FormError} />
199
202
203+ {submitSucceeded ? (
204+ <Grid.Item full as={ForwardButton} solid onClick={goHome}>
205+ Login Anyway
206+ </Grid.Item>
207+ ) : (
200− <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
208+ <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
201− Continue
209+ Continue
202− </Grid.Item>
210+ </Grid.Item>
211+ )}
203 </>
212 </>
204 )}
213 )}
205 </BridgeForm>
214 </BridgeForm>