import { DatePipe, TitleCasePipe } from '@angular/common'; import { ChangeDetectorRef, Component } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; import { NgSelectModule } from '@ng-select/ng-select'; import { initFlowbite } from 'flowbite'; import { KeycloakService } from 'keycloak-angular'; import { NgxCurrencyDirective } from 'ngx-currency'; import { ImageCropperComponent } from 'ngx-image-cropper'; import { QuillModule } from 'ngx-quill'; import { lastValueFrom } from 'rxjs'; import { User } from '../../../../../../bizmatch-server/src/models/db.model'; import { AutoCompleteCompleteEvent, Invoice, StripeSubscription, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; import { environment } from '../../../../environments/environment'; import { ConfirmationComponent } from '../../../components/confirmation/confirmation.component'; import { ConfirmationService } from '../../../components/confirmation/confirmation.service'; import { ImageCropAndUploadComponent, UploadReponse } from '../../../components/image-crop-and-upload/image-crop-and-upload.component'; import { MessageComponent } from '../../../components/message/message.component'; import { MessageService } from '../../../components/message/message.service'; import { TooltipComponent } from '../../../components/tooltip/tooltip.component'; import { ValidatedCityComponent } from '../../../components/validated-city/validated-city.component'; import { ValidatedCountyComponent } from '../../../components/validated-county/validated-county.component'; import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component'; import { ValidatedLocationComponent } from '../../../components/validated-location/validated-location.component'; import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component'; import { ValidatedSelectComponent } from '../../../components/validated-select/validated-select.component'; import { ValidationMessagesService } from '../../../components/validation-messages.service'; import { GeoService } from '../../../services/geo.service'; import { ImageService } from '../../../services/image.service'; import { LoadingService } from '../../../services/loading.service'; import { SelectOptionsService } from '../../../services/select-options.service'; import { SharedService } from '../../../services/shared.service'; import { SubscriptionsService } from '../../../services/subscriptions.service'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; import { checkAndUpdate, map2User } from '../../../utils/utils'; import { TOOLBAR_OPTIONS } from '../../utils/defaults'; @Component({ selector: 'app-account', standalone: true, imports: [ SharedModule, QuillModule, NgxCurrencyDirective, NgSelectModule, ImageCropperComponent, ConfirmationComponent, ImageCropAndUploadComponent, MessageComponent, ValidatedInputComponent, ValidatedSelectComponent, ValidatedQuillComponent, ValidatedCityComponent, TooltipComponent, ValidatedCountyComponent, ValidatedLocationComponent, ], providers: [TitleCasePipe, DatePipe], templateUrl: './account.component.html', styleUrl: './account.component.scss', }) export class AccountComponent { id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; user: User; companyLogoUrl: string; profileUrl: string; type: 'company' | 'profile'; environment = environment; editorModules = TOOLBAR_OPTIONS; env = environment; faTrash = faTrash; quillModules = { toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']], }; uploadParams: UploadParams; validationMessages: ValidationMessage[] = []; customerTypeOptions: Array<{ value: string; label: string }> = []; customerSubTypeOptions: Array<{ value: string; label: string }> = []; tooltipTargetAreasServed = 'tooltip-areasServed'; tooltipTargetLicensed = 'tooltip-licensedIn'; subscriptions: StripeSubscription[] | any[]; constructor( public userService: UserService, private geoService: GeoService, public selectOptions: SelectOptionsService, private cdref: ChangeDetectorRef, private activatedRoute: ActivatedRoute, private loadingService: LoadingService, private imageUploadService: ImageService, private imageService: ImageService, private keycloakService: KeycloakService, private confirmationService: ConfirmationService, private messageService: MessageService, private sharedService: SharedService, private titleCasePipe: TitleCasePipe, private validationMessagesService: ValidationMessagesService, private subscriptionService: SubscriptionsService, private datePipe: DatePipe, private router: Router, ) {} async ngOnInit() { setTimeout(() => { initFlowbite(); }, 10); if (this.id) { this.user = await this.userService.getById(this.id); } else { const token = await this.keycloakService.getToken(); const keycloakUser = map2User(token); const email = keycloakUser.email; this.user = await this.userService.getByMail(email); } this.subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.email)); await this.synchronizeSubscriptions(this.subscriptions); this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`; this.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`; this.customerTypeOptions = this.selectOptions.customerTypes .filter(ct => ct.value === 'buyer' || ct.value === 'seller' || this.user.customerType === 'professional') .map(type => ({ value: type.value, label: this.titleCasePipe.transform(type.name), })); this.customerSubTypeOptions = this.selectOptions.customerSubTypes .filter(ct => ct.value !== 'broker' || this.user.customerSubType === 'broker') .map(type => ({ value: type.value, label: this.titleCasePipe.transform(type.name), })); } async synchronizeSubscriptions(subscriptions: StripeSubscription[]) { let changed = false; if (this.subscriptions.length === 0) { if (!this.user.subscriptionPlan) { this.router.navigate(['pricing']); } else { this.subscriptions = [{ ended_at: null, start_date: Math.floor(new Date(this.user.created).getTime() / 1000), status: null, metadata: { plan: 'Free Plan' } }]; changed = checkAndUpdate(changed, this.user.customerType !== 'buyer' && this.user.customerType !== 'seller', () => (this.user.customerType = 'buyer')); changed = checkAndUpdate(changed, !!this.user.customerSubType, () => (this.user.customerSubType = null)); changed = checkAndUpdate(changed, this.user.subscriptionPlan !== 'free', () => (this.user.subscriptionPlan = 'free')); changed = checkAndUpdate(changed, !!this.user.subscriptionId, () => (this.user.subscriptionId = null)); } } else { const subscription = subscriptions[0]; changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional')); changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerSubType !== 'broker', () => (this.user.customerSubType = 'broker')); changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.subscriptionPlan !== 'broker', () => (this.user.subscriptionPlan = 'broker')); changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && !this.user.subscriptionId, () => (this.user.subscriptionId = subscription.id)); changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional')); changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionPlan !== 'professional', () => (this.user.subscriptionPlan = 'professional')); changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionId !== 'professional', () => (this.user.subscriptionId = subscription.id)); } if (changed) { await this.userService.saveGuaranteed(this.user); this.cdref.detectChanges(); this.cdref.markForCheck(); } } ngOnDestroy() { this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten } printInvoice(invoice: Invoice) {} async updateProfile(user: User) { try { await this.userService.save(this.user); this.userService.changeUser(this.user); this.messageService.addMessage({ severity: 'success', text: 'Account changes have been persisted', duration: 3000 }); this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten this.validationMessages = []; } catch (error) { this.messageService.addMessage({ severity: 'danger', text: 'An error occurred while saving the profile - Please check your inputs', duration: 5000, }); if (error.error && Array.isArray(error.error?.message)) { this.validationMessagesService.updateMessages(error.error.message); this.validationMessages = error.error.message; } } } onUploadCompanyLogo(event: any) { const uniqueSuffix = '?_ts=' + new Date().getTime(); this.companyLogoUrl = `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}${uniqueSuffix}`; } onUploadProfilePicture(event: any) { const uniqueSuffix = '?_ts=' + new Date().getTime(); this.profileUrl = `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}${uniqueSuffix}`; } setImageToFallback(event: Event) { (event.target as HTMLImageElement).src = `/assets/images/placeholder.png`; // Pfad zum Platzhalterbild } suggestions: string[] | undefined; async search(event: AutoCompleteCompleteEvent) { const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query)); this.suggestions = result.map(r => `${r.name} - ${r.state}`).slice(0, 5); } addLicence() { this.user.licensedIn.push({ registerNo: '', state: '' }); } removeLicence(index: number) { this.user.licensedIn.splice(index, 1); } addArea() { this.user.areasServed.push({ county: '', state: '' }); } removeArea(index: number) { this.user.areasServed.splice(index, 1); } get isProfessional() { return this.user.customerType === 'professional'; } uploadCompanyLogo() { this.uploadParams = { type: 'uploadCompanyLogo', imagePath: emailToDirName(this.user.email) }; } uploadProfile() { this.uploadParams = { type: 'uploadProfile', imagePath: emailToDirName(this.user.email) }; } async uploadFinished(response: UploadReponse) { if (response.success) { if (response.type === 'uploadCompanyLogo') { this.user.hasCompanyLogo = true; // this.companyLogoUrl = `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}`; } else { this.user.hasProfile = true; this.profileUrl = `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}`; this.sharedService.changeProfilePhoto(this.profileUrl); } await this.userService.saveGuaranteed(this.user); } } async deleteConfirm(type: 'profile' | 'logo') { const confirmed = await this.confirmationService.showConfirmation({ message: `Do you want to delete your ${type === 'logo' ? 'Logo' : 'Profile'} image` }); if (confirmed) { if (type === 'profile') { this.user.hasProfile = false; await Promise.all([this.imageService.deleteProfileImagesByMail(this.user.email), this.userService.saveGuaranteed(this.user)]); } else { this.user.hasCompanyLogo = false; await Promise.all([this.imageService.deleteLogoImagesByMail(this.user.email), this.userService.saveGuaranteed(this.user)]); } this.user = await this.userService.getById(this.user.id); // this.messageService.showMessage('Image deleted'); this.messageService.addMessage({ severity: 'success', text: 'Image deleted.', duration: 3000, // 3 seconds }); } } getValidationMessage(fieldName: string): string { const message = this.validationMessages.find(msg => msg.field === fieldName); return message ? message.message : ''; } isAdmin() { return this.keycloakService.getUserRoles(true).includes('ADMIN'); } setState(index: number, state: string) { if (state === null) { this.user.areasServed[index].county = null; } } getLevel(i: number) { return this.subscriptions[i].metadata.plan; } getStartDate(i: number) { return this.datePipe.transform(new Date(this.subscriptions[i].start_date * 1000)); } getEndDate(i: number) { return this.subscriptions[i].status === 'trialing' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---'; } getNextSettlement(i: number) { return this.subscriptions[i].status === 'active' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---'; } getStatus(i: number) { return this.subscriptions[i].status ? this.subscriptions[i].status : ''; } }