refactor: SharedService refactored and dissolved, created ButtonStateService, renamed ContactEditDialog to ContactOverlay and moved to Overlays

This commit is contained in:
Chneemann 2025-03-30 19:29:24 +02:00
parent 8659c16517
commit db242700e0
44 changed files with 209 additions and 726 deletions

View file

@ -1 +1 @@
<router-outlet></router-outlet> <app-overlay></app-overlay> <router-outlet></router-outlet>

View file

@ -1,11 +1,10 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { OverlayComponent } from './shared/components/overlay/overlay.component';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true, standalone: true,
imports: [RouterOutlet, OverlayComponent], imports: [RouterOutlet],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.scss', styleUrl: './app.component.scss',
}) })

View file

@ -9,7 +9,6 @@ import { OverlayService } from '../../services/overlay.service';
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 } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { SharedService } from '../../services/shared.service';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { firstValueFrom, map } from 'rxjs'; import { firstValueFrom, map } from 'rxjs';
import { TaskService } from '../../services/task.service'; import { TaskService } from '../../services/task.service';
@ -49,7 +48,6 @@ export class AddTaskComponent implements OnInit {
constructor( constructor(
public firebaseService: FirebaseService, public firebaseService: FirebaseService,
private overlayService: OverlayService, private overlayService: OverlayService,
private sharedService: SharedService,
private taskService: TaskService, private taskService: TaskService,
private apiService: ApiService, private apiService: ApiService,
private authService: AuthService, private authService: AuthService,

View file

@ -1,19 +0,0 @@
<section>
<div class="dialog">
<p>
{{ "contactDeleteDialog.deleteContact" | translate }}
</p>
<div class="btns">
<div class="btn">
<div class="btn-cancel" (click)="cancelDeleteContact()">
{{ "contactDeleteDialog.btnCancel" | translate }}
</div>
</div>
<div class="btn">
<div class="btn-delete-contact" (click)="deleteContact()">
{{ "contactDeleteDialog.btnDelete" | translate }}
</div>
</div>
</div>
</div>
</section>

View file

@ -1,109 +0,0 @@
section {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
.dialog {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: var(--white);
width: 600px;
height: 200px;
border-radius: 26px;
margin: 24px;
border: 2px solid rgba($color: #000000, $alpha: 0.1);
p {
text-align: center;
font-size: 27px;
font-weight: 500;
margin-bottom: 12px;
padding: 0 12px;
}
}
.btns {
display: flex;
justify-content: center;
align-items: center;
}
.btn {
display: flex;
flex-direction: column;
align-items: center;
width: 111px;
height: 57px;
margin: 0 24px;
}
.btn-cancel {
padding: 12px;
margin: 12px 12px;
border-radius: 10px;
border-color: var(--black);
background-color: var(--dark-blue);
color: var(--white);
font-size: 23px;
font-weight: 400;
transition: 125ms ease-in-out;
cursor: pointer;
&:hover {
background-color: var(--light-blue);
box-shadow: 2px 2px 2px 0px rgba(0, 0, 0, 0.3);
}
}
.btn-delete-contact {
padding: 12px;
margin: 12px 12px;
border-radius: 10px;
color: var(--red);
background-color: var(--white);
border: 1px solid var(--red);
font-size: 23px;
font-weight: 400;
transition: 125ms ease-in-out;
cursor: pointer;
&:hover {
color: var(--white);
background-color: var(--red);
border-color: var(--black);
box-shadow: 2px 2px 2px 0px rgba(0, 0, 0, 0.3);
}
}
@media screen and (max-width: 450px) {
.dialog {
p {
font-size: 22px;
}
}
}
@media screen and (max-width: 350px) {
.dialog {
width: 280px;
p {
font-size: 22px;
}
}
.btn {
width: 100px;
}
.btn-delete-contact,
.btn-cancel {
font-size: 18px;
}
}

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContactDeleteComponent } from './contact-delete.component';
describe('ContactDeleteComponent', () => {
let component: ContactDeleteComponent;
let fixture: ComponentFixture<ContactDeleteComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ContactDeleteComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ContactDeleteComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,69 +0,0 @@
import { Component, Input } from '@angular/core';
import { SharedService } from '../../../services/shared.service';
import { TranslateModule } from '@ngx-translate/core';
import { Router } from '@angular/router';
import { FirebaseService } from '../../../services/firebase.service';
@Component({
selector: 'app-contact-delete',
standalone: true,
imports: [TranslateModule],
templateUrl: './contact-delete.component.html',
styleUrl: './contact-delete.component.scss',
})
export class ContactDeleteComponent {
@Input() currentUserId: string = '';
constructor(
private sharedService: SharedService,
private firebaseService: FirebaseService,
private router: Router
) {}
/**
* Deletes the current user and performs cleanup operations.
* This method deletes the user with the current user ID from the database,
* navigates to the contacts page, and closes any open dialog flags in the shared service.
* It also checks if the deleted contact was assigned to any tasks and removes them.
*/
deleteContact() {
this.firebaseService.deleteUser(this.currentUserId);
this.router.navigate(['contacts']);
this.sharedService.isDeleteContactDialogOpen = false;
this.sharedService.isAnyDialogOpen = false;
this.checkIfContactAnAssigned();
}
/**
* Checks if the deleted contact was assigned to any tasks and removes them.
* This method retrieves all tasks from the database, iterates over them,
* and checks if the current user ID is in the task's assigned array.
* If the ID is found, it is removed from the array and the modified task
* is updated in the database.
*/
checkIfContactAnAssigned() {
const tasks = this.firebaseService.getAllTasks();
tasks.forEach((task) => {
const index = task.assigned.indexOf(this.currentUserId);
if (index !== -1) {
task.assigned.splice(index, 1);
const { id, ...taskWithoutIds } = task;
if (task.id) {
this.firebaseService.replaceTaskData(task.id, taskWithoutIds);
}
}
});
}
/**
* Closes the delete contact dialog by setting the appropriate flags in the shared service.
* If the edit contact dialog is not open, it also closes any open dialog flags in the shared service.
*/
cancelDeleteContact() {
this.sharedService.isDeleteContactDialogOpen = false;
if (!this.sharedService.isEditContactDialogOpen) {
this.sharedService.isAnyDialogOpen = false;
}
}
}

View file

@ -49,17 +49,17 @@
@if(selectedUser.isContactOnly || currentUser && selectedUser.id === @if(selectedUser.isContactOnly || currentUser && selectedUser.id ===
currentUser.id) { currentUser.id) {
<div class="btns"> <div class="btns">
<div class="btn btn-edit"> <div class="btn btn-edit" (click)="editContact(selectedUser)">
<img src="./../../../../assets/img/contact/edit.svg" alt="" /> <img src="./../../../../assets/img/contact/edit.svg" alt="" />
<p (click)="openEditDialog()"> <p>
{{ "contacts.btnEdit0" | translate }} {{ "contacts.btnEdit0" | translate }}
</p> </p>
</div> </div>
@if (selectedUser.isContactOnly || currentUser && selectedUser.id !== @if (selectedUser.isContactOnly || currentUser && selectedUser.id !==
currentUser.id) { currentUser.id) {
<div class="btn btn-delete"> <div class="btn btn-delete" (click)="openDeleteContactDialog()">
<img src="./../../../../assets/img/contact/delete.svg" alt="" /> <img src="./../../../../assets/img/contact/delete.svg" alt="" />
<p (click)="deleteContact()"> <p>
{{ "contacts.btnDelete0" | translate }} {{ "contacts.btnDelete0" | translate }}
</p> </p>
</div> </div>
@ -96,10 +96,7 @@
</div> </div>
</div> </div>
</div> </div>
} @if (sharedService.isMobileNavbarOpen) { } @if (isMobileNavbarOpen) {
<app-contact-nav <!-- TODO -->
[deleteContact]="deleteContact"
[openEditDialog]="openEditDialog"
></app-contact-nav>
} }
</section> </section>

View file

@ -1,26 +1,22 @@
import { import {
Component, Component,
EventEmitter, EventEmitter,
HostListener,
Input, Input,
Output, Output,
SimpleChanges, SimpleChanges,
} from '@angular/core'; } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Router } from '@angular/router';
import { SharedService } from '../../../services/shared.service';
import { ContactNavComponent } from '../contact-nav/contact-nav.component';
import { FirebaseService } from '../../../services/firebase.service'; import { FirebaseService } from '../../../services/firebase.service';
import { LanguageService } from '../../../services/language.service';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { finalize } from 'rxjs'; import { finalize } from 'rxjs';
import { User } from '../../../interfaces/user.interface'; import { User } from '../../../interfaces/user.interface';
import { OverlayService } from '../../../services/overlay.service';
@Component({ @Component({
selector: 'app-contact-detail', selector: 'app-contact-detail',
standalone: true, standalone: true,
imports: [CommonModule, TranslateModule, ContactNavComponent], imports: [CommonModule, TranslateModule],
templateUrl: './contact-detail.component.html', templateUrl: './contact-detail.component.html',
styleUrl: './contact-detail.component.scss', styleUrl: './contact-detail.component.scss',
}) })
@ -31,10 +27,11 @@ export class ContactDetailComponent {
isLoading: boolean = false; isLoading: boolean = false;
selectedUser: User | null = null; selectedUser: User | null = null;
isMobileNavbarOpen: boolean = false;
constructor( constructor(
public sharedService: SharedService,
public firebaseService: FirebaseService, public firebaseService: FirebaseService,
private overlayService: OverlayService,
private userService: UserService private userService: UserService
) {} ) {}
@ -84,68 +81,24 @@ export class ContactDetailComponent {
* the mobile navigation bar when the user clicks the three points icon. * the mobile navigation bar when the user clicks the three points icon.
*/ */
toggleNav() { toggleNav() {
this.sharedService.isMobileNavbarOpen = this.isMobileNavbarOpen = !this.isMobileNavbarOpen;
!this.sharedService.isMobileNavbarOpen;
} }
/** /**
* Opens the edit contact dialog by setting the appropriate flags in the shared service. * Opens the edit contact dialog by setting the appropriate flags in the shared service.
* This method is used by the contact details component to open the edit contact dialog when the user clicks the edit button. * This method is used by the contact details component to open the edit contact dialog when the user clicks the edit button.
*/ */
openEditDialog() { editContact(userData: User) {
this.sharedService.isAnyDialogOpen = true; this.overlayService.setOverlayData('contactOverlay', userData);
this.sharedService.isEditContactDialogOpen = true; this.isMobileNavbarOpen = false;
this.sharedService.isMobileNavbarOpen = false;
} }
/** /**
* Opens the delete contact dialog by setting the appropriate flags in the shared service. * Opens the delete contact dialog by setting the appropriate flags in the shared service.
* This method is used by the contact details component to open the delete contact dialog when the user clicks the delete button. * This method is used by the contact details component to open the delete contact dialog when the user clicks the delete button.
*/ */
deleteContact() { openDeleteContactDialog() {
this.sharedService.isAnyDialogOpen = true; // TODO
this.sharedService.isDeleteContactDialogOpen = true; this.isMobileNavbarOpen = false;
this.sharedService.isMobileNavbarOpen = false;
}
@HostListener('document:click', ['$event'])
/**
* Handles the opening of the contact edit, new contact, and delete contact
* dialogs when the respective buttons are clicked.
*
* @param event The MouseEvent that was triggered.
*/
checkOpenContactEdit(event: MouseEvent) {
const targetElement = event.target as HTMLElement;
if (targetElement.closest('.btn-edit')) {
this.setDialogStatus(true, true, false, false);
} else if (targetElement.closest('.btn-new')) {
this.setDialogStatus(true, false, true, false);
} else if (targetElement.closest('.btn-delete')) {
this.setDialogStatus(true, false, false, true);
} else if (!targetElement.closest('.dialog')) {
this.setDialogStatus(false, false, false, false);
}
}
/**
* Updates the dialog status flags in the shared service.
*
* @param anyOpen - Indicates if any dialog is open.
* @param editOpen - Indicates if the edit contact dialog is open.
* @param newOpen - Indicates if the new contact dialog is open.
* @param deleteOpen - Indicates if the delete contact dialog is open.
*/
private setDialogStatus(
anyOpen: boolean,
editOpen: boolean,
newOpen: boolean,
deleteOpen: boolean
) {
this.sharedService.isAnyDialogOpen = anyOpen;
this.sharedService.isEditContactDialogOpen = editOpen;
this.sharedService.isNewContactDialogOpen = newOpen;
this.sharedService.isDeleteContactDialogOpen = deleteOpen;
} }
} }

View file

@ -1,12 +0,0 @@
<nav class="navbar">
<div class="link btn-edit">
<span (click)="openEditDialog()">{{
"contacts.btnEdit1" | translate
}}</span>
</div>
<div class="link btn-delete">
<span (click)="deleteContact()">{{
"contacts.btnDelete1" | translate
}}</span>
</div>
</nav>

View file

@ -1,37 +0,0 @@
.navbar {
position: fixed;
display: flex;
flex-direction: column;
bottom: 146px;
right: 80px;
width: 110px;
height: 108px;
background-color: var(--bgSidebar);
border-radius: 20px 20px 0 20px;
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.1);
padding: 10px;
z-index: 5;
}
.link {
display: flex;
align-items: center;
height: 46px;
padding: 8px 16px;
border-radius: 8px;
color: var(--white);
cursor: pointer;
span {
text-align: center;
width: 118px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
&:hover {
background-color: var(--very-dark-blue);
span {
color: var(--light-blue);
}
}
}

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContactNavComponent } from './contact-nav.component';
describe('ContactNavComponent', () => {
let component: ContactNavComponent;
let fixture: ComponentFixture<ContactNavComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ContactNavComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ContactNavComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,17 +0,0 @@
import { Component, Input } from '@angular/core';
import { SharedService } from '../../../services/shared.service';
import { TranslateModule } from '@ngx-translate/core';
@Component({
selector: 'app-contact-nav',
standalone: true,
imports: [TranslateModule],
templateUrl: './contact-nav.component.html',
styleUrl: './contact-nav.component.scss',
})
export class ContactNavComponent {
@Input() deleteContact!: () => any;
@Input() openEditDialog!: () => any;
constructor(public sharedService: SharedService) {}
}

View file

@ -5,7 +5,7 @@
'd-none': !showAllUsers && selectedUserId != null 'd-none': !showAllUsers && selectedUserId != null
}" }"
> >
<button class="btn-new" type="submit" (click)="openNewContactDialog()"> <button class="btn-new" type="submit" (click)="addNewContact()">
<div class="btn-inside"> <div class="btn-inside">
<span>{{ "contacts.btnNew" | translate }}</span> <span>{{ "contacts.btnNew" | translate }}</span>
<img src="./../../../assets/img/contact/add.svg" alt="check" /> <img src="./../../../assets/img/contact/add.svg" alt="check" />

View file

@ -1,14 +1,12 @@
import { Component, HostListener } from '@angular/core'; import { Component, HostListener } from '@angular/core';
import { User } from '../../interfaces/user.interface'; import { User } from '../../interfaces/user.interface';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { ContactDetailComponent } from './contact-detail/contact-detail.component'; import { ContactDetailComponent } from './contact-detail/contact-detail.component';
import { SharedService } from '../../services/shared.service';
import { FirebaseService } from '../../services/firebase.service'; import { FirebaseService } from '../../services/firebase.service';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ApiService } from '../../services/api.service';
import { UserService } from '../../services/user.service'; import { UserService } from '../../services/user.service';
import { finalize } from 'rxjs'; import { finalize } from 'rxjs';
import { OverlayService } from '../../services/overlay.service';
@Component({ @Component({
selector: 'app-contacts', selector: 'app-contacts',
@ -27,7 +25,7 @@ export class ContactsComponent {
constructor( constructor(
public firebaseService: FirebaseService, public firebaseService: FirebaseService,
private userService: UserService, private userService: UserService,
public sharedService: SharedService private overlayService: OverlayService
) {} ) {}
/** /**
@ -95,15 +93,8 @@ export class ContactsComponent {
return uniqueFirstLetters; return uniqueFirstLetters;
} }
/** addNewContact() {
* Opens the new contact dialog. this.overlayService.setOverlayData('contactOverlay', 'new');
* This function sets the isAnyDialogOpen and isNewContactDialogOpen
* properties of the shared service to true, which will open the new contact
* dialog.
*/
openNewContactDialog() {
this.sharedService.isAnyDialogOpen = true;
this.sharedService.isNewContactDialogOpen = true;
} }
@HostListener('window:resize', ['$event']) @HostListener('window:resize', ['$event'])

View file

@ -57,7 +57,7 @@
[disabled]=" [disabled]="
mail.invalid || mail.invalid ||
!pwResetData.mail || !pwResetData.mail ||
sharedService.isBtnDisabled || isButtonDisabled() ||
!checkIfUserEmailIsValid(pwResetData.mail) !checkIfUserEmailIsValid(pwResetData.mail)
" "
></app-form-btn> ></app-form-btn>

View file

@ -6,8 +6,8 @@ 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';
import { LoginService } from '../../../services/login.service'; import { LoginService } from '../../../services/login.service';
import { SharedService } from '../../../services/shared.service';
import { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component'; import { BtnBackComponent } from '../../../shared/components/buttons/btn-back/btn-back.component';
import { ButtonStateService } from '../../../services/button-state.service';
@Component({ @Component({
selector: 'app-forgot-pw', selector: 'app-forgot-pw',
@ -31,16 +31,20 @@ export class ForgotPwComponent {
constructor( constructor(
public loginService: LoginService, public loginService: LoginService,
public sharedService: SharedService private buttonStateService: ButtonStateService
) {} ) {}
onSubmit(ngForm: NgForm) { onSubmit(ngForm: NgForm) {
this.sharedService.isBtnDisabled = true; this.buttonStateService.enableButton();
if (ngForm.submitted && ngForm.form.valid) { if (ngForm.submitted && ngForm.form.valid) {
this.loginService.passwordReset(this.pwResetData.mail.toLowerCase()); this.loginService.passwordReset(this.pwResetData.mail.toLowerCase());
} }
} }
isButtonDisabled() {
return this.buttonStateService.isButtonDisabled;
}
checkIfUserEmailIsValid(emailValue: string) { checkIfUserEmailIsValid(emailValue: string) {
const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/; const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
if (emailRegex.test(emailValue)) { if (emailRegex.test(emailValue)) {

View file

@ -92,7 +92,7 @@
!pwResetData.password || !pwResetData.password ||
passwordConfirm.invalid || passwordConfirm.invalid ||
!pwResetData.passwordConfirm || !pwResetData.passwordConfirm ||
sharedService.isBtnDisabled || isButtonDisabled() ||
pwResetData.password.length < 6 || pwResetData.password.length < 6 ||
pwResetData.password !== pwResetData.passwordConfirm pwResetData.password !== pwResetData.passwordConfirm
" "

View file

@ -5,11 +5,10 @@ import { FormBtnComponent } from '../../../../shared/components/buttons/form-btn
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';
import { FirebaseService } from '../../../../services/firebase.service';
import { LoginService } from '../../../../services/login.service'; import { LoginService } from '../../../../services/login.service';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { SharedService } from '../../../../services/shared.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ButtonStateService } from '../../../../services/button-state.service';
@Component({ @Component({
selector: 'app-pw-reset', selector: 'app-pw-reset',
@ -35,11 +34,9 @@ export class PwResetComponent {
}; };
constructor( constructor(
private firebaseService: FirebaseService,
public loginService: LoginService, public loginService: LoginService,
public sharedService: SharedService, private buttonStateService: ButtonStateService,
private route: ActivatedRoute, private route: ActivatedRoute
private router: Router
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
@ -50,8 +47,12 @@ export class PwResetComponent {
); );
} }
isButtonDisabled() {
return this.buttonStateService.isButtonDisabled;
}
onSubmit(ngForm: NgForm) { onSubmit(ngForm: NgForm) {
this.sharedService.isBtnDisabled = true; this.buttonStateService.enableButton();
if (ngForm.submitted && ngForm.form.valid) { if (ngForm.submitted && ngForm.form.valid) {
this.loginService.newPassword(this.pwResetData.password, this.oobCode); this.loginService.newPassword(this.pwResetData.password, this.oobCode);
} }

View file

@ -1,4 +1,4 @@
@if (sharedService.isBtnDisabled) { @if (isButtonDisabled()) {
<div class="loading-dialog"> <div class="loading-dialog">
<div id="loading" class="loader"></div> <div id="loading" class="loader"></div>
<div id="loadingText" class="loading-text"> <div id="loadingText" class="loading-text">

View file

@ -1,6 +1,6 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { SharedService } from '../../../services/shared.service';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ButtonStateService } from '../../../services/button-state.service';
@Component({ @Component({
selector: 'app-loading-dialog', selector: 'app-loading-dialog',
@ -10,5 +10,9 @@ import { TranslateModule } from '@ngx-translate/core';
styleUrl: './loading-dialog.component.scss', styleUrl: './loading-dialog.component.scss',
}) })
export class LoadingDialogComponent { export class LoadingDialogComponent {
constructor(public sharedService: SharedService) {} constructor(private buttonStateService: ButtonStateService) {}
isButtonDisabled() {
return this.buttonStateService.isButtonDisabled;
}
} }

View file

@ -33,14 +33,14 @@
<img class="custom-img" alt="" /> <img class="custom-img" alt="" />
} }
<div class="error-msg"> <div class="error-msg">
@if (!mail.valid && mail.touched && !sharedService.isBtnDisabled) { @if (!mail.valid && mail.touched && !isButtonDisabled()) {
<p>{{ "login.errorMail0" | translate }}</p> <p>{{ "login.errorMail0" | translate }}</p>
} @if (loginService.errorCode == 'auth/invalid-email' && } @if (loginService.errorCode == 'auth/invalid-email' &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "login.errorMail1" | translate }}</p> <p>{{ "login.errorMail1" | translate }}</p>
} @if (mail.valid && mail.touched && } @if (mail.valid && mail.touched &&
!checkIfUserEmailIsValid(loginData.email.toLowerCase()) && !checkIfUserEmailIsValid(loginData.email.toLowerCase()) &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "login.errorMail2" | translate }}</p> <p>{{ "login.errorMail2" | translate }}</p>
} }
</div> </div>
@ -73,11 +73,10 @@
/> />
} }
<div class="error-msg"> <div class="error-msg">
@if (!password.valid && password.touched && @if (!password.valid && password.touched && !isButtonDisabled()) {
!sharedService.isBtnDisabled) {
<p>{{ "login.errorPassword0" | translate }}</p> <p>{{ "login.errorPassword0" | translate }}</p>
} @if (loginService.errorCode == 'auth/invalid-credential' && } @if (loginService.errorCode == 'auth/invalid-credential' &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "login.errorPassword1" | translate }}</p> <p>{{ "login.errorPassword1" | translate }}</p>
} }
</div> </div>
@ -120,7 +119,7 @@
!loginData.email || !loginData.email ||
password.invalid || password.invalid ||
!loginData.password || !loginData.password ||
sharedService.isBtnDisabled || isButtonDisabled() ||
!checkIfUserEmailIsValid(loginData.email) !checkIfUserEmailIsValid(loginData.email)
" "
></app-form-btn> ></app-form-btn>
@ -128,7 +127,7 @@
[class]="'btn-guest-login'" [class]="'btn-guest-login'"
[type]="'button'" [type]="'button'"
[value]="'login.guest' | translate" [value]="'login.guest' | translate"
[disabled]="sharedService.isBtnDisabled" [disabled]="isButtonDisabled()"
(click)="guestLogin()" (click)="guestLogin()"
></app-form-btn> ></app-form-btn>
</div> </div>

View file

@ -2,10 +2,8 @@ import { CommonModule } from '@angular/common';
import { Component } from '@angular/core'; 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 { FirebaseService } from '../../services/firebase.service';
import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { LoginService } from '../../services/login.service'; import { LoginService } from '../../services/login.service';
import { SharedService } from '../../services/shared.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';
@ -13,6 +11,7 @@ import { LoadingDialogComponent } from './loading-dialog/loading-dialog.componen
import { OverlayService } from '../../services/overlay.service'; 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';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
@ -43,8 +42,8 @@ export class LoginComponent {
private authService: AuthService, private authService: AuthService,
public loginService: LoginService, public loginService: LoginService,
private tokenService: TokenService, private tokenService: TokenService,
public sharedService: SharedService,
private overlayService: OverlayService, private overlayService: OverlayService,
private buttonStateService: ButtonStateService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router private router: Router
) {} ) {}
@ -65,26 +64,30 @@ export class LoginComponent {
}); });
} }
isButtonDisabled() {
return this.buttonStateService.isButtonDisabled;
}
deleteTokens() { deleteTokens() {
this.tokenService.deleteAuthToken(); this.tokenService.deleteAuthToken();
this.tokenService.deleteUserId(); this.tokenService.deleteUserId();
} }
async onSubmit(ngForm: NgForm) { async onSubmit(ngForm: NgForm) {
this.sharedService.isBtnDisabled = true; this.buttonStateService.enableButton();
if (ngForm.submitted && ngForm.form.valid) { if (ngForm.submitted && ngForm.form.valid) {
try { try {
await this.authService.login(this.loginData, this.checkboxRememberMe); await this.authService.login(this.loginData, this.checkboxRememberMe);
this.router.navigate(['/summary']); this.router.navigate(['/summary']);
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
} catch (error) { } catch (error) {
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
} }
} }
} }
guestLogin() { guestLogin() {
this.sharedService.isBtnDisabled = true; this.buttonStateService.enableButton();
this.loginData.email = 'guest@guestaccount.com'; this.loginData.email = 'guest@guestaccount.com';
this.loginData.password = 'guest@guestaccount.com'; this.loginData.password = 'guest@guestaccount.com';
this.isPasswordIconVisible = !this.isPasswordIconVisible; this.isPasswordIconVisible = !this.isPasswordIconVisible;

View file

@ -39,11 +39,10 @@
<img class="custom-img" alt="" /> <img class="custom-img" alt="" />
} }
<div class="error-msg"> <div class="error-msg">
@if (name.hasError('pattern') && name.touched && @if (name.hasError('pattern') && name.touched && !isButtonDisabled())
!sharedService.isBtnDisabled) { {
<p>{{ "register.errorPattern" | translate }}</p> <p>{{ "register.errorPattern" | translate }}</p>
} @else { @if (!name.valid && name.touched && } @else { @if (!name.valid && name.touched && !isButtonDisabled()) {
!sharedService.isBtnDisabled) {
<p>{{ "register.errorName" | translate }}</p> <p>{{ "register.errorName" | translate }}</p>
}} }}
</div> </div>
@ -70,15 +69,15 @@
<div class="error-msg"> <div class="error-msg">
@if(!mail.valid && mail.touched && @if(!mail.valid && mail.touched &&
!checkIfUserEmailIsValid(registerData.mail.toLowerCase()) && !checkIfUserEmailIsValid(registerData.mail.toLowerCase()) &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "register.errorMail0" | translate }}</p> <p>{{ "register.errorMail0" | translate }}</p>
} @else { @if (mail.touched && } @else { @if (mail.touched &&
!checkIfUserEmailIsValid(registerData.mail.toLowerCase()) && !checkIfUserEmailIsValid(registerData.mail.toLowerCase()) &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "register.errorMail1" | translate }}</p> <p>{{ "register.errorMail1" | translate }}</p>
} @else { @if } @else { @if
(existEmailOnServer(registerData.mail.toLowerCase()).length > 0 && (existEmailOnServer(registerData.mail.toLowerCase()).length > 0 &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "register.errorMail2" | translate }}</p> <p>{{ "register.errorMail2" | translate }}</p>
} } } } } }
</div> </div>
@ -109,11 +108,10 @@
/> />
} }
<div class="error-msg"> <div class="error-msg">
@if (!password.valid && password.touched && @if (!password.valid && password.touched && !isButtonDisabled()) {
!sharedService.isBtnDisabled) {
<p>{{ "register.errorPassword0" | translate }}</p> <p>{{ "register.errorPassword0" | translate }}</p>
} @else { @if (registerData.password.length < 6 && password.touched && } @else { @if (registerData.password.length < 6 && password.touched &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "register.errorPassword1" | translate }}</p> <p>{{ "register.errorPassword1" | translate }}</p>
} } } }
</div> </div>
@ -145,11 +143,11 @@
} }
<div class="error-msg"> <div class="error-msg">
@if (!passwordConfirm.valid && passwordConfirm.touched && @if (!passwordConfirm.valid && passwordConfirm.touched &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "register.errorPassword0" | translate }}</p> <p>{{ "register.errorPassword0" | translate }}</p>
} @if (registerData.password !== registerData.passwordConfirm && } @if (registerData.password !== registerData.passwordConfirm &&
registerData.password !== "" && registerData.passwordConfirm !== "" && registerData.password !== "" && registerData.passwordConfirm !== "" &&
!sharedService.isBtnDisabled) { !isButtonDisabled()) {
<p>{{ "register.errorPassword2" | translate }}</p> <p>{{ "register.errorPassword2" | translate }}</p>
} }
</div> </div>
@ -188,7 +186,7 @@
password.invalid || password.invalid ||
!registerData.password || !registerData.password ||
!checkbox.valid || !checkbox.valid ||
sharedService.isBtnDisabled || isButtonDisabled() ||
registerData.password.length < 6 || registerData.password.length < 6 ||
registerData.password !== registerData.passwordConfirm || registerData.password !== registerData.passwordConfirm ||
!checkIfUserEmailIsValid(registerData.mail) !checkIfUserEmailIsValid(registerData.mail)

View file

@ -6,11 +6,11 @@ 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 { FirebaseService } from '../../../services/firebase.service'; import { FirebaseService } from '../../../services/firebase.service';
import { LoginService } from '../../../services/login.service'; import { LoginService } from '../../../services/login.service';
import { SharedService } from '../../../services/shared.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';
import { LoadingDialogComponent } from '../loading-dialog/loading-dialog.component'; import { LoadingDialogComponent } from '../loading-dialog/loading-dialog.component';
import { ButtonStateService } from '../../../services/button-state.service';
@Component({ @Component({
selector: 'app-register', selector: 'app-register',
@ -43,18 +43,22 @@ export class RegisterComponent {
constructor( constructor(
private firebaseService: FirebaseService, private firebaseService: FirebaseService,
public loginService: LoginService, public loginService: LoginService,
public sharedService: SharedService, public translateService: TranslateService,
public translateService: TranslateService private buttonStateService: ButtonStateService
) {} ) {}
onSubmit(ngForm: NgForm) { onSubmit(ngForm: NgForm) {
this.sharedService.isBtnDisabled = true; this.buttonStateService.enableButton();
if (ngForm.submitted && ngForm.form.valid) { if (ngForm.submitted && ngForm.form.valid) {
this.splitName(); this.splitName();
this.loginService.register(this.registerData); this.loginService.register(this.registerData);
} }
} }
isButtonDisabled() {
return this.buttonStateService.isButtonDisabled;
}
splitName() { splitName() {
const names = this.registerData.name.split(' '); const names = this.registerData.name.split(' ');
this.registerData.firstName = names[0]; this.registerData.firstName = names[0];

View file

@ -1,8 +1,4 @@
<div <div>
[ngClass]="{
'blur-background': sharedService.isAnyDialogOpen
}"
>
<div class="container"> <div class="container">
<app-sidebar></app-sidebar> <app-sidebar></app-sidebar>
<app-header></app-header> <app-header></app-header>
@ -12,23 +8,4 @@
</div> </div>
<app-sidebar-mobile></app-sidebar-mobile> <app-sidebar-mobile></app-sidebar-mobile>
</div> </div>
@if (sharedService.isNewContactDialogOpen) { <app-overlay></app-overlay>
<app-contact-edit
[ngClass]="{
'blur-background': sharedService.isDeleteContactDialogOpen,
'd-none': sharedService.isDeleteContactDialogOpen
}"
></app-contact-edit>
} @if (sharedService.isEditContactDialogOpen) {
<app-contact-edit
[currentUserId]="sharedService.currentUserId"
[ngClass]="{
'blur-background': sharedService.isDeleteContactDialogOpen,
'd-none': sharedService.isDeleteContactDialogOpen
}"
></app-contact-edit>
} @if (sharedService.isDeleteContactDialogOpen) {
<app-contact-delete
[currentUserId]="sharedService.currentUserId"
></app-contact-delete>
}

View file

@ -1,15 +1,14 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { NavigationEnd, Router, RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { filter } from 'rxjs';
import { FirebaseService } from '../../services/firebase.service';
import { LanguageService } from '../../services/language.service'; import { LanguageService } from '../../services/language.service';
import { SharedService } from '../../services/shared.service';
import { SidebarMobileComponent } from '../../shared/components/sidebar/sidebar-mobile/sidebar-mobile.component'; import { SidebarMobileComponent } from '../../shared/components/sidebar/sidebar-mobile/sidebar-mobile.component';
import { SidebarComponent } from '../../shared/components/sidebar/sidebar.component'; import { SidebarComponent } from '../../shared/components/sidebar/sidebar.component';
import { ContactDeleteComponent } from '../contacts/contact-delete/contact-delete.component';
import { ContactEditNewComponent } from '../contacts/contact-edit-new/contact-edit-new.component';
import { HeaderComponent } from '../../shared/components/header/header.component'; import { HeaderComponent } from '../../shared/components/header/header.component';
import { UserService } from '../../services/user.service';
import { User } from '../../interfaces/user.interface';
import { OverlayComponent } from '../../shared/components/overlay/overlay.component';
@Component({ @Component({
selector: 'app-main-layout', selector: 'app-main-layout',
@ -19,83 +18,27 @@ import { HeaderComponent } from '../../shared/components/header/header.component
HeaderComponent, HeaderComponent,
SidebarComponent, SidebarComponent,
SidebarMobileComponent, SidebarMobileComponent,
ContactEditNewComponent,
ContactDeleteComponent,
CommonModule, CommonModule,
OverlayComponent,
], ],
templateUrl: './main-layout.component.html', templateUrl: './main-layout.component.html',
styleUrl: './main-layout.component.scss', styleUrl: './main-layout.component.scss',
}) })
export class MainLayoutComponent { export class MainLayoutComponent {
isLoggedIn: string = ''; currentUser: User | null = null;
constructor( constructor(
public langService: LanguageService, public langService: LanguageService,
public sharedService: SharedService, private userService: UserService
private firebaseService: FirebaseService, ) {}
private router: Router
) { ngOnInit(): void {
this.checkAndClearLocalStorage(); this.loadCurrentUser();
this.isLoggedIn = this.firebaseService.getCurrentUserId();
} }
/** loadCurrentUser(): void {
* Lifecycle hook that is called after the component has been initialized. this.userService.getCurrentUser().subscribe((userData) => {
* this.currentUser = userData;
* Checks if the user is logged in by verifying the `isLoggedIn` property.
*/
ngOnInit() {
if (this.isLoggedIn === null) {
this.checkPwResetRoute();
} else {
this.router.navigate(['/summary']);
this.sharedService.isBtnDisabled = false;
}
}
/**
* Clears the local storage after a certain time period (12h) has
* passed since the last login.
*/
checkAndClearLocalStorage() {
const startTime = localStorage.getItem('sessionTimeJOIN');
if (startTime) {
const startTimeMills = parseInt(startTime);
const currentTime = new Date().getTime();
const timeDifference = 12 * 60 * 60 * 1000; // 12h
if (currentTime - startTimeMills > timeDifference) {
localStorage.clear();
}
}
}
/**
* Listens to router events and checks if the current route is one of the
* allowed routes for password reset, registration, forgotten password,
* or login. If it is not, it navigates to the login page.
*/
checkPwResetRoute() {
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => {
const urlTree = this.router.parseUrl(this.router.url);
const firstSegment = this.getFirstSegment(urlTree);
const allowedRoutes = ['pw-reset', 'register', 'forgot-pw', 'login'];
if (!allowedRoutes.includes(firstSegment)) {
this.router.navigate(['/login']);
}
}); });
} }
/**
* Returns the first segment of the current URL or an empty string if it does not exist.
*
* @param urlTree The current URL tree.
* @returns The first segment of the current URL or an empty string if it does not exist.
*/
private getFirstSegment(urlTree: any): string {
return urlTree.root.children['primary']?.segments[0]?.path || '';
}
} }

View file

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class ButtonStateService {
isButtonDisabled: boolean = false;
disableButton() {
this.isButtonDisabled = true;
}
enableButton() {
this.isButtonDisabled = false;
}
}

View file

@ -1,34 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class ColorService {
generateRandomColor(): string {
const letters = '0123456789ABCDEF';
let color = '#';
let isGrayScale = true;
while (isGrayScale) {
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
const rgb = this.hexToRgb(color);
if (rgb.r !== rgb.g || rgb.g !== rgb.b) {
isGrayScale = false;
} else {
color = '#';
}
}
return color;
}
hexToRgb(hex: string): { r: number; g: number; b: number } {
const bigint = parseInt(hex.substring(1), 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return { r, g, b };
}
}

View file

@ -19,13 +19,12 @@ import {
updateDoc, updateDoc,
where, where,
} from '@angular/fire/firestore'; } from '@angular/fire/firestore';
import { SharedService } from './shared.service';
import { User } from '../interfaces/user.interface'; import { User } from '../interfaces/user.interface';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import CryptoES from 'crypto-es'; import CryptoES from 'crypto-es';
import { CryptoESSecretKey } from '../environments/config'; import { CryptoESSecretKey } from '../environments/config';
import { catchError, map, Observable, of } from 'rxjs'; import { catchError, map, Observable, of } from 'rxjs';
import { ColorService } from './color.service'; import { ButtonStateService } from './button-state.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -40,8 +39,7 @@ export class LoginService {
constructor( constructor(
private firebaseService: FirebaseService, private firebaseService: FirebaseService,
private colorService: ColorService, private buttonStateService: ButtonStateService,
public sharedService: SharedService,
private router: Router private router: Router
) {} ) {}
@ -83,7 +81,7 @@ export class LoginService {
}) })
.catch((error) => { .catch((error) => {
this.errorCode = error.code; this.errorCode = error.code;
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
}); });
} }
@ -126,13 +124,13 @@ export class LoginService {
? registerData.firstName.slice(0, 1).toUpperCase() + ? registerData.firstName.slice(0, 1).toUpperCase() +
registerData.lastName.slice(0, 1).toUpperCase() registerData.lastName.slice(0, 1).toUpperCase()
: '', : '',
color: this.colorService.generateRandomColor(), color: '',
lastLogin: new Date().getTime(), lastLogin: new Date().getTime(),
}; };
this.createUserInFirestore(userDataToSave); this.createUserInFirestore(userDataToSave);
}) })
.catch((error) => { .catch((error) => {
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
}); });
} }
@ -171,7 +169,7 @@ export class LoginService {
isOnline: true, isOnline: true,
phone: '', phone: '',
initials: firstName.slice(0, 1) + lastName.slice(0, 1), initials: firstName.slice(0, 1) + lastName.slice(0, 1),
color: this.colorService.generateRandomColor(), color: '',
lastLogin: 0, lastLogin: 0,
}); });
} else { } else {
@ -180,7 +178,7 @@ export class LoginService {
}); });
}) })
.catch((error) => { .catch((error) => {
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
}); });
} }
@ -333,10 +331,10 @@ export class LoginService {
sendPasswordResetEmail(auth, email, actionCodeSettings) sendPasswordResetEmail(auth, email, actionCodeSettings)
.then(() => { .then(() => {
this.router.navigate(['/login/notice/pw-send']); this.router.navigate(['/login/notice/pw-send']);
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
}) })
.catch((error) => { .catch((error) => {
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
}); });
} }
@ -357,10 +355,10 @@ export class LoginService {
confirmPasswordReset(auth, oobCode, newPW) confirmPasswordReset(auth, oobCode, newPW)
.then(() => { .then(() => {
this.router.navigate(['/login/notice/pw-change']); this.router.navigate(['/login/notice/pw-change']);
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
}) })
.catch((error) => { .catch((error) => {
this.sharedService.isBtnDisabled = false; this.buttonStateService.disableButton();
}); });
} }
} }

View file

@ -1,14 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class SharedService {
isBtnDisabled: boolean = false;
isAnyDialogOpen: boolean = false;
isMobileNavbarOpen: boolean = false;
isNewContactDialogOpen: boolean = false;
isEditContactDialogOpen: boolean = false;
isDeleteContactDialogOpen: boolean = false;
currentUserId: string = '';
}

View file

@ -1,3 +1,3 @@
<div class="btn" (click)="closeClicked()"> <div class="btn">
<img src="./../../../../assets/img/close.svg" /> <img src="./../../../../assets/img/close.svg" />
</div> </div>

View file

@ -1,5 +1,4 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { SharedService } from '../../../../services/shared.service';
@Component({ @Component({
selector: 'app-btn-close', selector: 'app-btn-close',
@ -9,19 +8,5 @@ import { SharedService } from '../../../../services/shared.service';
styleUrl: './btn-close.component.scss', styleUrl: './btn-close.component.scss',
}) })
export class BtnCloseComponent { export class BtnCloseComponent {
@Input() isContactDialogOpen: boolean = false; constructor() {}
constructor(private sharedService: SharedService) {}
/**
* Called when the close button is clicked.
* Sets isAnyDialogOpen to false and sets
* isEditContactDialogOpen and isNewContactDialogOpen
* to the value of isContactDialogOpen.
*/
closeClicked() {
this.sharedService.isAnyDialogOpen = false;
this.sharedService.isEditContactDialogOpen = this.isContactDialogOpen;
this.sharedService.isNewContactDialogOpen = this.isContactDialogOpen;
}
} }

View file

@ -121,13 +121,12 @@
} }
</div> </div>
<div class="btns"> <div class="btns">
@if (currentUserId && currentUserId !== @if (selectedUser && selectedUser !== firebaseService.getCurrentUserId()){
firebaseService.getCurrentUserId()){
<app-form-btn <app-form-btn
class="btn-delete" class="btn-delete"
type="button" type="button"
value="{{ 'contactDialogForm.btnDelete' | translate }}" value="{{ 'contactDialogForm.btnDelete' | translate }}"
(click)="deleteContact()" (click)="closeDialog()"
></app-form-btn> ></app-form-btn>
} @else { } @else {
<app-form-btn <app-form-btn

View file

@ -9,12 +9,10 @@ import {
} from '@angular/core'; } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms'; import { FormsModule, NgForm } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { SharedService } from '../../../../services/shared.service'; import { FormBtnComponent } from '../../../buttons/form-btn/form-btn.component';
import { FirebaseService } from '../../../../services/firebase.service'; import { User } from '@sentry/angular';
import { FormBtnComponent } from '../../../../shared/components/buttons/form-btn/form-btn.component'; import { FirebaseService } from '../../../../../services/firebase.service';
import { User } from '../../../../interfaces/user.interface'; import { ResizeService } from '../../../../../services/resize.service';
import { Router } from '@angular/router';
import { ResizeService } from '../../../../services/resize.service';
@Component({ @Component({
selector: 'app-contact-form', selector: 'app-contact-form',
@ -24,20 +22,17 @@ import { ResizeService } from '../../../../services/resize.service';
styleUrl: './contact-form.component.scss', styleUrl: './contact-form.component.scss',
}) })
export class ContactFormComponent implements OnInit, OnChanges { export class ContactFormComponent implements OnInit, OnChanges {
@Input() currentUserId: string = ''; @Input() selectedUser: any = [];
@Input() randomColor: string = ''; @Input() randomColor: string = '';
@Input() newColor: string = ''; @Input() newColor: string = '';
@Input() currentColor: string = ''; @Input() currentColor: string = '';
@Output() initialsEmitter = new EventEmitter<string>(); @Output() initialsEmitter = new EventEmitter<string>();
@Output() closeDialogEmitter = new EventEmitter<string>();
constructor( constructor(
private router: Router,
public firebaseService: FirebaseService, public firebaseService: FirebaseService,
public resizeService: ResizeService, public resizeService: ResizeService
public sharedService: SharedService ) {}
) {
this.updateContactData();
}
contactData = { contactData = {
firstName: '', firstName: '',
@ -68,7 +63,8 @@ export class ContactFormComponent implements OnInit, OnChanges {
* properties of the userData object. * properties of the userData object.
*/ */
ngOnInit() { ngOnInit() {
if (!this.currentUserId) { this.updateContactData();
if (!this.selectedUser) {
this.userData = { this.userData = {
...this.userData, ...this.userData,
color: this.randomColor, color: this.randomColor,
@ -84,7 +80,7 @@ export class ContactFormComponent implements OnInit, OnChanges {
* @param changes an object containing the changed input properties * @param changes an object containing the changed input properties
*/ */
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes['currentUserId']) { if (changes['selectedUser']) {
this.updateContactData(); this.updateContactData();
} }
} }
@ -109,7 +105,7 @@ export class ContactFormComponent implements OnInit, OnChanges {
? this.contactData.firstName.slice(0, 1).toUpperCase() + ? this.contactData.firstName.slice(0, 1).toUpperCase() +
this.contactData.lastName.slice(0, 1).toUpperCase() this.contactData.lastName.slice(0, 1).toUpperCase()
: ''; : '';
if (!this.currentUserId) { if (!this.selectedUser) {
this.userData = { this.userData = {
...this.userData, ...this.userData,
initials: initials, initials: initials,
@ -130,7 +126,7 @@ export class ContactFormComponent implements OnInit, OnChanges {
* object. Otherwise, it updates the contactData object. * object. Otherwise, it updates the contactData object.
*/ */
updateUserData() { updateUserData() {
if (!this.currentUserId) { if (!this.selectedUser) {
this.userData = { this.userData = {
...this.userData, ...this.userData,
firstName: this.capitalizeFirstLetter(this.contactData.firstName), firstName: this.capitalizeFirstLetter(this.contactData.firstName),
@ -193,21 +189,11 @@ export class ContactFormComponent implements OnInit, OnChanges {
* joined into strings using a comma separator. * joined into strings using a comma separator.
*/ */
private updateContactData() { private updateContactData() {
this.contactData.firstName = this.firebaseService this.contactData.firstName = this.selectedUser.firstName;
.getUserDetails(this.currentUserId, 'firstName') this.contactData.lastName = this.selectedUser.lastName;
.join(', '); this.contactData.email = this.selectedUser.email;
this.contactData.lastName = this.firebaseService this.contactData.phone = this.selectedUser.phone;
.getUserDetails(this.currentUserId, 'lastName') this.contactData.initials = this.selectedUser.initials;
.join(', ');
this.contactData.email = this.firebaseService
.getUserDetails(this.currentUserId, 'email')
.join(', ');
this.contactData.phone = this.firebaseService
.getUserDetails(this.currentUserId, 'phone')
.join(', ');
this.contactData.initials = this.firebaseService
.getUserDetails(this.currentUserId, 'initials')
.join(', ');
} }
/** /**
@ -220,45 +206,15 @@ export class ContactFormComponent implements OnInit, OnChanges {
*/ */
onSubmit(ngForm: NgForm) { onSubmit(ngForm: NgForm) {
if (ngForm.submitted && ngForm.form.valid) { if (ngForm.submitted && ngForm.form.valid) {
if (this.currentUserId) {
console.log(this.newColor, this.currentColor);
this.newColor !== '' this.newColor !== ''
? (this.contactData.color = this.newColor) ? (this.contactData.color = this.newColor)
: (this.contactData.color = this.currentColor); : (this.contactData.color = this.currentColor);
this.firebaseService.updateUserData( console.log('Save');
this.currentUserId,
this.contactData
);
} else {
this.newColor !== ''
? (this.userData.color = this.newColor)
: (this.userData.color = this.randomColor);
const { id, ...taskWithoutIds } = this.userData;
this.firebaseService.addNewUser(taskWithoutIds).then((docRef) => {
this.router.navigate([`/contacts/${docRef.id}`]);
});
}
this.closeDialog(); this.closeDialog();
} }
} }
/**
* Closes the delete contact dialog and the main dialog overlay.
*/
deleteContact() {
this.sharedService.isDeleteContactDialogOpen = false;
this.sharedService.isAnyDialogOpen = false;
}
/**
* Closes the contact dialog by updating the shared service flags
* to indicate that no dialog, edit contact dialog, or new contact dialog
* is currently open.
*/
closeDialog() { closeDialog() {
this.sharedService.isAnyDialogOpen = false; this.closeDialogEmitter.emit('');
this.sharedService.isEditContactDialogOpen = false;
this.sharedService.isNewContactDialogOpen = false;
} }
} }

View file

@ -1,23 +1,22 @@
@if (currentUserId) { @for (user of @if (overlayData !== "new") {
firebaseService.getUserDataFromId(currentUserId); track user) {
<section> <section>
<div class="dialog"> <div class="dialog">
<div class="header"> <div class="header">
<img src="./../../../../assets/img/logo.svg" alt="" /> <img src="./../../../../assets/img/logo.svg" alt="" />
<p>{{ "contacts.editContact" | translate }}</p> <p>{{ "contacts.editContact" | translate }}</p>
<app-btn-close [isContactDialogOpen]="false"></app-btn-close> <app-btn-close (click)="closeDialog()"></app-btn-close>
</div> </div>
<div class="content"> <div class="content">
<div class="badge"> <div class="badge">
<div class="picker"> <div class="picker">
<input <input
[(colorPicker)]="currentColor" [(colorPicker)]="overlayData.color"
[style.background]="newColor === '' ? user.color : newColor" [style.background]="newColor === '' ? overlayData.color : newColor"
(colorPickerChange)="updateColor($event)" (colorPickerChange)="updateColor($event)"
/> />
<div class="initials"> <div class="initials">
@if (!userInitials) { @if (!userInitials) {
{{ user.initials }} } @else { {{ overlayData.initials }} } @else {
{{ userInitials }} {{ userInitials }}
} }
</div> </div>
@ -26,22 +25,23 @@ firebaseService.getUserDataFromId(currentUserId); track user) {
<div class="form"> <div class="form">
<app-contact-form <app-contact-form
(initialsEmitter)="initialsEmitter($event)" (initialsEmitter)="initialsEmitter($event)"
[currentUserId]="currentUserId" (closeDialogEmitter)="closeDialog()"
[selectedUser]="overlayData"
[newColor]="newColor" [newColor]="newColor"
[currentColor]="user.color" [currentColor]="overlayData.color"
></app-contact-form> ></app-contact-form>
</div> </div>
</div> </div>
<div class="notice">{{ "contacts.notice" | translate }}</div> <div class="notice">{{ "contacts.notice" | translate }}</div>
</div> </div>
</section> </section>
} } @else { } @else {
<section> <section>
<div class="dialog"> <div class="dialog">
<div class="header"> <div class="header">
<img src="./../../../../assets/img/logo.svg" alt="" /> <img src="./../../../../assets/img/logo.svg" alt="" />
<p>{{ "contacts.addContact" | translate }}</p> <p>{{ "contacts.addContact" | translate }}</p>
<app-btn-close [isContactDialogOpen]="false"></app-btn-close> <app-btn-close (click)="closeDialog()"></app-btn-close>
</div> </div>
<div class="content"> <div class="content">
<div class="badge"> <div class="badge">

View file

@ -1,16 +1,16 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContactEditNewComponent } from './contact-edit-new.component'; import { ContactOverlayComponent } from './contact-overlay.component';
describe('ContactEditComponent', () => { describe('ContactOverlayComponent', () => {
let component: ContactEditNewComponent; let component: ContactOverlayComponent;
let fixture: ComponentFixture<ContactEditNewComponent>; let fixture: ComponentFixture<ContactOverlayComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [ContactEditNewComponent], imports: [ContactOverlayComponent],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(ContactEditNewComponent); fixture = TestBed.createComponent(ContactOverlayComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View file

@ -1,15 +1,13 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { BtnCloseComponent } from '../../../shared/components/buttons/btn-close/btn-close.component';
import { FirebaseService } from '../../../services/firebase.service';
import { TranslateModule } from '@ngx-translate/core';
import { ContactFormComponent } from './contact-form/contact-form.component'; import { ContactFormComponent } from './contact-form/contact-form.component';
import { SharedService } from '../../../services/shared.service'; import { BtnCloseComponent } from '../../buttons/btn-close/btn-close.component';
import { TranslateModule } from '@ngx-translate/core';
import { ColorPickerModule } from 'ngx-color-picker'; import { ColorPickerModule } from 'ngx-color-picker';
import { ColorService } from '../../../services/color.service'; import { FirebaseService } from '../../../../services/firebase.service';
@Component({ @Component({
selector: 'app-contact-edit', selector: 'app-contact-overlay',
standalone: true, standalone: true,
imports: [ imports: [
CommonModule, CommonModule,
@ -18,22 +16,19 @@ import { ColorService } from '../../../services/color.service';
TranslateModule, TranslateModule,
ColorPickerModule, ColorPickerModule,
], ],
templateUrl: './contact-edit-new.component.html', templateUrl: './contact-overlay.component.html',
styleUrl: './contact-edit-new.component.scss', styleUrl: './contact-overlay.component.scss',
}) })
export class ContactEditNewComponent implements OnInit { export class ContactOverlayComponent implements OnInit {
@Input() currentUserId: string = ''; @Input() overlayData: any = [];
@Input() currentColor: string = ''; @Input() overlayType: string = '';
@Output() closeDialogEmitter = new EventEmitter<string>();
randomColor: string = ''; randomColor: string = '';
userInitials: string = ''; userInitials: string = '';
newColor: string = ''; newColor: string = '';
constructor( constructor(public firebaseService: FirebaseService) {}
public firebaseService: FirebaseService,
public sharedService: SharedService,
private colorService: ColorService
) {}
/** /**
* Lifecycle hook that is called after data-bound properties of a directive are * Lifecycle hook that is called after data-bound properties of a directive are
@ -41,7 +36,16 @@ export class ContactEditNewComponent implements OnInit {
* This method sets a random color for the user to be edited. * This method sets a random color for the user to be edited.
*/ */
ngOnInit() { ngOnInit() {
this.randomColor = this.colorService.generateRandomColor(); this.randomColor = this.getRandomColor();
}
getRandomColor(): string {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
} }
/** /**
@ -55,6 +59,10 @@ export class ContactEditNewComponent implements OnInit {
this.userInitials = emitter; this.userInitials = emitter;
} }
closeDialog() {
this.closeDialogEmitter.emit('');
}
/** /**
* Updates the newColor variable with the newly selected color. * Updates the newColor variable with the newly selected color.
* *

View file

@ -1,5 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { SharedService } from '../../../../services/shared.service';
import { BtnCloseComponent } from '../../buttons/btn-close/btn-close.component'; import { BtnCloseComponent } from '../../buttons/btn-close/btn-close.component';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@ -16,7 +15,7 @@ export class DialogOverlayComponent {
@Input() overlayType: string = ''; @Input() overlayType: string = '';
@Output() closeDialogEmitter = new EventEmitter<string>(); @Output() closeDialogEmitter = new EventEmitter<string>();
constructor(public sharedService: SharedService, private router: Router) {} constructor(private router: Router) {}
/** /**
* Navigates to the login route and emits an empty string via the * Navigates to the login route and emits an empty string via the

View file

@ -18,12 +18,18 @@
[overlayType]="overlayType" [overlayType]="overlayType"
(closeDialogEmitter)="onCloseOverlay($event)" (closeDialogEmitter)="onCloseOverlay($event)"
></app-task-edit-overlay> ></app-task-edit-overlay>
}@if (overlayType === "dialog") { } @if (overlayType === "dialog") {
<app-dialog-overlay <app-dialog-overlay
[overlayData]="overlayData" [overlayData]="overlayData"
[overlayType]="overlayType" [overlayType]="overlayType"
(closeDialogEmitter)="onCloseOverlay($event)" (closeDialogEmitter)="onCloseOverlay($event)"
></app-dialog-overlay> ></app-dialog-overlay>
} @if (overlayType === "contactOverlay") {
<app-contact-overlay
[overlayData]="overlayData"
[overlayType]="overlayType"
(closeDialogEmitter)="onCloseOverlay($event)"
></app-contact-overlay>
} }
</div> </div>
</div> </div>

View file

@ -5,6 +5,7 @@ import { TaskOverlayComponent } from './task-overlay/task-overlay.component';
import { TaskEditOverlayComponent } from './task-edit-overlay/task-edit-overlay.component'; import { TaskEditOverlayComponent } from './task-edit-overlay/task-edit-overlay.component';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DialogOverlayComponent } from './dialog-overlay/dialog-overlay.component'; import { DialogOverlayComponent } from './dialog-overlay/dialog-overlay.component';
import { ContactOverlayComponent } from './contact-overlay/contact-overlay.component';
@Component({ @Component({
selector: 'app-overlay', selector: 'app-overlay',
@ -14,6 +15,7 @@ import { DialogOverlayComponent } from './dialog-overlay/dialog-overlay.componen
TaskOverlayComponent, TaskOverlayComponent,
TaskEditOverlayComponent, TaskEditOverlayComponent,
DialogOverlayComponent, DialogOverlayComponent,
ContactOverlayComponent,
], ],
templateUrl: './overlay.component.html', templateUrl: './overlay.component.html',
styleUrl: './overlay.component.scss', styleUrl: './overlay.component.scss',