From 6aee03878ff14043a9e282c60757e32d13ddec28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20HUSS?= <theophile.huss@etu.unistra.fr>
Date: Thu, 5 Dec 2024 17:20:15 +0100
Subject: [PATCH 1/5] feat: Add new component sign

---
 src/app/modules/profile/profile.module.ts     |   5 +-
 .../modules/profile/sign/sign.component.css   |  38 ++++++
 .../modules/profile/sign/sign.component.html  |  38 ++++++
 .../modules/profile/sign/sign.component.ts    | 112 ++++++++++++++++++
 4 files changed, 191 insertions(+), 2 deletions(-)
 create mode 100644 src/app/modules/profile/sign/sign.component.css
 create mode 100644 src/app/modules/profile/sign/sign.component.html
 create mode 100644 src/app/modules/profile/sign/sign.component.ts

diff --git a/src/app/modules/profile/profile.module.ts b/src/app/modules/profile/profile.module.ts
index e34bddb..1f1b7cf 100644
--- a/src/app/modules/profile/profile.module.ts
+++ b/src/app/modules/profile/profile.module.ts
@@ -6,9 +6,10 @@ import { CreateCustomQuizComponent } from './create-custom-quiz/create-custom-qu
 import { ReactiveFormsModule } from '@angular/forms';
 import { MatIconModule } from '@angular/material/icon';
 import { SharedModule } from '../../shared/shared.module';
+import { SignComponent } from './sign/sign.component';
 
 @NgModule({
-    declarations: [ProfileQuizListComponent, ProfileInfoComponent, CreateCustomQuizComponent],
+    declarations: [ProfileQuizListComponent, ProfileInfoComponent, CreateCustomQuizComponent, SignComponent],
     imports: [CommonModule, ReactiveFormsModule, MatIconModule, SharedModule],
 })
-export class ProfileModule { }
+export class ProfileModule {}
diff --git a/src/app/modules/profile/sign/sign.component.css b/src/app/modules/profile/sign/sign.component.css
new file mode 100644
index 0000000..9528daa
--- /dev/null
+++ b/src/app/modules/profile/sign/sign.component.css
@@ -0,0 +1,38 @@
+.button-header {
+    background-color: #ffffff;
+    border-radius: 10px;
+    width: 80%;
+    height: 10%;
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 10px;
+    padding: 1rem;
+}
+
+.button-header > button {
+    background: none;
+    border: none;
+    height: 80%;
+    width: 100%;
+    padding: 10px 20px;
+    font-size: 16px;
+    cursor: pointer;
+    border-bottom: 3px solid #b9b9b9;
+    color: #b9b9b9;
+}
+
+.button-header > button:hover {
+    border-bottom-color: #2b73fe;
+    color: #2b73fe;
+}
+
+.login-signup-button {
+    height: 100%;
+    width: 80%;
+}
+
+.wrong {
+    color: red;
+    font-size: 12px;
+    margin-top: 5px;
+}
diff --git a/src/app/modules/profile/sign/sign.component.html b/src/app/modules/profile/sign/sign.component.html
new file mode 100644
index 0000000..9db6a8a
--- /dev/null
+++ b/src/app/modules/profile/sign/sign.component.html
@@ -0,0 +1,38 @@
+<p>sign works!</p>
+<div class="button-header">
+    <button (click)="showLogin()">LOGIN</button>
+    <button (click)="showSignup()">SIGNUP</button>
+</div>
+<div class="content">
+    <form [formGroup]="formGroup">
+        <input
+            formControlName="email"
+            placeholder="EMAIL"
+            (blur)="checkValidity('email')"
+            [ngClass]="{ wrong: !formGroup.get('email')?.valid && formGroup.get('email')?.touched }"
+        />
+        <input
+            formControlName="password"
+            placeholder="PASSWORD"
+            (blur)="checkValidity('password')"
+            [ngClass]="{ wrong: !formGroup.get('password')?.valid && formGroup.get('password')?.touched }"
+        />
+        <input
+            formControlName="confirm_password"
+            placeholder="CONFIRM PASSWORD"
+            *ngIf="signup"
+            (blur)="checkValidity('confirm_password')"
+            [ngClass]="{
+                wrong:
+                    formGroup.get('confirm_password')?.errors?.['missMatch'] && formGroup.get('confirm_password')?.touched
+            }"
+        />
+    </form>
+    <button
+        [disabled]="!formGroup.valid || (signup && formGroup.get('confirm_password')?.errors?.['missMatch'])"
+        (click)="submit()"
+        [ngClass]="{ valid: formGroup.valid }"
+    >
+        @if (login) { LOGIN } @else { SIGNUP }
+    </button>
+</div>
diff --git a/src/app/modules/profile/sign/sign.component.ts b/src/app/modules/profile/sign/sign.component.ts
new file mode 100644
index 0000000..4cc90eb
--- /dev/null
+++ b/src/app/modules/profile/sign/sign.component.ts
@@ -0,0 +1,112 @@
+import { Component, OnInit } from '@angular/core';
+import { AbstractControl, FormBuilder, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
+import { UserService } from '../../../services/user.service';
+import { Router } from '@angular/router';
+import Swal from 'sweetalert2';
+
+@Component({
+    selector: 'app-sign',
+    templateUrl: './sign.component.html',
+    styleUrl: './sign.component.css',
+})
+export class SignComponent implements OnInit {
+    constructor(private fb: FormBuilder, private userService: UserService, private router: Router) {}
+    formGroup: FormGroup = new FormGroup({});
+    login: boolean = true;
+    signup: boolean = false;
+    ngOnInit(): void {
+        this.formGroup = new FormGroup(
+            {
+                email: this.fb.control(null, [Validators.required]),
+                password: this.fb.control(null, [Validators.required, Validators.minLength(8)]),
+                confirm_password: this.fb.control(''),
+            },
+            { validators: this.passwordMatchValidator() }
+        );
+    }
+
+    passwordMatchValidator(): ValidatorFn {
+        return (control: AbstractControl): ValidationErrors | null => {
+            if (!this.signup) {
+                return null;
+            }
+            const password = control.get('password')?.value;
+            const confirmPassword = control.get('confirm_password')?.value;
+
+            if (password !== confirmPassword) {
+                control.get('confirm_password')?.setErrors({ missMatch: true });
+                return { missMatch: true };
+            }
+            control.get('confirm_password')?.setErrors(null);
+            return null;
+        };
+    }
+
+    showLogin() {
+        this.formGroup.get('confirm_password')?.clearValidators();
+        this.formGroup.get('email')?.clearValidators();
+        this.formGroup.get('email')?.addValidators([Validators.required]);
+        this.formGroup.reset();
+        this.login = true;
+        this.signup = false;
+    }
+
+    showSignup() {
+        this.formGroup
+            .get('confirm_password')
+            ?.addValidators([Validators.required, Validators.minLength(8), this.passwordValidators]);
+        this.formGroup.get('email')?.addValidators([Validators.required, Validators.email]);
+        this.formGroup.reset();
+        this.login = false;
+        this.signup = true;
+    }
+
+    passwordValidators(formArray: AbstractControl): ValidationErrors | null {
+        const samePassword = formArray.get('password')?.value === formArray.get('confirm_password')?.value;
+        return { samePassword: samePassword };
+    }
+
+    submit() {
+        console.log('formGroup : ', this.formGroup.value);
+        if (this.signup) {
+            this.userService
+                .createUser(this.formGroup.get('email')?.value, this.formGroup.get('password')?.value)
+                .subscribe({
+                    next: (data) => {
+                        Swal.fire({
+                            position: 'top-end',
+                            icon: 'success',
+                            title: 'Your account has been created',
+                            showConfirmButton: false,
+                            timer: 1000,
+                        });
+                        this.showLogin();
+                    },
+                });
+        } else if (this.login) {
+            this.userService
+                .loginUser(this.formGroup.get('email')?.value, this.formGroup.get('password')?.value)
+                .subscribe({
+                    next: (data: any) => {
+                        if (data['success']) {
+                            this.router.navigate(['/profile']);
+                        }
+                    },
+                });
+        }
+    }
+
+    checkValidity(controlName: string) {
+        const control = this.formGroup.get(controlName);
+        if (control) {
+            control.markAsTouched();
+        }
+    }
+
+    matchPasswords(control: any): { [key: string]: boolean } | null {
+        if (this.formGroup) {
+            return control.value === this.formGroup.get('password')?.value ? null : { mismatch: true };
+        }
+        return null;
+    }
+}
-- 
GitLab


From 49a84930d7f49446afaea754302eca5a73ff184e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20HUSS?= <theophile.huss@etu.unistra.fr>
Date: Thu, 5 Dec 2024 17:21:10 +0100
Subject: [PATCH 2/5] feat: add new routes to user

---
 src/app/services/user.service.ts | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts
index 3569db9..44a2afa 100644
--- a/src/app/services/user.service.ts
+++ b/src/app/services/user.service.ts
@@ -1,9 +1,18 @@
+import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
 
 @Injectable({
-  providedIn: 'root'
+    providedIn: 'root',
 })
 export class UserService {
+    constructor(private http: HttpClient) {}
+    private apiUrl = 'https://klebert-host.com:33037';
+    public createUser(email: string, password: string) {
+        const username = email.split('@')[0];
+        return this.http.post(`${this.apiUrl}/user`, { username: username, email: email, password: password });
+    }
 
-  constructor() { }
+    public loginUser(login: string, password: string) {
+        return this.http.post(`${this.apiUrl}/auth/login`, { login: login, password: password });
+    }
 }
-- 
GitLab


From e766ba0d3157d801014139c622ccb2d1725ed8c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20HUSS?= <theophile.huss@etu.unistra.fr>
Date: Thu, 5 Dec 2024 18:05:25 +0100
Subject: [PATCH 3/5] feat: add CookieService & Prepare credentials on request

---
 src/app/services/cookie.service.ts | 17 +++++++++++++++++
 src/app/services/user.service.ts   |  6 +++++-
 2 files changed, 22 insertions(+), 1 deletion(-)
 create mode 100644 src/app/services/cookie.service.ts

diff --git a/src/app/services/cookie.service.ts b/src/app/services/cookie.service.ts
new file mode 100644
index 0000000..9148c8b
--- /dev/null
+++ b/src/app/services/cookie.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+
+@Injectable({
+    providedIn: 'root',
+})
+export class CookieService {
+    getCookie(name: string): string | null {
+        const matches = document.cookie.match(
+            new RegExp('(?:^|; )' + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + '=([^;]*)')
+        );
+        return matches ? decodeURIComponent(matches[1]) : null;
+    }
+
+    hasCookie(name: string): boolean {
+        return this.getCookie(name) !== null;
+    }
+}
diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts
index 44a2afa..d44fa42 100644
--- a/src/app/services/user.service.ts
+++ b/src/app/services/user.service.ts
@@ -13,6 +13,10 @@ export class UserService {
     }
 
     public loginUser(login: string, password: string) {
-        return this.http.post(`${this.apiUrl}/auth/login`, { login: login, password: password });
+        return this.http.post(
+            `${this.apiUrl}/auth/login`,
+            { login: login, password: password }
+            // { withCredentials: true }
+        );
     }
 }
-- 
GitLab


From 31a7da0ec1f7f48882b8f84105d57266531d92fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20HUSS?= <theophile.huss@etu.unistra.fr>
Date: Thu, 5 Dec 2024 19:09:54 +0100
Subject: [PATCH 4/5] fix: Add credentials on login request

---
 src/app/services/user.service.ts | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts
index d44fa42..1dfdc80 100644
--- a/src/app/services/user.service.ts
+++ b/src/app/services/user.service.ts
@@ -15,8 +15,12 @@ export class UserService {
     public loginUser(login: string, password: string) {
         return this.http.post(
             `${this.apiUrl}/auth/login`,
-            { login: login, password: password }
-            // { withCredentials: true }
+            { login: login, password: password },
+            { withCredentials: true }
         );
     }
+
+    public getMe() {
+        return this.http.get(`${this.apiUrl}/user/me`, { withCredentials: true });
+    }
 }
-- 
GitLab


From 76b95152a0f6aaf486172056c5d0fc55a8e870e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20HUSS?= <theophile.huss@etu.unistra.fr>
Date: Thu, 5 Dec 2024 19:14:59 +0100
Subject: [PATCH 5/5] fix: Add login when user is not connected

---
 .../profile-info/profile-info.component.html  | 54 +++++++++------
 .../profile-info/profile-info.component.ts    | 69 ++++++++++---------
 2 files changed, 67 insertions(+), 56 deletions(-)

diff --git a/src/app/modules/profile/profile-info/profile-info.component.html b/src/app/modules/profile/profile-info/profile-info.component.html
index 9ff6a4e..6f14fa4 100644
--- a/src/app/modules/profile/profile-info/profile-info.component.html
+++ b/src/app/modules/profile/profile-info/profile-info.component.html
@@ -5,35 +5,45 @@
         <div class="container">
             <div class="grid-profile">
                 <div class="profile_info">
-                    <img src="https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png"
-                        alt="profile picture">
+                    <img
+                        src="https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png"
+                        alt="profile picture"
+                    />
                     <h3>User Name</h3>
                 </div>
-                <hr>
+                <hr />
                 <div class="profile_stats">
-                    <img src="https://img.uxcel.com/tags/key-performance-indicator-kpi-1698250709411-2x.jpg"
-                        alt="stats">
+                    <img
+                        src="https://img.uxcel.com/tags/key-performance-indicator-kpi-1698250709411-2x.jpg"
+                        alt="stats"
+                    />
                 </div>
             </div>
         </div>
-
-        <h3>Your quizzes</h3>
-        <div class="grid">
-            <!-- <form [formGroup]="form" (ngSubmit)="submitForm()"> -->
-            <form>
-                <div class="form-group">
-                    <input type="text" class="form-control" placeholder="Search a quiz..." formControlName="quizName" />
-                    <button class="btn btn-primary" type="submit">Search</button>
-                </div>
-            </form>
-            <div class="container">
-                <div class="list-wrapper">
-                    <app-quiz-list [quizList]="quizList"></app-quiz-list>
-                </div>
-                <div class="new-quiz">
-                    <button class="btn btn-primary" (click)="newQuiz()">Create a new quiz</button>
+        <app-sign *ngIf="!isLogged"></app-sign>
+        <div *ngIf="isLogged">
+            <h3>Your quizzes</h3>
+            <div class="grid">
+                <form>
+                    <div class="form-group">
+                        <input
+                            type="text"
+                            class="form-control"
+                            placeholder="Search a quiz..."
+                            formControlName="quizName"
+                        />
+                        <button class="btn btn-primary" type="submit">Search</button>
+                    </div>
+                </form>
+                <div class="container">
+                    <div class="list-wrapper">
+                        <app-quiz-list [quizList]="quizList"></app-quiz-list>
+                    </div>
+                    <div class="new-quiz">
+                        <button class="btn btn-primary" (click)="newQuiz()">Create a new quiz</button>
+                    </div>
                 </div>
             </div>
         </div>
     </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/src/app/modules/profile/profile-info/profile-info.component.ts b/src/app/modules/profile/profile-info/profile-info.component.ts
index f443113..851e215 100644
--- a/src/app/modules/profile/profile-info/profile-info.component.ts
+++ b/src/app/modules/profile/profile-info/profile-info.component.ts
@@ -1,41 +1,42 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
-
-
+import { CookieService } from '../../../services/cookie.service';
+import { UserService } from '../../../services/user.service';
 
 @Component({
-  selector: 'app-profile-info',
-  templateUrl: './profile-info.component.html',
-  styleUrl: './profile-info.component.css'
+    selector: 'app-profile-info',
+    templateUrl: './profile-info.component.html',
+    styleUrl: './profile-info.component.css',
 })
-export class ProfileInfoComponent {
-  quizList: any[] = [];
-  constructor(
-    private router: Router
-  ) { }
-
-  ngOnInit(): void {
-    this.quizList = [
-      { title: 'Quiz 1', content: 'Quiz 1 Content' },
-      { title: 'Quiz 2', content: 'Quiz 2 Content' },
-      { title: 'Quiz 3', content: 'Quiz 3 Content' },
-      { title: 'Quiz 4', content: 'Quiz 4 Content' },
-      { title: 'Quiz 5', content: 'Quiz 5 Content' },
-      { title: 'Quiz 6', content: 'Quiz 6 Content' },
-      { title: 'Quiz 7', content: 'Quiz 7 Content' },
-      { title: 'Quiz 8', content: 'Quiz 8 Content' },
-      { title: 'Quiz 9', content: 'Quiz 9 Content' },
-      { title: 'Quiz 10', content: 'Quiz 10 Content' },
-      { title: 'Quiz 11', content: 'Quiz 11 Content' },
-      { title: 'Quiz 12', content: 'Quiz 12 Content' },
-      { title: 'Quiz 13', content: 'Quiz 13 Content' },
-      { title: 'Quiz 14', content: 'Quiz 14 Content' },
-      { title: 'Quiz 15', content: 'Quiz 15 Content' }
-    ];
-  }
+export class ProfileInfoComponent implements OnInit {
+    quizList: any[] = [];
+    isLogged: boolean = false;
+    constructor(private router: Router, private cookieService: CookieService, private userSerivce: UserService) {}
 
-  newQuiz() {
-    this.router.navigate(['/']);
-  }
+    ngOnInit(): void {
+        this.quizList = [
+            { title: 'Quiz 1', content: 'Quiz 1 Content' },
+            { title: 'Quiz 2', content: 'Quiz 2 Content' },
+            { title: 'Quiz 3', content: 'Quiz 3 Content' },
+            { title: 'Quiz 4', content: 'Quiz 4 Content' },
+            { title: 'Quiz 5', content: 'Quiz 5 Content' },
+            { title: 'Quiz 6', content: 'Quiz 6 Content' },
+            { title: 'Quiz 7', content: 'Quiz 7 Content' },
+            { title: 'Quiz 8', content: 'Quiz 8 Content' },
+            { title: 'Quiz 9', content: 'Quiz 9 Content' },
+            { title: 'Quiz 10', content: 'Quiz 10 Content' },
+            { title: 'Quiz 11', content: 'Quiz 11 Content' },
+            { title: 'Quiz 12', content: 'Quiz 12 Content' },
+            { title: 'Quiz 13', content: 'Quiz 13 Content' },
+            { title: 'Quiz 14', content: 'Quiz 14 Content' },
+            { title: 'Quiz 15', content: 'Quiz 15 Content' },
+        ];
+        this.userSerivce.getMe().subscribe((data) => {
+            this.isLogged = true;
+        });
+    }
 
+    newQuiz() {
+        this.router.navigate(['/create-custom-quiz']);
+    }
 }
-- 
GitLab