Début du login + parametres

This commit is contained in:
2026-06-10 11:49:23 +02:00
parent 1cc9688d00
commit b85d09ee55
25 changed files with 464 additions and 221 deletions
@@ -0,0 +1,51 @@
<ion-header>
<ion-toolbar>
<ion-title>Modifier les coordonnées</ion-title>
<ion-buttons slot="end">
<ion-button (click)="close.emit()">
<ion-icon name="close-outline" />
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<form [formGroup]="form" (ngSubmit)="submit()">
<ion-item [class.invalid]="email.invalid && email.touched">
<ion-input
label="Email"
labelPlacement="floating"
type="email"
formControlName="email"
placeholder="exemple@email.com"
/>
</ion-item>
<ion-note *ngIf="email.invalid && email.touched" color="danger">
Adresse email invalide
</ion-note>
<ion-item [class.invalid]="tel.invalid && tel.touched" class="ion-margin-top">
<ion-input
label="Téléphone"
labelPlacement="floating"
type="tel"
formControlName="tel"
placeholder="0612345678"
/>
</ion-item>
<ion-note *ngIf="tel.invalid && tel.touched" color="danger">
Numéro à 10 chiffres requis
</ion-note>
<ion-button
expand="block"
class="ion-margin-top"
type="submit"
[disabled]="form.invalid"
>
Valider
</ion-button>
</form>
</ion-content>
@@ -0,0 +1,88 @@
import {Component, EventEmitter, inject, Output, signal} from '@angular/core';
import { addIcons } from 'ionicons';
import { closeOutline } from 'ionicons/icons';
import {
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon, IonInput,
IonItem, IonNote,
IonTitle,
IonToolbar
} from "@ionic/angular/standalone";
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
import {firstValueFrom} from "rxjs";
import {KnotsDTOUserUpdateUserContactDto, UsersService} from "../../../services/api";
@Component({
selector: 'app-parameters-coordinates-form',
imports: [
IonHeader,
IonToolbar,
IonTitle,
IonButtons,
IonButton,
IonIcon,
IonContent,
ReactiveFormsModule,
IonItem,
IonInput,
IonNote
],
templateUrl: './parameters-coordinates-form.component.html',
styleUrl: './parameters-coordinates-form.component.css'
})
export class ParametersCoordinatesFormComponent {
@Output() close = new EventEmitter<void>();
private userService = inject(UsersService);
//private authService = inject(AuthService);
loading = signal(false);
coordinatesForm = new FormGroup({
email: new FormControl<string>(null, [Validators.required, Validators.email]),
tel: new FormControl<string>(null, [Validators.required, Validators.pattern(/^\d{10}$/)]),
});
constructor() {
addIcons({ closeOutline });
}
ngOnInit() {
//const user = this.userService.currentUser();
//this.coordinatesForm.patchValue({
//email: user?.email ?? null,
//tel: user?.tel ?? null,
//});
}
get email() { return this.coordinatesForm.get('email')!; }
get tel() { return this.coordinatesForm.get('tel')!; }
async submitForm() {
if (this.coordinatesForm.invalid) return;
this.loading.set(true);
//const user = this.authService.currentUser();
const userValue: KnotsDTOUserUpdateUserContactDto = {
email: this.coordinatesForm.value.email,
tel: this.coordinatesForm.value.tel,
};
try {
//await firstValueFrom(this.userService.patchUserContactEndpoint(user.id, userValue));
this.coordinatesForm.reset();
this.close.emit();
} catch (e) {
console.error('Erreur lors de la mise à jour des coordonnées', e);
}
this.loading.set(false);
}
}
@@ -1,4 +1,4 @@
<button class="coordinates-btn">
<button class="coordinates-btn" (click)="openModal()">
<svg width="35" height="35" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path d="M256 48c-114.9 0-208 93.1-208 208s93.1 208 208 208c42.6 0 81.8-12.8 114.4-34.7 6.6-4.5 8.3-13.5 3.8-20.1-4.5-6.6-13.5-8.3-20.1-3.8C325.9 420.6 292.2 432 256 432c-97 0-176-79-176-176S159 80 256 80s176 79 176 176v40c0 22.1-17.9 40-40 40s-40-17.9-40-40v-40c0-53-43-96-96-96s-96 43-96 96 43 96 96 96c22.1 0 42.4-7.5 58.6-20.1C328.7 354.2 358.1 368 392 368c39.7 0 72-32.3 72-72v-40c0-114.9-93.1-208-208-208zm0 272c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z" fill="#BD5A5A" fill-opacity="0.8"
stroke="#BD5A5A"
@@ -8,4 +8,10 @@
/>
</svg>
<span class="username">Modifier les coordonnées</span>
</button>
</button>
<ion-modal [isOpen]="isModalOpen" (didDismiss)="closeModal()">
<ng-template>
<app-parameters-coordinates-form (close)="closeModal()" />
</ng-template>
</ion-modal>
@@ -1,43 +1,18 @@
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {Component} from '@angular/core';
import { IonModal, IonButton } from '@ionic/angular/standalone';
import {FormsModule} from "@angular/forms";
export interface Coordonnees {
phone: string;
email: string;
}
import {ParametersCoordinatesFormComponent} from "../parameters-coordinates-form/parameters-coordinates-form.component";
@Component({
selector: 'app-parameters-coordinates',
imports: [
FormsModule
],
imports: [IonModal, FormsModule, ParametersCoordinatesFormComponent],
templateUrl: './parameters-coordinates.component.html',
styleUrl: './parameters-coordinates.component.css'
})
export class ParametersCoordinatesComponent {
@Input() isOpen: boolean = false;
@Output() closed = new EventEmitter<void>();
@Output() saved = new EventEmitter<Coordonnees>();
isModalOpen = false;
form: Coordonnees = { phone: '', email: '' };
phoneFocused = false;
emailFocused = false;
close(): void {
this.closed.emit();
}
save(): void {
if (this.form.phone || this.form.email) {
this.saved.emit({ ...this.form });
this.close();
}
}
onOverlayClick(event: MouseEvent): void {
if ((event.target as HTMLElement).classList.contains('modal-overlay')) {
this.close();
}
}
openModal() { this.isModalOpen = true; }
closeModal() { this.isModalOpen = false; }
}
@@ -247,4 +247,10 @@
opacity: 1;
transform: scale(1.15);
}
.field-error {
color: var(--ion-color-danger, #eb445a);
font-size: 0.75rem;
padding: 2px 0 0 4px;
}
}
@@ -41,6 +41,9 @@
</button>
</div>
<!-- Sous la div.field du username -->
<span class="field-error" *ngIf="usernameError()">{{ usernameError() }}</span>
<!-- Bio field -->
<div class="field bio-field">
<ng-container *ngIf="!editingBio(); else editBioBlock">
@@ -1,6 +1,9 @@
import {Component, signal} from '@angular/core';
import {Component, inject, signal} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import {firstValueFrom} from "rxjs";
import {UsersService} from "../../../services/api";
import {AuthService} from "../../../core/auth/auth.service";
@Component({
@@ -10,36 +13,118 @@ import { FormsModule } from '@angular/forms';
styleUrl: './parameters-profile.component.css'
})
export class ParametersProfileComponent {
username = signal('Doggeybag');
bio = signal('Joueur Valorant');
private usersService = inject(UsersService);
private authService = inject(AuthService);
profileImage = signal<string>(null);
username = signal<string>(null);
bio = signal<string>(null);
editingUsername = signal(false);
editingBio = signal(false);
profileImage = signal<string | null>(null);
usernameError = signal<string>(null);
loading = signal(false);
onPhotoChange(event: Event): void {
const input = event.target as HTMLInputElement;
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = (e) => {
this.profileImage.set(e.target?.result as string);
};
reader.readAsDataURL(input.files[0]);
ngOnInit() {
const user = this.authService.currentUser();
this.profileImage.set(user?.profilePicture ?? null);
this.username.set(user?.username ?? null);
this.bio.set(user?.description ?? null);
}
// --- Username ---
toggleEditUsername() {
if (this.editingUsername()) {
this.submitUsername();
} else {
this.usernameError.set(null);
this.editingUsername.set(true);
}
}
triggerFileInput(): void {
const input = document.getElementById('photoInput') as HTMLInputElement;
input?.click();
async submitUsername() {
const value = this.username()?.trim();
if (!value) {
this.usernameError.set('Le pseudo ne peut pas être vide.');
return;
}
if (value === this.authService.currentUser()?.username) {
this.editingUsername.set(false);
return;
}
this.loading.set(true);
const user = this.authService.currentUser();
try {
await firstValueFrom(this.usersService.patchUsernameEndpoint(user.id, { username: value }));
this.usernameError.set(null);
this.editingUsername.set(false);
} catch (e: any) {
if (e?.status === 400) {
this.usernameError.set('Ce nom d\'utilisateur est déjà pris.');
} else {
this.usernameError.set('Erreur lors de la mise à jour.');
}
}
this.loading.set(false);
}
toggleEditUsername(): void {
this.editingUsername.update((v) => !v);
// --- Bio ---
toggleEditBio() {
if (this.editingBio()) {
this.submitBio();
} else {
this.editingBio.set(true);
}
}
toggleEditBio(): void {
this.editingBio.update((v) => !v);
async submitBio() {
this.loading.set(true);
const user = this.authService.currentUser();
try {
await firstValueFrom(this.usersService.patchUserDescriptionEndpoint(user.id, { description: this.bio() }));
this.editingBio.set(false);
} catch (e) {
console.error('Erreur lors de la mise à jour de la bio', e);
}
this.loading.set(false);
}
// --- Photo ---
triggerFileInput() {
document.getElementById('photoInput')?.click();
}
async onPhotoChange(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async () => {
const base64 = reader.result as string;
this.profileImage.set(base64);
this.loading.set(true);
const user = this.authService.currentUser();
try {
await firstValueFrom(this.usersService.patchUserProfilePictureEndpoint(user.id, { profilePicture: base64 }));
} catch (e) {
console.error('Erreur lors de la mise à jour de la photo', e);
}
this.loading.set(false);
};
reader.readAsDataURL(file);
}
}