Issues gitea
This commit is contained in:
@@ -13,17 +13,18 @@
|
||||
<div class="p-6 flex flex-col lg:flex-row">
|
||||
<!-- Left column -->
|
||||
<div class="w-full lg:w-1/2 pr-0 lg:pr-6">
|
||||
<h1 class="text-2xl font-bold mb-4">{{ listing.title }}</h1>
|
||||
<p class="mb-4" [innerHTML]="description"></p>
|
||||
<h1 class="text-2xl font-bold mb-4 break-words">{{ listing.title }}</h1>
|
||||
<p class="mb-4 break-words" [innerHTML]="description"></p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div *ngFor="let detail of listingDetails; let i = index" class="flex flex-col sm:flex-row"
|
||||
[ngClass]="{ 'bg-neutral-100': i % 2 === 0 }">
|
||||
<div class="w-full sm:w-1/3 font-semibold p-2">{{ detail.label }}</div>
|
||||
|
||||
<div class="w-full sm:w-2/3 p-2" *ngIf="!detail.isHtml && !detail.isListingBy">{{ detail.value }}</div>
|
||||
<div class="w-full sm:w-2/3 p-2 break-words" *ngIf="!detail.isHtml && !detail.isListingBy">{{ detail.value
|
||||
}}</div>
|
||||
|
||||
<div class="w-full sm:w-2/3 p-2 flex space-x-2" [innerHTML]="detail.value"
|
||||
<div class="w-full sm:w-2/3 p-2 flex space-x-2 break-words" [innerHTML]="detail.value"
|
||||
*ngIf="detail.isHtml && !detail.isListingBy"></div>
|
||||
|
||||
<div class="w-full sm:w-2/3 p-2 flex space-x-2" *ngIf="detail.isListingBy && listingUser">
|
||||
@@ -32,7 +33,8 @@
|
||||
listingUser.lastname }}</a>
|
||||
<img *ngIf="listing.imageName"
|
||||
ngSrc="{{ env.imageBaseUrl }}/pictures/logo/{{ listing.imageName }}.avif?_ts={{ ts }}"
|
||||
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 }}" />
|
||||
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 }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +50,7 @@
|
||||
} @if(user){
|
||||
<div class="inline">
|
||||
<button class="share share-save text-white font-bold text-xs py-1.5 px-2 inline-flex items-center"
|
||||
(click)="save()" [disabled]="listing.favoritesForUser.includes(user.email)">
|
||||
(click)="toggleFavorite()">
|
||||
<i class="fa-regular fa-heart"></i>
|
||||
@if(listing.favoritesForUser.includes(user.email)){
|
||||
<span class="ml-2">Saved ...</span>
|
||||
@@ -142,10 +144,13 @@
|
||||
<h2 class="text-2xl font-bold mb-6 text-gray-900">Frequently Asked Questions</h2>
|
||||
<div class="space-y-4">
|
||||
@for (faq of businessFAQs; track $index) {
|
||||
<details class="group border border-gray-200 rounded-lg overflow-hidden hover:border-primary-300 transition-colors">
|
||||
<summary class="flex items-center justify-between cursor-pointer p-4 bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<details
|
||||
class="group border border-gray-200 rounded-lg overflow-hidden hover:border-primary-300 transition-colors">
|
||||
<summary
|
||||
class="flex items-center justify-between cursor-pointer p-4 bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ faq.question }}</h3>
|
||||
<svg class="w-5 h-5 text-gray-600 group-open:rotate-180 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-gray-600 group-open:rotate-180 transition-transform" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</summary>
|
||||
@@ -157,7 +162,8 @@
|
||||
</div>
|
||||
<div class="mt-6 p-4 bg-primary-50 border-l-4 border-primary-500 rounded">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong class="text-primary-700">Have more questions?</strong> Contact the seller directly using the form above or reach out to our support team for assistance.
|
||||
<strong class="text-primary-700">Have more questions?</strong> Contact the seller directly using the form
|
||||
above or reach out to our support team for assistance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<div class="bg-white drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg rounded-lg overflow-hidden">
|
||||
@if(listing){
|
||||
<div class="p-6 relative">
|
||||
<h1 class="text-3xl font-bold mb-4">{{ listing?.title }}</h1>
|
||||
<h1 class="text-3xl font-bold mb-4 break-words">{{ listing?.title }}</h1>
|
||||
<button (click)="historyService.goBack()"
|
||||
class="print:hidden absolute top-4 right-4 bg-red-500 text-white rounded-full w-8 h-8 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<div class="w-full lg:w-1/2 pr-0 lg:pr-4">
|
||||
<p class="mb-4" [innerHTML]="description"></p>
|
||||
<p class="mb-4 break-words" [innerHTML]="description"></p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div *ngFor="let detail of propertyDetails; let i = index" class="flex flex-col sm:flex-row"
|
||||
@@ -20,10 +20,11 @@
|
||||
<div class="w-full sm:w-1/3 font-semibold p-2">{{ detail.label }}</div>
|
||||
|
||||
<!-- Standard Text -->
|
||||
<div class="w-full sm:w-2/3 p-2" *ngIf="!detail.isHtml && !detail.isListingBy">{{ detail.value }}</div>
|
||||
<div class="w-full sm:w-2/3 p-2 break-words" *ngIf="!detail.isHtml && !detail.isListingBy">{{ detail.value
|
||||
}}</div>
|
||||
|
||||
<!-- HTML Content (nicht für RouterLink) -->
|
||||
<div class="w-full sm:w-2/3 p-2 flex space-x-2" [innerHTML]="detail.value"
|
||||
<div class="w-full sm:w-2/3 p-2 flex space-x-2 break-words" [innerHTML]="detail.value"
|
||||
*ngIf="detail.isHtml && !detail.isListingBy"></div>
|
||||
|
||||
<!-- Speziell für Listing By mit RouterLink -->
|
||||
@@ -33,7 +34,8 @@
|
||||
detail.user.lastname }} </a>
|
||||
<img *ngIf="detail.user.hasCompanyLogo"
|
||||
[ngSrc]="detail.imageBaseUrl + '/pictures/logo/' + detail.imagePath + '.avif?_ts=' + detail.ts"
|
||||
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 }}" />
|
||||
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 }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,7 +51,7 @@
|
||||
} @if(user){
|
||||
<div class="inline">
|
||||
<button class="share share-save text-white font-bold text-xs py-1.5 px-2 inline-flex items-center"
|
||||
(click)="save()" [disabled]="listing.favoritesForUser.includes(user.email)">
|
||||
(click)="toggleFavorite()">
|
||||
<i class="fa-regular fa-heart"></i>
|
||||
@if(listing.favoritesForUser.includes(user.email)){
|
||||
<span class="ml-2">Saved ...</span>
|
||||
@@ -156,10 +158,13 @@
|
||||
<h2 class="text-2xl font-bold mb-6 text-gray-900">Frequently Asked Questions</h2>
|
||||
<div class="space-y-4">
|
||||
@for (faq of propertyFAQs; track $index) {
|
||||
<details class="group border border-gray-200 rounded-lg overflow-hidden hover:border-primary-300 transition-colors">
|
||||
<summary class="flex items-center justify-between cursor-pointer p-4 bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<details
|
||||
class="group border border-gray-200 rounded-lg overflow-hidden hover:border-primary-300 transition-colors">
|
||||
<summary
|
||||
class="flex items-center justify-between cursor-pointer p-4 bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ faq.question }}</h3>
|
||||
<svg class="w-5 h-5 text-gray-600 group-open:rotate-180 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-gray-600 group-open:rotate-180 transition-transform" fill="none"
|
||||
stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</summary>
|
||||
@@ -171,7 +176,8 @@
|
||||
</div>
|
||||
<div class="mt-6 p-4 bg-primary-50 border-l-4 border-primary-500 rounded">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong class="text-primary-700">Have more questions?</strong> Contact the seller directly using the form above or reach out to our support team for assistance.
|
||||
<strong class="text-primary-700">Have more questions?</strong> Contact the seller directly using the form
|
||||
above or reach out to our support team for assistance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -11,9 +11,12 @@
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- <img src="https://placehold.co/80x80" alt="Profile picture of Avery Brown smiling" class="w-20 h-20 rounded-full" /> -->
|
||||
@if(user.hasProfile){
|
||||
<img ngSrc="{{ env.imageBaseUrl }}/pictures//profile/{{ emailToDirName(user.email) }}.avif?_ts={{ ts }}" class="w-20 h-20 rounded-full object-cover" width="80" height="80" priority alt="Profile picture of {{ user.firstname }} {{ user.lastname }}" />
|
||||
<img ngSrc="{{ env.imageBaseUrl }}/pictures//profile/{{ emailToDirName(user.email) }}.avif?_ts={{ ts }}"
|
||||
class="w-20 h-20 rounded-full object-cover" width="80" height="80" priority
|
||||
alt="Profile picture of {{ user.firstname }} {{ user.lastname }}" />
|
||||
} @else {
|
||||
<img ngSrc="assets/images/person_placeholder.jpg" class="w-20 h-20 rounded-full" width="80" height="80" priority alt="Default profile picture" />
|
||||
<img ngSrc="assets/images/person_placeholder.jpg" class="w-20 h-20 rounded-full" width="80" height="80" priority
|
||||
alt="Default profile picture" />
|
||||
}
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold flex items-center">
|
||||
@@ -32,20 +35,19 @@
|
||||
</p>
|
||||
</div>
|
||||
@if(user.hasCompanyLogo){
|
||||
<img ngSrc="{{ env.imageBaseUrl }}/pictures/logo/{{ emailToDirName(user.email) }}.avif?_ts={{ ts }}" class="w-11 h-14" width="44" height="56" alt="Company logo of {{ user.companyName }}" />
|
||||
<img ngSrc="{{ env.imageBaseUrl }}/pictures/logo/{{ emailToDirName(user.email) }}.avif?_ts={{ ts }}"
|
||||
class="w-14 h-14 object-contain" width="56" height="56" alt="Company logo of {{ user.companyName }}" />
|
||||
}
|
||||
<!-- <img src="https://placehold.co/45x60" class="w-11 h-14" /> -->
|
||||
</div>
|
||||
<button
|
||||
(click)="historyService.goBack()"
|
||||
class="absolute top-4 right-4 bg-red-500 text-white rounded-full w-8 h-8 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50"
|
||||
>
|
||||
<button (click)="historyService.goBack()"
|
||||
class="absolute top-4 right-4 bg-red-500 text-white rounded-full w-8 h-8 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="p-4 text-neutral-700">{{ user.description }}</p>
|
||||
<p class="p-4 text-neutral-700 break-words">{{ user.description }}</p>
|
||||
|
||||
<!-- Like and Share Action Buttons -->
|
||||
<div class="py-4 px-4 print:hidden">
|
||||
@@ -58,10 +60,9 @@
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@if(keycloakUser && keycloakUser.email !== user.email){
|
||||
<div class="inline">
|
||||
<button class="share share-save text-white font-bold text-xs py-1.5 px-2 inline-flex items-center"
|
||||
(click)="save()" [disabled]="isAlreadyFavorite()">
|
||||
<button type="button" class="share share-save text-white font-bold text-xs py-1.5 px-2 inline-flex items-center"
|
||||
(click)="toggleFavorite()">
|
||||
<i class="fa-regular fa-heart"></i>
|
||||
@if(isAlreadyFavorite()){
|
||||
<span class="ml-2">Saved ...</span>
|
||||
@@ -70,7 +71,6 @@
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<share-button button="print" showText="true" (click)="createEvent('print')"></share-button>
|
||||
|
||||
<div class="inline">
|
||||
@@ -112,7 +112,7 @@
|
||||
<!-- Company Profile -->
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-semibold mb-4">Company Profile</h2>
|
||||
<p class="text-neutral-700 mb-4" [innerHTML]="companyOverview"></p>
|
||||
<p class="text-neutral-700 mb-4 break-words" [innerHTML]="companyOverview"></p>
|
||||
|
||||
<!-- Profile Details -->
|
||||
<div class="space-y-2">
|
||||
@@ -144,7 +144,7 @@
|
||||
<!-- Services -->
|
||||
<div class="mt-6">
|
||||
<h3 class="font-semibold mb-2">Services we offer</h3>
|
||||
<p class="text-neutral-700 mb-4" [innerHTML]="offeredServices"></p>
|
||||
<p class="text-neutral-700 mb-4 break-words" [innerHTML]="offeredServices"></p>
|
||||
</div>
|
||||
|
||||
<!-- Areas Served -->
|
||||
@@ -152,7 +152,8 @@
|
||||
<h3 class="font-semibold mb-2">Areas (Counties) we serve</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@for (area of user.areasServed; track area) {
|
||||
<span class="bg-primary-100 text-primary-800 px-2 py-1 rounded-full text-sm">{{ area.county }}{{ area.county ? '-' : '' }}{{ area.state }}</span>
|
||||
<span class="bg-primary-100 text-primary-800 px-2 py-1 rounded-full text-sm">{{ area.county }}{{ area.county ?
|
||||
'-' : '' }}{{ area.state }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,7 +162,8 @@
|
||||
<div class="mt-6">
|
||||
<h3 class="font-semibold mb-2">Licensed In</h3>
|
||||
@for (license of user.licensedIn; track license) {
|
||||
<span class="bg-success-100 text-success-800 px-2 py-1 rounded-full text-sm">{{ license.registerNo }}-{{ license.state }}</span>
|
||||
<span class="bg-success-100 text-success-800 px-2 py-1 rounded-full text-sm">{{ license.registerNo }}-{{
|
||||
license.state }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -174,7 +176,8 @@
|
||||
<h2 class="text-xl font-semibold mb-4">My Business Listings For Sale</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@for (listing of businessListings; track listing) {
|
||||
<div class="border rounded-lg p-4 hover:cursor-pointer" [routerLink]="['/business', listing.slug || listing.id]">
|
||||
<div class="border rounded-lg p-4 hover:cursor-pointer"
|
||||
[routerLink]="['/business', listing.slug || listing.id]">
|
||||
<div class="flex items-center mb-2">
|
||||
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="mr-2"></i>
|
||||
<span class="font-medium">{{ selectOptions.getBusiness(listing.type) }}</span>
|
||||
@@ -189,12 +192,17 @@
|
||||
<h2 class="text-xl font-semibold mb-4">My Commercial Property Listings For Sale</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@for (listing of commercialPropListings; track listing) {
|
||||
<div class="border rounded-lg p-4 hover:cursor-pointer" [routerLink]="['/commercial-property', listing.slug || listing.id]">
|
||||
<div class="border rounded-lg p-4 hover:cursor-pointer"
|
||||
[routerLink]="['/commercial-property', listing.slug || listing.id]">
|
||||
<div class="flex items-center space-x-4">
|
||||
@if (listing.imageOrder?.length>0){
|
||||
<img ngSrc="{{ env.imageBaseUrl }}/pictures/property/{{ listing.imagePath }}/{{ listing.serialId }}/{{ listing.imageOrder[0] }}?_ts={{ ts }}" class="w-12 h-12 object-cover rounded" width="48" height="48" alt="Property image for {{ listing.title }}" />
|
||||
<img
|
||||
ngSrc="{{ env.imageBaseUrl }}/pictures/property/{{ listing.imagePath }}/{{ listing.serialId }}/{{ listing.imageOrder[0] }}?_ts={{ ts }}"
|
||||
class="w-12 h-12 object-cover rounded" width="48" height="48"
|
||||
alt="Property image for {{ listing.title }}" />
|
||||
} @else {
|
||||
<img ngSrc="assets/images/placeholder_properties.jpg" class="w-12 h-12 object-cover rounded" width="48" height="48" alt="Property placeholder image" />
|
||||
<img ngSrc="assets/images/placeholder_properties.jpg" class="w-12 h-12 object-cover rounded" width="48"
|
||||
height="48" alt="Property placeholder image" />
|
||||
}
|
||||
<div>
|
||||
<p class="font-medium">{{ selectOptions.getCommercialProperty(listing.type) }}</p>
|
||||
@@ -208,4 +216,4 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -36,13 +36,15 @@
|
||||
<div
|
||||
class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg p-6 flex flex-col justify-between hover:shadow-2xl transition-all duration-300 hover:scale-[1.02] group relative">
|
||||
<!-- Quick Actions Overlay -->
|
||||
<div class="absolute top-4 right-4 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-20">
|
||||
<div
|
||||
class="absolute top-4 right-4 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-20">
|
||||
@if(currentUser) {
|
||||
<button class="bg-white rounded-full p-2 shadow-lg transition-colors"
|
||||
<button type="button" class="bg-white rounded-full p-2 shadow-lg transition-colors"
|
||||
[class.bg-red-50]="isFavorite(user)"
|
||||
[title]="isFavorite(user) ? 'Remove from favorites' : 'Save to favorites'"
|
||||
(click)="toggleFavorite($event, user)">
|
||||
<i [class]="isFavorite(user) ? 'fas fa-heart text-red-500' : 'far fa-heart text-red-500 hover:scale-110 transition-transform'"></i>
|
||||
<i
|
||||
[class]="isFavorite(user) ? 'fas fa-heart text-red-500' : 'far fa-heart text-red-500 hover:scale-110 transition-transform'"></i>
|
||||
</button>
|
||||
}
|
||||
<button type="button" class="bg-white rounded-full p-2 shadow-lg hover:bg-blue-50 transition-colors"
|
||||
@@ -134,7 +136,8 @@
|
||||
<h2 class="text-center text-black text-xl font-semibold leading-loose pb-2">There're no professionals here
|
||||
</h2>
|
||||
<p class="text-center text-black text-base font-normal leading-relaxed pb-4">Try changing your filters to
|
||||
<br />see professionals</p>
|
||||
<br />see professionals
|
||||
</p>
|
||||
<div class="flex gap-3">
|
||||
<button (click)="clearAllFilters()"
|
||||
class="w-full px-3 py-2 rounded-full border border-neutral-300 text-neutral-900 text-xs font-semibold leading-4">Clear
|
||||
|
||||
@@ -174,6 +174,9 @@ export class BrokerListingsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Toggling favorite for:', professional.email, 'Current user:', this.currentUser.email);
|
||||
console.log('Before update, favorites:', professional.favoritesForUser);
|
||||
|
||||
if (this.isFavorite(professional)) {
|
||||
// Remove from favorites
|
||||
await this.listingsService.removeFavorite(professional.id, 'user');
|
||||
@@ -186,8 +189,12 @@ export class BrokerListingsComponent implements OnInit, OnDestroy {
|
||||
if (!professional.favoritesForUser) {
|
||||
professional.favoritesForUser = [];
|
||||
}
|
||||
professional.favoritesForUser.push(this.currentUser.email);
|
||||
// Use spread to create new array reference
|
||||
professional.favoritesForUser = [...professional.favoritesForUser, this.currentUser.email];
|
||||
}
|
||||
|
||||
console.log('After update, favorites:', professional.favoritesForUser);
|
||||
this.cdRef.markForCheck();
|
||||
this.cdRef.detectChanges();
|
||||
} catch (error) {
|
||||
console.error('Error toggling favorite:', error);
|
||||
|
||||
@@ -16,27 +16,52 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@for(listing of favorites; track listing){
|
||||
@if(isBusinessOrCommercial(listing)){
|
||||
<tr class="border-b">
|
||||
<td class="py-2 px-4">{{ listing.title }}</td>
|
||||
<td class="py-2 px-4">{{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</td>
|
||||
<td class="py-2 px-4">{{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }}</td>
|
||||
<td class="py-2 px-4">${{ listing.price.toLocaleString() }}</td>
|
||||
<td class="py-2 px-4">{{ $any(listing).title }}</td>
|
||||
<td class="py-2 px-4">{{ $any(listing).listingsCategory === 'commercialProperty' ? 'Commercial Property' :
|
||||
'Business' }}</td>
|
||||
<td class="py-2 px-4">{{ listing.location.name ? listing.location.name : listing.location.county }}, {{
|
||||
listing.location.state }}</td>
|
||||
<td class="py-2 px-4">${{ $any(listing).price.toLocaleString() }}</td>
|
||||
<td class="py-2 px-4 flex">
|
||||
@if(listing.listingsCategory==='business'){
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2" [routerLink]="['/business', listing.slug || listing.id]">
|
||||
@if($any(listing).listingsCategory==='business'){
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
[routerLink]="['/business', $any(listing).slug || listing.id]">
|
||||
<i class="fa-regular fa-eye"></i>
|
||||
</button>
|
||||
|
||||
} @if(listing.listingsCategory==='commercialProperty'){
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2" [routerLink]="['/commercial-property', listing.slug || listing.id]">
|
||||
} @if($any(listing).listingsCategory==='commercialProperty'){
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
[routerLink]="['/commercial-property', $any(listing).slug || listing.id]">
|
||||
<i class="fa-regular fa-eye"></i>
|
||||
</button>
|
||||
}
|
||||
<button class="bg-red-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2" (click)="confirmDelete(listing)">
|
||||
<button class="bg-red-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
(click)="confirmDelete(listing)">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
<tr class="border-b">
|
||||
<td class="py-2 px-4">{{ $any(listing).firstname }} {{ $any(listing).lastname }}</td>
|
||||
<td class="py-2 px-4">Professional</td>
|
||||
<td class="py-2 px-4">{{ listing.location?.name ? listing.location.name : listing.location?.county
|
||||
}}, {{ listing.location?.state }}</td>
|
||||
<td class="py-2 px-4">-</td>
|
||||
<td class="py-2 px-4 flex">
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
[routerLink]="['/details-user', listing.id]">
|
||||
<i class="fa-regular fa-eye"></i>
|
||||
</button>
|
||||
<button class="bg-red-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
(click)="confirmDelete(listing)">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -45,25 +70,47 @@
|
||||
<!-- Mobile view -->
|
||||
<div class="md:hidden">
|
||||
<div *ngFor="let listing of favorites" class="bg-white drop-shadow-inner-faint rounded-lg p-4 mb-4">
|
||||
<h2 class="text-xl font-semibold mb-2">{{ listing.title }}</h2>
|
||||
<p class="text-gray-600 mb-2">Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</p>
|
||||
<p class="text-gray-600 mb-2">Located in: {{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }}</p>
|
||||
<p class="text-gray-600 mb-2">Price: ${{ listing.price.toLocaleString() }}</p>
|
||||
@if(isBusinessOrCommercial(listing)){
|
||||
<h2 class="text-xl font-semibold mb-2">{{ $any(listing).title }}</h2>
|
||||
<p class="text-gray-600 mb-2">Category: {{ $any(listing).listingsCategory === 'commercialProperty' ? 'Commercial
|
||||
Property' : 'Business' }}</p>
|
||||
<p class="text-gray-600 mb-2">Located in: {{ listing.location.name ? listing.location.name :
|
||||
listing.location.county }}, {{ listing.location.state }}</p>
|
||||
<p class="text-gray-600 mb-2">Price: ${{ $any(listing).price.toLocaleString() }}</p>
|
||||
<div class="flex justify-start">
|
||||
@if(listing.listingsCategory==='business'){
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2" [routerLink]="['/business', listing.slug || listing.id]">
|
||||
@if($any(listing).listingsCategory==='business'){
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
[routerLink]="['/business', $any(listing).slug || listing.id]">
|
||||
<i class="fa-regular fa-eye"></i>
|
||||
</button>
|
||||
|
||||
} @if(listing.listingsCategory==='commercialProperty'){
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2" [routerLink]="['/commercial-property', listing.slug || listing.id]">
|
||||
} @if($any(listing).listingsCategory==='commercialProperty'){
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
[routerLink]="['/commercial-property', $any(listing).slug || listing.id]">
|
||||
<i class="fa-regular fa-eye"></i>
|
||||
</button>
|
||||
}
|
||||
<button class="bg-red-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2" (click)="confirmDelete(listing)">
|
||||
<button class="bg-red-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
(click)="confirmDelete(listing)">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<h2 class="text-xl font-semibold mb-2">{{ $any(listing).firstname }} {{ $any(listing).lastname }}</h2>
|
||||
<p class="text-gray-600 mb-2">Category: Professional</p>
|
||||
<p class="text-gray-600 mb-2">Located in: {{ listing.location?.name ? listing.location.name :
|
||||
listing.location?.county }}, {{ listing.location?.state }}</p>
|
||||
<div class="flex justify-start">
|
||||
<button class="bg-green-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
[routerLink]="['/details-user', listing.id]">
|
||||
<i class="fa-regular fa-eye"></i>
|
||||
</button>
|
||||
<button class="bg-red-500 text-white w-10 h-10 flex items-center justify-center rounded-full mr-2"
|
||||
(click)="confirmDelete(listing)">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -82,4 +129,4 @@
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<app-confirmation></app-confirmation>
|
||||
<app-confirmation></app-confirmation>
|
||||
@@ -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<ListingType> = []; //= dataListings as unknown as Array<BusinessListing>;
|
||||
favorites: Array<BusinessListing | CommercialPropertyListing>;
|
||||
constructor(private listingsService: ListingsService, public selectOptions: SelectOptionsService, private confirmationService: ConfirmationService, private authService: AuthService) {}
|
||||
favorites: Array<BusinessListing | CommercialPropertyListing | User>;
|
||||
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<BusinessListing | CommercialPropertyListing | User>;
|
||||
}
|
||||
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<BusinessListing | CommercialPropertyListing | User>;
|
||||
}
|
||||
|
||||
isBusinessOrCommercial(listing: any): boolean {
|
||||
return !!listing.listingsCategory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ResponseBusinessListingArray | ResponseCommercialPropertyListingArray> {
|
||||
const criteria = getCriteriaByListingCategory(listingsCategory);
|
||||
@@ -35,8 +35,8 @@ export class ListingsService {
|
||||
getListingsByEmail(email: string, listingsCategory: 'business' | 'commercialProperty'): Promise<ListingType[]> {
|
||||
return lastValueFrom(this.http.get<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/user/${email}`));
|
||||
}
|
||||
getFavoriteListings(listingsCategory: 'business' | 'commercialProperty'): Promise<ListingType[]> {
|
||||
return lastValueFrom(this.http.get<BusinessListing[] | CommercialPropertyListing[]>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/favorites/all`));
|
||||
getFavoriteListings(listingsCategory: 'business' | 'commercialProperty' | 'user'): Promise<ListingType[]> {
|
||||
return lastValueFrom(this.http.post<BusinessListing[] | CommercialPropertyListing[] | any[]>(`${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<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/commercialProperty/listing/${id}/${imagePath}`));
|
||||
}
|
||||
async addToFavorites(id: string, listingsCategory?: 'business' | 'commercialProperty' | 'user') {
|
||||
await lastValueFrom(this.http.post<void>(`${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<void>(url, {}));
|
||||
}
|
||||
async removeFavorite(id: string, listingsCategory?: 'business' | 'commercialProperty' | 'user') {
|
||||
await lastValueFrom(this.http.delete<ListingType>(`${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<ListingType>(url));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user