Issues gitea

This commit is contained in:
2026-01-15 21:26:07 +01:00
parent 897ab1ff77
commit 09e7ce59a9
18 changed files with 496 additions and 124 deletions

View File

@@ -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>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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
*/