From d2e5e6acac952114046bad608e024473d844ac31 Mon Sep 17 00:00:00 2001
From: Avnish Raut
Date: Tue, 10 Mar 2026 19:09:53 +0100
Subject: [PATCH 1/3] Adding UI for reset password and chaning *ngif to if for
auth
---
backend/src/mail/mail.module.ts | 2 +-
frontend/src/app/app.routes.ts | 6 +-
frontend/src/app/auth/auth.service.ts | 18 ++++-
.../auth/forgot-password/forgot-password.html | 21 ++++++
.../auth/forgot-password/forgot-password.scss | 73 +++++++++++++++++++
.../forgot-password/forgot-password.spec.ts | 22 ++++++
.../auth/forgot-password/forgot-password.ts | 56 ++++++++++++++
.../src/app/auth/login/login.component.html | 26 ++++---
.../src/app/auth/login/login.component.ts | 6 +-
.../app/auth/register/register.component.html | 29 ++++----
.../auth/reset-password/reset-password.html | 27 +++++++
.../auth/reset-password/reset-password.scss | 73 +++++++++++++++++++
.../reset-password/reset-password.spec.ts | 22 ++++++
.../app/auth/reset-password/reset-password.ts | 63 ++++++++++++++++
14 files changed, 414 insertions(+), 30 deletions(-)
create mode 100644 frontend/src/app/auth/forgot-password/forgot-password.html
create mode 100644 frontend/src/app/auth/forgot-password/forgot-password.scss
create mode 100644 frontend/src/app/auth/forgot-password/forgot-password.spec.ts
create mode 100644 frontend/src/app/auth/forgot-password/forgot-password.ts
create mode 100644 frontend/src/app/auth/reset-password/reset-password.html
create mode 100644 frontend/src/app/auth/reset-password/reset-password.scss
create mode 100644 frontend/src/app/auth/reset-password/reset-password.spec.ts
create mode 100644 frontend/src/app/auth/reset-password/reset-password.ts
diff --git a/backend/src/mail/mail.module.ts b/backend/src/mail/mail.module.ts
index 53bc297..1ea1d72 100644
--- a/backend/src/mail/mail.module.ts
+++ b/backend/src/mail/mail.module.ts
@@ -3,6 +3,6 @@ import { MailService } from './mail.service';
@Module({
providers: [MailService],
- exports: [MailService], // ✅ VERY IMPORTANT
+ exports: [MailService],
})
export class MailModule {}
diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts
index a36dcf0..a5daf94 100644
--- a/frontend/src/app/app.routes.ts
+++ b/frontend/src/app/app.routes.ts
@@ -3,12 +3,16 @@ import { RegisterComponent } from './auth/register/register.component';
import { LoginComponent } from './auth/login/login.component';
import { authGuard } from './core/guards/auth.guard';
import { rolesGuard } from './core/guards/roles.guard';
+import { ForgotPasswordComponent } from './auth/forgot-password/forgot-password';
+import { ResetPasswordComponent } from './auth/reset-password/reset-password';
export const routes: Routes = [
// Public routes
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
+ { path: 'forgot-password', component: ForgotPasswordComponent },
+ { path: 'reset-password', component: ResetPasswordComponent },
// Protected routes (canActivate: [authGuard] applied — add components as they are built)
// Any logged-in user
// { path: 'events', component: EventListComponent, canActivate: [authGuard] },
@@ -28,4 +32,4 @@ export const routes: Routes = [
];
// Re-export guards so other modules can import from one place
-export { authGuard, rolesGuard };
\ No newline at end of file
+export { authGuard, rolesGuard };
diff --git a/frontend/src/app/auth/auth.service.ts b/frontend/src/app/auth/auth.service.ts
index 3ab8140..a88addc 100644
--- a/frontend/src/app/auth/auth.service.ts
+++ b/frontend/src/app/auth/auth.service.ts
@@ -18,6 +18,10 @@ interface LoginResponse {
access_token: string;
}
+interface ResetPasswordDto {
+ token: string;
+ password: string;
+}
@Injectable({
providedIn: 'root',
})
@@ -35,10 +39,12 @@ export class AuthService {
tap((res) => {
// Save JWT in localStorage
localStorage.setItem('token', res.access_token);
- })
+ }),
);
}
-
+ reset(dto: ResetPasswordDto): Observable {
+ return this.http.post(`${this.apiUrl}/reset-password`, dto);
+ }
logout(): void {
localStorage.removeItem('token');
}
@@ -51,6 +57,12 @@ export class AuthService {
return !!this.getToken();
}
+ forgotPassword(email: string) {
+ return this.http.post(`${this.apiUrl}/forgot-password`, {
+ email: email,
+ });
+ }
+
/**
* Decodes the JWT payload and returns the user's role.
* Returns null if no token or token is malformed.
@@ -79,4 +91,4 @@ export class AuthService {
return null;
}
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/app/auth/forgot-password/forgot-password.html b/frontend/src/app/auth/forgot-password/forgot-password.html
new file mode 100644
index 0000000..14c3793
--- /dev/null
+++ b/frontend/src/app/auth/forgot-password/forgot-password.html
@@ -0,0 +1,21 @@
+
+
Forgot Password
+
+
+ @if (message) {
+
{{ message }}
+ }
+ @if (error) {
+
{{ error }}
+ }
+
diff --git a/frontend/src/app/auth/forgot-password/forgot-password.scss b/frontend/src/app/auth/forgot-password/forgot-password.scss
new file mode 100644
index 0000000..348f65f
--- /dev/null
+++ b/frontend/src/app/auth/forgot-password/forgot-password.scss
@@ -0,0 +1,73 @@
+.forgot-container {
+ max-width: 400px;
+ margin: 2rem auto;
+ padding: 2rem;
+ border: 1px solid #ccc;
+ border-radius: 10px;
+ background: #f9f9f9;
+
+ h2 {
+ text-align: center;
+ margin-bottom: 1rem;
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+
+ label {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 0.5rem;
+ font-weight: bold;
+
+ input {
+ padding: 0.5rem;
+ margin-top: 0.25rem;
+ border-radius: 5px;
+ border: 1px solid #ccc;
+ }
+ }
+
+ button {
+ margin-top: 1rem;
+ padding: 0.5rem;
+ border: none;
+ background-color: #007bff;
+ color: white;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:disabled {
+ background-color: #999;
+ cursor: not-allowed;
+ }
+ }
+
+ p {
+ margin-top: 1rem;
+ text-align: center;
+
+ a {
+ color: #007bff;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+ }
+
+ .message {
+ color: green;
+ text-align: center;
+ margin-top: 0.5rem;
+ }
+
+ .error {
+ color: red;
+ font-size: 0.9rem;
+ text-align: center;
+ }
+}
diff --git a/frontend/src/app/auth/forgot-password/forgot-password.spec.ts b/frontend/src/app/auth/forgot-password/forgot-password.spec.ts
new file mode 100644
index 0000000..cfdf701
--- /dev/null
+++ b/frontend/src/app/auth/forgot-password/forgot-password.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ForgotPasswordComponent } from './forgot-password';
+
+describe('ForgotPasswordComponent', () => {
+ let component: ForgotPasswordComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ForgotPasswordComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ForgotPasswordComponent);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/auth/forgot-password/forgot-password.ts b/frontend/src/app/auth/forgot-password/forgot-password.ts
new file mode 100644
index 0000000..dd0186e
--- /dev/null
+++ b/frontend/src/app/auth/forgot-password/forgot-password.ts
@@ -0,0 +1,56 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { AuthService } from '../auth.service';
+
+@Component({
+ selector: 'app-forgot-password',
+ standalone: true,
+ imports: [CommonModule, ReactiveFormsModule],
+ templateUrl: './forgot-password.html',
+ styleUrls: ['./forgot-password.scss'],
+})
+export class ForgotPasswordComponent {
+ forgotForm: FormGroup;
+ message: string = '';
+ error: string = '';
+ loading: boolean = false;
+ constructor(
+ private fb: FormBuilder,
+ private auth: AuthService,
+ ) {
+ this.forgotForm = this.fb.group({
+ email: ['', [Validators.required, Validators.email]],
+ });
+ }
+
+ onSubmit() {
+ if (this.loading) return; // prevents second click
+
+ if (this.forgotForm.invalid) {
+ this.forgotForm.markAllAsTouched();
+ return;
+ }
+
+ this.loading = true; // disable button
+ this.message = '';
+ this.error = '';
+
+ const email = this.forgotForm.value.email;
+
+ this.auth.forgotPassword(email).subscribe({
+ next: () => {
+ this.message = 'Password reset email sent. Check your inbox.';
+
+ setTimeout(() => {
+ this.loading = true;
+ }, 1);
+ },
+
+ error: (err) => {
+ this.error = err.error?.message ?? 'Failed to send reset email';
+ this.loading = false;
+ },
+ });
+ }
+}
diff --git a/frontend/src/app/auth/login/login.component.html b/frontend/src/app/auth/login/login.component.html
index f087187..6a1383e 100644
--- a/frontend/src/app/auth/login/login.component.html
+++ b/frontend/src/app/auth/login/login.component.html
@@ -6,17 +6,17 @@ Login
Email
-
- Enter a valid email
-
+ @if (loginForm.get('email')?.invalid && loginForm.get('email')?.touched) {
+ Enter a valid email
+ }
-
- Password must be at least 6 characters
-
+ @if (loginForm.get('password')?.invalid && loginForm.get('password')?.touched) {
+ Password must be at least 8 characters
+ }
@@ -24,8 +24,16 @@ Login
Don't have an account?
Go to Registration
+
+ Forgot Password?
+ Reset
+
- {{ message }}
- {{ error }}
-
\ No newline at end of file
+ @if (message) {
+ {{ message }}
+ }
+ @if (error) {
+ {{ error }}
+ }
+
diff --git a/frontend/src/app/auth/login/login.component.ts b/frontend/src/app/auth/login/login.component.ts
index c1e91f9..edea58c 100644
--- a/frontend/src/app/auth/login/login.component.ts
+++ b/frontend/src/app/auth/login/login.component.ts
@@ -9,7 +9,7 @@ import { AuthService } from '../auth.service';
standalone: true,
imports: [CommonModule, ReactiveFormsModule, RouterModule],
templateUrl: './login.component.html',
- styleUrls: ['./login.component.scss']
+ styleUrls: ['./login.component.scss'],
})
export class LoginComponent {
loginForm: FormGroup;
@@ -23,7 +23,7 @@ export class LoginComponent {
) {
this.loginForm = this.fb.group({
email: ['', [Validators.required, Validators.email]],
- password: ['', [Validators.required, Validators.minLength(6)]]
+ password: ['', [Validators.required, Validators.minLength(8)]],
});
}
@@ -45,4 +45,4 @@ export class LoginComponent {
this.error = 'Please enter valid email and password.';
}
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/app/auth/register/register.component.html b/frontend/src/app/auth/register/register.component.html
index ee052ef..29ecca2 100644
--- a/frontend/src/app/auth/register/register.component.html
+++ b/frontend/src/app/auth/register/register.component.html
@@ -6,25 +6,24 @@ Register
Username
-
- Username is required
-
+ @if (registerForm.get('username')?.invalid && registerForm.get('username')?.touched) {
+ Username is required
+ }
-
- Enter a valid email
-
-
+ @if (registerForm.get('email')?.invalid && registerForm.get('email')?.touched) {
+ Enter a valid email
+ }
-
- Password must be at least 6 characters
-
+ @if (registerForm.get('password')?.invalid && registerForm.get('password')?.touched) {
+ Password must be at least 8 characters
+ }
- @if (registerForm.get('email')?.invalid && registerForm.get('email')?.touched) {
+ @if (registerForm.get('email')?.invalid && registerForm.get('email')?.dirty) {
Enter a valid email
}
- @if (registerForm.get('email')?.invalid && registerForm.get('email')?.dirty) {
+ @if (registerForm.get('email')?.invalid && registerForm.get('email')?.touched) {
Enter a valid email
}