From 7a05a448c35148c7a11d344afffa50e6b662e83b Mon Sep 17 00:00:00 2001 From: VardanHakobyan Date: Thu, 3 Jun 2021 20:04:00 +0400 Subject: [PATCH] refactor: migrate `account-menu` to react - implement functionality - implement download backup - implement import backup - add `useCallback` to function that is used in `useEffect`, so that its reference isn't changed on every render --- .../components/AccountMenuReact.tsx | 160 +++++++++++++----- 1 file changed, 113 insertions(+), 47 deletions(-) diff --git a/app/assets/javascripts/components/AccountMenuReact.tsx b/app/assets/javascripts/components/AccountMenuReact.tsx index ac8199a2f..881880317 100644 --- a/app/assets/javascripts/components/AccountMenuReact.tsx +++ b/app/assets/javascripts/components/AccountMenuReact.tsx @@ -14,12 +14,16 @@ import { STRING_ENC_NOT_ENABLED, STRING_GENERATING_LOGIN_KEYS, STRING_GENERATING_REGISTER_KEYS, + STRING_IMPORT_SUCCESS, + STRING_INVALID_IMPORT_FILE, STRING_LOCAL_ENC_ENABLED, STRING_NON_MATCHING_PASSCODES, STRING_NON_MATCHING_PASSWORDS, + STRING_UNSUPPORTED_BACKUP_FILE_VERSION, + StringImportError, StringUtils } from '@/strings'; -import { ContentType } from '@node_modules/@standardnotes/snjs'; +import { BackupFile, ContentType } from '@node_modules/@standardnotes/snjs'; import { PasswordWizardType } from '@/types'; import { JSXInternal } from '@node_modules/preact/src/jsx'; import TargetedEvent = JSXInternal.TargetedEvent; @@ -88,7 +92,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props const [url, setUrl] = useState(undefined); const [showPasscodeForm, setShowPasscodeForm] = useState(false); const [selectedAutoLockInterval, setSelectedAutoLockInterval] = useState(null); - const [isLoading, setIsLoading] = useState(false); + const [isImportDataLoading, setIsImportDataLoading] = useState(false); const [isErrorReportingEnabled, setIsErrorReportingEnabled] = useState(false); const [appVersion, setAppVersion] = useState(''); // TODO: Vardan: figure out how to get `appVersion` similar to original code const [hasPasscode, setHasPasscode] = useState(application.hasPasscode()); @@ -138,27 +142,27 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props passwordInputRef.current.blur(); }; -/* - // TODO: move to top - type FormData = { - email: string; - password: string; - passwordConfirmation: string; - showLogin: boolean; - showRegister: boolean; - showPasscodeForm: boolean; - isStrictSignin?: boolean; - isEphemeral: boolean; - shouldMergeLocal?: boolean; - url: string; - isAuthenticating: boolean; - status: string; - passcode: string; - passcodeConfirmation: string; - };*/ + /* + // TODO: move to top + type FormData = { + email: string; + password: string; + passwordConfirmation: string; + showLogin: boolean; + showRegister: boolean; + showPasscodeForm: boolean; + isStrictSignin?: boolean; + isEphemeral: boolean; + shouldMergeLocal?: boolean; + url: string; + isAuthenticating: boolean; + status: string; + passcode: string; + passcodeConfirmation: string; + };*/ - const login = async() => { + const login = async () => { setStatus(STRING_GENERATING_LOGIN_KEYS); setIsAuthenticating(true); @@ -287,7 +291,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props setProtectionsDisabledUntil(getProtectionsDisabledUntil()); }; - const refreshEncryptionStatus = () => { + const refreshEncryptionStatus = useCallback(() => { const hasUser = application.hasAccount(); const hasPasscode = application.hasPasscode(); @@ -304,7 +308,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props setEncryptionStatusString(newEncryptionStatusString); setIsEncryptionEnabled(encryptionEnabled); setIsBackupEncrypted(encryptionEnabled); - }; + }, [application]); const handleAddPassCode = () => { setShowPasscodeForm(true); @@ -408,11 +412,74 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props }; const downloadDataArchive = () => { - console.log('downloadDataArchive'); + application.getArchiveService().downloadBackup(isBackupEncrypted); }; - const importFileSelected = () => { - console.log('importFileSelected'); + + const readFile = async (file: File): Promise => { + return new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = JSON.parse(e.target!.result as string); + resolve(data); + } catch (e) { + application.alertService.alert(STRING_INVALID_IMPORT_FILE); + } + }; + reader.readAsText(file); + }); + }; + + const performImport = async (data: BackupFile) => { + setIsImportDataLoading(true); + + const result = await application.importData(data); + + setIsImportDataLoading(false); + + if (!result) { + return; + } else if ('error' in result) { + void alertDialog({ + text: result.error, + }); + } else if (result.errorCount) { + void alertDialog({ + text: StringImportError(result.errorCount), + }); + } else { + void alertDialog({ + text: STRING_IMPORT_SUCCESS, + }); + } + }; + + const importFileSelected = async (event: TargetedEvent) => { + const { files } = (event.target as HTMLInputElement); + + if (!files) { + return; + } + const file = files[0]; + const data = await readFile(file); + if (!data) { + return; + } + if (data.version || data.auth_params || data.keyParams) { + const version = + data.version || data.keyParams?.version || data.auth_params?.version; + if ( + application.protocolService.supportedVersions().includes(version) + ) { + await performImport(data); + } else { + setIsImportDataLoading(false); + void alertDialog({ text: STRING_UNSUPPORTED_BACKUP_FILE_VERSION }); + } + } else { + await performImport(data); + } }; const toggleErrorReportingEnabled = () => { @@ -826,31 +893,30 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props )} - {!isLoading && ( + {!isImportDataLoading && (
Data Backups
Download a backup of all your data.
{isEncryptionEnabled && (
-
- - +
+ + +
)}
@@ -872,7 +938,7 @@ const AccountMenu = observer(({ application, appState, closeAccountMenu }: Props

)}
- {isLoading && ( + {isImportDataLoading && (
)}