on master help sign in
fix: much better logic for warning double-confirmations
commit557ffe7c9ea94220c0a4922a6cfcb250157a7cb6 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateWed 14 Aug 2019, 11:57 (7 years ago) parentcdb1f70
9 files changed+105−102
src/form/FormError.js+18−6 src/form/SubmitButton.js+28−3 src/form/helpers.js+4−3 src/indigo-react/components/CheckboxInput.js+2−2 src/indigo-react/components/Input.js+2−9 src/indigo-react/components/SelectInput.js+2−2 src/indigo-react/components/ToggleInput.js+2−2 src/views/Activate/ActivateCode.js+25−37 src/views/Login/Ticket.js+22−38
src/form/FormError.js+18−6
@@ -1,22 +1,34 @@
11import React from 'react';
22import { useFormState } from 'react-final-form';
33import { ErrorText } from 'indigo-react';
4−import { FORM_ERROR } from 'final-form';
4+
5+import { WARNING } from './helpers';
56
67export default function FormError(props) {
7− const { submitError, errors, dirtySinceLastSubmit } = useFormState({
8+ const {
9+ submitError,
10+ submitErrors,
11+ error: validationError,
12+ dirtySinceLastSubmit,
13+ } = useFormState({
814 subscription: {
915 submitError: true,
16+ submitErrors: true,
1017 errors: true,
1118 dirtySinceLastSubmit: true,
1219 },
1320 });
1421
15− const formError = errors[FORM_ERROR];
16− const showFormError = !!formError;
22+ const showValidationError = !!validationError;
23+
1724 const showSubmitError = !!submitError && !dirtySinceLastSubmit;
1825
19− return showSubmitError || showFormError ? (
20− <ErrorText {...props}>{submitError || formError}</ErrorText>
26+ const warning = submitErrors && submitErrors[WARNING];
27+ const showWarning = !!warning && !dirtySinceLastSubmit;
28+
29+ return showSubmitError || showValidationError || showWarning ? (
30+ <ErrorText {...props}>
31+ {submitError || validationError || warning}
32+ </ErrorText>
2133 ) : null;
2234}
src/form/SubmitButton.js+28−3
@@ -5,6 +5,7 @@ import { useFormState } from 'react-final-form';
55import { ForwardButton } from 'components/Buttons';
66
77import { blinkIf } from 'components/Blinky';
8+import { onlyHasWarning } from './helpers';
89
910export default function SubmitButton({
1011 as: As = ForwardButton,
@@ -20,13 +21,35 @@ export default function SubmitButton({
2021 hasValidationErrors,
2122 hasSubmitErrors,
2223 dirtySinceLastSubmit,
24+ submitErrors,
25+ submitSucceeded,
2326 } = useFormState();
2427
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
2537 const canSubmit =
26− (valid || (hasSubmitErrors && dirtySinceLastSubmit)) &&
38+ (valid ||
39+ (hasSubmitErrors &&
40+ (dirtySinceLastSubmit || onlyWarningInSubmitErrors))) &&
2741 !validating &&
2842 !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);
3053
3154 return (
3255 <As
@@ -36,7 +59,9 @@ export default function SubmitButton({
3659 onClick={handleSubmit}
3760 solid
3861 {...rest}>
39− {children}
62+ {typeof children === 'function'
63+ ? children(showWarningSubmitText)
64+ : children}
4065 </As>
4166 );
4267}
src/form/helpers.js+4−3
@@ -1,4 +1,5 @@
1−import { some } from 'lodash';
1+export const WARNING = 'warn';
22
3−// we have warnings if some of the values are truthy
4−export const hasWarnings = warnings => some(warnings, v => !!v);
3+// if the object only has the WARNING key
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({
1414}) {
1515 const {
1616 input,
17− meta: { submitting },
17+ meta: { submitting, submitSucceeded },
1818 } = useField(name, { type: 'checkbox' });
1919
20− disabled = disabled || submitting;
20+ disabled = disabled || submitting || submitSucceeded;
2121
2222 return (
2323 <Flex
src/indigo-react/components/Input.js+2−9
@@ -13,7 +13,6 @@ export default function Input({
1313 accessory,
1414 disabled = false,
1515 mono = false,
16− warning,
1716 obscure,
1817
1918 // callbacks
@@ -27,10 +26,10 @@ export default function Input({
2726}) {
2827 const {
2928 input,
30− meta: { active, error, submitting, touched, valid },
29+ meta: { active, error, submitting, submitSucceeded, touched, valid },
3130 } = useField(name, config);
3231
33− disabled = disabled || submitting;
32+ disabled = disabled || submitting || submitSucceeded;
3433
3534 // choose the base dom component
3635 const BaseComponent = type === 'textarea' ? 'textarea' : 'input';
@@ -108,12 +107,6 @@ export default function Input({
108107 )}
109108 </Flex.Item>
110109
111− {warning && (
112− <Flex.Item as={ErrorText} className="mv1">
113− {warning}
114− </Flex.Item>
115− )}
116−
117110 {touched && !active && error && (
118111 <Flex.Item as={ErrorText} className="mv1">
119112 {error}
src/indigo-react/components/SelectInput.js+2−2
@@ -20,12 +20,12 @@ export default function SelectInput({
2020}) {
2121 const {
2222 input,
23− meta: { active, error, submitting, touched, valid },
23+ meta: { active, error, submitting, submitSucceeded, touched, valid },
2424 } = useField(name, {
2525 type: 'select',
2626 });
2727
28− disabled = disabled || submitting;
28+ disabled = disabled || submitting || submitSucceeded;
2929
3030 const [isOpen, setIsOpen] = useState(false);
3131 const ref = useRef();
src/indigo-react/components/ToggleInput.js+2−2
@@ -19,10 +19,10 @@ export default function ToggleInput({
1919}) {
2020 const {
2121 input,
22− meta: { submitting },
22+ meta: { submitting, submitSucceeded },
2323 } = useField(name, { type: 'checkbox' });
2424
25− disabled = disabled || submitting;
25+ disabled = disabled || submitting || submitSucceeded;
2626
2727 return (
2828 <Flex
src/views/Activate/ActivateCode.js+25−37
@@ -35,7 +35,7 @@ import {
3535import FormError from 'form/FormError';
3636
3737import { useActivateFlow } from './ActivateFlow';
38−import { hasWarnings } from 'form/helpers';
38+import { WARNING } from 'form/helpers';
3939
4040export default function ActivateCode() {
4141 const history = useHistory();
@@ -43,7 +43,7 @@ export default function ActivateCode() {
4343 const { contracts } = useNetwork();
4444 const impliedTicket = useImpliedTicket();
4545 const [hasDisclaimed] = useHasDisclaimed();
46− const warnings = useRef({});
46+ const didWarn = useRef(false);
4747
4848 const {
4949 setDerivedWallet,
@@ -71,7 +71,7 @@ export default function ActivateCode() {
7171 }, [hasDisclaimed, names.DISCLAIMER, names.PASSPORT, push]);
7272
7373 const validateForm = useCallback((values, errors) => {
74− warnings.current.ticket = null;
74+ didWarn.current = false;
7575
7676 if (hasErrors(errors)) {
7777 return errors;
@@ -106,10 +106,14 @@ export default function ActivateCode() {
106106 const incoming = [...owned, ...transferring];
107107
108108 if (incoming.length > 0) {
109− if (incoming.length > 1) {
110− warnings.current.ticket =
111− 'This invite code has multiple points available.\n' +
112− "Once you've activated this point, activate the next with the same process.";
109+ if (incoming.length > 1 && !didWarn.current) {
110+ didWarn.current = true;
111+ return {
112+ [WARNING]:
113+ 'This invite code has multiple points available. ' +
114+ "Once you've activated this point, " +
115+ 'activate the next with the same process.',
116+ };
113117 }
114118
115119 const point = parseInt(incoming[0], 10);
@@ -128,14 +132,6 @@ export default function ActivateCode() {
128132 [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
129133 );
130134
131− const afterSubmit = useCallback(() => {
132− if (hasWarnings(warnings.current)) {
133− return;
134− }
135−
136− goToPassport();
137− }, [goToPassport]);
138−
139135 const initialValues = useMemo(() => ({ ticket: impliedTicket || '' }), [
140136 impliedTicket,
141137 ]);
@@ -150,9 +146,9 @@ export default function ActivateCode() {
150146 <BridgeForm
151147 validate={validate}
152148 onSubmit={onSubmit}
153− afterSubmit={afterSubmit}
149+ afterSubmit={goToPassport}
154150 initialValues={initialValues}>
155− {({ validating, submitting, submitSucceeded, handleSubmit }) => (
151+ {({ validating, submitting, handleSubmit }) => (
156152 <>
157153 <Grid.Item
158154 full
@@ -160,33 +156,25 @@ export default function ActivateCode() {
160156 name="ticket"
161157 label="Activation Code"
162158 disabled={!activationAllowed}
163− warning={warnings.current.ticket}
164159 />
165160
166161 <Grid.Item full as={FormError} />
167162
168− {submitSucceeded ? (
169− <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
180− as={SubmitButton}
181− className="mt4"
182− handleSubmit={handleSubmit}>
183− {validating
163+ <Grid.Item
164+ full
165+ as={SubmitButton}
166+ className="mt4"
167+ handleSubmit={handleSubmit}>
168+ {isWarning =>
169+ validating
184170 ? 'Deriving...'
185171 : submitting
186172 ? 'Generating...'
187− : 'Go'}
188− </Grid.Item>
189− )}
173+ : isWarning
174+ ? 'Continue Activation'
175+ : 'Go'
176+ }
177+ </Grid.Item>
190178
191179 {!activationAllowed && (
192180 <Grid.Item full as={WarningBox} className="mt4">
src/views/Login/Ticket.js+22−38
@@ -29,8 +29,7 @@ import {
2929} from 'form/validators';
3030import FormError from 'form/FormError';
3131import SubmitButton from 'form/SubmitButton';
32−import { hasWarnings } from 'form/helpers';
33−import { ForwardButton } from 'components/Buttons';
32+import { WARNING } from 'form/helpers';
3433
3534export default function Ticket({ className, goHome }) {
3635 useLoginView(WALLET_TYPES.TICKET);
@@ -39,10 +38,10 @@ export default function Ticket({ className, goHome }) {
3938 const { setUrbitWallet } = useWallet();
4039 const { setPointCursor } = usePointCursor();
4140 const impliedPoint = useImpliedPoint();
42− const warnings = useRef({});
41+ const didWarn = useRef(false);
4342
4443 const validateForm = useCallback((values, errors) => {
45− warnings.current.point = null;
44+ didWarn.current = false;
4645
4746 if (errors.point) {
4847 return errors;
@@ -90,10 +89,13 @@ export default function Ticket({ className, goHome }) {
9089 ]);
9190
9291 const noPermissions = !isOwner && !isTransferProxy;
93− // notify the user, but allow login regardless
94− if (noPermissions) {
95− warnings.current.point =
96− 'This wallet is not the owner or transfer proxy for this point.';
92+ // warn the user
93+ if (noPermissions && !didWarn.current) {
94+ didWarn.current = true;
95+ return {
96+ [WARNING]:
97+ 'This wallet is not the owner or transfer proxy for this point.',
98+ };
9799 }
98100
99101 setUrbitWallet(Just(urbitWallet));
@@ -110,14 +112,6 @@ export default function Ticket({ className, goHome }) {
110112 [contracts, setPointCursor, setUrbitWallet]
111113 );
112114
113− const afterSubmit = useCallback(() => {
114− if (hasWarnings(warnings.current)) {
115− return;
116− }
117−
118− goHome();
119− }, [goHome]);
120−
121115 const validate = useMemo(
122116 () =>
123117 composeValidator(
@@ -150,16 +144,11 @@ export default function Ticket({ className, goHome }) {
150144 <BridgeForm
151145 validate={validate}
152146 onSubmit={onSubmit}
153− afterSubmit={afterSubmit}
147+ afterSubmit={goHome}
154148 initialValues={initialValues}>
155− {({ handleSubmit, submitSucceeded }) => (
149+ {({ handleSubmit, submitting }) => (
156150 <>
157− <Grid.Item
158− full
159− as={PointInput}
160− name="point"
161− warning={warnings.current.point}
162− />
151+ <Grid.Item full as={PointInput} name="point" />
163152
164153 <Condition when="useShards" is={false}>
165154 <Grid.Item
@@ -200,20 +189,15 @@ export default function Ticket({ className, goHome }) {
200189
201190 <Grid.Item full as={FormError} />
202191
203− {submitSucceeded ? (
204− <Grid.Item
205− full
206− as={ForwardButton}
207− solid
208− className="mt4"
209− onClick={goHome}>
210− Login Anyway
211− </Grid.Item>
212− ) : (
213− <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
214− Continue
215− </Grid.Item>
216− )}
192+ <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
193+ {isWarning =>
194+ submitting
195+ ? 'Logging in...'
196+ : isWarning
197+ ? 'Login Anyway'
198+ : 'Continue'
199+ }
200+ </Grid.Item>
217201 </>
218202 )}
219203 </BridgeForm>