Sign Up Component
import { ChangeDetectionStrategy, Component, ChangeDetectorRef,} from '@angular/core';import { FormBuilder, Validators, type AbstractControl } from '@angular/forms';import { AuthService } from '../auth.service';import { Router } from '@angular/router';import type { NewUser } from '../../core/user/user.model';
function passwordMatchValidator(control: AbstractControl) { const password = control.get('password'); const confirmPassword = control.get('confirmPassword');
if (!password || !confirmPassword) { return null; }
return password.value === confirmPassword.value ? null : { passwordMismatch: true };}
@Component({ selector: 'app-sign-up', templateUrl: './sign-up.component.html', styleUrl: './sign-up.component.css', changeDetection: ChangeDetectionStrategy.OnPush,})export class SignUpComponent { isSubmitting = false;
constructor( private fb: FormBuilder, private authService: AuthService, private router: Router, private cdr: ChangeDetectorRef ) {}
signUpForm = this.fb.group( { name: ['', [Validators.required, Validators.minLength(3)]], email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(6)]], confirmPassword: ['', [Validators.required]], }, { validators: [passwordMatchValidator] } );
onSubmit(event: Event) { event.preventDefault();
if (this.signUpForm.valid) { this.isSubmitting = true;
const newUser = this.signUpForm.value as NewUser;
this.authService.registerUser(newUser).subscribe({ next: (response) => { console.log('User registered successfully'); this.isSubmitting = false; this.cdr.markForCheck(); this.router.navigate(['/sign-in']); }, error: (error) => { console.error('Registration failed: ', error); this.isSubmitting = false; this.cdr.markForCheck(); }, }); } else { Object.keys(this.signUpForm.controls).forEach((key) => { this.signUpForm.get(key)?.markAllAsTouched(); }); } }}
<app-card title="Create Account" subtitle="Join us today and get started with your journey"> <div card-content> <form class="signup-form" [formGroup]="signUpForm" (ngSubmit)="onSubmit($event)" > <div class="form-group"> <label for="name">Full Name</label> <input type="text" id="name" placeholder="Enter your full name" class="form-input" formControlName="name" [class.error]="signUpForm.get('name')?.invalid && signUpForm.get('name')?.touched" /> <div class="error-message" *ngIf="signUpForm.get('name')?.invalid && signUpForm.get('name')?.touched" > <span *ngIf="signUpForm.get('name')?.errors?.['required']"> Name is required </span> <span *ngIf="signUpForm.get('name')?.errors?.['minlength']"> Name must be at least 3 characters </span> </div> </div> <div class="form-group"> <label for="email">Email Address</label> <input type="email" id="email" placeholder="Enter your email" class="form-input" formControlName="email" [class.error]="signUpForm.get('email')?.invalid && signUpForm.get('email')?.touched" /> <div class="error-message" *ngIf="signUpForm.get('email')?.invalid && signUpForm.get('email')?.touched" > <span *ngIf="signUpForm.get('email')?.errors?.['required']"> Email is required </span> <span *ngIf="signUpForm.get('email')?.errors?.['email']"> Please enter a valid email </span> </div> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" id="password" formControlName="password" placeholder="Create a password" class="form-input" [class.error]="signUpForm.get('password')?.invalid && signUpForm.get('password')?.touched" /> <div class="error-message" *ngIf="signUpForm.get('password')?.invalid && signUpForm.get('password')?.touched" > <span *ngIf="signUpForm.get('password')?.errors?.['required']"> Password is required </span> <span *ngIf="signUpForm.get('password')?.errors?.['minlength']"> Password must be at least 6 characters </span> </div> </div> <div class="form-group"> <label for="confirm-password">Confirm Password</label> <input type="password" id="confirm-password" formControlName="confirmPassword" placeholder="Confirm your password" class="form-input" [class.error]="signUpForm.get('confirmPassword')?.invalid && signUpForm.get('confirmPassword')?.touched" /> <div class="error-message" *ngIf="signUpForm.get('confirmPassword')?.invalid && signUpForm.get('confirmPassword')?.touched" > <span *ngIf="signUpForm.get('confirmPassword')?.errors?.['required']"> Confirm Password is required </span> <span *ngIf="signUpForm.get('confirmPassword')?.errors?.['mustMatch']"> Passwords do not match </span> </div> </div>
<button type="submit" class="submit-btn" [disabled]="signUpForm.invalid || isSubmitting" [class.disabled]="signUpForm.invalid || isSubmitting" > <span *ngIf="!isSubmitting">Get Started</span> <span *ngIf="isSubmitting" class="loading" >Signing up...</span> </button> </form> </div> <div card-footer> <div class="form-footer"> <p> Already have an account? <a routerLink="/sign-in" class="login-link" >Sign In</a> </p> </div> </div></app-card>
@import "../../../styles/_form-elements.css";
import { applicationConfig, moduleMetadata, type Meta, type StoryObj,} from '@storybook/angular';
import { SignUpComponent } from './sign-up.component';import { provideHttpClient } from '@angular/common/http';import { SharedModule } from '../../shared/shared.module';import { http, HttpResponse, delay } from 'msw';import { expect, userEvent, within, waitFor } from 'storybook/test';import { Router } from '@angular/router';import { environment } from '../../../environments/environment';
const meta: Meta<SignUpComponent> = { title: 'Auth/SignUp', component: SignUpComponent, tags: ['autodocs'], decorators: [ moduleMetadata({ imports: [SharedModule], }), applicationConfig({ providers: [ provideHttpClient(), { provide: Router, useValue: { navigate: () => null } }, ], }), ],};
export default meta;type Story = StoryObj<SignUpComponent>;
export const Default: Story = { parameters: { msw: { handlers: [ http.post(`${environment.todoApiBaseUrl}/users`, async () => { await delay(500); return HttpResponse.json({ data: { id: '1', name: 'John Doe' } }); }), ], }, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement);
const nameInput = canvas.getByPlaceholderText('Enter your full name'); const emailInput = canvas.getByPlaceholderText('Enter your email'); const passwordInput = canvas.getByPlaceholderText('Create a password'); const confirmPasswordInput = canvas.getByPlaceholderText( 'Confirm your password' ); const submitButton = canvas.getByRole('button', { name: 'Get Started' });
await userEvent.type(nameInput, 'John Doe', { delay: 100 }); await userEvent.type(emailInput, 'johndoe@example.com', { delay: 100 }); await userEvent.type(passwordInput, '123456', { delay: 100 }); await userEvent.type(confirmPasswordInput, '123456', { delay: 100 });
expect(submitButton).toBeEnabled();
await userEvent.click(submitButton);
expect(canvas.getByText('Signing up...')).toBeInTheDocument(); },};