Commit 939c4317 authored by Emily Jensen's avatar Emily Jensen

Merge from dev

parents fd8f341b 60c278b7
......@@ -88,7 +88,7 @@
"raven-js": "^3.27.0",
"reflect-metadata": "~0.1.8",
"rxjs": "^6.3.3",
"rxjs-tslint": "^0.1.5",
"rxjs-tslint": "0.1.5",
"stream": "^0.0.2",
"susy": "^2.2.12",
"text-encoding-utf-8": "^1.0.2",
......@@ -139,4 +139,4 @@
"version": "5.0.0"
}
}
}
}
\ No newline at end of file
......@@ -5,11 +5,6 @@
</h2>
<ul class="account__list">
<li class="account__list-item">
<a [routerLink]=" ['./change-backup-code'] " class="text-link text-link--caret text-link--large">
<h3 class="account__item-heading">Change Backup Code</h3>
</a>
</li>
<li class="account__list-item">
<a [routerLink]=" ['./change-password'] " class="text-link text-link--caret text-link--large">
......@@ -18,14 +13,8 @@
</li>
<li class="account__list-item">
<a [routerLink]=" ['../import'] " class="text-link text-link--caret text-link--large">
<h3 class="account__item-heading">Import Passwords</h3>
</a>
</li>
<li class="account__list-item">
<a [routerLink]=" ['../export'] " class="text-link text-link--caret text-link--large">
<h3 class="account__item-heading">Export Passwords</h3>
<a [routerLink]=" ['./change-backup-code'] " class="text-link text-link--caret text-link--large">
<h3 class="account__item-heading">Change Backup Code</h3>
</a>
</li>
......@@ -68,5 +57,24 @@
</a>
</li>
</ul>
<br/><br/>
<h2 class="heading-main">
Data Management
</h2>
<ul class="account__list">
<li class="account__list-item">
<a [routerLink]=" ['../import'] " class="text-link text-link--caret text-link--large">
<h3 class="account__item-heading">Import Passwords</h3>
</a>
</li>
<li class="account__list-item">
<a [routerLink]=" ['../export'] " class="text-link text-link--caret text-link--large">
<h3 class="account__item-heading">Export Passwords</h3>
</a>
</li>
</ul>
</div>
\ No newline at end of file
......@@ -246,7 +246,6 @@ label.import__toggle-preview.form-field__checkbox {
margin: 10px 0;
}
}
&__error-list {
position: relative;
margin-top: 0;
......
......@@ -46,6 +46,7 @@ import { RegisterContainer } from "./register/register.container";
import { RegisterComponent } from "./register/register.component";
import { LoginComponent } from "./login/login.component";
import { LoginContainer } from "./login/login.container";
import { ForgotLearnMoreComponent } from "./change-password/forgot-learn-more/forgot-learn-more.component";
import { ConfirmEmailGuard } from "./confirm-email/confirm-email.guard";
import { UserService } from "./user";
......@@ -75,7 +76,8 @@ export const COMPONENTS = [
ManageBackupCodeComponent,
ManageBackupCodeContainer,
MarketingFrameComponent,
ServerSelectComponent
ServerSelectComponent,
ForgotLearnMoreComponent
];
export const SERVICES = [BackupCodePdfService, UserService, ConfirmEmailGuard];
......
......@@ -12,9 +12,7 @@
<div class="form-field form-field--large">
<div class="form-field__wrapper-for-link">
<label for="oldPassword" class="form-field__label">Current Password</label>
<a class="sign-out text-link text-link--caret form-field__label-link" href="javascript:void(0)" (click)="onForgotPassword.emit()">
<span class="u-visible--all-but-small">Forgot? </span>Log&nbsp;out&nbsp;to&nbsp;reset
</a>
<a class="sign-out text-link text-link--caret form-field__label-link" href="javascript:void(0)" (click)="onForgotPassword.emit()"><span class="u-visible--all-but-small">Forgot? </span>Log&nbsp;out&nbsp;to&nbsp;reset</a>
</div>
<input type="password" [ngrxFormControlState]="form.controls.oldPassword" class="form-field__input form-field__input--one-action"
[class.form-field__input--invalid]="form.errors._oldPassword && form.isSubmitted" name="oldPassword"
......
<div class="account l-container-narrow u-margin-tb-50">
<a class="text-link text-link--left-caret heading-back-link" [routerLink]="['/account']">Account Management</a>
<h2 class="heading-main heading-main--pad-bottom">Forgot Your Password?</h2>
<div class="register-step__feedback">
<div class="text--large text--less-bottom-margin">
If you forgot your password, you will need to recover your account.
</div>
<div class="text--large">
Do you have your backup code? Your backup code is contained in a PDF that you printed or saved when you created your Passit account.
</div>
</div>
<h3 class="heading-small">I have my backup code</h3>
<ul class="text--large text--less-bottom-margin">
<li><a class="text-link text-link--caret text-link--large" href="javascript:void(0)" (click)="onLogOut()">Log&nbsp;out</a></li>
<li>Click the “Recover your account” link on the login&nbsp;page</li>
<li>Follow the steps to change your password</li>
</ul>
<h3 class="heading-small">I don’t have my backup code</h3>
<div class="text--large text--less-bottom-margin">
If you didn’t save your backup code, then you will need to transfer your passwords to a new account. Since we require your password to delete your account, you will not be able to do so. Therefore, you will need to use a different email address for your new account.
</div>
<ul class="text--large text--less-bottom-margin">
<li><a class="text-link text-link--caret text-link--large" href="javascript:void(0)" [routerLink]="['/export']">Export your passwords</a></li>
<li>Register a new account</li>
<li><a class="text-link text-link--caret text-link--large" href="javascript:void(0)" [routerLink]="['/import']">Import your passwords</a></li>
</ul>
\ No newline at end of file
<Button text="forgot-learn-more works!" class="btn btn-primary"></Button>
\ No newline at end of file
import { Component } from "@angular/core";
import { Store } from "@ngrx/store";
import { IState } from "../../../app.reducers";
import { LogoutAction } from "../.././account.actions";
@Component({
selector: "forgot-learn-more",
templateUrl: "./forgot-learn-more.component.html",
styleUrls: [
"./forgot-learn-more.component.css",
"../../account.component.scss",
]
})
export class ForgotLearnMoreComponent {
constructor(private store: Store<IState>) {}
onLogOut() {
if (
window.confirm("Please confirm that you have your backup code ready before logging out.")
) {
this.store.dispatch(new LogoutAction());
}
}
}
......@@ -12,7 +12,12 @@
<app-non-field-messages *ngIf="errorMessage" [messages]="[errorMessage]"></app-non-field-messages>
<div class="form-field form-field--large">
<label [for]="form.controls.email.id" class="form-field__label">Email</label>
<app-form-label
[for]="form.controls.email.id"
[isLarge]="true"
>
Email
</app-form-label>
<input
type="email"
autofocus
......@@ -32,13 +37,20 @@
<div class="form-field form-field--large">
<div class="form-field__wrapper-for-link">
<label [for]="form.controls.password.id" class="form-field__label">Password</label>
<app-form-label
[for]="form.controls.password.id"
[isLarge]="true"
>
Password
</app-form-label>
<aside-link
text="Forgot? Recover your account"
tabindex="8"
[link]="['/reset-password']"
></aside-link>
</div>
>
<span class="u-visible--all-but-small">Forgot? </span>Recover&nbsp;your&nbsp;account
</aside-link>
</div>
<input
type="password"
[ngrxFormControlState]="form.controls.password"
......
......@@ -62,10 +62,11 @@
></Label>
<aside-link
text="Forgot? Recover your account"
tabindex="8"
[link]="['/reset-password']"
></aside-link>
>
Forgot? Recover your account
</aside-link>
<progress-indicator
[inProgress]="hasLoginStarted"
......
......@@ -20,7 +20,7 @@ export class SubmitFormSuccess implements Action {
export class SubmitFormFailure implements Action {
readonly type = ManageBackupCodeActionTypes.SUBMIT_FORM_FAILURE;
constructor(payload: any) {}
constructor(public payload: string[]) {}
}
export class ResetForm implements Action {
......
<div class="account l-container-narrow u-margin-tb-50">
<a class="text-link text-link--left-caret heading-back-link" [routerLink]="['/account']">
Account Management
</a>
<a class="text-link text-link--left-caret heading-back-link" [routerLink]="['/account']">Account Management</a>
<h2 class="heading-main heading-main--pad-bottom">Change Backup Code</h2>
<h2 class="heading-main u-margin-b-20">Change Backup Code</h2>
<div class="register-step__feedback">
<span class="register-step__feedback__static-element">
Replace your old backup code with a new one.<br />Not sure what this is?
<a [routerLink]=" ['/account/forgot-password'] " class="text-link text-link--caret text-link--large" id="btn-signin">Learn&nbsp;more</a>
</span>
</div>
<form novalidate [ngrxFormState]="form" (submit)="onSubmit()">
<app-non-field-messages [messages]="nonFieldErrors"></app-non-field-messages>
<div class="form-field form-field--large">
<label for="oldPassword" class="form-field__label">Current Password</label>
<input
#passwordInput
type="password"
[ngrxFormControlState]="form.controls.oldPassword"
class="form-field__input form-field__input--one-action"
[class.form-field__input--invalid]="form.errors._oldPassword && form.isSubmitted"
name="oldPassword"
autocomplete="off"
/>
</div>
<app-form-label
[isLarge]="true"
[isComplete]="hasFinished"
[class.u-margin-b-20]="hasFinished">
Verify Password
</app-form-label>
<ul *ngIf="form.errors._oldPassword && form.isSubmitted" class="form-field__error-list">
<li *ngIf="form.errors._oldPassword.required" class="form-field__error">
You must enter your current password
</li>
<li *ngIf="form.errors._oldPassword.minLength" class="form-field__error">
Password must be at least
{{ form.errors._oldPassword.minLength.minLength }} characters long.
</li>
</ul>
<div
*ngIf="!hasFinished"
class="form-field__input-with-actions">
<input
#passwordInput
name="oldPassword"
[type]="form.value.showConfirm ? 'password' : 'text'"
[ngrxFormControlState]="form.controls.oldPassword"
class="form-field__input form-field__input--one-action"
[class.form-field__input--invalid]="form.errors._oldPassword && form.isSubmitted"
[disabled]="hasFinished"
autocomplete="off"
/>
<div class="form-field__actions">
<button type="button" tabindex="-1" (click)="toggleShowConfirm.emit()" class="form-field__action">
<div class="button__icon button__icon--no-text" [inlineSVG]="form.value.showConfirm ? '../assets/svg/eye.svg' : '../assets/svg/eye-slash.svg'"></div>
</button>
</div>
</div>
</div>
<div class="change-password__actions">
<button type="submit" class="button button--primary">Submit</button>
<div *ngIf="!hasFinished">
<ul *ngIf="form.errors._oldPassword && form.isSubmitted" class="form-field__error-list">
<li *ngIf="form.errors._oldPassword.required" class="form-field__error">
You must enter your current password
</li>
<li *ngIf="form.errors._oldPassword.minLength" class="form-field__error">
Password must be at least
{{ form.errors._oldPassword.minLength.minLength }} characters long.
</li>
</ul>
<div class="manage-backup-code__actions">
<button type="submit" class="button button--primary" [disabled]="hasStarted">
Next
</button>
<progress-indicator [inProgress]="hasStarted" inProgressText="Verifying"></progress-indicator>
</div>
</div>
<progress-indicator [inProgress]="hasStarted" inProgressText="Saving" [completed]="hasFinished" completedText="Saved"></progress-indicator>
<app-form-label
[isLarge]="true"
[isInactive]="!hasFinished"
[isComplete]="false">
Download PDF with New Backup Code
</app-form-label>
<div *ngIf="hasFinished" class="register-step__feedback">
<p>
Your backup code is the <strong>only</strong> way to recover your
account if you forget&nbsp;your&nbsp;password.
</p>
<p>
Download it, print it out, and save it someplace safe. Destroy your old
backup&nbsp;code&nbsp;file.
</p>
</div>
</form>
<div *ngIf="hasFinished">
<button (click)="downloadPDF()" class="button button--primary">
Download PDF
</button>
</div>
<div *ngIf="hasFinished">
<button (click)="downloadPDF()" class="button button--primary u-margin-b-15">
Download
</button>
<p class="manage-backup-code__note">
Your previous code is now invalid. If you navigate away from this page,
you will not be able to use&nbsp;this&nbsp;code&nbsp;either.
</p>
</div>
</div>
@import '../../../styles/colors';
.manage-backup-code {
&__actions {
position: relative;
margin-bottom: 20px;
}
&__note {
margin-top: 5px;
color: $color-gothic;
line-height: 1.2857em;
font-size: 14px;
}
}
......@@ -14,7 +14,11 @@ import { IForm } from "./manage-backup-code.reducer";
@Component({
selector: "app-manage-backup-code",
templateUrl: "./manage-backup-code.component.html",
styleUrls: ["./manage-backup-code.component.scss"],
styleUrls: [
"./manage-backup-code.component.scss",
"../account.component.scss",
"../../../styles/_utility.scss"
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ManageBackupCodeComponent implements OnDestroy {
......@@ -37,6 +41,8 @@ export class ManageBackupCodeComponent implements OnDestroy {
getBackupPDF = new EventEmitter();
@Output()
reset = new EventEmitter();
@Output()
toggleShowConfirm = new EventEmitter();
@ViewChild("passwordInput")
passwordInput: ElementRef;
......
......@@ -4,6 +4,8 @@ import { Store, select } from "@ngrx/store";
import { SubmitForm, ResetForm } from "./manage-backup-code.actions";
import { selectManageBackupCodeNewBackupCode } from "../account.reducer";
import { BackupCodePdfService } from "../backup-code-pdf.service";
import { SetValueAction } from "ngrx-forms";
import { FORM_ID } from "./manage-backup-code.reducer";
@Component({
template: `
......@@ -11,11 +13,12 @@ import { BackupCodePdfService } from "../backup-code-pdf.service";
[form]="form$ | async"
[hasStarted]="hasStarted$ | async"
[hasFinished]="hasFinished$ | async"
[errorMessage]="errorMessage$ | async"
[nonFieldErrors]="errorMessage$ | async"
[backupCode]="backupCode"
(newBackupCode)="newBackupCode()"
(getBackupPDF)="getBackupPDF()"
(reset)="reset()"
(toggleShowConfirm)="toggleConfirm()"
></app-manage-backup-code>
`,
changeDetection: ChangeDetectionStrategy.OnPush
......@@ -26,20 +29,22 @@ export class ManageBackupCodeContainer {
hasFinished$ = this.store.pipe(
select(fromAccount.manageBackupCodeHasFinished)
);
errorMessage$ = this.store.pipe(
select(fromAccount.manageBackupCodeErrorMessage)
);
errorMessage$ = this.store.select(fromAccount.manageBackupCodeErrorMessage);
backupCode: string;
showConfirm = true;
constructor(
private store: Store<any>,
private backupCodeToPdf: BackupCodePdfService
) {
this.store
.pipe(select(selectManageBackupCodeNewBackupCode))
.subscribe(backupCode =>
backupCode ? (this.backupCode = backupCode) : null
.select(selectManageBackupCodeNewBackupCode)
.subscribe(
backupCode => (backupCode ? (this.backupCode = backupCode) : null)
);
this.form$.subscribe(
form => (this.showConfirm = form.controls.showConfirm.value)
);
}
getBackupPDF() {
......@@ -51,4 +56,9 @@ export class ManageBackupCodeContainer {
reset() {
this.store.dispatch(new ResetForm());
}
toggleConfirm() {
this.store.dispatch(
new SetValueAction(FORM_ID + ".showConfirm", !this.showConfirm)
);
}
}
......@@ -13,6 +13,7 @@ import {
import * as fromAccount from "../account.reducer";
import { UserService } from "../user";
@Injectable()
export class ManageBackupCodeEffects {
constructor(
......@@ -29,7 +30,13 @@ export class ManageBackupCodeEffects {
exhaustMap(currentPassword =>
this.userService.makeAndSetBackupCode(currentPassword).pipe(
map(backupCode => new SubmitFormSuccess(backupCode)),
catchError(err => of(new SubmitFormFailure(err)))
catchError((err) => {
if (err.status === 400) {
return of(new SubmitFormFailure(["Incorrect password."]));
} else {
return of(new SubmitFormFailure(["Unexpected error."]));
}
})
)
)
);
......
......@@ -15,11 +15,12 @@ export const FORM_ID = "Manage Backup Code Form";
export interface IForm {
oldPassword: string;
showConfirm: boolean;
}
export interface IState extends IBaseFormState {
form: FormGroupState<IForm>;
errorMessage: string | null;
errorMessage: string[] | null;
newBackupCode: string | null;
}
......@@ -31,7 +32,8 @@ export const newBackupCode = (state: IState) => state.newBackupCode;
export const initialFormState = validateAndUpdateFormState(
createFormGroupState<IForm>(FORM_ID, {
oldPassword: ""
oldPassword: "",
showConfirm: true
})
);
......@@ -76,7 +78,7 @@ export function reducer(
case ManageBackupCodeActionTypes.SUBMIT_FORM_FAILURE:
return {
...state,
errorMessage: "An error occured",
errorMessage: action.payload,
hasFinished: false,
hasStarted: false,
newBackupCode: null
......
......@@ -24,6 +24,7 @@ import { ManageBackupCodeContainer } from "./account/manage-backup-code/manage-b
import { ResetPasswordContainer } from "./account/reset-password/reset-password.container";
import { ResetPasswordVerifyContainer } from "./account/reset-password/reset-password-verify/reset-password-verify.container";
import { SetPasswordContainer } from "./account/reset-password/set-password/set-password.container";
import { ForgotLearnMoreComponent } from "./account/change-password/forgot-learn-more/forgot-learn-more.component";
/* tslint:disable:object-literal-sort-keys */
const appRoutes: Routes = [
......@@ -52,11 +53,20 @@ const appRoutes: Routes = [
title: "Change Account Password"
}
},
{
path: "account/forgot-password",
component: ForgotLearnMoreComponent,
canActivate: [LoggedInGuard],
data: {
title: "Forgot Your Password?"
}
},
{
path: "account/change-backup-code",
component: ManageBackupCodeContainer,
canActivate: [LoggedInGuard],
data: {
title: "Manage Backup Code"
title: "Change Backup Code"
}
},
{
......
......@@ -4,5 +4,5 @@
[routerLink]="link"
[tabindex]="tabindex"
>
<span class="u-visible--all-but-small">{{ text }}</span>
<ng-content></ng-content>
</a>
\ No newline at end of file
<Label
[text]="text"
[nsRouterLink]="link"
></Label>
\ No newline at end of file
>
<ng-content></ng-content>
</Label>
\ No newline at end of file
......@@ -10,7 +10,6 @@ import { Component, Input } from "@angular/core";
styleUrls: ["./aside-link.component.css"]
})
export class AsideLinkComponent {
@Input() text: string;
@Input() link: string[];
@Input() tabindex: string;
......
......@@ -14,6 +14,7 @@ import { Component, ChangeDetectionStrategy, Input } from "@angular/core";
<span
class="form-label__icon"
[class.form-label__icon--active]="isComplete"
[class.form-label__icon--inactive]="isInactive"
[inlineSVG]="'../assets/svg/check.svg'"
></span>
</ng-template>
......
......@@ -18,10 +18,12 @@ $transition-time: 0.2s;
font-weight $transition-time, letter-spacing $transition-time;
&--inactive {
display: inline-block;
color: $color-tiara;
font-size: 1.14583em;
font-weight: bold;
line-height: 1.77083em;
line-height: 1.27083em;
padding: 0.25em 0;
}
&--completed {
......@@ -41,5 +43,9 @@ $transition-time: 0.2s;
&--active {
opacity: 1;
}
&--inactive {
display: none;
}
}
}
......@@ -2,14 +2,13 @@ import { InlineSVGModule } from "ng-inline-svg";
import { HttpClientModule } from "@angular/common/http";
import { storiesOf, moduleMetadata } from "@storybook/angular";
import { action } from "@storybook/addon-actions";
import { boolean, select, text, withKnobs } from "@storybook/addon-knobs";
import { boolean, select, withKnobs } from "@storybook/addon-knobs";
import { StoreModule } from "@ngrx/store";
import { NgrxFormsModule } from "ngrx-forms";
import { BackupCodeComponent } from "../app/account/backup-code/backup-code.component";
import { ResetPasswordComponent } from "../app/account/reset-password/reset-password.component";
import { MarketingFrameComponent } from "../app/account/marketing-frame/marketing-frame.component";
import { FormLabelComponent } from "../app/shared/form-label/form-label.component";
import { LoginComponent } from "../app/account/login/login.component";
import { SharedModule } from "../app/shared";
import { ProgressIndicatorModule } from "../app/progress-indicator";
......@@ -83,21 +82,90 @@ storiesOf("Account", module)
defaultFormLabelState
);
// If I don't do something like this, I get an error about
// FormLabelComponent being declared but not used
if (FormLabelComponent.name === "") {
}
const defaultLabelTextState = "Password";
const labelTextControls = {
Short: defaultLabelTextState,
Medium: "Verify Email Address",
Long: "Download PDF with New Backup Code",
"Extra Long":
"Download PDF with New Backup Code So That You Can Forget Your Password As Long As You Don't Forget Where You Kept This",
Weird: "🍫 ⋆ 🍭 🎀 𝒫𝒶𝓈𝓈𝒾𝓉 𝓁𝑒𝓉'𝓈 𝑔𝑒𝓉 𝓈𝒶𝒻𝑒 𝓉💙𝒹𝒶𝓎 🎀 🍭 ⋆ 🍫"
};
// any type is, I think, because storybook is 4.0 but types aren't
const labelTextControl: any = select(
"Label Text",
labelTextControls,
defaultLabelTextState
);
return {
template: `
<h3>Basic label</h3>
<app-form-label
for="nothing"
[isLarge]="${boolean("Is Large", true)}"
[isInactive]="${formLabelControl === "inactive" ? true : false}"
[isComplete]="${formLabelControl === "complete" ? true : false}"
>
${text("Label text", "Password")}
${labelTextControl}
</app-form-label>
<h3>Testing out consecutive fields (these are wrapped in large form-field divs)</h3>
<div class="form-field form-field--large">
<app-form-label
for="nothing"
[isLarge]="true"
[isInactive]="${formLabelControl === "inactive" ? true : false}"
[isComplete]="${formLabelControl === "complete" ? true : false}"
>
${labelTextControl}
</app-form-label>
</div>
<div class="form-field form-field--large">
<app-form-label
for="nothing"
[isLarge]="true"
[isInactive]="${formLabelControl === "inactive" ? true : false}"
[isComplete]="${formLabelControl === "complete" ? true : false}"
>
${labelTextControl}
</app-form-label>
</div>
<div class="form-field form-field--large">
<app-form-label
for="nothing"
[isLarge]="true"
[isInactive]="${formLabelControl === "inactive" ? true : false}"
[isComplete]="${formLabelControl === "complete" ? true : false}"
>
${labelTextControl}
</app-form-label>
</div>
<h3>Testing out a field with input and link to the right</h3>
<div style="max-width: 500px; margin: 0 auto;">
<div class="form-field form-field--large">
<div class="form-field__wrapper-for-link">