SEO/AEO, Farb schema, breadcrumbs

This commit is contained in:
2025-11-29 23:41:54 +01:00
parent 4fa24c8f3d
commit d2953fd0d9
87 changed files with 5672 additions and 579 deletions

View File

@@ -0,0 +1,68 @@
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { RouterModule } from '@angular/router';
export interface BreadcrumbItem {
label: string;
url?: string;
icon?: string;
}
@Component({
selector: 'app-breadcrumbs',
standalone: true,
imports: [CommonModule, RouterModule],
template: `
<nav aria-label="Breadcrumb" class="mb-4">
<ol
class="flex flex-wrap items-center text-sm text-neutral-600"
itemscope
itemtype="https://schema.org/BreadcrumbList"
>
@for (item of breadcrumbs; track $index) {
<li
class="inline-flex items-center"
itemprop="itemListElement"
itemscope
itemtype="https://schema.org/ListItem"
>
@if ($index > 0) {
<span class="inline-flex items-center mx-2 text-neutral-400 select-none">
<i class="fas fa-chevron-right text-xs"></i>
</span>
}
@if (item.url && $index < breadcrumbs.length - 1) {
<a
[routerLink]="item.url"
class="inline-flex items-center hover:text-blue-600 transition-colors"
itemprop="item"
>
@if (item.icon) {
<i [class]="item.icon + ' mr-1'"></i>
}
<span itemprop="name">{{ item.label }}</span>
</a>
} @else {
<span
class="inline-flex items-center font-semibold text-neutral-900"
itemprop="item"
>
@if (item.icon) {
<i [class]="item.icon + ' mr-1'"></i>
}
<span itemprop="name">{{ item.label }}</span>
</span>
}
<meta itemprop="position" [content]="($index + 1).toString()" />
</li>
}
</ol>
</nav>
`,
styles: []
})
export class BreadcrumbsComponent {
@Input() breadcrumbs: BreadcrumbItem[] = [];
}

View File

@@ -17,7 +17,15 @@ import { EMailService } from './email.service';
template: ``,
})
export class EMailComponent {
shareByEMail: ShareByEMail = {};
shareByEMail: ShareByEMail = {
yourName: '',
recipientEmail: '',
yourEmail: '',
type: 'business',
listingTitle: '',
url: '',
id: ''
};
constructor(public eMailService: EMailService, private mailService: MailService, private validationMessagesService: ValidationMessagesService) {}
ngOnInit() {
this.eMailService.shareByEMail$.pipe(untilDestroyed(this)).subscribe(val => {

View File

@@ -0,0 +1,93 @@
import { CommonModule } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core';
import { SeoService } from '../../services/seo.service';
export interface FAQItem {
question: string;
answer: string;
}
@Component({
selector: 'app-faq',
standalone: true,
imports: [CommonModule],
template: `
<section class="bg-white rounded-lg shadow-lg p-6 md:p-8 my-8">
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 mb-6">Frequently Asked Questions</h2>
<div class="space-y-4">
@for (item of faqItems; track $index) {
<div class="border-b border-gray-200 pb-4">
<button
(click)="toggle($index)"
class="w-full text-left flex justify-between items-center py-2 hover:text-blue-600 transition-colors"
[attr.aria-expanded]="openIndex === $index"
>
<h3 class="text-lg font-semibold text-gray-800">{{ item.question }}</h3>
<svg
class="w-5 h-5 transition-transform"
[class.rotate-180]="openIndex === $index"
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" />
</svg>
</button>
@if (openIndex === $index) {
<div class="mt-3 text-gray-600 leading-relaxed">
<p [innerHTML]="item.answer"></p>
</div>
}
</div>
}
</div>
</section>
`,
styles: [`
.rotate-180 {
transform: rotate(180deg);
}
`]
})
export class FaqComponent implements OnInit {
@Input() faqItems: FAQItem[] = [];
openIndex: number | null = null;
constructor(private seoService: SeoService) {}
ngOnInit() {
// Generate and inject FAQ Schema for rich snippets
if (this.faqItems.length > 0) {
const faqSchema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
'mainEntity': this.faqItems.map(item => ({
'@type': 'Question',
'name': item.question,
'acceptedAnswer': {
'@type': 'Answer',
'text': this.stripHtml(item.answer)
}
}))
};
this.seoService.injectStructuredData(faqSchema);
}
}
toggle(index: number) {
this.openIndex = this.openIndex === index ? null : index;
}
private stripHtml(html: string): string {
const tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
}
ngOnDestroy() {
this.seoService.clearStructuredData();
}
}

View File

@@ -3,32 +3,32 @@
<div class="flex flex-col lg:flex-row items-center mb-4 lg:mb-0">
<!-- <img src="assets/images/header-logo.png" alt="BizMatch Logo" class="h-8 mb-2 lg:mb-0 lg:mr-4" /> -->
<a routerLink="/home" class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="assets/images/header-logo.png" class="h-8" class="h-8 mb-2 lg:mb-0 lg:mr-4" />
<img src="assets/images/header-logo.png" class="h-8 mb-2 lg:mb-0 lg:mr-4" width="120" height="32" />
</a>
<p class="text-sm text-gray-600 text-center lg:text-left">© {{ currentYear }} Bizmatch All rights reserved.</p>
<p class="text-sm text-neutral-600 text-center lg:text-left">© {{ currentYear }} Bizmatch All rights reserved.</p>
</div>
<div class="flex flex-col lg:flex-row items-center order-3 lg:order-2">
<a class="text-sm text-blue-600 hover:underline hover:cursor-pointer mx-2" data-drawer-target="terms-of-use" data-drawer-show="terms-of-use" aria-controls="terms-of-use">Terms of use</a>
<a class="text-sm text-blue-600 hover:underline hover:cursor-pointer mx-2" data-drawer-target="privacy" data-drawer-show="privacy" aria-controls="privacy">Privacy statement</a>
<!-- <a class="text-sm text-blue-600 hover:underline hover:cursor-pointer mx-2" routerLink="/pricingOverview">Pricing</a> -->
<a class="text-sm text-primary-600 hover:underline hover:cursor-pointer mx-2" routerLink="/terms-of-use">Terms of use</a>
<a class="text-sm text-primary-600 hover:underline hover:cursor-pointer mx-2" routerLink="/privacy-statement">Privacy statement</a>
<!-- <a class="text-sm text-primary-600 hover:underline hover:cursor-pointer mx-2" routerLink="/pricingOverview">Pricing</a> -->
</div>
<div class="flex flex-col lg:flex-row items-center order-2 lg:order-3">
<div class="mb-4 lg:mb-0 lg:mr-6 text-center lg:text-right">
<p class="text-sm text-gray-600 mb-1 lg:mb-2">BizMatch, Inc., 1001 Blucher Street, Corpus</p>
<p class="text-sm text-gray-600">Christi, Texas 78401</p>
<p class="text-sm text-neutral-600 mb-1 lg:mb-2">BizMatch, Inc., 1001 Blucher Street, Corpus</p>
<p class="text-sm text-neutral-600">Christi, Texas 78401</p>
</div>
<div class="mb-4 lg:mb-0 flex flex-col items-center lg:items-end">
<a class="text-sm text-gray-600 mb-1 lg:mb-2 hover:text-blue-600 w-full"> <i class="fas fa-phone-alt mr-2"></i>1-800-840-6025 </a>
<a class="text-sm text-gray-600 hover:text-blue-600"> <i class="fas fa-envelope mr-2"></i>info&#64;bizmatch.net </a>
<a class="text-sm text-neutral-600 mb-1 lg:mb-2 hover:text-primary-600 w-full"> <i class="fas fa-phone-alt mr-2"></i>1-800-840-6025 </a>
<a class="text-sm text-neutral-600 hover:text-primary-600"> <i class="fas fa-envelope mr-2"></i>info&#64;bizmatch.net </a>
</div>
</div>
</div>
</footer>
<div id="privacy" class="fixed top-0 left-0 z-40 h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white lg:w-1/3 w-96 dark:bg-gray-800" tabindex="-1" aria-labelledby="drawer-label">
<h5 id="drawer-label" class="inline-flex items-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400">
<div id="privacy" class="fixed top-0 left-0 z-40 h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white lg:w-1/3 w-96 dark:bg-neutral-800" tabindex="-1" aria-labelledby="drawer-label">
<h5 id="drawer-label" class="inline-flex items-center mb-4 text-base font-semibold text-neutral-500 dark:text-neutral-400">
<svg class="w-4 h-4 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
</svg>
@@ -38,7 +38,7 @@
type="button"
data-drawer-hide="privacy"
aria-controls="privacy"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 absolute top-2.5 end-2.5 flex items-center justify-center dark:hover:bg-gray-600 dark:hover:text-white"
class="text-neutral-400 bg-transparent hover:bg-neutral-200 hover:text-neutral-900 rounded-lg text-sm w-8 h-8 absolute top-2.5 end-2.5 flex items-center justify-center dark:hover:bg-neutral-600 dark:hover:text-white"
>
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
@@ -242,8 +242,8 @@
</article>
</section>
</div>
<div id="terms-of-use" class="fixed top-0 left-0 z-40 h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white lg:w-1/3 w-96 dark:bg-gray-800" tabindex="-1" aria-labelledby="drawer-label">
<h5 id="drawer-label" class="inline-flex items-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400">
<div id="terms-of-use" class="fixed top-0 left-0 z-40 h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white lg:w-1/3 w-96 dark:bg-neutral-800" tabindex="-1" aria-labelledby="drawer-label">
<h5 id="drawer-label" class="inline-flex items-center mb-4 text-base font-semibold text-neutral-500 dark:text-neutral-400">
<svg class="w-4 h-4 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
</svg>
@@ -253,7 +253,7 @@
type="button"
data-drawer-hide="terms-of-use"
aria-controls="terms-of-use"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 absolute top-2.5 end-2.5 flex items-center justify-center dark:hover:bg-gray-600 dark:hover:text-white"
class="text-neutral-400 bg-transparent hover:bg-neutral-200 hover:text-neutral-900 rounded-lg text-sm w-8 h-8 absolute top-2.5 end-2.5 flex items-center justify-center dark:hover:bg-neutral-600 dark:hover:text-white"
>
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />

View File

@@ -1,7 +1,7 @@
<nav class="bg-white border-gray-200 dark:bg-gray-900 print:hidden">
<nav class="bg-white border-neutral-200 dark:bg-neutral-900 print:hidden">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a routerLink="/home" class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="assets/images/header-logo.png" class="h-10" alt="Flowbite Logo" />
<img src="assets/images/header-logo.png" class="h-10" alt="BizMatch - Business Marketplace for Buying and Selling Businesses" width="150" height="40" />
</a>
<div class="flex items-center md:order-2 space-x-3 rtl:space-x-reverse">
<!-- Filter button -->
@@ -11,18 +11,18 @@
<button
type="button"
id="sortDropdownButton"
class="max-sm:hidden px-4 py-2 text-sm font-medium bg-white border border-gray-200 rounded-lg hover:bg-gray-100 hover:text-blue-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
class="max-sm:hidden px-4 py-2 text-sm font-medium bg-white border border-neutral-200 rounded-lg hover:bg-neutral-100 hover:text-primary-600 dark:bg-neutral-800 dark:text-neutral-400 dark:border-neutral-600 dark:hover:text-white dark:hover:bg-neutral-700"
(click)="toggleSortDropdown()"
[ngClass]="{ 'text-blue-500': selectOptions.getSortByOption(sortBy) !== 'Sort', 'text-gray-900': selectOptions.getSortByOption(sortBy) === 'Sort' }"
[ngClass]="{ 'text-primary-500': selectOptions.getSortByOption(sortBy) !== 'Sort', 'text-neutral-900': selectOptions.getSortByOption(sortBy) === 'Sort' }"
>
<i class="fas fa-sort mr-2"></i>{{ selectOptions.getSortByOption(sortBy) }}
</button>
<!-- Sort options dropdown -->
<div *ngIf="sortDropdownVisible" class="absolute right-0 z-50 w-48 md:mt-2 max-md:mt-20 max-md:mr-[-2.5rem] bg-white border border-gray-200 rounded-lg drop-shadow-custom-bg dark:bg-gray-800 dark:border-gray-600">
<ul class="py-1 text-sm text-gray-700 dark:text-gray-200">
<div *ngIf="sortDropdownVisible" class="absolute right-0 z-50 w-48 md:mt-2 max-md:mt-20 max-md:mr-[-2.5rem] bg-white border border-neutral-200 rounded-lg drop-shadow-custom-bg dark:bg-neutral-800 dark:border-neutral-600">
<ul class="py-1 text-sm text-neutral-700 dark:text-neutral-200">
@for(item of sortByOptions; track item){
<li (click)="sortByFct(item.value)" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer">{{ item.selectName ? item.selectName : item.name }}</li>
<li (click)="sortByFct(item.value)" class="block px-4 py-2 hover:bg-neutral-100 dark:hover:bg-neutral-700 cursor-pointer">{{ item.selectName ? item.selectName : item.name }}</li>
}
</ul>
</div>
@@ -30,7 +30,7 @@
}
<button
type="button"
class="flex text-sm bg-gray-400 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600"
class="flex text-sm bg-neutral-400 rounded-full md:me-0 focus:ring-4 focus:ring-neutral-300 dark:focus:ring-neutral-600"
id="user-menu-button"
aria-expanded="false"
[attr.data-dropdown-toggle]="user ? 'user-login' : 'user-unknown'"
@@ -38,52 +38,52 @@
>
<span class="sr-only">Open user menu</span>
@if(isProfessional || (authService.isAdmin() | async) && user?.hasProfile){
<img class="w-8 h-8 rounded-full object-cover" src="{{ profileUrl }}" alt="user photo" />
<img class="w-8 h-8 rounded-full object-cover" src="{{ profileUrl }}" alt="{{ user?.firstname }} {{ user?.lastname }} profile photo" width="32" height="32" />
} @else {
<i class="flex justify-center items-center text-stone-50 w-8 h-8 rounded-full fa-solid fa-bars"></i>
}
</button>
<!-- Dropdown menu -->
@if(user){
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-login">
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-neutral-100 rounded-lg shadow dark:bg-neutral-700 dark:divide-neutral-600" id="user-login">
<div class="px-4 py-3">
<span class="block text-sm text-gray-900 dark:text-white">Welcome, {{ user.firstname }} </span>
<span class="block text-sm text-gray-500 truncate dark:text-gray-400">{{ user.email }}</span>
<span class="block text-sm text-neutral-900 dark:text-white">Welcome, {{ user.firstname }} </span>
<span class="block text-sm text-neutral-500 truncate dark:text-neutral-400">{{ user.email }}</span>
</div>
<ul class="py-2" aria-labelledby="user-menu-button">
<li>
<a routerLink="/account" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Account</a>
<a routerLink="/account" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white">Account</a>
</li>
@if((user.customerType==='professional' && user.customerSubType==='broker') || user.customerType==='seller' || (authService.isAdmin() | async)){
<li>
@if(user.customerType==='professional'){
<a routerLink="/createBusinessListing" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
<a routerLink="/createBusinessListing" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white"
>Create Listing</a
>
}@else {
<a routerLink="/createCommercialPropertyListing" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
<a routerLink="/createCommercialPropertyListing" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white"
>Create Listing</a
>
}
</li>
<li>
<a routerLink="/myListings" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">My Listings</a>
<a routerLink="/myListings" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white">My Listings</a>
</li>
}
<li>
<a routerLink="/myFavorites" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">My Favorites</a>
<a routerLink="/myFavorites" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white">My Favorites</a>
</li>
<li>
<a routerLink="/emailUs" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">EMail Us</a>
<a routerLink="/emailUs" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white">EMail Us</a>
</li>
<li>
<a routerLink="/logout" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Logout</a>
<a routerLink="/logout" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white">Logout</a>
</li>
</ul>
@if(authService.isAdmin() | async){
<ul class="py-2">
<li>
<a routerLink="admin/users" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Users (Admin)</a>
<a routerLink="admin/users" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white">Users (Admin)</a>
</li>
</ul>
}
@@ -91,7 +91,7 @@
<li>
<a
routerLink="/businessListings"
[ngClass]="{ 'text-blue-700': isActive('/businessListings'), 'text-gray-700': !isActive('/businessListings') }"
[ngClass]="{ 'text-primary-600': isActive('/businessListings'), 'text-neutral-700': !isActive('/businessListings') }"
class="block px-4 py-2 text-sm font-semibold"
(click)="closeMenusAndSetCriteria('businessListings')"
>Businesses</a
@@ -101,7 +101,7 @@
<li>
<a
routerLink="/commercialPropertyListings"
[ngClass]="{ 'text-blue-700': isActive('/commercialPropertyListings'), 'text-gray-700': !isActive('/commercialPropertyListings') }"
[ngClass]="{ 'text-primary-600': isActive('/commercialPropertyListings'), 'text-neutral-700': !isActive('/commercialPropertyListings') }"
class="block px-4 py-2 text-sm font-semibold"
(click)="closeMenusAndSetCriteria('commercialPropertyListings')"
>Properties</a
@@ -111,7 +111,7 @@
<li>
<a
routerLink="/brokerListings"
[ngClass]="{ 'text-blue-700': isActive('/brokerListings'), 'text-gray-700': !isActive('/brokerListings') }"
[ngClass]="{ 'text-primary-600': isActive('/brokerListings'), 'text-neutral-700': !isActive('/brokerListings') }"
class="block px-4 py-2 text-sm font-semibold"
(click)="closeMenusAndSetCriteria('brokerListings')"
>Professionals</a
@@ -121,20 +121,20 @@
</ul>
</div>
} @else {
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-unknown">
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-neutral-100 rounded-lg shadow dark:bg-neutral-700 dark:divide-neutral-600" id="user-unknown">
<ul class="py-2" aria-labelledby="user-menu-button">
<li>
<a routerLink="/login" [queryParams]="{ mode: 'login' }" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Log In</a>
<a routerLink="/login" [queryParams]="{ mode: 'login' }" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white">Log In</a>
</li>
<li>
<a routerLink="/login" [queryParams]="{ mode: 'register' }" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Sign Up</a>
<a routerLink="/login" [queryParams]="{ mode: 'register' }" class="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-600 dark:text-neutral-200 dark:hover:text-white">Sign Up</a>
</li>
</ul>
<ul class="py-2 md:hidden">
<li>
<a
routerLink="/businessListings"
[ngClass]="{ 'text-blue-700': isActive('/businessListings'), 'text-gray-700': !isActive('/businessListings') }"
[ngClass]="{ 'text-primary-600': isActive('/businessListings'), 'text-neutral-700': !isActive('/businessListings') }"
class="block px-4 py-2 text-sm font-bold"
(click)="closeMenusAndSetCriteria('businessListings')"
>Businesses</a
@@ -144,7 +144,7 @@
<li>
<a
routerLink="/commercialPropertyListings"
[ngClass]="{ 'text-blue-700': isActive('/commercialPropertyListings'), 'text-gray-700': !isActive('/commercialPropertyListings') }"
[ngClass]="{ 'text-primary-600': isActive('/commercialPropertyListings'), 'text-neutral-700': !isActive('/commercialPropertyListings') }"
class="block px-4 py-2 text-sm font-bold"
(click)="closeMenusAndSetCriteria('commercialPropertyListings')"
>Properties</a
@@ -154,7 +154,7 @@
<li>
<a
routerLink="/brokerListings"
[ngClass]="{ 'text-blue-700': isActive('/brokerListings'), 'text-gray-700': !isActive('/brokerListings') }"
[ngClass]="{ 'text-primary-600': isActive('/brokerListings'), 'text-neutral-700': !isActive('/brokerListings') }"
class="block px-4 py-2 text-sm font-bold"
(click)="closeMenusAndSetCriteria('brokerListings')"
>Professionals</a
@@ -167,40 +167,46 @@
</div>
<div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user">
<ul
class="flex flex-col font-medium p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700"
class="flex flex-col font-medium p-4 md:p-0 mt-4 border border-neutral-100 rounded-lg bg-neutral-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-neutral-800 md:dark:bg-neutral-900 dark:border-neutral-700"
>
<li>
<a
routerLinkActive="active-link"
routerLink="/businessListings"
[ngClass]="{ 'bg-blue-700 text-white md:text-blue-700 md:bg-transparent md:dark:text-blue-500': isActive('/businessListings') }"
class="block py-2 px-3 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
[ngClass]="{ 'bg-primary-600 text-white md:text-primary-600 md:bg-transparent md:dark:text-primary-500': isActive('/businessListings') }"
class="block py-2 px-3 rounded hover:bg-neutral-100 md:hover:bg-transparent md:hover:text-primary-600 md:p-0 dark:text-white md:dark:hover:text-primary-500 dark:hover:bg-neutral-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-neutral-700 inline-flex items-center"
aria-current="page"
(click)="closeMenusAndSetCriteria('businessListings')"
>Businesses</a
>
<img src="assets/images/business_logo.png" alt="Business" class="w-5 h-5 mr-2 object-contain" />
<span>Businesses</span>
</a>
</li>
@if ((numberOfCommercial$ | async) > 0) {
<li>
<a
routerLinkActive="active-link"
routerLink="/commercialPropertyListings"
[ngClass]="{ 'bg-blue-700 text-white md:text-blue-700 md:bg-transparent md:dark:text-blue-500': isActive('/commercialPropertyListings') }"
class="block py-2 px-3 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
[ngClass]="{ 'bg-primary-600 text-white md:text-primary-600 md:bg-transparent md:dark:text-primary-500': isActive('/commercialPropertyListings') }"
class="block py-2 px-3 rounded hover:bg-neutral-100 md:hover:bg-transparent md:hover:text-primary-600 md:p-0 dark:text-white md:dark:hover:text-primary-500 dark:hover:bg-neutral-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-neutral-700 inline-flex items-center"
(click)="closeMenusAndSetCriteria('commercialPropertyListings')"
>Properties</a
>
<img src="assets/images/properties_logo.png" alt="Properties" class="w-5 h-5 mr-2 object-contain" />
<span>Properties</span>
</a>
</li>
} @if ((numberOfBroker$ | async) > 0) {
<li>
<a
routerLinkActive="active-link"
routerLink="/brokerListings"
[ngClass]="{ 'bg-blue-700 text-white md:text-blue-700 md:bg-transparent md:dark:text-blue-500': isActive('/brokerListings') }"
class="block py-2 px-3 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
[ngClass]="{ 'bg-primary-600 text-white md:text-primary-600 md:bg-transparent md:dark:text-primary-500': isActive('/brokerListings') }"
class="inline-flex items-center py-2 px-3 rounded hover:bg-neutral-100 md:hover:bg-transparent md:hover:text-primary-600 md:p-0 dark:text-white md:dark:hover:text-primary-500 dark:hover:bg-neutral-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-neutral-700"
(click)="closeMenusAndSetCriteria('brokerListings')"
>Professionals</a
>
<img src="assets/images/icon_professionals.png" alt="Professionals" class="w-5 h-5 mr-2 object-contain bg-transparent" style="mix-blend-mode: darken;" />
<span>Professionals</span>
</a>
</li>
}
</ul>
@@ -212,8 +218,8 @@
(click)="toggleSortDropdown()"
type="button"
id="sortDropdownMobileButton"
class="mx-4 w-1/2 px-4 py-2 text-sm font-medium bg-white border border-gray-200 rounded-lg hover:bg-gray-100 hover:text-blue-700 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
[ngClass]="{ 'text-blue-500': selectOptions.getSortByOption(sortBy) !== 'Sort', 'text-gray-900': selectOptions.getSortByOption(sortBy) === 'Sort' }"
class="mx-4 w-1/2 px-4 py-2 text-sm font-medium bg-white border border-neutral-200 rounded-lg hover:bg-neutral-100 hover:text-primary-600 focus:ring-2 focus:ring-primary-600 focus:text-primary-600 dark:bg-neutral-800 dark:text-neutral-400 dark:border-neutral-600 dark:hover:text-white dark:hover:bg-neutral-700"
[ngClass]="{ 'text-primary-500': selectOptions.getSortByOption(sortBy) !== 'Sort', 'text-neutral-900': selectOptions.getSortByOption(sortBy) === 'Sort' }"
>
<i class="fas fa-sort mr-2"></i>{{ selectOptions.getSortByOption(sortBy) }}
</button>

View File

@@ -1,5 +1,13 @@
<div class="flex flex-col items-center justify-center min-h-screen">
<div class="bg-white p-8 rounded-lg drop-shadow-custom-bg w-full max-w-md">
<!-- Home Button -->
<div class="flex justify-end mb-4">
<a [routerLink]="['/home']" class="inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer">
<i class="fas fa-home mr-2"></i>
Home
</a>
</div>
<h2 class="text-2xl font-bold mb-6 text-center text-gray-800">
{{ isLoginMode ? 'Login' : 'Sign Up' }}
</h2>
@@ -25,7 +33,7 @@
type="email"
[(ngModel)]="email"
placeholder="Please enter E-Mail Address"
class="w-full px-3 py-2 pl-10 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<fa-icon [icon]="envelope" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>
@@ -40,7 +48,7 @@
type="password"
[(ngModel)]="password"
placeholder="Please enter Password"
class="w-full px-3 py-2 pl-10 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>
@@ -55,7 +63,7 @@
type="password"
[(ngModel)]="confirmPassword"
placeholder="Repeat Password"
class="w-full px-3 py-2 pl-10 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>

View File

@@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faArrowRight, faEnvelope, faLock, faUserPlus } from '@fortawesome/free-solid-svg-icons';
import { AuthService } from '../../services/auth.service';
@@ -9,7 +9,7 @@ import { LoadingService } from '../../services/loading.service';
@Component({
selector: 'app-login-register',
standalone: true,
imports: [CommonModule, FormsModule, FontAwesomeModule],
imports: [CommonModule, FormsModule, FontAwesomeModule, RouterModule],
templateUrl: './login-register.component.html',
})
export class LoginRegisterComponent {
@@ -45,7 +45,7 @@ export class LoginRegisterComponent {
.loginWithEmail(this.email, this.password)
.then(userCredential => {
console.log('Successfully logged in:', userCredential);
this.router.navigate([`home`]);
this.router.navigate([`myListing`]);
})
.catch(error => {
console.error('Error during email login:', error);
@@ -85,7 +85,7 @@ export class LoginRegisterComponent {
.loginWithGoogle()
.then(userCredential => {
console.log('Successfully logged in with Google:', userCredential);
this.router.navigate([`home`]);
this.router.navigate([`myListing`]);
})
.catch(error => {
console.error('Error during Google login:', error);

View File

@@ -14,6 +14,11 @@
</section> -->
<section class="bg-white dark:bg-gray-900">
<div class="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
<!-- Breadcrumbs -->
<div class="mb-4">
<app-breadcrumbs [breadcrumbs]="breadcrumbs"></app-breadcrumbs>
</div>
<div class="mx-auto max-w-screen-sm text-center">
<h1 class="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-blue-700 dark:text-blue-500">404</h1>
<p class="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Something's missing.</p>

View File

@@ -1,11 +1,32 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SeoService } from '../../services/seo.service';
import { BreadcrumbItem, BreadcrumbsComponent } from '../breadcrumbs/breadcrumbs.component';
@Component({
selector: 'app-not-found',
standalone: true,
imports: [CommonModule, RouterModule],
imports: [CommonModule, RouterModule, BreadcrumbsComponent],
templateUrl: './not-found.component.html',
})
export class NotFoundComponent {}
export class NotFoundComponent implements OnInit {
breadcrumbs: BreadcrumbItem[] = [
{ label: 'Home', url: '/home', icon: 'fas fa-home' },
{ label: '404 - Page Not Found' }
];
constructor(private seoService: SeoService) {}
ngOnInit(): void {
// Set noindex to prevent 404 pages from being indexed
this.seoService.setNoIndex();
// Set appropriate meta tags for 404 page
this.seoService.updateMetaTags({
title: '404 - Page Not Found | BizMatch',
description: 'The page you are looking for could not be found. Return to BizMatch to browse businesses for sale or commercial properties.',
type: 'website'
});
}
}

View File

@@ -0,0 +1,260 @@
<div
*ngIf="isModal && (modalService.modalVisible$ | async)?.visible && (modalService.modalVisible$ | async)?.type === 'brokerListings'"
class="fixed inset-0 bg-neutral-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50"
>
<div class="relative w-full h-screen max-h-screen">
<div class="relative bg-white rounded-lg shadow h-full">
<div class="flex items-start justify-between p-4 border-b rounded-t bg-primary-600">
<h3 class="text-xl font-semibold text-white p-2 rounded">Professional Search</h3>
<button (click)="closeAndSearch()" type="button" class="text-white bg-transparent hover:bg-gray-200 hover:text-neutral-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
<span class="sr-only">Close Modal</span>
</button>
</div>
<div class="p-6 space-y-6">
<div class="flex space-x-4 mb-4">
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
Clear all Filter
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- Display active filters as tags -->
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.types?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Types: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Professional Name: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.companyName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Company: {{ criteria.companyName }} <button (click)="removeFilter('companyName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.counties?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Areas Served: {{ criteria.counties.join(', ') }} <button (click)="removeFilter('counties')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
</div>
@if(criteria.criteriaType==='brokerListings') {
<div class="grid grid-cols-1 gap-6">
<div class="space-y-4">
<div>
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
</div>
<div>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
</div>
<div *ngIf="criteria.city">
<label class="block mb-2 text-sm font-medium text-neutral-900">Search Type</label>
<div class="flex items-center space-x-4">
<label class="inline-flex items-center">
<input type="radio" class="form-radio" name="searchType" [ngModel]="criteria.searchType" (ngModelChange)="updateCriteria({ searchType: $event })" value="exact" />
<span class="ml-2">Exact City</span>
</label>
<label class="inline-flex items-center">
<input type="radio" class="form-radio" name="searchType" [ngModel]="criteria.searchType" (ngModelChange)="updateCriteria({ searchType: $event })" value="radius" />
<span class="ml-2">Radius Search</span>
</label>
</div>
</div>
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
<label class="block mb-2 text-sm font-medium text-neutral-900">Select Radius (in miles)</label>
<div class="flex flex-wrap">
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
<button
type="button"
class="px-3 py-2 text-xs font-medium text-center border border-neutral-200 hover:bg-gray-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-neutral-900 bg-white'"
(click)="setRadius(radius)"
>
{{ radius }}
</button>
}
</div>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-neutral-900">Professional Type</label>
<ng-select
class="custom"
[items]="selectOptions.customerSubTypes"
bindLabel="name"
bindValue="value"
[ngModel]="criteria.types"
(ngModelChange)="onCategoryChange($event)"
[multiple]="true"
[closeOnSelect]="true"
placeholder="Select professional types"
></ng-select>
</div>
<div>
<label for="brokerName" class="block mb-2 text-sm font-medium text-neutral-900">Professional Name</label>
<input
type="text"
id="brokerName"
[ngModel]="criteria.brokerName"
(ngModelChange)="updateCriteria({ brokerName: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-full p-2.5"
placeholder="e.g. John Smith"
/>
</div>
<div>
<label for="companyName" class="block mb-2 text-sm font-medium text-neutral-900">Company Name</label>
<input
type="text"
id="companyName"
[ngModel]="criteria.companyName"
(ngModelChange)="updateCriteria({ companyName: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-full p-2.5"
placeholder="e.g. ABC Brokers"
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-neutral-900">Counties / Areas Served</label>
<ng-select
class="custom"
[items]="counties$ | async"
[multiple]="true"
[loading]="countyLoading"
[typeahead]="countyInput$"
[ngModel]="criteria.counties"
(ngModelChange)="onCountiesChange($event)"
[closeOnSelect]="true"
placeholder="Type to search counties"
></ng-select>
</div>
</div>
</div>
}
</div>
</div>
</div>
</div>
<div *ngIf="!isModal" class="space-y-6 pb-10">
<div class="flex space-x-4 mb-4">
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
Clear all Filter
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- Display active filters as tags -->
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.types?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Types: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Professional Name: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.companyName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Company: {{ criteria.companyName }} <button (click)="removeFilter('companyName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.counties?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Areas Served: {{ criteria.counties.join(', ') }} <button (click)="removeFilter('counties')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
</div>
@if(criteria.criteriaType==='brokerListings') {
<div class="space-y-4">
<div>
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
</div>
<div>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
</div>
<div *ngIf="criteria.city">
<label class="block mb-2 text-sm font-medium text-neutral-900">Search Type</label>
<div class="flex items-center space-x-4">
<label class="inline-flex items-center">
<input type="radio" class="form-radio" name="searchType" [ngModel]="criteria.searchType" (ngModelChange)="updateCriteria({ searchType: $event })" value="exact" />
<span class="ml-2">Exact City</span>
</label>
<label class="inline-flex items-center">
<input type="radio" class="form-radio" name="searchType" [ngModel]="criteria.searchType" (ngModelChange)="updateCriteria({ searchType: $event })" value="radius" />
<span class="ml-2">Radius Search</span>
</label>
</div>
</div>
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
<label class="block mb-2 text-sm font-medium text-neutral-900">Select Radius (in miles)</label>
<div class="flex flex-wrap">
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
<button
type="button"
class="px-3 py-2 text-xs font-medium text-center border border-neutral-200 hover:bg-gray-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-neutral-900 bg-white'"
(click)="setRadius(radius)"
>
{{ radius }}
</button>
}
</div>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-neutral-900">Professional Type</label>
<ng-select
class="custom"
[items]="selectOptions.customerSubTypes"
bindLabel="name"
bindValue="value"
[ngModel]="criteria.types"
(ngModelChange)="onCategoryChange($event)"
[multiple]="true"
[closeOnSelect]="true"
placeholder="Select professional types"
></ng-select>
</div>
<div>
<label for="brokerName" class="block mb-2 text-sm font-medium text-neutral-900">Professional Name</label>
<input
type="text"
id="brokerName"
[ngModel]="criteria.brokerName"
(ngModelChange)="updateCriteria({ brokerName: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-full p-2.5"
placeholder="e.g. John Smith"
/>
</div>
<div>
<label for="companyName" class="block mb-2 text-sm font-medium text-neutral-900">Company Name</label>
<input
type="text"
id="companyName"
[ngModel]="criteria.companyName"
(ngModelChange)="updateCriteria({ companyName: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-full p-2.5"
placeholder="e.g. ABC Brokers"
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-neutral-900">Counties / Areas Served</label>
<ng-select
class="custom"
[items]="counties$ | async"
[multiple]="true"
[loading]="countyLoading"
[typeahead]="countyInput$"
[ngModel]="criteria.counties"
(ngModelChange)="onCountiesChange($event)"
[closeOnSelect]="true"
placeholder="Type to search counties"
></ng-select>
</div>
</div>
}
</div>

View File

@@ -0,0 +1,316 @@
import { CommonModule } from '@angular/common';
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { catchError, concat, debounceTime, distinctUntilChanged, map, Observable, of, Subject, switchMap, takeUntil, tap } from 'rxjs';
import { CountyResult, GeoResult, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
import { FilterStateService } from '../../services/filter-state.service';
import { GeoService } from '../../services/geo.service';
import { SearchService } from '../../services/search.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { UserService } from '../../services/user.service';
import { ValidatedCityComponent } from '../validated-city/validated-city.component';
import { ModalService } from './modal.service';
@UntilDestroy()
@Component({
selector: 'app-search-modal-broker',
standalone: true,
imports: [CommonModule, FormsModule, NgSelectModule, ValidatedCityComponent],
templateUrl: './search-modal-broker.component.html',
styleUrls: ['./search-modal.component.scss'],
})
export class SearchModalBrokerComponent implements OnInit, OnDestroy {
@Input() isModal: boolean = true;
private destroy$ = new Subject<void>();
private searchDebounce$ = new Subject<void>();
// State
criteria: UserListingCriteria;
backupCriteria: any;
// Geo search
counties$: Observable<CountyResult[]>;
countyLoading = false;
countyInput$ = new Subject<string>();
// Results count
numberOfResults$: Observable<number>;
cancelDisable = false;
constructor(
public selectOptions: SelectOptionsService,
public modalService: ModalService,
private geoService: GeoService,
private filterStateService: FilterStateService,
private userService: UserService,
private searchService: SearchService,
) {}
ngOnInit(): void {
// Load counties
this.loadCounties();
if (this.isModal) {
// Modal mode: Wait for messages from ModalService
this.modalService.message$.pipe(untilDestroyed(this)).subscribe(criteria => {
if (criteria?.criteriaType === 'brokerListings') {
this.initializeWithCriteria(criteria);
}
});
this.modalService.modalVisible$.pipe(untilDestroyed(this)).subscribe(val => {
if (val.visible && val.type === 'brokerListings') {
// Reset pagination when modal opens
if (this.criteria) {
this.criteria.page = 1;
this.criteria.start = 0;
}
}
});
} else {
// Embedded mode: Subscribe to state changes
this.subscribeToStateChanges();
}
// Setup debounced search
this.searchDebounce$.pipe(debounceTime(400), takeUntil(this.destroy$)).subscribe(() => {
this.triggerSearch();
});
}
private initializeWithCriteria(criteria: UserListingCriteria): void {
this.criteria = criteria;
this.backupCriteria = JSON.parse(JSON.stringify(criteria));
this.setTotalNumberOfResults();
}
private subscribeToStateChanges(): void {
if (!this.isModal) {
this.filterStateService
.getState$('brokerListings')
.pipe(takeUntil(this.destroy$))
.subscribe(state => {
this.criteria = { ...state.criteria };
this.setTotalNumberOfResults();
});
}
}
private loadCounties(): void {
this.counties$ = concat(
of([]), // default items
this.countyInput$.pipe(
distinctUntilChanged(),
tap(() => (this.countyLoading = true)),
switchMap(term =>
this.geoService.findCountiesStartingWith(term).pipe(
catchError(() => of([])),
map(counties => counties.map(county => county.name)),
tap(() => (this.countyLoading = false)),
),
),
),
);
}
// Filter removal methods
removeFilter(filterType: string): void {
const updates: any = {};
switch (filterType) {
case 'state':
updates.state = null;
updates.city = null;
updates.radius = null;
updates.searchType = 'exact';
break;
case 'city':
updates.city = null;
updates.radius = null;
updates.searchType = 'exact';
break;
case 'types':
updates.types = [];
break;
case 'brokerName':
updates.brokerName = null;
break;
case 'companyName':
updates.companyName = null;
break;
case 'counties':
updates.counties = [];
break;
}
this.updateCriteria(updates);
}
// Professional type handling
onCategoryChange(selectedCategories: string[]): void {
this.updateCriteria({ types: selectedCategories });
}
categoryClicked(checked: boolean, value: string): void {
const types = [...(this.criteria.types || [])];
if (checked) {
if (!types.includes(value)) {
types.push(value);
}
} else {
const index = types.indexOf(value);
if (index > -1) {
types.splice(index, 1);
}
}
this.updateCriteria({ types });
}
// Counties handling
onCountiesChange(selectedCounties: string[]): void {
this.updateCriteria({ counties: selectedCounties });
}
// Location handling
setState(state: string): void {
const updates: any = { state };
if (!state) {
updates.city = null;
updates.radius = null;
updates.searchType = 'exact';
}
this.updateCriteria(updates);
}
setCity(city: any): void {
const updates: any = {};
if (city) {
updates.city = city;
updates.state = city.state;
// Automatically set radius to 50 miles and enable radius search
updates.searchType = 'radius';
updates.radius = 50;
} else {
updates.city = null;
updates.radius = null;
updates.searchType = 'exact';
}
this.updateCriteria(updates);
}
setRadius(radius: number): void {
this.updateCriteria({ radius });
}
onCriteriaChange(): void {
this.triggerSearch();
}
// Debounced search for text inputs
debouncedSearch(): void {
this.searchDebounce$.next();
}
// Clear all filters
clearFilter(): void {
if (this.isModal) {
// In modal: Reset locally
const defaultCriteria = this.getDefaultCriteria();
this.criteria = defaultCriteria;
this.setTotalNumberOfResults();
} else {
// Embedded: Use state service
this.filterStateService.clearFilters('brokerListings');
}
}
// Modal-specific methods
closeAndSearch(): void {
if (this.isModal) {
// Save changes to state
this.filterStateService.setCriteria('brokerListings', this.criteria);
this.modalService.accept();
this.searchService.search('brokerListings');
}
}
close(): void {
if (this.isModal) {
// Discard changes
this.modalService.reject(this.backupCriteria);
}
}
// Helper methods
public updateCriteria(updates: any): void {
if (this.isModal) {
// In modal: Update locally only
this.criteria = { ...this.criteria, ...updates };
this.setTotalNumberOfResults();
} else {
// Embedded: Update through state service
this.filterStateService.updateCriteria('brokerListings', updates);
}
// Trigger search after update
this.debouncedSearch();
}
private triggerSearch(): void {
if (this.isModal) {
// In modal: Only update count
this.setTotalNumberOfResults();
this.cancelDisable = true;
} else {
// Embedded: Full search
this.searchService.search('brokerListings');
}
}
private setTotalNumberOfResults(): void {
this.numberOfResults$ = this.userService.getNumberOfBroker(this.criteria);
}
private getDefaultCriteria(): UserListingCriteria {
return {
criteriaType: 'brokerListings',
types: [],
state: null,
city: null,
radius: null,
searchType: 'exact' as const,
brokerName: null,
companyName: null,
counties: [],
prompt: null,
page: 1,
start: 0,
length: 12,
};
}
hasActiveFilters(): boolean {
if (!this.criteria) return false;
return !!(
this.criteria.state ||
this.criteria.city ||
this.criteria.types?.length ||
this.criteria.brokerName ||
this.criteria.companyName ||
this.criteria.counties?.length
);
}
trackByFn(item: GeoResult): any {
return item.id;
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@@ -1,12 +1,12 @@
<div
*ngIf="isModal && (modalService.modalVisible$ | async)?.visible && (modalService.modalVisible$ | async)?.type === 'commercialPropertyListings'"
class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50"
class="fixed inset-0 bg-neutral-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50"
>
<div class="relative w-full h-screen max-h-screen">
<div class="relative bg-white rounded-lg shadow h-full">
<div class="flex items-start justify-between p-4 border-b rounded-t bg-blue-600">
<div class="flex items-start justify-between p-4 border-b rounded-t bg-primary-600">
<h3 class="text-xl font-semibold text-white p-2 rounded">Commercial Property Listing Search</h3>
<button (click)="closeAndSearch()" type="button" class="text-white bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
<button (click)="closeAndSearch()" type="button" class="text-white bg-transparent hover:bg-gray-200 hover:text-neutral-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
@@ -15,28 +15,28 @@
</div>
<div class="p-6 space-y-6">
<div class="flex space-x-4 mb-4">
<button class="text-blue-600 font-medium border-b-2 border-blue-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-blue-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 tooltip">
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
Clear all Filter
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- Display active filters as tags -->
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
<span *ngIf="criteria.state" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.city" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.types.length" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.title" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
</div>
@@ -44,14 +44,14 @@
<div class="grid grid-cols-1 gap-6">
<div class="space-y-4">
<div>
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
</div>
<div>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
</div>
<div *ngIf="criteria.city">
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Search Type</label>
<div class="flex items-center space-x-4">
<label class="inline-flex items-center">
<input type="radio" class="form-radio" name="searchType" [ngModel]="criteria.searchType" (ngModelChange)="updateCriteria({ searchType: $event })" value="exact" />
@@ -64,13 +64,13 @@
</div>
</div>
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Select Radius (in miles)</label>
<div class="flex flex-wrap">
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
<button
type="button"
class="px-3 py-2 text-xs font-medium text-center border border-gray-200 hover:bg-gray-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-gray-900 bg-white'"
class="px-3 py-2 text-xs font-medium text-center border border-neutral-200 hover:bg-gray-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-neutral-900 bg-white'"
(click)="setRadius(radius)"
>
{{ radius }}
@@ -79,7 +79,7 @@
</div>
</div>
<div>
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
<label for="price" class="block mb-2 text-sm font-medium text-neutral-900">Price</label>
<div class="flex items-center space-x-2">
<app-validated-price name="price-from" [ngModel]="criteria.minPrice" (ngModelChange)="updateCriteria({ minPrice: $event })" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
</app-validated-price>
@@ -89,7 +89,7 @@
</div>
</div>
<div>
<label for="title" class="block mb-2 text-sm font-medium text-gray-900">Title / Description (Free Search)</label>
<label for="title" class="block mb-2 text-sm font-medium text-neutral-900">Title / Description (Free Search)</label>
<input
type="text"
id="title"
@@ -100,7 +100,7 @@
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Category</label>
<ng-select
class="custom"
[items]="selectOptions.typesOfCommercialProperty"
@@ -122,42 +122,42 @@
</div>
<div *ngIf="!isModal" class="space-y-6 pb-10">
<div class="flex space-x-4 mb-4">
<h3 class="text-xl font-semibold text-gray-900">Filter ({{ numberOfResults$ | async }})</h3>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-blue-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 tooltip">
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
Clear all Filter
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- Display active filters as tags -->
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
<span *ngIf="criteria.state" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.city" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.types.length" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.title" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
</div>
@if(criteria.criteriaType==='commercialPropertyListings') {
<div class="space-y-4">
<div>
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
</div>
<div>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
</div>
<div *ngIf="criteria.city">
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Search Type</label>
<div class="flex items-center space-x-4">
<label class="inline-flex items-center">
<input type="radio" class="form-radio" name="searchType" [ngModel]="criteria.searchType" (ngModelChange)="updateCriteria({ searchType: $event })" value="exact" />
@@ -170,13 +170,13 @@
</div>
</div>
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Select Radius (in miles)</label>
<div class="flex flex-wrap">
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
<button
type="button"
class="px-3 py-2 text-xs font-medium text-center border border-gray-200 hover:bg-gray-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-gray-900 bg-white'"
class="px-3 py-2 text-xs font-medium text-center border border-neutral-200 hover:bg-gray-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-neutral-900 bg-white'"
(click)="setRadius(radius)"
>
{{ radius }}
@@ -185,7 +185,7 @@
</div>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Category</label>
<ng-select
class="custom"
[items]="selectOptions.typesOfCommercialProperty"
@@ -199,7 +199,7 @@
></ng-select>
</div>
<div>
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
<label for="price" class="block mb-2 text-sm font-medium text-neutral-900">Price</label>
<div class="flex items-center space-x-2">
<app-validated-price name="price-from" [ngModel]="criteria.minPrice" (ngModelChange)="updateCriteria({ minPrice: $event })" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"> </app-validated-price>
<span>-</span>
@@ -207,7 +207,7 @@
</div>
</div>
<div>
<label for="title" class="block mb-2 text-sm font-medium text-gray-900">Title / Description (Free Search)</label>
<label for="title" class="block mb-2 text-sm font-medium text-neutral-900">Title / Description (Free Search)</label>
<input
type="text"
id="title"

View File

@@ -184,6 +184,9 @@ export class SearchModalCommercialComponent implements OnInit, OnDestroy {
if (city) {
updates.city = city;
updates.state = city.state;
// Automatically set radius to 50 miles and enable radius search
updates.searchType = 'radius';
updates.radius = 50;
} else {
updates.city = null;
updates.radius = null;

View File

@@ -1,12 +1,12 @@
<div
*ngIf="isModal && (modalService.modalVisible$ | async)?.visible && (modalService.modalVisible$ | async)?.type === 'businessListings'"
class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50"
class="fixed inset-0 bg-neutral-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50"
>
<div class="relative w-full max-h-full">
<div class="relative bg-white rounded-lg shadow">
<div class="flex items-start justify-between p-4 border-b rounded-t bg-blue-600">
<div class="flex items-start justify-between p-4 border-b rounded-t bg-primary-600">
<h3 class="text-xl font-semibold text-white p-2 rounded">Business Listing Search</h3>
<button (click)="closeAndSearch()" type="button" class="text-white bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
<button (click)="closeAndSearch()" type="button" class="text-white bg-transparent hover:bg-neutral-200 hover:text-neutral-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
@@ -15,60 +15,60 @@
</div>
<div class="p-6 space-y-6">
<div class="flex space-x-4 mb-4">
<button class="text-blue-600 font-medium border-b-2 border-blue-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-blue-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 tooltip">
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
Clear all Filter
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- Display active filters as tags -->
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
<span *ngIf="criteria.state" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.city" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Revenue: {{ criteria.minRevenue || 'Any' }} - {{ criteria.maxRevenue || 'Any' }} <button (click)="removeFilter('revenue')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Cashflow: {{ criteria.minCashFlow || 'Any' }} - {{ criteria.maxCashFlow || 'Any' }} <button (click)="removeFilter('cashflow')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.title" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.types.length" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="selectedPropertyType" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="selectedPropertyType" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Property Type: {{ getSelectedPropertyTypeName() }} <button (click)="removeFilter('propertyType')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Employees: {{ criteria.minNumberEmployees || 'Any' }} - {{ criteria.maxNumberEmployees || 'Any' }} <button (click)="removeFilter('employees')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.establishedMin" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.establishedMin" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Established: {{ criteria.establishedMin || 'Any' }} <button (click)="removeFilter('established')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.brokerName" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
</div>
<div class="grid grid-cols-1 gap-6">
<div class="space-y-4">
<div>
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
</div>
<div>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
</div>
<div *ngIf="criteria.city">
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Search Type</label>
<div class="flex items-center space-x-4">
<label class="inline-flex items-center">
<input type="radio" class="form-radio" name="searchType" [ngModel]="criteria.searchType" (ngModelChange)="updateCriteria({ searchType: $event })" value="exact" />
@@ -81,13 +81,13 @@
</div>
</div>
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Select Radius (in miles)</label>
<div class="flex flex-wrap">
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
<button
type="button"
class="px-3 py-2 text-xs font-medium text-center border border-gray-200 hover:bg-gray-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-gray-900 bg-white'"
class="px-3 py-2 text-xs font-medium text-center border border-neutral-200 hover:bg-neutral-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-neutral-500' : 'text-neutral-900 bg-white'"
(click)="setRadius(radius)"
>
{{ radius }}
@@ -96,48 +96,48 @@
</div>
</div>
<div>
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
<label for="price" class="block mb-2 text-sm font-medium text-neutral-900">Price</label>
<div class="flex items-center space-x-2">
<app-validated-price name="price-from" [ngModel]="criteria.minPrice" (ngModelChange)="updateCriteria({ minPrice: $event })" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
<app-validated-price name="price-from" [ngModel]="criteria.minPrice" (ngModelChange)="updateCriteria({ minPrice: $event })" placeholder="From" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5">
</app-validated-price>
<span>-</span>
<app-validated-price name="price-to" [ngModel]="criteria.maxPrice" (ngModelChange)="updateCriteria({ maxPrice: $event })" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
<app-validated-price name="price-to" [ngModel]="criteria.maxPrice" (ngModelChange)="updateCriteria({ maxPrice: $event })" placeholder="To" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5">
</app-validated-price>
</div>
</div>
<div>
<label for="salesRevenue" class="block mb-2 text-sm font-medium text-gray-900">Sales Revenue</label>
<label for="salesRevenue" class="block mb-2 text-sm font-medium text-neutral-900">Sales Revenue</label>
<div class="flex items-center space-x-2">
<app-validated-price name="salesRevenue-from" [ngModel]="criteria.minRevenue" (ngModelChange)="updateCriteria({ minRevenue: $event })" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
<app-validated-price name="salesRevenue-from" [ngModel]="criteria.minRevenue" (ngModelChange)="updateCriteria({ minRevenue: $event })" placeholder="From" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5">
</app-validated-price>
<span>-</span>
<app-validated-price name="salesRevenue-to" [ngModel]="criteria.maxRevenue" (ngModelChange)="updateCriteria({ maxRevenue: $event })" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p.2.5">
<app-validated-price name="salesRevenue-to" [ngModel]="criteria.maxRevenue" (ngModelChange)="updateCriteria({ maxRevenue: $event })" placeholder="To" inputClasses="bg-neutral-50 text-sm !mt-0 p.2.5">
</app-validated-price>
</div>
</div>
<div>
<label for="cashflow" class="block mb-2 text-sm font-medium text-gray-900">Cashflow</label>
<label for="cashflow" class="block mb-2 text-sm font-medium text-neutral-900">Cashflow</label>
<div class="flex items-center space-x-2">
<app-validated-price name="cashflow-from" [ngModel]="criteria.minCashFlow" (ngModelChange)="updateCriteria({ minCashFlow: $event })" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
<app-validated-price name="cashflow-from" [ngModel]="criteria.minCashFlow" (ngModelChange)="updateCriteria({ minCashFlow: $event })" placeholder="From" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5">
</app-validated-price>
<span>-</span>
<app-validated-price name="cashflow-to" [ngModel]="criteria.maxCashFlow" (ngModelChange)="updateCriteria({ maxCashFlow: $event })" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
<app-validated-price name="cashflow-to" [ngModel]="criteria.maxCashFlow" (ngModelChange)="updateCriteria({ maxCashFlow: $event })" placeholder="To" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5">
</app-validated-price>
</div>
</div>
<div>
<label for="title" class="block mb-2 text-sm font-medium text-gray-900">Title / Description (Free Search)</label>
<label for="title" class="block mb-2 text-sm font-medium text-neutral-900">Title / Description (Free Search)</label>
<input
type="text"
id="title"
[ngModel]="criteria.title"
(ngModelChange)="updateCriteria({ title: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-full p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-full p-2.5"
placeholder="e.g. Restaurant"
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Category</label>
<ng-select
class="custom"
[items]="selectOptions.typesOfBusiness"
@@ -151,7 +151,7 @@
></ng-select>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900">Type of Property</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Type of Property</label>
<ng-select
class="custom"
[items]="propertyTypeOptions"
@@ -163,14 +163,14 @@
></ng-select>
</div>
<div>
<label for="numberEmployees" class="block mb-2 text-sm font-medium text-gray-900">Number of Employees</label>
<label for="numberEmployees" class="block mb-2 text-sm font-medium text-neutral-900">Number of Employees</label>
<div class="flex items-center space-x-2">
<input
type="number"
id="numberEmployees-from"
[ngModel]="criteria.minNumberEmployees"
(ngModelChange)="updateCriteria({ minNumberEmployees: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-1/2 p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-1/2 p-2.5"
placeholder="From"
/>
<span>-</span>
@@ -179,32 +179,32 @@
id="numberEmployees-to"
[ngModel]="criteria.maxNumberEmployees"
(ngModelChange)="updateCriteria({ maxNumberEmployees: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-1/2 p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-1/2 p-2.5"
placeholder="To"
/>
</div>
</div>
<div>
<label for="establishedMin" class="block mb-2 text-sm font-medium text-gray-900">Minimum years established</label>
<label for="establishedMin" class="block mb-2 text-sm font-medium text-neutral-900">Minimum years established</label>
<div class="flex items-center space-x-2">
<input
type="number"
id="establishedMin"
[ngModel]="criteria.establishedMin"
(ngModelChange)="updateCriteria({ establishedMin: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-1/2 p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-1/2 p-2.5"
placeholder="YY"
/>
</div>
</div>
<div>
<label for="brokername" class="block mb-2 text-sm font-medium text-gray-900">Broker Name / Company Name</label>
<label for="brokername" class="block mb-2 text-sm font-medium text-neutral-900">Broker Name / Company Name</label>
<input
type="text"
id="brokername"
[ngModel]="criteria.brokerName"
(ngModelChange)="updateCriteria({ brokerName: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-full p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-full p-2.5"
placeholder="e.g. Brokers Invest"
/>
</div>
@@ -219,60 +219,60 @@
<!-- ################################################################################## -->
<div *ngIf="!isModal" class="space-y-6">
<div class="flex space-x-4 mb-4">
<h3 class="text-xl font-semibold text-gray-900">Filter ({{ numberOfResults$ | async }})</h3>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-blue-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 tooltip">
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
Clear all Filter
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- Display active filters as tags -->
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
<span *ngIf="criteria.state" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.city" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Revenue: {{ criteria.minRevenue || 'Any' }} - {{ criteria.maxRevenue || 'Any' }} <button (click)="removeFilter('revenue')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Cashflow: {{ criteria.minCashFlow || 'Any' }} - {{ criteria.maxCashFlow || 'Any' }} <button (click)="removeFilter('cashflow')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.title" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.types.length" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="selectedPropertyType" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="selectedPropertyType" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Property Type: {{ getSelectedPropertyTypeName() }} <button (click)="removeFilter('propertyType')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Employees: {{ criteria.minNumberEmployees || 'Any' }} - {{ criteria.maxNumberEmployees || 'Any' }} <button (click)="removeFilter('employees')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.establishedMin" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.establishedMin" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Years established: {{ criteria.establishedMin || 'Any' }} <button (click)="removeFilter('established')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.brokerName" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
</div>
@if(criteria.criteriaType==='businessListings') {
<div class="space-y-4">
<div>
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
</div>
<div>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
</div>
<div *ngIf="criteria.city">
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Search Type</label>
<div class="flex items-center space-x-4">
<label class="inline-flex items-center">
<input type="radio" class="form-radio" name="searchType" [ngModel]="criteria.searchType" (ngModelChange)="updateCriteria({ searchType: $event })" value="exact" />
@@ -285,13 +285,13 @@
</div>
</div>
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Select Radius (in miles)</label>
<div class="flex flex-wrap">
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
<button
type="button"
class="px-3 py-2 text-xs font-medium text-center border border-gray-200 hover:bg-gray-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-gray-900 bg-white'"
class="px-3 py-2 text-xs font-medium text-center border border-neutral-200 hover:bg-neutral-500 hover:text-white"
[ngClass]="criteria.radius === radius ? 'text-white bg-neutral-500' : 'text-neutral-900 bg-white'"
(click)="setRadius(radius)"
>
{{ radius }}
@@ -300,46 +300,46 @@
</div>
</div>
<div>
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
<label for="price" class="block mb-2 text-sm font-medium text-neutral-900">Price</label>
<div class="flex items-center space-x-2">
<app-validated-price name="price-from" [ngModel]="criteria.minPrice" (ngModelChange)="updateCriteria({ minPrice: $event })" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"> </app-validated-price>
<app-validated-price name="price-from" [ngModel]="criteria.minPrice" (ngModelChange)="updateCriteria({ minPrice: $event })" placeholder="From" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5"> </app-validated-price>
<span>-</span>
<app-validated-price name="price-to" [ngModel]="criteria.maxPrice" (ngModelChange)="updateCriteria({ maxPrice: $event })" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"> </app-validated-price>
<app-validated-price name="price-to" [ngModel]="criteria.maxPrice" (ngModelChange)="updateCriteria({ maxPrice: $event })" placeholder="To" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5"> </app-validated-price>
</div>
</div>
<div>
<label for="salesRevenue" class="block mb-2 text-sm font-medium text-gray-900">Sales Revenue</label>
<label for="salesRevenue" class="block mb-2 text-sm font-medium text-neutral-900">Sales Revenue</label>
<div class="flex items-center space-x-2">
<app-validated-price name="salesRevenue-from" [ngModel]="criteria.minRevenue" (ngModelChange)="updateCriteria({ minRevenue: $event })" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
<app-validated-price name="salesRevenue-from" [ngModel]="criteria.minRevenue" (ngModelChange)="updateCriteria({ minRevenue: $event })" placeholder="From" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5">
</app-validated-price>
<span>-</span>
<app-validated-price name="salesRevenue-to" [ngModel]="criteria.maxRevenue" (ngModelChange)="updateCriteria({ maxRevenue: $event })" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p.2.5">
<app-validated-price name="salesRevenue-to" [ngModel]="criteria.maxRevenue" (ngModelChange)="updateCriteria({ maxRevenue: $event })" placeholder="To" inputClasses="bg-neutral-50 text-sm !mt-0 p.2.5">
</app-validated-price>
</div>
</div>
<div>
<label for="cashflow" class="block mb-2 text-sm font-medium text-gray-900">Cashflow</label>
<label for="cashflow" class="block mb-2 text-sm font-medium text-neutral-900">Cashflow</label>
<div class="flex items-center space-x-2">
<app-validated-price name="cashflow-from" [ngModel]="criteria.minCashFlow" (ngModelChange)="updateCriteria({ minCashFlow: $event })" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
<app-validated-price name="cashflow-from" [ngModel]="criteria.minCashFlow" (ngModelChange)="updateCriteria({ minCashFlow: $event })" placeholder="From" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5">
</app-validated-price>
<span>-</span>
<app-validated-price name="cashflow-to" [ngModel]="criteria.maxCashFlow" (ngModelChange)="updateCriteria({ maxCashFlow: $event })" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5">
<app-validated-price name="cashflow-to" [ngModel]="criteria.maxCashFlow" (ngModelChange)="updateCriteria({ maxCashFlow: $event })" placeholder="To" inputClasses="bg-neutral-50 text-sm !mt-0 p-2.5">
</app-validated-price>
</div>
</div>
<div>
<label for="title" class="block mb-2 text-sm font-medium text-gray-900">Title / Description (Free Search)</label>
<label for="title" class="block mb-2 text-sm font-medium text-neutral-900">Title / Description (Free Search)</label>
<input
type="text"
id="title"
[ngModel]="criteria.title"
(ngModelChange)="updateCriteria({ title: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-full p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-full p-2.5"
placeholder="e.g. Restaurant"
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Category</label>
<ng-select
class="custom"
[items]="selectOptions.typesOfBusiness"
@@ -353,7 +353,7 @@
></ng-select>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900">Type of Property</label>
<label class="block mb-2 text-sm font-medium text-neutral-900">Type of Property</label>
<ng-select
class="custom"
[items]="propertyTypeOptions"
@@ -365,14 +365,14 @@
></ng-select>
</div>
<div>
<label for="numberEmployees" class="block mb-2 text-sm font-medium text-gray-900">Number of Employees</label>
<label for="numberEmployees" class="block mb-2 text-sm font-medium text-neutral-900">Number of Employees</label>
<div class="flex items-center space-x-2">
<input
type="number"
id="numberEmployees-from"
[ngModel]="criteria.minNumberEmployees"
(ngModelChange)="updateCriteria({ minNumberEmployees: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-1/2 p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-1/2 p-2.5"
placeholder="From"
/>
<span>-</span>
@@ -381,32 +381,32 @@
id="numberEmployees-to"
[ngModel]="criteria.maxNumberEmployees"
(ngModelChange)="updateCriteria({ maxNumberEmployees: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-1/2 p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-1/2 p-2.5"
placeholder="To"
/>
</div>
</div>
<div>
<label for="establishedMin" class="block mb-2 text-sm font-medium text-gray-900">Minimum years established</label>
<label for="establishedMin" class="block mb-2 text-sm font-medium text-neutral-900">Minimum years established</label>
<div class="flex items-center space-x-2">
<input
type="number"
id="establishedMin"
[ngModel]="criteria.establishedMin"
(ngModelChange)="updateCriteria({ establishedMin: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-1/2 p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-1/2 p-2.5"
placeholder="YY"
/>
</div>
</div>
<div>
<label for="brokername" class="block mb-2 text-sm font-medium text-gray-900">Broker Name / Company Name</label>
<label for="brokername" class="block mb-2 text-sm font-medium text-neutral-900">Broker Name / Company Name</label>
<input
type="text"
id="brokername"
[ngModel]="criteria.brokerName"
(ngModelChange)="updateCriteria({ brokerName: $event })"
class="bg-gray-50 border border-gray-300 text-sm rounded-lg block w-full p-2.5"
class="bg-neutral-50 border border-neutral-300 text-sm rounded-lg block w-full p-2.5"
placeholder="e.g. Brokers Invest"
/>
</div>

View File

@@ -257,6 +257,9 @@ export class SearchModalComponent implements OnInit, OnDestroy {
if (city) {
updates.city = city;
updates.state = city.state;
// Automatically set radius to 50 miles and enable radius search
updates.searchType = 'radius';
updates.radius = 50;
} else {
updates.city = null;
updates.radius = null;