diff --git a/bizmatch-server/src/file/file.service.ts b/bizmatch-server/src/file/file.service.ts index 609e7f8..4d18558 100644 --- a/bizmatch-server/src/file/file.service.ts +++ b/bizmatch-server/src/file/file.service.ts @@ -30,19 +30,33 @@ export class FileService { return this.subscriptions } async storeProfilePicture(file: Express.Multer.File, userId: string) { - const suffix = file.mimetype.includes('png') ? 'png' : 'jpg' - await fs.outputFile(`./pictures/profile/${userId}`, file.buffer); + // const suffix = file.mimetype.includes('png') ? 'png' : 'jpg' + // await fs.outputFile(`./pictures/profile/${userId}`, file.buffer); + let quality = 50; + const output = await sharp(file.buffer) + .resize({ width: 300 }) + .avif({ quality }) // Verwende AVIF + //.webp({ quality }) // Verwende Webp + .toBuffer(); + await sharp(output).toFile(`./pictures/profile/${userId}.avif`); } hasProfile(userId: string){ - return fs.existsSync(`./pictures/profile/${userId}`) + return fs.existsSync(`./pictures/profile/${userId}.avif`) } async storeCompanyLogo(file: Express.Multer.File, userId: string) { - const suffix = file.mimetype.includes('png') ? 'png' : 'jpg' - await fs.outputFile(`./pictures/logo/${userId}`, file.buffer); + // const suffix = file.mimetype.includes('png') ? 'png' : 'jpg' + let quality = 50; + const output = await sharp(file.buffer) + .resize({ width: 300 }) + .avif({ quality }) // Verwende AVIF + //.webp({ quality }) // Verwende Webp + .toBuffer(); + await sharp(output).toFile(`./pictures/logo/${userId}.avif`); // Ersetze Dateierweiterung + // await fs.outputFile(`./pictures/logo/${userId}`, file.buffer); } hasCompanyLogo(userId: string){ - return fs.existsSync(`./pictures/logo/${userId}`) + return fs.existsSync(`./pictures/logo/${userId}.avif`) } async getPropertyImages(listingId: string): Promise { @@ -103,13 +117,28 @@ export class FileService { //.webp({ quality }) // Verwende Webp .toBuffer(); - if (output.byteLength > maxSize) { - quality -= 5; // Justiere Qualität in feineren Schritten - } + // if (output.byteLength > maxSize) { + // quality -= 5; // Justiere Qualität in feineren Schritten + // } // } while (output.byteLength > maxSize && quality > 0); await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung let timeTaken = Date.now() - start; this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`) } - + getProfileImagesForUsers(userids:string){ + const ids = userids.split(','); + let result = {}; + for (const id of ids){ + result = {...result,[id]:fs.existsSync(`./pictures/profile/${id}.avif`)} + } + return result; + } + getCompanyLogosForUsers(userids:string){ + const ids = userids.split(','); + let result = {}; + for (const id of ids){ + result = {...result,[id]:fs.existsSync(`./pictures/logo/${id}.avif`)} + } + return result; + } } diff --git a/bizmatch-server/src/image/image.controller.ts b/bizmatch-server/src/image/image.controller.ts index 4cfeba1..55bb6cb 100644 --- a/bizmatch-server/src/image/image.controller.ts +++ b/bizmatch-server/src/image/image.controller.ts @@ -23,13 +23,22 @@ export class ImageController { @Post('uploadProfile/:id') @UseInterceptors(FileInterceptor('file'),) - uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) { - this.fileService.storeProfilePicture(file,id); + async uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) { + await this.fileService.storeProfilePicture(file,id); } @Post('uploadCompanyLogo/:id') @UseInterceptors(FileInterceptor('file'),) - uploadCompanyLogo(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) { - this.fileService.storeCompanyLogo(file,id); + async uploadCompanyLogo(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) { + await this.fileService.storeCompanyLogo(file,id); + } + + @Get('profileImages/:userids') + async getProfileImagesForUsers(@Param('userids') userids:string): Promise { + return await this.fileService.getProfileImagesForUsers(userids); + } + @Get('companyLogos/:userids') + async getCompanyLogosForUsers(@Param('userids') userids:string): Promise { + return await this.fileService.getCompanyLogosForUsers(userids); } } diff --git a/bizmatch-server/src/user/user.controller.ts b/bizmatch-server/src/user/user.controller.ts index 46c8d58..d67c6bc 100644 --- a/bizmatch-server/src/user/user.controller.ts +++ b/bizmatch-server/src/user/user.controller.ts @@ -7,22 +7,30 @@ import { UserEntity } from 'src/models/server.model.js'; @Controller('user') export class UserController { - constructor(private userService:UserService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger){} - - @Get(':id') - findById(@Param('id') id:string): any { - return this.userService.getUserById(id); - } + constructor(private userService: UserService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {} - @Post() - save(@Body() user: any):Promise{ - this.logger.info(`User persisted: `); - return this.userService.saveUser(user); - } - - @Post('search') - find(@Body() criteria: any): any { - return this.userService.findUser(criteria); - } + @Get(':id') + findById(@Param('id') id: string): any { + this.logger.info(`Searching for user with ID: ${id}`); + const user = this.userService.getUserById(id); + this.logger.info(`Found user: ${JSON.stringify(user)}`); + return user; + } + + @Post() + save(@Body() user: any): Promise { + this.logger.info(`Saving user: ${JSON.stringify(user)}`); + const savedUser = this.userService.saveUser(user); + this.logger.info(`User persisted: ${JSON.stringify(savedUser)}`); + return savedUser; + } + + @Post('search') + find(@Body() criteria: any): any { + this.logger.info(`Searching for users with criteria: ${JSON.stringify(criteria)}`); + const foundUsers = this.userService.findUser(criteria); + this.logger.info(`Found users: ${JSON.stringify(foundUsers)}`); + return foundUsers; + } } diff --git a/bizmatch-server/src/user/user.service.ts b/bizmatch-server/src/user/user.service.ts index 7d7cf30..2cda06f 100644 --- a/bizmatch-server/src/user/user.service.ts +++ b/bizmatch-server/src/user/user.service.ts @@ -19,7 +19,7 @@ export class UserService { companyWebsite:{ type: 'string' }, companyLocation:{ type: 'string' }, offeredServices:{ type: 'string' }, - areasServed:{ type: 'string' }, + areasServed:{ type: 'string[]' }, names:{ type: 'string[]', path:'$.licensedIn.name' }, values:{ type: 'string[]', path:'$.licensedIn.value' } }, { diff --git a/bizmatch/angular.json b/bizmatch/angular.json index 908d580..dd766d1 100644 --- a/bizmatch/angular.json +++ b/bizmatch/angular.json @@ -31,7 +31,8 @@ "src/assets" ], "styles": [ - "src/styles.scss" + "src/styles.scss", + "node_modules/quill/dist/quill.snow.css" ] }, "configurations": { diff --git a/bizmatch/package.json b/bizmatch/package.json index 625da45..0977085 100644 --- a/bizmatch/package.json +++ b/bizmatch/package.json @@ -39,6 +39,7 @@ "primeflex": "^3.3.1", "primeicons": "^6.0.1", "primeng": "^17.10.0", + "quill": "^1.3.7", "rxjs": "~7.8.1", "tslib": "^2.3.0", "urlcat": "^3.1.0", diff --git a/bizmatch/src/app/app.routes.ts b/bizmatch/src/app/app.routes.ts index a538ba5..2134fec 100644 --- a/bizmatch/src/app/app.routes.ts +++ b/bizmatch/src/app/app.routes.ts @@ -42,7 +42,7 @@ export const routes: Routes = [ component: DetailsUserComponent, }, { - path: 'account', + path: 'account/:id', component: AccountComponent, canActivate: [authGuard], }, diff --git a/bizmatch/src/app/components/footer/footer.component.html b/bizmatch/src/app/components/footer/footer.component.html index fce6111..5c7b0ce 100644 --- a/bizmatch/src/app/components/footer/footer.component.html +++ b/bizmatch/src/app/components/footer/footer.component.html @@ -4,7 +4,6 @@
footer sections
© 2024 Bizmatch All rights reserved.
-
BizMatch, Inc., 1001 Blucher Street, Corpus Christi, Texas 78401
@@ -19,7 +18,7 @@
diff --git a/bizmatch/src/app/components/header/header.component.ts b/bizmatch/src/app/components/header/header.component.ts index ef47ee0..f927703 100644 --- a/bizmatch/src/app/components/header/header.component.ts +++ b/bizmatch/src/app/components/header/header.component.ts @@ -21,8 +21,8 @@ import { User } from '../../../../../common-models/src/main.model'; }) export class HeaderComponent { public buildVersion = environment.buildVersion; - user:User; user$:Observable + user:User; public tabItems: MenuItem[]; public menuItems: MenuItem[]; activeItem @@ -33,6 +33,60 @@ export class HeaderComponent { ngOnInit(){ this.user$=this.userService.getUserObservable(); + this.user$.subscribe(u=>{ + this.user=u; + this.menuItems = [ + { + label: 'User Actions', + icon: 'fas fa-cog', + items: [ + { + label: 'Account', + icon: 'pi pi-user', + routerLink: `/account/${this.user.id}`, + visible: this.isUserLogedIn() + }, + { + label: 'Create Listing', + icon: 'pi pi-plus-circle', + routerLink: "/createListing", + visible: this.isUserLogedIn() + }, + { + label: 'My Listings', + icon: 'pi pi-list', + routerLink:"/myListings", + visible: this.isUserLogedIn() + }, + { + label: 'My Favorites', + icon: 'pi pi-star', + routerLink:"/myFavorites", + visible: this.isUserLogedIn() + }, + { + label: 'EMail Us', + icon: 'fa-regular fa-envelope', + routerLink:"/emailUs", + visible: this.isUserLogedIn() + }, + { + label: 'Logout', + icon: 'fa-solid fa-right-from-bracket', + routerLink:"/logout", + visible: this.isUserLogedIn() + }, + { + label: 'Login', + icon: 'fa-solid fa-right-from-bracket', + //routerLink:"/account", + command: () => this.login(), + visible: !this.isUserLogedIn() + }, + ] + } + ] + }); this.tabItems = [ { label: 'Businesses for Sale', @@ -50,63 +104,13 @@ export class HeaderComponent { fragment:'' } ]; - this.menuItems = [ - { - label: 'User Actions', - icon: 'fas fa-cog', - items: [ - { - label: 'Account', - icon: 'pi pi-user', - routerLink: '/account', - visible: this.isUserLoogedIn() - }, - { - label: 'Create Listing', - icon: 'pi pi-plus-circle', - routerLink: "/createListing", - visible: this.isUserLoogedIn() - }, - { - label: 'My Listings', - icon: 'pi pi-list', - routerLink:"/myListings", - visible: this.isUserLoogedIn() - }, - { - label: 'My Favorites', - icon: 'pi pi-star', - routerLink:"/myFavorites", - visible: this.isUserLoogedIn() - }, - { - label: 'EMail Us', - icon: 'fa-regular fa-envelope', - routerLink:"/emailUs", - visible: this.isUserLoogedIn() - }, - { - label: 'Logout', - icon: 'fa-solid fa-right-from-bracket', - routerLink:"/logout", - visible: this.isUserLoogedIn() - }, - { - label: 'Login', - icon: 'fa-solid fa-right-from-bracket', - //routerLink:"/account", - command: () => this.login(), - visible: !this.isUserLoogedIn() - }, - ] - } - ] + this.activeItem=this.tabItems[0]; } navigateWithState(dest: string, state: any) { this.router.navigate([dest], { state: state }); } - isUserLoogedIn(){ + isUserLogedIn(){ return this.userService?.isLoggedIn(); } login(){ diff --git a/bizmatch/src/app/pages/details/details-user/details-user.component.html b/bizmatch/src/app/pages/details/details-user/details-user.component.html index 769abfc..5a1d4bd 100644 --- a/bizmatch/src/app/pages/details/details-user/details-user.component.html +++ b/bizmatch/src/app/pages/details/details-user/details-user.component.html @@ -8,8 +8,12 @@
- + @if(user.hasProfile){ + + } @else { + + }
{{user.firstname}} {{user.lastname}} @@ -28,8 +32,14 @@
-
+
+ @if(user.hasCompanyLogo){ + + } + +
@@ -37,18 +47,13 @@
-

{{user.description}}

Company Profile
-
{{user.companyOverview}}
+
  • Name
    @@ -67,14 +72,10 @@
    {{user.companyLocation}}
  • -
    Company Overview
    -
    {{user.companyOverview}}
    +
    Services we offer
    +
  • -
    Services we offer
    -
    {{user.offeredServices}}
    -
  • -
  • Areas we serve
    @for (area of user.areasServed; track area) { @@ -123,7 +124,7 @@
    @if( user?.id===(user$| async)?.id || isAdmin()){ + [routerLink]="['/account',user.id]"> }
diff --git a/bizmatch/src/app/pages/details/details-user/details-user.component.scss b/bizmatch/src/app/pages/details/details-user/details-user.component.scss index e69de29..2980734 100644 --- a/bizmatch/src/app/pages/details/details-user/details-user.component.scss +++ b/bizmatch/src/app/pages/details/details-user/details-user.component.scss @@ -0,0 +1,3 @@ +:host ::ng-deep p { + margin:3px 0; +} \ No newline at end of file diff --git a/bizmatch/src/app/pages/details/details-user/details-user.component.ts b/bizmatch/src/app/pages/details/details-user/details-user.component.ts index 741cf2a..7e5d194 100644 --- a/bizmatch/src/app/pages/details/details-user/details-user.component.ts +++ b/bizmatch/src/app/pages/details/details-user/details-user.component.ts @@ -9,6 +9,8 @@ import { UserService } from '../../../services/user.service'; import { Observable } from 'rxjs'; import { ListingsService } from '../../../services/listings.service'; import { SelectOptionsService } from '../../../services/select-options.service'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { ImageService } from '../../../services/image.service'; @Component({ selector: 'app-details-user', @@ -25,18 +27,25 @@ export class DetailsUserComponent { environment = environment; criteria:ListingCriteria; userListings:BusinessListing[] + companyOverview:SafeHtml; + offeredServices:SafeHtml; constructor(private activatedRoute: ActivatedRoute, private router: Router, private userService: UserService, private listingsService:ListingsService, private messageService: MessageService, - public selectOptions: SelectOptionsService) { + public selectOptions: SelectOptionsService, + private sanitizer: DomSanitizer, + private imageService:ImageService) { } async ngOnInit() { this.user = await this.userService.getById(this.id); + this.userListings = await this.listingsService.getListingByUserId(this.id); this.user$ = this.userService.getUserObservable(); + this.companyOverview=this.sanitizer.bypassSecurityTrustHtml(this.user.companyOverview); + this.offeredServices=this.sanitizer.bypassSecurityTrustHtml(this.user.offeredServices); } back() { this.router.navigate(['listings', this.criteria.listingsCategory]) diff --git a/bizmatch/src/app/pages/home/home.component.html b/bizmatch/src/app/pages/home/home.component.html index af00958..9a120e7 100644 --- a/bizmatch/src/app/pages/home/home.component.html +++ b/bizmatch/src/app/pages/home/home.component.html @@ -5,31 +5,13 @@
diff --git a/bizmatch/src/app/pages/home/home.component.ts b/bizmatch/src/app/pages/home/home.component.ts index 2439ac1..e74f759 100644 --- a/bizmatch/src/app/pages/home/home.component.ts +++ b/bizmatch/src/app/pages/home/home.component.ts @@ -12,7 +12,8 @@ import { SelectOptionsService } from '../../services/select-options.service'; import { UserService } from '../../services/user.service'; import onChange from 'on-change'; import { getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils'; -import { ListingCriteria } from '../../../../../common-models/src/main.model'; +import { ListingCriteria, User } from '../../../../../common-models/src/main.model'; +import { Observable } from 'rxjs'; @Component({ selector: 'app-home', standalone: true, @@ -26,20 +27,20 @@ export class HomeComponent { maxPrice:string; minPrice:string; criteria:ListingCriteria + user$:Observable public constructor(private router: Router,private activatedRoute: ActivatedRoute, public selectOptions:SelectOptionsService, public userService:UserService) { this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler); } ngOnInit(){ - + this.user$=this.userService.getUserObservable(); } search(){ this.router.navigate([`listings/${this.activeTabAction}`]) } - account(){ - setTimeout(()=>{ - this.router.navigate([`account`]) - },10); - } + + login(){ + this.userService.login(window.location.href); + } } \ No newline at end of file diff --git a/bizmatch/src/app/pages/listings/listings.component.html b/bizmatch/src/app/pages/listings/listings.component.html index 3813700..9bc1a14 100644 --- a/bizmatch/src/app/pages/listings/listings.component.html +++ b/bizmatch/src/app/pages/listings/listings.component.html @@ -121,11 +121,15 @@ } @for (user of users; track user.id) { -
-
+
+
- + @if(user.hasProfile){ + + } @else { + + }

{{user.description}}

@@ -134,7 +138,11 @@
- + @if(user.hasCompanyLogo){ + + } @else { + + } diff --git a/bizmatch/src/app/pages/listings/listings.component.scss b/bizmatch/src/app/pages/listings/listings.component.scss index 248f146..af0a00b 100644 --- a/bizmatch/src/app/pages/listings/listings.component.scss +++ b/bizmatch/src/app/pages/listings/listings.component.scss @@ -14,8 +14,9 @@ } .rounded-image { border-radius: 6px; - width: 100px; - height: 25px; + // width: 100px; + max-width: 100px; + height: 45px; border: 1px solid rgba(0,0,0,0.2); padding: 1px 1px; object-fit: contain; diff --git a/bizmatch/src/app/pages/listings/listings.component.ts b/bizmatch/src/app/pages/listings/listings.component.ts index 098d2eb..bcb2307 100644 --- a/bizmatch/src/app/pages/listings/listings.component.ts +++ b/bizmatch/src/app/pages/listings/listings.component.ts @@ -18,6 +18,7 @@ import { InitEditableRow } from 'primeng/table'; import { environment } from '../../../environments/environment'; import { ListingCriteria, ListingType, User } from '../../../../../common-models/src/main.model'; import { UserService } from '../../services/user.service'; +import { ImageService } from '../../services/image.service'; @Component({ selector: 'app-listings', standalone: true, @@ -49,7 +50,8 @@ export class ListingsComponent { private userService:UserService, private activatedRoute: ActivatedRoute, private router:Router, - private cdRef:ChangeDetectorRef) { + private cdRef:ChangeDetectorRef, + private imageService:ImageService) { this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler); this.router.getCurrentNavigation() this.activatedRoute.snapshot @@ -80,6 +82,12 @@ export class ListingsComponent { this.listings=[] this.filteredListings=[]; this.users=await this.userService.search(this.criteria); + const profiles = await this.imageService.getProfileImagesForUsers(this.users.map(u=>u.id)); + const logos = await this.imageService.getCompanyLogosForUsers(this.users.map(u=>u.id)); + this.users.forEach(u=>{ + u.hasProfile=profiles[u.id] + u.hasCompanyLogo=logos[u.id] + }) this.cdRef.markForCheck(); this.cdRef.detectChanges(); } diff --git a/bizmatch/src/app/pages/subscription/account/account.component.html b/bizmatch/src/app/pages/subscription/account/account.component.html index 2df31a5..cc856a6 100644 --- a/bizmatch/src/app/pages/subscription/account/account.component.html +++ b/bizmatch/src/app/pages/subscription/account/account.component.html @@ -1,4 +1,3 @@ -
@@ -16,7 +15,8 @@
-

You can only modify your email by contacting us at emailchange@bizmatch.net

+

You can only modify your email by contacting us at + emailchange@bizmatch.net

@@ -48,60 +48,109 @@
- - + +
- -
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + +
+
- -
-
+ +
+
@for (licensedIn of user.licensedIn; track licensedIn.value){
- +
- +
} -
+
- +  (Add more licenses or remove existing ones.) -
- +
+
- +
Company Logo (is shown in every offer) - - + @if(user.hasCompanyLogo){ + + } @else { + + } +
Your Profile Picture - - + @if(user.hasProfile){ + + } @else { + + } +
Membership Level
- + @@ -116,7 +165,9 @@ - + {{ subscription.id }} {{ subscription.level }} @@ -132,31 +183,51 @@
- - - ID - Date - Price - - - - - - - - {{ invoice.id }} - {{ invoice.date | date}} - {{ invoice.price | currency}} - - - - - -
+ + + ID + Date + Price + +
+ + + + + {{ invoice.id }} + {{ invoice.date | date}} + {{ invoice.price | currency}} + +
+ + + +
- \ No newline at end of file + + + + + + +
+ @if(type==='company'){ +
+ +
+ } @else { +
+ } +
+ + +
+
+
+
\ No newline at end of file diff --git a/bizmatch/src/app/pages/subscription/account/account.component.ts b/bizmatch/src/app/pages/subscription/account/account.component.ts index 8c3eec8..cb87c63 100644 --- a/bizmatch/src/app/pages/subscription/account/account.component.ts +++ b/bizmatch/src/app/pages/subscription/account/account.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectorRef, Component, ViewChild } from '@angular/core'; import { ButtonModule } from 'primeng/button'; import { CheckboxModule } from 'primeng/checkbox'; import { InputTextModule } from 'primeng/inputtext'; @@ -15,56 +15,72 @@ import { ChipModule } from 'primeng/chip'; import { MenuAccountComponent } from '../../menu-account/menu-account.component'; import { DividerModule } from 'primeng/divider'; import { TableModule } from 'primeng/table'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpEventType } from '@angular/common/http'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; import { SubscriptionsService } from '../../../services/subscriptions.service'; import { lastValueFrom } from 'rxjs'; import { MessageService } from 'primeng/api'; import { environment } from '../../../../environments/environment'; -import { FileUploadModule } from 'primeng/fileupload'; -import { AutoCompleteCompleteEvent, Invoice, Subscription, User } from '../../../../../../common-models/src/main.model'; +import { FileUpload, FileUploadModule } from 'primeng/fileupload'; +import { AutoCompleteCompleteEvent, Invoice, KeyValue, KeyValueRatio, Subscription, User } from '../../../../../../common-models/src/main.model'; import { GeoService } from '../../../services/geo.service'; import { ChangeDetectionStrategy } from '@angular/compiler'; - - +import { EditorModule } from 'primeng/editor'; +import { LoadingService } from '../../../services/loading.service'; +import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs'; +import { ImageService } from '../../../services/image.service'; +import { DialogModule } from 'primeng/dialog'; +import { SelectButtonModule } from 'primeng/selectbutton'; @Component({ selector: 'app-account', standalone: true, - // imports: [CommonModule, StyleClassModule, MenuAccountComponent, DividerModule,ButtonModule, TableModule, InputTextModule, DropdownModule, FormsModule, ChipModule,InputTextareaModule ], - imports: [SharedModule,FileUploadModule], + imports: [SharedModule,FileUploadModule,EditorModule,AngularCropperjsModule,DialogModule,SelectButtonModule], providers:[MessageService], templateUrl: './account.component.html', styleUrl: './account.component.scss' }) export class AccountComponent { + @ViewChild(CropperComponent) public angularCropper: CropperComponent; + @ViewChild('companyUpload') public companyUpload: FileUpload; + @ViewChild('profileUpload') public profileUpload: FileUpload; + private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; user:User; subscriptions:Array; userSubscriptions:Array=[]; - uploadProfileUrl:string; - uploadCompanyUrl:string; maxFileSize=1000000; companyLogoUrl:string; profileUrl:string; + imageUrl; + type:'company'|'profile' + stateOptions:KeyValueRatio[]=[ + {label:'16/9',value:16/9}, + {label:'1/1',value:1}, + {label:'2/3',value:2/3}, + ] + value:number = this.stateOptions[0].value; + config={aspectRatio: this.value} + environment=environment constructor(public userService: UserService, private subscriptionService: SubscriptionsService, private messageService: MessageService, private geoService:GeoService, public selectOptions:SelectOptionsService, - private cdref:ChangeDetectorRef) { - this.user=this.userService.getUser() + private cdref:ChangeDetectorRef, + private activatedRoute: ActivatedRoute, + private loadingService:LoadingService, + private imageUploadService: ImageService) { + } async ngOnInit(){ - + this.user=await this.userService.getById(this.id); this.userSubscriptions=await lastValueFrom(this.subscriptionService.getAllSubscriptions()); - this.uploadProfileUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadProfile/${this.user.id}`; - this.uploadCompanyUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadCompanyLogo/${this.user.id}`; if (!this.user.licensedIn || this.user.licensedIn?.length===0){ this.user.licensedIn = [{name:'',value:''}] } this.user=await this.userService.getById(this.user.id); - this.profileUrl = this.user.hasProfile?`${environment.apiBaseUrl}/profile/${this.user.id}`:`/assets/images/placeholder.png` - this.companyLogoUrl = this.user.hasCompanyLogo?`${environment.apiBaseUrl}/logo/${this.user.id}`:`/assets/images/placeholder.png` + this.profileUrl = this.user.hasProfile?`${environment.apiBaseUrl}/profile/${this.user.id}.avif`:`/assets/images/placeholder.png` + this.companyLogoUrl = this.user.hasCompanyLogo?`${environment.apiBaseUrl}/logo/${this.user.id}.avif`:`/assets/images/placeholder.png` } printInvoice(invoice:Invoice){} @@ -99,4 +115,60 @@ export class AccountComponent { removeLicence(){ this.user.licensedIn.splice(this.user.licensedIn.length-2,1); } + + select(event:any,type:'company'|'profile'){ + this.imageUrl = URL.createObjectURL(event.files[0]); + this.type=type + this.config={aspectRatio: type==='company'?this.stateOptions[0].value:this.stateOptions[2].value} + } + sendImage(){ + this.imageUrl=null + this.loadingService.startLoading('uploadImage'); + this.angularCropper.cropper.getCroppedCanvas().toBlob(async(blob) => { + if (this.type==='company'){ + this.imageUploadService.uploadCompanyLogo(blob,this.user.id).subscribe(async(event) => { + if (event.type === HttpEventType.UploadProgress) { + // Berechne und zeige den Fortschritt basierend auf event.loaded und event.total + const progress = event.total ? event.loaded / event.total : 0; + console.log(`Upload-Fortschritt: ${progress * 100}%`); + // Hier könntest du beispielsweise eine Fortschrittsanzeige aktualisieren + } else if (event.type === HttpEventType.Response) { + console.log('Upload abgeschlossen', event.body); + this.companyUpload.clear(); + this.loadingService.stopLoading('uploadImage'); + this.companyLogoUrl=`${environment.apiBaseUrl}/logo/${this.user.id}.avif?_ts=${new Date().getTime()}` + } + }, error => console.error('Fehler beim Upload:', error)); + } else { + this.imageUploadService.uploadProfileImage(blob,this.user.id).subscribe(async(event) => { + if (event.type === HttpEventType.UploadProgress) { + // Berechne und zeige den Fortschritt basierend auf event.loaded und event.total + const progress = event.total ? event.loaded / event.total : 0; + console.log(`Upload-Fortschritt: ${progress * 100}%`); + // Hier könntest du beispielsweise eine Fortschrittsanzeige aktualisieren + } else if (event.type === HttpEventType.Response) { + console.log('Upload abgeschlossen', event.body); + this.profileUpload.clear(); + this.loadingService.stopLoading('uploadImage'); + this.profileUrl=`${environment.apiBaseUrl}/profile/${this.user.id}.avif?_ts=${new Date().getTime()}` + } + }, error => console.error('Fehler beim Upload:', error)); + } + + + // this.fileUpload.upload(); + }, 'image/png'); + } + cancelUpload(){ + this.imageUrl=null + if (this.type==='company'){ + this.companyUpload.clear(); + } else { + this.profileUpload.clear(); + } + } + changeAspectRation(ratio:number){ + this.config={aspectRatio: ratio} + this.angularCropper.cropper.setAspectRatio(ratio); + } } diff --git a/bizmatch/src/app/pages/subscription/edit-listing/edit-listing.component.ts b/bizmatch/src/app/pages/subscription/edit-listing/edit-listing.component.ts index 7bf9edd..dafc117 100644 --- a/bizmatch/src/app/pages/subscription/edit-listing/edit-listing.component.ts +++ b/bizmatch/src/app/pages/subscription/edit-listing/edit-listing.component.ts @@ -134,7 +134,7 @@ export class EditListingComponent { this.loadingService.startLoading('uploadImage'); this.angularCropper.cropper.getCroppedCanvas().toBlob(async(blob) => { - this.imageUploadService.uploadImage(blob).subscribe(async(event) => { + this.imageUploadService.uploadPropertyImage(blob,this.listing.id).subscribe(async(event) => { if (event.type === HttpEventType.UploadProgress) { // Berechne und zeige den Fortschritt basierend auf event.loaded und event.total const progress = event.total ? event.loaded / event.total : 0; diff --git a/bizmatch/src/app/services/image.service.ts b/bizmatch/src/app/services/image.service.ts index 2bf4817..c584c43 100644 --- a/bizmatch/src/app/services/image.service.ts +++ b/bizmatch/src/app/services/image.service.ts @@ -1,23 +1,44 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { environment } from '../../environments/environment'; +import { lastValueFrom } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ImageService { - private uploadUrl = 'http://localhost:3000/bizmatch/image/uploadPropertyPicture/1a4b800e-793c-4c47-b987-7bf634060a4e'; + private apiBaseUrl = environment.apiBaseUrl; constructor(private http: HttpClient) { } - uploadImage(imageBlob: Blob) { + uploadPropertyImage(imageBlob: Blob,listingId:string) { + const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${listingId}`; + return this.uploadImage(imageBlob,uploadUrl); + } + uploadCompanyLogo(imageBlob: Blob,userId:string) { + const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/uploadCompanyLogo/${userId}`; + return this.uploadImage(imageBlob,uploadUrl); + } + uploadProfileImage(imageBlob: Blob,userId:string) { + const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/uploadProfile/${userId}`; + return this.uploadImage(imageBlob,uploadUrl); + } + uploadImage(imageBlob: Blob,uploadUrl:string) { const formData = new FormData(); formData.append('file', imageBlob, 'image.png'); - return this.http.post(this.uploadUrl, formData,{ + return this.http.post(uploadUrl, formData,{ // headers: this.headers, reportProgress: true, observe: 'events', }); } + + async getProfileImagesForUsers(userids:string[]){ + return await lastValueFrom(this.http.get<[]>(`${this.apiBaseUrl}/bizmatch/image/profileImages/${userids.join(',')}`)); + } + async getCompanyLogosForUsers(userids:string[]){ + return await lastValueFrom(this.http.get<[]>(`${this.apiBaseUrl}/bizmatch/image/companyLogos/${userids.join(',')}`)); + } } diff --git a/bizmatch/src/app/services/loading.service.ts b/bizmatch/src/app/services/loading.service.ts index 2f823ff..a5b7cf6 100644 --- a/bizmatch/src/app/services/loading.service.ts +++ b/bizmatch/src/app/services/loading.service.ts @@ -20,7 +20,7 @@ export class LoadingService { public startLoading(type: string,request?:string): void { if (!this.loading$.value.includes(type)) { this.loading$.next(this.loading$.value.concat(type)); - if (type==='uploadImage' || request?.includes('uploadPropertyPicture')) { + if (type==='uploadImage' || request?.includes('uploadPropertyPicture')|| request?.includes('uploadProfile')|| request?.includes('uploadCompanyLogo')) { this.loadingTextSubject.next("Please wait - we're processing your image..."); } else { this.loadingTextSubject.next(null); diff --git a/bizmatch/src/assets/images/person_placeholder.jpg b/bizmatch/src/assets/images/person_placeholder.jpg new file mode 100644 index 0000000..5e2b51e Binary files /dev/null and b/bizmatch/src/assets/images/person_placeholder.jpg differ diff --git a/bizmatch/src/assets/images/person_placeholder.jpg:Zone.Identifier b/bizmatch/src/assets/images/person_placeholder.jpg:Zone.Identifier new file mode 100644 index 0000000..60f0f97 --- /dev/null +++ b/bizmatch/src/assets/images/person_placeholder.jpg:Zone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +LastWriterPackageFamilyName=Microsoft.Windows.Photos_8wekyb3d8bbwe +ZoneId=3 diff --git a/common-models/src/main.model.ts b/common-models/src/main.model.ts index a8aedf0..8aef2c5 100644 --- a/common-models/src/main.model.ts +++ b/common-models/src/main.model.ts @@ -2,6 +2,10 @@ export interface KeyValue { name: string; value: string; } +export interface KeyValueRatio { + label: string; + value: number; +} export interface KeyValueStyle { name: string; value: string; diff --git a/crawler/import.ts b/crawler/importListing.ts similarity index 100% rename from crawler/import.ts rename to crawler/importListing.ts diff --git a/crawler/importUser.ts b/crawler/importUser.ts new file mode 100644 index 0000000..938ad26 --- /dev/null +++ b/crawler/importUser.ts @@ -0,0 +1,13 @@ +import fs from 'fs-extra'; + +(async () => { + const listings = await fs.readJson('./users.json'); + //listings.forEach(element => { + for (const listing of listings) { + const response = await fetch('http://localhost:3000/bizmatch/user', { + method: 'POST', + body: JSON.stringify(listing), + headers: { 'Content-Type': 'application/json' }, + }); + } +})(); diff --git a/crawler/index.ts b/crawler/index.ts index 77adc67..5dbb98e 100644 --- a/crawler/index.ts +++ b/crawler/index.ts @@ -106,7 +106,7 @@ async function extractListingData(page: Page): Promise { employees: content[labels.employeesLabel], reasonForSale: content[labels.reasonLabel], internals: '', - } as BusinessListing; + } as any; return listing; } catch (error) { console.log(`Fehler bei ${title}`); diff --git a/crawler/updateFields.ts b/crawler/updateFields.ts index 2c451c4..0560e9e 100644 --- a/crawler/updateFields.ts +++ b/crawler/updateFields.ts @@ -21,9 +21,9 @@ import { BusinessListing } from "../common-models/src/main.model" }) const listings:Array = await response.json(); for (const listing of listings) { - const option = selectOptions.locations.find(l=>l.name.toLowerCase()===listing.location.toLowerCase()); + const option = selectOptions.locations.find(l=>l.name.toLowerCase()===listing.state.toLowerCase()); if (option){ - listing.location=option.value + listing.state=option.value } const response = await fetch(`http://localhost:3000/bizmatch/listings/${listing.id}`, { method: 'PUT', diff --git a/crawler/users.json b/crawler/users.json new file mode 100644 index 0000000..a9574ba --- /dev/null +++ b/crawler/users.json @@ -0,0 +1,156 @@ +[ + { + "id": "8a2b1c5d-7e6f-4g3h-9i1j-2k3l4m5n6o7p", + "firstname": "Sarah", + "lastname": "Thompson", + "email": "sarah.thompson@businessbrokers.com", + "licensedIn": [ + { + "name": "California", + "value": "123456" + }, + { + "name": "Nevada", + "value": "789012" + } + ], + "phoneNumber": "(310) 555-1234", + "companyLocation": "Los Angeles - CA", + "hasCompanyLogo": true, + "hasProfile": true, + "companyName": "Business Brokers Inc.", + "companyWebsite": "https://www.businessbrokers.com", + "description": "Experienced business brokers helping clients buy and sell businesses in California and Nevada.", + "companyOverview": "

Business Brokers Inc. is a team of experienced professionals dedicated to helping clients navigate the complex process of buying and selling businesses. With a strong focus on client satisfaction and a deep understanding of the local markets in California and Nevada, we strive to deliver exceptional results for our clients.

", + "areasServed": [ + "Los Angeles County, CA", + "Orange County, CA", + "San Diego County, CA", + "Clark County, NV", + "Washoe County, NV" + ], + "offeredServices": "

Services Offered

  • Business valuation
  • Market analysis
  • Buyer and seller representation
  • Due diligence assistance
  • Negotiations and closing support
" + }, + { + "id": "4q5r6s7t-8u9v-0w1x-2y3z-4a5b6c7d8e9f", + "firstname": "Michael", + "lastname": "Johnson", + "email": "michael@bizbrokers.net", + "licensedIn": [ + { + "name": "Florida", + "value": "654321" + } + ], + "phoneNumber": "(407) 555-9876", + "companyLocation": "Orlando - FL", + "hasCompanyLogo": false, + "hasProfile": true, + "companyName": "BizBrokers", + "companyWebsite": "https://www.bizbrokers.net", + "description": "Helping business owners in Florida sell their businesses and assisting buyers in finding the perfect opportunity.", + "companyOverview": "

At BizBrokers, we understand the unique challenges of buying and selling businesses in the Florida market. Our team of experienced brokers is committed to providing personalized service and expert guidance to ensure a smooth and successful transaction for our clients.

", + "areasServed": [ + "Orange County, FL", + "Seminole County, FL", + "Osceola County, FL", + "Brevard County, FL", + "Volusia County, FL" + ], + "offeredServices": "

Services Offered

  • Business listings
  • Buyer search and qualification
  • Due diligence support
  • Financing assistance
  • Closing coordination
" + }, + { + "id": "1g2h3i4j-5k6l-7m8n-9o0p-1q2r3s4t5u6v", + "firstname": "Emily", + "lastname": "Davis", + "email": "emily.davis@bizsaleexperts.com", + "licensedIn": [ + { + "name": "New York", + "value": "987654" + }, + { + "name": "New Jersey", + "value": "210987" + } + ], + "phoneNumber": "(212) 555-4321", + "companyLocation": "New York City - NY", + "hasCompanyLogo": true, + "hasProfile": true, + "companyName": "Business Sale Experts", + "companyWebsite": "https://www.bizsaleexperts.com", + "description": "Experienced business brokers specializing in the sale of businesses in the New York and New Jersey area.", + "companyOverview": "

Business Sale Experts is a leading business brokerage firm serving the New York and New Jersey markets. With a team of seasoned professionals and a proven track record of success, we are dedicated to helping our clients achieve their goals in the sale or acquisition of businesses.

", + "areasServed": [ + "New York County, NY", + "Kings County, NY", + "Queens County, NY", + "Bronx County, NY", + "Hudson County, NJ", + "Bergen County, NJ", + "Essex County, NJ" + ], + "offeredServices": "

Services Offered

  • Business valuation
  • Marketing and advertising
  • Buyer screening and qualification
  • Negotiations and deal structuring
  • Due diligence coordination
  • Closing support
" + }, + { + "id": "7w8x9y0z-1a2b-3c4d-5e6f-7g8h9i0j1k2l", + "firstname": "David", + "lastname": "Wilson", + "email": "david.wilson@bizacquisitions.com", + "licensedIn": [ + { + "name": "Texas", + "value": "543210" + } + ], + "phoneNumber": "(713) 555-8765", + "companyLocation": "Houston - TX", + "hasCompanyLogo": true, + "hasProfile": true, + "companyName": "Business Acquisitions Ltd.", + "companyWebsite": "https://www.bizacquisitions.com", + "description": "Helping business owners in Texas sell their businesses and assisting buyers in finding the perfect acquisition opportunity.", + "companyOverview": "

Business Acquisitions Ltd. is a leading business brokerage firm serving the Texas market. Our team of experienced brokers is dedicated to providing expert guidance and personalized service to help our clients achieve their goals in the sale or acquisition of businesses.

", + "areasServed": [ + "Harris County, TX", + "Dallas County, TX", + "Tarrant County, TX", + "Bexar County, TX", + "Travis County, TX" + ], + "offeredServices": "

Services Offered

  • Business valuation
  • Market analysis
  • Buyer search and qualification
  • Due diligence coordination
  • Financing assistance
  • Closing support
" + }, + { + "id": "3m4n5o6p-7q8r-9s0t-1u2v-3w4x5y6z7a8b", + "firstname": "Jessica", + "lastname": "Brown", + "email": "jessica@bizbrokerexperts.com", + "licensedIn": [ + { + "name": "Illinois", + "value": "876543" + }, + { + "name": "Wisconsin", + "value": "109876" + } + ], + "phoneNumber": "(312) 555-2109", + "companyLocation": "Chicago - IL", + "hasCompanyLogo": true, + "hasProfile": true, + "companyName": "Business Broker Experts", + "companyWebsite": "https://www.bizbrokerexperts.com", + "description": "Experienced business brokers helping clients buy and sell businesses in Illinois and Wisconsin.", + "companyOverview": "

Business Broker Experts is a leading business brokerage firm serving the Illinois and Wisconsin markets. With a team of seasoned professionals and a commitment to client satisfaction, we strive to deliver exceptional results for our clients in the sale or acquisition of businesses.

", + "areasServed": [ + "Cook County, IL", + "DuPage County, IL", + "Lake County, IL", + "Milwaukee County, WI", + "Dane County, WI" + ], + "offeredServices": "

Services Offered

  • Business valuation
  • Market analysis
  • Buyer and seller representation
  • Due diligence assistance
  • Negotiations and closing support
" + } + ] \ No newline at end of file