refactor: remove LoginService and FirebaseService, add PasswordVisibilityService and integrate

This commit is contained in:
Chneemann 2025-04-08 08:22:42 +02:00
parent c2c5bd850e
commit 99b1e14034
11 changed files with 60 additions and 749 deletions

View file

@ -19,7 +19,7 @@
id="password" id="password"
name="password" name="password"
#password="ngModel" #password="ngModel"
[type]="loginService.passwordFieldType" [type]="pwVisibility.passwordFieldType"
placeholder="{{ 'register.password' | translate }}" placeholder="{{ 'register.password' | translate }}"
class="custom-input" class="custom-input"
autocomplete="new-password" autocomplete="new-password"
@ -36,8 +36,8 @@
} @else { } @else {
<img <img
class="passwordEye" class="passwordEye"
(click)="loginService.togglePasswordVisibility()" (click)="pwVisibility.togglePasswordVisibility()"
[src]="loginService.passwordIcon" [src]="pwVisibility.passwordIcon"
/> />
} }
<div class="error-msg"> <div class="error-msg">
@ -52,7 +52,7 @@
id="passwordConfirm" id="passwordConfirm"
name="passwordConfirm" name="passwordConfirm"
#passwordConfirm="ngModel" #passwordConfirm="ngModel"
[type]="loginService.passwordFieldType" [type]="pwVisibility.passwordFieldType"
placeholder="{{ 'register.passwordConfirm' | translate }}" placeholder="{{ 'register.passwordConfirm' | translate }}"
class="custom-input" class="custom-input"
autocomplete="new-password" autocomplete="new-password"
@ -69,8 +69,8 @@
} @else { } @else {
<img <img
class="passwordEyeConfirm" class="passwordEyeConfirm"
(click)="loginService.togglePasswordVisibility()" (click)="pwVisibility.togglePasswordVisibility()"
[src]="loginService.passwordIcon" [src]="pwVisibility.passwordIcon"
/> />
} }
<div class="error-msg"> <div class="error-msg">

View file

@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ButtonStateService } from '../../../../services/button-state.service'; import { ButtonStateService } from '../../../../services/button-state.service';
import { AuthService } from '../../../../services/auth.service'; import { AuthService } from '../../../../services/auth.service';
import { LoginService } from '../../../../services/login.service'; import { PasswordVisibilityService } from '../../../../services/password-visibility.service';
@Component({ @Component({
selector: 'app-pw-reset', selector: 'app-pw-reset',
@ -38,7 +38,7 @@ export class PwResetComponent {
}; };
constructor( constructor(
public loginService: LoginService, public pwVisibility: PasswordVisibilityService,
private authService: AuthService, private authService: AuthService,
private buttonStateService: ButtonStateService, private buttonStateService: ButtonStateService,
private route: ActivatedRoute private route: ActivatedRoute

View file

@ -35,9 +35,6 @@
<div class="error-msg"> <div class="error-msg">
@if (!mail.valid && mail.touched && !isButtonDisabled()) { @if (!mail.valid && mail.touched && !isButtonDisabled()) {
<p>{{ "login.errorMail0" | translate }}</p> <p>{{ "login.errorMail0" | translate }}</p>
} @if (loginService.errorCode == 'auth/invalid-email' &&
!isButtonDisabled()) {
<p>{{ "login.errorMail1" | translate }}</p>
} @if (mail.valid && mail.touched && } @if (mail.valid && mail.touched &&
!isEmailValid(loginData.email.toLowerCase()) && !isButtonDisabled()) { !isEmailValid(loginData.email.toLowerCase()) && !isButtonDisabled()) {
<p>{{ "login.errorMail2" | translate }}</p> <p>{{ "login.errorMail2" | translate }}</p>
@ -48,7 +45,7 @@
id="password" id="password"
name="password" name="password"
#password="ngModel" #password="ngModel"
[type]="loginService.passwordFieldType" [type]="pwVisibility.passwordFieldType"
placeholder="{{ 'login.password' | translate }}" placeholder="{{ 'login.password' | translate }}"
class="custom-input" class="custom-input"
autocomplete="current-password" autocomplete="current-password"
@ -67,16 +64,13 @@
<img <img
class="passwordEye" class="passwordEye"
*ngIf="isPasswordIconVisible" *ngIf="isPasswordIconVisible"
(click)="loginService.togglePasswordVisibility()" (click)="pwVisibility.togglePasswordVisibility()"
[src]="loginService.passwordIcon" [src]="pwVisibility.passwordIcon"
/> />
} }
<div class="error-msg"> <div class="error-msg">
@if (!password.valid && password.touched && !isButtonDisabled()) { @if (!password.valid && password.touched && !isButtonDisabled()) {
<p>{{ "login.errorPassword0" | translate }}</p> <p>{{ "login.errorPassword0" | translate }}</p>
} @if (loginService.errorCode == 'auth/invalid-credential' &&
!isButtonDisabled()) {
<p>{{ "login.errorPassword1" | translate }}</p>
} }
</div> </div>
<div class="checkbox"> <div class="checkbox">

View file

@ -3,7 +3,6 @@ import { Component } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms'; import { FormsModule, NgForm } from '@angular/forms';
import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form-btn.component'; import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form-btn.component';
import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { LoginService } from '../../services/login.service';
import { FooterComponent } from './footer/footer.component'; import { FooterComponent } from './footer/footer.component';
import { HeaderComponent } from './header/header.component'; import { HeaderComponent } from './header/header.component';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@ -12,6 +11,7 @@ import { OverlayService } from '../../services/overlay.service';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { TokenService } from '../../services/token.service'; import { TokenService } from '../../services/token.service';
import { ButtonStateService } from '../../services/button-state.service'; import { ButtonStateService } from '../../services/button-state.service';
import { PasswordVisibilityService } from '../../services/password-visibility.service';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
@ -39,13 +39,12 @@ export class LoginComponent {
}; };
constructor( constructor(
public pwVisibility: PasswordVisibilityService,
private authService: AuthService, private authService: AuthService,
public loginService: LoginService,
private tokenService: TokenService, private tokenService: TokenService,
private overlayService: OverlayService, private overlayService: OverlayService,
private buttonStateService: ButtonStateService, private buttonStateService: ButtonStateService,
private route: ActivatedRoute, private route: ActivatedRoute
private router: Router
) {} ) {}
ngOnInit() { ngOnInit() {

View file

@ -85,7 +85,7 @@
id="password" id="password"
name="password" name="password"
#password="ngModel" #password="ngModel"
[type]="loginService.passwordFieldType" [type]="pwVisibility.passwordFieldType"
placeholder="{{ 'register.password' | translate }}" placeholder="{{ 'register.password' | translate }}"
class="custom-input" class="custom-input"
autocomplete="new-password" autocomplete="new-password"
@ -102,8 +102,8 @@
} @else { } @else {
<img <img
class="passwordEye" class="passwordEye"
(click)="loginService.togglePasswordVisibility()" (click)="pwVisibility.togglePasswordVisibility()"
[src]="loginService.passwordIcon" [src]="pwVisibility.passwordIcon"
/> />
} }
<div class="error-msg"> <div class="error-msg">
@ -119,7 +119,7 @@
id="passwordConfirm" id="passwordConfirm"
name="passwordConfirm" name="passwordConfirm"
#passwordConfirm="ngModel" #passwordConfirm="ngModel"
[type]="loginService.passwordFieldType" [type]="pwVisibility.passwordFieldType"
placeholder="{{ 'register.passwordConfirm' | translate }}" placeholder="{{ 'register.passwordConfirm' | translate }}"
class="custom-input" class="custom-input"
autocomplete="new-password" autocomplete="new-password"
@ -136,8 +136,8 @@
} @else { } @else {
<img <img
class="passwordEyeConfirm" class="passwordEyeConfirm"
(click)="loginService.togglePasswordVisibility()" (click)="pwVisibility.togglePasswordVisibility()"
[src]="loginService.passwordIcon" [src]="pwVisibility.passwordIcon"
/> />
} }
<div class="error-msg"> <div class="error-msg">

View file

@ -4,7 +4,7 @@ import { FooterComponent } from '../footer/footer.component';
import { FormsModule, NgForm } from '@angular/forms'; import { FormsModule, NgForm } from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormBtnComponent } from '../../../shared/components/buttons/form-btn/form-btn.component'; import { FormBtnComponent } from '../../../shared/components/buttons/form-btn/form-btn.component';
import { LoginService } from '../../../services/login.service'; import { PasswordVisibilityService } from '../../../services/password-visibility.service';
import { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component'; import { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core';
@ -43,7 +43,7 @@ export class RegisterComponent {
}; };
constructor( constructor(
public loginService: LoginService, public pwVisibility: PasswordVisibilityService,
public translateService: TranslateService, public translateService: TranslateService,
private authService: AuthService, private authService: AuthService,
private buttonStateService: ButtonStateService private buttonStateService: ButtonStateService

View file

@ -18,6 +18,9 @@ import { ToastNotificationService } from './toast-notification.service';
export class AuthService { export class AuthService {
private currentUserIdSubject = new BehaviorSubject<string | null>(null); private currentUserIdSubject = new BehaviorSubject<string | null>(null);
passwordFieldType: string = 'password';
passwordIcon: string = './../../../assets/img/login/close-eye.svg';
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
private tokenService: TokenService, private tokenService: TokenService,
@ -112,4 +115,17 @@ export class AuthService {
getCurrentUserId(): Observable<string | null> { getCurrentUserId(): Observable<string | null> {
return this.currentUserIdSubject.asObservable(); return this.currentUserIdSubject.asObservable();
} }
togglePasswordVisibility() {
this.passwordFieldType =
this.passwordFieldType === 'password' ? 'text' : 'password';
this.toggleIcon();
}
toggleIcon() {
this.passwordIcon =
this.passwordIcon === './../../../assets/img/login/close-eye.svg'
? './../../../assets/img/login/open-eye.svg'
: './../../../assets/img/login/close-eye.svg';
}
} }

View file

@ -1,358 +0,0 @@
import { Injectable, OnDestroy, inject } from '@angular/core';
import {
Firestore,
addDoc,
collection,
deleteDoc,
doc,
onSnapshot,
setDoc,
updateDoc,
} from '@angular/fire/firestore';
import { Task } from '../interfaces/task.interface';
import { User } from '../interfaces/user.interface';
import CryptoES from 'crypto-es';
import { CryptoESSecretKey } from './../environments/config';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class FirebaseService implements OnDestroy {
firestore: Firestore = inject(Firestore);
private secretKey: string = CryptoESSecretKey.secretKey;
allTasks: Task[] = [];
filteredTasks: Task[] = [];
allUsers: User[] = [];
unsubTask;
unsubUser;
constructor() {
this.unsubTask = this.subTaskList();
this.unsubUser = this.subUserList();
}
// ------------- TASKS ------------- //
/**
* Subscribes to Firestore for the list of all tasks.
*
* This function uses the `onSnapshot` function from `@angular/fire/firestore` to
* subscribe to the 'tasks' collection in Firestore. When the data in the
* collection changes, this function is called with a `QuerySnapshot` object as an
* argument. The function then iterates over the `QuerySnapshot` and creates a
* `Task` object from each document in the snapshot. The created `Task` objects are
* then stored in the `allTasks` array.
*
* @returns The unsubscribe function from `onSnapshot`.
*/
subTaskList() {
return onSnapshot(collection(this.firestore, 'tasks'), (list) => {
this.allTasks = [];
list.forEach((element) => {
const taskWithId = { id: element.id, ...element.data() } as Task;
this.allTasks.push(taskWithId);
});
});
}
/**
* Returns the list of all tasks.
*
* This function simply returns the value of the `allTasks` property, which is
* an array of `Task` objects that is kept up-to-date by the `subTaskList`
* function.
*
* @returns The list of all tasks.
*/
getAllTasks(): Task[] {
return this.allTasks;
}
/**
* Returns the list of tasks that match the current filter.
*
* This function simply returns the value of the `filteredTasks` property, which
* is an array of `Task` objects that is kept up-to-date by the `searchTask`
* function.
*
* @returns The list of filtered tasks.
*/
getFiltertTasks(): Task[] {
return this.filteredTasks;
}
/**
* Updates the status of a task in the Firestore database.
*
* This function updates the status of the task identified by `taskId`
* using the status from the `allTasks` array at the specified `index`.
*
* @param taskId The ID of the task to be updated.
* @param index The index of the task in the `allTasks` array.
*/
async updateTask(taskId: string, index: number) {
await updateDoc(doc(collection(this.firestore, 'tasks'), taskId), {
status: this.allTasks[index].status,
}).catch((err) => {
console.error(err);
});
}
/**
* Updates the subtasksDone property of a task in the Firestore database.
*
* This function updates the subtasksDone property of the task identified by
* `taskId` with the `array` parameter. The `array` parameter is an array of booleans
* that indicates which subtasks are completed.
*
* @param taskId The ID of the task to be updated.
* @param array An array of booleans that indicates which subtasks are completed.
*/
async updateSubTask(taskId: string, array: boolean[]) {
await updateDoc(doc(collection(this.firestore, 'tasks'), taskId), {
subtasksDone: array,
}).catch((err) => {
console.error(err);
});
}
/**
* Replaces the task identified by `taskId` with the data from the `newData` object.
*
* This function uses the `setDoc` function from `@angular/fire/firestore` to
* replace the task identified by `taskId` with the data from the `newData` object.
* The `newData` object should be of type `Task` and should contain all the fields
* that are required for a task.
*
* @param taskId The ID of the task to be replaced.
* @param newData The new task data.
*/
async replaceTaskData(taskId: string, newData: Task) {
await setDoc(
doc(collection(this.firestore, 'tasks'), taskId),
newData
).catch((err) => {
console.error(err);
});
}
/**
* Deletes the task with the specified ID from the Firestore database.
*
* This function uses the `deleteDoc` function to remove the task identified by
* `taskId` from the 'tasks' collection in Firestore.
*
* @param taskId The ID of the task to be deleted.
*/
async deleteTask(taskId: string) {
await deleteDoc(doc(collection(this.firestore, 'tasks'), taskId)).catch(
(err) => {
console.error(err);
}
);
}
/**
* Adds a new task to the Firestore database.
*
* This function uses the `addDoc` function from `@angular/fire/firestore` to add
* the task to the 'tasks' collection in Firestore. The `task` parameter should be
* of type `Task` and should contain all the fields that are required for a task.
*
* @param task The new task data to be added.
*/
async addNewTask(task: Task) {
await addDoc(collection(this.firestore, 'tasks'), task).catch((err) => {
console.error(err);
});
}
// ------------- USERS ------------- //
/**
* Subscribes to Firestore for the list of all users.
*
* This function uses the `onSnapshot` function from `@angular/fire/firestore` to
* subscribe to the 'users' collection in Firestore. When the data in the
* collection changes, this function is called with a `QuerySnapshot` object as an
* argument. The function then iterates over the `QuerySnapshot` and creates a
* `User` object from each document in the snapshot. The created `User` objects are
* then stored in the `allUsers` array.
*
* @returns The unsubscribe function from `onSnapshot`.
*/
subUserList() {
return onSnapshot(collection(this.firestore, 'users'), (list) => {
this.allUsers = [];
list.forEach((element) => {
const userWithId = { id: element.id, ...element.data() } as User;
this.allUsers.push(userWithId);
});
});
}
/**
* Returns the list of all users.
*
* @returns An array of User objects.
*/
getAllUsers(): User[] {
return this.allUsers;
}
/**
* Retrieves a list of all users excluding those with initials 'G' (typically representing guest users).
*
* @returns An array of User objects without guest users.
*/
getAllUserWithoutGuest(): User[] {
return this.getAllUsers().filter((user) => user.initials !== 'G');
}
/**
* Retrieves a list of all users excluding guest users and the current user and the task creator.
*
* @param taskCreator - The id of the task creator.
* @returns An array of User objects excluding guest users, the current user, and the task creator.
*/
getFilteredUsers(taskCreator: string): User[] {
const currentUser = this.getCurrentUserId();
const filteredUsers = this.getAllUsers().filter(
(user) => user.initials !== 'G' && user.id !== currentUser
);
return filteredUsers.filter((user) => user.id !== taskCreator);
}
/**
* Retrieves a list of users from the list of all users that have the given userId.
*
* @param userId - The id of the user to be retrieved.
* @returns An array of User objects with the given userId.
*/
getUserDataFromId(userId: string): User[] {
return this.getAllUsers().filter((user) => user.id === userId);
}
/**
* Retrieves a list of users from the list of all users that have the given userUid.
*
* @param userUid - The unique identifier of the user to be retrieved.
* @returns An array of User objects with the given userUid.
*/
getUserDataFromUid(userUid: string): User[] {
return this.getAllUsers().filter((user) => user.id === userUid);
}
/**
* Retrieves the current user ID from local storage.
*
* The current user ID is stored in local storage encrypted with the secret key.
* This method decrypts the value and returns the current user ID as a string.
* If the value does not exist or is not decryptable, it returns null.
* @returns The current user ID as a string, or null if not found.
*/
getCurrentUserId() {
const encryptedValue = localStorage.getItem('currentUserJOIN');
if (encryptedValue) {
const bytes = CryptoES.AES.decrypt(encryptedValue, this.secretKey);
const decryptedValue = bytes.toString(CryptoES.enc.Utf8);
return JSON.parse(decryptedValue);
}
return null;
}
/**
* Retrieves specific user information from the list of all users.
*
* @param userId - The id of the user whose information is to be retrieved.
* @param query - The specific field of the user object to be retrieved, such as 'firstName', 'email', etc.
* @returns An array containing the values of the specified field for the user with the given userId.
*/
getUserDetails(userId: string, query: keyof User) {
return this.getAllUsers()
.filter((user) => user.id === userId)
.map((user) => user[query]);
}
/**
* Deletes a user document from the Firestore database.
*
* @param docId The id of the document to be deleted.
* @returns A Promise resolving to void.
* @throws An error if the deletion attempt fails.
*/
async deleteUser(docId: string) {
await deleteDoc(doc(collection(this.firestore, 'users'), docId)).catch(
(err) => {
console.error(err);
}
);
}
/**
* Updates a user document in the Firestore database.
*
* @param docId The id of the document to be updated.
* @param data The object with the new data to be updated.
* @returns A Promise resolving to void.
* @throws An error if the update attempt fails.
*/
async updateUserData(docId: string, data: any) {
await updateDoc(
doc(collection(this.firestore, 'users'), docId),
data
).catch((err) => {
console.error(err);
});
}
/**
* Adds a new user to the Firestore database.
*
* @param userData The data of the user to be added.
* @returns A Promise resolving to the newly added user document.
* @throws An error if the add attempt fails.
*/
async addNewUser(userData: User) {
try {
return await addDoc(collection(this.firestore, 'users'), userData);
} catch (err) {
console.error(err);
throw err;
}
}
// ------------- AUTH ------------- //
/**
* Retrieves the current user from local storage.
*
* The current user is stored in local storage encrypted with the secret key.
* This method decrypts the value and returns the current user as an Observable,
* or null if not found.
* @returns An Observable resolving to the current user, or null if not found.
*/
getAuthUser(): Observable<any> {
const encryptedValue = localStorage.getItem('currentUserJOIN');
if (encryptedValue) {
const bytes = CryptoES.AES.decrypt(encryptedValue, this.secretKey);
const decryptedValue = bytes.toString(CryptoES.enc.Utf8);
return of(JSON.parse(decryptedValue));
}
return of(null);
}
// ------------- Destroy ------------- //
/**
* Lifecycle hook that is called when the component is about to be destroyed.
* Unsubscribes from task and user changes to prevent memory leaks.
*/
ngOnDestroy() {
this.unsubTask();
this.unsubUser();
}
}

View file

@ -1,361 +0,0 @@
import { Injectable, inject } from '@angular/core';
import {
getAuth,
signInWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
createUserWithEmailAndPassword,
sendPasswordResetEmail,
confirmPasswordReset,
} from 'firebase/auth';
import { FirebaseService } from './firebase.service';
import {
Firestore,
addDoc,
collection,
doc,
getDocs,
query,
updateDoc,
where,
} from '@angular/fire/firestore';
import { User } from '../interfaces/user.interface';
import { Router } from '@angular/router';
import CryptoES from 'crypto-es';
import { CryptoESSecretKey } from '../environments/config';
import { catchError, map, Observable, of } from 'rxjs';
import { ButtonStateService } from './button-state.service';
@Injectable({
providedIn: 'root',
})
export class LoginService {
firestore: Firestore = inject(Firestore);
passwordFieldType: string = 'password';
passwordIcon: string = './../../../assets/img/login/close-eye.svg';
errorCode: string = '';
private secretKey: string = CryptoESSecretKey.secretKey;
constructor(
private firebaseService: FirebaseService,
private buttonStateService: ButtonStateService,
private router: Router
) {}
/**
* Checks if a user is logged in.
*
* @returns An Observable resolving to a boolean indicating whether a user is logged in.
*/
checkAuthUser(): Observable<boolean> {
return this.firebaseService.getAuthUser().pipe(
map((user) => {
if (user) {
return true;
} else {
return false;
}
}),
catchError(() => {
return of(false);
})
);
}
/**
* Logs in a user given their email and password.
* @param loginData An object with properties `mail` and `password`.
* @returns A Promise resolving to void.
* @throws An error if the login attempt fails.
*/
login(loginData: { mail: string; password: string }) {
signInWithEmailAndPassword(getAuth(), loginData.mail, loginData.password)
.then((userCredential) => {
const user = userCredential.user;
const userData = this.firebaseService.getUserDataFromUid(user.uid);
if (userData.length > 0 && userData[0].id) {
this.getUserIdInLocalStorage(userData[0].id);
this.updateUserOnlineStatus(userData[0].id, true);
}
})
.catch((error) => {
this.errorCode = error.code;
this.buttonStateService.disableButton();
});
}
/**
* Registers a new user given their name, first name, last name, email and password.
* @param registerData An object with properties `name`, `firstName`, `lastName`, `mail` and `password`.
* @returns A Promise resolving to void.
* @throws An error if the registration attempt fails.
*/
register(registerData: {
name: string;
firstName: string;
lastName: string;
mail: string;
password: string;
}) {
createUserWithEmailAndPassword(
getAuth(),
registerData.mail,
registerData.password
)
.then((userCredential) => {
const user = userCredential.user;
const userDataToSave: User = {
email: user.email || '',
firstName: registerData.firstName
? registerData.firstName.charAt(0).toUpperCase() +
registerData.firstName.slice(1)
: '',
lastName: registerData.lastName
? registerData.lastName.charAt(0).toUpperCase() +
registerData.lastName.slice(1)
: '',
isContactOnly: false,
isOnline: true,
phone: '',
initials: registerData.name
? registerData.firstName.slice(0, 1).toUpperCase() +
registerData.lastName.slice(0, 1).toUpperCase()
: '',
color: '',
lastLogin: new Date().getTime(),
};
this.createUserInFirestore(userDataToSave);
})
.catch((error) => {
this.buttonStateService.disableButton();
});
}
/**
* Google login method.
*
* Signs in the user with Google Authentication.
* If the user is not in the database, creates a new user with the provided name and email.
* If the user is already in the database, logins the user.
*
* @returns {Promise<void>}
* @memberof LoginService
*/
googleLogin() {
const auth = getAuth();
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then((userCredential) => {
const user = userCredential.user;
const usersCollection = collection(this.firestore, 'users');
const querySnapshot = query(
usersCollection,
where('uId', '==', user.uid)
);
getDocs(querySnapshot).then((snapshot) => {
if (snapshot.empty) {
const displayName = user.displayName || '';
const [firstName = '', lastName = ''] = displayName.split(' ');
this.createUserInFirestore({
email: user.email || 'no mail',
firstName: firstName.charAt(0).toUpperCase() + firstName.slice(1),
lastName: lastName.charAt(0).toUpperCase() + lastName.slice(1),
isContactOnly: false,
isOnline: true,
phone: '',
initials: firstName.slice(0, 1) + lastName.slice(0, 1),
color: '',
lastLogin: 0,
});
} else {
this.ifExistUser(user.uid);
}
});
})
.catch((error) => {
this.buttonStateService.disableButton();
});
}
/**
* Creates a new user in the Firestore database with the given user data.
* If the user already exists in the database, the method does nothing.
* @param user The user data to save. The user data should contain the following properties: `uId`, `email`, `firstName`, `lastName`, `initials`, `color`.
* @returns A Promise resolving to void.
*/
async createUserInFirestore(user: User) {
const userDataToSave: User = {
email: user.email,
firstName: user.firstName || '',
lastName: user.lastName || '',
isContactOnly: false,
isOnline: true,
phone: '',
initials: user.initials,
color: user.color,
lastLogin: new Date().getTime(),
};
const usersCollection = collection(this.firestore, 'users');
try {
const docRef = await addDoc(usersCollection, userDataToSave);
//this.ifExistUser(user.uId);
} catch (error) {
console.error(error);
}
}
/**
* Checks if a user with the given user id exists in the database.
* If the user exists, the method saves the user's id in the local storage and
* updates the user's online status in the database.
* @param user The id of the user to check.
* @returns void
*/
ifExistUser(user: string) {
const userData = this.firebaseService.getUserDataFromUid(user);
if (userData.length > 0 && userData[0].id) {
this.getUserIdInLocalStorage(userData[0].id);
this.updateUserOnlineStatus(userData[0].id, true);
}
}
/**
* Updates the online status of the user with the given id in the Firestore database.
* If the user's status is set to true, the user's lastLogin property is also updated
* to the current time.
* @param userId The id of the user to update.
* @param status The new online status of the user.
* @returns A Promise resolving to void.
*/
async updateUserOnlineStatus(userId: string, status: boolean) {
await updateDoc(doc(collection(this.firestore, 'users'), userId), {
status: status,
lastLogin: new Date().getTime(),
});
window.location.reload();
}
/**
* Toggles the visibility of the password field.
*
* This function switches the input type between 'password' and 'text',
* allowing the user to view or hide the password as needed.
* It also updates the icon to reflect the current visibility state.
*/
togglePasswordVisibility() {
this.passwordFieldType =
this.passwordFieldType === 'password' ? 'text' : 'password';
this.toggleIcon();
}
/**
* Toggles the password visibility icon.
*
* This function switches the icon src between the 'close eye' and 'open eye'
* icons, indicating whether the password is hidden or visible.
*/
toggleIcon() {
this.passwordIcon =
this.passwordIcon === './../../../assets/img/login/close-eye.svg'
? './../../../assets/img/login/open-eye.svg'
: './../../../assets/img/login/close-eye.svg';
}
/**
* Saves the given userId to local storage, encrypted with the secret key.
*
* The userId is first stringified and then encrypted with the secret key.
* The encrypted string is then saved to local storage under the key
* 'currentUserJOIN'. Additionally, the current timestamp is saved to local
* storage under the key 'sessionTimeJOIN'.
*
* @param userId The id of the user to be saved.
*/
getUserIdInLocalStorage(userId: string): void {
const encryptedValue = CryptoES.AES.encrypt(
JSON.stringify(userId),
this.secretKey
).toString();
localStorage.setItem('currentUserJOIN', encryptedValue);
localStorage.setItem('sessionTimeJOIN', new Date().getTime().toString());
}
// LOGOUT
/**
* Logs out the current user by deleting the user ID from local storage and
* updating the user's online status to offline in the Firestore database.
*
* @param userId The id of the user to log out.
*/
logout(userId: string) {
this.deleteUserIdInLocalStorage();
this.updateUserOnlineStatus(userId, false);
}
/**
* Deletes the user ID from local storage, effectively logging the user out.
*
* This function removes the user ID from local storage by deleting the
* 'currentUserJOIN' and 'sessionTimeJOIN' items.
*/
deleteUserIdInLocalStorage() {
localStorage.removeItem('currentUserJOIN');
localStorage.removeItem('sessionTimeJOIN');
}
// FORGOT PASSWORD
/**
* Initiates the password reset process for the given email address.
*
* This function sends a password reset email to the specified email address
* using Firebase Authentication. Upon successful sending of the email, the
* user is redirected to the password reset notice page. If there is an error,
* the button will be re-enabled for the user to try again.
*
* @param email The email address of the user requesting a password reset.
*/
passwordReset(email: string) {
const auth = getAuth();
const actionCodeSettings = {
url: 'https://join.andre-kempf.com/',
handleCodeInApp: true,
};
sendPasswordResetEmail(auth, email, actionCodeSettings)
.then(() => {
this.router.navigate(['/login/notice/pw-send']);
this.buttonStateService.disableButton();
})
.catch((error) => {
this.buttonStateService.disableButton();
});
}
/**
* Resets the user's password given the new password and the oobCode from the
* password reset email.
*
* This function sends a request to Firebase Authentication to reset the user's
* password. If the request is successful, it navigates to the password reset
* notice page and re-enables the submit button. If there is an error, the
* button will be re-enabled for the user to try again.
*
* @param newPW The new password to be set for the user.
* @param oobCode The one-time code from the password reset email.
*/
newPassword(newPW: string, oobCode: string) {
const auth = getAuth();
confirmPasswordReset(auth, oobCode, newPW)
.then(() => {
this.router.navigate(['/login/notice/pw-change']);
this.buttonStateService.disableButton();
})
.catch((error) => {
this.buttonStateService.disableButton();
});
}
}

View file

@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class PasswordVisibilityService {
passwordFieldType: string = 'password';
passwordIcon: string = './../../../assets/img/login/close-eye.svg';
togglePasswordVisibility() {
this.passwordFieldType =
this.passwordFieldType === 'password' ? 'text' : 'password';
this.toggleIcon();
}
toggleIcon() {
this.passwordIcon =
this.passwordIcon === './../../../assets/img/login/close-eye.svg'
? './../../../assets/img/login/open-eye.svg'
: './../../../assets/img/login/close-eye.svg';
}
}

View file

@ -3,7 +3,6 @@ import { Router, RouterModule } from '@angular/router';
import { LanguageService } from '../../../../services/language.service'; import { LanguageService } from '../../../../services/language.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { LoginService } from '../../../../services/login.service';
import { AuthService } from '../../../../services/auth.service'; import { AuthService } from '../../../../services/auth.service';
@Component({ @Component({