feat(profile): enhance profile form with validation messages and submission logic

This commit is contained in:
ncoronel 2026-07-08 12:12:22 -03:00
parent 7507580c2c
commit 036b2a950f
2 changed files with 138 additions and 63 deletions

View File

@ -1,56 +1,71 @@
<form [formGroup]="profileForm" class="profile-form">
<form [formGroup]="profileForm" class="profile-form" (ngSubmit)="onSubmit()">
<div class="form-group">
<label>Nombre y Apellido:</label>
<app-input
type="text"
placeholder="Descripción"
placeholder="Nombre y Apellido"
[value]="profileForm.controls['fullName'].value"
(valueChange)="profileForm.controls['fullName'].setValue($event)"
></app-input>
@if (getControlError('fullName'); as errorMessage) {
<small class="text-danger">{{ errorMessage }}</small>
}
</div>
<div class="form-group">
<label>Email:</label>
<app-input
type="email"
placeholder="Descripción"
placeholder="Email"
[value]="profileForm.controls['email'].value"
(valueChange)="profileForm.controls['email'].setValue($event)"
></app-input>
@if (getControlError('email'); as errorMessage) {
<small class="text-danger">{{ errorMessage }}</small>
}
</div>
<div class="form-group">
<label>DNI:</label>
<app-input
type="text"
placeholder="Descripción"
placeholder="DNI"
[value]="profileForm.controls['dni'].value"
(valueChange)="profileForm.controls['dni'].setValue($event)"
></app-input>
@if (getControlError('dni'); as errorMessage) {
<small class="text-danger">{{ errorMessage }}</small>
}
</div>
<div class="form-group">
<label>Teléfono:</label>
<app-input
type="text"
placeholder="Descripción"
placeholder="Teléfono"
[value]="profileForm.controls['phone'].value"
(valueChange)="profileForm.controls['phone'].setValue($event)"
></app-input>
@if (getControlError('phone'); as errorMessage) {
<small class="text-danger">{{ errorMessage }}</small>
}
</div>
<div class="form-group">
<label>Contraseña:</label>
<label>Contraseña (dejar en blanco para no modificar):</label>
<app-input
type="password"
placeholder="Descripción"
placeholder="Contraseña"
[value]="profileForm.controls['password'].value"
(valueChange)="profileForm.controls['password'].setValue($event)"
></app-input>
@if (getControlError('password'); as errorMessage) {
<small class="text-danger">{{ errorMessage }}</small>
}
</div>
<div class="form-actions">
<app-button type="button" variant="cancel" hostClass="action-btn" buttonClass="w-100">Cancelar</app-button>
<app-button type="submit" variant="primary" hostClass="action-btn" buttonClass="w-100">Guardar</app-button>
<div class="form-actions mt-3">
<app-button type="button" variant="cancel" hostClass="action-btn" buttonClass="w-100" (click)="onCancel()">Cancelar</app-button>
<app-button type="submit" variant="primary" hostClass="action-btn" buttonClass="w-100" [disabled]="isSubmitting()">Guardar</app-button>
</div>
</form>

View File

@ -1,8 +1,9 @@
import { Component, inject, effect } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, FormGroup } from '@angular/forms';
import { Component, inject, effect, signal } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { InputComponent } from '../../../../../../shared/components/input/input.component';
import { ButtonComponent } from '../../../../../../shared/components/button/button.component';
import { AuthService } from '../../../../../../core/services/auth/auth.service';
import { ToastService } from '../../../../../../core/services/toast.service';
@Component({
selector: 'app-profile-form',
@ -14,14 +15,16 @@ import { AuthService } from '../../../../../../core/services/auth/auth.service';
export class ProfileForm {
profileForm: FormGroup;
private authService = inject(AuthService);
private toastService = inject(ToastService);
isSubmitting = signal(false);
constructor(private fb: FormBuilder) {
this.profileForm = this.fb.group({
fullName: [''],
email: [''],
dni: [''],
phone: [''],
password: ['']
fullName: ['', [Validators.required, Validators.maxLength(255)]],
email: ['', [Validators.required, Validators.email, Validators.maxLength(255)]],
dni: ['', [Validators.pattern(/^[0-9]{7,8}$/)]],
phone: ['', [Validators.pattern(/^\+?[0-9\s\-]{10,20}$/)]],
password: ['', [Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[\W_]).{8,}$/)]]
});
effect(() => {
@ -37,4 +40,61 @@ export class ProfileForm {
});
}
getControlError(controlName: string): string | null {
const control = this.profileForm.get(controlName);
if (!control || !control.errors || (!control.touched && !this.isSubmitting())) return null;
if (control.errors['required']) return 'Este campo es obligatorio.';
if (control.errors['email']) return 'Ingresa un email válido.';
if (control.errors['maxlength']) return 'Excede el largo máximo.';
if (controlName === 'dni' && control.errors['pattern']) return 'DNI inválido (7-8 números).';
if (controlName === 'phone' && control.errors['pattern']) return 'Teléfono inválido.';
if (controlName === 'password' && control.errors['pattern']) return 'Debe contener mayúscula, minúscula, carácter especial y mínimo 8 caracteres.';
return 'Valor inválido.';
}
onSubmit() {
if (this.profileForm.invalid) {
this.profileForm.markAllAsTouched();
return;
}
this.isSubmitting.set(true);
const formValue = this.profileForm.value;
const payload: any = {
nombre_apellido: formValue.fullName,
email: formValue.email,
};
if (formValue.dni) payload.dni = formValue.dni;
if (formValue.phone) payload.telefono = formValue.phone;
if (formValue.password) payload.password = formValue.password;
this.authService.updateProfile(payload).subscribe({
next: () => {
this.isSubmitting.set(false);
this.toastService.success('Perfil actualizado correctamente.');
},
error: (error: any) => {
this.isSubmitting.set(false);
const errorMessage = error.error?.message || 'Error al actualizar el perfil.';
this.toastService.danger(errorMessage);
}
});
}
onCancel() {
const user = this.authService.user();
if (user) {
this.profileForm.reset({
fullName: user.nombre_apellido,
email: user.email,
dni: user.dni || '',
phone: user.telefono || '',
password: ''
});
} else {
this.profileForm.reset();
}
}
}