Skip to content

Commit 92926ca

Browse files
committed
Cleanup logic powering totp enabling modal
1 parent a4feed2 commit 92926ca

File tree

2 files changed

+76
-87
lines changed

2 files changed

+76
-87
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,24 @@ import { useStoreState } from 'easy-peasy';
33
import { ApplicationStore } from '@/state';
44
import tw from 'twin.macro';
55
import { Button } from '@/components/elements/button/index';
6-
import SetupTOTPModal from '@/components/dashboard/forms/SetupTOTPModal';
76
import DisableTwoFactorModal from '@/components/dashboard/forms/DisableTwoFactorModal';
7+
import SetupTOTPModal from '@/components/dashboard/forms/SetupTOTPModal';
8+
import RecoveryTokensDialog from '@/components/dashboard/forms/RecoveryTokensDialog';
89

910
export default () => {
11+
const [tokens, setTokens] = useState<string[]>([]);
1012
const [visible, setVisible] = useState<'enable' | 'disable' | null>(null);
1113
const isEnabled = useStoreState((state: ApplicationStore) => state.user.data!.useTotp);
1214

15+
const onTokens = (tokens: string[]) => {
16+
setTokens(tokens);
17+
setVisible(null);
18+
};
19+
1320
return (
1421
<div>
15-
<SetupTOTPModal open={visible === 'enable'} onClose={() => setVisible(null)} />
22+
<SetupTOTPModal open={visible === 'enable'} onClose={() => setVisible(null)} onTokens={onTokens} />
23+
<RecoveryTokensDialog tokens={tokens} open={tokens.length > 0} onClose={() => setTokens([])} />
1624
<DisableTwoFactorModal visible={visible === 'disable'} onModalDismissed={() => setVisible(null)} />
1725
<p css={tw`text-sm`}>
1826
{isEnabled
Lines changed: 66 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useEffect, useState } from 'react';
2-
import { Dialog, DialogProps } from '@/components/elements/dialog';
1+
import React, { useContext, useEffect, useState } from 'react';
2+
import { Dialog, DialogWrapperContext } from '@/components/elements/dialog';
33
import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
44
import { useFlashKey } from '@/plugins/useFlash';
55
import tw from 'twin.macro';
@@ -11,39 +11,28 @@ import CopyOnClick from '@/components/elements/CopyOnClick';
1111
import Tooltip from '@/components/elements/tooltip/Tooltip';
1212
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
1313
import FlashMessageRender from '@/components/FlashMessageRender';
14-
import RecoveryTokensDialog from '@/components/dashboard/forms/RecoveryTokensDialog';
1514
import { Actions, useStoreActions } from 'easy-peasy';
1615
import { ApplicationStore } from '@/state';
16+
import asDialog from '@/hoc/asDialog';
1717

18-
type SetupTOTPModalProps = DialogProps;
18+
interface Props {
19+
onTokens: (tokens: string[]) => void;
20+
}
1921

20-
export default ({ open, onClose }: SetupTOTPModalProps) => {
22+
const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
2123
const [submitting, setSubmitting] = useState(false);
2224
const [value, setValue] = useState('');
23-
const [tokens, setTokens] = useState<string[]>([]);
2425
const [token, setToken] = useState<TwoFactorTokenData | null>(null);
2526
const { clearAndAddHttpError } = useFlashKey('account:two-step');
2627
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
2728

28-
useEffect(() => {
29-
if (!open) return;
29+
const { close } = useContext(DialogWrapperContext);
3030

31+
useEffect(() => {
3132
getTwoFactorTokenData()
3233
.then(setToken)
33-
.then(() => updateUserData({ useTotp: true }))
3434
.catch((error) => clearAndAddHttpError(error));
35-
}, [open]);
36-
37-
useEffect(() => {
38-
if (!open) return;
39-
40-
return () => {
41-
setToken(null);
42-
setValue('');
43-
setSubmitting(false);
44-
clearAndAddHttpError(undefined);
45-
};
46-
}, [open]);
35+
}, []);
4736

4837
const submit = () => {
4938
if (submitting) return;
@@ -52,76 +41,68 @@ export default ({ open, onClose }: SetupTOTPModalProps) => {
5241
clearAndAddHttpError();
5342

5443
enableAccountTwoFactor(value)
55-
.then(setTokens)
56-
.catch(clearAndAddHttpError)
57-
.then(() => setSubmitting(false));
44+
.then((tokens) => {
45+
updateUserData({ useTotp: true });
46+
onTokens(tokens);
47+
})
48+
.catch((error) => {
49+
clearAndAddHttpError(error);
50+
setSubmitting(false);
51+
});
5852
};
5953

6054
return (
6155
<>
62-
<RecoveryTokensDialog tokens={tokens} open={open && tokens.length > 0} onClose={onClose} />
63-
<Dialog
64-
open={open && !tokens.length}
65-
onClose={onClose}
66-
title={'Enable Two-Step Verification'}
67-
preventExternalClose={submitting}
68-
description={
69-
"Help protect your account from unauthorized access. You'll be prompted for a verification code each time you sign in."
70-
}
56+
<FlashMessageRender byKey={'account:two-step'} className={'mt-4'} />
57+
<div
58+
className={'flex items-center justify-center w-56 h-56 p-2 bg-gray-800 rounded-lg shadow mx-auto mt-6'}
7159
>
72-
<FlashMessageRender byKey={'account:two-step'} className={'mt-4'} />
73-
<div
74-
className={
75-
'flex items-center justify-center w-56 h-56 p-2 bg-gray-800 rounded-lg shadow mx-auto mt-6'
76-
}
60+
{!token ? (
61+
<Spinner />
62+
) : (
63+
<QRCode renderAs={'svg'} value={token.image_url_data} css={tw`w-full h-full shadow-none rounded`} />
64+
)}
65+
</div>
66+
<CopyOnClick text={token?.secret}>
67+
<p className={'font-mono text-sm text-gray-100 text-center mt-2'}>
68+
{token?.secret.match(/.{1,4}/g)!.join(' ') || 'Loading...'}
69+
</p>
70+
</CopyOnClick>
71+
<div className={'mt-6'}>
72+
<p>
73+
Scan the QR code above using the two-step authentication app of your choice. Then, enter the 6-digit
74+
code generated into the field below.
75+
</p>
76+
</div>
77+
<Input.Text
78+
variant={Input.Text.Variants.Loose}
79+
value={value}
80+
onChange={(e) => setValue(e.currentTarget.value)}
81+
className={'mt-4'}
82+
placeholder={'000000'}
83+
type={'text'}
84+
inputMode={'numeric'}
85+
autoComplete={'one-time-code'}
86+
pattern={'\\d{6}'}
87+
/>
88+
<Dialog.Footer>
89+
<Button.Text onClick={close}>Cancel</Button.Text>
90+
<Tooltip
91+
disabled={value.length === 6}
92+
content={!token ? 'Waiting for QR code to load...' : 'You must enter the 6-digit code to continue.'}
93+
delay={100}
7794
>
78-
{!token ? (
79-
<Spinner />
80-
) : (
81-
<QRCode
82-
renderAs={'svg'}
83-
value={token.image_url_data}
84-
css={tw`w-full h-full shadow-none rounded`}
85-
/>
86-
)}
87-
</div>
88-
<CopyOnClick text={token?.secret}>
89-
<p className={'font-mono text-sm text-gray-100 text-center mt-2'}>
90-
{token?.secret.match(/.{1,4}/g)!.join(' ') || 'Loading...'}
91-
</p>
92-
</CopyOnClick>
93-
<div className={'mt-6'}>
94-
<p>
95-
Scan the QR code above using the two-step authentication app of your choice. Then, enter the
96-
6-digit code generated into the field below.
97-
</p>
98-
</div>
99-
<Input.Text
100-
variant={Input.Text.Variants.Loose}
101-
value={value}
102-
onChange={(e) => setValue(e.currentTarget.value)}
103-
className={'mt-4'}
104-
placeholder={'000000'}
105-
type={'text'}
106-
inputMode={'numeric'}
107-
autoComplete={'one-time-code'}
108-
pattern={'\\d{6}'}
109-
/>
110-
<Dialog.Footer>
111-
<Button.Text onClick={onClose}>Cancel</Button.Text>
112-
<Tooltip
113-
disabled={value.length === 6}
114-
content={
115-
!token ? 'Waiting for QR code to load...' : 'You must enter the 6-digit code to continue.'
116-
}
117-
delay={100}
118-
>
119-
<Button disabled={!token || value.length !== 6} onClick={submit}>
120-
Enable
121-
</Button>
122-
</Tooltip>
123-
</Dialog.Footer>
124-
</Dialog>
95+
<Button disabled={!token || value.length !== 6} onClick={submit}>
96+
Enable
97+
</Button>
98+
</Tooltip>
99+
</Dialog.Footer>
125100
</>
126101
);
127102
};
103+
104+
export default asDialog({
105+
title: 'Enable Two-Step Verification',
106+
description:
107+
"Help protect your account from unauthorized access. You'll be prompted for a verification code each time you sign in.",
108+
})(ConfigureTwoFactorForm);

0 commit comments

Comments
 (0)