diff --git a/apps/mobile/app/screens/settings/components/2fa.tsx b/apps/mobile/app/screens/settings/components/2fa.tsx index 3fee25cf3..958c56621 100644 --- a/apps/mobile/app/screens/settings/components/2fa.tsx +++ b/apps/mobile/app/screens/settings/components/2fa.tsx @@ -19,7 +19,7 @@ along with this program. If not, see . import { sanitizeFilename, useIsFeatureAvailable } from "@notesnook/common"; import { strings } from "@notesnook/intl"; -import { useThemeColors, VariantsWithStaticColors } from "@notesnook/theme"; +import { useThemeColors } from "@notesnook/theme"; import Clipboard from "@react-native-clipboard/clipboard"; import isMobilePhone from "validator/lib/isMobilePhone"; import React, { @@ -37,20 +37,20 @@ import { View } from "react-native"; import RNFetchBlob from "react-native-blob-util"; -import { FlatList } from "react-native-gesture-handler"; import * as ScopedStorage from "react-native-scoped-storage"; +import { Radius, Spacing } from "../../../common/design/spacing"; import { db } from "../../../common/database"; import filesystem from "../../../common/filesystem"; import DialogHeader from "../../../components/dialog/dialog-header"; +import AppIcon from "../../../components/ui/AppIcon"; import { Button } from "../../../components/ui/button"; -import { IconButton } from "../../../components/ui/icon-button"; import FormInput, { createFormRef, validators } from "../../../components/ui/input/form-input"; +import PinInput from "../../../components/ui/pin-input"; import { Pressable } from "../../../components/ui/pressable"; import Seperator from "../../../components/ui/seperator"; -import { SvgView } from "../../../components/ui/svg"; import Heading from "../../../components/ui/typography/heading"; import Paragraph from "../../../components/ui/typography/paragraph"; import useTimer from "../../../hooks/use-timer"; @@ -63,27 +63,25 @@ import { useUserStore } from "../../../stores/use-user-store"; import { eCloseSheet } from "../../../utils/events"; import { AppFontSize } from "../../../utils/size"; import { sleep } from "../../../utils/time"; -import { DefaultAppStyles } from "../../../utils/styles"; import PaywallSheet from "../../../components/sheets/paywall"; const mfaMethods: MFAMethod[] = [ { id: "app", title: strings.mfaAuthAppTitle(), body: strings.mfaAuthAppDesc(), - icon: "cellphone-key", - recommended: true + icon: "device-mobile-camera" }, { id: "sms", title: strings.mfaSmsTitle(), body: strings.mfaSmsDesc(), - icon: "message-plus-outline" + icon: "chat" }, { id: "email", title: strings.mfaEmailTitle(), body: strings.mfaEmailDesc(), - icon: "email-outline" + icon: "envelope-simple" } ]; type MFAMethod = { @@ -116,67 +114,78 @@ export const MFAMethodsPickerStep = ({ recovery, onSuccess }: MFAStepProps) => { return mfaMethods.filter((m) => m.id !== user?.mfa?.primaryMethod); }; + const onMethodPress = (item: MFAMethod) => { + if (item.id === "sms" && featureAvailable && !featureAvailable?.isAllowed) { + ToastManager.show({ + message: featureAvailable?.error, + type: "info", + context: "local", + actionText: strings.upgrade(), + func: () => { + PaywallSheet.present(featureAvailable); + } + }); + return; + } + + onSuccess && onSuccess(item); + }; + return ( <> - {getMethods().map((item) => ( + {getMethods().map((item, index, methods) => ( { - if ( - item.id === "sms" && - featureAvailable && - !featureAvailable?.isAllowed - ) { - ToastManager.show({ - message: featureAvailable?.error, - type: "info", - context: "local", - actionText: strings.upgrade(), - func: () => { - PaywallSheet.present(featureAvailable); - } - }); - return; - } - onSuccess && onSuccess(item); - }} + onPress={() => onMethodPress(item)} style={{ - paddingHorizontal: DefaultAppStyles.GAP, - paddingVertical: DefaultAppStyles.GAP_VERTICAL, - marginTop: 0, - marginBottom: DefaultAppStyles.GAP_VERTICAL, + paddingHorizontal: Spacing.LEVEL_3, + paddingVertical: Spacing.LEVEL_1, + marginBottom: index === methods.length - 1 ? 0 : Spacing.LEVEL_0, flexDirection: "row", - borderRadius: 0, - alignItems: "flex-start" + borderRadius: Radius.S, + alignItems: "center", + backgroundColor: colors.primary.background }} > {item.icon && ( - + > + + )} - {item.title} + + {item.title} + {item.body} @@ -211,6 +220,7 @@ export const MFASetup = ({ const [loading, setLoading] = useState(method?.id === "app" ? true : false); const [enabling, setEnabling] = useState(false); const [sending, setSending] = useState(false); + const [codeValue, setCodeValue] = useState(""); const [generalError, setGeneralError] = useState(); useEffect(() => { @@ -245,6 +255,7 @@ export const MFASetup = ({ : formRef.current.getValue("target") ); formRef.current.setValue("code", ""); + setCodeValue(""); setGeneralError(undefined); }, [authenticatorDetails.sharedKey, methodId, user?.email]); @@ -280,11 +291,18 @@ export const MFASetup = ({ ]; const onNext = async () => { - if (formRef.current.validateField("code")) return; + const code = codeValue.trim(); + const codeValidationError = codeValidators + .map((validator) => validator(code)) + .find(Boolean); + + if (codeValidationError) { + setGeneralError(codeValidationError); + return; + } try { if (!method) return; - const code = formRef.current.getValue("code").trim(); setGeneralError(undefined); setEnabling(true); @@ -300,7 +318,7 @@ export const MFASetup = ({ setEnabling(false); } catch (e) { const error = e as Error; - formRef.current.setError("code", error.message); + setGeneralError(error.message); setEnabling(false); } }; @@ -363,18 +381,34 @@ export const MFASetup = ({ } }; + const onChangeMethodPress = () => { + setStep && + setStep({ + id: "mfapick", + props: { + recovery: recovery + } + }); + }; + return !method ? null : ( - + - {loading ? ( @@ -423,16 +457,27 @@ export const MFASetup = ({ } onSubmitEditing={onSendCode} validators={targetValidators} + containerStyle={{ + borderWidth: 0, + borderRadius: Radius.XS + }} keyboardType={ method.id === "email" ? "email-address" : "phone-pad" } buttons={