diff --git a/apps/web/src/app/[orgShortCode]/settings/user/security/_components/reset-modals.tsx b/apps/web/src/app/[orgShortCode]/settings/user/security/_components/reset-modals.tsx index 3a920ced..8b2852a9 100644 --- a/apps/web/src/app/[orgShortCode]/settings/user/security/_components/reset-modals.tsx +++ b/apps/web/src/app/[orgShortCode]/settings/user/security/_components/reset-modals.tsx @@ -25,7 +25,8 @@ import { } from '@/src/components/input-otp'; import { ms } from 'itty-time'; import Image from 'next/image'; -// import { downloadAsFile } from '@/src/lib/utils'; +import { downloadAsFile } from '@/src/lib/utils'; +import { toast } from 'sonner'; export function PasswordModal({ open, @@ -367,53 +368,84 @@ const MemoizedQrCode = memo( (prev, next) => prev.text === next.text ); -/* export const recoveryCodeModal = () => - Modal( - ({ onResolve, open, args }) => { - const [downloaded, setDownloaded] = useState(false); +export function RecoveryCodeModal({ + open, + mode, + onResolve, + onClose, + verificationToken +}: ModalComponent<{ mode: 'reset' | 'disable'; verificationToken: string }>) { + const { + mutateAsync: resetRecovery, + isLoading: resetRecoveryLoading, + error: resetError + } = api.account.security.resetRecoveryCode.useMutation(); + const { + mutateAsync: disableRecovery, + isLoading: disableRecoveryLoading, + error: disableError + } = api.account.security.disableRecoveryCode.useMutation(); - return ( - - - - Recovery Code - - - - Save this recovery code in a safe place, without this code you - would not be able to recover your account - - - - {args?.recoveryCode} - - - - - - - - ); - } - ); */ + const [code, setCode] = useState(''); + + return ( + + + + {mode === 'reset' ? 'Set up Recovery' : 'Disable Recovery'} + + + {mode === 'reset' ? ( + + You are going to setup/reset your Recovery Code, If you already + had a recovery code that would be invalidated after this. + + ) : ( + Are you sure you want to disable your Recovery Code? + )} + +
+
+ {resetError?.message ?? disableError?.message} +
+ {code ? ( + + ) : ( + + )} + +
+
+
+ ); +} diff --git a/apps/web/src/app/[orgShortCode]/settings/user/security/page.tsx b/apps/web/src/app/[orgShortCode]/settings/user/security/page.tsx index 6f60f7c2..5ef7b7bf 100644 --- a/apps/web/src/app/[orgShortCode]/settings/user/security/page.tsx +++ b/apps/web/src/app/[orgShortCode]/settings/user/security/page.tsx @@ -4,7 +4,11 @@ import { Flex, Heading, Spinner, Text, Switch, Button } from '@radix-ui/themes'; import { useEffect, useState } from 'react'; import { api } from '@/src/lib/trpc'; import { VerificationModal } from './_components/verification-modal'; -import { PasswordModal, TOTPModal } from './_components/reset-modals'; +import { + PasswordModal, + TOTPModal, + RecoveryCodeModal +} from './_components/reset-modals'; import useAwaitableModal from '@/src/hooks/use-awaitable-modal'; import { toast } from 'sonner'; @@ -47,6 +51,14 @@ export default function Page() { verificationToken: '' }); + const [RecoveryModalRoot, openRecoveryModal] = useAwaitableModal( + RecoveryCodeModal, + { + verificationToken: '', + mode: 'reset' + } + ); + async function waitForVerification() { if (!initData) throw new Error('No init data'); if (verificationToken) return verificationToken; @@ -93,85 +105,108 @@ export default function Page() { )} {!isInitDataLoading && initData && ( - - - - Enable Password and 2FA Login - { - const token = await waitForVerification(); - if (!token) return; - - if (isPassword2FaEnabled) { - disableLegacySecurity({ - verificationToken: verificationToken ?? token - }); - await refreshSecurityData(); - setIsPassword2FaEnabled(false); - } else { - const passwordSet = await openPasswordModal({ +
+
+ Legacy Security + + + Enable Password and 2FA Login + { + const token = await waitForVerification(); + if (!token) return; + + if (isPassword2FaEnabled) { + disableLegacySecurity({ + verificationToken: verificationToken ?? token + }); + await refreshSecurityData(); + setIsPassword2FaEnabled(false); + } else { + const passwordSet = await openPasswordModal({ + verificationToken: verificationToken ?? token + }).catch(() => false); + const otpSet = await openTOTPModal({ + verificationToken: verificationToken ?? token + }).catch(() => false); + if (!passwordSet || !otpSet) return; + await refreshSecurityData(); + setIsPassword2FaEnabled(true); + } + }} + /> + {isDisablingLegacySecurity && } + + + +
+ {initData?.passwordSet && ( + + )} + + {initData?.twoFactorEnabled && ( + - )} - - {initData?.twoFactorEnabled && ( + }).catch(() => null); + }}> + Reset 2FA + + )} +
+
+ {(initData.recoveryCodeSet || isPassword2FaEnabled) && ( +
+ Account Recovery - )} -
- +
+ )} + )} +
); }