on master help sign in
fix: much better logic for warning double-confirmations
557ffe7Matt Condon (shrugs)
src/form/FormError.js+18−6
@@ -1,22 +1,34 @@
1import React from 'react';
1import React from 'react';
2import { useFormState } from 'react-final-form';
2import { useFormState } from 'react-final-form';
3import { ErrorText } from 'indigo-react';
3import { ErrorText } from 'indigo-react';
4−import { FORM_ERROR } from 'final-form';
4+
5+import { WARNING } from './helpers';
5
6
6export default function FormError(props) {
7export default function FormError(props) {
8+ const {
9+ submitError,
10+ submitErrors,
11+ error: validationError,
7− const { submitError, errors, dirtySinceLastSubmit } = useFormState({
12+ dirtySinceLastSubmit,
13+ } = useFormState({
8 subscription: {
14 subscription: {
9 submitError: true,
15 submitError: true,
16+ submitErrors: true,
10 errors: true,
17 errors: true,
11 dirtySinceLastSubmit: true,
18 dirtySinceLastSubmit: true,
12 },
19 },
13 });
20 });
14
21
15− const formError = errors[FORM_ERROR];
22+ const showValidationError = !!validationError;
16− const showFormError = !!formError;
23+
17 const showSubmitError = !!submitError && !dirtySinceLastSubmit;
24 const showSubmitError = !!submitError && !dirtySinceLastSubmit;
18
25
26+ const warning = submitErrors && submitErrors[WARNING];
27+ const showWarning = !!warning && !dirtySinceLastSubmit;
28+
19− return showSubmitError || showFormError ? (
29+ return showSubmitError || showValidationError || showWarning ? (
20− <ErrorText {...props}>{submitError || formError}</ErrorText>
30+ <ErrorText {...props}>
31+ {submitError || validationError || warning}
32+ </ErrorText>
21 ) : null;
33 ) : null;
22}
34}
src/form/SubmitButton.js+28−3
@@ -5,6 +5,7 @@ import { useFormState } from 'react-final-form';
5import { ForwardButton } from 'components/Buttons';
5import { ForwardButton } from 'components/Buttons';
6
6
7import { blinkIf } from 'components/Blinky';
7import { blinkIf } from 'components/Blinky';
8+import { onlyHasWarning } from './helpers';
8
9
9export default function SubmitButton({
10export default function SubmitButton({
10 as: As = ForwardButton,
11 as: As = ForwardButton,
@@ -20,13 +21,35 @@ export default function SubmitButton({
20 hasValidationErrors,
21 hasValidationErrors,
21 hasSubmitErrors,
22 hasSubmitErrors,
22 dirtySinceLastSubmit,
23 dirtySinceLastSubmit,
24+ submitErrors,
25+ submitSucceeded,
23 } = useFormState();
26 } = useFormState();
24
27
28+ const onlyWarningInSubmitErrors = onlyHasWarning(submitErrors);
29+
30+ // can submit if:
31+ // 1) is valid
32+ // OR has submit errors AND is dirty or only has a warning (double conf)
33+ // 2) AND is not validating
34+ // 3) AND has no validation errors
35+ // 4) AND is not actively submitting
36+ // 5) AND submit has not yet succeeded
25 const canSubmit =
37 const canSubmit =
38+ (valid ||
26− (valid || (hasSubmitErrors && dirtySinceLastSubmit)) &&
39+ (hasSubmitErrors &&
40+ (dirtySinceLastSubmit || onlyWarningInSubmitErrors))) &&
27 !validating &&
41 !validating &&
28 !hasValidationErrors &&
42 !hasValidationErrors &&
29− !submitting;
43+ !submitting &&
44+ !submitSucceeded;
45+
46+ // show the warning action text if
47+ // 1) we have that warning at all
48+ // 2) we can submit or are submitting
49+ const showWarningSubmitText =
50+ onlyWarningInSubmitErrors &&
51+ !dirtySinceLastSubmit &&
52+ (canSubmit || submitting);
30
53
31 return (
54 return (
32 <As
55 <As
@@ -36,7 +59,9 @@ export default function SubmitButton({
36 onClick={handleSubmit}
59 onClick={handleSubmit}
37 solid
60 solid
38 {...rest}>
61 {...rest}>
62+ {typeof children === 'function'
63+ ? children(showWarningSubmitText)
39− {children}
64+ : children}
40 </As>
65 </As>
41 );
66 );
42}
67}
src/form/helpers.js+4−3
@@ -1,4 +1,5 @@
1−import { some } from 'lodash';
1+export const WARNING = 'warn';
2
2
3−// we have warnings if some of the values are truthy
3+// if the object only has the WARNING key
4−export const hasWarnings = warnings => some(warnings, v => !!v);
4+export const onlyHasWarning = obj =>
5+ !!obj && Object.keys(obj).length === 1 && !!obj[WARNING];
src/indigo-react/components/CheckboxInput.js+2−2
@@ -14,10 +14,10 @@ export default function CheckboxInput({
14}) {
14}) {
15 const {
15 const {
16 input,
16 input,
17− meta: { submitting },
17+ meta: { submitting, submitSucceeded },
18 } = useField(name, { type: 'checkbox' });
18 } = useField(name, { type: 'checkbox' });
19
19
20− disabled = disabled || submitting;
20+ disabled = disabled || submitting || submitSucceeded;
21
21
22 return (
22 return (
23 <Flex
23 <Flex
src/indigo-react/components/Input.js+2−9
@@ -13,7 +13,6 @@ export default function Input({
13 accessory,
13 accessory,
14 disabled = false,
14 disabled = false,
15 mono = false,
15 mono = false,
16− warning,
17 obscure,
16 obscure,
18
17
19 // callbacks
18 // callbacks
@@ -27,10 +26,10 @@ export default function Input({
27}) {
26}) {
28 const {
27 const {
29 input,
28 input,
30− meta: { active, error, submitting, touched, valid },
29+ meta: { active, error, submitting, submitSucceeded, touched, valid },
31 } = useField(name, config);
30 } = useField(name, config);
32
31
33− disabled = disabled || submitting;
32+ disabled = disabled || submitting || submitSucceeded;
34
33
35 // choose the base dom component
34 // choose the base dom component
36 const BaseComponent = type === 'textarea' ? 'textarea' : 'input';
35 const BaseComponent = type === 'textarea' ? 'textarea' : 'input';
@@ -108,12 +107,6 @@ export default function Input({
108 )}
107 )}
109 </Flex.Item>
108 </Flex.Item>
110
109
111− {warning && (
112− <Flex.Item as={ErrorText} className="mv1">
113− {warning}
114− </Flex.Item>
115− )}
116−
117 {touched && !active && error && (
110 {touched && !active && error && (
118 <Flex.Item as={ErrorText} className="mv1">
111 <Flex.Item as={ErrorText} className="mv1">
119 {error}
112 {error}
src/indigo-react/components/SelectInput.js+2−2
@@ -20,12 +20,12 @@ export default function SelectInput({
20}) {
20}) {
21 const {
21 const {
22 input,
22 input,
23− meta: { active, error, submitting, touched, valid },
23+ meta: { active, error, submitting, submitSucceeded, touched, valid },
24 } = useField(name, {
24 } = useField(name, {
25 type: 'select',
25 type: 'select',
26 });
26 });
27
27
28− disabled = disabled || submitting;
28+ disabled = disabled || submitting || submitSucceeded;
29
29
30 const [isOpen, setIsOpen] = useState(false);
30 const [isOpen, setIsOpen] = useState(false);
31 const ref = useRef();
31 const ref = useRef();
src/indigo-react/components/ToggleInput.js+2−2
@@ -19,10 +19,10 @@ export default function ToggleInput({
19}) {
19}) {
20 const {
20 const {
21 input,
21 input,
22− meta: { submitting },
22+ meta: { submitting, submitSucceeded },
23 } = useField(name, { type: 'checkbox' });
23 } = useField(name, { type: 'checkbox' });
24
24
25− disabled = disabled || submitting;
25+ disabled = disabled || submitting || submitSucceeded;
26
26
27 return (
27 return (
28 <Flex
28 <Flex
src/views/Activate/ActivateCode.js+25−37
@@ -35,7 +35,7 @@ import {
35import FormError from 'form/FormError';
35import FormError from 'form/FormError';
36
36
37import { useActivateFlow } from './ActivateFlow';
37import { useActivateFlow } from './ActivateFlow';
38−import { hasWarnings } from 'form/helpers';
38+import { WARNING } from 'form/helpers';
39
39
40export default function ActivateCode() {
40export default function ActivateCode() {
41 const history = useHistory();
41 const history = useHistory();
@@ -43,7 +43,7 @@ export default function ActivateCode() {
43 const { contracts } = useNetwork();
43 const { contracts } = useNetwork();
44 const impliedTicket = useImpliedTicket();
44 const impliedTicket = useImpliedTicket();
45 const [hasDisclaimed] = useHasDisclaimed();
45 const [hasDisclaimed] = useHasDisclaimed();
46− const warnings = useRef({});
46+ const didWarn = useRef(false);
47
47
48 const {
48 const {
49 setDerivedWallet,
49 setDerivedWallet,
@@ -71,7 +71,7 @@ export default function ActivateCode() {
71 }, [hasDisclaimed, names.DISCLAIMER, names.PASSPORT, push]);
71 }, [hasDisclaimed, names.DISCLAIMER, names.PASSPORT, push]);
72
72
73 const validateForm = useCallback((values, errors) => {
73 const validateForm = useCallback((values, errors) => {
74− warnings.current.ticket = null;
74+ didWarn.current = false;
75
75
76 if (hasErrors(errors)) {
76 if (hasErrors(errors)) {
77 return errors;
77 return errors;
@@ -106,10 +106,14 @@ export default function ActivateCode() {
106 const incoming = [...owned, ...transferring];
106 const incoming = [...owned, ...transferring];
107
107
108 if (incoming.length > 0) {
108 if (incoming.length > 0) {
109− if (incoming.length > 1) {
109+ if (incoming.length > 1 && !didWarn.current) {
110− warnings.current.ticket =
110+ didWarn.current = true;
111+ return {
112+ [WARNING]:
111− 'This invite code has multiple points available.\n' +
113+ 'This invite code has multiple points available. ' +
114+ "Once you've activated this point, " +
112− "Once you've activated this point, activate the next with the same process.";
115+ 'activate the next with the same process.',
116+ };
113 }
117 }
114
118
115 const point = parseInt(incoming[0], 10);
119 const point = parseInt(incoming[0], 10);
@@ -128,14 +132,6 @@ export default function ActivateCode() {
128 [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
132 [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
129 );
133 );
130
134
131− const afterSubmit = useCallback(() => {
132− if (hasWarnings(warnings.current)) {
133− return;
134− }
135−
136− goToPassport();
137− }, [goToPassport]);
138−
139 const initialValues = useMemo(() => ({ ticket: impliedTicket || '' }), [
135 const initialValues = useMemo(() => ({ ticket: impliedTicket || '' }), [
140 impliedTicket,
136 impliedTicket,
141 ]);
137 ]);
@@ -150,9 +146,9 @@ export default function ActivateCode() {
150 <BridgeForm
146 <BridgeForm
151 validate={validate}
147 validate={validate}
152 onSubmit={onSubmit}
148 onSubmit={onSubmit}
153− afterSubmit={afterSubmit}
149+ afterSubmit={goToPassport}
154 initialValues={initialValues}>
150 initialValues={initialValues}>
155− {({ validating, submitting, submitSucceeded, handleSubmit }) => (
151+ {({ validating, submitting, handleSubmit }) => (
156 <>
152 <>
157 <Grid.Item
153 <Grid.Item
158 full
154 full
@@ -160,33 +156,25 @@ export default function ActivateCode() {
160 name="ticket"
156 name="ticket"
161 label="Activation Code"
157 label="Activation Code"
162 disabled={!activationAllowed}
158 disabled={!activationAllowed}
163− warning={warnings.current.ticket}
164 />
159 />
165
160
166 <Grid.Item full as={FormError} />
161 <Grid.Item full as={FormError} />
167
162
168− {submitSucceeded ? (
169− <Grid.Item
163+ <Grid.Item
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
164+ full
180− as={SubmitButton}
165+ as={SubmitButton}
181− className="mt4"
166+ className="mt4"
182− handleSubmit={handleSubmit}>
167+ handleSubmit={handleSubmit}>
168+ {isWarning =>
183− {validating
169+ validating
184 ? 'Deriving...'
170 ? 'Deriving...'
185 : submitting
171 : submitting
186 ? 'Generating...'
172 ? 'Generating...'
173+ : isWarning
174+ ? 'Continue Activation'
187− : 'Go'}
175+ : 'Go'
176+ }
188− </Grid.Item>
177+ </Grid.Item>
189− )}
190
178
191 {!activationAllowed && (
179 {!activationAllowed && (
192 <Grid.Item full as={WarningBox} className="mt4">
180 <Grid.Item full as={WarningBox} className="mt4">
src/views/Login/Ticket.js+22−38
@@ -29,8 +29,7 @@ import {
29} from 'form/validators';
29} from 'form/validators';
30import FormError from 'form/FormError';
30import FormError from 'form/FormError';
31import SubmitButton from 'form/SubmitButton';
31import SubmitButton from 'form/SubmitButton';
32−import { hasWarnings } from 'form/helpers';
32+import { WARNING } from 'form/helpers';
33−import { ForwardButton } from 'components/Buttons';
34
33
35export default function Ticket({ className, goHome }) {
34export default function Ticket({ className, goHome }) {
36 useLoginView(WALLET_TYPES.TICKET);
35 useLoginView(WALLET_TYPES.TICKET);
@@ -39,10 +38,10 @@ export default function Ticket({ className, goHome }) {
39 const { setUrbitWallet } = useWallet();
38 const { setUrbitWallet } = useWallet();
40 const { setPointCursor } = usePointCursor();
39 const { setPointCursor } = usePointCursor();
41 const impliedPoint = useImpliedPoint();
40 const impliedPoint = useImpliedPoint();
42− const warnings = useRef({});
41+ const didWarn = useRef(false);
43
42
44 const validateForm = useCallback((values, errors) => {
43 const validateForm = useCallback((values, errors) => {
45− warnings.current.point = null;
44+ didWarn.current = false;
46
45
47 if (errors.point) {
46 if (errors.point) {
48 return errors;
47 return errors;
@@ -90,10 +89,13 @@ export default function Ticket({ className, goHome }) {
90 ]);
89 ]);
91
90
92 const noPermissions = !isOwner && !isTransferProxy;
91 const noPermissions = !isOwner && !isTransferProxy;
93− // notify the user, but allow login regardless
92+ // warn the user
94− if (noPermissions) {
93+ if (noPermissions && !didWarn.current) {
95− warnings.current.point =
94+ didWarn.current = true;
95+ return {
96+ [WARNING]:
96− 'This wallet is not the owner or transfer proxy for this point.';
97+ 'This wallet is not the owner or transfer proxy for this point.',
98+ };
97 }
99 }
98
100
99 setUrbitWallet(Just(urbitWallet));
101 setUrbitWallet(Just(urbitWallet));
@@ -110,14 +112,6 @@ export default function Ticket({ className, goHome }) {
110 [contracts, setPointCursor, setUrbitWallet]
112 [contracts, setPointCursor, setUrbitWallet]
111 );
113 );
112
114
113− const afterSubmit = useCallback(() => {
114− if (hasWarnings(warnings.current)) {
115− return;
116− }
117−
118− goHome();
119− }, [goHome]);
120−
121 const validate = useMemo(
115 const validate = useMemo(
122 () =>
116 () =>
123 composeValidator(
117 composeValidator(
@@ -150,16 +144,11 @@ export default function Ticket({ className, goHome }) {
150 <BridgeForm
144 <BridgeForm
151 validate={validate}
145 validate={validate}
152 onSubmit={onSubmit}
146 onSubmit={onSubmit}
153− afterSubmit={afterSubmit}
147+ afterSubmit={goHome}
154 initialValues={initialValues}>
148 initialValues={initialValues}>
155− {({ handleSubmit, submitSucceeded }) => (
149+ {({ handleSubmit, submitting }) => (
156 <>
150 <>
157− <Grid.Item
158− full
159− as={PointInput}
151+ <Grid.Item full as={PointInput} name="point" />
160− name="point"
161− warning={warnings.current.point}
162− />
163
152
164 <Condition when="useShards" is={false}>
153 <Condition when="useShards" is={false}>
165 <Grid.Item
154 <Grid.Item
@@ -200,20 +189,15 @@ export default function Ticket({ className, goHome }) {
200
189
201 <Grid.Item full as={FormError} />
190 <Grid.Item full as={FormError} />
202
191
203− {submitSucceeded ? (
192+ <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
204− <Grid.Item
193+ {isWarning =>
205− full
194+ submitting
206− as={ForwardButton}
195+ ? 'Logging in...'
207− solid
196+ : isWarning
208− className="mt4"
209− onClick={goHome}>
210− Login Anyway
197+ ? 'Login Anyway'
211− </Grid.Item>
212− ) : (
213− <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
214− Continue
198+ : 'Continue'
199+ }
215− </Grid.Item>
200+ </Grid.Item>
216− )}
217 </>
201 </>
218 )}
202 )}
219 </BridgeForm>
203 </BridgeForm>