Skip to content

Commit 6b16b9b

Browse files
committed
Cleanup logic for asModal to make it a little easier to use dynamically
1 parent 69ac2ca commit 6b16b9b

File tree

12 files changed

+210
-203
lines changed

12 files changed

+210
-203
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"i18next-chained-backend": "^2.0.0",
1919
"i18next-localstorage-backend": "^3.0.0",
2020
"i18next-xhr-backend": "^3.2.2",
21+
"qrcode.react": "^1.0.1",
2122
"query-string": "^6.7.0",
2223
"react": "^16.13.1",
2324
"react-copy-to-clipboard": "^5.0.2",

resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,53 +7,29 @@ import tw from 'twin.macro';
77
import Button from '@/components/elements/Button';
88

99
export default () => {
10-
const user = useStoreState((state: ApplicationStore) => state.user.data!);
1110
const [ visible, setVisible ] = useState(false);
11+
const isEnabled = useStoreState((state: ApplicationStore) => state.user.data!.useTotp);
1212

13-
return user.useTotp ?
13+
return (
1414
<div>
15-
{visible &&
16-
<DisableTwoFactorModal
17-
appear
18-
visible={visible}
19-
onDismissed={() => setVisible(false)}
20-
/>
21-
}
15+
{visible && (
16+
isEnabled ?
17+
<DisableTwoFactorModal visible={visible} onModalDismissed={() => setVisible(false)}/>
18+
:
19+
<SetupTwoFactorModal visible={visible} onModalDismissed={() => setVisible(false)}/>
20+
)}
2221
<p css={tw`text-sm`}>
23-
Two-factor authentication is currently enabled on your account.
22+
{isEnabled ?
23+
'Two-factor authentication is currently enabled on your account.'
24+
:
25+
'You do not currently have two-factor authentication enabled on your account. Click the button below to begin configuring it.'
26+
}
2427
</p>
2528
<div css={tw`mt-6`}>
26-
<Button
27-
color={'red'}
28-
isSecondary
29-
onClick={() => setVisible(true)}
30-
>
31-
Disable
29+
<Button color={'red'} isSecondary onClick={() => setVisible(true)}>
30+
{isEnabled ? 'Disable' : 'Enable'}
3231
</Button>
3332
</div>
3433
</div>
35-
:
36-
<div>
37-
{visible &&
38-
<SetupTwoFactorModal
39-
appear
40-
visible={visible}
41-
onDismissed={() => setVisible(false)}
42-
/>
43-
}
44-
<p css={tw`text-sm`}>
45-
You do not currently have two-factor authentication enabled on your account. Click
46-
the button below to begin configuring it.
47-
</p>
48-
<div css={tw`mt-6`}>
49-
<Button
50-
color={'green'}
51-
isSecondary
52-
onClick={() => setVisible(true)}
53-
>
54-
Begin Setup
55-
</Button>
56-
</div>
57-
</div>
58-
;
34+
);
5935
};
Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import React from 'react';
1+
import React, { useContext } from 'react';
22
import { Form, Formik, FormikHelpers } from 'formik';
3-
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
43
import FlashMessageRender from '@/components/FlashMessageRender';
54
import Field from '@/components/elements/Field';
65
import { object, string } from 'yup';
@@ -9,26 +8,31 @@ import { ApplicationStore } from '@/state';
98
import disableAccountTwoFactor from '@/api/account/disableAccountTwoFactor';
109
import tw from 'twin.macro';
1110
import Button from '@/components/elements/Button';
11+
import asModal from '@/hoc/asModal';
12+
import ModalContext from '@/context/ModalContext';
1213

1314
interface Values {
1415
password: string;
1516
}
1617

17-
export default ({ ...props }: RequiredModalProps) => {
18+
const DisableTwoFactorModal = () => {
19+
const { dismiss, setPropOverrides } = useContext(ModalContext);
1820
const { clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
1921
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
2022

2123
const submit = ({ password }: Values, { setSubmitting }: FormikHelpers<Values>) => {
24+
setPropOverrides({ showSpinnerOverlay: true, dismissable: false });
2225
disableAccountTwoFactor(password)
2326
.then(() => {
2427
updateUserData({ useTotp: false });
25-
props.onDismissed();
28+
dismiss();
2629
})
2730
.catch(error => {
2831
console.error(error);
2932

3033
clearAndAddHttpError({ error, key: 'account:two-factor' });
3134
setSubmitting(false);
35+
setPropOverrides(null);
3236
});
3337
};
3438

@@ -42,29 +46,26 @@ export default ({ ...props }: RequiredModalProps) => {
4246
password: string().required('You must provider your current password in order to continue.'),
4347
})}
4448
>
45-
{({ isSubmitting, isValid }) => (
46-
<Modal {...props} dismissable={!isSubmitting} showSpinnerOverlay={isSubmitting}>
47-
<Form className={'mb-0'}>
48-
<FlashMessageRender css={tw`mb-6`} byKey={'account:two-factor'}/>
49-
<Field
50-
id={'password'}
51-
name={'password'}
52-
type={'password'}
53-
label={'Current Password'}
54-
description={'In order to disable two-factor authentication you will need to provide your account password.'}
55-
autoFocus
56-
/>
57-
<div css={tw`mt-6 text-right`}>
58-
<Button
59-
color={'red'}
60-
disabled={!isValid}
61-
>
62-
Disable Two-Factor
63-
</Button>
64-
</div>
65-
</Form>
66-
</Modal>
49+
{({ isValid }) => (
50+
<Form className={'mb-0'}>
51+
<FlashMessageRender css={tw`mb-6`} byKey={'account:two-factor'}/>
52+
<Field
53+
id={'password'}
54+
name={'password'}
55+
type={'password'}
56+
label={'Current Password'}
57+
description={'In order to disable two-factor authentication you will need to provide your account password.'}
58+
autoFocus
59+
/>
60+
<div css={tw`mt-6 text-right`}>
61+
<Button color={'red'} disabled={!isValid}>
62+
Disable Two-Factor
63+
</Button>
64+
</div>
65+
</Form>
6766
)}
6867
</Formik>
6968
);
7069
};
70+
71+
export default asModal()(DisableTwoFactorModal);
Lines changed: 83 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
2-
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
1+
import React, { useContext, useEffect, useState } from 'react';
32
import { Form, Formik, FormikHelpers } from 'formik';
43
import { object, string } from 'yup';
54
import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl';
@@ -10,16 +9,19 @@ import FlashMessageRender from '@/components/FlashMessageRender';
109
import Field from '@/components/elements/Field';
1110
import tw from 'twin.macro';
1211
import Button from '@/components/elements/Button';
12+
import asModal from '@/hoc/asModal';
13+
import ModalContext from '@/context/ModalContext';
1314

1415
interface Values {
1516
code: string;
1617
}
1718

18-
export default ({ onDismissed, ...props }: RequiredModalProps) => {
19+
const SetupTwoFactorModal = () => {
1920
const [ token, setToken ] = useState('');
2021
const [ loading, setLoading ] = useState(true);
2122
const [ recoveryTokens, setRecoveryTokens ] = useState<string[]>([]);
2223

24+
const { dismiss, setPropOverrides } = useContext(ModalContext);
2325
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
2426
const { clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
2527

@@ -33,6 +35,7 @@ export default ({ onDismissed, ...props }: RequiredModalProps) => {
3335
}, []);
3436

3537
const submit = ({ code }: Values, { setSubmitting }: FormikHelpers<Values>) => {
38+
setPropOverrides(state => ({ ...state, showSpinnerOverlay: true }));
3639
enableAccountTwoFactor(code)
3740
.then(tokens => {
3841
setRecoveryTokens(tokens);
@@ -42,16 +45,25 @@ export default ({ onDismissed, ...props }: RequiredModalProps) => {
4245

4346
clearAndAddHttpError({ error, key: 'account:two-factor' });
4447
})
45-
.then(() => setSubmitting(false));
48+
.then(() => {
49+
setSubmitting(false);
50+
setPropOverrides(state => ({ ...state, showSpinnerOverlay: false }));
51+
});
4652
};
4753

48-
const dismiss = () => {
49-
if (recoveryTokens.length > 0) {
50-
updateUserData({ useTotp: true });
51-
}
54+
useEffect(() => {
55+
setPropOverrides(state => ({
56+
...state,
57+
closeOnEscape: !recoveryTokens.length,
58+
closeOnBackground: !recoveryTokens.length,
59+
}));
5260

53-
onDismissed();
54-
};
61+
return () => {
62+
if (recoveryTokens.length > 0) {
63+
updateUserData({ useTotp: true });
64+
}
65+
};
66+
}, [ recoveryTokens ]);
5567

5668
return (
5769
<Formik
@@ -63,79 +75,69 @@ export default ({ onDismissed, ...props }: RequiredModalProps) => {
6375
.matches(/^(\d){6}$/, 'Authenticator code must be 6 digits.'),
6476
})}
6577
>
66-
{({ isSubmitting }) => (
67-
<Modal
68-
{...props}
69-
top={false}
70-
onDismissed={dismiss}
71-
dismissable={!isSubmitting}
72-
showSpinnerOverlay={loading || isSubmitting}
73-
closeOnEscape={!recoveryTokens}
74-
closeOnBackground={!recoveryTokens}
75-
>
76-
{recoveryTokens.length > 0 ?
77-
<>
78-
<h2 css={tw`text-2xl mb-4`}>Two-factor authentication enabled</h2>
79-
<p css={tw`text-neutral-300`}>
80-
Two-factor authentication has been enabled on your account. Should you loose access to
81-
this device you&apos;ll need to use one of the codes displayed below in order to access your
82-
account.
83-
</p>
84-
<p css={tw`text-neutral-300 mt-4`}>
85-
<strong>These codes will not be displayed again.</strong> Please take note of them now
86-
by storing them in a secure repository such as a password manager.
87-
</p>
88-
<pre css={tw`text-sm mt-4 rounded font-mono bg-neutral-900 p-4`}>
89-
{recoveryTokens.map(token => <code key={token} css={tw`block mb-1`}>{token}</code>)}
90-
</pre>
91-
<div css={tw`text-right`}>
92-
<Button css={tw`mt-6`} onClick={dismiss}>
93-
Close
94-
</Button>
78+
{recoveryTokens.length > 0 ?
79+
<>
80+
<h2 css={tw`text-2xl mb-4`}>Two-factor authentication enabled</h2>
81+
<p css={tw`text-neutral-300`}>
82+
Two-factor authentication has been enabled on your account. Should you loose access to
83+
this device you&apos;ll need to use one of the codes displayed below in order to access your
84+
account.
85+
</p>
86+
<p css={tw`text-neutral-300 mt-4`}>
87+
<strong>These codes will not be displayed again.</strong> Please take note of them now
88+
by storing them in a secure repository such as a password manager.
89+
</p>
90+
<pre css={tw`text-sm mt-4 rounded font-mono bg-neutral-900 p-4`}>
91+
{recoveryTokens.map(token => <code key={token} css={tw`block mb-1`}>{token}</code>)}
92+
</pre>
93+
<div css={tw`text-right`}>
94+
<Button css={tw`mt-6`} onClick={dismiss}>
95+
Close
96+
</Button>
97+
</div>
98+
</>
99+
:
100+
<Form css={tw`mb-0`}>
101+
<FlashMessageRender css={tw`mb-6`} byKey={'account:two-factor'}/>
102+
<div css={tw`flex flex-wrap`}>
103+
<div css={tw`w-full md:flex-1`}>
104+
<div css={tw`w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto`}>
105+
{!token || !token.length ?
106+
<img
107+
src={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='}
108+
css={tw`w-64 h-64 rounded`}
109+
/>
110+
:
111+
<img
112+
src={`https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${token}`}
113+
onLoad={() => setLoading(false)}
114+
css={tw`w-full h-full shadow-none rounded-none`}
115+
/>
116+
}
95117
</div>
96-
</>
97-
:
98-
<Form css={tw`mb-0`}>
99-
<FlashMessageRender css={tw`mb-6`} byKey={'account:two-factor'}/>
100-
<div css={tw`flex flex-wrap`}>
101-
<div css={tw`w-full md:flex-1`}>
102-
<div css={tw`w-32 h-32 md:w-64 md:h-64 bg-neutral-600 p-2 rounded mx-auto`}>
103-
{!token || !token.length ?
104-
<img
105-
src={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='}
106-
css={tw`w-64 h-64 rounded`}
107-
/>
108-
:
109-
<img
110-
src={`https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${token}`}
111-
onLoad={() => setLoading(false)}
112-
css={tw`w-full h-full shadow-none rounded-none`}
113-
/>
114-
}
115-
</div>
116-
</div>
117-
<div css={tw`w-full mt-6 md:mt-0 md:flex-1 md:flex md:flex-col`}>
118-
<div css={tw`flex-1`}>
119-
<Field
120-
id={'code'}
121-
name={'code'}
122-
type={'text'}
123-
title={'Code From Authenticator'}
124-
description={'Enter the code from your authenticator device after scanning the QR image.'}
125-
autoFocus={!loading}
126-
/>
127-
</div>
128-
<div css={tw`mt-6 md:mt-0 text-right`}>
129-
<Button>
130-
Setup
131-
</Button>
132-
</div>
133-
</div>
118+
</div>
119+
<div css={tw`w-full mt-6 md:mt-0 md:flex-1 md:flex md:flex-col`}>
120+
<div css={tw`flex-1`}>
121+
<Field
122+
id={'code'}
123+
name={'code'}
124+
type={'text'}
125+
title={'Code From Authenticator'}
126+
description={'Enter the code from your authenticator device after scanning the QR image.'}
127+
autoFocus={!loading}
128+
/>
134129
</div>
135-
</Form>
136-
}
137-
</Modal>
138-
)}
130+
<div css={tw`mt-6 md:mt-0 text-right`}>
131+
<Button>
132+
Setup
133+
</Button>
134+
</div>
135+
</div>
136+
</div>
137+
</Form>
138+
}
139139
</Formik>
140140
);
141141
};
142+
143+
export default asModal()(SetupTwoFactorModal);

0 commit comments

Comments
 (0)