mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 19:49:54 +02:00
mobile: update 2fa sheets design
This commit is contained in:
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 (
|
||||
<>
|
||||
<DialogHeader
|
||||
title={strings.twoFactorAuth()}
|
||||
paragraph={strings.twoFactorAuthDesc()}
|
||||
padding={12}
|
||||
style={{
|
||||
paddingTop: Spacing.LEVEL_2,
|
||||
paddingHorizontal: Spacing.LEVEL_3
|
||||
}}
|
||||
/>
|
||||
<Seperator />
|
||||
{getMethods().map((item) => (
|
||||
{getMethods().map((item, index, methods) => (
|
||||
<Pressable
|
||||
key={item.title}
|
||||
onPress={() => {
|
||||
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 && (
|
||||
<IconButton
|
||||
type="secondary"
|
||||
<View
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
marginRight: 10
|
||||
width: Spacing.LEVEL_7,
|
||||
height: Spacing.LEVEL_7,
|
||||
borderRadius: Radius.XS,
|
||||
marginRight: Spacing.LEVEL_1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: colors.secondary.background
|
||||
}}
|
||||
size={20}
|
||||
color={
|
||||
item.recommended ? colors.primary.accent : colors.primary.icon
|
||||
}
|
||||
name={item.icon}
|
||||
/>
|
||||
>
|
||||
<AppIcon
|
||||
size={16}
|
||||
iconFamily="notesnook"
|
||||
color={colors.primary.icon}
|
||||
name={item.icon}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View
|
||||
style={{
|
||||
flexShrink: 1
|
||||
flex: 1,
|
||||
flexShrink: 1,
|
||||
gap: Spacing.LEVEL_1
|
||||
}}
|
||||
>
|
||||
<Heading size={AppFontSize.md}>{item.title}</Heading>
|
||||
<Heading size={AppFontSize.md} lineHeight="100%">
|
||||
{item.title}
|
||||
</Heading>
|
||||
<Paragraph size={AppFontSize.sm}>{item.body}</Paragraph>
|
||||
</View>
|
||||
</Pressable>
|
||||
@@ -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<string>();
|
||||
|
||||
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 : (
|
||||
<View>
|
||||
<View
|
||||
style={{
|
||||
gap: Spacing.LEVEL_3
|
||||
}}
|
||||
>
|
||||
<DialogHeader
|
||||
title={method?.title}
|
||||
paragraph={method?.body}
|
||||
padding={12}
|
||||
style={{
|
||||
paddingTop: Spacing.LEVEL_2,
|
||||
paddingHorizontal: Spacing.LEVEL_3
|
||||
}}
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
paddingHorizontal: Spacing.LEVEL_3
|
||||
}}
|
||||
>
|
||||
{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={
|
||||
<Button
|
||||
onPress={onSendCode}
|
||||
type="transparent"
|
||||
fontFamily="MEDIUM"
|
||||
fontSize={AppFontSize.sm}
|
||||
buttonType={{
|
||||
text: colors.primary.accent
|
||||
}}
|
||||
loading={sending}
|
||||
style={{
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 0
|
||||
paddingHorizontal: 0,
|
||||
marginLeft: Spacing.LEVEL_1
|
||||
}}
|
||||
title={
|
||||
sending
|
||||
@@ -449,42 +494,67 @@ export const MFASetup = ({
|
||||
}
|
||||
/>
|
||||
|
||||
<Heading size={AppFontSize.md}>
|
||||
{strings.enterSixDigitCode()}
|
||||
</Heading>
|
||||
<Paragraph>{codeHelpText[method?.id]}</Paragraph>
|
||||
<Seperator />
|
||||
<FormInput
|
||||
name="code"
|
||||
formRef={formRef}
|
||||
fwdRef={codeInputRef}
|
||||
placeholder="xxxxxx"
|
||||
maxLength={6}
|
||||
loading={loading}
|
||||
textAlign="center"
|
||||
keyboardType="numeric"
|
||||
onChangeText={() => {
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
marginVertical: Spacing.LEVEL_3,
|
||||
backgroundColor: colors.primary.border
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
gap: Spacing.LEVEL_1,
|
||||
paddingVertical: Spacing.LEVEL_1,
|
||||
marginBottom: Spacing.LEVEL_3
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: Spacing.LEVEL_7,
|
||||
height: Spacing.LEVEL_7,
|
||||
borderRadius: Radius.XS,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: colors.secondary.background
|
||||
}}
|
||||
>
|
||||
<AppIcon
|
||||
size={16}
|
||||
iconFamily="notesnook"
|
||||
color={colors.primary.icon}
|
||||
name="device-mobile-camera"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
gap: Spacing.LEVEL_1
|
||||
}}
|
||||
>
|
||||
<Heading size={AppFontSize.md} lineHeight="100%">
|
||||
{strings.enterSixDigitCode()}
|
||||
</Heading>
|
||||
<Paragraph size={AppFontSize.sm}>
|
||||
{codeHelpText[method?.id]}
|
||||
</Paragraph>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<PinInput
|
||||
value={codeValue}
|
||||
length={6}
|
||||
inputRef={codeInputRef}
|
||||
onSubmitEditing={onNext}
|
||||
onChangeText={(value) => {
|
||||
formRef.current.setValue("code", value);
|
||||
setCodeValue(value);
|
||||
setGeneralError(undefined);
|
||||
}}
|
||||
onSubmitEditing={onNext}
|
||||
returnKeyLabel={strings.next()}
|
||||
returnKeyType="done"
|
||||
validators={codeValidators}
|
||||
inputStyle={{
|
||||
fontSize: AppFontSize.lg,
|
||||
height: 60,
|
||||
textAlign: "center",
|
||||
letterSpacing: 10,
|
||||
width: undefined
|
||||
}}
|
||||
containerStyle={{
|
||||
height: 60,
|
||||
borderWidth: 0,
|
||||
width: undefined
|
||||
}}
|
||||
errorStyle={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
sanitize={(value) => value.replace(/\D/g, "")}
|
||||
/>
|
||||
|
||||
{generalError ? (
|
||||
@@ -492,7 +562,7 @@ export const MFASetup = ({
|
||||
size={AppFontSize.sm}
|
||||
style={{
|
||||
color: colors.error.icon,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
marginBottom: Spacing.LEVEL_2,
|
||||
textAlign: "center",
|
||||
width: "100%"
|
||||
}}
|
||||
@@ -501,33 +571,30 @@ export const MFASetup = ({
|
||||
</Paragraph>
|
||||
) : null}
|
||||
|
||||
<Seperator />
|
||||
<Button
|
||||
title={enabling ? null : strings.next()}
|
||||
type="accent"
|
||||
width={250}
|
||||
onPress={onNext}
|
||||
loading={enabling}
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
gap: Spacing.LEVEL_2,
|
||||
marginTop: Spacing.LEVEL_4
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Button
|
||||
title={enabling ? null : strings.next()}
|
||||
type="accent"
|
||||
width="100%"
|
||||
onPress={onNext}
|
||||
loading={enabling}
|
||||
style={{
|
||||
borderRadius: Radius.S
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={strings.change2faMethod()}
|
||||
type="plain"
|
||||
height={25}
|
||||
onPress={() => {
|
||||
setStep &&
|
||||
setStep({
|
||||
id: "mfapick",
|
||||
props: {
|
||||
recovery: recovery
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title={strings.change2faMethod()}
|
||||
type="plain-outline"
|
||||
width="100%"
|
||||
onPress={onChangeMethodPress}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
@@ -544,6 +611,11 @@ export const MFARecoveryCodes = ({
|
||||
const [codes, setCodes] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const codeRows = [] as string[][];
|
||||
for (let i = 0; i < codes.length; i += 3) {
|
||||
codeRows.push(codes.slice(i, i + 3));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
@@ -558,140 +630,208 @@ export const MFARecoveryCodes = ({
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const onCopyCodesPress = () => {
|
||||
const codeString = codes.join("\n");
|
||||
Clipboard.setString(codeString);
|
||||
ToastManager.show({
|
||||
heading: strings.codesCopied(),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
};
|
||||
|
||||
const onSaveToFilePress = async () => {
|
||||
try {
|
||||
let path;
|
||||
let fileName = "notesnook_recoverycodes";
|
||||
fileName = sanitizeFilename(fileName, {
|
||||
replacement: "_"
|
||||
});
|
||||
fileName = fileName + ".txt";
|
||||
const codeString = codes.join("\n");
|
||||
if (Platform.OS === "android") {
|
||||
const file = await ScopedStorage.createDocument(
|
||||
fileName,
|
||||
"text/plain",
|
||||
codeString,
|
||||
"utf8"
|
||||
);
|
||||
if (!file) return;
|
||||
path = file.uri;
|
||||
} else {
|
||||
path = await filesystem.checkAndCreateDir("/");
|
||||
await RNFetchBlob.fs.writeFile(path + fileName, codeString, "utf8");
|
||||
path = path + fileName;
|
||||
}
|
||||
|
||||
ToastManager.show({
|
||||
heading: strings.codesSaved(),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
return path;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const onCompletePress = () => {
|
||||
if (isSetup) {
|
||||
onSuccess && onSuccess(method);
|
||||
} else {
|
||||
eSendEvent(eCloseSheet);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View
|
||||
style={{
|
||||
gap: Spacing.LEVEL_3
|
||||
}}
|
||||
>
|
||||
<DialogHeader
|
||||
centered={true}
|
||||
title={strings.saveRecoveryCodes()}
|
||||
paragraph={strings.saveRecoveryCodesDesc()}
|
||||
padding={12}
|
||||
style={{
|
||||
paddingTop: Spacing.LEVEL_2,
|
||||
paddingHorizontal: Spacing.LEVEL_3
|
||||
}}
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
{loading ? (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginBottom: 50
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator
|
||||
color={colors.primary.accent}
|
||||
style={{
|
||||
height: 50
|
||||
}}
|
||||
/>
|
||||
<Paragraph>
|
||||
{strings.gettingRecoveryCodes()}... {strings.pleaseWait()}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<FlatList
|
||||
data={codes}
|
||||
contentContainerStyle={{
|
||||
alignItems: "center"
|
||||
}}
|
||||
numColumns={2}
|
||||
renderItem={({ item }) => (
|
||||
<Heading
|
||||
style={{
|
||||
marginHorizontal: 15,
|
||||
marginVertical: 5,
|
||||
fontFamily: "monospace"
|
||||
}}
|
||||
size={AppFontSize.lg}
|
||||
>
|
||||
{item}
|
||||
</Heading>
|
||||
)}
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: Spacing.LEVEL_3
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginBottom: 50
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title={strings.copyCodes()}
|
||||
onPress={() => {
|
||||
const codeString = codes.join("\n");
|
||||
Clipboard.setString(codeString);
|
||||
ToastManager.show({
|
||||
heading: strings.codesCopied(),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
}}
|
||||
<ActivityIndicator
|
||||
color={colors.primary.accent}
|
||||
style={{
|
||||
marginRight: 10
|
||||
height: 50
|
||||
}}
|
||||
/>
|
||||
<Paragraph>
|
||||
{strings.gettingRecoveryCodes()}... {strings.pleaseWait()}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: Radius.S,
|
||||
paddingHorizontal: Spacing.LEVEL_2,
|
||||
paddingVertical: Spacing.LEVEL_3,
|
||||
gap: Spacing.LEVEL_1
|
||||
}}
|
||||
>
|
||||
{codeRows.map((row, rowIndex) => (
|
||||
<View
|
||||
key={`row-${rowIndex}`}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: Spacing.LEVEL_2
|
||||
}}
|
||||
>
|
||||
{row.map((code, colIndex) => (
|
||||
<Paragraph
|
||||
key={`${rowIndex}-${colIndex}`}
|
||||
size={AppFontSize.xs}
|
||||
style={{
|
||||
flex: 1,
|
||||
color: colors.primary.heading
|
||||
}}
|
||||
>
|
||||
{code}
|
||||
</Paragraph>
|
||||
))}
|
||||
{row.length < 3
|
||||
? Array.from({ length: 3 - row.length }).map((_, idx) => (
|
||||
<View
|
||||
key={`empty-${rowIndex}-${idx}`}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
</View>
|
||||
))}
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
marginTop: Spacing.LEVEL_1,
|
||||
backgroundColor: colors.primary.border
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: Spacing.LEVEL_2,
|
||||
marginTop: Spacing.LEVEL_1
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title={strings.copyCodes()}
|
||||
type="transparent"
|
||||
fontFamily="SEMI_BOLD"
|
||||
fontSize={AppFontSize.xs}
|
||||
icon="content-copy"
|
||||
iconSize={14}
|
||||
iconColor={colors.primary.accent}
|
||||
buttonType={{ text: colors.primary.accent }}
|
||||
onPress={onCopyCodesPress}
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: Radius.S,
|
||||
paddingVertical: Spacing.LEVEL_1,
|
||||
paddingHorizontal: Spacing.LEVEL_1
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={strings.saveToFile()}
|
||||
type="transparent"
|
||||
fontFamily="SEMI_BOLD"
|
||||
fontSize={AppFontSize.xs}
|
||||
icon="download-simple"
|
||||
iconFamily="notesnook"
|
||||
iconSize={14}
|
||||
iconColor={colors.primary.accent}
|
||||
buttonType={{ text: colors.primary.accent }}
|
||||
onPress={onSaveToFilePress}
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingVertical: Spacing.LEVEL_1,
|
||||
paddingHorizontal: Spacing.LEVEL_1,
|
||||
borderRadius: Radius.S
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title={strings.saveToFile()}
|
||||
onPress={async () => {
|
||||
try {
|
||||
let path;
|
||||
let fileName = "notesnook_recoverycodes";
|
||||
fileName = sanitizeFilename(fileName, { replacement: "_" });
|
||||
fileName = fileName + ".txt";
|
||||
const codeString = codes.join("\n");
|
||||
if (Platform.OS === "android") {
|
||||
const file = await ScopedStorage.createDocument(
|
||||
fileName,
|
||||
"text/plain",
|
||||
codeString,
|
||||
"utf8"
|
||||
);
|
||||
if (!file) return;
|
||||
path = file.uri;
|
||||
} else {
|
||||
path = await filesystem.checkAndCreateDir("/");
|
||||
await RNFetchBlob.fs.writeFile(
|
||||
path + fileName,
|
||||
codeString,
|
||||
"utf8"
|
||||
);
|
||||
path = path + fileName;
|
||||
}
|
||||
|
||||
ToastManager.show({
|
||||
heading: strings.codesSaved(),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
return path;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
title={isSetup ? strings.next() : strings.done()}
|
||||
type="accent"
|
||||
width="100%"
|
||||
onPress={onCompletePress}
|
||||
style={{
|
||||
borderRadius: Radius.S,
|
||||
marginTop: Spacing.LEVEL_2,
|
||||
marginBottom: Spacing.LEVEL_2
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title={isSetup ? strings.next() : strings.done()}
|
||||
type="accent"
|
||||
width={250}
|
||||
onPress={() => {
|
||||
if (isSetup) {
|
||||
onSuccess && onSuccess(method);
|
||||
} else {
|
||||
eSendEvent(eCloseSheet);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -702,61 +842,120 @@ MFARecoveryCodes.present = (methodId: MFAMethod["id"]) => {
|
||||
});
|
||||
};
|
||||
|
||||
const mfaSvg = (
|
||||
colors: VariantsWithStaticColors
|
||||
) => `<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 382.94 405.93">
|
||||
<path fill="${colors.primary.paragraph}" d="M192.58 405.92a75.19 75.19 0 0 1-18.64-2.41l-1.2-.33-1.12-.56c-40.24-20.18-74.19-46.83-100.9-79.21a299.86 299.86 0 0 1-50.95-90.47A348.21 348.21 0 0 1 .07 110.27l.04-2.02c0-20.29 11.26-38.09 28.7-45.35C42.13 57.34 163.24 7.6 172 4c16.48-8.26 34.06-1.36 36.87-.16 6.31 2.58 118.28 48.38 142.47 59.9 24.94 11.87 31.6 33.2 31.6 43.93 0 48.6-8.43 94-25.02 134.97a312.52 312.52 0 0 1-56.16 90.51c-45.85 51.6-91.7 69.89-92.15 70.05a50.11 50.11 0 0 1-17.04 2.72zm-10.79-26.71c3.98.89 13.13 2.22 19.1.05 7.58-2.77 45.96-22.67 81.83-63.03 49.55-55.77 74.7-125.88 74.74-208.38-.1-1.67-1.28-13.59-17.07-21.1-23.72-11.3-140.1-58.89-141.27-59.37l-.32-.14c-2.44-1.02-10.2-3.17-15.55-.37l-1.08.5c-1.3.54-129.86 53.34-143.57 59.05-9.6 4-13 13.9-13 21.83 0 .58-.02 1.43-.05 2.52-1.1 56.44 11.97 195.34 156.24 268.44z"/>
|
||||
<path fill="${colors.secondary.background}" d="M177.33 15.59S47.61 68.87 33.71 74.66c-13.9 5.79-20.85 19.7-20.85 33.6 0 13.9-10.45 195.26 164.47 282.96 0 0 15.88 4.39 27.92 0 12.04-4.39 164.96-78.52 164.96-283.55 0 0 0-20.85-24.33-32.43C321.55 63.66 203.94 15.6 203.94 15.6s-14.44-6.37-26.6 0z"/>
|
||||
<path d="M191.23 57.29v284.25S60.34 278.53 61.51 112.89z" opacity=".2"/>
|
||||
<path fill="${colors.primary.icon}" d="m192.94 261.58-41.69-53.61 24.24-18.86 19.75 25.38 66.7-70.4 22.3 21.13z"/>
|
||||
</svg>`;
|
||||
|
||||
const MFASuccess = ({ recovery }: MFAStepProps) => {
|
||||
const { colors } = useThemeColors();
|
||||
const { colors, isDark } = useThemeColors();
|
||||
|
||||
const onDonePress = () => {
|
||||
eSendEvent(eCloseSheet);
|
||||
};
|
||||
|
||||
const onSecondaryMethodPress = () => {
|
||||
MFASheet.present(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center"
|
||||
gap: Spacing.LEVEL_4
|
||||
}}
|
||||
>
|
||||
<Seperator />
|
||||
<SvgView width={150} height={150} src={mfaSvg(colors)} />
|
||||
<Seperator />
|
||||
<DialogHeader
|
||||
centered={true}
|
||||
title={
|
||||
recovery
|
||||
? strings.fallbackMethodEnabled()
|
||||
: strings.twoFactorAuthEnabled()
|
||||
}
|
||||
paragraph={strings.accountIsSecure()}
|
||||
padding={12}
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
<Button
|
||||
title={strings.done()}
|
||||
type="accent"
|
||||
width={250}
|
||||
onPress={() => {
|
||||
eSendEvent(eCloseSheet);
|
||||
}}
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginTop: Spacing.LEVEL_4
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 93,
|
||||
height: 93,
|
||||
borderRadius: 46.5,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: isDark ? "#0A572920" : "#00883614"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: isDark ? "#0A572930" : "#0088361F"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 47,
|
||||
height: 47,
|
||||
borderRadius: 23.5,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: colors.primary.accent
|
||||
}}
|
||||
>
|
||||
<AppIcon
|
||||
name="check"
|
||||
size={20}
|
||||
color={colors.primary.accentForeground}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{!recovery ? (
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
gap: Spacing.LEVEL_1,
|
||||
paddingHorizontal: Spacing.LEVEL_3
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
size={AppFontSize.xl}
|
||||
lineHeight="100%"
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
{recovery
|
||||
? strings.fallbackMethodEnabled()
|
||||
: strings.twoFactorAuthEnabled()}
|
||||
</Heading>
|
||||
<Paragraph size={AppFontSize.xs} style={{ textAlign: "center" }}>
|
||||
{strings.accountIsSecure()}
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
gap: Spacing.LEVEL_2,
|
||||
paddingHorizontal: Spacing.LEVEL_3
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title={strings.secondary2faMethod()}
|
||||
type="plain"
|
||||
height={25}
|
||||
onPress={() => {
|
||||
MFASheet.present(true);
|
||||
title={strings.done()}
|
||||
type="accent"
|
||||
width="100%"
|
||||
onPress={onDonePress}
|
||||
style={{
|
||||
borderRadius: Radius.S
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!recovery ? (
|
||||
<Button
|
||||
title={strings.secondary2faMethod()}
|
||||
type="plain-outline"
|
||||
width="100%"
|
||||
onPress={onSecondaryMethodPress}
|
||||
style={{
|
||||
borderRadius: Radius.S
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -47,9 +47,15 @@ const Group = ({
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const renderItem = ({ item }: { item: SettingSection; index: number }) =>
|
||||
const renderItem = ({
|
||||
item,
|
||||
index
|
||||
}: {
|
||||
item: SettingSection;
|
||||
index: number;
|
||||
}) =>
|
||||
item.type === "group" ? (
|
||||
<SectionGroup item={item} />
|
||||
<SectionGroup item={item} isLast={!route.params.sections?.[index + 1]} />
|
||||
) : (
|
||||
<SectionItem item={item} />
|
||||
);
|
||||
|
||||
@@ -292,7 +292,6 @@ export const accountGroup: SettingSection = {
|
||||
name: strings.subscriptionNotActivated(),
|
||||
icon: "warning-circle",
|
||||
iconFamily: "notesnook",
|
||||
isNavigation: true,
|
||||
hidden: () => Platform.OS !== "ios",
|
||||
modifer: async () => {
|
||||
if (Platform.OS === "android") return;
|
||||
@@ -355,63 +354,77 @@ export const accountGroup: SettingSection = {
|
||||
iconFamily: "notesnook",
|
||||
sections: [
|
||||
{
|
||||
id: "enable-2fa",
|
||||
name: strings.change2faMethod(),
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFASheet.present();
|
||||
});
|
||||
},
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
description: strings.change2faMethodDesc()
|
||||
},
|
||||
{
|
||||
id: "2fa-fallback",
|
||||
name: strings.addFallback2faMethod(),
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) => {
|
||||
return (
|
||||
!!(user as User)?.mfa?.secondaryMethod ||
|
||||
!(user as User)?.mfa?.isEnabled
|
||||
);
|
||||
},
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFASheet.present(true);
|
||||
});
|
||||
},
|
||||
description: strings.addFallback2faMethodDesc()
|
||||
},
|
||||
{
|
||||
id: "change-2fa-method",
|
||||
name: strings.change2faFallbackMethod(),
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) => {
|
||||
return (
|
||||
!(user as User)?.mfa?.secondaryMethod ||
|
||||
!(user as User)?.mfa?.isEnabled
|
||||
);
|
||||
},
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFASheet.present(true);
|
||||
});
|
||||
},
|
||||
description: strings.change2faFallbackMethod()
|
||||
},
|
||||
{
|
||||
id: "view-2fa-codes",
|
||||
name: strings.viewRecoveryCodes(),
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFARecoveryCodes.present("sms");
|
||||
});
|
||||
},
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) => {
|
||||
return !(user as User)?.mfa?.isEnabled;
|
||||
},
|
||||
description: strings.viewRecoveryCodesDesc()
|
||||
id: "2fa-settings-group",
|
||||
name: strings.twoFactorAuth(),
|
||||
type: "group",
|
||||
sections: [
|
||||
{
|
||||
id: "enable-2fa",
|
||||
name: strings.change2faMethod(),
|
||||
icon: "shield-check",
|
||||
iconFamily: "notesnook",
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFASheet.present();
|
||||
});
|
||||
},
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
description: strings.change2faMethodDesc()
|
||||
},
|
||||
{
|
||||
id: "2fa-fallback",
|
||||
name: strings.addFallback2faMethod(),
|
||||
icon: "shield-plus",
|
||||
iconFamily: "notesnook",
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) => {
|
||||
return (
|
||||
!!(user as User)?.mfa?.secondaryMethod ||
|
||||
!(user as User)?.mfa?.isEnabled
|
||||
);
|
||||
},
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFASheet.present(true);
|
||||
});
|
||||
},
|
||||
description: strings.addFallback2faMethodDesc()
|
||||
},
|
||||
{
|
||||
id: "change-2fa-method",
|
||||
name: strings.change2faFallbackMethod(),
|
||||
icon: "shield-plus",
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) => {
|
||||
return (
|
||||
!(user as User)?.mfa?.secondaryMethod ||
|
||||
!(user as User)?.mfa?.isEnabled
|
||||
);
|
||||
},
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFASheet.present(true);
|
||||
});
|
||||
},
|
||||
description: strings.change2faFallbackMethod()
|
||||
},
|
||||
{
|
||||
id: "view-2fa-codes",
|
||||
name: strings.viewRecoveryCodes(),
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFARecoveryCodes.present("sms");
|
||||
});
|
||||
},
|
||||
icon: "numpad",
|
||||
iconFamily: "notesnook",
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) => {
|
||||
return !(user as User)?.mfa?.isEnabled;
|
||||
},
|
||||
description: strings.viewRecoveryCodesDesc()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -419,7 +432,6 @@ export const accountGroup: SettingSection = {
|
||||
id: "recovery-key",
|
||||
name: strings.saveDataRecoveryKey(),
|
||||
iconFamily: "notesnook",
|
||||
isNavigation: true,
|
||||
modifer: async () => {
|
||||
// if (await verifyUser()) {
|
||||
|
||||
@@ -686,7 +698,6 @@ export const accountGroup: SettingSection = {
|
||||
name: strings.forcePullChanges(),
|
||||
description: strings.forcePullChangesDesc(),
|
||||
icon: "git-pull-request",
|
||||
isNavigation: true,
|
||||
iconFamily: "notesnook",
|
||||
modifer: () => {
|
||||
presentDialog({
|
||||
@@ -711,7 +722,6 @@ export const accountGroup: SettingSection = {
|
||||
description: strings.forcePushChangesDesc(),
|
||||
icon: "arrow-fat-up",
|
||||
iconFamily: "notesnook",
|
||||
isNavigation: true,
|
||||
modifer: () => {
|
||||
presentDialog({
|
||||
title: strings.forcePushChanges(),
|
||||
|
||||
@@ -46,11 +46,17 @@ const Home = ({
|
||||
focusOnInit: true
|
||||
});
|
||||
|
||||
const renderItem = ({ item }: { item: SettingSection; index: number }) =>
|
||||
const renderItem = ({
|
||||
item,
|
||||
index
|
||||
}: {
|
||||
item: SettingSection;
|
||||
index: number;
|
||||
}) =>
|
||||
item.id === "account" ? (
|
||||
<SettingsUserSection item={item} />
|
||||
) : (
|
||||
<SectionGroup item={item} />
|
||||
<SectionGroup item={item} isLast={!settingsGroups[index + 1]} />
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,7 +25,13 @@ import Heading from "../../components/ui/typography/heading";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { SectionItem } from "./section-item";
|
||||
import { SettingSection } from "./types";
|
||||
export const SectionGroup = ({ item }: { item: SettingSection }) => {
|
||||
export const SectionGroup = ({
|
||||
item,
|
||||
isLast
|
||||
}: {
|
||||
item: SettingSection;
|
||||
isLast?: boolean;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const current = item.useHook && item.useHook();
|
||||
const isHidden = item.hidden && item.hidden(current);
|
||||
@@ -49,28 +55,34 @@ export const SectionGroup = ({ item }: { item: SettingSection }) => {
|
||||
</Heading>
|
||||
) : null}
|
||||
|
||||
{item.sections?.map((item) =>
|
||||
item.type === "group" ? (
|
||||
<SectionGroup key={item.id} item={item} />
|
||||
{item.sections?.map((sectionItem, index) =>
|
||||
sectionItem.type === "group" ? (
|
||||
<SectionGroup
|
||||
key={sectionItem.id}
|
||||
item={sectionItem}
|
||||
isLast={!item.sections?.[index + 1]}
|
||||
/>
|
||||
) : (
|
||||
<SectionItem key={item.id as string} item={item} />
|
||||
<SectionItem key={sectionItem.id as string} item={sectionItem} />
|
||||
)
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: Spacing.LEVEL_3,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
{isLast ? null : (
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
width: "100%",
|
||||
backgroundColor: colors.primary.border
|
||||
paddingHorizontal: Spacing.LEVEL_3,
|
||||
width: "100%"
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
width: "100%",
|
||||
backgroundColor: colors.primary.border
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -421,7 +421,7 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{item.type === "screen" || item.isNavigation ? (
|
||||
{item.type === "screen" ? (
|
||||
<AppIcon
|
||||
name="chevron-right"
|
||||
iconFamily="notesnook"
|
||||
|
||||
@@ -37,7 +37,6 @@ export type SettingSection = {
|
||||
description?: string | ((current: unknown) => string);
|
||||
icon?: string;
|
||||
iconFamily?: IconProps["iconFamily"];
|
||||
isNavigation?: boolean;
|
||||
iconSize?: number;
|
||||
property?: keyof Settings;
|
||||
sections?: SettingSection[];
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
{"m":{"f":"notesnook-icons","u":1024,"z":1020,"s":59648,"h":"732c10c1b7a68a314543aa54f89c5cc11a6dcf1d4457e785521a86655a4306c0"},"i":{"archive":[1024,[[59648,"#666666"]]],"arrow-back":[1024,[[59649,"rgba(255,255,255,0.7)"]]],"arrow-counter-clockwise":[1024,[[59650,"var(--fill-0, #181818)"]]],"arrow-fat-up":[1024,[[59651,"var(--fill-0, #181818)"]]],"arrow-u-up-left":[1024,[[59652,"var(--fill-0, #181818)"]]],"arrows-clockwise":[1024,[[59653,"var(--fill-0, #181818)"]]],"bag-simple":[1024,[[59654,"var(--fill-0, #181818)"]]],"bell":[1024,[[59655,"#666666"]]],"book-open":[1024,[[59656,"#666666"]]],"bookmark":[1024,[[59657,"#666666"]]],"box-empty":[1024,[[59658,"#B0B0B1"]]],"bug-droid":[1024,[[59659,"var(--fill-0, #181818)"]]],"calendar":[1024,[[59660,"rgba(102,102,102,0.8)"]]],"chart-line-up":[1024,[[59661,"var(--fill-0, #181818)"]]],"chat":[1024,[[59662,"white"]]],"checkbox":[1024,[[59663,"#008836"],[59664,"white"]]],"checks":[1024,[[59665,"#181818"]]],"chevron-down":[1024,[[59666,"#181818"]]],"chevron-right":[1024,[[59667,"#181818"]]],"chevron-up":[1024,[[59668,"#181818"]]],"clock-counter-clockwise":[1024,[[59669,"var(--fill-0, #181818)"]]],"close":[1024,[[59670,"#666666"]]],"cloud-check":[1024,[[59671,"var(--fill-0, #181818)"]]],"cloud":[1024,[[59672,"white"]]],"dark-mode-outline":[1024,[[59673,"currentColor"]]],"device-mobile-camera":[1024,[[59674,"var(--fill-0, #181818)"]]],"discord-logo":[1024,[[59675,"var(--fill-0, #181818)"]]],"dots-three":[1024,[[59676,"#202020"]]],"download-simple":[1024,[[59677,"var(--fill-0, #181818)"]]],"drive-file-move":[1088,[[59678,"#666666"]]],"edit-pencil":[1024,[[59679,"#181818"]]],"envelope-simple":[1024,[[59680,"white"]]],"export":[1024,[[59681,"var(--fill-0, #181818)"]]],"eye-closed":[1024,[[59682,"white"]]],"eye-open":[1024,[[59683,"currentColor"]]],"eye-slash":[1024,[[59684,"var(--fill-0, #181818)"]]],"file-dashed":[1024,[[59685,"var(--fill-0, #181818)"]]],"file-text":[1024,[[59686,"var(--fill-0, #181818)"]]],"file":[1024,[[59687,"white"]]],"funnel":[1024,[[59688,"#666666"]]],"git-pull-request":[1024,[[59689,"var(--fill-0, #181818)"]]],"github-logo":[1024,[[59690,"var(--fill-0, #181818)"]]],"hard-drives":[1024,[[59691,"var(--fill-0, #181818)"]]],"home":[1024,[[59692,"#181818"]]],"image-outline":[1024,[[59693,"white"]]],"key":[1024,[[59694,"var(--fill-0, #181818)"]]],"link":[560,[[59695,"#666666"]]],"lock-simple":[1024,[[59696,"white"]]],"lock":[1024,[[59697,"#181818"]]],"mastodon-logo":[1024,[[59698,"var(--fill-0, #181818)"]]],"menu":[1024,[[59699,"#181818"]]],"network":[1024,[[59700,"var(--fill-0, #181818)"]]],"note":[1024,[[59701,"#181818"]]],"notification":[1024,[[59702,"var(--fill-0, #181818)"]]],"paperclip":[1024,[[59703,"var(--fill-0, #181818)"]]],"pause":[1024,[[59704,"var(--fill-0, #181818)"]]],"pencil-simple-line":[1024,[[59705,"var(--fill-0, #181818)"]]],"pin":[939,[[59706,"#666666"]]],"plus":[1024,[[59707,"#666666"]]],"recovery-key-cloud-arrow-down":[1024,[[59708,"var(--fill-0, #181818)"]]],"recovery-key-copy":[1024,[[59709,"var(--stroke-0, #008836)"]]],"recovery-key-file":[1024,[[59710,"var(--fill-0, #181818)"]]],"recovery-key-key":[1024,[[59711,"var(--fill-0, #181818)"]]],"recovery-key-qr-code":[1024,[[59712,"var(--fill-0, #181818)"]]],"recovery-key-shield-check":[1024,[[59713,"var(--fill-0, #008836)"]]],"search":[1024,[[59714,"#181818"]]],"shield-check":[1024,[[59715,"#181818"]]],"shield":[1024,[[59716,"var(--fill-0, #181818)"]]],"shopping-mode":[1024,[[59717,"#666666"]]],"sliders-horizontal":[1024,[[59718,"var(--fill-0, #181818)"]]],"sliders":[1024,[[59719,"#181818"]]],"sort-ascending":[1024,[[59720,"#666666"]]],"sort-descending":[1024,[[59721,"currentColor"]]],"square-out":[1024,[[59722,"#181818"]]],"squares-four":[1024,[[59723,"var(--fill-0, #181818)"]]],"star-filled":[1024,[[59724,"#E5C131"]]],"star":[1024,[[59725,"#666666"]]],"sun":[1024,[[59726,"#666666"]]],"telegram-logo":[1024,[[59727,"var(--fill-0, #181818)"]]],"toggle-off":[1725,[[59728,"#DADADA"]]],"toggle-on":[1725,[[59729,"#008836"]]],"trash":[1024,[[59730,"#666666"]]],"user-circle-minus":[1024,[[59731,"var(--fill-0, #FF242E)"]]],"user-sheet-docs":[1024,[[59732,"var(--fill-0, #181818)"]]],"user-sheet-logout":[1024,[[59733,"var(--fill-0, #FB2C36)"]]],"user-sheet-settings":[1024,[[59734,"var(--fill-0, #181818)"]]],"user-sheet-support":[1024,[[59735,"var(--fill-0, #181818)"]]],"user-sheet-sync":[1024,[[59736,"var(--fill-0, #181818)"]]],"user":[1024,[[59737,"var(--fill-0, #181818)"]]],"users-three":[1024,[[59738,"var(--fill-0, #181818)"]]],"view-list":[1024,[[59739,"#181818"]]],"warning-circle":[1024,[[59740,"#BB3431"]]],"wifi-slash":[1024,[[59741,"var(--fill-0, #181818)"]]],"x-logo":[1024,[[59742,"var(--fill-0, #181818)"]]]}}
|
||||
{"m":{"f":"notesnook-icons","u":1024,"z":1020,"s":59648,"h":"ed87d51c541700d863c75e1c0ba9f42fa7e5f8cc5549f0049cb3931307de6789"},"i":{"archive":[1024,[[59648,"#666666"]]],"arrow-back":[1024,[[59649,"rgba(255,255,255,0.7)"]]],"arrow-counter-clockwise":[1024,[[59650,"var(--fill-0, #181818)"]]],"arrow-fat-up":[1024,[[59651,"var(--fill-0, #181818)"]]],"arrow-u-up-left":[1024,[[59652,"var(--fill-0, #181818)"]]],"arrows-clockwise":[1024,[[59653,"var(--fill-0, #181818)"]]],"bag-simple":[1024,[[59654,"var(--fill-0, #181818)"]]],"bell":[1024,[[59655,"#666666"]]],"book-open":[1024,[[59656,"#666666"]]],"bookmark":[1024,[[59657,"#666666"]]],"box-empty":[1024,[[59658,"#B0B0B1"]]],"bug-droid":[1024,[[59659,"var(--fill-0, #181818)"]]],"calendar":[1024,[[59660,"rgba(102,102,102,0.8)"]]],"chart-line-up":[1024,[[59661,"var(--fill-0, #181818)"]]],"chat":[1024,[[59662,"white"]]],"checkbox":[1024,[[59663,"#008836"],[59664,"white"]]],"checks":[1024,[[59665,"#181818"]]],"chevron-down":[1024,[[59666,"#181818"]]],"chevron-right":[1024,[[59667,"#181818"]]],"chevron-up":[1024,[[59668,"#181818"]]],"clock-counter-clockwise":[1024,[[59669,"var(--fill-0, #181818)"]]],"close":[1024,[[59670,"#666666"]]],"cloud-check":[1024,[[59671,"var(--fill-0, #181818)"]]],"cloud":[1024,[[59672,"white"]]],"dark-mode-outline":[1024,[[59673,"currentColor"]]],"device-mobile-camera":[1024,[[59674,"var(--fill-0, #181818)"]]],"discord-logo":[1024,[[59675,"var(--fill-0, #181818)"]]],"dots-three":[1024,[[59676,"#202020"]]],"download-simple":[1024,[[59677,"var(--fill-0, #181818)"]]],"drive-file-move":[1088,[[59678,"#666666"]]],"edit-pencil":[1024,[[59679,"#181818"]]],"envelope-simple":[1024,[[59680,"white"]]],"export":[1024,[[59681,"var(--fill-0, #181818)"]]],"eye-closed":[1024,[[59682,"white"]]],"eye-open":[1024,[[59683,"currentColor"]]],"eye-slash":[1024,[[59684,"var(--fill-0, #181818)"]]],"file-dashed":[1024,[[59685,"var(--fill-0, #181818)"]]],"file-text":[1024,[[59686,"var(--fill-0, #181818)"]]],"file":[1024,[[59687,"white"]]],"funnel":[1024,[[59688,"#666666"]]],"git-pull-request":[1024,[[59689,"var(--fill-0, #181818)"]]],"github-logo":[1024,[[59690,"var(--fill-0, #181818)"]]],"hard-drives":[1024,[[59691,"var(--fill-0, #181818)"]]],"home":[1024,[[59692,"#181818"]]],"image-outline":[1024,[[59693,"white"]]],"key":[1024,[[59694,"var(--fill-0, #181818)"]]],"link":[560,[[59695,"#666666"]]],"lock-simple":[1024,[[59696,"white"]]],"lock":[1024,[[59697,"#181818"]]],"mastodon-logo":[1024,[[59698,"var(--fill-0, #181818)"]]],"menu":[1024,[[59699,"#181818"]]],"network":[1024,[[59700,"var(--fill-0, #181818)"]]],"note":[1024,[[59701,"#181818"]]],"notification":[1024,[[59702,"var(--fill-0, #181818)"]]],"numpad":[1024,[[59703,"#181818"]]],"paperclip":[1024,[[59704,"var(--fill-0, #181818)"]]],"pause":[1024,[[59705,"var(--fill-0, #181818)"]]],"pencil-simple-line":[1024,[[59706,"var(--fill-0, #181818)"]]],"pin":[939,[[59707,"#666666"]]],"plus":[1024,[[59708,"#666666"]]],"recovery-key-cloud-arrow-down":[1024,[[59709,"var(--fill-0, #181818)"]]],"recovery-key-copy":[1024,[[59710,"var(--stroke-0, #008836)"]]],"recovery-key-file":[1024,[[59711,"var(--fill-0, #181818)"]]],"recovery-key-key":[1024,[[59712,"var(--fill-0, #181818)"]]],"recovery-key-qr-code":[1024,[[59713,"var(--fill-0, #181818)"]]],"recovery-key-shield-check":[1024,[[59714,"var(--fill-0, #008836)"]]],"search":[1024,[[59715,"#181818"]]],"shield-check":[1024,[[59716,"#181818"]]],"shield-plus":[1024,[[59717,"#181818"]]],"shield":[1024,[[59718,"var(--fill-0, #181818)"]]],"shopping-mode":[1024,[[59719,"#666666"]]],"sliders-horizontal":[1024,[[59720,"var(--fill-0, #181818)"]]],"sliders":[1024,[[59721,"#181818"]]],"sort-ascending":[1024,[[59722,"#666666"]]],"sort-descending":[1024,[[59723,"currentColor"]]],"square-out":[1024,[[59724,"#181818"]]],"squares-four":[1024,[[59725,"var(--fill-0, #181818)"]]],"star-filled":[1024,[[59726,"#E5C131"]]],"star":[1024,[[59727,"#666666"]]],"sun":[1024,[[59728,"#666666"]]],"telegram-logo":[1024,[[59729,"var(--fill-0, #181818)"]]],"toggle-off":[1725,[[59730,"#DADADA"]]],"toggle-on":[1725,[[59731,"#008836"]]],"trash":[1024,[[59732,"#666666"]]],"user-circle-minus":[1024,[[59733,"var(--fill-0, #FF242E)"]]],"user-sheet-docs":[1024,[[59734,"var(--fill-0, #181818)"]]],"user-sheet-logout":[1024,[[59735,"var(--fill-0, #FB2C36)"]]],"user-sheet-settings":[1024,[[59736,"var(--fill-0, #181818)"]]],"user-sheet-support":[1024,[[59737,"var(--fill-0, #181818)"]]],"user-sheet-sync":[1024,[[59738,"var(--fill-0, #181818)"]]],"user":[1024,[[59739,"var(--fill-0, #181818)"]]],"users-three":[1024,[[59740,"var(--fill-0, #181818)"]]],"view-list":[1024,[[59741,"#181818"]]],"warning-circle":[1024,[[59742,"#BB3431"]]],"wifi-slash":[1024,[[59743,"var(--fill-0, #181818)"]]],"x-logo":[1024,[[59744,"var(--fill-0, #181818)"]]]}}
|
||||
3
packages/icons/svgs/numpad.svg
Normal file
3
packages/icons/svgs/numpad.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 3C5 3.19778 4.94135 3.39112 4.83147 3.55557C4.72159 3.72002 4.56541 3.84819 4.38268 3.92388C4.19996 3.99957 3.99889 4.01937 3.80491 3.98079C3.61093 3.9422 3.43275 3.84696 3.29289 3.70711C3.15304 3.56725 3.0578 3.38907 3.01922 3.19509C2.98063 3.00111 3.00043 2.80004 3.07612 2.61732C3.15181 2.43459 3.27998 2.27841 3.44443 2.16853C3.60888 2.05865 3.80222 2 4 2C4.26522 2 4.51957 2.10536 4.70711 2.29289C4.89464 2.48043 5 2.73478 5 3ZM8 2C7.80222 2 7.60888 2.05865 7.44443 2.16853C7.27998 2.27841 7.15181 2.43459 7.07612 2.61732C7.00043 2.80004 6.98063 3.00111 7.01922 3.19509C7.0578 3.38907 7.15304 3.56725 7.29289 3.70711C7.43275 3.84696 7.61093 3.9422 7.80491 3.98079C7.99889 4.01937 8.19996 3.99957 8.38268 3.92388C8.56541 3.84819 8.72159 3.72002 8.83147 3.55557C8.94135 3.39112 9 3.19778 9 3C9 2.73478 8.89464 2.48043 8.70711 2.29289C8.51957 2.10536 8.26522 2 8 2ZM12 4C12.1978 4 12.3911 3.94135 12.5556 3.83147C12.72 3.72159 12.8482 3.56541 12.9239 3.38268C12.9996 3.19996 13.0194 2.99889 12.9808 2.80491C12.9422 2.61093 12.847 2.43275 12.7071 2.29289C12.5673 2.15304 12.3891 2.0578 12.1951 2.01922C12.0011 1.98063 11.8 2.00043 11.6173 2.07612C11.4346 2.15181 11.2784 2.27998 11.1685 2.44443C11.0587 2.60888 11 2.80222 11 3C11 3.26522 11.1054 3.51957 11.2929 3.70711C11.4804 3.89464 11.7348 4 12 4ZM4 5.5C3.80222 5.5 3.60888 5.55865 3.44443 5.66853C3.27998 5.77841 3.15181 5.93459 3.07612 6.11732C3.00043 6.30004 2.98063 6.50111 3.01922 6.69509C3.0578 6.88907 3.15304 7.06725 3.29289 7.20711C3.43275 7.34696 3.61093 7.4422 3.80491 7.48079C3.99889 7.51937 4.19996 7.49957 4.38268 7.42388C4.56541 7.34819 4.72159 7.22002 4.83147 7.05557C4.94135 6.89112 5 6.69778 5 6.5C5 6.23478 4.89464 5.98043 4.70711 5.79289C4.51957 5.60536 4.26522 5.5 4 5.5ZM8 5.5C7.80222 5.5 7.60888 5.55865 7.44443 5.66853C7.27998 5.77841 7.15181 5.93459 7.07612 6.11732C7.00043 6.30004 6.98063 6.50111 7.01922 6.69509C7.0578 6.88907 7.15304 7.06725 7.29289 7.20711C7.43275 7.34696 7.61093 7.4422 7.80491 7.48079C7.99889 7.51937 8.19996 7.49957 8.38268 7.42388C8.56541 7.34819 8.72159 7.22002 8.83147 7.05557C8.94135 6.89112 9 6.69778 9 6.5C9 6.23478 8.89464 5.98043 8.70711 5.79289C8.51957 5.60536 8.26522 5.5 8 5.5ZM12 5.5C11.8022 5.5 11.6089 5.55865 11.4444 5.66853C11.28 5.77841 11.1518 5.93459 11.0761 6.11732C11.0004 6.30004 10.9806 6.50111 11.0192 6.69509C11.0578 6.88907 11.153 7.06725 11.2929 7.20711C11.4327 7.34696 11.6109 7.4422 11.8049 7.48079C11.9989 7.51937 12.2 7.49957 12.3827 7.42388C12.5654 7.34819 12.7216 7.22002 12.8315 7.05557C12.9414 6.89112 13 6.69778 13 6.5C13 6.23478 12.8946 5.98043 12.7071 5.79289C12.5196 5.60536 12.2652 5.5 12 5.5ZM4 9C3.80222 9 3.60888 9.05865 3.44443 9.16853C3.27998 9.27841 3.15181 9.43459 3.07612 9.61732C3.00043 9.80004 2.98063 10.0011 3.01922 10.1951C3.0578 10.3891 3.15304 10.5673 3.29289 10.7071C3.43275 10.847 3.61093 10.9422 3.80491 10.9808C3.99889 11.0194 4.19996 10.9996 4.38268 10.9239C4.56541 10.8482 4.72159 10.72 4.83147 10.5556C4.94135 10.3911 5 10.1978 5 10C5 9.73478 4.89464 9.48043 4.70711 9.29289C4.51957 9.10536 4.26522 9 4 9ZM8 9C7.80222 9 7.60888 9.05865 7.44443 9.16853C7.27998 9.27841 7.15181 9.43459 7.07612 9.61732C7.00043 9.80004 6.98063 10.0011 7.01922 10.1951C7.0578 10.3891 7.15304 10.5673 7.29289 10.7071C7.43275 10.847 7.61093 10.9422 7.80491 10.9808C7.99889 11.0194 8.19996 10.9996 8.38268 10.9239C8.56541 10.8482 8.72159 10.72 8.83147 10.5556C8.94135 10.3911 9 10.1978 9 10C9 9.73478 8.89464 9.48043 8.70711 9.29289C8.51957 9.10536 8.26522 9 8 9ZM8 12.5C7.80222 12.5 7.60888 12.5587 7.44443 12.6685C7.27998 12.7784 7.15181 12.9346 7.07612 13.1173C7.00043 13.3 6.98063 13.5011 7.01922 13.6951C7.0578 13.8891 7.15304 14.0673 7.29289 14.2071C7.43275 14.347 7.61093 14.4422 7.80491 14.4808C7.99889 14.5194 8.19996 14.4996 8.38268 14.4239C8.56541 14.3482 8.72159 14.22 8.83147 14.0556C8.94135 13.8911 9 13.6978 9 13.5C9 13.2348 8.89464 12.9804 8.70711 12.7929C8.51957 12.6054 8.26522 12.5 8 12.5ZM12 9C11.8022 9 11.6089 9.05865 11.4444 9.16853C11.28 9.27841 11.1518 9.43459 11.0761 9.61732C11.0004 9.80004 10.9806 10.0011 11.0192 10.1951C11.0578 10.3891 11.153 10.5673 11.2929 10.7071C11.4327 10.847 11.6109 10.9422 11.8049 10.9808C11.9989 11.0194 12.2 10.9996 12.3827 10.9239C12.5654 10.8482 12.7216 10.72 12.8315 10.5556C12.9414 10.3911 13 10.1978 13 10C13 9.73478 12.8946 9.48043 12.7071 9.29289C12.5196 9.10536 12.2652 9 12 9Z" fill="#181818"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
3
packages/icons/svgs/shield-plus.svg
Normal file
3
packages/icons/svgs/shield-plus.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.5 8C5.5 7.86739 5.55268 7.74021 5.64645 7.64645C5.74021 7.55268 5.86739 7.5 6 7.5H7.5V6C7.5 5.86739 7.55268 5.74021 7.64645 5.64645C7.74021 5.55268 7.86739 5.5 8 5.5C8.13261 5.5 8.25979 5.55268 8.35355 5.64645C8.44732 5.74021 8.5 5.86739 8.5 6V7.5H10C10.1326 7.5 10.2598 7.55268 10.3536 7.64645C10.4473 7.74021 10.5 7.86739 10.5 8C10.5 8.13261 10.4473 8.25979 10.3536 8.35355C10.2598 8.44732 10.1326 8.5 10 8.5H8.5V10C8.5 10.1326 8.44732 10.2598 8.35355 10.3536C8.25979 10.4473 8.13261 10.5 8 10.5C7.86739 10.5 7.74021 10.4473 7.64645 10.3536C7.55268 10.2598 7.5 10.1326 7.5 10V8.5H6C5.86739 8.5 5.74021 8.44732 5.64645 8.35355C5.55268 8.25979 5.5 8.13261 5.5 8ZM14 3.5V7C14 10.295 12.405 12.2919 11.0669 13.3869C9.62563 14.5656 8.19188 14.9663 8.12938 14.9825C8.04344 15.0059 7.95281 15.0059 7.86688 14.9825C7.80438 14.9663 6.3725 14.5656 4.92938 13.3869C3.595 12.2919 2 10.295 2 7V3.5C2 3.23478 2.10536 2.98043 2.29289 2.79289C2.48043 2.60536 2.73478 2.5 3 2.5H13C13.2652 2.5 13.5196 2.60536 13.7071 2.79289C13.8946 2.98043 14 3.23478 14 3.5ZM13 3.5H3V7C3 9.33125 3.86375 11.2194 5.56687 12.6131C6.29203 13.2062 7.11553 13.6675 8 13.9762C8.89619 13.6621 9.72999 13.1924 10.4631 12.5887C12.1462 11.1975 13 9.31687 13 7V3.5Z" fill="#181818"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
Reference in New Issue
Block a user