Skip to content

Commit

Permalink
login form refactored to be more reusable
Browse files Browse the repository at this point in the history
  • Loading branch information
ZenSoftware committed Jan 4, 2021
1 parent 64a48eb commit accf396
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 162 deletions.
8 changes: 5 additions & 3 deletions libs/auth/src/lib/zen-auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ZenComponentsModule } from '@zen/components';

import { IsLoggedInDirective, NotRolesDirective, RolesDirective } from './directives';
import { ZenAuthRoutingModule } from './zen-auth-routing.module';
import { ZenLoginFormComponent } from './zen-login-form/zen-login-form.component';
import { ZenLoginLinkComponent } from './zen-login-link/zen-login-link.component';
import { ZenLoginPageComponent } from './zen-login-page/zen-login-page.component';
import { ZenLoginComponent } from './zen-login/zen-login.component';
Expand Down Expand Up @@ -39,16 +40,17 @@ import { ZenRegisterComponent } from './zen-register/zen-register.component';
IsLoggedInDirective,
NotRolesDirective,
RolesDirective,
ZenLoginComponent,
ZenLoginPageComponent,
ZenLoginFormComponent,
ZenLoginLinkComponent,
ZenLoginPageComponent,
ZenLoginComponent,
ZenPasswordChangeComponent,
ZenPasswordResetConfirmationPageComponent,
ZenPasswordResetConfirmationComponent,
ZenPasswordResetRequestPageComponent,
ZenPasswordResetRequestComponent,
ZenRegisterComponent,
ZenRegisterPageComponent,
ZenRegisterComponent,
],
exports: [
IsLoggedInDirective,
Expand Down
36 changes: 36 additions & 0 deletions libs/auth/src/lib/zen-login-form/zen-login-form.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<zen-loading *ngIf="loading"></zen-loading>

<mat-form-field hideRequiredMarker appearance="outline" class="w-100 mb-2">
<mat-label>Username</mat-label>
<input #usernameInput matInput placeholder="Username" [formControl]="username" hideRequiredMarker="true" maxlength="254" (keyup)="usernameNotFoundReset()">
<mat-error *ngIf="username.errors?.required">Required</mat-error>
<mat-error *ngIf="username.errors?.minlength && !username.errors?.required">Minimum length of {{username.errors?.minlength?.requiredLength}} characters</mat-error>
<mat-error *ngIf="username.errors?.maxlength">Maximum length of {{username.errors?.maxlength?.requiredLength}} characters</mat-error>
<mat-error *ngIf="username.errors?.includesSpace">Cannot contain spaces</mat-error>
<mat-error *ngIf="username.errors?.notFound">User not found</mat-error>
</mat-form-field>

<mat-form-field hideRequiredMarker appearance="outline" class="w-100 mb-2">
<mat-label>Password</mat-label>
<input #passwordInput matInput [type]="hidePassword ? 'password' : 'text'" [formControl]="password" (keyup)="incorrectPasswordReset()">
<span mat-icon-button matSuffix (click)="hidePassword = !hidePassword" tabindex="-1" [attr.aria-label]="'Hide password'" [attr.aria-pressed]="hidePassword">
<mat-icon>{{hidePassword ? 'visibility_off' : 'visibility'}}</mat-icon>
</span>
<mat-error *ngIf="password.errors?.required">Required</mat-error>
<mat-error *ngIf="password.errors?.incorrect">Incorrect password</mat-error>
</mat-form-field>

<div class="mb-4">
<mat-checkbox color="primary" [formControl]="rememberMe">Remember me</mat-checkbox>
</div>

<button type="submit" mat-raised-button color="primary" class="w-100 text-lg py-1" [disabled]="!form.valid">
<i class="fas fa-fw fa-sign-in-alt mr-1"></i>
Login
</button>

<mat-error @verticalAccordion *ngIf="generalError" class="text-center my-3">
There was a problem logging you in. If this continues, please contact us.
</mat-error>
</form>
132 changes: 132 additions & 0 deletions libs/auth/src/lib/zen-login-form/zen-login-form.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { GqlErrors } from '@zen/graphql';

import { verticalAccordion } from '../animations';
import { AuthService } from '../auth.service';
import { usernameValidator } from '../validators';

@Component({
selector: 'zen-login-form',
templateUrl: 'zen-login-form.component.html',
animations: [...verticalAccordion],
})
export class ZenLoginFormComponent {
@Output() loggedIn = new EventEmitter();
@ViewChild('usernameInput') usernameInput?: ElementRef;
@ViewChild('passwordInput') passwordInput?: ElementRef;

#loading = false;
#incorrectPassword = false;
#usernameNotFound = false;
hidePassword = true;
form: FormGroup;
generalError = false;

constructor(private formBuilder: FormBuilder, private auth: AuthService) {
this.form = this.formBuilder.group({
username: [
'',
[Validators.required, this.usernameValidator(), this.usernameNotFoundValidator()],
],
password: ['', [Validators.required, this.incorrectPasswordValidator()]],
rememberMe: [false],
});
}

get loading() {
return this.#loading;
}

set loading(value) {
this.#loading = value;
if (value) this.form.disable();
else this.form.enable();
}

get username() {
return this.form.get('username');
}

get password() {
return this.form.get('password');
}

get rememberMe() {
return this.form.get('rememberMe');
}

usernameNotFoundReset() {
this.#usernameNotFound = false;
this.username?.updateValueAndValidity();
}

usernameNotFoundValidator(): ValidatorFn {
return control => (this.#usernameNotFound ? { notFound: true } : null);
}

usernameValidator(): ValidatorFn {
return control => {
if (this.form) return usernameValidator(control);
return null;
};
}

incorrectPasswordReset() {
this.#incorrectPassword = false;
this.password?.updateValueAndValidity();
}

incorrectPasswordValidator(): ValidatorFn {
return control => (this.#incorrectPassword ? { incorrect: true } : null);
}

onSubmit() {
if (!this.loading) {
this.loading = true;
this.generalError = false;
this.form.disable();

this.auth
.login({
username: this.username?.value.trim(),
password: this.password?.value,
rememberMe: this.rememberMe?.value,
})
.subscribe({
next: () => {
this.loading = false;
this.loggedIn.emit();
},

error: errors => {
this.form.enable();
this.loading = false;
this.generalError = true;

const gqlErrors = new GqlErrors(errors);

if (gqlErrors.find(e => e.code === 'INCORRECT_PASSWORD')) {
this.generalError = false;
this.#incorrectPassword = true;
this.password?.markAsTouched();
this.password?.updateValueAndValidity();
this.passwordInput?.nativeElement.select();
}

if (gqlErrors.find(e => e.code === 'USER_NOT_FOUND')) {
this.generalError = false;
this.#usernameNotFound = true;
this.username?.markAsTouched();
this.username?.updateValueAndValidity();
this.usernameInput?.nativeElement.select();
}

if (gqlErrors.hasThrottleError) {
this.generalError = true;
}
},
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h1 class="m-0">
</div>
<div class="card-body">
<div class="mb-4">
<zen-login (loggedIn)="onLoggedIn()"></zen-login>
<zen-login-form (loggedIn)="onLoggedIn()"></zen-login-form>
</div>

<hr>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthSession } from '@zen/graphql';

import { AuthService } from '../auth.service';

Expand Down
45 changes: 10 additions & 35 deletions libs/auth/src/lib/zen-login/zen-login.component.html
Original file line number Diff line number Diff line change
@@ -1,36 +1,11 @@
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<zen-loading *ngIf="loading"></zen-loading>

<mat-form-field hideRequiredMarker appearance="outline" class="w-100 mb-2">
<mat-label>Username</mat-label>
<input #usernameInput matInput placeholder="Username" [formControl]="username" hideRequiredMarker="true" maxlength="254" (keyup)="usernameNotFoundReset()">
<mat-error *ngIf="username.errors?.required">Required</mat-error>
<mat-error *ngIf="username.errors?.minlength && !username.errors?.required">Minimum length of {{username.errors?.minlength?.requiredLength}} characters</mat-error>
<mat-error *ngIf="username.errors?.maxlength">Maximum length of {{username.errors?.maxlength?.requiredLength}} characters</mat-error>
<mat-error *ngIf="username.errors?.includesSpace">Cannot contain spaces</mat-error>
<mat-error *ngIf="username.errors?.notFound">User not found</mat-error>
</mat-form-field>

<mat-form-field hideRequiredMarker appearance="outline" class="w-100 mb-2">
<mat-label>Password</mat-label>
<input #passwordInput matInput [type]="hidePassword ? 'password' : 'text'" [formControl]="password" (keyup)="incorrectPasswordReset()">
<span mat-icon-button matSuffix (click)="hidePassword = !hidePassword" tabindex="-1" [attr.aria-label]="'Hide password'" [attr.aria-pressed]="hidePassword">
<mat-icon>{{hidePassword ? 'visibility_off' : 'visibility'}}</mat-icon>
</span>
<mat-error *ngIf="password.errors?.required">Required</mat-error>
<mat-error *ngIf="password.errors?.incorrect">Incorrect password</mat-error>
</mat-form-field>

<div class="mb-4">
<mat-checkbox color="primary" [formControl]="rememberMe">Remember me</mat-checkbox>
<div class="card m-0">
<div class="card-header text-white bg-primary">
<h1 class="m-0">
<i class="fas fa-fw fa-key mr-1"></i>
Login
</h1>
</div>

<button type="submit" mat-raised-button color="primary" class="w-100 text-lg py-1" [disabled]="!form.valid">
<i class="fas fa-fw fa-sign-in-alt mr-1"></i>
Login
</button>

<mat-error @verticalAccordion *ngIf="generalError" class="text-center my-3">
There was a problem logging you in. If this continues, please contact us.
</mat-error>
</form>
<div class="card-body">
<zen-login-form (loggedIn)="onLoggedIn()"></zen-login-form>
</div>
</div>

0 comments on commit accf396

Please sign in to comment.