feat: add changing email (#634)
This commit is contained in:
@@ -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<Props> = ({ 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 (
|
|
||||||
<div>
|
|
||||||
<ModalDialog>
|
|
||||||
<ModalDialogLabel closeDialog={onCloseDialog}>
|
|
||||||
Change Email
|
|
||||||
</ModalDialogLabel>
|
|
||||||
<ModalDialogDescription>
|
|
||||||
<div className={'mt-2 mb-3'}>
|
|
||||||
<DecoratedInput
|
|
||||||
onChange={(newEmail) => {
|
|
||||||
setEmail(newEmail);
|
|
||||||
}}
|
|
||||||
text={email}
|
|
||||||
placeholder={'New Email'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={'mt-2 mb-3'}>
|
|
||||||
<DecoratedInput
|
|
||||||
type={HtmlInputTypes.Password}
|
|
||||||
placeholder={'Password'}
|
|
||||||
onChange={password => setPassword(password)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ModalDialogDescription>
|
|
||||||
<ModalDialogButtons>
|
|
||||||
<Button
|
|
||||||
className="min-w-20"
|
|
||||||
type="normal"
|
|
||||||
label="Cancel"
|
|
||||||
onClick={onCloseDialog}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className="min-w-20"
|
|
||||||
type="primary"
|
|
||||||
label="Submit"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
/>
|
|
||||||
</ModalDialogButtons>
|
|
||||||
</ModalDialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -5,7 +5,7 @@ import { observer } from '@node_modules/mobx-react-lite';
|
|||||||
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
|
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
|
||||||
import { dateToLocalizedString } from '@/utils';
|
import { dateToLocalizedString } from '@/utils';
|
||||||
import { useState } from 'preact/hooks';
|
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';
|
import { ChangePassword } from '@/preferences/panes/account/changePassword';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -57,7 +57,7 @@ export const Credentials = observer(({ application }: Props) => {
|
|||||||
{isChangeEmailDialogOpen && (
|
{isChangeEmailDialogOpen && (
|
||||||
<ChangeEmail
|
<ChangeEmail
|
||||||
onCloseDialog={() => setIsChangeEmailDialogOpen(false)}
|
onCloseDialog={() => setIsChangeEmailDialogOpen(false)}
|
||||||
snAlert={application.alertService.alert}
|
application={application}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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<string>
|
||||||
|
setCurrentPassword: StateUpdater<string>
|
||||||
|
}
|
||||||
|
export const ChangeEmailForm: FunctionalComponent<Props> = ({
|
||||||
|
setNewEmail,
|
||||||
|
setCurrentPassword
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
<>
|
||||||
|
<div className={'mt-2 mb-3'}>
|
||||||
|
<DecoratedInput
|
||||||
|
onChange={(newEmail) => {
|
||||||
|
setNewEmail(newEmail);
|
||||||
|
}}
|
||||||
|
placeholder={'New Email'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={'mt-2 mb-3'}>
|
||||||
|
<DecoratedInput
|
||||||
|
type={HtmlInputTypes.Password}
|
||||||
|
onChange={(currentPassword) => {
|
||||||
|
setCurrentPassword(currentPassword);
|
||||||
|
}}
|
||||||
|
placeholder={'Current Password'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { FunctionalComponent } from 'preact';
|
||||||
|
|
||||||
|
export const ChangeEmailSuccess: FunctionalComponent = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={'sk-label sk-bold info'}>Your email has been successfully changed.</div>
|
||||||
|
<p className={'sk-p'}>
|
||||||
|
Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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<Props> = ({
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<ModalDialog>
|
||||||
|
<ModalDialogLabel closeDialog={handleDialogClose}>
|
||||||
|
Change Email
|
||||||
|
</ModalDialogLabel>
|
||||||
|
<ModalDialogDescription>
|
||||||
|
{currentStep === Steps.InitialStep && (
|
||||||
|
<ChangeEmailForm
|
||||||
|
setNewEmail={setNewEmail}
|
||||||
|
setCurrentPassword={setCurrentPassword}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
|
||||||
|
</ModalDialogDescription>
|
||||||
|
<ModalDialogButtons>
|
||||||
|
{currentStep === Steps.InitialStep && (
|
||||||
|
<Button
|
||||||
|
className='min-w-20'
|
||||||
|
type='normal'
|
||||||
|
label='Cancel'
|
||||||
|
onClick={handleDialogClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className='min-w-20'
|
||||||
|
type='primary'
|
||||||
|
label={submitButtonTitle}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
/>
|
||||||
|
</ModalDialogButtons>
|
||||||
|
</ModalDialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user