diff --git a/app/assets/javascripts/components/AccountMenu/Authentication.tsx b/app/assets/javascripts/components/AccountMenu/Authentication.tsx index 1e16c3b81..7e99deae1 100644 --- a/app/assets/javascripts/components/AccountMenu/Authentication.tsx +++ b/app/assets/javascripts/components/AccountMenu/Authentication.tsx @@ -9,36 +9,27 @@ import { JSXInternal } from 'preact/src/jsx'; import TargetedEvent = JSXInternal.TargetedEvent; import TargetedKeyboardEvent = JSXInternal.TargetedKeyboardEvent; import { WebApplication } from '@/ui_models/application'; -import { StateUpdater, useEffect, useRef, useState } from 'preact/hooks'; +import { useEffect, useRef, useState } from 'preact/hooks'; import TargetedMouseEvent = JSXInternal.TargetedMouseEvent; -import { FunctionalComponent } from 'preact'; import { User } from '@standardnotes/snjs/dist/@types/services/api/responses'; +import { observer } from 'mobx-react-lite'; +import { AppState } from '@/ui_models/app_state'; type Props = { application: WebApplication; - server: string | undefined; - setServer: StateUpdater; + appState: AppState; closeAccountMenu: () => void; notesAndTagsCount: number; - showLogin: boolean; - setShowLogin: StateUpdater; - showRegister: boolean; - setShowRegister: StateUpdater; user: User | undefined; } -const Authentication: FunctionalComponent = ({ - application, - server, - setServer, - closeAccountMenu, - notesAndTagsCount, - showLogin, - setShowLogin, - showRegister, - setShowRegister, - user - }: Props) => { +const Authentication = observer(({ + application, + appState, + closeAccountMenu, + notesAndTagsCount, + user + }: Props) => { const [showAdvanced, setShowAdvanced] = useState(false); const [isAuthenticating, setIsAuthenticating] = useState(false); @@ -52,6 +43,15 @@ const Authentication: FunctionalComponent = ({ const [isStrictSignIn, setIsStrictSignIn] = useState(false); const [shouldMergeLocal, setShouldMergeLocal] = useState(true); + const { + server, + showLogin, + showRegister, + setShowLogin, + setShowRegister, + setServer + } = appState.accountMenu; + useEffect(() => { if (isEmailFocused) { emailInputRef.current.focus(); @@ -108,6 +108,7 @@ const Authentication: FunctionalComponent = ({ if (!error) { setIsAuthenticating(false); setPassword(''); + setShowLogin(false); closeAccountMenu(); return; @@ -119,7 +120,6 @@ const Authentication: FunctionalComponent = ({ if (error.message) { await application.alertService.alert(error.message); - // await handleAlert(error.message); } setIsAuthenticating(false); @@ -128,7 +128,6 @@ const Authentication: FunctionalComponent = ({ const register = async () => { if (passwordConfirmation !== password) { application.alertService.alert(STRING_NON_MATCHING_PASSWORDS); - // handleAlert(STRING_NON_MATCHING_PASSWORDS); return; } setStatus(STRING_GENERATING_REGISTER_KEYS); @@ -147,9 +146,9 @@ const Authentication: FunctionalComponent = ({ setIsAuthenticating(false); application.alertService.alert(error.message); - // handleAlert(error.message); } else { setIsAuthenticating(false); + setShowRegister(false); closeAccountMenu(); } }; @@ -280,6 +279,7 @@ const Authentication: FunctionalComponent = ({ />}
)} ); -}; +}); export default Authentication; diff --git a/app/assets/javascripts/components/AccountMenu/DataBackup.tsx b/app/assets/javascripts/components/AccountMenu/DataBackup.tsx index 21e0e7ffb..81dcf0af8 100644 --- a/app/assets/javascripts/components/AccountMenu/DataBackup.tsx +++ b/app/assets/javascripts/components/AccountMenu/DataBackup.tsx @@ -11,25 +11,23 @@ import { useState } from 'preact/hooks'; import { WebApplication } from '@/ui_models/application'; import { JSXInternal } from 'preact/src/jsx'; import TargetedEvent = JSXInternal.TargetedEvent; -import { StateUpdater } from 'preact/hooks'; -import { FunctionalComponent } from 'preact'; +import { AppState } from '@/ui_models/app_state'; +import { observer } from 'mobx-react-lite'; type Props = { application: WebApplication; - isBackupEncrypted: boolean; - isEncryptionEnabled: boolean; - setIsBackupEncrypted: StateUpdater; + appState: AppState; } -const DataBackup: FunctionalComponent = ({ - application, - isBackupEncrypted, - isEncryptionEnabled, - setIsBackupEncrypted - }) => { +const DataBackup = observer(({ + application, + appState + }: Props) => { const [isImportDataLoading, setIsImportDataLoading] = useState(false); + const { isBackupEncrypted, isEncryptionEnabled, setIsBackupEncrypted } = appState.accountMenu; + const downloadDataArchive = () => { application.getArchiveService().downloadBackup(isBackupEncrypted); }; @@ -152,6 +150,6 @@ const DataBackup: FunctionalComponent = ({ )} ); -}; +}); export default DataBackup; diff --git a/app/assets/javascripts/components/AccountMenu/Encryption.tsx b/app/assets/javascripts/components/AccountMenu/Encryption.tsx index c27c70f36..3e8316e84 100644 --- a/app/assets/javascripts/components/AccountMenu/Encryption.tsx +++ b/app/assets/javascripts/components/AccountMenu/Encryption.tsx @@ -1,16 +1,17 @@ -import { FunctionalComponent } from 'preact'; +import { AppState } from '@/ui_models/app_state'; +import { observer } from 'mobx-react-lite'; type Props = { - isEncryptionEnabled: boolean; + appState: AppState; notesAndTagsCount: number; - encryptionStatusString: string | undefined; } -const Encryption: FunctionalComponent = ({ - isEncryptionEnabled, - notesAndTagsCount, - encryptionStatusString - }) => { +const Encryption = observer(({ + appState, + notesAndTagsCount + }: Props) => { + const { isEncryptionEnabled, encryptionStatusString } = appState.accountMenu; + const getEncryptionStatusForNotes = () => { const length = notesAndTagsCount; return `${length}/${length} notes and tags encrypted`; @@ -31,6 +32,6 @@ const Encryption: FunctionalComponent = ({

); -}; +}); export default Encryption; diff --git a/app/assets/javascripts/components/AccountMenu/Footer.tsx b/app/assets/javascripts/components/AccountMenu/Footer.tsx index b9f86a4cb..a19f07dc6 100644 --- a/app/assets/javascripts/components/AccountMenu/Footer.tsx +++ b/app/assets/javascripts/components/AccountMenu/Footer.tsx @@ -1,49 +1,43 @@ import { AppState } from '@/ui_models/app_state'; -import { StateUpdater, useState } from 'preact/hooks'; +import { useState } from 'preact/hooks'; import { WebApplication } from '@/ui_models/application'; import { User } from '@standardnotes/snjs/dist/@types/services/api/responses'; import { observer } from 'mobx-react-lite'; type Props = { - appState: AppState; application: WebApplication; - showLogin: boolean; - setShowLogin: StateUpdater; - showRegister: boolean; - setShowRegister: StateUpdater; + appState: AppState; user: User | undefined; } const Footer = observer(({ - appState, application, - showLogin, - setShowLogin, - showRegister, - setShowRegister, + appState, user }: Props) => { + const { + showLogin, + showRegister, + setShowLogin, + setShowRegister, + setSigningOut + } = appState.accountMenu; - const showBetaWarning = appState.showBetaWarning; - + const { showBetaWarning, disableBetaWarning: disableAppStateBetaWarning } = appState; const [appVersion] = useState(() => `v${((window as any).electronAppVersion || application.bridge.appVersion)}`); const disableBetaWarning = () => { - appState.disableBetaWarning(); + disableAppStateBetaWarning(); }; const signOut = () => { - appState.accountMenu.setSigningOut(true); + setSigningOut(true); }; const hidePasswordForm = () => { setShowLogin(false); setShowRegister(false); - // TODO: Vardan: this comes from main `index.tsx` and the below commented parts should reset password and confirmation. - // Check whether it works when I don't call them explicitly. - // setPassword(''); - // setPasswordConfirmation(undefined); }; return ( diff --git a/app/assets/javascripts/components/AccountMenu/PasscodeLock.tsx b/app/assets/javascripts/components/AccountMenu/PasscodeLock.tsx index 9f8d29d77..4050d2358 100644 --- a/app/assets/javascripts/components/AccountMenu/PasscodeLock.tsx +++ b/app/assets/javascripts/components/AccountMenu/PasscodeLock.tsx @@ -12,25 +12,23 @@ import { alertDialog } from '@Services/alertService'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { ApplicationEvent } from '@standardnotes/snjs'; import TargetedMouseEvent = JSXInternal.TargetedMouseEvent; -import { StateUpdater } from 'preact/hooks'; -import { FunctionalComponent } from 'preact'; +import { observer } from 'mobx-react-lite'; +import { AppState } from '@/ui_models/app_state'; type Props = { application: WebApplication; - setEncryptionStatusString: StateUpdater; - setIsEncryptionEnabled: StateUpdater; - setIsBackupEncrypted: StateUpdater; + appState: AppState; }; -const PasscodeLock: FunctionalComponent = ({ - application, - setEncryptionStatusString, - setIsEncryptionEnabled, - setIsBackupEncrypted - }) => { +const PasscodeLock = observer(({ + application, + appState, + }: Props) => { const keyStorageInfo = StringUtils.keyStorageInfo(application); const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions(); + const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } = appState.accountMenu; + const passcodeInputRef = useRef(); const [passcode, setPasscode] = useState(undefined); @@ -263,6 +261,6 @@ const PasscodeLock: FunctionalComponent = ({ )} ); -}; +}); export default PasscodeLock; diff --git a/app/assets/javascripts/components/AccountMenu/Protections.tsx b/app/assets/javascripts/components/AccountMenu/Protections.tsx index f48b02918..2b4049974 100644 --- a/app/assets/javascripts/components/AccountMenu/Protections.tsx +++ b/app/assets/javascripts/components/AccountMenu/Protections.tsx @@ -1,19 +1,65 @@ import { WebApplication } from '@/ui_models/application'; import { FunctionalComponent } from 'preact'; +import { useCallback, useState } from '@node_modules/preact/hooks'; +import { useEffect } from 'preact/hooks'; +import { ApplicationEvent } from '@node_modules/@standardnotes/snjs'; +import { isSameDay } from '@/utils'; type Props = { application: WebApplication; - protectionsDisabledUntil: string | null; }; -const Protections: FunctionalComponent = ({ - application, - protectionsDisabledUntil - }) => { +const Protections: FunctionalComponent = ({ application }) => { const enableProtections = () => { application.clearProtectionSession(); }; + const hasProtections = application.hasProtectionSources(); + + const getProtectionsDisabledUntil = useCallback((): string | null => { + const protectionExpiry = application.getProtectionSessionExpiryDate(); + const now = new Date(); + if (protectionExpiry > now) { + let f: Intl.DateTimeFormat; + if (isSameDay(protectionExpiry, now)) { + f = new Intl.DateTimeFormat(undefined, { + hour: 'numeric', + minute: 'numeric' + }); + } else { + f = new Intl.DateTimeFormat(undefined, { + weekday: 'long', + day: 'numeric', + month: 'short', + hour: 'numeric', + minute: 'numeric' + }); + } + + return f.format(protectionExpiry); + } + return null; + }, [application]); + + const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil()); + + useEffect(() => { + const removeProtectionSessionExpiryDateChangedObserver = application.addEventObserver( + async () => { + setProtectionsDisabledUntil(getProtectionsDisabledUntil()); + }, + ApplicationEvent.ProtectionSessionExpiryDateChanged + ); + + return () => { + removeProtectionSessionExpiryDateChangedObserver(); + }; + }, [application, getProtectionsDisabledUntil]); + + if (!hasProtections) { + return null; + } + return (
Protections
diff --git a/app/assets/javascripts/components/AccountMenu/User.tsx b/app/assets/javascripts/components/AccountMenu/User.tsx index f7f172338..52cbbd9f2 100644 --- a/app/assets/javascripts/components/AccountMenu/User.tsx +++ b/app/assets/javascripts/components/AccountMenu/User.tsx @@ -5,7 +5,6 @@ import { WebApplication } from '@/ui_models/application'; type Props = { email: string; - server: string | undefined; appState: AppState; application: WebApplication; closeAccountMenu: () => void; @@ -13,11 +12,12 @@ type Props = { const User = observer(({ email, - server, appState, application, closeAccountMenu }: Props) => { + const { server } = appState.accountMenu; + const openPasswordWizard = () => { closeAccountMenu(); application.presentPasswordWizard(PasswordWizardType.ChangePassword); diff --git a/app/assets/javascripts/components/AccountMenu/index.tsx b/app/assets/javascripts/components/AccountMenu/index.tsx index 694d8cbab..56c6ef6e0 100644 --- a/app/assets/javascripts/components/AccountMenu/index.tsx +++ b/app/assets/javascripts/components/AccountMenu/index.tsx @@ -3,7 +3,6 @@ import { toDirective } from '@/components/utils'; import { AppState } from '@/ui_models/app_state'; import { WebApplication } from '@/ui_models/application'; import { useEffect, useState } from 'preact/hooks'; -import { isSameDay } from '@/utils'; import { ApplicationEvent } from '@standardnotes/snjs'; import { ConfirmSignoutContainer } from '@/components/ConfirmSignoutModal'; import Authentication from '@/components/AccountMenu/Authentication'; @@ -14,7 +13,6 @@ import Protections from '@/components/AccountMenu/Protections'; import PasscodeLock from '@/components/AccountMenu/PasscodeLock'; import DataBackup from '@/components/AccountMenu/DataBackup'; import ErrorReporting from '@/components/AccountMenu/ErrorReporting'; -import { useCallback } from 'preact/hooks'; type Props = { appState: AppState; @@ -22,45 +20,17 @@ type Props = { }; const AccountMenu = observer(({ application, appState }: Props) => { - const getProtectionsDisabledUntil = useCallback((): string | null => { - const protectionExpiry = application.getProtectionSessionExpiryDate(); - const now = new Date(); - if (protectionExpiry > now) { - let f: Intl.DateTimeFormat; - if (isSameDay(protectionExpiry, now)) { - f = new Intl.DateTimeFormat(undefined, { - hour: 'numeric', - minute: 'numeric' - }); - } else { - f = new Intl.DateTimeFormat(undefined, { - weekday: 'long', - day: 'numeric', - month: 'short', - hour: 'numeric', - minute: 'numeric' - }); - } - - return f.format(protectionExpiry); - } - return null; - }, [application]); - - const [showLogin, setShowLogin] = useState(false); - const [showRegister, setShowRegister] = useState(false); - const [encryptionStatusString, setEncryptionStatusString] = useState(undefined); - const [isEncryptionEnabled, setIsEncryptionEnabled] = useState(false); - const [server, setServer] = useState(application.getHost()); - const [isBackupEncrypted, setIsBackupEncrypted] = useState(isEncryptionEnabled); - const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil()); const [user, setUser] = useState(application.getUser()); - const [hasProtections] = useState(application.hasProtectionSources()); - const { notesAndTagsCount } = appState.accountMenu; + const { + notesAndTagsCount, + showLogin, + showRegister, + closeAccountMenu: closeAppStateAccountMenu + } = appState.accountMenu; const closeAccountMenu = () => { - appState.accountMenu.closeAccountMenu(); + closeAppStateAccountMenu(); }; // Add the required event observers @@ -72,18 +42,10 @@ const AccountMenu = observer(({ application, appState }: Props) => { ApplicationEvent.KeyStatusChanged ); - const removeProtectionSessionExpiryDateChangedObserver = application.addEventObserver( - async () => { - setProtectionsDisabledUntil(getProtectionsDisabledUntil()); - }, - ApplicationEvent.ProtectionSessionExpiryDateChanged - ); - return () => { removeKeyStatusChangedObserver(); - removeProtectionSessionExpiryDateChangedObserver(); }; - }, [application, getProtectionsDisabledUntil]); + }, [application]); return (
@@ -95,62 +57,45 @@ const AccountMenu = observer(({ application, appState }: Props) => {
{!showLogin && !showRegister && (
{user && ( )} - {hasProtections && ( - - )} +
)}
- -
diff --git a/app/assets/javascripts/ui_models/app_state/account_menu_state.ts b/app/assets/javascripts/ui_models/app_state/account_menu_state.ts index 0e492a09b..c3e58baa4 100644 --- a/app/assets/javascripts/ui_models/app_state/account_menu_state.ts +++ b/app/assets/javascripts/ui_models/app_state/account_menu_state.ts @@ -6,7 +6,13 @@ import { SNItem } from '@standardnotes/snjs/dist/@types/models/core/item'; export class AccountMenuState { show = false; signingOut = false; + server: string | undefined = undefined; notesAndTags: SNItem[] = []; + isEncryptionEnabled = false; + encryptionStatusString = ''; + isBackupEncrypted = false; + showLogin = false; + showRegister = false; constructor( private application: WebApplication, @@ -15,21 +21,34 @@ export class AccountMenuState { makeObservable(this, { show: observable, signingOut: observable, + server: observable, notesAndTags: observable, + isEncryptionEnabled: observable, + encryptionStatusString: observable, + isBackupEncrypted: observable, + showLogin: observable, + showRegister: observable, setShow: action, toggleShow: action, setSigningOut: action, + setIsEncryptionEnabled: action, + setEncryptionStatusString: action, + setIsBackupEncrypted: action, notesAndTagsCount: computed }); appEventListeners.push( this.application.streamItems( - [ContentType.Note, ContentType.Tag], + [ + ContentType.Note, ContentType.Tag, + ContentType.Component // TODO: is this correct for streaming `server`? + ], () => { runInAction(() => { this.notesAndTags = this.application.getItems([ContentType.Note, ContentType.Tag]); + this.setServer(this.application.getHost()); }); } ) @@ -48,6 +67,30 @@ export class AccountMenuState { this.signingOut = signingOut; }; + setServer = (server: string | undefined): void => { + this.server = server; + }; + + setIsEncryptionEnabled = (isEncryptionEnabled: boolean): void => { + this.isEncryptionEnabled = isEncryptionEnabled; + }; + + setEncryptionStatusString = (encryptionStatusString: string): void => { + this.encryptionStatusString = encryptionStatusString; + }; + + setIsBackupEncrypted = (isBackupEncrypted: boolean): void => { + this.isBackupEncrypted = isBackupEncrypted; + }; + + setShowLogin = (showLogin: boolean): void => { + this.showLogin = showLogin; + }; + + setShowRegister = (showRegister: boolean): void => { + this.showRegister = showRegister; + }; + toggleShow = (): void => { this.show = !this.show; };