From 0c8f11a4630853b695100aecd4895de869d971b7 Mon Sep 17 00:00:00 2001 From: Borgia Leiva Date: Tue, 10 Feb 2026 13:49:14 +0100 Subject: [PATCH] Added user registration --- src/app/app.routes.ts | 17 +++ src/app/models/auth.models.ts | 14 +- src/app/pages/login/login.component.css | 17 +++ src/app/pages/login/login.component.html | 4 +- src/app/pages/login/login.component.ts | 4 +- src/app/pages/register/register.component.css | 121 ++++++++++++++++++ .../pages/register/register.component.html | 67 ++++++++++ src/app/pages/register/register.component.ts | 51 ++++++++ 8 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 src/app/pages/register/register.component.css create mode 100644 src/app/pages/register/register.component.html create mode 100644 src/app/pages/register/register.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index af14fe9..068aaf2 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -8,6 +8,11 @@ export const routes: Routes = [ path: 'login', component: LoginComponent, }, + { + path: 'register', + loadComponent: () => + import('./pages/register/register.component').then((m) => m.RegisterComponent), + }, // Protected routes - require authentication { @@ -32,6 +37,18 @@ export const routes: Routes = [ (m) => m.ProjectDetailsComponent, ), }, + { + path: 'projects/:id/edit', + loadComponent: () => + import('./pages/project-edit/project-edit.component').then((m) => m.ProjectEditComponent), + }, + { + path: 'projects/:id/collaborators/new', + loadComponent: () => + import('./pages/collaborator-add/collaborator-add.component').then( + (m) => m.CollaboratorAddComponent, + ), + }, { path: 'projects/:id/tasks/new', loadComponent: () => diff --git a/src/app/models/auth.models.ts b/src/app/models/auth.models.ts index dc2bdc7..ae3e30d 100644 --- a/src/app/models/auth.models.ts +++ b/src/app/models/auth.models.ts @@ -2,7 +2,7 @@ * Authentication-related type definitions */ -import { Project } from "./projects.models"; +import { Project } from './projects.models'; export interface LoginRequest { email: string; @@ -16,6 +16,18 @@ export interface LoginResponse { token?: string; // Optional: if you need the JWT on client side } +export interface RegisterRequest { + name: string; + email: string; + password: string; +} + +export interface RegisterResponse { + success?: boolean; + message?: string; + user?: User; +} + export interface User { id: string | number; name: string; diff --git a/src/app/pages/login/login.component.css b/src/app/pages/login/login.component.css index 346f65b..983fb63 100644 --- a/src/app/pages/login/login.component.css +++ b/src/app/pages/login/login.component.css @@ -102,3 +102,20 @@ opacity: 0.6; cursor: not-allowed; } + +.register-link { + margin: 16px 0 0 0; + text-align: center; + font-size: 14px; + color: #4b5563; +} + +.register-link a { + color: #667eea; + font-weight: 600; + text-decoration: none; +} + +.register-link a:hover { + text-decoration: underline; +} diff --git a/src/app/pages/login/login.component.html b/src/app/pages/login/login.component.html index 21eb54a..0bf3494 100644 --- a/src/app/pages/login/login.component.html +++ b/src/app/pages/login/login.component.html @@ -14,7 +14,7 @@ + + diff --git a/src/app/pages/login/login.component.ts b/src/app/pages/login/login.component.ts index efb7908..34af9df 100644 --- a/src/app/pages/login/login.component.ts +++ b/src/app/pages/login/login.component.ts @@ -1,6 +1,6 @@ import { Component, inject, signal, OnInit } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { Router } from '@angular/router'; +import { Router, RouterLink } from '@angular/router'; import { CommonModule } from '@angular/common'; import { AuthService } from '../../services/auth.service'; import { catchError, of, throwError } from 'rxjs'; @@ -8,7 +8,7 @@ import { catchError, of, throwError } from 'rxjs'; @Component({ selector: 'app-login', standalone: true, - imports: [FormsModule, CommonModule], + imports: [FormsModule, CommonModule, RouterLink], templateUrl: './login.component.html', styleUrl: './login.component.css', }) diff --git a/src/app/pages/register/register.component.css b/src/app/pages/register/register.component.css new file mode 100644 index 0000000..057608f --- /dev/null +++ b/src/app/pages/register/register.component.css @@ -0,0 +1,121 @@ +.register-container { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 20px; +} + +.register-card { + background: white; + border-radius: 12px; + padding: 40px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 400px; +} + +.register-card h1 { + font-size: 24px; + font-weight: 700; + color: #667eea; + margin: 0 0 10px 0; + text-align: center; +} + +.register-card h2 { + font-size: 28px; + font-weight: 600; + color: #333; + margin: 0 0 30px 0; + text-align: center; +} + +.error-message { + background-color: #fee; + color: #c33; + padding: 12px; + border-radius: 6px; + margin-bottom: 20px; + border: 1px solid #fcc; + font-size: 14px; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #333; + font-size: 14px; +} + +.form-group input { + width: 100%; + padding: 12px 16px; + border: 2px solid #e1e8ed; + border-radius: 8px; + font-size: 15px; + transition: border-color 0.3s; + box-sizing: border-box; +} + +.form-group input:focus { + outline: none; + border-color: #667eea; +} + +.form-group input::placeholder { + color: #aab8c2; +} + +.btn-primary { + width: 100%; + padding: 14px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + margin-top: 10px; +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4); +} + +.btn-primary:active:not(:disabled) { + transform: translateY(0); +} + +.btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.login-link { + margin: 16px 0 0 0; + text-align: center; + font-size: 14px; + color: #4b5563; +} + +.login-link a { + color: #667eea; + font-weight: 600; + text-decoration: none; +} + +.login-link a:hover { + text-decoration: underline; +} diff --git a/src/app/pages/register/register.component.html b/src/app/pages/register/register.component.html new file mode 100644 index 0000000..8e66ce6 --- /dev/null +++ b/src/app/pages/register/register.component.html @@ -0,0 +1,67 @@ +
+
+

{{ 'KanbanCloneAngular' }}

+

Create account

+ + @if (errorMessage()) { +
+ {{ errorMessage() }} +
+ } + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+
diff --git a/src/app/pages/register/register.component.ts b/src/app/pages/register/register.component.ts new file mode 100644 index 0000000..d698a45 --- /dev/null +++ b/src/app/pages/register/register.component.ts @@ -0,0 +1,51 @@ +import { Component, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Router, RouterLink } from '@angular/router'; +import { ApiService } from '../../services/api.service'; +import { RegisterRequest, RegisterResponse } from '../../models/auth.models'; + +@Component({ + selector: 'app-register', + standalone: true, + imports: [CommonModule, FormsModule, RouterLink], + templateUrl: './register.component.html', + styleUrl: './register.component.css', +}) +export class RegisterComponent { + private apiService = inject(ApiService); + private router = inject(Router); + + name = ''; + email = ''; + password = ''; + isLoading = signal(false); + errorMessage = signal(''); + + onSubmit() { + if (!this.name || !this.email || !this.password) { + this.errorMessage.set('Please fill in all fields'); + return; + } + + const payload: RegisterRequest = { + name: this.name.trim(), + email: this.email.trim(), + password: this.password, + }; + + this.isLoading.set(true); + this.errorMessage.set(''); + + this.apiService.post('/users/', payload).subscribe({ + next: () => { + this.isLoading.set(false); + this.router.navigate(['/login']); + }, + error: (error) => { + this.isLoading.set(false); + this.errorMessage.set(error?.error?.message || 'Registration failed.'); + }, + }); + } +}