diff --git a/.gitignore b/.gitignore index 2f7289f..841573c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ Thumbs.db .firebase *-debug.log .runtimeconfig.json +/src/app/app.config.ts #firebase functions diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 217ee44..cc0617f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,14 +1,6 @@ import { Component } from "@angular/core"; -import { MatIconModule } from "@angular/material/icon"; import { RouterOutlet } from "@angular/router"; -import { - trigger, - state, - style, - animate, - transition, -} from "@angular/animations"; -import { FirestoreService } from "./firestore.service"; +import { trigger, style, animate, transition } from "@angular/animations"; @Component({ selector: "app-root", @@ -20,11 +12,4 @@ import { FirestoreService } from "./firestore.service"; }) export class AppComponent { title = "DABubble"; - - constructor(private firestore: FirestoreService) { - this.firestore.currentUser$.subscribe((uid) => { - console.log("Aktuelle Benutzer UID:", uid); - // Führen Sie hier Aktionen aus, die vom aktuellen Benutzerstatus abhängen - }); - } } diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 5e2e5b6..35a9de6 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -6,8 +6,6 @@ import { RecoveryComponent } from "./start/recovery/recovery.component"; import { ResetPasswordComponent } from "./start/reset-password/reset-password.component"; import { PrivacyPolicyComponent } from "./start/privacy-policy/privacy-policy.component"; import { LegalNoticeComponent } from "./start/legal-notice/legal-notice.component"; -import { ChatComponent } from "./main/chat/chat.component"; -import { ThreadComponent } from "./main/thread/thread.component"; export const routes: Routes = [ { path: "", component: MainComponent }, diff --git a/src/app/auth.guard.ts b/src/app/auth.guard.ts index 1e8702d..e828897 100644 --- a/src/app/auth.guard.ts +++ b/src/app/auth.guard.ts @@ -1,35 +1,17 @@ import { Injectable, inject } from "@angular/core"; -import { - CanActivateFn, - ActivatedRouteSnapshot, - RouterStateSnapshot, - Router, -} from "@angular/router"; -import { map, catchError } from "rxjs"; +import { CanActivateFn } from "@angular/router"; import { CurrentuserService } from "./currentuser.service"; @Injectable({ providedIn: "root", }) export class AuthGuard { - constructor( - authService: CurrentuserService, - private router: Router, - ) { - console.log(authService.isLoggedIn); - } } -export const canActivateGuard: CanActivateFn = ( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot, -) => { +export const canActivateGuard: CanActivateFn = () => { if (inject(CurrentuserService).isLoggedIn) { - // inject(Router).navigate(['']); - console.log("user is logged in"); return true; } else { - // inject(Router).navigate(['login']); return false; } }; diff --git a/src/app/bottomsheet-add-member-new-channel/bottomsheet-add-member-new-channel.component.ts b/src/app/bottomsheet-add-member-new-channel/bottomsheet-add-member-new-channel.component.ts index be9bda2..2cd5d81 100644 --- a/src/app/bottomsheet-add-member-new-channel/bottomsheet-add-member-new-channel.component.ts +++ b/src/app/bottomsheet-add-member-new-channel/bottomsheet-add-member-new-channel.component.ts @@ -1,9 +1,6 @@ import { Component, Inject } from "@angular/core"; import { DialogAddChannelAddMemberComponent } from "../dialog-add-channel-add-member/dialog-add-channel-add-member.component"; -import { - MAT_BOTTOM_SHEET_DATA, - MatBottomSheetRef, -} from "@angular/material/bottom-sheet"; +import { MAT_BOTTOM_SHEET_DATA, MatBottomSheetRef } from "@angular/material/bottom-sheet"; @Component({ selector: "app-bottomsheet-add-member-new-channel", @@ -15,9 +12,10 @@ import { export class BottomsheetAddMemberNewChannelComponent { constructor( @Inject(MAT_BOTTOM_SHEET_DATA) - public data: { channelName: string; channelDescription: string }, + public data: { channelName: string; channelDescription: string; }, private bottomSheetRef: MatBottomSheetRef, - ) {} + ) { } + closeSheet(): void { this.bottomSheetRef.dismiss(); diff --git a/src/app/bottomsheet-profile-menu/bottomsheet-profile-menu.component.ts b/src/app/bottomsheet-profile-menu/bottomsheet-profile-menu.component.ts index d657d61..01ee914 100644 --- a/src/app/bottomsheet-profile-menu/bottomsheet-profile-menu.component.ts +++ b/src/app/bottomsheet-profile-menu/bottomsheet-profile-menu.component.ts @@ -1,12 +1,7 @@ import { Component } from "@angular/core"; -import { - MatBottomSheet, - MatBottomSheetModule, - MatBottomSheetRef, -} from "@angular/material/bottom-sheet"; -import { MatDialog, MatDialogRef } from "@angular/material/dialog"; +import { MatBottomSheetModule, MatBottomSheetRef } from "@angular/material/bottom-sheet"; +import { MatDialog } from "@angular/material/dialog"; import { FirestoreService } from "../firestore.service"; -import { DialogEditProfileComponent } from "../dialog-edit-profile/dialog-edit-profile.component"; import { DialogEditProfileEditProfileComponent } from "../dialog-edit-profile-edit-profile/dialog-edit-profile-edit-profile.component"; @Component({ @@ -21,13 +16,15 @@ export class BottomsheetProfileMenuComponent { private _bottomSheetRef: MatBottomSheetRef, public dialog: MatDialog, public firestore: FirestoreService, - ) {} + ) { } + logout() { this._bottomSheetRef.dismiss(); this.firestore.logout(); } + openProfile(): void { this.dialog.open(DialogEditProfileEditProfileComponent, {}); } diff --git a/src/app/common-fn.service.spec.ts b/src/app/common-fn.service.spec.ts new file mode 100644 index 0000000..0a3cd05 --- /dev/null +++ b/src/app/common-fn.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { CommonFnService } from './common-fn.service'; + +describe('CommonFnService', () => { + let service: CommonFnService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(CommonFnService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/common-fn.service.ts b/src/app/common-fn.service.ts new file mode 100644 index 0000000..6222875 --- /dev/null +++ b/src/app/common-fn.service.ts @@ -0,0 +1,138 @@ +import { Injectable } from '@angular/core'; +import { EmojiService } from '@ctrl/ngx-emoji-mart/ngx-emoji'; +import { UsersList } from './interfaces/users-list'; +import { ChatService } from './main/chat/chat.service'; +import { Message } from './interfaces/message'; +import { PofileInfoCardComponent } from './pofile-info-card/pofile-info-card.component'; +import { MatDialog } from '@angular/material/dialog'; + +@Injectable({ + providedIn: 'root' +}) +export class CommonFnService { + recentEmojis: string[] = []; + + + constructor( + private emojiService: EmojiService, + private chatService: ChatService, + private dialog: MatDialog + ) { } + + + onMessageClick(event: MouseEvent) { + const target = event.target as HTMLElement; + if (target.classList.contains("highlight-mention")) { + const username = target.getAttribute("data-username"); + if (username) { + this.openProfileCard(username); + } else { + console.error("Kein Benutzername definiert für dieses Element"); + } + } + } + + + openProfileCard(username: string) { + const user = this.chatService.usersList.find( + (u) => u.name === username, + ); + if (user) { + this.dialog.open(PofileInfoCardComponent, { + data: user, + }); + } + } + + + noReactions(message: Message): boolean { + return !message.reactions || Object.keys(message.reactions).length === 0; + } + + + padNumber(num: number, size: number) { + let s = num + ""; + while (s.length < size) s = "0" + s; + return s; + } + + + dayTime(timestamp: string): string { + const date = new Date(timestamp); + const options: Intl.DateTimeFormatOptions = { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }; + return date.toLocaleTimeString("de-DE", options); + } + + + dayDate(timestamp: string): string { + const date = new Date(timestamp); + if (this.isToday(date)) return "Heute"; + if (this.isYesterday(date)) return "Gestern"; + return this.formatDate(date); + } + + + private isToday(date: Date): boolean { + const today = new Date(); + today.setHours(0, 0, 0, 0); + return date.setHours(0, 0, 0, 0) === today.getTime(); + } + + + private isYesterday(date: Date): boolean { + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + yesterday.setHours(0, 0, 0, 0); + return date.setHours(0, 0, 0, 0) === yesterday.getTime(); + } + + + private formatDate(date: Date): string { + return date.toLocaleDateString("de-DE", { + weekday: "long", + day: "numeric", + month: "long", + }); + } + + + _filter(value: string): UsersList[] { + if (this.mentionUser(value)) { + const filterValue = value + .slice(value.lastIndexOf("@") + 1) + .toLowerCase(); + return this.chatService.usersList.filter((user) => + user.name.toLowerCase().includes(filterValue), + ); + } else { + return []; + } + } + + + mentionUser(value: string): boolean { + const atIndex = value.lastIndexOf("@"); + if (atIndex === -1) return false; + const charAfterAt = value.charAt(atIndex + 1); + return charAfterAt !== " "; + } + + + getEmojiById(emojiId: string) { + const emoji = this.emojiService.getData(emojiId); // Get the emoji by ID + return emoji ? emoji.native : null; // Return the native emoji + } + + + loadRecentEmojis() { + const recentEmojiData = localStorage.getItem('emoji-mart.frequently'); + if (recentEmojiData) { + const recentEmojiObj = JSON.parse(recentEmojiData); + this.recentEmojis = Object.keys(recentEmojiObj).slice(-2).reverse(); // Get the last two emojis and reverse the order + } + } +} diff --git a/src/app/currentuser.service.ts b/src/app/currentuser.service.ts index 2f306d0..dc8010f 100644 --- a/src/app/currentuser.service.ts +++ b/src/app/currentuser.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { Firestore, doc, onSnapshot } from "@angular/fire/firestore"; +import { doc, onSnapshot } from "@angular/fire/firestore"; import { FirestoreService } from "./firestore.service"; import { UsersList } from "./interfaces/users-list"; @@ -17,6 +17,7 @@ export class CurrentuserService { online: false, }; + constructor(private firestore: FirestoreService) { this.firestore.currentUser$.subscribe((uid) => { this.currentUserUid = uid; @@ -24,6 +25,18 @@ export class CurrentuserService { }); } + + setUsersListObj(obj: any, id: string): UsersList { + return { + id: id || '', + name: obj.name || '', + avatar: obj.avatar || '', + email: obj.email || '', + online: obj.online || false, // Standardwert ist false, wird durch Realtime-Daten aktualisiert + }; + } + + subCurrentUser(): void { let firestore = this.firestore.getFirestore(); if (this.currentUserUid) { @@ -31,14 +44,13 @@ export class CurrentuserService { let ref = doc(firestore, "users", this.currentUserUid); onSnapshot(ref, (doc) => { this.currentUser = this.setCurrentUserObj(doc.data(), doc.id); - console.log(this.currentUser); }); } else { this.isLoggedIn = false; - console.log("invalid user uid"); } } + setCurrentUserObj(obj: any, id: string): UsersList { return { id: id || "", diff --git a/src/app/dialog-add-channel-add-member/dialog-add-channel-add-member.component.ts b/src/app/dialog-add-channel-add-member/dialog-add-channel-add-member.component.ts index 11ff585..4937f56 100644 --- a/src/app/dialog-add-channel-add-member/dialog-add-channel-add-member.component.ts +++ b/src/app/dialog-add-channel-add-member/dialog-add-channel-add-member.component.ts @@ -1,47 +1,18 @@ import { CommonModule } from "@angular/common"; import { COMMA, ENTER } from "@angular/cdk/keycodes"; -import { - Component, - ElementRef, - EventEmitter, - Inject, - Input, - OnInit, - Optional, - Output, - ViewChild, -} from "@angular/core"; +import { Component, ElementRef, EventEmitter, Inject, Input, Optional, Output, ViewChild } from "@angular/core"; import { FormControl, FormsModule, ReactiveFormsModule } from "@angular/forms"; import { MatButtonModule } from "@angular/material/button"; -import { - MAT_DIALOG_DATA, - MatDialog, - MatDialogActions, - MatDialogContent, - MatDialogRef, -} from "@angular/material/dialog"; +import { MAT_DIALOG_DATA, MatDialog, MatDialogActions, MatDialogContent, MatDialogRef } from "@angular/material/dialog"; import { MatInputModule } from "@angular/material/input"; import { MatRadioModule } from "@angular/material/radio"; import { MatIcon } from "@angular/material/icon"; import { ChatService } from "../main/chat/chat.service"; import { UsersList } from "../interfaces/users-list"; -import { FirestoreService } from "../firestore.service"; import { addDoc, collection, getFirestore } from "@angular/fire/firestore"; -import { - MatAutocomplete, - MatAutocompleteModule, - MatAutocompleteSelectedEvent, - MatOption, -} from "@angular/material/autocomplete"; +import { MatAutocomplete, MatAutocompleteModule, MatAutocompleteSelectedEvent, MatOption } from "@angular/material/autocomplete"; import { Observable, map, startWith } from "rxjs"; -import { - MatChip, - MatChipGrid, - MatChipInputEvent, - MatChipListbox, - MatChipSet, - MatChipsModule, -} from "@angular/material/chips"; +import { MatChipGrid, MatChipsModule } from "@angular/material/chips"; import { CurrentuserService } from "../currentuser.service"; @Component({ @@ -72,29 +43,25 @@ export class DialogAddChannelAddMemberComponent { channelDescription: string; }; @Output() closeSheet = new EventEmitter(); + @ViewChild("nameInput") nameInput!: ElementRef; separatorKeysCodes: number[] = [ENTER, COMMA]; userCtrl = new FormControl(""); filteredMembers: Observable; addedMembers: UsersList[] = []; - public allOfficeUsers: UsersList[] = this.chatService.usersList; selectedOption: string = "1"; - - @ViewChild("nameInput") - nameInput!: ElementRef; - dataBase = getFirestore(); + public allOfficeUsers: UsersList[] = this.chatService.usersList; constructor( @Optional() @Inject(MAT_DIALOG_DATA) - private dialogData: { channelName: string; channelDescription: string }, + private dialogData: { channelName: string; channelDescription: string; }, @Optional() public dialogRef: MatDialogRef, public dialog: MatDialog, public chatService: ChatService, private currentUser: CurrentuserService, ) { - console.log(this.data); this.filteredMembers = this.userCtrl.valueChanges.pipe( startWith(""), map((value: string | null) => @@ -104,51 +71,69 @@ export class DialogAddChannelAddMemberComponent { .filter(user => user.id !== this.currentUser.currentUser.id) // Hier filtern wir den currentUser nach der ID ), ); - } + get data() { return this.bottomsheetData || this.dialogData; } + public async createChannel() { if (!this.data) { throw new Error("No data provided for channel creation"); } - - // Initialisiere die Mitglieder-Liste mit dem aktuellen Benutzer (currentUser) + + const members = this.initializeMembers(); + await this.saveChannel(members); + + this.closeSheet.emit(); + this.dialog.closeAll(); + } + + + private initializeMembers(): UsersList[] { let members: UsersList[] = [this.currentUser.currentUser]; - + if (this.selectedOption === "2") { - // Füge nur ausgewählte Mitglieder hinzu, die nicht bereits in der Liste sind - this.addedMembers.forEach((user) => { - if (!members.some(member => member.id === user.id)) { - members.push(user); - } - }); + this.addSelectedMembers(members); } else { - // Füge alle Mitglieder hinzu, aber stelle sicher, dass der currentUser nicht doppelt hinzugefügt wird - this.chatService.usersList.forEach((user) => { - if (!members.some(member => member.id === user.id)) { - members.push(user); - } - }); + this.addAllMembers(members); } - + + return members; + } + + + private addSelectedMembers(members: UsersList[]): void { + this.addedMembers.forEach((user) => { + if (!members.some(member => member.id === user.id)) { + members.push(user); + } + }); + } + + + private addAllMembers(members: UsersList[]): void { + this.chatService.usersList.forEach((user) => { + if (!members.some(member => member.id === user.id)) { + members.push(user); + } + }); + } + + + private async saveChannel(members: UsersList[]): Promise { const newChannel = await addDoc(collection(this.dataBase, "channels"), { name: this.data.channelName, description: this.data.channelDescription, creator: this.currentUser.currentUser.name, members: members, }); - - this.closeSheet.emit(); - this.dialog.closeAll(); + this.showChannel(newChannel.id); } - - - + remove(user: UsersList): void { const index = this.addedMembers.indexOf(user); @@ -158,6 +143,7 @@ export class DialogAddChannelAddMemberComponent { } } + selected(event: MatAutocompleteSelectedEvent): void { const value = (event.option.viewValue || "").trim(); @@ -171,15 +157,16 @@ export class DialogAddChannelAddMemberComponent { this.userCtrl.setValue(null); } + private _filter(value: string): UsersList[] { const filterValue = value.toLowerCase(); - + // Filtere den currentUser nach der ID aus return this.chatService.usersList .filter(user => user.name.toLowerCase().includes(filterValue)) .filter(user => user.id !== this.currentUser.currentUser.id); // Filter nach ID - } - + } + showChannel(id: string) { this.chatService.openChannel(id); @@ -188,6 +175,7 @@ export class DialogAddChannelAddMemberComponent { this.chatService.selectedDirectmessage = ""; } + closeDialog(): void { if (window.matchMedia("(max-width: 768px)").matches) { this.closeSheet.emit(); diff --git a/src/app/dialog-add-channel/dialog-add-channel.component.ts b/src/app/dialog-add-channel/dialog-add-channel.component.ts index b984700..aa87aab 100644 --- a/src/app/dialog-add-channel/dialog-add-channel.component.ts +++ b/src/app/dialog-add-channel/dialog-add-channel.component.ts @@ -2,17 +2,10 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { MatButtonModule } from "@angular/material/button"; -import { - MatDialog, - MatDialogActions, - MatDialogContent, - MatDialogRef, -} from "@angular/material/dialog"; +import { MatDialog, MatDialogActions, MatDialogContent, MatDialogRef } from "@angular/material/dialog"; import { MatInputModule } from "@angular/material/input"; import { DialogAddChannelAddMemberComponent } from "../dialog-add-channel-add-member/dialog-add-channel-add-member.component"; -import { collection, addDoc } from "firebase/firestore"; import { FirestoreService } from "../firestore.service"; -import { doc, setDoc } from "@angular/fire/firestore"; import { MatBottomSheet } from "@angular/material/bottom-sheet"; import { BottomsheetAddMemberNewChannelComponent } from "../bottomsheet-add-member-new-channel/bottomsheet-add-member-new-channel.component"; @@ -31,45 +24,54 @@ import { BottomsheetAddMemberNewChannelComponent } from "../bottomsheet-add-memb styleUrl: "./dialog-add-channel.component.scss", }) export class DialogAddChannelComponent { + dataBase = this.firestore.getFirestore(); + channelName: string = ""; + channelDescription: string = ""; + invalidName = false; + + constructor( public dialogRef: MatDialogRef, public dialog: MatDialog, private firestore: FirestoreService, private _bottomSheet: MatBottomSheet, - ) {} - dataBase = this.firestore.getFirestore(); - channelName: string = ""; - channelDescription: string = ""; - invalidName = false; + ) { } + closeDialog(): void { this.dialogRef.close(); } + nameValid() { return this.channelName.indexOf(" ") == -1; } + async forwardDialog(): Promise { if (this.nameValid() && this.channelName) { if (window.matchMedia("(max-width: 768px)").matches) { this.openBottomSheet(); } else { this.dialogRef.close(); - - // Öffnen Sie das neue Dialogfeld - this.dialog.open(DialogAddChannelAddMemberComponent, { - data: { - channelName: this.channelName, - channelDescription: this.channelDescription, - }, - }); + this.openDialogAddChannalAddMember(); } } else { this.invalidName = true; } } + + openDialogAddChannalAddMember() { + this.dialog.open(DialogAddChannelAddMemberComponent, { + data: { + channelName: this.channelName, + channelDescription: this.channelDescription, + }, + }); + } + + openBottomSheet(): void { this._bottomSheet.open(BottomsheetAddMemberNewChannelComponent, { data: { diff --git a/src/app/dialog-add-member-to-chnl/dialog-add-member-to-chnl.component.ts b/src/app/dialog-add-member-to-chnl/dialog-add-member-to-chnl.component.ts index bcc5fb8..8c623ae 100644 --- a/src/app/dialog-add-member-to-chnl/dialog-add-member-to-chnl.component.ts +++ b/src/app/dialog-add-member-to-chnl/dialog-add-member-to-chnl.component.ts @@ -1,41 +1,19 @@ import { CommonModule } from "@angular/common"; -import { Component, ElementRef, OnInit, ViewChild } from "@angular/core"; +import { Component, ElementRef, ViewChild } from "@angular/core"; import { FormControl, FormsModule, ReactiveFormsModule } from "@angular/forms"; import { MatButtonModule } from "@angular/material/button"; -import { - MatDialog, - MatDialogActions, - MatDialogContent, - MatDialogRef, -} from "@angular/material/dialog"; -import { - MatAutocomplete, - MatAutocompleteModule, - MatAutocompleteSelectedEvent, - MatOption, -} from "@angular/material/autocomplete"; +import { MatDialogActions, MatDialogContent, MatDialogRef } from "@angular/material/dialog"; +import { MatAutocomplete, MatAutocompleteModule, MatAutocompleteSelectedEvent, MatOption } from "@angular/material/autocomplete"; import { MatInputModule } from "@angular/material/input"; import { map, startWith } from "rxjs/operators"; import { COMMA, ENTER } from "@angular/cdk/keycodes"; import { Observable } from "rxjs"; import { ChatService } from "../main/chat/chat.service"; import { UsersList } from "../interfaces/users-list"; -import { - MatChipGrid, - MatChipInputEvent, - MatChipsModule, -} from "@angular/material/chips"; +import { MatChipGrid, MatChipsModule } from "@angular/material/chips"; import { MatIcon, MatIconModule } from "@angular/material/icon"; import { DialogChannelInfoComponent } from "../dialog-channel-info/dialog-channel-info.component"; -import { User } from "../interfaces/user"; import { doc, getFirestore, updateDoc } from "@angular/fire/firestore"; -interface Message { - avatar: string; - name: string; - time: string; - message: string; - reactions: object; -} @Component({ selector: "app-dialog-add-member-to-chnl", @@ -60,17 +38,13 @@ interface Message { styleUrls: ["./dialog-add-member-to-chnl.component.scss"], // Achtung: 'styleUrl' zu 'styleUrls' geändert und als Array definiert }) export class DialogAddMemberToChnlComponent { + @ViewChild("nameInput") nameInput!: ElementRef; separatorKeysCodes: number[] = [ENTER, COMMA]; userCtrl = new FormControl(""); filteredMembers: Observable; - // fruits: string[] = ['Lemon']; addedMembers: UsersList[] = []; - // allFruits: string[] = ['Apple', 'Lemon', 'Lime', 'Orange', 'Strawberry']; - - @ViewChild("nameInput") - nameInput!: ElementRef; dataBase = getFirestore(); - // announcer = inject(LiveAnnouncer); + constructor( public dialogRef: MatDialogRef, @@ -79,11 +53,11 @@ export class DialogAddMemberToChnlComponent { this.filteredMembers = this.userCtrl.valueChanges.pipe( startWith(""), map((value: string | null) => - value ? this._filter(value) : this.getAvailableUsers(), - ), + value ? this._filter(value) : this.getAvailableUsers()), ); } + public async addSelectedUsers() { const members = this.chatService.currentChannel.members; @@ -93,18 +67,14 @@ export class DialogAddMemberToChnlComponent { } await updateDoc( - doc( - this.dataBase, - "channels", - `${this.chatService.currentChannelID}`, - ), - { - members: members, - }, + doc(this.dataBase, "channels", `${this.chatService.currentChannelID}`), + { members: members }, ); + this.closeDialog(); } + getAvailableUsers(): UsersList[] { // Erstellen Sie ein Set mit den IDs der aktuellen Mitglieder für eine effiziente Überprüfung const memberIds = new Set( @@ -117,6 +87,7 @@ export class DialogAddMemberToChnlComponent { ); } + remove(user: UsersList): void { const index = this.addedMembers.indexOf(user); @@ -125,10 +96,8 @@ export class DialogAddMemberToChnlComponent { } } + selected(event: MatAutocompleteSelectedEvent): void { - console.log(this.chatService.usersList); - console.log(this.chatService.currentChannel.members); - console.log(this.getAvailableUsers()); const value = (event.option.value || "").trim(); // Add our member @@ -142,6 +111,7 @@ export class DialogAddMemberToChnlComponent { this.userCtrl.setValue(null); } + private _filter(value: string): UsersList[] { const filterValue = value.toLowerCase(); @@ -150,6 +120,7 @@ export class DialogAddMemberToChnlComponent { ); } + closeDialog(): void { this.dialogRef.close(); } diff --git a/src/app/dialog-channel-info/dialog-channel-info.component.ts b/src/app/dialog-channel-info/dialog-channel-info.component.ts index e0f8b18..f25cf5f 100644 --- a/src/app/dialog-channel-info/dialog-channel-info.component.ts +++ b/src/app/dialog-channel-info/dialog-channel-info.component.ts @@ -20,14 +20,15 @@ export class DialogChannelInfoComponent { name = ""; description = ""; invalidName = false; - dataBase = getFirestore(); + constructor( public dialogRef: MatDialogRef, public chatService: ChatService, private currentUser: CurrentuserService, - ) {} + ) { } + async leaveChannel() { const updatedMembers = this.chatService.currentChannel.members.filter( @@ -36,67 +37,63 @@ export class DialogChannelInfoComponent { await updateDoc( doc(this.dataBase, "channels", this.chatService.currentChannelID), - { - members: updatedMembers, - }, + { members: updatedMembers }, ); this.chatService.setComponent(""); - this.mobileGoBack() + this.mobileGoBack(); this.closeDialog(); } + editName() { this.name = this.chatService.currentChannel.name; this.editingName = true; } + mobileGoBack() { this.chatService.mobileOpen = ""; this.chatService.selectedChannel = ""; this.chatService.selectedDirectmessage = ""; } + async saveName() { if (this.nameValid() && this.name) { await updateDoc( - doc( - this.dataBase, - "channels", - this.chatService.currentChannelID, - ), - { - name: this.name, - }, + doc(this.dataBase, "channels", this.chatService.currentChannelID), + { name: this.name }, ); - this.editingName = false; } else { this.invalidName = true; } } + editDescription() { this.description = this.chatService.currentChannel.description; this.editingDescription = true; } + async saveDescription() { await updateDoc( doc(this.dataBase, "channels", this.chatService.currentChannelID), - { - description: this.description, - }, + { description: this.description }, ); - this.chatService.currentChannel.description = this.description; + this.chatService.currentChannel.description = this.description; this.editingDescription = false; } + nameValid() { return this.name.indexOf(" ") == -1; } + closeDialog(): void { this.dialogRef.close(); } diff --git a/src/app/dialog-edit-message/dialog-edit-message.component.ts b/src/app/dialog-edit-message/dialog-edit-message.component.ts index e924d0d..df35ab4 100644 --- a/src/app/dialog-edit-message/dialog-edit-message.component.ts +++ b/src/app/dialog-edit-message/dialog-edit-message.component.ts @@ -1,14 +1,7 @@ -import { Component, HostListener, Inject, viewChild, ViewChild } from "@angular/core"; -import { Dialog } from "@angular/cdk/dialog"; +import { Component, HostListener, Inject } from "@angular/core"; import { CommonModule } from "@angular/common"; import { MatButtonModule } from "@angular/material/button"; -import { - MAT_DIALOG_DATA, - MatDialog, - MatDialogActions, - MatDialogModule, -} from "@angular/material/dialog"; -import { MatMenu, MatMenuModule, MatMenuTrigger } from "@angular/material/menu"; +import { MAT_DIALOG_DATA, MatDialogActions } from "@angular/material/dialog"; import { PickerComponent } from "@ctrl/ngx-emoji-mart"; import { MatIconModule } from "@angular/material/icon"; import { MatDialogRef } from '@angular/material/dialog'; @@ -38,6 +31,7 @@ export class DialogEditMessageComponent { messageControl: FormControl; perLineCount = 9; + constructor( public dialogRef: MatDialogRef, // Hier wird `dialogRef` korrekt definiert @Inject(MAT_DIALOG_DATA) public data: { message: string; } @@ -45,31 +39,35 @@ export class DialogEditMessageComponent { this.messageControl = new FormControl(this.data.message, [Validators.required]); // Initialisierung von `messageControl` } + @HostListener('window:resize', ['$event']) onResize(event: Event) { this.isPickerVisible = false; } + togglePicker(event: MouseEvent) { if (window.matchMedia("(max-width: 350px)").matches) { this.perLineCount = 8; - } else { + } else { this.perLineCount = 9; - } + } this.isPickerVisible = !this.isPickerVisible; } + closePicker(event: Event) { if (this.isPickerVisible) { this.isPickerVisible = false; } } + addEmoji(event: any) { // Füge das Emoji an den aktuellen Wert der FormControl an const currentValue = this.messageControl.value || ''; // Falls der aktuelle Wert null oder leer ist this.messageControl.setValue(currentValue + event.emoji.native); -} + } onSave(): void { @@ -79,6 +77,7 @@ export class DialogEditMessageComponent { } } + onCancel() { this.dialogRef.close(); } diff --git a/src/app/dialog-edit-profile-edit-profile/dialog-edit-profile-edit-profile.component.ts b/src/app/dialog-edit-profile-edit-profile/dialog-edit-profile-edit-profile.component.ts index f0aa525..3b449dc 100644 --- a/src/app/dialog-edit-profile-edit-profile/dialog-edit-profile-edit-profile.component.ts +++ b/src/app/dialog-edit-profile-edit-profile/dialog-edit-profile-edit-profile.component.ts @@ -1,20 +1,13 @@ -import { Dialog, DialogModule, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; -import { Component, OnInit, ElementRef, ViewChild } from "@angular/core"; +import { Component, ElementRef, ViewChild } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { MatButtonModule } from "@angular/material/button"; -import { - MatDialog, - MatDialogActions, - MatDialogContent, - MatDialogRef, -} from "@angular/material/dialog"; +import { MatDialog, MatDialogActions, MatDialogContent, MatDialogRef } from "@angular/material/dialog"; import { MatInputModule } from "@angular/material/input"; import { FirestoreService } from "../firestore.service"; import { UsersList } from "../interfaces/users-list"; -import { User } from "../interfaces/user"; import { HeaderComponent } from "../main/header/header.component"; -import { DocumentData, doc, onSnapshot } from "@angular/fire/firestore"; +import { doc, onSnapshot } from "@angular/fire/firestore"; import { ImageService } from "../image.service"; @Component({ @@ -34,7 +27,6 @@ import { ImageService } from "../image.service"; }) export class DialogEditProfileEditProfileComponent { @ViewChild('avatarInput') avatarInput!: ElementRef; - currentUserUid: string | null = ""; currentUser: UsersList = { id: "", @@ -50,6 +42,7 @@ export class DialogEditProfileEditProfileComponent { emailError = false; nameError = false; + constructor( public dialogRef: MatDialogRef, public dialog: MatDialog, @@ -59,22 +52,24 @@ export class DialogEditProfileEditProfileComponent { this.firestore.currentUser$.subscribe((uid) => { this.currentUserUid = uid; this.subCurrentUser(); - // Führen Sie hier Aktionen aus, die vom aktuellen Benutzerstatus abhängen }); } + editProfile() { this.name = this.currentUser.name; this.email = this.currentUser.email; this.editing = true; } + cancel() { this.editing = false; this.name = this.currentUser.name; this.email = this.currentUser.email; } + save() { if (!this.name) { this.nameError = true; @@ -87,58 +82,69 @@ export class DialogEditProfileEditProfileComponent { } } + updateUser() { const oldName = this.currentUser.name; // Speichere den alten Namen + this.updateEmailAndUser() + .then(() => this.updateUserInChannels()) + .then(() => this.updateUserInChannelCreators(oldName)) + .then(() => this.onUserUpdateSuccess()) + .catch((error) => this.handleUpdateError(error)); + } - this.firestore - .updateEmail(this.email) - .then(() => { - return this.firestore.updateUser( - this.name, - this.email, - this.currentUser.avatar, - ); - }) - .then(() => { - // Aktualisiere den Benutzer in allen Channels, in denen er Mitglied ist - return this.firestore.updateUserInChannels({ - id: this.currentUser.id, - name: this.name, - email: this.email, - avatar: this.currentUser.avatar - }); - }) - .then(() => { - // Aktualisiere den Namen des Benutzers als creator in den Channels - return this.firestore.updateUserInChannelCreators(oldName, this.name); - }) - .then(() => { - console.log("User updated successfully"); - this.editing = false; - this.reloginError = false; + + updateEmailAndUser() { + return this.firestore.updateEmail(this.email).then(() => { + return this.firestore.updateUser( + this.name, + this.email, + this.currentUser.avatar + ); + }); + } + + + updateUserInChannels() { + return this.firestore.updateUserInChannels({ + id: this.currentUser.id, + name: this.name, + email: this.email, + avatar: this.currentUser.avatar, + }); + } + + + updateUserInChannelCreators(oldName: string) { + return this.firestore.updateUserInChannelCreators(oldName, this.name); + } + + + onUserUpdateSuccess() { + this.editing = false; + this.reloginError = false; + this.emailError = false; + } + + + handleUpdateError(error: any) { + switch (error.code) { + case "auth/requires-recent-login": + this.reloginError = true; this.emailError = false; - }) - .catch((error) => { - switch (error.code) { - case "auth/requires-recent-login": - this.emailError = false; - this.reloginError = true; - break; - case "auth/invalid-email": - this.reloginError = false; - this.emailError = true; - break; - default: - console.log("An unexpected error occurred."); - break; - } - }); + break; + case "auth/invalid-email": + this.reloginError = false; + this.emailError = true; + break; + } } + closeDialog() { this.dialogRef.close(); } + subCurrentUser() { let firestore = this.firestore.getFirestore(); let ref; @@ -148,10 +154,11 @@ export class DialogEditProfileEditProfileComponent { this.currentUser = this.setCurrentUserObj(doc.data(), doc.id); }); } else { - return console.log("invalid user uid"); + return; } } + setCurrentUserObj(obj: any, id: string): UsersList { return { id: id || "", @@ -162,12 +169,14 @@ export class DialogEditProfileEditProfileComponent { }; } + onAvatarClick() { if (this.currentUserUid !== "mMqjWie0OWa6lWCnq5hStLQqXow1") { this.avatarInput.nativeElement.click(); } } + onFileSelected(event: any) { const input = event.target as HTMLInputElement; if (input && input.files && input.files.length > 0) { @@ -184,14 +193,13 @@ export class DialogEditProfileEditProfileComponent { } } + saveAvatarUrl(url: string) { this.firestore.updateUser( this.currentUser.name, this.currentUser.email, url // Update the avatar URL in the user's profile - ).then(() => { - console.log("Avatar updated successfully"); - }).catch((error) => { + ).catch((error) => { console.error('Error updating avatar in Firestore:', error); }); } diff --git a/src/app/dialog-edit-profile/dialog-edit-profile.component.ts b/src/app/dialog-edit-profile/dialog-edit-profile.component.ts index 61032c4..7a0bdb5 100644 --- a/src/app/dialog-edit-profile/dialog-edit-profile.component.ts +++ b/src/app/dialog-edit-profile/dialog-edit-profile.component.ts @@ -1,14 +1,8 @@ -import { Dialog, DialogModule, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { MatButtonModule } from "@angular/material/button"; -import { - MatDialog, - MatDialogActions, - MatDialogContent, - MatDialogRef, -} from "@angular/material/dialog"; +import { MatDialog, MatDialogActions, MatDialogContent, MatDialogRef } from "@angular/material/dialog"; import { MatInputModule } from "@angular/material/input"; import { DialogEditProfileEditProfileComponent } from "../dialog-edit-profile-edit-profile/dialog-edit-profile-edit-profile.component"; import { FirestoreService } from "../firestore.service"; @@ -28,19 +22,22 @@ import { FirestoreService } from "../firestore.service"; styleUrl: "./dialog-edit-profile.component.scss", }) export class DialogEditProfileComponent { + + constructor( public dialogRef: MatDialogRef, public dialog: MatDialog, public firestore: FirestoreService, - ) {} + ) { } + logout() { this.dialogRef.close(); this.firestore.logout(); } + openDialog(event: MouseEvent): void { - // Sicherstellen, dass event.target tatsächlich ein Element ist. let element = event.target as Element | null; if (element) { // Casten zu HTMLElement, um Zugriff auf getBoundingClientRect zu gewährleisten. @@ -53,9 +50,14 @@ export class DialogEditProfileComponent { right: `${window.innerWidth - boundingClientRect.left - boundingClientRect.width + window.scrollX - 30}px`, }; - this.dialog.open(DialogEditProfileEditProfileComponent, { - position: dialogPosition, - }); + this.openDialogEditProfileEditProfile(dialogPosition); } } + + + openDialogEditProfileEditProfile(dialogPosition: { top: string, right: string; }) { + this.dialog.open(DialogEditProfileEditProfileComponent, { + position: dialogPosition, + }); + } } diff --git a/src/app/dialog-image/dialog-image.component.ts b/src/app/dialog-image/dialog-image.component.ts index 70cb241..3827c8a 100644 --- a/src/app/dialog-image/dialog-image.component.ts +++ b/src/app/dialog-image/dialog-image.component.ts @@ -1,5 +1,5 @@ import { Component, Inject } from '@angular/core'; -import {MAT_DIALOG_DATA} from '@angular/material/dialog'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; @Component({ selector: 'app-dialog-image', @@ -11,7 +11,5 @@ import {MAT_DIALOG_DATA} from '@angular/material/dialog'; export class DialogImageComponent { - constructor(@Inject(MAT_DIALOG_DATA) public data: string) { - - } + constructor(@Inject(MAT_DIALOG_DATA) public data: string) { } } diff --git a/src/app/dialog-show-channel-member/dialog-show-channel-member.component.ts b/src/app/dialog-show-channel-member/dialog-show-channel-member.component.ts index adbd8e2..619622e 100644 --- a/src/app/dialog-show-channel-member/dialog-show-channel-member.component.ts +++ b/src/app/dialog-show-channel-member/dialog-show-channel-member.component.ts @@ -14,28 +14,34 @@ import { UsersList } from "../interfaces/users-list"; styleUrl: "./dialog-show-channel-member.component.scss", }) export class DialogShowChannelMemberComponent { + + constructor( public dialogRef: MatDialogRef, public chatService: ChatService, public dialog: MatDialog, - ) {} + ) { } + closeDialog() { this.dialogRef.close(); } + openDialogAddMembers() { this.dialog.open(DialogAddMemberToChnlComponent, { panelClass: "custom-dialog-mid", }); } + openProfileCard(user: UsersList) { this.dialog.open(PofileInfoCardComponent, { data: user, }); } + isOnline(userId: string): boolean { const user = this.chatService.usersList.find( (user) => user.id === userId, diff --git a/src/app/emoji-picker/emoji-picker.component.html b/src/app/emoji-picker/emoji-picker.component.html deleted file mode 100644 index a7ca00d..0000000 --- a/src/app/emoji-picker/emoji-picker.component.html +++ /dev/null @@ -1 +0,0 @@ -

emoji-picker works!

diff --git a/src/app/emoji-picker/emoji-picker.component.scss b/src/app/emoji-picker/emoji-picker.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/emoji-picker/emoji-picker.component.spec.ts b/src/app/emoji-picker/emoji-picker.component.spec.ts deleted file mode 100644 index c555cd5..0000000 --- a/src/app/emoji-picker/emoji-picker.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { EmojiPickerComponent } from './emoji-picker.component'; - -describe('EmojiPickerComponent', () => { - let component: EmojiPickerComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [EmojiPickerComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(EmojiPickerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/emoji-picker/emoji-picker.component.ts b/src/app/emoji-picker/emoji-picker.component.ts deleted file mode 100644 index 1fdd6d2..0000000 --- a/src/app/emoji-picker/emoji-picker.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-emoji-picker', - standalone: true, - imports: [], - templateUrl: './emoji-picker.component.html', - styleUrl: './emoji-picker.component.scss' -}) -export class EmojiPickerComponent { - -} diff --git a/src/app/firestore.service.ts b/src/app/firestore.service.ts index 258b9ec..38dc550 100644 --- a/src/app/firestore.service.ts +++ b/src/app/firestore.service.ts @@ -1,36 +1,12 @@ import { Injectable, inject } from "@angular/core"; -import { - Firestore, - collection, - doc, - setDoc, - onSnapshot, - getFirestore, - CollectionReference, - DocumentData, - getDocs, - serverTimestamp, -} from "@angular/fire/firestore"; +import { Firestore, collection, doc, setDoc, CollectionReference, DocumentData, getDocs } from "@angular/fire/firestore"; import { Observable } from "rxjs"; -import { - getAuth, - GoogleAuthProvider, - createUserWithEmailAndPassword, - signInWithEmailAndPassword, - onAuthStateChanged, - sendPasswordResetEmail, - confirmPasswordReset, -} from "firebase/auth"; +import { getAuth, GoogleAuthProvider, createUserWithEmailAndPassword, signInWithEmailAndPassword, onAuthStateChanged, sendPasswordResetEmail, confirmPasswordReset } from "firebase/auth"; import { Router } from "@angular/router"; -import { - getRedirectResult, - signInWithPopup, - signOut, - updateEmail, -} from "@angular/fire/auth"; +import { signInWithPopup, signOut, updateEmail } from "@angular/fire/auth"; import { User } from "./interfaces/user"; import { getDatabase, onDisconnect, onValue, ref, set } from "@angular/fire/database"; -import { appConfig, firebaseConfig } from "./app.config"; +import { firebaseConfig } from "./app.config"; import { initializeApp } from "@angular/fire/app"; @Injectable({ @@ -46,79 +22,74 @@ export class FirestoreService { channelsRef: CollectionReference; private userStatusDatabaseRef: any; private heartbeatInterval: any; - app = initializeApp(firebaseConfig) + app = initializeApp(firebaseConfig); db = getDatabase(this.app, "https://dabubble-2a68b-default-rtdb.europe-west1.firebasedatabase.app"); constructor(private router: Router) { this.usersRef = collection(this.firestore, "users"); this.channelsRef = collection(this.firestore, "channels"); - - this.currentUser$ = new Observable((observer) => { - onAuthStateChanged(this.auth, (user) => { - if (user) { - console.log("Benutzer angemeldet:", user.uid); - observer.next(user.uid); - this.currentUserID = user.uid; - this.startHeartbeat(user.uid); // Heartbeat starten - if ( - this.router.url === "/login" || - this.router.url === "/signup" || - this.router.url === "/recovery" || - this.router.url === "/reset-password" - ) { - this.router.navigate(["/"]); - } - } else { - console.log("Kein Benutzer angemeldet"); - observer.next(null); - this.stopHeartbeat(); // Heartbeat stoppen - if (this.router.url === "/") { - this.router.navigate(["/login"]); - } - } - }); - }); + this.initializeReferences(); + this.currentUser$ = new Observable(this.observeAuthState); } - - private startHeartbeat(userId: string) { - this.userStatusDatabaseRef = ref(this.db, `status/${userId}`); - // Setze den Online-Status - set(this.userStatusDatabaseRef, { - online: true, - lastActive: new Date().toISOString(), // Manuell generierter Timestamp - }); + + private initializeReferences() { + this.usersRef = collection(this.firestore, "users"); + this.channelsRef = collection(this.firestore, "channels"); + } - // Offline-Status setzen, wenn die Verbindung unterbrochen wird - onDisconnect(this.userStatusDatabaseRef).set({ - online: false, - lastActive: new Date().toISOString(), // Manuell generierter Timestamp + + private observeAuthState = (observer: any) => { + onAuthStateChanged(this.auth, (user) => { + user ? this.handleUserLogin(observer, user) : this.handleUserLogout(observer); }); + }; - // Heartbeat alle 10 Sekunden senden - this.heartbeatInterval = setInterval(() => { - set(this.userStatusDatabaseRef, { - online: true, - lastActive: new Date().toISOString(), // Manuell generierter Timestamp - }); - }, 10000); // Alle 10 Sekunden + + private handleUserLogin(observer: any, user: any) { + observer.next(user.uid); + this.currentUserID = user.uid; + this.startHeartbeat(user.uid); + this.redirectAuthenticatedUser(); } + + private handleUserLogout(observer: any) { + observer.next(null); + this.stopHeartbeat(); + if (this.router.url === "/") this.router.navigate(["/login"]); + } + + private redirectAuthenticatedUser() { + const restrictedRoutes = ["/login", "/signup", "/recovery", "/reset-password"]; + if (restrictedRoutes.includes(this.router.url)) this.router.navigate(["/"]); + } + + private startHeartbeat(userId: string) { + this.userStatusDatabaseRef = ref(this.db, `status/${userId}`); + this.setUserOnlineStatus(); + onDisconnect(this.userStatusDatabaseRef).set({ online: false, lastActive: new Date().toISOString() }); + this.heartbeatInterval = setInterval(() => this.setUserOnlineStatus(), 10000); + } - + + private setUserOnlineStatus() { + set(this.userStatusDatabaseRef, { online: true, lastActive: new Date().toISOString() }); + } + + private stopHeartbeat() { if (!this.currentUserID) { - console.log("Kein Benutzer eingeloggt, Heartbeat-Stop wird übersprungen."); return; // Keine Aktionen, wenn kein Benutzer eingeloggt ist } - + if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); } - + if (this.userStatusDatabaseRef) { // Benutzer als offline markieren, wenn authentifiziert set(this.userStatusDatabaseRef, { @@ -126,83 +97,66 @@ export class FirestoreService { lastActive: new Date().toISOString(), // Manuell generierter Timestamp }); } - + this.currentUserID = ''; // Benutzer-ID löschen, da kein Benutzer mehr eingeloggt ist } - - - getUserStatus(userId: string, callback: (status: { online: boolean }) => void): void { + + + getUserStatus(userId: string, callback: (status: { online: boolean; }) => void): void { const db = getDatabase(); const statusRef = ref(db, `status/${userId}`); - + onValue(statusRef, (snapshot) => { - if (snapshot.exists()) { - const data = snapshot.val(); - callback({ online: data.online }); - } else { - callback({ online: false }); - } - }); - } - - // Abfrage des Online-Status für mehrere Benutzer - getUsersStatus(userIds: string[], callback: (statuses: { [key: string]: boolean }) => void): void { - const db = getDatabase(); - const statuses: { [key: string]: boolean } = {}; - - userIds.forEach(userId => { - const statusRef = ref(db, `status/${userId}`); - - onValue(statusRef, (snapshot) => { if (snapshot.exists()) { - const data = snapshot.val(); - statuses[userId] = data.online; + const data = snapshot.val(); + callback({ online: data.online }); } else { - statuses[userId] = false; + callback({ online: false }); } - - // Wenn alle Statusdaten abgerufen wurden - if (Object.keys(statuses).length === userIds.length) { - callback(statuses); // Callback mit den gesammelten Statusdaten - } - }); }); - } + } + + getUsersStatus(userIds: string[], callback: (statuses: { [key: string]: boolean }) => void): void { + const statuses: { [key: string]: boolean } = {}; + userIds.forEach((userId) => this.getUserStatusForId(userId, statuses, userIds, callback)); + } + + + private getUserStatusForId(userId: string, statuses: { [key: string]: boolean }, userIds: string[], callback: (statuses: { [key: string]: boolean }) => void) { + const statusRef = ref(this.db, `status/${userId}`); + onValue(statusRef, (snapshot) => { + statuses[userId] = snapshot.exists() ? snapshot.val().online : false; + if (Object.keys(statuses).length === userIds.length) callback(statuses); + }); + } + getFirestore(): Firestore { return this.firestore; } + loginWithGoogle = () => { signInWithPopup(this.auth, this.provider) - .then(async (result) => { - // User information is already available in the result from signInWithPopup - const user = result.user; + .then(this.handleGoogleSignIn) + .catch(this.handleGoogleSignInError); + }; - // Save the user data in Firestore - await this.saveUser( - { - avatar: user.photoURL || "", - name: user.displayName || "", - email: user.email || "", - }, - user.uid, - ); + + private handleGoogleSignIn = async (result: any) => { + const user = result.user; + await this.saveUser({ avatar: user.photoURL || "", name: user.displayName || "", email: user.email || "" }, user.uid); + await this.router.navigate(["/"]); + }; - // Navigate to home or any other route after successful login - await this.router.navigate(["/"]); - }) - .catch((error) => { - console.error("Google sign-in error:", error); - }); + + private handleGoogleSignInError = (error: any) => { + console.error("Google sign-in error:", error); }; - signUpWithEmailAndPassword( - email: string, - password: string, - ): Promise { + signUpWithEmailAndPassword( email: string, password: string ): Promise { return new Promise((resolve, reject) => { createUserWithEmailAndPassword(this.auth, email, password) .then((userCredential) => { @@ -215,14 +169,11 @@ export class FirestoreService { }); } - loginWithEmailAndPassword = ( - email: string, - password: string, - ): Promise => { + + loginWithEmailAndPassword = (email: string, password: string ): Promise => { return signInWithEmailAndPassword(this.auth, email, password) .then((userCredential) => { const user = userCredential.user; - console.log("Anmeldung erfolgreich", user.uid); this.router.navigate(["/"]); return null; }) @@ -231,18 +182,19 @@ export class FirestoreService { }); }; + logout() { this.stopHeartbeat(); // Setze den Online-Status auf false und stoppe den Heartbeat signOut(this.auth) .then(() => { - console.log("User erfolgreich ausgeloggt"); this.currentUserID = ''; // Clear the current user ID after logout - this.router.navigate(['/login']); // Weiterleitung nach dem Ausloggen + location.reload(); }) .catch((error) => { console.error("Fehler beim Ausloggen: ", error); }); - } + } + async saveUser(item: User, uid: string) { await setDoc( @@ -256,11 +208,9 @@ export class FirestoreService { ); } + resetPassword(email: string): Promise { return sendPasswordResetEmail(this.auth, email) - .then(() => { - console.log("Passwort-Reset-E-Mail gesendet."); - }) .catch((error) => { console.error( "Fehler beim Senden der Passwort-Reset-E-Mail: ", @@ -270,16 +220,17 @@ export class FirestoreService { }); } + confirmPasswordReset(code: string, newPassword: string): Promise { return confirmPasswordReset(this.auth, code, newPassword); } + async updateEmail(newEmail: string): Promise { const user = this.auth.currentUser; if (user) { try { await updateEmail(user, newEmail); - console.log("Email successfully updated!"); } catch (error) { console.error("Error updating email:", error); throw error; @@ -289,6 +240,7 @@ export class FirestoreService { } } + async updateUser(name: string, email: string, avatar: string) { const user = { avatar: avatar, @@ -299,55 +251,42 @@ export class FirestoreService { this.saveUser(user, this.currentUserID); } - async updateUserInChannels(updatedUser: { id: string, name: string, email: string, avatar: string }) { - // Holen Sie alle Channels ab + + async updateUserInChannels(updatedUser: { id: string; name: string; email: string; avatar: string }) { const channelsSnapshot = await getDocs(this.channelsRef); + channelsSnapshot.forEach((channelDoc) => this.updateChannelIfMember(channelDoc, updatedUser)); + } - // Durchlaufen Sie jeden Channel - channelsSnapshot.forEach(async (channelDoc) => { - const channelData = channelDoc.data(); - - // Wenn der Channel Mitglieder hat - if (channelData["members"] && Array.isArray(channelData["members"])) { - const memberIndex = channelData["members"].findIndex((member: any) => member.id === updatedUser.id); - - // Wenn der Benutzer in den Mitgliedern dieses Channels ist - if (memberIndex !== -1) { - // Aktualisieren Sie die Benutzerinformationen in der members-Liste - channelData["members"][memberIndex] = { - ...channelData["members"][memberIndex], - name: updatedUser.name, - email: updatedUser.email, - avatar: updatedUser.avatar - }; - // Speichern Sie die aktualisierten Mitgliederinformationen in der Datenbank - await setDoc(doc(this.channelsRef, channelDoc.id), { members: channelData["members"] }, { merge: true }); + private async updateChannelIfMember(channelDoc: any, updatedUser: { id: string; name: string; email: string; avatar: string }) { + const channelData = channelDoc.data(); + if (channelData["members"] && Array.isArray(channelData["members"])) { + const memberIndex = channelData["members"].findIndex((member: any) => member.id === updatedUser.id); + if (memberIndex !== -1) this.updateChannelMembers(channelDoc, channelData, memberIndex, updatedUser); + } + } - console.log(`User ${updatedUser.id} updated in channel ${channelDoc.id}`); - } - } - }); + + private async updateChannelMembers(channelDoc: any, channelData: any, memberIndex: number, updatedUser: { id: string; name: string; email: string; avatar: string }) { + channelData["members"][memberIndex] = { ...channelData["members"][memberIndex], name: updatedUser.name, email: updatedUser.email, avatar: updatedUser.avatar }; + await setDoc(doc(this.channelsRef, channelDoc.id), { members: channelData["members"] }, { merge: true }); } + async updateUserInChannelCreators(oldName: string, newName: string) { - // Holen Sie alle Channels ab const channelsSnapshot = await getDocs(this.channelsRef); - - // Durchlaufen Sie jeden Channel + channelsSnapshot.forEach(async (channelDoc) => { const channelData = channelDoc.data(); - + // Überprüfen, ob der alte Name des Benutzers als creator im Channel hinterlegt ist if (channelData["creator"] && channelData["creator"] === oldName) { // Aktualisieren Sie den Namen des Erstellers await setDoc(doc(this.channelsRef, channelDoc.id), { creator: newName }, { merge: true }); - - console.log(`Creator name updated from ${oldName} to ${newName} in channel ${channelDoc.id}`); } }); - } - + } + loginAsGuest() { const guestEmail = "guest@guest.guest"; diff --git a/src/app/image.service.ts b/src/app/image.service.ts index b2d2249..e8e260a 100644 --- a/src/app/image.service.ts +++ b/src/app/image.service.ts @@ -1,56 +1,44 @@ -import { inject, Injectable, OnInit } from "@angular/core"; +import { inject, Injectable } from "@angular/core"; import { Storage, getDownloadURL, ref, uploadBytesResumable } from '@angular/fire/storage'; @Injectable({ - providedIn: "root", + providedIn: "root", }) -export class ImageService { - public storage = inject(Storage); - -// uploadFile(input: HTMLInputElement) { -// if (!input.files) return - -// const files: FileList = input.files; - -// for (let i = 0; i < files.length; i++) { -// const file = files.item(i); -// if (file) { -// const storageRef = ref(this.storage, file.name); -// uploadBytesResumable(storageRef, file); -// } -// } -// } +export class ImageService { + public storage = inject(Storage); + + uploadFile(input: HTMLInputElement): Promise { + if (!this.hasValidFile(input)) return Promise.resolve(''); + const file = input.files![0]; + return this.uploadToStorage(file); + } + + + private hasValidFile(input: HTMLInputElement): boolean { + return !!(input.files && input.files.length > 0); + } + + + private uploadToStorage(file: File): Promise { return new Promise((resolve, reject) => { - if (!input.files || input.files.length === 0) { - resolve(''); // Rückgabe eines leeren Strings, wenn keine Dateien vorhanden sind - return; - } - - const file = input.files[0]; const storageRef = ref(this.storage, file.name); const uploadTask = uploadBytesResumable(storageRef, file); - - uploadTask.on( - 'state_changed', - (snapshot) => { - // Optional: Handle progress - }, - (error) => { - // Handle unsuccessful uploads - reject(error); - }, - () => { - // Handle successful uploads on complete - getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { - resolve(downloadURL); - console.log(downloadURL) - }); - } - ); + this.monitorUpload(uploadTask, resolve, reject); }); } - -} + private monitorUpload( + uploadTask: ReturnType, + resolve: (url: string) => void, + reject: (error: any) => void + ) { + uploadTask.on( + 'state_changed', + null, + (error) => reject(error), + () => getDownloadURL(uploadTask.snapshot.ref).then(resolve) + ); + } +} \ No newline at end of file diff --git a/src/app/interfaces/message.ts b/src/app/interfaces/message.ts index 16364be..62dd798 100644 --- a/src/app/interfaces/message.ts +++ b/src/app/interfaces/message.ts @@ -3,6 +3,7 @@ export interface Reaction { users: string[]; } + export interface Message { id: string; avatar: string; @@ -16,6 +17,7 @@ export interface Message { imageUrl: string; } + export const EMPTY_MESSAGE: Message = { id: '', avatar: '', diff --git a/src/app/main/chat/chat.component.html b/src/app/main/chat/chat.component.html index 0bcecd5..9d8c86d 100644 --- a/src/app/main/chat/chat.component.html +++ b/src/app/main/chat/chat.component.html @@ -31,7 +31,7 @@

{{ chatService.currentChannel.members.length }}

- @if (!emptyChannel()) { + @if (!chatService.emptyChannel()) {
@for ( message of chatService.currentChannel.messages | keyvalue; @@ -40,7 +40,7 @@

{{ chatService.currentChannel.members.length }}

) { @if (i == 0 || isLater(message.value.time, i - 1)) {
-

{{ dayDate(message.value.time) }}

+

{{ commonFnService.dayDate(message.value.time) }}

} @@ -48,7 +48,7 @@

{{ dayDate(message.value.time) }}

[id]="message.key" class="message">
- @@ -143,7 +143,7 @@
{{ message.value.name}}
} @else { 1 Antwort } -

Letzte Antwort {{ threadDateTime(lastMessageTime) }}

+

Letzte Antwort {{ threadService.threadDateTime(lastMessageTime) }}

@@ -151,8 +151,6 @@
{{ message.value.name}}
- - } @else {
@@ -163,7 +161,7 @@

{{ chatService.currentChannel.name }}