From dbcceed912d11fc6b4ebe9cb532fb23e26bff01c Mon Sep 17 00:00:00 2001 From: Hugo <hugo.cubino@estudiantes.uva.es> Date: Wed, 27 Nov 2024 09:39:33 +0100 Subject: [PATCH 1/3] Implementacion de formulario con datos de usuario --- angular/RestClient/src/app/app.routes.ts | 5 + .../user/user-form/user-form.component.css | 68 ++++++++++++++ .../user/user-form/user-form.component.html | 79 ++++++++++++++++ .../user-form/user-form.component.spec.ts | 91 +++++++++++++++++++ .../user/user-form/user-form.component.ts | 61 +++++++++++++ .../src/app/shared/user-client.service.ts | 29 ++++++ 6 files changed, 333 insertions(+) create mode 100644 angular/RestClient/src/app/core/features/user/user-form/user-form.component.css create mode 100644 angular/RestClient/src/app/core/features/user/user-form/user-form.component.html create mode 100644 angular/RestClient/src/app/core/features/user/user-form/user-form.component.spec.ts create mode 100644 angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts diff --git a/angular/RestClient/src/app/app.routes.ts b/angular/RestClient/src/app/app.routes.ts index b8ee77b..1ce26ae 100644 --- a/angular/RestClient/src/app/app.routes.ts +++ b/angular/RestClient/src/app/app.routes.ts @@ -5,6 +5,7 @@ import { HotelRegisterComponent } from './core/features/hotel/hotel-register/hot import { MainPageComponent } from './core/features/user/main-page/main-page.component'; import { BookingListComponent } from './core/features/bookings/booking-list/booking-list.component'; import { UserBookingListComponent } from './core/features/user/user-booking-list/user-booking-list.component'; +import { UserFormComponent} from './core/features/user/user-form/user-form.component'; export const routes: Routes = [ { @@ -35,6 +36,10 @@ export const routes: Routes = [ path: 'hotels/:id', component: HotelRegisterComponent, }, + { + path: 'users/:id', + component: UserFormComponent, + }, { path: '**', redirectTo: '', diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.css b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.css new file mode 100644 index 0000000..66cd833 --- /dev/null +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.css @@ -0,0 +1,68 @@ +.form-container { + width: 100%; + max-width: 500px; + margin: 0 auto; + padding: 20px; + background: #f9f9f9; + border: 1px solid #ddd; + border-radius: 8px; + } + + h2 { + text-align: center; + margin-bottom: 20px; + } + + .form-group { + margin-bottom: 15px; + } + + label { + display: block; + font-weight: bold; + margin-bottom: 5px; + } + + input { + width: 100%; + padding: 10px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; + } + + input[readonly] { + background-color: #f5f5f5; + cursor: not-allowed; + } + + .button-group { + display: flex; + gap: 10px; + justify-content: center; + } + + button { + padding: 10px 20px; + font-size: 16px; + border-radius: 4px; + border: none; + cursor: pointer; + } + + .btn-primary { + background-color: #007bff; + color: #fff; + } + + .btn-secondary { + background-color: #6c757d; + color: #fff; + } + + .btn-success { + background-color: #28a745; + color: #fff; + } + \ No newline at end of file diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html new file mode 100644 index 0000000..0a6e33f --- /dev/null +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html @@ -0,0 +1,79 @@ +<div class="form-container"> + <h2>Perfil de Usuario</h2> + <form [formGroup]="userForm"> + <div class="form-group"> + <label for="name">Nombre:</label> + <input + id="name" + type="text" + class="form-control" + formControlName="name" + [readonly]="!isEditing" + /> + </div> + + <div class="form-group"> + <label for="email">Email:</label> + <input + id="email" + type="email" + class="form-control" + formControlName="email" + [readonly]="!isEditing" + /> + </div> + + <div class="form-group" *ngIf="isEditing"> + <label for="currentPassword">Contraseña actual:</label> + <input + id="currentPassword" + type="password" + class="form-control" + formControlName="currentPassword" + placeholder="Introduce tu contraseña actual" + /> + </div> + + <div class="form-group" *ngIf="isEditing"> + <label for="newPassword">Nueva contraseña:</label> + <input + id="newPassword" + type="password" + class="form-control" + formControlName="newPassword" + placeholder="Introduce tu nueva contraseña" + /> + </div> + + <div class="button-group"> + <button + *ngIf="!isEditing" + type="button" + class="btn btn-primary" + (click)="toggleEdit()" + > + Editar + </button> + + <button + *ngIf="isEditing" + type="button" + class="btn btn-secondary" + (click)="cancelEdit()" + > + Cancelar + </button> + + <button + *ngIf="isEditing" + type="submit" + class="btn btn-success" + (click)="saveChanges()" + [disabled]="!userForm.valid" + > + Guardar + </button> + </div> + </form> + </div> + \ No newline at end of file diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.spec.ts b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.spec.ts new file mode 100644 index 0000000..292f234 --- /dev/null +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.spec.ts @@ -0,0 +1,91 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { HttpResponse } from '@angular/common/http'; +import { UserFormComponent } from './user-form.component'; +import { UserClientService } from '../../../../shared/user-client.service'; +import { of } from 'rxjs'; + +describe('UserFormComponent', () => { + let component: UserFormComponent; + let fixture: ComponentFixture<UserFormComponent>; + let userService: UserClientService; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, FormsModule], + declarations: [UserFormComponent], + providers: [UserClientService], + }).compileComponents(); + + fixture = TestBed.createComponent(UserFormComponent); + component = fixture.componentInstance; + userService = TestBed.inject(UserClientService); + + spyOn(userService, 'getCurrentUser').and.returnValue( + of({ + id: 1, + name: 'John Doe', + email: 'johndoe@example.com', + rol: 'CONSUMER', + status: 'WITH_ACTIVE_BOOKINGS', + }) + ); + + spyOn(userService, 'updateUser').and.returnValue(of(new HttpResponse({ body: 'User updated successfully' }))); + spyOn(userService, 'updatePassword').and.returnValue(of(new HttpResponse({ body: 'Password updated successfully' }))); + + fixture.detectChanges(); + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should load user data on initialization', () => { + expect(component.userForm.value).toEqual({ + name: 'John Doe', + email: 'johndoe@example.com', + password: '', + confirmPassword: '', + }); + }); + + it('should call updateUser when saving valid user data', () => { + component.userForm.patchValue({ + name: 'Jane Doe', + email: 'janedoe@example.com', + }); + + component.saveChanges(); + + expect(userService.updateUser).toHaveBeenCalledWith({ + name: 'Jane Doe', + email: 'janedoe@example.com', + }); + }); + + it('should call updatePassword when password is updated and matches confirmPassword', () => { + component.userForm.patchValue({ + password: 'newpassword123', + confirmPassword: 'newpassword123', + }); + + component.saveChanges(); + + expect(userService.updatePassword).toHaveBeenCalledWith( + '', + 'newpassword123' + ); + }); + + it('should not call updatePassword if password and confirmPassword do not match', () => { + component.userForm.patchValue({ + password: 'newpassword123', + confirmPassword: 'differentpassword', + }); + + component.saveChanges(); + + expect(userService.updatePassword).not.toHaveBeenCalled(); + }); +}); diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts new file mode 100644 index 0000000..238d811 --- /dev/null +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts @@ -0,0 +1,61 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { UserClientService } from '../../../../shared/user-client.service'; + +@Component({ + selector: 'app-user-form', + templateUrl: './user-form.component.html', + styleUrls: ['./user-form.component.css'], +}) +export class UserFormComponent implements OnInit { + userForm!: FormGroup; + isEditing = false; + + constructor(private fb: FormBuilder, private userService: UserClientService) {} + + ngOnInit(): void { + this.initializeForm(); + this.loadUserData(); + } + + private initializeForm(): void { + this.userForm = this.fb.group({ + name: [{ value: '', disabled: true }, Validators.required], + email: [{ value: '', disabled: true }, [Validators.required, Validators.email]], + currentPassword: [''], // Solo habilitado en modo edición + newPassword: [''], // Solo habilitado en modo edición + }); + } + + private loadUserData(): void { + this.userService.getCurrentUser().subscribe((user) => { + this.userForm.patchValue({ + name: user.name, + email: user.email, + }); + }); + } + + toggleEdit(): void { + this.isEditing = true; + this.userForm.get('name')?.enable(); + this.userForm.get('email')?.enable(); + } + + cancelEdit(): void { + this.isEditing = false; + this.loadUserData(); // Volver a cargar los datos originales + this.userForm.get('name')?.disable(); + this.userForm.get('email')?.disable(); + } + + saveChanges(): void { + if (this.userForm.valid) { + const updatedData = this.userForm.value; + this.userService.updateUser(updatedData).subscribe(() => { + this.isEditing = false; + this.loadUserData(); + }); + } + } +} diff --git a/angular/RestClient/src/app/shared/user-client.service.ts b/angular/RestClient/src/app/shared/user-client.service.ts index 2a09c6d..3e42700 100644 --- a/angular/RestClient/src/app/shared/user-client.service.ts +++ b/angular/RestClient/src/app/shared/user-client.service.ts @@ -8,18 +8,22 @@ import { User, UserState } from '../../types'; }) export class UserClientService { private readonly URI = environment.userAPI; + constructor(private http: HttpClient) {} + // Obtener un usuario por ID getUser(userId: number) { return this.http.get<User>(`${this.URI}/${userId}`); } + // Obtener todos los usuarios getAllUsers() { return this.http.get<User[]>(this.URI, { observe: 'body', }); } + // Cambiar estado de un usuario alterUserStatus(userId: number, status: UserState) { return this.http.patch( `${this.URI}/${userId}`, @@ -32,4 +36,29 @@ export class UserClientService { } ); } + + // Obtener el usuario actual (autenticado) + getCurrentUser() { + return this.http.get<User>(`${this.URI}/me`); + } + + // Actualizar los datos del usuario + updateUser(user: Partial<User>) { + return this.http.patch(`${this.URI}/me`, user, { + observe: 'response', + responseType: 'text', + }); + } + + // Cambiar la contraseña del usuario + updatePassword(currentPassword: string, newPassword: string) { + return this.http.patch( + `${this.URI}/me/password`, + { currentPassword, newPassword }, + { + observe: 'response', + responseType: 'text', + } + ); + } } -- GitLab From 5032d5b85911ea1eb534943b33aaea0e5782f21a Mon Sep 17 00:00:00 2001 From: Hugo <hugo.cubino@estudiantes.uva.es> Date: Thu, 28 Nov 2024 16:46:00 +0100 Subject: [PATCH 2/3] mocks quitados --- .../user/main-page/main-page.component.ts | 26 ++- .../user/user-form/user-form.component.css | 147 ++++++++-------- .../user/user-form/user-form.component.html | 159 +++++++++--------- .../user/user-form/user-form.component.ts | 6 +- angular/RestClient/src/mocks/users.json | 14 ++ 5 files changed, 197 insertions(+), 155 deletions(-) create mode 100644 angular/RestClient/src/mocks/users.json diff --git a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts b/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts index e609b7e..468ec54 100644 --- a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts +++ b/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts @@ -1,11 +1,11 @@ -// main-page.component.ts import { Component, OnInit } from '@angular/core'; import { User, UserStateFilter } from '../../../../../types'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; -import users from '../../../../../mocks/users.json'; import { RouterModule } from '@angular/router'; import { UserClientService } from '../../../../shared/user-client.service'; +import mockUsers from '../../../../../mocks/users.json'; // Renombrado para claridad + @Component({ standalone: true, imports: [FormsModule, CommonModule, RouterModule], @@ -21,16 +21,24 @@ export class MainPageComponent implements OnInit { constructor(private userClient: UserClientService) {} ngOnInit(): void { - this.users = users as unknown as User[]; - this.userClient.getAllUsers().subscribe((data: User[]) => { - this.users = data; - this.filteredUsers = data; // Inicialmente, muestra todos los usuarios + // Validar que el mock sea del tipo correcto + const isValidMock = Array.isArray(mockUsers) && mockUsers.every(user => 'id' in user && 'name' in user && 'status' in user); + this.users = isValidMock ? (mockUsers as User[]) : []; + this.filteredUsers = [...this.users]; + + // Sobrescribir con datos reales si están disponibles + this.userClient.getAllUsers().subscribe({ + next: (data: User[]) => { + this.users = data; + this.filteredUsers = [...data]; + }, + error: (err) => console.error('Error al cargar usuarios:', err), }); } filterUsers(): void { if (this.selectedStatus === 'All') { - this.filteredUsers = this.users; + this.filteredUsers = [...this.users]; } else { this.filteredUsers = this.users.filter( (user) => user.status === this.selectedStatus @@ -38,7 +46,7 @@ export class MainPageComponent implements OnInit { } } - getState(user: User) { + getState(user: User): string { switch (user.status) { case 'NO_BOOKINGS': return 'SIN RESERVAS'; @@ -46,6 +54,8 @@ export class MainPageComponent implements OnInit { return 'CON RESERVAS ACTIVAS'; case 'WITH_INACTIVE_BOOKINGS': return 'CON RESERVAS INACTIVAS'; + default: + return 'ESTADO DESCONOCIDO'; } } } diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.css b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.css index 66cd833..1eef9bb 100644 --- a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.css +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.css @@ -1,68 +1,81 @@ .form-container { - width: 100%; - max-width: 500px; - margin: 0 auto; - padding: 20px; - background: #f9f9f9; - border: 1px solid #ddd; - border-radius: 8px; - } - - h2 { - text-align: center; - margin-bottom: 20px; - } - - .form-group { - margin-bottom: 15px; - } - - label { - display: block; - font-weight: bold; - margin-bottom: 5px; - } - - input { - width: 100%; - padding: 10px; - font-size: 16px; - border: 1px solid #ccc; - border-radius: 4px; - box-sizing: border-box; - } - - input[readonly] { - background-color: #f5f5f5; - cursor: not-allowed; - } - - .button-group { - display: flex; - gap: 10px; - justify-content: center; - } - - button { - padding: 10px 20px; - font-size: 16px; - border-radius: 4px; - border: none; - cursor: pointer; - } - - .btn-primary { - background-color: #007bff; - color: #fff; - } - - .btn-secondary { - background-color: #6c757d; - color: #fff; - } - - .btn-success { - background-color: #28a745; - color: #fff; - } - \ No newline at end of file + max-width: 400px; + margin: 50px auto; + background: #f8f9fa; + padding: 20px; + border-radius: 10px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + font-family: Arial, sans-serif; +} + +h2 { + text-align: center; + margin-bottom: 20px; + color: #333; +} + +.form-group { + margin-bottom: 15px; +} + +label { + display: block; + font-weight: bold; + margin-bottom: 5px; + color: #555; +} + +input { + width: 100%; + padding: 10px; + font-size: 14px; + border: 1px solid #ccc; + border-radius: 5px; + background: #fff; +} + +input[readonly] { + background: #f3f3f3; +} + +.button-group { + display: flex; + justify-content: space-between; + margin-top: 20px; +} + +.btn { + padding: 10px 20px; + font-size: 14px; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.btn-primary { + background-color: #007bff; + color: white; +} + +.btn-primary:hover { + background-color: #0056b3; +} + +.btn-secondary { + background-color: #6c757d; + color: white; +} + +.btn-secondary:hover { + background-color: #5a6268; +} + +.btn-success { + background-color: #28a745; + color: white; +} + +.btn-success:hover { + background-color: #218838; +} diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html index 0a6e33f..92bcb7c 100644 --- a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html @@ -1,79 +1,82 @@ <div class="form-container"> - <h2>Perfil de Usuario</h2> - <form [formGroup]="userForm"> - <div class="form-group"> - <label for="name">Nombre:</label> - <input - id="name" - type="text" - class="form-control" - formControlName="name" - [readonly]="!isEditing" - /> - </div> - - <div class="form-group"> - <label for="email">Email:</label> - <input - id="email" - type="email" - class="form-control" - formControlName="email" - [readonly]="!isEditing" - /> - </div> - - <div class="form-group" *ngIf="isEditing"> - <label for="currentPassword">Contraseña actual:</label> - <input - id="currentPassword" - type="password" - class="form-control" - formControlName="currentPassword" - placeholder="Introduce tu contraseña actual" - /> - </div> - - <div class="form-group" *ngIf="isEditing"> - <label for="newPassword">Nueva contraseña:</label> - <input - id="newPassword" - type="password" - class="form-control" - formControlName="newPassword" - placeholder="Introduce tu nueva contraseña" - /> - </div> - - <div class="button-group"> - <button - *ngIf="!isEditing" - type="button" - class="btn btn-primary" - (click)="toggleEdit()" - > - Editar - </button> - - <button - *ngIf="isEditing" - type="button" - class="btn btn-secondary" - (click)="cancelEdit()" - > - Cancelar - </button> - - <button - *ngIf="isEditing" - type="submit" - class="btn btn-success" - (click)="saveChanges()" - [disabled]="!userForm.valid" - > - Guardar - </button> - </div> - </form> - </div> - \ No newline at end of file + <h2>Perfil de Usuario</h2> + <form [formGroup]="userForm" (ngSubmit)="saveChanges()"> + <!-- Campo Nombre --> + <div class="form-group"> + <label for="name">Nombre:</label> + <input + id="name" + type="text" + class="form-control" + formControlName="name" + [readonly]="!isEditing" + /> + </div> + + <!-- Campo Email --> + <div class="form-group"> + <label for="email">Email:</label> + <input + id="email" + type="email" + class="form-control" + formControlName="email" + [readonly]="!isEditing" + /> + </div> + + <!-- Campo Contraseña Actual (solo en edición) --> + <div class="form-group" *ngIf="isEditing"> + <label for="currentPassword">Contraseña actual:</label> + <input + id="currentPassword" + type="password" + class="form-control" + formControlName="currentPassword" + placeholder="Introduce tu contraseña actual" + /> + </div> + + <!-- Campo Nueva Contraseña (solo en edición) --> + <div class="form-group" *ngIf="isEditing"> + <label for="newPassword">Nueva contraseña:</label> + <input + id="newPassword" + type="password" + class="form-control" + formControlName="newPassword" + placeholder="Introduce tu nueva contraseña" + /> + </div> + + <!-- Grupo de Botones --> + <div class="button-group"> + <button + *ngIf="!isEditing" + type="button" + class="btn btn-primary" + (click)="toggleEdit()" + > + Editar + </button> + + <button + *ngIf="isEditing" + type="button" + class="btn btn-secondary" + (click)="cancelEdit()" + > + Cancelar + </button> + + <button + *ngIf="isEditing" + type="submit" + class="btn btn-success" + [disabled]="!userForm.valid" + > + Guardar + </button> + </div> + </form> +</div> diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts index 238d811..9627686 100644 --- a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts @@ -1,11 +1,13 @@ import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms'; import { UserClientService } from '../../../../shared/user-client.service'; @Component({ + standalone: true, selector: 'app-user-form', templateUrl: './user-form.component.html', styleUrls: ['./user-form.component.css'], + imports: [ReactiveFormsModule, FormsModule], }) export class UserFormComponent implements OnInit { userForm!: FormGroup; @@ -15,7 +17,7 @@ export class UserFormComponent implements OnInit { ngOnInit(): void { this.initializeForm(); - this.loadUserData(); + // this.loadUserData(); } private initializeForm(): void { diff --git a/angular/RestClient/src/mocks/users.json b/angular/RestClient/src/mocks/users.json new file mode 100644 index 0000000..79c155f --- /dev/null +++ b/angular/RestClient/src/mocks/users.json @@ -0,0 +1,14 @@ +[ + { + "id": 1, + "name": "John Doe", + "email": "jon@com", + "status": "NO_BOOKINGS" + }, + { + "id": 2, + "name": "Jane Smith", + "status": "WITH_ACTIVE_BOOKINGS" + } + ] + \ No newline at end of file -- GitLab From 4a0ddf6137fabad9c99574168aab0be0667bc8e2 Mon Sep 17 00:00:00 2001 From: migudel <miguel.moras@estudiantes.uva.es> Date: Thu, 28 Nov 2024 17:54:31 +0100 Subject: [PATCH 3/3] Avances en cabecera --- .gitignore | 7 ++- angular/RestClient/src/app/app.routes.ts | 6 +- .../user/main-page/main-page.component.ts | 17 +++--- .../user/user-form/user-form.component.html | 25 +++----- .../user/user-form/user-form.component.ts | 48 ++++++++++++--- .../core/navigation/navigation.component.html | 58 +++++++++++++++++++ .../core/navigation/navigation.component.ts | 23 +++++++- .../src/app/shared/auth-client.service.ts | 22 +++---- .../src/app/shared/user-client.service.ts | 4 +- angular/RestClient/src/mocks/users.json | 14 ----- angular/RestClient/src/mocks/users.ts | 18 ++++++ angular/RestClient/src/types/User.d.ts | 12 +++- .../controllers/AuthController.java | 1 + 13 files changed, 191 insertions(+), 64 deletions(-) delete mode 100644 angular/RestClient/src/mocks/users.json create mode 100644 angular/RestClient/src/mocks/users.ts diff --git a/.gitignore b/.gitignore index 73cfdb6..7498efe 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,9 @@ taller *.pdf **/target/ -**/.vscode \ No newline at end of file +**/.vscode + +**/tmp +**/*.tmp +**/target +**/*.ln \ No newline at end of file diff --git a/angular/RestClient/src/app/app.routes.ts b/angular/RestClient/src/app/app.routes.ts index 1ce26ae..51aacbb 100644 --- a/angular/RestClient/src/app/app.routes.ts +++ b/angular/RestClient/src/app/app.routes.ts @@ -5,7 +5,7 @@ import { HotelRegisterComponent } from './core/features/hotel/hotel-register/hot import { MainPageComponent } from './core/features/user/main-page/main-page.component'; import { BookingListComponent } from './core/features/bookings/booking-list/booking-list.component'; import { UserBookingListComponent } from './core/features/user/user-booking-list/user-booking-list.component'; -import { UserFormComponent} from './core/features/user/user-form/user-form.component'; +import { UserFormComponent } from './core/features/user/user-form/user-form.component'; export const routes: Routes = [ { @@ -40,6 +40,10 @@ export const routes: Routes = [ path: 'users/:id', component: UserFormComponent, }, + { + path: 'register', + component: UserFormComponent, + }, { path: '**', redirectTo: '', diff --git a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts b/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts index 468ec54..c3506bb 100644 --- a/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts +++ b/angular/RestClient/src/app/core/features/user/main-page/main-page.component.ts @@ -1,10 +1,10 @@ import { Component, OnInit } from '@angular/core'; -import { User, UserStateFilter } from '../../../../../types'; +import { Client, User, UserStateFilter } from '../../../../../types'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { UserClientService } from '../../../../shared/user-client.service'; -import mockUsers from '../../../../../mocks/users.json'; // Renombrado para claridad +import { users } from '../../../../../mocks/users'; // Renombrado para claridad @Component({ standalone: true, @@ -14,21 +14,22 @@ import mockUsers from '../../../../../mocks/users.json'; // Renombrado para clar styleUrls: ['./main-page.component.css'], }) export class MainPageComponent implements OnInit { - users: User[] = []; - filteredUsers: User[] = []; + users: Client[] = []; + filteredUsers: Client[] = []; selectedStatus: UserStateFilter = 'All'; constructor(private userClient: UserClientService) {} ngOnInit(): void { // Validar que el mock sea del tipo correcto - const isValidMock = Array.isArray(mockUsers) && mockUsers.every(user => 'id' in user && 'name' in user && 'status' in user); - this.users = isValidMock ? (mockUsers as User[]) : []; + // const isValidMock = Array.isArray(mockUsers) && mockUsers.every(user => 'id' in user && 'name' in user && 'status' in user); + // this.users = isValidMock ? (mockUsers as User[]) : []; + this.users = users; this.filteredUsers = [...this.users]; // Sobrescribir con datos reales si están disponibles this.userClient.getAllUsers().subscribe({ - next: (data: User[]) => { + next: (data: Client[]) => { this.users = data; this.filteredUsers = [...data]; }, @@ -46,7 +47,7 @@ export class MainPageComponent implements OnInit { } } - getState(user: User): string { + getState(user: Client): string { switch (user.status) { case 'NO_BOOKINGS': return 'SIN RESERVAS'; diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html index 92bcb7c..d68f32b 100644 --- a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.html @@ -25,8 +25,9 @@ /> </div> + @if (isEditing) { <!-- Campo Contraseña Actual (solo en edición) --> - <div class="form-group" *ngIf="isEditing"> + <div class="form-group"> <label for="currentPassword">Contraseña actual:</label> <input id="currentPassword" @@ -38,7 +39,7 @@ </div> <!-- Campo Nueva Contraseña (solo en edición) --> - <div class="form-group" *ngIf="isEditing"> + <div class="form-group"> <label for="newPassword">Nueva contraseña:</label> <input id="newPassword" @@ -48,35 +49,27 @@ placeholder="Introduce tu nueva contraseña" /> </div> + } <!-- Grupo de Botones --> <div class="button-group"> - <button - *ngIf="!isEditing" - type="button" - class="btn btn-primary" - (click)="toggleEdit()" - > + @if (!isEditing) { + <button type="button" class="btn btn-primary" (click)="toggleEdit()"> Editar </button> - - <button - *ngIf="isEditing" - type="button" - class="btn btn-secondary" - (click)="cancelEdit()" - > + } @else { + <button type="button" class="btn btn-secondary" (click)="cancelEdit()"> Cancelar </button> <button - *ngIf="isEditing" type="submit" class="btn btn-success" [disabled]="!userForm.valid" > Guardar </button> + } </div> </form> </div> diff --git a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts index 9627686..558e1c5 100644 --- a/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts +++ b/angular/RestClient/src/app/core/features/user/user-form/user-form.component.ts @@ -1,6 +1,14 @@ import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { + FormBuilder, + FormGroup, + Validators, + ReactiveFormsModule, + FormsModule, +} from '@angular/forms'; import { UserClientService } from '../../../../shared/user-client.service'; +import { users } from '../../../../../mocks/users'; +import { ActivatedRoute } from '@angular/router'; @Component({ standalone: true, @@ -12,30 +20,52 @@ import { UserClientService } from '../../../../shared/user-client.service'; export class UserFormComponent implements OnInit { userForm!: FormGroup; isEditing = false; + id = 0; - constructor(private fb: FormBuilder, private userService: UserClientService) {} + constructor( + private fb: FormBuilder, + private userService: UserClientService, + private route: ActivatedRoute + ) {} ngOnInit(): void { this.initializeForm(); - // this.loadUserData(); + this.route.paramMap.subscribe({ + next: (params) => { + const id = Number(params.get('id')); + if (id) { + this.id = id; + this.isEditing = true; + this.loadUserData(); + } + }, + }); } private initializeForm(): void { this.userForm = this.fb.group({ name: [{ value: '', disabled: true }, Validators.required], - email: [{ value: '', disabled: true }, [Validators.required, Validators.email]], + email: [ + { value: '', disabled: true }, + [Validators.required, Validators.email], + ], currentPassword: [''], // Solo habilitado en modo edición newPassword: [''], // Solo habilitado en modo edición }); } private loadUserData(): void { - this.userService.getCurrentUser().subscribe((user) => { - this.userForm.patchValue({ - name: user.name, - email: user.email, + // this.userService.getCurrentUser().subscribe((user) => { + console.log({ id: this.id }); + users + .filter((u) => u.id == this.id) + .slice(0) + .map((user) => { + this.userForm.patchValue({ + name: user.name, + email: user.email, + }); }); - }); } toggleEdit(): void { diff --git a/angular/RestClient/src/app/core/navigation/navigation.component.html b/angular/RestClient/src/app/core/navigation/navigation.component.html index 6361546..6ba3f13 100644 --- a/angular/RestClient/src/app/core/navigation/navigation.component.html +++ b/angular/RestClient/src/app/core/navigation/navigation.component.html @@ -8,5 +8,63 @@ <li> <a class="btn" [routerLink]="['/bookings', 'search']">Nueva Reserva</a> </li> + <li class="ml-auto"> + @if (isLogged){ + <!-- Dropdown para usuario registrado --> + <div class="relative ml-6"> + <button + (click)="toggleDropdown()" + class="flex items-center space-x-2 p-2 bg-blue-500 text-white rounded hover:bg-blue-600 focus:outline-none" + > + <span>{{ userName }}</span> + <svg + xmlns="http://www.w3.org/2000/svg" + class="h-5 w-5" + viewBox="0 0 20 20" + fill="currentColor" + > + <path + fill-rule="evenodd" + d="M5.23 7.21a.75.75 0 011.06.02L10 10.94l3.71-3.71a.75.75 0 111.06 1.06l-4 4a.75.75 0 01-1.06 0l-4-4a.75.75 0 01.02-1.06z" + clip-rule="evenodd" + /> + </svg> + </button> + + <!-- Menú desplegable --> + @if (dropdownOpen) { + <div + class="absolute right-0 mt-2 w-48 bg-slate-700 border border-gray-500 rounded shadow-lg" + > + <ul> + <li> + <a + class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" + [routerLink]="['/bookings']" + > + Mis Reservas + </a> + </li> + <li> + <a + class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" + > + <!-- (click)="logout()" --> + Cerrar Sesión + </a> + </li> + </ul> + </div> + } + </div> + + } @else { + <a + class="btn bg-blue-500 text-white ml-6 hover:bg-blue-600" + [routerLink]="['/login']" + >Login</a + > + } + </li> </ul> </nav> diff --git a/angular/RestClient/src/app/core/navigation/navigation.component.ts b/angular/RestClient/src/app/core/navigation/navigation.component.ts index ccdab77..69efb6a 100644 --- a/angular/RestClient/src/app/core/navigation/navigation.component.ts +++ b/angular/RestClient/src/app/core/navigation/navigation.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { Router, RouterModule } from '@angular/router'; +import { AuthClientService } from '../../shared/auth-client.service'; @Component({ selector: 'app-navigation', standalone: true, @@ -7,4 +8,24 @@ import { Router, RouterModule } from '@angular/router'; templateUrl: './navigation.component.html', styleUrl: './navigation.component.css', }) -export class NavigationComponent {} +export class NavigationComponent { + isLogged = true; + userName = 'User'; + dropdownOpen = false; + + constructor(private authClient: AuthClientService) {} + + toggleDropdown() { + this.dropdownOpen = !this.dropdownOpen; + this.authClient.login('migudel@dev.com', 'NQSASorry').subscribe({ + next: (response) => { + console.log(response); + alert('OKEY! You are logged'); + }, + error: (error) => { + console.error(error); + alert('Error! You are not logged'); + }, + }); + } +} diff --git a/angular/RestClient/src/app/shared/auth-client.service.ts b/angular/RestClient/src/app/shared/auth-client.service.ts index 61bf7a2..931fc7d 100644 --- a/angular/RestClient/src/app/shared/auth-client.service.ts +++ b/angular/RestClient/src/app/shared/auth-client.service.ts @@ -13,17 +13,17 @@ export class AuthClientService { login(email: String, password: String) { return this.http.post( `${this.URI}/login`, - { email, password }, - { - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': - 'GET, POST, OPTIONS, PUT, PATCH, DELETE', - 'Access-Control-Allow-Headers': 'X-Requested-With,content-type', - 'Access-Control-Allow-Credentials': 'true', - }, - } + { email, password } + // { + // headers: { + // 'Content-Type': 'application/json', + // 'Access-Control-Allow-Origin': '*', + // 'Access-Control-Allow-Methods': + // 'GET, POST, OPTIONS, PUT, PATCH, DELETE', + // 'Access-Control-Allow-Headers': 'X-Requested-With,content-type', + // 'Access-Control-Allow-Credentials': 'true', + // }, + // } ); } diff --git a/angular/RestClient/src/app/shared/user-client.service.ts b/angular/RestClient/src/app/shared/user-client.service.ts index 3e42700..7383a72 100644 --- a/angular/RestClient/src/app/shared/user-client.service.ts +++ b/angular/RestClient/src/app/shared/user-client.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { environment } from '../../../environments/environment'; -import { User, UserState } from '../../types'; +import { Client, User, UserState } from '../../types'; @Injectable({ providedIn: 'root', @@ -18,7 +18,7 @@ export class UserClientService { // Obtener todos los usuarios getAllUsers() { - return this.http.get<User[]>(this.URI, { + return this.http.get<Client[]>(this.URI, { observe: 'body', }); } diff --git a/angular/RestClient/src/mocks/users.json b/angular/RestClient/src/mocks/users.json deleted file mode 100644 index 79c155f..0000000 --- a/angular/RestClient/src/mocks/users.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "id": 1, - "name": "John Doe", - "email": "jon@com", - "status": "NO_BOOKINGS" - }, - { - "id": 2, - "name": "Jane Smith", - "status": "WITH_ACTIVE_BOOKINGS" - } - ] - \ No newline at end of file diff --git a/angular/RestClient/src/mocks/users.ts b/angular/RestClient/src/mocks/users.ts new file mode 100644 index 0000000..ffdf8f2 --- /dev/null +++ b/angular/RestClient/src/mocks/users.ts @@ -0,0 +1,18 @@ +import { Client, User } from '../types'; + +export const users: Client[] = [ + { + id: 1, + name: 'John Doe', + email: 'jon@com', + rol: 'CLIENT', + status: 'NO_BOOKINGS', + }, + { + id: 2, + name: 'Angela Doe', + email: 'angle@com', + rol: 'CLIENT', + status: 'NO_BOOKINGS', + }, +]; diff --git a/angular/RestClient/src/types/User.d.ts b/angular/RestClient/src/types/User.d.ts index 1ecc3d7..277f806 100644 --- a/angular/RestClient/src/types/User.d.ts +++ b/angular/RestClient/src/types/User.d.ts @@ -2,10 +2,20 @@ export interface User { id: number; name: string; email: String; - // status: "noBookings" | "withActiveBookings" | "withInactiveBookings"; + rol: UserRol; +} + +export interface Client extends User { status: UserState; + // bookings: number[] // Booking[] } +export interface HotelAdmin extends User { + // hotels: number[] // Hotel[] +} + +export type UserRol = 'ADMIN' | 'CLIENT' | 'HOTEL_ADMIN'; + export type UserStateFilter = 'All' | UserState; export type UserState = diff --git a/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java b/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java index 08ad46a..a877bb7 100644 --- a/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java +++ b/java/services/auth/src/main/java/com/uva/authentication/controllers/AuthController.java @@ -9,6 +9,7 @@ import com.uva.authentication.models.*; import com.uva.authentication.services.AuthService; @RestController +@CrossOrigin(origins = "*") public class AuthController { @Autowired -- GitLab