feat: Update "Change Email" and "Change Password" modal designs (#714)

* feat: Add new utility classes

* feat: Update "Change Email" modal design

* feat: Use .sk-input.contrast className to mimic password wizard inputs

* feat: Add explicit labels to password wizard inputs

* feat: Make button sizing consistent

* refactor: Remove unused dependencies

* refactor: Remove unused component
This commit is contained in:
Aman Harwara
2021-11-01 19:49:06 +05:30
committed by GitHub
parent 5da51048bf
commit 4f56c453cb
8 changed files with 86 additions and 308 deletions

View File

@@ -5,7 +5,6 @@ import {
AlertDialogLabel,
} from '@node_modules/@reach/alert-dialog';
import { useRef } from '@node_modules/preact/hooks';
import { IconButton } from '@/components/IconButton';
export const ModalDialog: FunctionComponent = ({ children }) => {
const ldRef = useRef<HTMLButtonElement>(null);
@@ -19,7 +18,7 @@ export const ModalDialog: FunctionComponent = ({ children }) => {
<div tabIndex={-1} className="sn-component">
<div
tabIndex={0}
className="w-160 bg-default rounded shadow-overlay focus:padded-ring-info"
className="sk-panel w-160 bg-default rounded shadow-overlay focus:padded-ring-info"
>
{children}
</div>
@@ -30,17 +29,20 @@ export const ModalDialog: FunctionComponent = ({ children }) => {
export const ModalDialogLabel: FunctionComponent<{
closeDialog: () => void;
}> = ({ children, closeDialog }) => (
<AlertDialogLabel className="">
<div className="px-4 py-4 flex flex-row items-center">
<div className="flex-grow color-text text-lg font-bold">{children}</div>
<IconButton
focusable={true}
title="Close"
className="color-neutral h-5 w-5"
icon="close"
onClick={() => closeDialog()}
/>
className?: string;
}> = ({ children, closeDialog, className }) => (
<AlertDialogLabel className={className}>
<div className="w-full flex flex-row justify-between items-center">
<div className="flex-grow color-text text-base font-medium">
{children}
</div>
<div
tabIndex={0}
className="font-bold color-info cursor-pointer"
onClick={closeDialog}
>
Close
</div>
</div>
<hr className="h-1px bg-border no-border m-0" />
</AlertDialogLabel>
@@ -55,17 +57,20 @@ export const ModalDialogDescription: FunctionComponent<{ className?: string }> =
</AlertDialogDescription>
);
export const ModalDialogButtons: FunctionComponent = ({ children }) => (
export const ModalDialogButtons: FunctionComponent<{ className?: string }> = ({
children,
className,
}) => (
<>
<hr className="h-1px bg-border no-border m-0" />
<div className="px-4 py-4 flex flex-row justify-end items-center">
<div className={`px-4 py-4 flex flex-row items-center ${className}`}>
{children != undefined && Array.isArray(children)
? children.map((child, idx, arr) => (
<>
{child}
{idx < arr.length - 1 ? <div className="min-w-3" /> : undefined}
</>
))
<>
{child}
{idx < arr.length - 1 ? <div className="min-w-3" /> : undefined}
</>
))
: children}
</div>
</>

View File

@@ -1,37 +1,47 @@
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>
}
setNewEmail: StateUpdater<string>;
setCurrentPassword: StateUpdater<string>;
};
const labelClassName = `block mb-1`;
const inputClassName = 'sk-input contrast';
export const ChangeEmailForm: FunctionalComponent<Props> = ({
setNewEmail,
setCurrentPassword
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>
</>
)
<div className="w-full flex flex-col">
<div className="mt-2 mb-3">
<label className={labelClassName} htmlFor="change-email-email-input">
New Email:
</label>
<input
id="change-email-email-input"
className={inputClassName}
type="email"
onChange={({ target }) => {
setNewEmail((target as HTMLInputElement).value);
}}
/>
</div>
<div className="mb-2">
<label className={labelClassName} htmlFor="change-email-password-input">
Current Password:
</label>
<input
id="change-email-password-input"
className={inputClassName}
type="password"
onChange={({ target }) => {
setCurrentPassword((target as HTMLInputElement).value);
}}
/>
</div>
</div>
);
};

View File

@@ -154,10 +154,13 @@ export const ChangeEmail: FunctionalComponent<Props> = ({
return (
<div>
<ModalDialog>
<ModalDialogLabel closeDialog={handleDialogClose}>
<ModalDialogLabel
closeDialog={handleDialogClose}
className="sk-panel-header px-4.5"
>
Change Email
</ModalDialogLabel>
<ModalDialogDescription>
<ModalDialogDescription className="px-4.5">
{currentStep === Steps.InitialStep && (
<ChangeEmailForm
setNewEmail={setNewEmail}
@@ -166,15 +169,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({
)}
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
</ModalDialogDescription>
<ModalDialogButtons>
{currentStep === Steps.InitialStep && (
<Button
className="min-w-20"
type="normal"
label="Cancel"
onClick={handleDialogClose}
/>
)}
<ModalDialogButtons className="px-4.5">
<Button
className="min-w-20"
type="primary"

View File

@@ -1,45 +0,0 @@
import { DecoratedInput } from '@/components/DecoratedInput';
import { StateUpdater } from 'preact/hooks';
import { FunctionalComponent } from 'preact';
import { HtmlInputTypes } from '@/enums';
type Props = {
setCurrentPassword: StateUpdater<string>
setNewPassword: StateUpdater<string>
setNewPasswordConfirmation: StateUpdater<string>
}
export const ChangePasswordForm: FunctionalComponent<Props> = ({
setCurrentPassword,
setNewPassword,
setNewPasswordConfirmation
}) => {
return (
(
<>
<div className={'mt-2 mb-3'}>
<DecoratedInput
type={HtmlInputTypes.Password}
onChange={(currentPassword) => {
setCurrentPassword(currentPassword);
}}
placeholder={'Current Password'}
/>
</div>
<div className={'mt-2 mb-3'}>
<DecoratedInput
type={HtmlInputTypes.Password}
placeholder={'New Password'}
onChange={newPassword => setNewPassword(newPassword)}
/>
</div>
<div className={'mt-2 mb-3'}>
<DecoratedInput
type={HtmlInputTypes.Password}
placeholder={'Confirm New Password'}
onChange={newPasswordConfirmation => setNewPasswordConfirmation(newPasswordConfirmation)}
/>
</div>
</>
)
);
};

View File

@@ -1,12 +0,0 @@
import { FunctionalComponent } from 'preact';
export const ChangePasswordSuccess: FunctionalComponent = () => {
return (
<>
<div className={'sk-label sk-bold info'}>Your password 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>
</>
);
};

View File

@@ -1,189 +0,0 @@
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 { ChangePasswordSuccess } from '@/preferences/panes/account/changePassword/ChangePasswordSuccess';
import { ChangePasswordForm } from '@/preferences/panes/account/changePassword/ChangePasswordForm';
import { useBeforeUnload } from '@/hooks/useBeforeUnload';
enum SubmitButtonTitles {
Default = 'Continue',
GeneratingKeys = 'Generating Keys...',
Finish = 'Finish',
}
enum Steps {
InitialStep,
FinishStep,
}
type Props = {
onCloseDialog: () => void;
application: WebApplication;
};
export const ChangePassword: FunctionalComponent<Props> = ({
onCloseDialog,
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 [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;
}
if (!newPassword || newPassword.length === 0) {
applicationAlertService.alert('Please enter a new password.');
return false;
}
if (newPassword !== newPasswordConfirmation) {
applicationAlertService.alert(
'Your new password does not match its confirmation.'
);
return false;
}
if (!application.getUser()?.email) {
applicationAlertService.alert(
"We don't have your email stored. Please sign out then sign back in to fix this issue."
);
return false;
}
/** Validate current password */
const success = await application.validateAccountPassword(currentPassword);
if (!success) {
applicationAlertService.alert(
'The current password you entered is not correct. Please try again.'
);
}
return success;
};
const resetProgressState = () => {
setSubmitButtonTitle(SubmitButtonTitles.Default);
setIsContinuing(false);
};
const processPasswordChange = async () => {
await application.downloadBackup();
setLockContinue(true);
const response = await application.changePassword(
currentPassword,
newPassword
);
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();
if (!valid) {
resetProgressState();
return;
}
const success = await processPasswordChange();
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 Password
</ModalDialogLabel>
<ModalDialogDescription>
{currentStep === Steps.InitialStep && (
<ChangePasswordForm
setCurrentPassword={setCurrentPassword}
setNewPassword={setNewPassword}
setNewPasswordConfirmation={setNewPasswordConfirmation}
/>
)}
{currentStep === Steps.FinishStep && <ChangePasswordSuccess />}
</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>
);
};

View File

@@ -427,6 +427,12 @@
padding-right: 0;
}
.sn-component .px-4\.5,
.sn-component .sk-panel .px-4\.5 {
padding-left: 1.375rem;
padding-right: 1.375rem;
}
.px-9 {
padding-left: 2.25rem;
padding-right: 2.25rem;
@@ -648,3 +654,7 @@
.list-style-none {
list-style-type: none;
}
.rounded-0\.5 {
border-radius: 0.125rem;
}

View File

@@ -12,24 +12,28 @@
.sk-panel-row
.sk-panel-column.stretch
form.sk-panel-form
label.block.mb-1(for='password-wiz-current-password') Current Password:
input.sk-input.contrast(
id='password-wiz-current-password'
ng-model='ctrl.state.formData.currentPassword',
placeholder='Current Password',
should-focus='true',
sn-autofocus='true',
type='password'
)
.sk-panel-row
label.block.mb-1(for='password-wiz-new-password') New Password:
input.sk-input.contrast(
id='password-wiz-new-password',
ng-if='ctrl.props.changePassword',
ng-model='ctrl.state.formData.newPassword',
placeholder='New Password',
type='password'
)
.sk-panel-row
label.block.mb-1(for='password-wiz-confirm-new-password') Confirm New Password:
input.sk-input.contrast(
id='password-wiz-confirm-new-password',
ng-if='ctrl.props.changePassword',
ng-model='ctrl.state.formData.newPasswordConfirmation',
placeholder='Confirm New Password',
type='password'
)
.sk-panel-section(ng-if='ctrl.state.step == 2')
@@ -41,7 +45,7 @@
| Please ensure you are running the latest version of Standard Notes
| on all platforms to ensure maximum compatibility.
.sk-panel-footer
button.sn-button.small.info(
button.sn-button.min-w-20.info(
ng-click='ctrl.nextStep()',
ng-disabled='ctrl.state.lockContinue'
) {{ctrl.state.continueTitle}}