* 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 <antsgar@gmail.com> * Update app/assets/javascripts/components/InputWithIcon.tsx Co-authored-by: Antonella Sgarlatta <antsgar@gmail.com> * refactor: Use server state from AccountMenuState * Update app/assets/javascripts/components/AccountMenu/CreateAccount.tsx Co-authored-by: Antonella Sgarlatta <antsgar@gmail.com> * Update app/assets/javascripts/components/AccountMenu/ConfirmPassword.tsx Co-authored-by: Antonella Sgarlatta <antsgar@gmail.com> * 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 <antsgar@gmail.com> Co-authored-by: Antonella Sgarlatta <antonella@standardnotes.org>
245 lines
5.9 KiB
TypeScript
245 lines
5.9 KiB
TypeScript
import { WebApplication } from '@/ui_models/application';
|
|
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';
|
|
enum Steps {
|
|
PasswordStep = 1,
|
|
FinishStep = 2,
|
|
}
|
|
|
|
type FormData = {
|
|
currentPassword?: string;
|
|
newPassword?: string;
|
|
newPasswordConfirmation?: string;
|
|
status?: string;
|
|
};
|
|
|
|
type State = {
|
|
lockContinue: boolean;
|
|
formData: FormData;
|
|
continueTitle: string;
|
|
step: Steps;
|
|
title: string;
|
|
showSpinner: boolean;
|
|
processing: boolean;
|
|
};
|
|
|
|
type Props = {
|
|
type: PasswordWizardType;
|
|
changePassword: boolean;
|
|
securityUpdate: boolean;
|
|
};
|
|
|
|
class PasswordWizardCtrl
|
|
extends PureViewCtrl<Props, State>
|
|
implements PasswordWizardScope
|
|
{
|
|
$element: JQLite;
|
|
application!: WebApplication;
|
|
type!: PasswordWizardType;
|
|
isContinuing = false;
|
|
|
|
/* @ngInject */
|
|
constructor($element: JQLite, $timeout: ng.ITimeoutService) {
|
|
super($timeout);
|
|
this.$element = $element;
|
|
this.registerWindowUnloadStopper();
|
|
}
|
|
|
|
$onInit() {
|
|
super.$onInit();
|
|
this.initProps({
|
|
type: this.type,
|
|
changePassword: this.type === PasswordWizardType.ChangePassword,
|
|
securityUpdate: this.type === PasswordWizardType.AccountUpgrade,
|
|
});
|
|
this.setState({
|
|
formData: {},
|
|
continueTitle: DEFAULT_CONTINUE_TITLE,
|
|
step: Steps.PasswordStep,
|
|
title: this.props.changePassword ? 'Change Password' : 'Account Update',
|
|
});
|
|
}
|
|
|
|
$onDestroy() {
|
|
super.$onDestroy();
|
|
window.onbeforeunload = null;
|
|
}
|
|
|
|
/** Confirms with user before closing tab */
|
|
registerWindowUnloadStopper() {
|
|
window.onbeforeunload = () => {
|
|
return true;
|
|
};
|
|
}
|
|
|
|
resetContinueState() {
|
|
this.setState({
|
|
showSpinner: false,
|
|
continueTitle: DEFAULT_CONTINUE_TITLE,
|
|
});
|
|
this.isContinuing = false;
|
|
}
|
|
|
|
async nextStep() {
|
|
if (this.state.lockContinue || this.isContinuing) {
|
|
return;
|
|
}
|
|
if (this.state.step === Steps.FinishStep) {
|
|
this.dismiss();
|
|
return;
|
|
}
|
|
|
|
this.isContinuing = true;
|
|
await this.setState({
|
|
showSpinner: true,
|
|
continueTitle: 'Generating Keys...',
|
|
});
|
|
const valid = await this.validateCurrentPassword();
|
|
if (!valid) {
|
|
this.resetContinueState();
|
|
return;
|
|
}
|
|
const success = await this.processPasswordChange();
|
|
if (!success) {
|
|
this.resetContinueState();
|
|
return;
|
|
}
|
|
this.isContinuing = false;
|
|
this.setState({
|
|
showSpinner: false,
|
|
continueTitle: 'Finish',
|
|
step: Steps.FinishStep,
|
|
});
|
|
}
|
|
|
|
async setFormDataState(formData: Partial<FormData>) {
|
|
return this.setState({
|
|
formData: {
|
|
...this.state.formData,
|
|
...formData,
|
|
},
|
|
});
|
|
}
|
|
|
|
async validateCurrentPassword() {
|
|
const currentPassword = this.state.formData.currentPassword;
|
|
const newPass = this.props.securityUpdate
|
|
? currentPassword
|
|
: this.state.formData.newPassword;
|
|
if (!currentPassword || currentPassword.length === 0) {
|
|
this.application.alertService!.alert(
|
|
'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.');
|
|
return false;
|
|
}
|
|
if (newPass !== this.state.formData.newPasswordConfirmation) {
|
|
this.application.alertService!.alert(
|
|
'Your new password does not match its confirmation.'
|
|
);
|
|
this.setFormDataState({
|
|
status: undefined,
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
if (!this.application.getUser()?.email) {
|
|
this.application.alertService!.alert(
|
|
"We don't have your email stored. Please sign out then log back in to fix this issue."
|
|
);
|
|
this.setFormDataState({
|
|
status: undefined,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
/** Validate current password */
|
|
const success = await this.application.validateAccountPassword(
|
|
this.state.formData.currentPassword!
|
|
);
|
|
if (!success) {
|
|
this.application.alertService!.alert(
|
|
'The current password you entered is not correct. Please try again.'
|
|
);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
async processPasswordChange() {
|
|
await this.application.downloadBackup();
|
|
await this.setState({
|
|
lockContinue: true,
|
|
processing: true,
|
|
});
|
|
await this.setFormDataState({
|
|
status: 'Processing encryption keys…',
|
|
});
|
|
const newPassword = this.props.securityUpdate
|
|
? this.state.formData.currentPassword
|
|
: this.state.formData.newPassword;
|
|
const response = await this.application.changePassword(
|
|
this.state.formData.currentPassword!,
|
|
newPassword!
|
|
);
|
|
const success = !response.error;
|
|
await this.setState({
|
|
processing: false,
|
|
lockContinue: false,
|
|
});
|
|
if (!success) {
|
|
this.setFormDataState({
|
|
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.',
|
|
},
|
|
});
|
|
}
|
|
return success;
|
|
}
|
|
|
|
dismiss() {
|
|
if (this.state.lockContinue) {
|
|
this.application.alertService!.alert(
|
|
'Cannot close window until pending tasks are complete.'
|
|
);
|
|
} else {
|
|
const elem = this.$element;
|
|
const scope = elem.scope();
|
|
scope.$destroy();
|
|
elem.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PasswordWizard extends WebDirective {
|
|
constructor() {
|
|
super();
|
|
this.restrict = 'E';
|
|
this.template = template;
|
|
this.controller = PasswordWizardCtrl;
|
|
this.controllerAs = 'ctrl';
|
|
this.bindToController = true;
|
|
this.scope = {
|
|
type: '=',
|
|
application: '=',
|
|
};
|
|
}
|
|
}
|