From f1122f292ecf8f44a1b8f4a711bc5d56a0069cdf Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 8 Oct 2021 21:48:31 +0530 Subject: [PATCH] feat: New account menu and text input with icon & toggle (#665) * feat: Add new icons * Revert "feat: Add new icons" This reverts commit 0acb403fe846dbb2e48fd22de35c3568c3cb4453. * feat: Add new icons for account menu * feat: Add new Icons * feat: Add "currentPane" state to prefs view * feat: Update account menu to new design * feat: Add input component with icon & toggle * fix: sync icon & function * fix: Fix eye icon * feat: Create re-usable checkbox feat: Add "merge local" option * feat: Allow using className on IconButton * feat: Add disabled state on input feat: Make toggle circle * refactor: Move checkbox to components * feat: Handle invalid email/password error * feat: Implement new design for Create Account * feat: Implement new account menu design * feat: Add disabled option to IconButton * feat: Set account menu pane from other component * feat: Add 2fa account menu pane feat: Add lock icon * feat: Remove unnecessary 2FA menu pane feat: Reset current menu pane on clickOutside * feat: Change "Log in" to "Sign in" * feat: Remove sync from footer * feat: Change "Login" to "Sign in" feat: Add spinner to "Syncing..." refactor: Use then-catch-finally for sync * feat: Use common enableCustomServer state * feat: Animate account menu closing * fix: Reset menu pane only after it's closed * feat: Add keyDown handler to InputWithIcon * feat: Handle Enter press in inputs * Update app/assets/javascripts/components/InputWithIcon.tsx Co-authored-by: Antonella Sgarlatta * Update app/assets/javascripts/components/InputWithIcon.tsx Co-authored-by: Antonella Sgarlatta * refactor: Use server state from AccountMenuState * Update app/assets/javascripts/components/AccountMenu/CreateAccount.tsx Co-authored-by: Antonella Sgarlatta * Update app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx Co-authored-by: Antonella Sgarlatta * feat: Use common AdvancedOptions * feat: Add "eye-off" icon and toggle state * feat: Allow undefined values * refactor: Remove enableCustomServer state * feat: Persist server option state * feat: Add bottom-100 and cursor-auto util classes refactor: Use bottom-100 and cursor-auto classes * refactor: Invert ternary operator * refactor: Remove unused imports * refactor: Use toggled as prop instead of state * refactor: Change "Log in/out" to "Sign in/out" * refactor: Change "Login" to "Sign in" * refactor: Remove hardcoded width/height * refactor: Use success class * feat: Remove hardcoded width & height from svg * fix: Fix chevron-down icon Co-authored-by: Antonella Sgarlatta Co-authored-by: Antonella Sgarlatta --- app/assets/icons/ic-accessibility.svg | 2 +- app/assets/icons/ic-arrow-left.svg | 4 + app/assets/icons/ic-check-circle.svg | 4 + app/assets/icons/ic-chevron-down.svg | 4 + app/assets/icons/ic-cloud-off.svg | 4 + app/assets/icons/ic-copy.svg | 2 +- app/assets/icons/ic-download.svg | 2 +- app/assets/icons/ic-email.svg | 4 + app/assets/icons/ic-eye-off.svg | 4 + app/assets/icons/ic-eye.svg | 4 + app/assets/icons/ic-help.svg | 2 +- app/assets/icons/ic-info.svg | 2 +- app/assets/icons/ic-keyboard.svg | 2 +- app/assets/icons/ic-listed.svg | 2 +- app/assets/icons/ic-lock.svg | 4 + app/assets/icons/ic-security.svg | 2 +- app/assets/icons/ic-server.svg | 4 + app/assets/icons/ic-settings.svg | 2 +- app/assets/icons/ic-signin.svg | 4 + app/assets/icons/ic-signout.svg | 4 + app/assets/icons/ic-star.svg | 2 +- app/assets/icons/ic-sync.svg | 4 + app/assets/icons/ic-themes.svg | 2 +- app/assets/icons/ic-user.svg | 2 +- .../AccountMenu/AdvancedOptions.tsx | 73 ++++++ .../components/AccountMenu/Authentication.tsx | 147 +++++++----- .../AccountMenu/ConfirmPassword.tsx | 172 +++++++++++++ .../components/AccountMenu/CreateAccount.tsx | 137 +++++++++++ .../components/AccountMenu/Footer.tsx | 41 ++-- .../AccountMenu/GeneralAccountMenu.tsx | 166 +++++++++++++ .../components/AccountMenu/SignIn.tsx | 227 ++++++++++++++++++ .../components/AccountMenu/index.tsx | 147 ++++++++---- .../javascripts/components/Checkbox.tsx | 32 +++ app/assets/javascripts/components/Icon.tsx | 24 ++ .../javascripts/components/IconButton.tsx | 7 +- .../javascripts/components/InputWithIcon.tsx | 89 +++++++ ...onsLogout.tsx => OtherSessionsSignOut.tsx} | 22 +- .../directives/views/passwordWizard.ts | 114 ++++----- .../preferences/PreferencesView.tsx | 22 +- .../preferences/panes/AccountPreferences.tsx | 6 +- .../panes/account/Authentication.tsx | 98 +++++--- .../{LogOutView.tsx => SignOutView.tsx} | 44 ++-- .../preferences/panes/account/Sync.tsx | 93 +++---- .../panes/account/changePassword/index.tsx | 34 ++- .../preferences/panes/account/index.ts | 2 +- .../ui_models/app_state/account_menu_state.ts | 86 +++++-- .../ui_models/app_state/preferences_state.ts | 9 + .../javascripts/views/footer/footer-view.pug | 11 +- .../javascripts/views/footer/footer_view.ts | 36 +-- app/assets/stylesheets/_menus.scss | 12 + app/assets/stylesheets/_sn.scss | 50 ++++ 51 files changed, 1566 insertions(+), 407 deletions(-) create mode 100644 app/assets/icons/ic-arrow-left.svg create mode 100644 app/assets/icons/ic-check-circle.svg create mode 100644 app/assets/icons/ic-chevron-down.svg create mode 100644 app/assets/icons/ic-cloud-off.svg create mode 100644 app/assets/icons/ic-email.svg create mode 100644 app/assets/icons/ic-eye-off.svg create mode 100644 app/assets/icons/ic-eye.svg create mode 100644 app/assets/icons/ic-lock.svg create mode 100644 app/assets/icons/ic-server.svg create mode 100644 app/assets/icons/ic-signin.svg create mode 100644 app/assets/icons/ic-signout.svg create mode 100644 app/assets/icons/ic-sync.svg create mode 100644 app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx create mode 100644 app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx create mode 100644 app/assets/javascripts/components/AccountMenu/CreateAccount.tsx create mode 100644 app/assets/javascripts/components/AccountMenu/GeneralAccountMenu.tsx create mode 100644 app/assets/javascripts/components/AccountMenu/SignIn.tsx create mode 100644 app/assets/javascripts/components/Checkbox.tsx create mode 100644 app/assets/javascripts/components/InputWithIcon.tsx rename app/assets/javascripts/components/{OtherSessionsLogout.tsx => OtherSessionsSignOut.tsx} (77%) rename app/assets/javascripts/preferences/panes/account/{LogOutView.tsx => SignOutView.tsx} (69%) diff --git a/app/assets/icons/ic-accessibility.svg b/app/assets/icons/ic-accessibility.svg index de1e6249e..ffb780043 100644 --- a/app/assets/icons/ic-accessibility.svg +++ b/app/assets/icons/ic-accessibility.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-arrow-left.svg b/app/assets/icons/ic-arrow-left.svg new file mode 100644 index 000000000..f80337cff --- /dev/null +++ b/app/assets/icons/ic-arrow-left.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-check-circle.svg b/app/assets/icons/ic-check-circle.svg new file mode 100644 index 000000000..ddb5737f7 --- /dev/null +++ b/app/assets/icons/ic-check-circle.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-chevron-down.svg b/app/assets/icons/ic-chevron-down.svg new file mode 100644 index 000000000..1c89552e6 --- /dev/null +++ b/app/assets/icons/ic-chevron-down.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-cloud-off.svg b/app/assets/icons/ic-cloud-off.svg new file mode 100644 index 000000000..49015714d --- /dev/null +++ b/app/assets/icons/ic-cloud-off.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-copy.svg b/app/assets/icons/ic-copy.svg index 9ad40e8f1..694626a33 100644 --- a/app/assets/icons/ic-copy.svg +++ b/app/assets/icons/ic-copy.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-download.svg b/app/assets/icons/ic-download.svg index de2c70fc2..923b753bd 100644 --- a/app/assets/icons/ic-download.svg +++ b/app/assets/icons/ic-download.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-email.svg b/app/assets/icons/ic-email.svg new file mode 100644 index 000000000..378c18e0c --- /dev/null +++ b/app/assets/icons/ic-email.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-eye-off.svg b/app/assets/icons/ic-eye-off.svg new file mode 100644 index 000000000..76cf09013 --- /dev/null +++ b/app/assets/icons/ic-eye-off.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-eye.svg b/app/assets/icons/ic-eye.svg new file mode 100644 index 000000000..9248599f6 --- /dev/null +++ b/app/assets/icons/ic-eye.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-help.svg b/app/assets/icons/ic-help.svg index c312b7255..eaed4c3f7 100644 --- a/app/assets/icons/ic-help.svg +++ b/app/assets/icons/ic-help.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-info.svg b/app/assets/icons/ic-info.svg index 47ea73219..14107de40 100644 --- a/app/assets/icons/ic-info.svg +++ b/app/assets/icons/ic-info.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-keyboard.svg b/app/assets/icons/ic-keyboard.svg index 9a18af39c..8068326fd 100644 --- a/app/assets/icons/ic-keyboard.svg +++ b/app/assets/icons/ic-keyboard.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-listed.svg b/app/assets/icons/ic-listed.svg index 03e347717..3bac23a5a 100644 --- a/app/assets/icons/ic-listed.svg +++ b/app/assets/icons/ic-listed.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-lock.svg b/app/assets/icons/ic-lock.svg new file mode 100644 index 000000000..946b674fc --- /dev/null +++ b/app/assets/icons/ic-lock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-security.svg b/app/assets/icons/ic-security.svg index badc9d1ad..dfa4b37cc 100644 --- a/app/assets/icons/ic-security.svg +++ b/app/assets/icons/ic-security.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-server.svg b/app/assets/icons/ic-server.svg new file mode 100644 index 000000000..faa3ea19e --- /dev/null +++ b/app/assets/icons/ic-server.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-settings.svg b/app/assets/icons/ic-settings.svg index cc14f94a8..2191bea9a 100644 --- a/app/assets/icons/ic-settings.svg +++ b/app/assets/icons/ic-settings.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-signin.svg b/app/assets/icons/ic-signin.svg new file mode 100644 index 000000000..f211b7c45 --- /dev/null +++ b/app/assets/icons/ic-signin.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-signout.svg b/app/assets/icons/ic-signout.svg new file mode 100644 index 000000000..3b66c862a --- /dev/null +++ b/app/assets/icons/ic-signout.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-star.svg b/app/assets/icons/ic-star.svg index 638dae331..f74b0d567 100644 --- a/app/assets/icons/ic-star.svg +++ b/app/assets/icons/ic-star.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-sync.svg b/app/assets/icons/ic-sync.svg new file mode 100644 index 000000000..b93f0ba9d --- /dev/null +++ b/app/assets/icons/ic-sync.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/assets/icons/ic-themes.svg b/app/assets/icons/ic-themes.svg index 88606ca76..33abb061a 100644 --- a/app/assets/icons/ic-themes.svg +++ b/app/assets/icons/ic-themes.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/icons/ic-user.svg b/app/assets/icons/ic-user.svg index 65ac58800..1bdfaf61f 100644 --- a/app/assets/icons/ic-user.svg +++ b/app/assets/icons/ic-user.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx b/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx new file mode 100644 index 000000000..82865ede9 --- /dev/null +++ b/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx @@ -0,0 +1,73 @@ +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { useState } from 'preact/hooks'; +import { Checkbox } from '../Checkbox'; +import { Icon } from '../Icon'; +import { InputWithIcon } from '../InputWithIcon'; + +type Props = { + application: WebApplication; + appState: AppState; + disabled?: boolean; +}; + +export const AdvancedOptions: FunctionComponent = observer( + ({ appState, application, disabled = false, children }) => { + const { server, setServer, enableServerOption, setEnableServerOption } = + appState.accountMenu; + const [showAdvanced, setShowAdvanced] = useState(false); + + const handleServerOptionChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setEnableServerOption(e.target.checked); + } + }; + + const handleSyncServerChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setServer(e.target.value); + application.setCustomHost(e.target.value); + } + }; + + const toggleShowAdvanced = () => { + setShowAdvanced(!showAdvanced); + }; + + return ( + <> +
+ Advanced options + +
+ + {showAdvanced ? ( +
+ {children} + + +
+ ) : null} + + ); + } +); diff --git a/app/assets/javascripts/components/AccountMenu/Authentication.tsx b/app/assets/javascripts/components/AccountMenu/Authentication.tsx index a3b42a0b8..cc401159a 100644 --- a/app/assets/javascripts/components/AccountMenu/Authentication.tsx +++ b/app/assets/javascripts/components/AccountMenu/Authentication.tsx @@ -3,7 +3,7 @@ import { STRING_ACCOUNT_MENU_UNCHECK_MERGE, STRING_GENERATING_LOGIN_KEYS, STRING_GENERATING_REGISTER_KEYS, - STRING_NON_MATCHING_PASSWORDS + STRING_NON_MATCHING_PASSWORDS, } from '@/strings'; import { JSXInternal } from 'preact/src/jsx'; import TargetedEvent = JSXInternal.TargetedEvent; @@ -17,13 +17,9 @@ import { AppState } from '@/ui_models/app_state'; type Props = { application: WebApplication; appState: AppState; -} - -const Authentication = observer(({ - application, - appState, - }: Props) => { +}; +const Authentication = observer(({ application, appState }: Props) => { const [showAdvanced, setShowAdvanced] = useState(false); const [isAuthenticating, setIsAuthenticating] = useState(false); const [email, setEmail] = useState(''); @@ -39,12 +35,12 @@ const Authentication = observer(({ const { server, notesAndTagsCount, - showLogin, + showSignIn, showRegister, - setShowLogin, + setShowSignIn, setShowRegister, setServer, - closeAccountMenu + closeAccountMenu, } = appState.accountMenu; const user = application.getUser(); @@ -58,11 +54,11 @@ const Authentication = observer(({ // Reset password and confirmation fields when hiding the form useEffect(() => { - if (!showLogin && !showRegister) { + if (!showSignIn && !showRegister) { setPassword(''); setPasswordConfirmation(''); } - }, [showLogin, showRegister]); + }, [showSignIn, showRegister]); const handleHostInputChange = (event: TargetedEvent) => { const { value } = event.target as HTMLInputElement; @@ -75,7 +71,7 @@ const Authentication = observer(({ const passwordConfirmationInputRef = useRef(); const handleSignInClick = () => { - setShowLogin(true); + setShowSignIn(true); setIsEmailFocused(true); }; @@ -90,7 +86,7 @@ const Authentication = observer(({ passwordConfirmationInputRef.current?.blur(); }; - const login = async () => { + const signin = async () => { setStatus(STRING_GENERATING_LOGIN_KEYS); setIsAuthenticating(true); @@ -105,13 +101,13 @@ const Authentication = observer(({ if (!error) { setIsAuthenticating(false); setPassword(''); - setShowLogin(false); + setShowSignIn(false); closeAccountMenu(); return; } - setShowLogin(true); + setShowSignIn(true); setStatus(undefined); setPassword(''); @@ -150,10 +146,11 @@ const Authentication = observer(({ } }; - const handleAuthFormSubmit = (event: - TargetedEvent | - TargetedMouseEvent | - TargetedKeyboardEvent + const handleAuthFormSubmit = ( + event: + | TargetedEvent + | TargetedMouseEvent + | TargetedKeyboardEvent ) => { event.preventDefault(); @@ -163,8 +160,8 @@ const Authentication = observer(({ blurAuthFields(); - if (showLogin) { - login(); + if (showSignIn) { + signin(); } else { register(); } @@ -186,19 +183,23 @@ const Authentication = observer(({ setEmail(value); }; - const handlePasswordConfirmationChange = (event: TargetedEvent) => { + const handlePasswordConfirmationChange = ( + event: TargetedEvent + ) => { const { value } = event.target as HTMLInputElement; setPasswordConfirmation(value); }; - const handleMergeLocalData = async (event: TargetedEvent) => { + const handleMergeLocalData = async ( + event: TargetedEvent + ) => { const { checked } = event.target as HTMLInputElement; setShouldMergeLocal(checked); if (!checked) { const confirmResult = await confirmDialog({ text: STRING_ACCOUNT_MENU_UNCHECK_MERGE, - confirmButtonStyle: 'danger' + confirmButtonStyle: 'danger', }); setShouldMergeLocal(!confirmResult); } @@ -206,10 +207,12 @@ const Authentication = observer(({ return ( <> - {!user && !showLogin && !showRegister && ( + {!user && !showSignIn && !showRegister && (
-
Sign in or register to enable sync and end-to-end encryption.
+
+ Sign in or register to enable sync and end-to-end encryption. +
- Standard Notes is free on every platform, and comes - standard with sync and encryption. + Standard Notes is free on every platform, and comes standard with + sync and encryption.
)} - {(showLogin || showRegister) && ( + {(showSignIn || showRegister) && (
- {showLogin ? 'Sign In' : 'Register'} + {showSignIn ? 'Sign In' : 'Register'}
-
+
- {showRegister && - } + {showRegister && ( + + )}
@@ -301,24 +310,28 @@ const Authentication = observer(({ required />
- {showLogin && ( + {showSignIn && ( )} @@ -327,9 +340,12 @@ const Authentication = observer(({ )} {!isAuthenticating && (
-
)} @@ -337,9 +353,9 @@ const Authentication = observer(({
No Password Reset.
- Because your notes are encrypted using your password, - Standard Notes does not have a password reset option. - You cannot forget your password. + Because your notes are encrypted using your password, Standard + Notes does not have a password reset option. You cannot forget + your password.
)} @@ -358,7 +374,7 @@ const Authentication = observer(({ setIsEphemeral(prevState => !prevState)} + onChange={() => setIsEphemeral((prevState) => !prevState)} />

Stay signed in

@@ -371,7 +387,9 @@ const Authentication = observer(({ checked={shouldMergeLocal} onChange={handleMergeLocalData} /> -

Merge local data ({notesAndTagsCount}) notes and tags

+

+ Merge local data ({notesAndTagsCount}) notes and tags +

)} @@ -379,7 +397,8 @@ const Authentication = observer(({ )} - )} + )} + ); }); diff --git a/app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx b/app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx new file mode 100644 index 000000000..5511e2cae --- /dev/null +++ b/app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx @@ -0,0 +1,172 @@ +import { STRING_NON_MATCHING_PASSWORDS } from '@/strings'; +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { StateUpdater, useRef, useState } from 'preact/hooks'; +import { AccountMenuPane } from '.'; +import { Button } from '../Button'; +import { Checkbox } from '../Checkbox'; +import { IconButton } from '../IconButton'; +import { InputWithIcon } from '../InputWithIcon'; +import { AdvancedOptions } from './AdvancedOptions'; + +type Props = { + appState: AppState; + application: WebApplication; + setMenuPane: (pane: AccountMenuPane) => void; + email: string; + password: string; + setPassword: StateUpdater; +}; + +export const ConfirmPassword: FunctionComponent = observer( + ({ application, appState, setMenuPane, email, password, setPassword }) => { + const { notesAndTagsCount } = appState.accountMenu; + const [confirmPassword, setConfirmPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [isRegistering, setIsRegistering] = useState(false); + const [isEphemeral, setIsEphemeral] = useState(false); + const [shouldMergeLocal, setShouldMergeLocal] = useState(true); + + const passwordInputRef = useRef(); + + const handlePasswordChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setConfirmPassword(e.target.value); + } + }; + + const handleEphemeralChange = () => { + setIsEphemeral(!isEphemeral); + }; + + const handleShouldMergeChange = () => { + setShouldMergeLocal(!shouldMergeLocal); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + handleConfirmFormSubmit(e); + } + }; + + const handleConfirmFormSubmit = (e: Event) => { + e.preventDefault(); + + if (!password) { + passwordInputRef?.current.focus(); + return; + } + + if (password === confirmPassword) { + setIsRegistering(true); + application + .register(email, password, isEphemeral, shouldMergeLocal) + .then((res) => { + if (res.error) { + throw new Error(res.error.message); + } + appState.accountMenu.closeAccountMenu(); + appState.accountMenu.setCurrentPane(AccountMenuPane.GeneralMenu); + }) + .catch((err) => { + console.error(err); + application.alertService.alert(err).finally(() => { + setPassword(''); + handleGoBack(); + }); + }) + .finally(() => { + setIsRegistering(false); + }); + } else { + application.alertService + .alert(STRING_NON_MATCHING_PASSWORDS) + .finally(() => { + setConfirmPassword(''); + passwordInputRef?.current.focus(); + }); + } + }; + + const handleGoBack = () => { + setMenuPane(AccountMenuPane.Register); + }; + + return ( + <> +
+ +
Confirm password
+
+
+ Because your notes are encrypted using your password,{' '} + + Standard Notes does not have a password reset option + + . If you forget your password, you will permanently lose access to + your data. +
+
+ + + ) : ( + <> + + + + )} + + {user ? ( + <> +
+ + + ) : null} + + ); + } +); diff --git a/app/assets/javascripts/components/AccountMenu/SignIn.tsx b/app/assets/javascripts/components/AccountMenu/SignIn.tsx new file mode 100644 index 000000000..3b1ab702f --- /dev/null +++ b/app/assets/javascripts/components/AccountMenu/SignIn.tsx @@ -0,0 +1,227 @@ +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import { AccountMenuPane } from '.'; +import { Button } from '../Button'; +import { Checkbox } from '../Checkbox'; +import { Icon } from '../Icon'; +import { IconButton } from '../IconButton'; +import { InputWithIcon } from '../InputWithIcon'; +import { AdvancedOptions } from './AdvancedOptions'; + +type Props = { + appState: AppState; + application: WebApplication; + setMenuPane: (pane: AccountMenuPane) => void; +}; + +export const SignInPane: FunctionComponent = observer( + ({ application, appState, setMenuPane }) => { + const { notesAndTagsCount } = appState.accountMenu; + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isInvalid, setIsInvalid] = useState(false); + const [isEphemeral, setIsEphemeral] = useState(false); + const [isStrictSignin, setIsStrictSignin] = useState(false); + const [isSigningIn, setIsSigningIn] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const [shouldMergeLocal, setShouldMergeLocal] = useState(true); + + const emailInputRef = useRef(); + const passwordInputRef = useRef(); + + useEffect(() => { + if (emailInputRef?.current) { + emailInputRef.current.focus(); + } + }, []); + + const resetInvalid = () => { + if (isInvalid) { + setIsInvalid(false); + } + }; + + const handleEmailChange = (e: Event) => { + if (e.target instanceof HTMLInputElement) { + setEmail(e.target.value); + } + }; + + const handlePasswordChange = (e: Event) => { + if (isInvalid) { + setIsInvalid(false); + } + if (e.target instanceof HTMLInputElement) { + setPassword(e.target.value); + } + }; + + const handleEphemeralChange = () => { + setIsEphemeral(!isEphemeral); + }; + + const handleStrictSigninChange = () => { + setIsStrictSignin(!isStrictSignin); + }; + + const handleShouldMergeChange = () => { + setShouldMergeLocal(!shouldMergeLocal); + }; + + const signIn = () => { + setIsSigningIn(true); + emailInputRef?.current.blur(); + passwordInputRef?.current.blur(); + + application + .signIn(email, password, isStrictSignin, isEphemeral, shouldMergeLocal) + .then((res) => { + if (res.error) { + throw new Error(res.error.message); + } + appState.accountMenu.closeAccountMenu(); + }) + .catch((err) => { + console.error(err); + if (err.toString().includes('Invalid email or password')) { + setIsInvalid(true); + } else { + application.alertService.alert(err); + } + setPassword(''); + passwordInputRef?.current.blur(); + }) + .finally(() => { + setIsSigningIn(false); + }); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + handleSignInFormSubmit(e); + } + }; + + const handleSignInFormSubmit = (e: Event) => { + e.preventDefault(); + + if (!email || email.length === 0) { + emailInputRef?.current.focus(); + return; + } + + if (!password || password.length === 0) { + passwordInputRef?.current.focus(); + return; + } + + signIn(); + }; + + return ( + <> +
+ setMenuPane(AccountMenuPane.GeneralMenu)} + focusable={true} + disabled={isSigningIn} + /> +
Sign in
+
+
+
+ + + {isInvalid ? ( +
+ Invalid email or password. +
+ ) : null} +
+
+
+ +
+ + + + +
+
+ + ); + } +); diff --git a/app/assets/javascripts/components/AccountMenu/index.tsx b/app/assets/javascripts/components/AccountMenu/index.tsx index 14bdacecf..77da5608c 100644 --- a/app/assets/javascripts/components/AccountMenu/index.tsx +++ b/app/assets/javascripts/components/AccountMenu/index.tsx @@ -2,72 +2,115 @@ import { observer } from 'mobx-react-lite'; import { toDirective } from '@/components/utils'; import { AppState } from '@/ui_models/app_state'; import { WebApplication } from '@/ui_models/application'; -import { ConfirmSignoutContainer } from '@/components/ConfirmSignoutModal'; -import Authentication from '@/components/AccountMenu/Authentication'; -import Footer from '@/components/AccountMenu/Footer'; -import User from '@/components/AccountMenu/User'; -import { useEffect } from 'preact/hooks'; +import { useState } from 'preact/hooks'; +import { GeneralAccountMenu } from './GeneralAccountMenu'; +import { FunctionComponent } from 'preact'; +import { SignInPane } from './SignIn'; +import { CreateAccount } from './CreateAccount'; +import { ConfirmSignoutContainer } from '../ConfirmSignoutModal'; +import { ConfirmPassword } from './ConfirmPassword'; + +export enum AccountMenuPane { + GeneralMenu, + SignIn, + Register, + ConfirmPassword, +} type Props = { appState: AppState; application: WebApplication; }; -const AccountMenu = observer(({ application, appState }: Props) => { - const { - show: showAccountMenu, - showLogin, - showRegister, - setShowLogin, - setShowRegister, - closeAccountMenu - } = appState.accountMenu; +type PaneSelectorProps = Props & { + menuPane: AccountMenuPane; + setMenuPane: (pane: AccountMenuPane) => void; + closeMenu: () => void; +}; - const user = application.getUser(); +const MenuPaneSelector: FunctionComponent = observer( + ({ application, appState, menuPane, setMenuPane, closeMenu }) => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); - useEffect(() => { - // Reset "Login" and "Registration" sections state when hiding account menu, - // so the next time account menu is opened these sections are closed - if (!showAccountMenu) { - setShowLogin(false); - setShowRegister(false); - } - }, [setShowLogin, setShowRegister, showAccountMenu]); - - return ( -
-
-
-
Account
- Close -
-
- + ); + case AccountMenuPane.SignIn: + return ( + + ); + case AccountMenuPane.Register: + return ( + + ); + case AccountMenuPane.ConfirmPassword: + return ( + + ); + } + } +); + +const AccountMenu: FunctionComponent = observer( + ({ application, appState }) => { + const { + currentPane, + setCurrentPane, + shouldAnimateCloseMenu, + closeAccountMenu, + } = appState.accountMenu; + + return ( +
+
+ - {!showLogin && !showRegister && user && ( -
- -
- )}
-
-
- ); -}); - -export const AccountMenuDirective = toDirective( - AccountMenu + ); + } ); + +export const AccountMenuDirective = toDirective(AccountMenu); diff --git a/app/assets/javascripts/components/Checkbox.tsx b/app/assets/javascripts/components/Checkbox.tsx new file mode 100644 index 000000000..e453f55d6 --- /dev/null +++ b/app/assets/javascripts/components/Checkbox.tsx @@ -0,0 +1,32 @@ +import { FunctionComponent } from 'preact'; + +type CheckboxProps = { + name: string; + checked: boolean; + onChange: (e: Event) => void; + disabled?: boolean; + label: string; +}; + +export const Checkbox: FunctionComponent = ({ + name, + checked, + onChange, + disabled, + label, +}) => { + return ( + + ); +}; diff --git a/app/assets/javascripts/components/Icon.tsx b/app/assets/javascripts/components/Icon.tsx index 9e2ca1b1a..051ac9c19 100644 --- a/app/assets/javascripts/components/Icon.tsx +++ b/app/assets/javascripts/components/Icon.tsx @@ -36,11 +36,35 @@ import InfoIcon from '../../icons/ic-info.svg'; import CheckIcon from '../../icons/ic-check.svg'; import CheckBoldIcon from '../../icons/ic-check-bold.svg'; import AccountCircleIcon from '../../icons/ic-account-circle.svg'; +import CloudOffIcon from '../../icons/ic-cloud-off.svg'; +import SignInIcon from '../../icons/ic-signin.svg'; +import SignOutIcon from '../../icons/ic-signout.svg'; +import CheckCircleIcon from '../../icons/ic-check-circle.svg'; +import SyncIcon from '../../icons/ic-sync.svg'; +import ArrowLeftIcon from '../../icons/ic-arrow-left.svg'; +import ChevronDownIcon from '../../icons/ic-chevron-down.svg'; +import EmailIcon from '../../icons/ic-email.svg'; +import ServerIcon from '../../icons/ic-server.svg'; +import EyeIcon from '../../icons/ic-eye.svg'; +import EyeOffIcon from '../../icons/ic-eye-off.svg'; +import LockIcon from '../../icons/ic-lock.svg'; import { toDirective } from './utils'; import { FunctionalComponent } from 'preact'; const ICONS = { + lock: LockIcon, + eye: EyeIcon, + 'eye-off': EyeOffIcon, + server: ServerIcon, + email: EmailIcon, + 'chevron-down': ChevronDownIcon, + 'arrow-left': ArrowLeftIcon, + sync: SyncIcon, + 'check-circle': CheckCircleIcon, + signIn: SignInIcon, + signOut: SignOutIcon, + 'cloud-off': CloudOffIcon, 'pencil-off': PencilOffIcon, 'plain-text': PlainTextIcon, 'rich-text': RichTextIcon, diff --git a/app/assets/javascripts/components/IconButton.tsx b/app/assets/javascripts/components/IconButton.tsx index e229a23ae..45ce99942 100644 --- a/app/assets/javascripts/components/IconButton.tsx +++ b/app/assets/javascripts/components/IconButton.tsx @@ -19,6 +19,8 @@ interface Props { title: string; focusable: boolean; + + disabled?: boolean; } /** @@ -31,6 +33,8 @@ export const IconButton: FunctionComponent = ({ icon, title, focusable, + iconClassName = '', + disabled = false, }) => { const click = (e: MouseEvent) => { e.preventDefault(); @@ -42,8 +46,9 @@ export const IconButton: FunctionComponent = ({ title={title} className={`no-border cursor-pointer bg-transparent flex flex-row items-center hover:brightness-130 p-0 ${focusableClass} ${className}`} onClick={click} + disabled={disabled} > - + ); }; diff --git a/app/assets/javascripts/components/InputWithIcon.tsx b/app/assets/javascripts/components/InputWithIcon.tsx new file mode 100644 index 000000000..5277d0238 --- /dev/null +++ b/app/assets/javascripts/components/InputWithIcon.tsx @@ -0,0 +1,89 @@ +import { FunctionComponent, Ref } from 'preact'; +import { JSXInternal } from 'preact/src/jsx'; +import { forwardRef } from 'preact/compat'; +import { Icon, IconType } from './Icon'; +import { IconButton } from './IconButton'; + +type ToggleProps = { + toggleOnIcon: IconType; + toggleOffIcon: IconType; + title: string; + toggled: boolean; + onClick: (toggled: boolean) => void; +}; + +type Props = { + icon: IconType; + inputType: 'text' | 'email' | 'password'; + className?: string; + iconClassName?: string; + value: string | undefined; + onChange: JSXInternal.GenericEventHandler; + onFocus?: JSXInternal.GenericEventHandler; + onKeyDown?: JSXInternal.KeyboardEventHandler; + disabled?: boolean; + placeholder: string; + toggle?: ToggleProps; +}; + +const DISABLED_CLASSNAME = 'bg-grey-5 cursor-not-allowed'; + +export const InputWithIcon: FunctionComponent = forwardRef( + ( + { + icon, + inputType, + className, + iconClassName, + value, + onChange, + onFocus, + onKeyDown, + disabled, + toggle, + placeholder, + }, + ref: Ref + ) => { + const handleToggle = () => { + if (toggle) toggle.onClick(!toggle?.toggled); + }; + + return ( +
+
+ +
+ + {toggle ? ( +
+ +
+ ) : null} +
+ ); + } +); diff --git a/app/assets/javascripts/components/OtherSessionsLogout.tsx b/app/assets/javascripts/components/OtherSessionsSignOut.tsx similarity index 77% rename from app/assets/javascripts/components/OtherSessionsLogout.tsx rename to app/assets/javascripts/components/OtherSessionsSignOut.tsx index a6da7ef9f..b72487bd1 100644 --- a/app/assets/javascripts/components/OtherSessionsLogout.tsx +++ b/app/assets/javascripts/components/OtherSessionsSignOut.tsx @@ -14,19 +14,18 @@ type Props = { appState: AppState; }; -export const OtherSessionsLogoutContainer = observer((props: Props) => { - if (!props.appState.accountMenu.otherSessionsLogOut) { +export const OtherSessionsSignOutContainer = observer((props: Props) => { + if (!props.appState.accountMenu.otherSessionsSignOut) { return null; } - return ; + return ; }); -const ConfirmOtherSessionsLogout = observer( +const ConfirmOtherSessionsSignOut = observer( ({ application, appState }: Props) => { - const cancelRef = useRef(); function closeDialog() { - appState.accountMenu.setOtherSessionsLogout(false); + appState.accountMenu.setOtherSessionsSignOut(false); } return ( @@ -41,9 +40,10 @@ const ConfirmOtherSessionsLogout = observer(

- This action will sign out all other devices signed into your account, - and remove your data from those devices when they next regain connection - to the internet. You may sign back in on those devices at any time. + This action will sign out all other devices signed into + your account, and remove your data from those devices when + they next regain connection to the internet. You may sign + back in on those devices at any time.

@@ -60,9 +60,9 @@ const ConfirmOtherSessionsLogout = observer( application.revokeAllOtherSessions(); closeDialog(); application.alertService.alert( - "You have successfully revoked your sessions from other devices.", + 'You have successfully revoked your sessions from other devices.', undefined, - "Finish" + 'Finish' ); }} > diff --git a/app/assets/javascripts/directives/views/passwordWizard.ts b/app/assets/javascripts/directives/views/passwordWizard.ts index 4f0dfe8ee..13c2f5dc2 100644 --- a/app/assets/javascripts/directives/views/passwordWizard.ts +++ b/app/assets/javascripts/directives/views/passwordWizard.ts @@ -1,48 +1,52 @@ import { WebApplication } from '@/ui_models/application'; -import { PasswordWizardScope, PasswordWizardType, WebDirective } from './../../types'; +import { + PasswordWizardScope, + PasswordWizardType, + WebDirective, +} from './../../types'; import template from '%/directives/password-wizard.pug'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; -const DEFAULT_CONTINUE_TITLE = "Continue"; +const DEFAULT_CONTINUE_TITLE = 'Continue'; enum Steps { PasswordStep = 1, - FinishStep = 2 + FinishStep = 2, } type FormData = { - currentPassword?: string, - newPassword?: string, - newPasswordConfirmation?: string, - status?: string -} + currentPassword?: string; + newPassword?: string; + newPasswordConfirmation?: string; + status?: string; +}; type State = { - lockContinue: boolean - formData: FormData, - continueTitle: string, - step: Steps, - title: string, - showSpinner: boolean - processing: boolean -} + lockContinue: boolean; + formData: FormData; + continueTitle: string; + step: Steps; + title: string; + showSpinner: boolean; + processing: boolean; +}; type Props = { - type: PasswordWizardType, - changePassword: boolean, - securityUpdate: boolean -} + type: PasswordWizardType; + changePassword: boolean; + securityUpdate: boolean; +}; -class PasswordWizardCtrl extends PureViewCtrl implements PasswordWizardScope { - $element: JQLite - application!: WebApplication - type!: PasswordWizardType - isContinuing = false +class PasswordWizardCtrl + extends PureViewCtrl + implements PasswordWizardScope +{ + $element: JQLite; + application!: WebApplication; + type!: PasswordWizardType; + isContinuing = false; /* @ngInject */ - constructor( - $element: JQLite, - $timeout: ng.ITimeoutService, - ) { + constructor($element: JQLite, $timeout: ng.ITimeoutService) { super($timeout); this.$element = $element; this.registerWindowUnloadStopper(); @@ -53,13 +57,13 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW this.initProps({ type: this.type, changePassword: this.type === PasswordWizardType.ChangePassword, - securityUpdate: this.type === PasswordWizardType.AccountUpgrade + securityUpdate: this.type === PasswordWizardType.AccountUpgrade, }); this.setState({ formData: {}, continueTitle: DEFAULT_CONTINUE_TITLE, step: Steps.PasswordStep, - title: this.props.changePassword ? 'Change Password' : 'Account Update' + title: this.props.changePassword ? 'Change Password' : 'Account Update', }); } @@ -78,7 +82,7 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW resetContinueState() { this.setState({ showSpinner: false, - continueTitle: DEFAULT_CONTINUE_TITLE + continueTitle: DEFAULT_CONTINUE_TITLE, }); this.isContinuing = false; } @@ -95,7 +99,7 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW this.isContinuing = true; await this.setState({ showSpinner: true, - continueTitle: "Generating Keys..." + continueTitle: 'Generating Keys...', }); const valid = await this.validateCurrentPassword(); if (!valid) { @@ -110,8 +114,8 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW this.isContinuing = false; this.setState({ showSpinner: false, - continueTitle: "Finish", - step: Steps.FinishStep + continueTitle: 'Finish', + step: Steps.FinishStep, }); } @@ -119,43 +123,43 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW return this.setState({ formData: { ...this.state.formData, - ...formData - } + ...formData, + }, }); } async validateCurrentPassword() { const currentPassword = this.state.formData.currentPassword; - const newPass = this.props.securityUpdate ? currentPassword : this.state.formData.newPassword; + const newPass = this.props.securityUpdate + ? currentPassword + : this.state.formData.newPassword; if (!currentPassword || currentPassword.length === 0) { this.application.alertService!.alert( - "Please enter your current password." + 'Please enter your current password.' ); return false; } if (this.props.changePassword) { if (!newPass || newPass.length === 0) { - this.application.alertService!.alert( - "Please enter a new password." - ); + this.application.alertService!.alert('Please enter a new password.'); return false; } if (newPass !== this.state.formData.newPasswordConfirmation) { this.application.alertService!.alert( - "Your new password does not match its confirmation." + 'Your new password does not match its confirmation.' ); this.setFormDataState({ - status: undefined + status: undefined, }); return false; } } if (!this.application.getUser()?.email) { this.application.alertService!.alert( - "We don't have your email stored. Please log out then log back in to fix this issue." + "We don't have your email stored. Please sign out then log back in to fix this issue." ); this.setFormDataState({ - status: undefined + status: undefined, }); return false; } @@ -166,7 +170,7 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW ); if (!success) { this.application.alertService!.alert( - "The current password you entered is not correct. Please try again." + 'The current password you entered is not correct. Please try again.' ); } return success; @@ -176,10 +180,10 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW await this.application.downloadBackup(); await this.setState({ lockContinue: true, - processing: true + processing: true, }); await this.setFormDataState({ - status: "Processing encryption keys…" + status: 'Processing encryption keys…', }); const newPassword = this.props.securityUpdate ? this.state.formData.currentPassword @@ -195,16 +199,16 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW }); if (!success) { this.setFormDataState({ - status: "Unable to process your password. Please try again." + status: 'Unable to process your password. Please try again.', }); } else { this.setState({ formData: { ...this.state.formData, status: this.props.changePassword - ? "Successfully changed password." - : "Successfully performed account update." - } + ? 'Successfully changed password.' + : 'Successfully performed account update.', + }, }); } return success; @@ -213,7 +217,7 @@ class PasswordWizardCtrl extends PureViewCtrl implements PasswordW dismiss() { if (this.state.lockContinue) { this.application.alertService!.alert( - "Cannot close window until pending tasks are complete." + 'Cannot close window until pending tasks are complete.' ); } else { const elem = this.$element; @@ -234,7 +238,7 @@ export class PasswordWizard extends WebDirective { this.bindToController = true; this.scope = { type: '=', - application: '=' + application: '=', }; } } diff --git a/app/assets/javascripts/preferences/PreferencesView.tsx b/app/assets/javascripts/preferences/PreferencesView.tsx index e6b2b47bd..870348f24 100644 --- a/app/assets/javascripts/preferences/PreferencesView.tsx +++ b/app/assets/javascripts/preferences/PreferencesView.tsx @@ -1,14 +1,20 @@ import { RoundIconButton } from '@/components/RoundIconButton'; import { TitleBar, Title } from '@/components/TitleBar'; import { FunctionComponent } from 'preact'; -import { AccountPreferences, HelpAndFeedback, Listed, General, Security } from './panes'; +import { + AccountPreferences, + HelpAndFeedback, + Listed, + General, + Security, +} from './panes'; import { observer } from 'mobx-react-lite'; import { PreferencesMenu } from './PreferencesMenu'; import { PreferencesMenuView } from './PreferencesMenuView'; import { WebApplication } from '@/ui_models/application'; import { MfaProps } from './panes/two-factor-auth/MfaProps'; import { AppState } from '@/ui_models/app_state'; -import { useEffect } from 'preact/hooks'; +import { useEffect, useMemo } from 'preact/hooks'; import { Extensions } from './panes/Extensions'; interface PreferencesProps extends MfaProps { @@ -22,7 +28,9 @@ const PaneSelector: FunctionComponent< > = observer((props) => { switch (props.menu.selectedPaneId) { case 'general': - return + return ( + + ); case 'account': return ( = observer( (props) => { + const menu = useMemo(() => new PreferencesMenu(), []); useEffect(() => { + menu.selectPane(props.appState.preferences.currentPane); const removeEscKeyObserver = props.application.io.addKeyObserver({ key: 'Escape', onKeyDown: (event) => { event.preventDefault(); props.closePreferences(); - } + }, }); return () => { removeEscKeyObserver(); }; - }, [props]); - const menu = new PreferencesMenu(); + }, [props, menu]); + return (
diff --git a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx index 6d27c63cf..1523e9a0f 100644 --- a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx +++ b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx @@ -2,7 +2,7 @@ import { Sync, SubscriptionWrapper, Credentials, - LogOutWrapper, + SignOutWrapper, Authentication, } from '@/preferences/panes/account'; import { PreferencesPane } from '@/preferences/components'; @@ -23,7 +23,7 @@ export const AccountPreferences = observer( return ( - + ); } @@ -33,7 +33,7 @@ export const AccountPreferences = observer( - + ); } diff --git a/app/assets/javascripts/preferences/panes/account/Authentication.tsx b/app/assets/javascripts/preferences/panes/account/Authentication.tsx index d98fc839e..72e1e27f6 100644 --- a/app/assets/javascripts/preferences/panes/account/Authentication.tsx +++ b/app/assets/javascripts/preferences/panes/account/Authentication.tsx @@ -1,41 +1,65 @@ -import { Button } from "@/components/Button"; -import { PreferencesGroup, PreferencesSegment, Subtitle, Text, Title } from "@/preferences/components"; -import { WebApplication } from "@/ui_models/application"; -import { AppState } from "@/ui_models/app_state"; -import { observer } from "mobx-react-lite"; -import { FunctionComponent } from "preact"; +import { AccountMenuPane } from '@/components/AccountMenu'; +import { Button } from '@/components/Button'; +import { + PreferencesGroup, + PreferencesSegment, + Subtitle, + Text, + Title, +} from '@/preferences/components'; +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; -export const Authentication: FunctionComponent<{ application: WebApplication, appState: AppState }> = - observer(({ appState }) => { +export const Authentication: FunctionComponent<{ + application: WebApplication; + appState: AppState; +}> = observer(({ appState }) => { + const clickSignIn = () => { + appState.preferences.closePreferences(); + appState.accountMenu.setCurrentPane(AccountMenuPane.SignIn); + appState.accountMenu.setShow(true); + }; - const clickSignIn = () => { - appState.preferences.closePreferences(); - appState.accountMenu.setShowLogin(true); - appState.accountMenu.setShow(true); - }; + const clickRegister = () => { + appState.preferences.closePreferences(); + appState.accountMenu.setCurrentPane(AccountMenuPane.Register); + appState.accountMenu.setShow(true); + }; - const clickRegister = () => { - appState.preferences.closePreferences(); - appState.accountMenu.setShowRegister(true); - appState.accountMenu.setShow(true); - }; - - return ( - - -
- You're not signed in - Sign in to sync your notes and preferences across all your devices and enable end-to-end encryption. -
-
-
-
- Standard Notes is free on every platform, and comes standard with sync and encryption. + return ( + + +
+ You're not signed in + + Sign in to sync your notes and preferences across all your devices + and enable end-to-end encryption. + +
+
+
- - - ); - }); +
+ + Standard Notes is free on every platform, and comes standard with + sync and encryption. + +
+ + + ); +}); diff --git a/app/assets/javascripts/preferences/panes/account/LogOutView.tsx b/app/assets/javascripts/preferences/panes/account/SignOutView.tsx similarity index 69% rename from app/assets/javascripts/preferences/panes/account/LogOutView.tsx rename to app/assets/javascripts/preferences/panes/account/SignOutView.tsx index 5acfeed05..725e8b7f3 100644 --- a/app/assets/javascripts/preferences/panes/account/LogOutView.tsx +++ b/app/assets/javascripts/preferences/panes/account/SignOutView.tsx @@ -1,6 +1,6 @@ import { Button } from '@/components/Button'; import { ConfirmSignoutContainer } from '@/components/ConfirmSignoutModal'; -import { OtherSessionsLogoutContainer } from '@/components/OtherSessionsLogout'; +import { OtherSessionsSignOutContainer } from '@/components/OtherSessionsSignOut'; import { PreferencesGroup, PreferencesSegment, @@ -13,30 +13,33 @@ import { AppState } from '@/ui_models/app_state'; import { observer } from 'mobx-react-lite'; import { FunctionComponent } from 'preact'; -const LogOutView: FunctionComponent<{ +const SignOutView: FunctionComponent<{ application: WebApplication; appState: AppState; }> = observer(({ application, appState }) => { - return ( <> - Log out + Sign out
Other devices - Want to log out on all devices except this one? + Want to sign out on all devices except this one?
@@ -45,20 +48,19 @@ const LogOutView: FunctionComponent<{
-
-
-
- ); -}); + + + ); + } +); diff --git a/app/assets/javascripts/preferences/panes/account/changePassword/index.tsx b/app/assets/javascripts/preferences/panes/account/changePassword/index.tsx index 1691537b3..93fa3de3c 100644 --- a/app/assets/javascripts/preferences/panes/account/changePassword/index.tsx +++ b/app/assets/javascripts/preferences/panes/account/changePassword/index.tsx @@ -3,7 +3,7 @@ import { ModalDialog, ModalDialogButtons, ModalDialogDescription, - ModalDialogLabel + ModalDialogLabel, } from '@/components/shared/ModalDialog'; import { Button } from '@/components/Button'; import { FunctionalComponent } from 'preact'; @@ -15,29 +15,31 @@ import { useBeforeUnload } from '@/hooks/useBeforeUnload'; enum SubmitButtonTitles { Default = 'Continue', GeneratingKeys = 'Generating Keys...', - Finish = 'Finish' + Finish = 'Finish', } enum Steps { InitialStep, - FinishStep + FinishStep, } type Props = { onCloseDialog: () => void; application: WebApplication; -} +}; export const ChangePassword: FunctionalComponent = ({ onCloseDialog, - application + application, }) => { const [currentPassword, setCurrentPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [newPasswordConfirmation, setNewPasswordConfirmation] = useState(''); const [isContinuing, setIsContinuing] = useState(false); const [lockContinue, setLockContinue] = useState(false); - const [submitButtonTitle, setSubmitButtonTitle] = useState(SubmitButtonTitles.Default); + const [submitButtonTitle, setSubmitButtonTitle] = useState( + SubmitButtonTitles.Default + ); const [currentStep, setCurrentStep] = useState(Steps.InitialStep); useBeforeUnload(); @@ -46,16 +48,12 @@ export const ChangePassword: FunctionalComponent = ({ const validateCurrentPassword = async () => { if (!currentPassword || currentPassword.length === 0) { - applicationAlertService.alert( - 'Please enter your current password.' - ); + applicationAlertService.alert('Please enter your current password.'); return false; } if (!newPassword || newPassword.length === 0) { - applicationAlertService.alert( - 'Please enter a new password.' - ); + applicationAlertService.alert('Please enter a new password.'); return false; } if (newPassword !== newPasswordConfirmation) { @@ -67,7 +65,7 @@ export const ChangePassword: FunctionalComponent = ({ if (!application.getUser()?.email) { applicationAlertService.alert( - 'We don\'t have your email stored. Please log out then log back in to fix this issue.' + "We don't have your email stored. Please sign out then sign back in to fix this issue." ); return false; } @@ -172,15 +170,15 @@ export const ChangePassword: FunctionalComponent = ({ {currentStep === Steps.InitialStep && (