feat: 2fa pin input component
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
"perfect-freehand": "^1.2.0",
|
"perfect-freehand": "^1.2.0",
|
||||||
"posthog-js": "^1.75.3",
|
"posthog-js": "^1.75.3",
|
||||||
"posthog-node": "^3.1.1",
|
"posthog-node": "^3.1.1",
|
||||||
|
"rci": "^0.1.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"uqr": "^0.1.2",
|
"uqr": "^0.1.2",
|
||||||
|
"use-is-focused": "^0.0.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@documenso/ui/primitives/form/form';
|
} from '@documenso/ui/primitives/form/form';
|
||||||
import { Input } from '@documenso/ui/primitives/input';
|
|
||||||
import { PasswordInput } from '@documenso/ui/primitives/password-input';
|
import { PasswordInput } from '@documenso/ui/primitives/password-input';
|
||||||
|
import { PinInput, type PinInputState } from '@documenso/ui/primitives/pin-input';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
import { RecoveryCodeList } from './recovery-code-list';
|
import { RecoveryCodeList } from './recovery-code-list';
|
||||||
@@ -54,6 +54,7 @@ export const EnableAuthenticatorAppDialog = ({
|
|||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: EnableAuthenticatorAppDialogProps) => {
|
}: EnableAuthenticatorAppDialogProps) => {
|
||||||
|
const [state, setState] = useState<PinInputState>('input');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
@@ -119,13 +120,15 @@ export const EnableAuthenticatorAppDialog = ({
|
|||||||
token,
|
token,
|
||||||
}: TEnableTwoFactorAuthenticationForm) => {
|
}: TEnableTwoFactorAuthenticationForm) => {
|
||||||
try {
|
try {
|
||||||
await enableTwoFactorAuthentication({ code: token });
|
const enabled2fa = await enableTwoFactorAuthentication({ code: token });
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Two-factor authentication enabled',
|
title: 'Two-factor authentication enabled',
|
||||||
description:
|
description:
|
||||||
'Two-factor authentication has been enabled for your account. You will now be required to enter a code from your authenticator app when signing in.',
|
'Two-factor authentication has been enabled for your account. You will now be required to enter a code from your authenticator app when signing in.',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return enabled2fa;
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Unable to setup two-factor authentication',
|
title: 'Unable to setup two-factor authentication',
|
||||||
@@ -136,6 +139,31 @@ export const EnableAuthenticatorAppDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const onPinInputChange = ({ currentTarget: input }: any) => {
|
||||||
|
input.value = input.value.replace(/\D+/g, '');
|
||||||
|
|
||||||
|
if (input.value.length === 6) {
|
||||||
|
setState('loading');
|
||||||
|
|
||||||
|
void onEnableTwoFactorAuthenticationFormSubmit({ token: input.value }).then((success) => {
|
||||||
|
if (success) {
|
||||||
|
setState('success');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState('error');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setState('input');
|
||||||
|
input.value = '';
|
||||||
|
input.dispatchEvent(new Event('input'));
|
||||||
|
input.focus();
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onCompleteClick = () => {
|
const onCompleteClick = () => {
|
||||||
flushSync(() => {
|
flushSync(() => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
@@ -146,7 +174,7 @@ export const EnableAuthenticatorAppDialog = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl">
|
<DialogContent className="w-full max-w-md md:max-w-md lg:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Enable Authenticator App</DialogTitle>
|
<DialogTitle>Enable Authenticator App</DialogTitle>
|
||||||
|
|
||||||
@@ -241,18 +269,18 @@ export const EnableAuthenticatorAppDialog = ({
|
|||||||
<FormField
|
<FormField
|
||||||
name="token"
|
name="token"
|
||||||
control={enableTwoFactorAuthenticationForm.control}
|
control={enableTwoFactorAuthenticationForm.control}
|
||||||
render={({ field }) => (
|
render={({ field: _field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="text-muted-foreground">Token</FormLabel>
|
<FormLabel className="text-muted-foreground">Token</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} type="text" value={field.value ?? ''} />
|
<PinInput id="remix" state={state} onChange={onPinInputChange} autoFocus />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DialogFooter>
|
{/* <DialogFooter>
|
||||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
@@ -260,7 +288,7 @@ export const EnableAuthenticatorAppDialog = ({
|
|||||||
<Button type="submit" loading={isEnableTwoFactorAuthenticationSubmitting}>
|
<Button type="submit" loading={isEnableTwoFactorAuthenticationSubmitting}>
|
||||||
Enable 2FA
|
Enable 2FA
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter> */}
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
))
|
))
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -151,6 +151,7 @@
|
|||||||
"perfect-freehand": "^1.2.0",
|
"perfect-freehand": "^1.2.0",
|
||||||
"posthog-js": "^1.75.3",
|
"posthog-js": "^1.75.3",
|
||||||
"posthog-node": "^3.1.1",
|
"posthog-node": "^3.1.1",
|
||||||
|
"rci": "^0.1.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
@@ -163,6 +164,7 @@
|
|||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"uqr": "^0.1.2",
|
"uqr": "^0.1.2",
|
||||||
|
"use-is-focused": "^0.0.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -15726,6 +15728,18 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rci": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rci/-/rci-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-o/elFrXXRLdYDAq/qQUFE175TqzJ5nU3MYwIwa6WOZfljNJ4akQSy1n7zA79swB696MNIFDWJs+Do0q2FBTy+Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"use-code-input": "0.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/re-resizable": {
|
"node_modules/re-resizable": {
|
||||||
"version": "6.9.6",
|
"version": "6.9.6",
|
||||||
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.6.tgz",
|
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.6.tgz",
|
||||||
@@ -18860,6 +18874,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-code-input": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-code-input/-/use-code-input-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-lDIUiRca0K8sF+c/KZ9cz5g6oPqlFiTmaDgwGzg0wlNSnFAvROtweKy0XpihEWJwo2tjETtgAxIh82RVGaBFHQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/use-is-focused": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-is-focused/-/use-is-focused-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-EXVmfDqdzUJOYukC9rBCs4TYd93lDVAL6TxegnV0+3U4cBxWxhbyt1bOm5u1ox+0MZZjamBFU/NSTLTtex2uwQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/use-sidecar": {
|
"node_modules/use-sidecar": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
||||||
|
|||||||
Reference in New Issue
Block a user