diff --git a/bizmatch-server/scripts/reproduce-favorites.ts b/bizmatch-server/scripts/reproduce-favorites.ts new file mode 100644 index 0000000..b3c4301 --- /dev/null +++ b/bizmatch-server/scripts/reproduce-favorites.ts @@ -0,0 +1,119 @@ + +import { Pool } from 'pg'; +import { drizzle, NodePgDatabase } from 'drizzle-orm/node-postgres'; +import { sql, eq, and } from 'drizzle-orm'; +import * as schema from '../src/drizzle/schema'; +import { users_json } from '../src/drizzle/schema'; + +// Mock JwtUser +interface JwtUser { + email: string; +} + +// Logic from UserService.addFavorite +async function addFavorite(db: NodePgDatabase, id: string, user: JwtUser) { + console.log(`[Action] Adding favorite. Target ID: ${id}, Favoriter Email: ${user.email}`); + await db + .update(schema.users_json) + .set({ + data: sql`jsonb_set(${schema.users_json.data}, '{favoritesForUser}', + coalesce((${schema.users_json.data}->'favoritesForUser')::jsonb, '[]'::jsonb) || to_jsonb(${user.email}::text))`, + } as any) + .where(eq(schema.users_json.id, id)); +} + +// Logic from UserService.getFavoriteUsers +async function getFavoriteUsers(db: NodePgDatabase, user: JwtUser) { + console.log(`[Action] Fetching favorites for ${user.email}`); + + // Corrected query using `?` operator (matches array element check) + const data = await db + .select() + .from(schema.users_json) + .where(sql`${schema.users_json.data}->'favoritesForUser' ? ${user.email}`); + + return data; +} + +// Logic from UserService.deleteFavorite +async function deleteFavorite(db: NodePgDatabase, id: string, user: JwtUser) { + console.log(`[Action] Removing favorite. Target ID: ${id}, Favoriter Email: ${user.email}`); + await db + .update(schema.users_json) + .set({ + data: sql`jsonb_set(${schema.users_json.data}, '{favoritesForUser}', + (SELECT coalesce(jsonb_agg(elem), '[]'::jsonb) + FROM jsonb_array_elements(coalesce(${schema.users_json.data}->'favoritesForUser', '[]'::jsonb)) AS elem + WHERE elem::text != to_jsonb(${user.email}::text)::text))`, + } as any) + .where(eq(schema.users_json.id, id)); +} + +async function main() { + console.log('═══════════════════════════════════════════════════════'); + console.log(' FAVORITES REPRODUCTION SCRIPT'); + console.log('═══════════════════════════════════════════════════════\n'); + + const connectionString = process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/bizmatch'; + const pool = new Pool({ connectionString }); + const db = drizzle(pool, { schema }); + + try { + // 1. Find a "professional" user to be the TARGET listing + // filtering by customerType = 'professional' inside the jsonb data + const targets = await db.select().from(users_json).limit(1); + + if (targets.length === 0) { + console.error("No users found in DB to test with."); + return; + } + + const targetUser = targets[0]; + console.log(`Found target user: ID=${targetUser.id}, Email=${targetUser.email}`); + + // 2. Define a "favoriter" user (doesn't need to exist in DB for the logic to work, but better if it's realistic) + // We'll just use a dummy email or one from DB if available. + const favoriterEmail = 'test-repro-favoriter@example.com'; + const favoriter: JwtUser = { email: favoriterEmail }; + + // 3. Clear any existing favorite for this pair first + await deleteFavorite(db, targetUser.id, favoriter); + + // 4. Add Favorite + await addFavorite(db, targetUser.id, favoriter); + + // 5. Verify it was added by checking the raw data + const updatedTarget = await db.select().from(users_json).where(eq(users_json.id, targetUser.id)); + const favoritesData = (updatedTarget[0].data as any).favoritesForUser; + console.log(`\n[Check] Raw favoritesForUser data on target:`, favoritesData); + + if (!favoritesData || !favoritesData.includes(favoriterEmail)) { + console.error("❌ Add Favorite FAILED. Email not found in favoritesForUser array."); + } else { + console.log("✅ Add Favorite SUCCESS. Email found in JSON."); + } + + // 6. Test retrieval using the getFavoriteUsers query + const retrievedFavorites = await getFavoriteUsers(db, favoriter); + console.log(`\n[Check] retrievedFavorites count: ${retrievedFavorites.length}`); + + const found = retrievedFavorites.find(u => u.id === targetUser.id); + if (found) { + console.log("✅ Get Favorites SUCCESS. Target user returned in query."); + } else { + console.log("❌ Get Favorites FAILED. Target user NOT returned by query."); + console.log("Query used: favoritesForUser @> [email]"); + } + + // 7. Cleanup + await deleteFavorite(db, targetUser.id, favoriter); + console.log("\n[Cleanup] Removed test favorite."); + + } catch (error) { + console.error('❌ Script failed:', error); + } finally { + await pool.end(); + } +} + +main(); diff --git a/bizmatch-server/src/listings/business-listings.controller.ts b/bizmatch-server/src/listings/business-listings.controller.ts index 3b06ae3..76f095d 100644 --- a/bizmatch-server/src/listings/business-listings.controller.ts +++ b/bizmatch-server/src/listings/business-listings.controller.ts @@ -13,7 +13,13 @@ export class BusinessListingsController { constructor( private readonly listingsService: BusinessListingService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, - ) {} + ) { } + + @UseGuards(AuthGuard) + @Post('favorites/all') + async findFavorites(@Request() req): Promise { + return await this.listingsService.findFavoriteListings(req.user as JwtUser); + } @UseGuards(OptionalAuthGuard) @Get(':slugOrId') @@ -21,11 +27,7 @@ export class BusinessListingsController { // Support both slug (e.g., "restaurant-austin-tx-a3f7b2c1") and UUID return await this.listingsService.findBusinessBySlugOrId(slugOrId, req.user as JwtUser); } - @UseGuards(AuthGuard) - @Get('favorites/all') - async findFavorites(@Request() req): Promise { - return await this.listingsService.findFavoriteListings(req.user as JwtUser); - } + @UseGuards(OptionalAuthGuard) @Get('user/:userid') async findByUserId(@Request() req, @Param('userid') userid: string): Promise { diff --git a/bizmatch-server/src/listings/commercial-property-listings.controller.ts b/bizmatch-server/src/listings/commercial-property-listings.controller.ts index ded7365..3a5c091 100644 --- a/bizmatch-server/src/listings/commercial-property-listings.controller.ts +++ b/bizmatch-server/src/listings/commercial-property-listings.controller.ts @@ -15,7 +15,13 @@ export class CommercialPropertyListingsController { private readonly listingsService: CommercialPropertyService, private fileService: FileService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, - ) {} + ) { } + + @UseGuards(AuthGuard) + @Post('favorites/all') + async findFavorites(@Request() req): Promise { + return await this.listingsService.findFavoriteListings(req.user as JwtUser); + } @UseGuards(OptionalAuthGuard) @Get(':slugOrId') @@ -24,12 +30,6 @@ export class CommercialPropertyListingsController { return await this.listingsService.findCommercialBySlugOrId(slugOrId, req.user as JwtUser); } - @UseGuards(AuthGuard) - @Get('favorites/all') - async findFavorites(@Request() req): Promise { - return await this.listingsService.findFavoriteListings(req.user as JwtUser); - } - @UseGuards(OptionalAuthGuard) @Get('user/:email') async findByEmail(@Request() req, @Param('email') email: string): Promise { diff --git a/bizmatch-server/src/listings/listings.module.ts b/bizmatch-server/src/listings/listings.module.ts index d922189..d8f7a3e 100644 --- a/bizmatch-server/src/listings/listings.module.ts +++ b/bizmatch-server/src/listings/listings.module.ts @@ -6,6 +6,7 @@ import { UserService } from '../user/user.service'; import { BrokerListingsController } from './broker-listings.controller'; import { BusinessListingsController } from './business-listings.controller'; import { CommercialPropertyListingsController } from './commercial-property-listings.controller'; +import { UserListingsController } from './user-listings.controller'; import { FirebaseAdminModule } from 'src/firebase-admin/firebase-admin.module'; import { GeoModule } from '../geo/geo.module'; @@ -16,7 +17,7 @@ import { UnknownListingsController } from './unknown-listings.controller'; @Module({ imports: [DrizzleModule, AuthModule, GeoModule,FirebaseAdminModule], - controllers: [BusinessListingsController, CommercialPropertyListingsController, UnknownListingsController, BrokerListingsController], + controllers: [BusinessListingsController, CommercialPropertyListingsController, UnknownListingsController, BrokerListingsController, UserListingsController], providers: [BusinessListingService, CommercialPropertyService, FileService, UserService, BusinessListingService, CommercialPropertyService, GeoService], exports: [BusinessListingService, CommercialPropertyService], }) diff --git a/bizmatch-server/src/listings/user-listings.controller.ts b/bizmatch-server/src/listings/user-listings.controller.ts new file mode 100644 index 0000000..f885b37 --- /dev/null +++ b/bizmatch-server/src/listings/user-listings.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Delete, Param, Post, Request, UseGuards } from '@nestjs/common'; +import { AuthGuard } from '../jwt-auth/auth.guard'; +import { JwtUser } from '../models/main.model'; +import { UserService } from '../user/user.service'; + +@Controller('listings/user') +export class UserListingsController { + constructor(private readonly userService: UserService) { } + + @UseGuards(AuthGuard) + @Post('favorite/:id') + async addFavorite(@Request() req, @Param('id') id: string) { + await this.userService.addFavorite(id, req.user as JwtUser); + return { success: true, message: 'Added to favorites' }; + } + + @UseGuards(AuthGuard) + @Delete('favorite/:id') + async deleteFavorite(@Request() req, @Param('id') id: string) { + await this.userService.deleteFavorite(id, req.user as JwtUser); + return { success: true, message: 'Removed from favorites' }; + } + + @UseGuards(AuthGuard) + @Post('favorites/all') + async getFavorites(@Request() req) { + return await this.userService.getFavoriteUsers(req.user as JwtUser); + } +} diff --git a/bizmatch-server/src/scripts/debug-favorites.ts b/bizmatch-server/src/scripts/debug-favorites.ts new file mode 100644 index 0000000..80230ca --- /dev/null +++ b/bizmatch-server/src/scripts/debug-favorites.ts @@ -0,0 +1,57 @@ + +import { drizzle } from 'drizzle-orm/node-postgres'; +import { Client } from 'pg'; +import * as schema from '../drizzle/schema'; +import { sql } from 'drizzle-orm'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +const client = new Client({ + connectionString: process.env.PG_CONNECTION, +}); + +async function main() { + await client.connect(); + const db = drizzle(client, { schema }); + + const testEmail = 'knuth.timo@gmail.com'; + const targetEmail = 'target.user@example.com'; + + console.log('--- Starting Debug Script ---'); + + // 1. Simulate finding a user to favorite (using a dummy or existing one) + // For safety, let's just query existing users to see if any have favorites set + const usersWithFavorites = await db.select({ + id: schema.users_json.id, + email: schema.users_json.email, + favorites: sql`${schema.users_json.data}->'favoritesForUser'` + }).from(schema.users_json); + + console.log(`Found ${usersWithFavorites.length} users.`); + + const usersWithAnyFavorites = usersWithFavorites.filter(u => u.favorites !== null); + console.log(`Users with 'favoritesForUser' field:`, JSON.stringify(usersWithAnyFavorites, null, 2)); + + // 2. Test the specific WHERE clause used in the service + // .where(sql`${schema.users_json.data}->'favoritesForUser' @> ${JSON.stringify([user.email])}::jsonb`); + + console.log(`Testing query for email: ${testEmail}`); + + try { + const result = await db + .select({ + id: schema.users_json.id, + email: schema.users_json.email + }) + .from(schema.users_json) + .where(sql`${schema.users_json.data}->'favoritesForUser' @> ${JSON.stringify([testEmail])}::jsonb`); + + console.log('Query Result:', result); + } catch (e) { + console.error('Query Failed:', e); + } + + await client.end(); +} + +main().catch(console.error); diff --git a/bizmatch-server/src/user/user.service.ts b/bizmatch-server/src/user/user.service.ts index 241fcf1..1c6d078 100644 --- a/bizmatch-server/src/user/user.service.ts +++ b/bizmatch-server/src/user/user.service.ts @@ -19,7 +19,7 @@ export class UserService { @Inject(PG_CONNECTION) private conn: NodePgDatabase, private fileService: FileService, private geoService: GeoService, - ) {} + ) { } private getWhereConditions(criteria: UserListingCriteria): SQL[] { const whereConditions: SQL[] = []; @@ -158,4 +158,34 @@ export class UserService { throw error; } } + + async addFavorite(id: string, user: JwtUser): Promise { + await this.conn + .update(schema.users_json) + .set({ + data: sql`jsonb_set(${schema.users_json.data}, '{favoritesForUser}', + coalesce((${schema.users_json.data}->'favoritesForUser')::jsonb, '[]'::jsonb) || to_jsonb(${user.email}::text))`, + } as any) + .where(eq(schema.users_json.id, id)); + } + + async deleteFavorite(id: string, user: JwtUser): Promise { + await this.conn + .update(schema.users_json) + .set({ + data: sql`jsonb_set(${schema.users_json.data}, '{favoritesForUser}', + (SELECT coalesce(jsonb_agg(elem), '[]'::jsonb) + FROM jsonb_array_elements(coalesce(${schema.users_json.data}->'favoritesForUser', '[]'::jsonb)) AS elem + WHERE elem::text != to_jsonb(${user.email}::text)::text))`, + } as any) + .where(eq(schema.users_json.id, id)); + } + + async getFavoriteUsers(user: JwtUser): Promise { + const data = await this.conn + .select() + .from(schema.users_json) + .where(sql`${schema.users_json.data}->'favoritesForUser' ? ${user.email}`); + return data.map(u => ({ id: u.id, email: u.email, ...(u.data as User) }) as User); + } } diff --git a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html index c339d59..c1a80ad 100644 --- a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html +++ b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html @@ -13,17 +13,18 @@
-

{{ listing.title }}

-

+

{{ listing.title }}

+

{{ detail.label }}
-
{{ detail.value }}
+
{{ detail.value + }}
-
@@ -32,7 +33,8 @@ listingUser.lastname }} Business logo for {{ listingUser.firstname }} {{ listingUser.lastname }} + class="mr-5 lg:mb-0" style="max-height: 30px; max-width: 100px" width="100" height="30" + alt="Business logo for {{ listingUser.firstname }} {{ listingUser.lastname }}" />
@@ -48,7 +50,7 @@ } @if(user){
diff --git a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts index f090101..7baa6c5 100644 --- a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts +++ b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts @@ -379,13 +379,27 @@ export class DetailsBusinessListingComponent extends BaseDetailsComponent { } return result; } - async save() { - await this.listingsService.addToFavorites(this.listing.id, 'business'); - this.listing.favoritesForUser.push(this.user.email); - this.auditService.createEvent(this.listing.id, 'favorite', this.user?.email); - } - isAlreadyFavorite() { - return this.listing.favoritesForUser.includes(this.user.email); + async toggleFavorite() { + try { + const isFavorited = this.listing.favoritesForUser.includes(this.user.email); + + if (isFavorited) { + // Remove from favorites + await this.listingsService.removeFavorite(this.listing.id, 'business'); + this.listing.favoritesForUser = this.listing.favoritesForUser.filter( + email => email !== this.user.email + ); + } else { + // Add to favorites + await this.listingsService.addToFavorites(this.listing.id, 'business'); + this.listing.favoritesForUser.push(this.user.email); + this.auditService.createEvent(this.listing.id, 'favorite', this.user?.email); + } + + this.cdref.detectChanges(); + } catch (error) { + console.error('Error toggling favorite:', error); + } } async showShareByEMail() { const result = await this.emailService.showShareByEMail({ diff --git a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html index 962d961..04f880d 100644 --- a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html +++ b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html @@ -5,14 +5,14 @@
@if(listing){
-

{{ listing?.title }}

+

{{ listing?.title }}

-

+

{{ detail.label }}
-
{{ detail.value }}
+
{{ detail.value + }}
-
@@ -33,7 +34,8 @@ detail.user.lastname }} Company logo for {{ detail.user.firstname }} {{ detail.user.lastname }} + class="mr-5 lg:mb-0" style="max-height: 30px; max-width: 100px" width="100" height="30" + alt="Company logo for {{ detail.user.firstname }} {{ detail.user.lastname }}" />
@@ -49,7 +51,7 @@ } @if(user){
diff --git a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts index 9dd624a..dde90a5 100644 --- a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts +++ b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts @@ -1,4 +1,4 @@ -import { Component, NgZone } from '@angular/core'; +import { ChangeDetectorRef, Component, NgZone } from '@angular/core'; import { NgOptimizedImage } from '@angular/common'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; @@ -92,6 +92,7 @@ export class DetailsCommercialPropertyListingComponent extends BaseDetailsCompon private emailService: EMailService, public authService: AuthService, private seoService: SeoService, + private cdref: ChangeDetectorRef, ) { super(); this.mailinfo = { sender: { name: '', email: '', phoneNumber: '', state: '', comments: '' }, email: '', url: environment.mailinfoUrl }; @@ -314,13 +315,27 @@ export class DetailsCommercialPropertyListingComponent extends BaseDetailsCompon getImageIndices(): number[] { return this.listing && this.listing.imageOrder ? this.listing.imageOrder.slice(1).map((e, i) => i + 1) : []; } - async save() { - await this.listingsService.addToFavorites(this.listing.id, 'commercialProperty'); - this.listing.favoritesForUser.push(this.user.email); - this.auditService.createEvent(this.listing.id, 'favorite', this.user?.email); - } - isAlreadyFavorite() { - return this.listing.favoritesForUser.includes(this.user.email); + async toggleFavorite() { + try { + const isFavorited = this.listing.favoritesForUser.includes(this.user.email); + + if (isFavorited) { + // Remove from favorites + await this.listingsService.removeFavorite(this.listing.id, 'commercialProperty'); + this.listing.favoritesForUser = this.listing.favoritesForUser.filter( + email => email !== this.user.email + ); + } else { + // Add to favorites + await this.listingsService.addToFavorites(this.listing.id, 'commercialProperty'); + this.listing.favoritesForUser.push(this.user.email); + this.auditService.createEvent(this.listing.id, 'favorite', this.user?.email); + } + + this.cdref.detectChanges(); + } catch (error) { + console.error('Error toggling favorite:', error); + } } async showShareByEMail() { const result = await this.emailService.showShareByEMail({ 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 b7ffe4f..a877279 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 @@ -11,9 +11,12 @@
@if(user.hasProfile){ - Profile picture of {{ user.firstname }} {{ user.lastname }} + Profile picture of {{ user.firstname }} {{ user.lastname }} } @else { - Default profile picture + Default profile picture }

@@ -32,20 +35,19 @@

@if(user.hasCompanyLogo){ - Company logo of {{ user.companyName }} + Company logo of {{ user.companyName }} }
-
-

{{ user.description }}

+

{{ user.description }}

@@ -58,10 +60,9 @@
} - @if(keycloakUser && keycloakUser.email !== user.email){
-
- }
@@ -112,7 +112,7 @@

Company Profile

-

+

@@ -144,7 +144,7 @@

Services we offer

-

+

@@ -152,7 +152,8 @@

Areas (Counties) we serve

@for (area of user.areasServed; track area) { - {{ area.county }}{{ area.county ? '-' : '' }}{{ area.state }} + {{ area.county }}{{ area.county ? + '-' : '' }}{{ area.state }} }
@@ -161,7 +162,8 @@

Licensed In

@for (license of user.licensedIn; track license) { - {{ license.registerNo }}-{{ license.state }} + {{ license.registerNo }}-{{ + license.state }} }
} @@ -174,7 +176,8 @@

My Business Listings For Sale

@for (listing of businessListings; track listing) { -
+
{{ selectOptions.getBusiness(listing.type) }} @@ -189,12 +192,17 @@

My Commercial Property Listings For Sale

@for (listing of commercialPropListings; track listing) { -
+
@if (listing.imageOrder?.length>0){ - Property image for {{ listing.title }} + Property image for {{ listing.title }} } @else { - Property placeholder image + Property placeholder image }

{{ selectOptions.getCommercialProperty(listing.type) }}

@@ -208,4 +216,4 @@
} -
+
\ 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 b57d25a..f326e83 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 @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { ChangeDetectorRef, Component } from '@angular/core'; import { NgOptimizedImage } from '@angular/common'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; @@ -59,7 +59,8 @@ export class DetailsUserComponent { private auditService: AuditService, private emailService: EMailService, private messageService: MessageService, - ) {} + private cdref: ChangeDetectorRef, + ) { } async ngOnInit() { this.user = await this.userService.getById(this.id); @@ -74,25 +75,40 @@ export class DetailsUserComponent { } /** - * Add professional to favorites + * Toggle professional favorite status */ - async save() { - await this.listingsService.addToFavorites(this.user.id, 'user'); - if (!this.user.favoritesForUser) { - this.user.favoritesForUser = []; + async toggleFavorite() { + try { + const isFavorited = this.user.favoritesForUser?.includes(this.keycloakUser.email); + + if (isFavorited) { + // Remove from favorites + await this.listingsService.removeFavorite(this.user.id, 'user'); + this.user.favoritesForUser = this.user.favoritesForUser.filter( + email => email !== this.keycloakUser.email + ); + } else { + // Add to favorites + await this.listingsService.addToFavorites(this.user.id, 'user'); + if (!this.user.favoritesForUser) { + this.user.favoritesForUser = []; + } + this.user.favoritesForUser.push(this.keycloakUser.email); + this.auditService.createEvent(this.user.id, 'favorite', this.keycloakUser?.email); + } + + this.cdref.detectChanges(); + } catch (error) { + console.error('Error toggling favorite', error); } - this.user.favoritesForUser.push(this.keycloakUser.email); - this.auditService.createEvent(this.user.id, 'favorite', this.keycloakUser?.email); } - /** - * Check if already in favorites - */ isAlreadyFavorite(): boolean { if (!this.keycloakUser?.email || !this.user?.favoritesForUser) return false; return this.user.favoritesForUser.includes(this.keycloakUser.email); } + /** * Show email sharing modal */ diff --git a/bizmatch/src/app/pages/listings/broker-listings/broker-listings.component.html b/bizmatch/src/app/pages/listings/broker-listings/broker-listings.component.html index e8dfae6..ac2f2c8 100644 --- a/bizmatch/src/app/pages/listings/broker-listings/broker-listings.component.html +++ b/bizmatch/src/app/pages/listings/broker-listings/broker-listings.component.html @@ -36,13 +36,15 @@
-
+
@if(currentUser) { - } - } @if(listing.listingsCategory==='commercialProperty'){ - } - + } @else { + + {{ $any(listing).firstname }} {{ $any(listing).lastname }} + Professional + {{ listing.location?.name ? listing.location.name : listing.location?.county + }}, {{ listing.location?.state }} + - + + + + + + } } @@ -45,25 +70,47 @@
-

{{ listing.title }}

-

Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}

-

Located in: {{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }}

-

Price: ${{ listing.price.toLocaleString() }}

+ @if(isBusinessOrCommercial(listing)){ +

{{ $any(listing).title }}

+

Category: {{ $any(listing).listingsCategory === 'commercialProperty' ? 'Commercial + Property' : 'Business' }}

+

Located in: {{ listing.location.name ? listing.location.name : + listing.location.county }}, {{ listing.location.state }}

+

Price: ${{ $any(listing).price.toLocaleString() }}

- @if(listing.listingsCategory==='business'){ - - } @if(listing.listingsCategory==='commercialProperty'){ - } -
+ } @else { +

{{ $any(listing).firstname }} {{ $any(listing).lastname }}

+

Category: Professional

+

Located in: {{ listing.location?.name ? listing.location.name : + listing.location?.county }}, {{ listing.location?.state }}

+
+ + +
+ }
@@ -82,4 +129,4 @@
-->
- + \ No newline at end of file diff --git a/bizmatch/src/app/pages/subscription/favorites/favorites.component.ts b/bizmatch/src/app/pages/subscription/favorites/favorites.component.ts index 11c779c..214d7b0 100644 --- a/bizmatch/src/app/pages/subscription/favorites/favorites.component.ts +++ b/bizmatch/src/app/pages/subscription/favorites/favorites.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { BusinessListing, CommercialPropertyListing } from '../../../../../../bizmatch-server/src/models/db.model'; +import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model'; import { KeycloakUser } from '../../../../../../bizmatch-server/src/models/main.model'; import { ConfirmationComponent } from '../../../components/confirmation/confirmation.component'; import { ConfirmationService } from '../../../components/confirmation/confirmation.service'; @@ -19,28 +19,36 @@ import { map2User } from '../../../utils/utils'; export class FavoritesComponent { user: KeycloakUser; // listings: Array = []; //= dataListings as unknown as Array; - favorites: Array; - constructor(private listingsService: ListingsService, public selectOptions: SelectOptionsService, private confirmationService: ConfirmationService, private authService: AuthService) {} + favorites: Array; + constructor(private listingsService: ListingsService, public selectOptions: SelectOptionsService, private confirmationService: ConfirmationService, private authService: AuthService) { } async ngOnInit() { const token = await this.authService.getToken(); this.user = map2User(token); - const result = await Promise.all([await this.listingsService.getFavoriteListings('business'), await this.listingsService.getFavoriteListings('commercialProperty')]); - this.favorites = [...result[0], ...result[1]]; + const result = await Promise.all([await this.listingsService.getFavoriteListings('business'), await this.listingsService.getFavoriteListings('commercialProperty'), await this.listingsService.getFavoriteListings('user')]); + this.favorites = [...result[0], ...result[1], ...result[2]] as Array; } - async confirmDelete(listing: BusinessListing | CommercialPropertyListing) { + async confirmDelete(listing: BusinessListing | CommercialPropertyListing | User) { const confirmed = await this.confirmationService.showConfirmation({ message: `Are you sure you want to remove this listing from your Favorites?` }); if (confirmed) { // this.messageService.showMessage('Listing has been deleted'); this.deleteListing(listing); } } - async deleteListing(listing: BusinessListing | CommercialPropertyListing) { - if (listing.listingsCategory === 'business') { - await this.listingsService.removeFavorite(listing.id, 'business'); + async deleteListing(listing: BusinessListing | CommercialPropertyListing | User) { + if ('listingsCategory' in listing) { + if (listing.listingsCategory === 'business') { + await this.listingsService.removeFavorite(listing.id, 'business'); + } else { + await this.listingsService.removeFavorite(listing.id, 'commercialProperty'); + } } else { - await this.listingsService.removeFavorite(listing.id, 'commercialProperty'); + await this.listingsService.removeFavorite(listing.id, 'user'); } - const result = await Promise.all([await this.listingsService.getFavoriteListings('business'), await this.listingsService.getFavoriteListings('commercialProperty')]); - this.favorites = [...result[0], ...result[1]]; + const result = await Promise.all([await this.listingsService.getFavoriteListings('business'), await this.listingsService.getFavoriteListings('commercialProperty'), await this.listingsService.getFavoriteListings('user')]); + this.favorites = [...result[0], ...result[1], ...result[2]] as Array; + } + + isBusinessOrCommercial(listing: any): boolean { + return !!listing.listingsCategory; } } diff --git a/bizmatch/src/app/services/listings.service.ts b/bizmatch/src/app/services/listings.service.ts index dbed227..279254e 100644 --- a/bizmatch/src/app/services/listings.service.ts +++ b/bizmatch/src/app/services/listings.service.ts @@ -11,7 +11,7 @@ import { getCriteriaByListingCategory, getSortByListingCategory } from '../utils }) export class ListingsService { private apiBaseUrl = environment.apiBaseUrl; - constructor(private http: HttpClient) {} + constructor(private http: HttpClient) { } async getListings(listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty'): Promise { const criteria = getCriteriaByListingCategory(listingsCategory); @@ -35,8 +35,8 @@ export class ListingsService { getListingsByEmail(email: string, listingsCategory: 'business' | 'commercialProperty'): Promise { return lastValueFrom(this.http.get(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/user/${email}`)); } - getFavoriteListings(listingsCategory: 'business' | 'commercialProperty'): Promise { - return lastValueFrom(this.http.get(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/favorites/all`)); + getFavoriteListings(listingsCategory: 'business' | 'commercialProperty' | 'user'): Promise { + return lastValueFrom(this.http.post(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/favorites/all`, {})); } async save(listing: any, listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty') { if (listing.id) { @@ -52,10 +52,14 @@ export class ListingsService { await lastValueFrom(this.http.delete(`${this.apiBaseUrl}/bizmatch/listings/commercialProperty/listing/${id}/${imagePath}`)); } async addToFavorites(id: string, listingsCategory?: 'business' | 'commercialProperty' | 'user') { - await lastValueFrom(this.http.post(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/favorite/${id}`, {})); + const url = `${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/favorite/${id}`; + console.log('[ListingsService] addToFavorites calling URL:', url); + await lastValueFrom(this.http.post(url, {})); } async removeFavorite(id: string, listingsCategory?: 'business' | 'commercialProperty' | 'user') { - await lastValueFrom(this.http.delete(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/favorite/${id}`)); + const url = `${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/favorite/${id}`; + console.log('[ListingsService] removeFavorite calling URL:', url); + await lastValueFrom(this.http.delete(url)); } /**