feat: add email format validator and switch RegisterComponent to ReactiveFormsModule
This commit is contained in:
parent
de381bf5b9
commit
c049f29407
4 changed files with 123 additions and 101 deletions
|
|
@ -10,54 +10,50 @@
|
||||||
<a routerLink="/login">To the login</a>
|
<a routerLink="/login">To the login</a>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
|
|
||||||
<form
|
<form
|
||||||
#registerForm="ngForm"
|
[formGroup]="form"
|
||||||
(ngSubmit)="onSubmit(registerForm)"
|
(ngSubmit)="onSubmit()"
|
||||||
onsubmit="return false"
|
|
||||||
(input)="this.errorService.clearError()"
|
(input)="this.errorService.clearError()"
|
||||||
>
|
>
|
||||||
<div class="mail-field">
|
<div class="mail-field">
|
||||||
<input
|
<input
|
||||||
type="mail"
|
formControlName="mail"
|
||||||
|
type="email"
|
||||||
id="mail"
|
id="mail"
|
||||||
name="mail"
|
|
||||||
#mail="ngModel"
|
|
||||||
placeholder="Email Address"
|
placeholder="Email Address"
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
[(ngModel)]="authData.mail"
|
|
||||||
[class.error-border]="
|
[class.error-border]="
|
||||||
(!mail.valid && mail.touched) ||
|
form.controls['mail'].invalid && form.controls['mail'].touched
|
||||||
(mail.touched &&
|
|
||||||
!isUserEmailValid(authData.mail) &&
|
|
||||||
authData.mail.length > 0)
|
|
||||||
"
|
"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<img class="mail-icon" src="./assets/img/mail.svg" />
|
<img class="mail-icon" src="./assets/img/mail.svg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="error-container">
|
||||||
|
@if (form.controls['mail'].touched && form.controls['mail'].invalid) {
|
||||||
<div class="error-msg">
|
<div class="error-msg">
|
||||||
@if (!mail.valid && mail.touched) {
|
@if (form.controls['mail'].errors?.['required']) {
|
||||||
<p>Please enter your email</p>
|
<p role="alert">Please enter your email</p>
|
||||||
} @else { @if (mail.touched && authData.mail.length > 0 &&
|
} @if (form.controls['mail'].errors?.['invalidEmailFormat']) {
|
||||||
!isUserEmailValid(authData.mail)) {
|
<p role="alert">This is not a valid email format</p>
|
||||||
<p>This is not a valid email format</p>
|
}
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="password-field">
|
<div class="password-field">
|
||||||
<input
|
<input
|
||||||
[type]="authService.passwordFieldType"
|
[type]="authService.passwordFieldType"
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
formControlName="password"
|
||||||
#password="ngModel"
|
|
||||||
placeholder="Enter a password"
|
placeholder="Enter a password"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
[(ngModel)]="authData.password"
|
minlength="8"
|
||||||
pattern="[^<>]*"
|
|
||||||
minlength="6"
|
|
||||||
[class.error-border]="
|
[class.error-border]="
|
||||||
password.invalid && password.touched && authData.password.length < 6
|
form.controls['password'].invalid &&
|
||||||
|
form.controls['password'].touched
|
||||||
"
|
"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<img class="password-icon" src="./assets/img/password.svg" />
|
<img class="password-icon" src="./assets/img/password.svg" />
|
||||||
<img
|
<img
|
||||||
|
|
@ -66,31 +62,32 @@
|
||||||
[src]="authService.passwordIcon"
|
[src]="authService.passwordIcon"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="error-container">
|
||||||
|
@if (form.controls['password'].touched &&
|
||||||
|
form.controls['password'].invalid) {
|
||||||
<div class="error-msg">
|
<div class="error-msg">
|
||||||
@if (!password.valid && password.touched && authData.password.length <
|
@if (form.controls['password'].errors?.['required']) {
|
||||||
1) {
|
<p role="alert">Please enter a password</p>
|
||||||
<p>Please enter a password</p>
|
} @if (form.controls['password'].errors?.['minlength']) {
|
||||||
} @else { @if (authData.password && authData.password.length < 6 &&
|
<p role="alert">Password is too short, min 8 characters</p>
|
||||||
password.touched) {
|
}
|
||||||
<p>Password is too short, min 6 characters</p>
|
|
||||||
} }
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="password-field">
|
<div class="password-field">
|
||||||
<input
|
<input
|
||||||
[type]="authService.passwordFieldType"
|
[type]="authService.passwordFieldType"
|
||||||
id="passwordConfirm"
|
id="passwordConfirm"
|
||||||
name="passwordConfirm"
|
formControlName="passwordConfirm"
|
||||||
#passwordConfirm="ngModel"
|
|
||||||
placeholder="Confirm password"
|
placeholder="Confirm password"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
[(ngModel)]="authData.passwordConfirm"
|
minlength="8"
|
||||||
pattern="[^<>]*"
|
|
||||||
minlength="6"
|
|
||||||
[class.error-border]="
|
[class.error-border]="
|
||||||
authData.password !== authData.passwordConfirm &&
|
form.controls['passwordConfirm'].invalid &&
|
||||||
passwordConfirm.touched
|
form.controls['passwordConfirm'].touched
|
||||||
"
|
"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<img class="password-icon" src="./assets/img/password.svg" />
|
<img class="password-icon" src="./assets/img/password.svg" />
|
||||||
<img
|
<img
|
||||||
|
|
@ -99,37 +96,35 @@
|
||||||
[src]="authService.passwordIcon"
|
[src]="authService.passwordIcon"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="error-container">
|
||||||
|
@if ( form.controls['passwordConfirm'].touched &&
|
||||||
|
form.controls['passwordConfirm'].invalid ) {
|
||||||
<div class="error-msg">
|
<div class="error-msg">
|
||||||
@if (authData.password !== authData.passwordConfirm &&
|
@if (form.controls['passwordConfirm'].errors?.['required']) {
|
||||||
passwordConfirm.touched) {
|
<p>Please confirm your password</p>
|
||||||
|
} @if ( form.controls['password'].value !==
|
||||||
|
form.controls['passwordConfirm'].value &&
|
||||||
|
form.controls['passwordConfirm'].touched ) {
|
||||||
<p>The passwords do not match</p>
|
<p>The passwords do not match</p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label class="container"
|
<label class="container">
|
||||||
>I agree to the
|
I agree to the
|
||||||
<a routerLink="/privacy-policy">Privacy policy</a>.
|
<a routerLink="/privacy-policy">Privacy policy</a>.
|
||||||
<input
|
<input type="checkbox" formControlName="privacyPolicy" />
|
||||||
type="checkbox"
|
|
||||||
id="checkbox"
|
|
||||||
name="checkbox"
|
|
||||||
#checkbox
|
|
||||||
[(ngModel)]="authData.privacyPolicy"
|
|
||||||
/>
|
|
||||||
<span class="checkmark"></span>
|
<span class="checkmark"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-btn-large
|
<app-btn-large
|
||||||
[value]="'Get Started'"
|
[value]="'Get Started'"
|
||||||
[disabled]="
|
[disabled]="
|
||||||
(!isUserEmailValid(authData.mail) &&
|
form.invalid || form.controls['privacyPolicy'].value === false
|
||||||
authData.mail &&
|
|
||||||
authData.mail.length > 0) ||
|
|
||||||
authData.password !== authData.passwordConfirm ||
|
|
||||||
!authData.password ||
|
|
||||||
!authData.passwordConfirm ||
|
|
||||||
!authData.privacyPolicy ||
|
|
||||||
authData.send
|
|
||||||
"
|
"
|
||||||
></app-btn-large>
|
></app-btn-large>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,41 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { BtnLargeComponent } from '../../../shared/components/buttons/btn-large/btn-large.component';
|
import { BtnLargeComponent } from '../../../shared/components/buttons/btn-large/btn-large.component';
|
||||||
import { FormsModule, NgForm } from '@angular/forms';
|
import {
|
||||||
|
FormBuilder,
|
||||||
|
FormGroup,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms';
|
||||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { AuthService } from '../../../services/auth.service';
|
import { AuthService } from '../../../services/auth.service';
|
||||||
import { ErrorService } from '../../../services/error.service';
|
import { ErrorService } from '../../../services/error.service';
|
||||||
|
import { emailFormatValidator } from '../../../validators/email-format.validator';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-register',
|
selector: 'app-register',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, BtnLargeComponent, FormsModule, RouterLink],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
BtnLargeComponent,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterLink,
|
||||||
|
],
|
||||||
templateUrl: './register.component.html',
|
templateUrl: './register.component.html',
|
||||||
styleUrl: './register.component.scss',
|
styleUrl: './register.component.scss',
|
||||||
})
|
})
|
||||||
export class RegisterComponent implements OnInit {
|
export class RegisterComponent implements OnInit {
|
||||||
registrationSuccess: boolean = false;
|
registrationSuccess: boolean = false;
|
||||||
|
form: FormGroup = new FormGroup({});
|
||||||
authData = {
|
|
||||||
mail: '',
|
|
||||||
password: '',
|
|
||||||
passwordConfirm: '',
|
|
||||||
privacyPolicy: false,
|
|
||||||
send: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the RegisterComponent with ActivatedRoute, AuthService, and ErrorService.
|
* Initializes the RegisterComponent with ActivatedRoute, FormBuilder, AuthService, and ErrorService.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
private fb: FormBuilder,
|
||||||
public authService: AuthService,
|
public authService: AuthService,
|
||||||
public errorService: ErrorService
|
public errorService: ErrorService
|
||||||
) {}
|
) {}
|
||||||
|
|
@ -37,68 +44,69 @@ export class RegisterComponent implements OnInit {
|
||||||
* Initializes the component by setting up necessary data.
|
* Initializes the component by setting up necessary data.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.createRegisterForm();
|
||||||
this.setEmailFromQueryParams();
|
this.setEmailFromQueryParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the registration form with required fields and validators.
|
||||||
|
*/
|
||||||
|
private createRegisterForm(): void {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
mail: ['', [Validators.required, emailFormatValidator()]],
|
||||||
|
password: ['', Validators.required],
|
||||||
|
passwordConfirm: ['', Validators.required],
|
||||||
|
privacyPolicy: [false, Validators.requiredTrue],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the email address from the query parameters in the URL.
|
* Sets the email address from the query parameters in the URL.
|
||||||
*/
|
*/
|
||||||
private setEmailFromQueryParams(): void {
|
private setEmailFromQueryParams(): void {
|
||||||
this.route.queryParams.subscribe((params) => {
|
this.route.queryParams.subscribe((params) => {
|
||||||
this.authData.mail = params['mail'] || '';
|
const email = params['mail'] || '';
|
||||||
|
this.form.patchValue({ mail: email });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the given email address.
|
* Handles the submission of the registration form.
|
||||||
* Converts to lowercase before checking against the regex.
|
|
||||||
*
|
*
|
||||||
* @param emailValue The email address to validate.
|
* @returns A Promise that resolves when the registration process is complete.
|
||||||
* @returns True if the email format is valid, false otherwise.
|
|
||||||
*/
|
*/
|
||||||
isUserEmailValid(emailValue: string): boolean {
|
async onSubmit(): Promise<void> {
|
||||||
const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
|
if (this.form.valid) {
|
||||||
return emailRegex.test(emailValue.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles form submission for user registration.
|
|
||||||
* Sends registration request and handles success/error responses.
|
|
||||||
*
|
|
||||||
* @param ngForm The registration form instance.
|
|
||||||
*/
|
|
||||||
async onSubmit(ngForm: NgForm): Promise<void> {
|
|
||||||
if (ngForm.submitted && ngForm.form.valid) {
|
|
||||||
const body = this.createRegistrationRequestBody();
|
const body = this.createRegistrationRequestBody();
|
||||||
|
this.form.disable();
|
||||||
this.authData.send = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.authService.register(body);
|
await this.authService.register(body);
|
||||||
ngForm.resetForm();
|
this.form.reset();
|
||||||
this.registrationSuccess = true;
|
this.registrationSuccess = true;
|
||||||
this.errorService.clearError();
|
this.errorService.clearError();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.authData.send = false;
|
this.form.enable();
|
||||||
this.errorService.handleError(error);
|
this.errorService.handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the request body for the registration API.
|
* Constructs the registration request payload from form values.
|
||||||
*
|
*
|
||||||
* @returns The registration request body.
|
* @returns An object containing the email (lowercased), username (from email), and password.
|
||||||
*/
|
*/
|
||||||
private createRegistrationRequestBody(): {
|
private createRegistrationRequestBody(): {
|
||||||
email: string;
|
email: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
} {
|
} {
|
||||||
|
const formValue = this.form.value;
|
||||||
return {
|
return {
|
||||||
email: this.authData.mail.toLowerCase(),
|
email: formValue.mail.toLowerCase(),
|
||||||
username: this.authData.mail.split('@')[0],
|
username: formValue.mail.split('@')[0],
|
||||||
password: this.authData.password,
|
password: formValue.password,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
frontend/src/app/validators/email-format.validator.ts
Normal file
11
frontend/src/app/validators/email-format.validator.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
|
||||||
|
|
||||||
|
export function emailFormatValidator(): ValidatorFn {
|
||||||
|
return (control: AbstractControl): ValidationErrors | null => {
|
||||||
|
const email = control.value;
|
||||||
|
const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
|
||||||
|
const isValid = emailRegex.test(email?.toLowerCase());
|
||||||
|
|
||||||
|
return isValid ? null : { invalidEmailFormat: true };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,14 @@ form {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 24px;
|
||||||
|
padding: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
.error-msg {
|
.error-msg {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue