From 6afce84324cefe446521da09489b67b2c702de70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Mon, 13 Sep 2021 10:01:32 +0200 Subject: [PATCH] feat: add changing email (#634) --- .../preferences/panes/account/ChangeEmail.tsx | 79 -------- .../preferences/panes/account/Credentials.tsx | 4 +- .../account/changeEmail/ChangeEmailForm.tsx | 37 ++++ .../changeEmail/ChangeEmailSuccess.tsx | 12 ++ .../panes/account/changeEmail/index.tsx | 188 ++++++++++++++++++ 5 files changed, 239 insertions(+), 81 deletions(-) delete mode 100644 app/assets/javascripts/preferences/panes/account/ChangeEmail.tsx create mode 100644 app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailForm.tsx create mode 100644 app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailSuccess.tsx create mode 100644 app/assets/javascripts/preferences/panes/account/changeEmail/index.tsx diff --git a/app/assets/javascripts/preferences/panes/account/ChangeEmail.tsx b/app/assets/javascripts/preferences/panes/account/ChangeEmail.tsx deleted file mode 100644 index 3754eb2ee..000000000 --- a/app/assets/javascripts/preferences/panes/account/ChangeEmail.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { - ModalDialog, - ModalDialogButtons, - ModalDialogDescription, - ModalDialogLabel -} from '@/components/shared/ModalDialog'; -import { FunctionalComponent } from 'preact'; -import { DecoratedInput } from '@/components/DecoratedInput'; -import { Button } from '@/components/Button'; -import { useState } from 'preact/hooks'; -import { SNAlertService } from '@node_modules/@standardnotes/snjs'; -import { HtmlInputTypes } from '@/enums'; -import { isEmailValid } from '@/utils'; - -type Props = { - onCloseDialog: () => void; - snAlert: SNAlertService['alert'] -}; - -export const ChangeEmail: FunctionalComponent = ({ onCloseDialog, snAlert }) => { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - - const handleSubmit = () => { - let errorMessage = ''; - if (email.trim() === '' || password.trim() === '') { - errorMessage = 'Some fields have not been filled out. Please fill out all fields and try again.'; - } else if (!isEmailValid(email)) { - errorMessage = 'The email you entered has an invalid format. Please review your input and try again.'; - } - - if (errorMessage) { - snAlert(errorMessage); - return; - } - }; - - return ( -
- - - Change Email - - -
- { - setEmail(newEmail); - }} - text={email} - placeholder={'New Email'} - /> -
-
- setPassword(password)} - /> -
-
- -
- ); -}; diff --git a/app/assets/javascripts/preferences/panes/account/Credentials.tsx b/app/assets/javascripts/preferences/panes/account/Credentials.tsx index 0b3caa736..793e6849e 100644 --- a/app/assets/javascripts/preferences/panes/account/Credentials.tsx +++ b/app/assets/javascripts/preferences/panes/account/Credentials.tsx @@ -5,7 +5,7 @@ import { observer } from '@node_modules/mobx-react-lite'; import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator'; import { dateToLocalizedString } from '@/utils'; import { useState } from 'preact/hooks'; -import { ChangeEmail } from '@/preferences/panes/account/ChangeEmail'; +import { ChangeEmail } from '@/preferences/panes/account/changeEmail'; import { ChangePassword } from '@/preferences/panes/account/changePassword'; type Props = { @@ -57,7 +57,7 @@ export const Credentials = observer(({ application }: Props) => { {isChangeEmailDialogOpen && ( setIsChangeEmailDialogOpen(false)} - snAlert={application.alertService.alert} + application={application} /> )} { diff --git a/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailForm.tsx b/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailForm.tsx new file mode 100644 index 000000000..c1f8ed7e3 --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailForm.tsx @@ -0,0 +1,37 @@ +import { DecoratedInput } from '@/components/DecoratedInput'; +import { StateUpdater } from 'preact/hooks'; +import { FunctionalComponent } from 'preact'; +import { HtmlInputTypes } from '@/enums'; + +type Props = { + setNewEmail: StateUpdater + setCurrentPassword: StateUpdater +} +export const ChangeEmailForm: FunctionalComponent = ({ + setNewEmail, + setCurrentPassword +}) => { + return ( + ( + <> +
+ { + setNewEmail(newEmail); + }} + placeholder={'New Email'} + /> +
+
+ { + setCurrentPassword(currentPassword); + }} + placeholder={'Current Password'} + /> +
+ + ) + ); +}; diff --git a/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailSuccess.tsx b/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailSuccess.tsx new file mode 100644 index 000000000..ff5058ff0 --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/changeEmail/ChangeEmailSuccess.tsx @@ -0,0 +1,12 @@ +import { FunctionalComponent } from 'preact'; + +export const ChangeEmailSuccess: FunctionalComponent = () => { + return ( + <> +
Your email has been successfully changed.
+

+ Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility. +

+ + ); +}; diff --git a/app/assets/javascripts/preferences/panes/account/changeEmail/index.tsx b/app/assets/javascripts/preferences/panes/account/changeEmail/index.tsx new file mode 100644 index 000000000..37340e21c --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/changeEmail/index.tsx @@ -0,0 +1,188 @@ +import { useState } from '@node_modules/preact/hooks'; +import { + ModalDialog, + ModalDialogButtons, + ModalDialogDescription, + ModalDialogLabel +} from '@/components/shared/ModalDialog'; +import { Button } from '@/components/Button'; +import { FunctionalComponent } from 'preact'; +import { WebApplication } from '@/ui_models/application'; +import { useBeforeUnload } from '@/hooks/useBeforeUnload'; +import { ChangeEmailForm } from './ChangeEmailForm'; +import { ChangeEmailSuccess } from './ChangeEmailSuccess'; +import { isEmailValid } from '@/utils'; + +enum SubmitButtonTitles { + Default = 'Continue', + GeneratingKeys = 'Generating Keys...', + Finish = 'Finish' +} + +enum Steps { + InitialStep, + FinishStep +} + +type Props = { + onCloseDialog: () => void; + application: WebApplication; +} + +export const ChangeEmail: FunctionalComponent = ({ + onCloseDialog, + application +}) => { + const [currentPassword, setCurrentPassword] = useState(''); + const [newEmail, setNewEmail] = useState(''); + const [isContinuing, setIsContinuing] = useState(false); + const [lockContinue, setLockContinue] = useState(false); + const [submitButtonTitle, setSubmitButtonTitle] = useState(SubmitButtonTitles.Default); + const [currentStep, setCurrentStep] = useState(Steps.InitialStep); + + useBeforeUnload(); + + const applicationAlertService = application.alertService; + + const validateCurrentPassword = async () => { + if (!currentPassword || currentPassword.length === 0) { + applicationAlertService.alert( + 'Please enter your current password.' + ); + + return false; + } + + const success = await application.validateAccountPassword(currentPassword); + if (!success) { + applicationAlertService.alert( + 'The current password you entered is not correct. Please try again.' + ); + + return false; + } + + return success; + }; + + const validateNewEmail = async () => { + if (!isEmailValid(newEmail)) { + applicationAlertService.alert('The email you entered has an invalid format. Please review your input and try again.'); + + return false; + } + + return true; + }; + + const resetProgressState = () => { + setSubmitButtonTitle(SubmitButtonTitles.Default); + setIsContinuing(false); + }; + + const processEmailChange = async () => { + await application.downloadBackup(); + + setLockContinue(true); + + const response = await application.changeEmail( + newEmail, + currentPassword, + ); + + const success = !response.error; + + setLockContinue(false); + + return success; + }; + + const dismiss = () => { + if (lockContinue) { + applicationAlertService.alert( + 'Cannot close window until pending tasks are complete.' + ); + } else { + onCloseDialog(); + } + }; + + const handleSubmit = async () => { + if (lockContinue || isContinuing) { + return; + } + + if (currentStep === Steps.FinishStep) { + dismiss(); + + return; + } + + setIsContinuing(true); + setSubmitButtonTitle(SubmitButtonTitles.GeneratingKeys); + + const valid = await validateCurrentPassword() && await validateNewEmail(); + + if (!valid) { + resetProgressState(); + + return; + } + + const success = await processEmailChange(); + if (!success) { + resetProgressState(); + + return; + } + + setIsContinuing(false); + setSubmitButtonTitle(SubmitButtonTitles.Finish); + setCurrentStep(Steps.FinishStep); + }; + + const handleDialogClose = () => { + if (lockContinue) { + applicationAlertService.alert( + 'Cannot close window until pending tasks are complete.' + ); + } else { + onCloseDialog(); + } + }; + + return ( +
+ + + Change Email + + + {currentStep === Steps.InitialStep && ( + + )} + {currentStep === Steps.FinishStep && } + + + {currentStep === Steps.InitialStep && ( +
+ ); +};