diff --git a/apps/web/package.json b/apps/web/package.json index fd4faa0c1..8656d5092 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -35,6 +35,7 @@ "perfect-freehand": "^1.2.0", "posthog-js": "^1.75.3", "posthog-node": "^3.1.1", + "rci": "^0.1.0", "react": "18.2.0", "react-dom": "18.2.0", "react-dropzone": "^14.2.3", @@ -47,6 +48,7 @@ "typescript": "5.2.2", "ua-parser-js": "^1.0.37", "uqr": "^0.1.2", + "use-is-focused": "^0.0.1", "zod": "^3.22.4" }, "devDependencies": { diff --git a/apps/web/src/components/forms/2fa/enable-authenticator-app-dialog.tsx b/apps/web/src/components/forms/2fa/enable-authenticator-app-dialog.tsx index 7a181c4cc..7a493d5b0 100644 --- a/apps/web/src/components/forms/2fa/enable-authenticator-app-dialog.tsx +++ b/apps/web/src/components/forms/2fa/enable-authenticator-app-dialog.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; @@ -27,8 +27,8 @@ import { FormLabel, FormMessage, } from '@documenso/ui/primitives/form/form'; -import { Input } from '@documenso/ui/primitives/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 { RecoveryCodeList } from './recovery-code-list'; @@ -54,6 +54,7 @@ export const EnableAuthenticatorAppDialog = ({ open, onOpenChange, }: EnableAuthenticatorAppDialogProps) => { + const [state, setState] = useState('input'); const router = useRouter(); const { toast } = useToast(); @@ -119,13 +120,15 @@ export const EnableAuthenticatorAppDialog = ({ token, }: TEnableTwoFactorAuthenticationForm) => { try { - await enableTwoFactorAuthentication({ code: token }); + const enabled2fa = await enableTwoFactorAuthentication({ code: token }); toast({ title: 'Two-factor authentication enabled', 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.', }); + + return enabled2fa; } catch (_err) { toast({ 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 = () => { flushSync(() => { onOpenChange(false); @@ -146,7 +174,7 @@ export const EnableAuthenticatorAppDialog = ({ return ( - + Enable Authenticator App @@ -241,18 +269,18 @@ export const EnableAuthenticatorAppDialog = ({ ( + render={({ field: _field }) => ( Token - + )} /> - + {/* @@ -260,7 +288,7 @@ export const EnableAuthenticatorAppDialog = ({ - + */} )) diff --git a/package-lock.json b/package-lock.json index 3c136e801..803a68344 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,6 +151,7 @@ "perfect-freehand": "^1.2.0", "posthog-js": "^1.75.3", "posthog-node": "^3.1.1", + "rci": "^0.1.0", "react": "18.2.0", "react-dom": "18.2.0", "react-dropzone": "^14.2.3", @@ -163,6 +164,7 @@ "typescript": "5.2.2", "ua-parser-js": "^1.0.37", "uqr": "^0.1.2", + "use-is-focused": "^0.0.1", "zod": "^3.22.4" }, "devDependencies": { @@ -15726,6 +15728,18 @@ "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": { "version": "6.9.6", "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": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",