-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Login and Registration Pages (#32)
* Add basic login page * Add basic registration page * Add loading button for login and registration page * Add error toast message for login and registration pages * Change wording for login and registration pages * Update routing and UI for login and registration pages * Update routing and UI for login and registration pages * Fix linting * Clean up CSS styles for login and registration pages * Redirect account route to login * Adjust body styles * Add default spec file for register component * Register: Ensure strong password * Register: Add email error message * Register: Validate password match * Register: Minor fix * Add validation for username * Clean up code and styles * Add restricted set of characters for password * Shift registration validators into account folder * Fix linting * Add special character requirement for password * Fix styles for password input in login page * Register: Clean error messages * Login: Fix toast sizing --------- Co-authored-by: Samuel Lim <samuelim01@gmail.com>
- Loading branch information
1 parent
0c7e193
commit f2df810
Showing
17 changed files
with
436 additions
and
2 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
frontend/src/app/account/_validators/invalid-password.validator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; | ||
|
||
const PASSWORD_REGEX = /^[a-zA-Z0-9!"#$%&'()*+,-.:;<=>?@\\/\\[\]^_`{|}~]+$/; | ||
|
||
export const PASSWORD_INVALID = 'passwordInvalid'; | ||
|
||
export function invalidPasswordValidator(): ValidatorFn { | ||
return (control: AbstractControl): ValidationErrors | null => { | ||
const weak = !PASSWORD_REGEX.test(control.value); | ||
return weak ? { [PASSWORD_INVALID]: true } : null; | ||
}; | ||
} |
12 changes: 12 additions & 0 deletions
12
frontend/src/app/account/_validators/invalid-username.validator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; | ||
|
||
const USERNAME_REGEX = /^[a-zA-Z0-9._-]+$/; | ||
|
||
export const USERNAME_INVALID = 'usernameInvalid'; | ||
|
||
export function invalidUsernameValidator(): ValidatorFn { | ||
return (control: AbstractControl): ValidationErrors | null => { | ||
const invalid = !USERNAME_REGEX.test(control.value); | ||
return invalid ? { [USERNAME_INVALID]: true } : null; | ||
}; | ||
} |
11 changes: 11 additions & 0 deletions
11
frontend/src/app/account/_validators/mismatch-password.validator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; | ||
|
||
export const PASSWORD_MISMATCH = 'passwordMismatch'; | ||
|
||
export function mismatchPasswordValidator(firstPasswordField: string, secondPasswordField: string): ValidatorFn { | ||
return (formGroup: AbstractControl): ValidationErrors | null => { | ||
const password = formGroup.get(firstPasswordField)?.value; | ||
const confirmPassword = formGroup.get(secondPasswordField)?.value; | ||
return password !== confirmPassword ? { [PASSWORD_MISMATCH]: true } : null; | ||
}; | ||
} |
13 changes: 13 additions & 0 deletions
13
frontend/src/app/account/_validators/weak-password.validator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; | ||
|
||
export const STRONG_PASSWORD_REGEX = | ||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})(?=.*[!"#$%&'()*+,-.:;<=>?@\\/\\[\]^_`{|}~])/; | ||
|
||
export const PASSWORD_WEAK = 'passwordWeak'; | ||
|
||
export function weakPasswordValidator(): ValidatorFn { | ||
return (control: AbstractControl): ValidationErrors | null => { | ||
const weak = !STRONG_PASSWORD_REGEX.test(control.value); | ||
return weak ? { [PASSWORD_WEAK]: true } : null; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
.container { | ||
padding: 2rem; | ||
background-color: var(--surface-section); | ||
border-radius: 0.75rem; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
|
||
.form-container { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 1rem; | ||
width: 100%; | ||
} | ||
|
||
.form-field { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.5rem; | ||
width: 100%; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { Routes, RouterModule } from '@angular/router'; | ||
|
||
import { LoginComponent } from './login.component'; | ||
import { RegisterComponent } from './register.component'; | ||
import { LayoutComponent } from './layout.component'; | ||
|
||
const routes: Routes = [ | ||
{ | ||
path: '', | ||
component: LayoutComponent, | ||
children: [ | ||
{ path: '', redirectTo: 'login', pathMatch: 'full' }, | ||
{ path: 'login', component: LoginComponent }, | ||
{ path: 'register', component: RegisterComponent }, | ||
], | ||
}, | ||
]; | ||
|
||
@NgModule({ | ||
imports: [RouterModule.forChild(routes)], | ||
exports: [RouterModule], | ||
}) | ||
export class AccountRoutingModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { ReactiveFormsModule } from '@angular/forms'; | ||
import { CommonModule } from '@angular/common'; | ||
|
||
import { LoginComponent } from './login.component'; | ||
import { RegisterComponent } from './register.component'; | ||
import { LayoutComponent } from './layout.component'; | ||
import { AccountRoutingModule } from './account.component'; | ||
|
||
@NgModule({ | ||
imports: [ | ||
CommonModule, | ||
ReactiveFormsModule, | ||
AccountRoutingModule, | ||
LayoutComponent, | ||
LoginComponent, | ||
RegisterComponent, | ||
], | ||
}) | ||
export class AccountModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<div class="flex flex-column h-full w-full justify-content-center align-items-center p-2"> | ||
<h2 class="mb-2">Welcome to PeerPrep</h2> | ||
<router-outlet></router-outlet> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Component } from '@angular/core'; | ||
import { Router, RouterModule } from '@angular/router'; | ||
|
||
@Component({ | ||
standalone: true, | ||
imports: [RouterModule], | ||
templateUrl: './layout.component.html', | ||
}) | ||
export class LayoutComponent { | ||
constructor(private router: Router) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<div class="container w-full sm:w-22rem"> | ||
<h2 class="mt-0 align-self-start">Log In</h2> | ||
|
||
<form #loginForm="ngForm" (ngSubmit)="onSubmit()" class="form-container"> | ||
<div class="form-field"> | ||
<label for="username">Username</label> | ||
<input pInputText required type="text" id="username" name="username" [(ngModel)]="user.username" /> | ||
</div> | ||
|
||
<div class="form-field"> | ||
<label for="password">Password</label> | ||
<p-password | ||
required | ||
inputId="password" | ||
name="password" | ||
[(ngModel)]="user.password" | ||
[toggleMask]="true" | ||
[feedback]="false" | ||
class="p-fluid" /> | ||
</div> | ||
|
||
<p-button | ||
type="submit" | ||
[label]="isProcessingLogin ? '' : 'Log In'" | ||
[loading]="isProcessingLogin" | ||
[disabled]="!loginForm.valid" | ||
styleClass="w-full justify-content-center" /> | ||
</form> | ||
|
||
<p class="mt-4 mb-0"> | ||
Don't have an account? | ||
<a routerLink="../register" class="text-blue-500 no-underline">Register</a> | ||
</p> | ||
|
||
<p-toast position="bottom-right" [breakpoints]="{ '920px': { width: '90%' } }" /> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { LoginComponent } from './login.component'; | ||
|
||
describe('LoginComponent', () => { | ||
let component: LoginComponent; | ||
let fixture: ComponentFixture<LoginComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [LoginComponent], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(LoginComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { Component } from '@angular/core'; | ||
import { FormsModule } from '@angular/forms'; | ||
import { RouterLink } from '@angular/router'; | ||
import { SelectButtonModule } from 'primeng/selectbutton'; | ||
import { InputTextModule } from 'primeng/inputtext'; | ||
import { PasswordModule } from 'primeng/password'; | ||
import { ButtonModule } from 'primeng/button'; | ||
import { ToastModule } from 'primeng/toast'; | ||
import { MessageService } from 'primeng/api'; | ||
|
||
@Component({ | ||
selector: 'app-login', | ||
standalone: true, | ||
imports: [RouterLink, FormsModule, InputTextModule, ButtonModule, SelectButtonModule, PasswordModule, ToastModule], | ||
providers: [MessageService], | ||
templateUrl: './login.component.html', | ||
styleUrl: './account.component.css', | ||
}) | ||
export class LoginComponent { | ||
constructor(private messageService: MessageService) {} | ||
|
||
user = { | ||
username: '', | ||
password: '', | ||
}; | ||
|
||
isProcessingLogin = false; | ||
|
||
showError() { | ||
this.messageService.add({ severity: 'error', summary: 'Log In Error', detail: 'Missing Details' }); | ||
} | ||
|
||
onSubmit() { | ||
if (this.user.username && this.user.password) { | ||
this.isProcessingLogin = true; | ||
this.showError(); | ||
// Simulate API request | ||
setTimeout(() => { | ||
this.isProcessingLogin = false; | ||
console.log('Form Submitted', this.user); | ||
}, 3000); | ||
} else { | ||
console.log('Invalid form'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<div class="container w-full sm:w-22rem"> | ||
<h2 class="mt-0 align-self-start">Register</h2> | ||
|
||
<form [formGroup]="userForm" (ngSubmit)="onSubmit()" class="form-container"> | ||
<div class="form-field"> | ||
<label for="username">Username</label> | ||
<input id="username" type="text" formControlName="username" pInputText /> | ||
@if (isUsernameInvalid) { | ||
<small class="text-red-300"> | ||
The provided username can only contain alphanumeric characters, dots, dashes, and underscores | ||
</small> | ||
} | ||
</div> | ||
<div class="form-field"> | ||
<label for="email">Email</label> | ||
<input id="email" type="email" formControlName="email" pInputText /> | ||
@if (isEmailInvalid) { | ||
<small class="text-red-300">The provided email is invalid</small> | ||
} | ||
</div> | ||
<div class="form-field"> | ||
<label for="password">Password</label> | ||
<p-password | ||
id="password" | ||
class="p-fluid" | ||
formControlName="password" | ||
[strongRegex]="strongPasswordRegex" | ||
[toggleMask]="true"> | ||
<ng-template pTemplate="footer"> | ||
<p-divider /> | ||
<p class="mt-1 text-sm">Your password must contain:</p> | ||
<ul class="pl-2 ml-2 mt-0 text-sm line-height-3"> | ||
<li>At least one lowercase</li> | ||
<li>At least one uppercase</li> | ||
<li>At least one numeric</li> | ||
<li>At least one special character</li> | ||
<li>Minimum 8 characters</li> | ||
</ul> | ||
</ng-template> | ||
</p-password> | ||
@if (isPasswordWeak) { | ||
<small class="text-red-300">The provided password is too weak</small> | ||
} @else if (isPasswordInvalid) { | ||
<small class="text-red-300">The provided password contains invalid characters</small> | ||
} | ||
</div> | ||
<div class="form-field"> | ||
<label for="confirmPassword">Confirm Password</label> | ||
<p-password | ||
id="confirmPassword" | ||
class="p-fluid" | ||
formControlName="confirmPassword" | ||
[toggleMask]="true" | ||
[feedback]="false" /> | ||
@if (hasPasswordMismatch) { | ||
<small class="text-red-300">The provided passwords do not match!</small> | ||
} | ||
</div> | ||
<p-button | ||
type="submit" | ||
[label]="isProcessingRegistration ? '' : 'Register'" | ||
[loading]="isProcessingRegistration" | ||
[disabled]="!userForm.valid" | ||
styleClass="w-full justify-content-center" /> | ||
</form> | ||
|
||
<p class="mt-4 mb-0"> | ||
Already have an account? | ||
<a routerLink="../login" class="text-blue-500 no-underline">Log In</a> | ||
</p> | ||
|
||
<p-toast position="bottom-right" [breakpoints]="{ '920px': { width: '90%' } }" /> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { RegisterComponent } from './register.component'; | ||
|
||
describe('RegisterComponent', () => { | ||
let component: RegisterComponent; | ||
let fixture: ComponentFixture<RegisterComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [RegisterComponent], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(RegisterComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
Oops, something went wrong.