diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 9bce3e8..bf3a3fa 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -307,6 +307,20 @@ "description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.", "submit": "I have noted down my recovery codes" } + }, + "delete": { + "showAction": "Disable 2FA", + "askType": { + "code": "I have my 2FA code", + "recoveryCode": "I have a recovery code" + }, + "askCode": { + "label": "Code" + }, + "askRecoveryCode": { + "label": "Recovery Code" + }, + "submit": "Disable 2FA" } } }, diff --git a/src/apis/delete-2fa.ts b/src/apis/delete-2fa.ts new file mode 100644 index 0000000..cf98b2b --- /dev/null +++ b/src/apis/delete-2fa.ts @@ -0,0 +1,22 @@ +import {SimpleDetailResponse} from "~/server-types" +import {client} from "~/constants/axios-client" + +export interface Delete2FAData { + code?: string + recoveryCode?: string +} + +export default async function delete2FA({ + recoveryCode, + code, +}: Delete2FAData): Promise { + const {data} = await client.delete(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/setup-otp`, { + withCredentials: true, + data: { + code, + recoveryCode, + }, + }) + + return data +} diff --git a/src/apis/index.ts b/src/apis/index.ts index 1a76ae7..b131e41 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -64,3 +64,5 @@ export * from "./setup-2fa" export {default as setup2FA} from "./setup-2fa" export * from "./setup-2fa-verify" export {default as verify2FASetup} from "./setup-2fa-verify" +export * from "./delete-2fa" +export {default as delete2FA} from "./delete-2fa" diff --git a/src/route-widgets/Settings2FARoute/Delete2FA.tsx b/src/route-widgets/Settings2FARoute/Delete2FA.tsx new file mode 100644 index 0000000..771d57e --- /dev/null +++ b/src/route-widgets/Settings2FARoute/Delete2FA.tsx @@ -0,0 +1,105 @@ +import {ReactElement, useState} from "react" +import {AxiosError} from "axios" +import {useTranslation} from "react-i18next" +import {BsPhone, BsShieldLockFill} from "react-icons/bs" +import {MdSettingsBackupRestore} from "react-icons/md" + +import {useMutation} from "@tanstack/react-query" +import {Button, Grid, TextField} from "@mui/material" +import {LoadingButton} from "@mui/lab" + +import {Delete2FAData, delete2FA} from "~/apis" +import {useErrorSuccessSnacks} from "~/hooks" +import {SimpleDetailResponse} from "~/server-types" + +export interface Delete2FAProps { + onSuccess: () => void +} + +export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { + const {t} = useTranslation() + const {showError} = useErrorSuccessSnacks() + const {mutate} = useMutation(delete2FA, { + onSuccess, + onError: showError, + }) + + const [view, setView] = useState<"showAction" | "askType" | "askCode" | "askRecoveryCode">( + "showAction", + ) + const [value, setValue] = useState("") + + switch (view) { + case "showAction": + return ( + + ) + + case "askType": + return ( + + + + + + + + + ) + + case "askCode": + return ( + + + setValue(e.target.value)} + /> + + + mutate({code: value})} + variant="contained" + startIcon={} + > + {t("routes.SettingsRoute.2fa.delete.submit")} + + + + ) + + case "askRecoveryCode": + return ( + + + setValue(e.target.value)} + /> + + + mutate({recoveryCode: value})} + variant="contained" + startIcon={} + > + {t("routes.SettingsRoute.2fa.delete.submit")} + + + + ) + } +} diff --git a/src/routes/Settings2FARoute.tsx b/src/routes/Settings2FARoute.tsx index 8595c14..068dc3c 100644 --- a/src/routes/Settings2FARoute.tsx +++ b/src/routes/Settings2FARoute.tsx @@ -3,9 +3,10 @@ import {AxiosError} from "axios" import {useTranslation} from "react-i18next" import {useQuery} from "@tanstack/react-query" -import {Alert} from "@mui/material" +import {Alert, Grid} from "@mui/material" import {QueryResult, SimplePageBuilder} from "~/components" +import Delete2FA from "~/route-widgets/Settings2FARoute/Delete2FA" import Setup2FA from "~/route-widgets/Settings2FARoute/Setup2FA" import getHas2FAEnabled from "~/apis/get-has-2fa-enabled" @@ -19,11 +20,16 @@ export default function Settings2FARoute(): ReactElement { query={query}> {has2FAEnabled => has2FAEnabled ? ( - <> - - {t("routes.SettingsRoute.2fa.alreadyEnabled")} - - + + + + {t("routes.SettingsRoute.2fa.alreadyEnabled")} + + + + + + ) : ( )