neue Landing page

This commit is contained in:
Andreas Knuth 2025-04-03 11:12:07 +02:00
parent b39370a6b5
commit eb23bebc10
14 changed files with 392 additions and 598 deletions

View File

@ -20,6 +20,7 @@
"@angular/core": "^18.1.3",
"@angular/fire": "^18.0.1",
"@angular/forms": "^18.1.3",
"@angular/google-maps": "^18.2.14",
"@angular/platform-browser": "^18.1.3",
"@angular/platform-browser-dynamic": "^18.1.3",
"@angular/platform-server": "^18.1.3",

View File

@ -6,7 +6,6 @@
<main class="flex-1">
<router-outlet></router-outlet>
</main>
<app-footer></app-footer>
</div>

View File

@ -43,38 +43,11 @@ export class AppComponent {
while (currentRoute.children[0] !== undefined) {
currentRoute = currentRoute.children[0];
}
// Hier haben Sie Zugriff auf den aktuellen Route-Pfad
this.actualRoute = currentRoute.snapshot.url[0].path;
});
}
ngOnInit() {
// this.keycloakService.keycloakEvents$.subscribe({
// next: event => {
// if (event.type === KeycloakEventType.OnTokenExpired) {
// this.handleTokenExpiration();
// }
// },
// });
}
// private async handleTokenExpiration(): Promise<void> {
// try {
// // Versuche, den Token zu erneuern
// const refreshed = await this.keycloakService.updateToken();
// if (!refreshed) {
// // Wenn der Token nicht erneuert werden kann, leite zur Login-Seite weiter
// this.keycloakService.login({
// redirectUri: window.location.href, // oder eine andere Seite
// });
// }
// } catch (error) {
// if (error.error === 'invalid_grant' && error.error_description === 'Token is not active') {
// // Hier wird der Fehler "invalid_grant" abgefangen
// this.keycloakService.login({
// redirectUri: window.location.href,
// });
// }
// }
// }
ngOnInit() {}
@HostListener('window:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
if (event.shiftKey && event.ctrlKey && event.key === 'V') {

View File

@ -28,17 +28,14 @@ export const routes: Routes = [
{
path: 'businessListings',
component: BusinessListingsComponent,
runGuardsAndResolvers: 'always',
},
{
path: 'commercialPropertyListings',
component: CommercialPropertyListingsComponent,
runGuardsAndResolvers: 'always',
},
{
path: 'brokerListings',
component: BrokerListingsComponent,
runGuardsAndResolvers: 'always',
},
{
path: 'home',

View File

@ -1,4 +1,5 @@
<footer class="bg-white px-4 py-2 md:px-6 mt-auto w-full print:hidden">
<ng-template #otherRoute>
<footer class="bg-white px-4 py-2 md:px-6 mt-auto w-full print:hidden">
<div class="container mx-auto flex flex-col lg:flex-row justify-between items-center">
<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" /> -->
@ -26,6 +27,55 @@
</div>
</div>
</div>
</footer>
</ng-template>
<footer *ngIf="isHomeRoute; else otherRoute" class="bg-gray-800 text-white pt-12 pb-4">
<div class="container mx-auto px-6">
<div class="flex flex-wrap">
<div class="w-full md:w-1/3 mb-8 md:mb-0">
<h3 class="text-xl font-semibold mb-4">BizMatch</h3>
<p class="mb-2">Your trusted partner in business brokerage.</p>
<p class="mb-2">TREC License #0516 788</p>
</div>
<div class="w-full md:w-1/3 mb-8 md:mb-0">
<h3 class="text-xl font-semibold mb-4">Quick Links</h3>
<ul>
<li class="mb-2">
<a href="#" class="text-gray-300 hover:text-white">Home</a>
</li>
<li class="mb-2">
<a href="#services" class="text-gray-300 hover:text-white">Services</a>
</li>
<li class="mb-2">
<a href="#location" class="text-gray-300 hover:text-white">Location</a>
</li>
<li class="mb-2">
<a href="#contact" class="text-gray-300 hover:text-white">Contact</a>
</li>
<li class="mb-2">
<a data-drawer-target="terms-of-use" data-drawer-show="terms-of-use" aria-controls="terms-of-use" class="text-gray-300 hover:text-white">Terms of use</a>
</li>
<li class="mb-2">
<a data-drawer-target="privacy" data-drawer-show="privacy" aria-controls="privacy" class="text-gray-300 hover:text-white">Privacy statement</a>
</li>
</ul>
</div>
<div class="w-full md:w-1/3">
<h3 class="text-xl font-semibold mb-4">Contact Us</h3>
<p class="mb-2">1001 Blucher Street</p>
<p class="mb-2">Corpus Christi, TX 78401</p>
<p class="mb-4">United States</p>
<p class="mb-2">1-800-840-6025</p>
<p class="mb-2">info&#64;bizmatch.net</p>
</div>
</div>
<div class="pt-4 text-center">
<p class="text-sm text-gray-400 mt-4">&copy; 2025 BizMatch. All rights reserved.</p>
</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">

View File

@ -1,11 +1,7 @@
:host {
// position: absolute;
// bottom: 0px;
width: 100%;
}
div {
font-size: small;
}
@media (max-width: 1023px) {
.order-2 {
order: 2;

View File

@ -15,10 +15,12 @@ export class FooterComponent {
privacyVisible = false;
termsVisible = false;
currentYear: number = new Date().getFullYear();
isHomeRoute = false;
constructor(private router: Router) {}
ngOnInit() {
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.isHomeRoute = event.url === '/home';
initFlowbite();
}
});

View File

@ -1,5 +1,5 @@
<nav class="bg-white border-gray-200 dark:bg-gray-900 print:hidden">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<div class="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-8" alt="Flowbite Logo" />
</a>

View File

@ -1,183 +1,243 @@
<header class="w-full flex justify-between items-center p-4 bg-white top-0 z-10 h-16 md:h-20">
<img src="assets/images/header-logo.png" alt="Logo" class="h-8 md:h-10" />
<div class="hidden md:flex items-center space-x-4">
@if(user){
<a routerLink="/account" class="text-blue-600 border border-blue-600 px-3 py-2 rounded">Account</a>
} @else {
<!-- <a routerLink="/pricing" class="text-gray-800">Pricing</a> -->
<a routerLink="/login" [queryParams]="{ mode: 'login' }" class="text-blue-600 border border-blue-600 px-3 py-2 rounded">Log In</a>
<a routerLink="/login" [queryParams]="{ mode: 'register' }" class="text-white bg-blue-600 px-4 py-2 rounded">Register</a>
<!-- <a routerLink="/login" class="text-blue-500 hover:underline">Login/Register</a> -->
}
<!-- Navigation -->
<nav class="bg-white">
<div class="container mx-auto px-6 py-3 flex justify-between items-center">
<div class="flex items-center">
<a href="#" class="text-2xl font-bold text-blue-800">
<img src="assets/images/header-logo.png" alt="BizMatch.net" class="h-10" />
</a>
</div>
<button (click)="toggleMenu()" class="md:hidden text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16m-7 6h7"></path>
</svg>
</button>
</header>
<div *ngIf="isMenuOpen" class="fixed inset-0 bg-gray-800 bg-opacity-75 z-20">
<div class="flex flex-col items-center justify-center h-full">
<!-- <a href="#" class="text-white text-xl py-2">Pricing</a> -->
@if(user){
<a routerLink="/account" class="text-white text-xl py-2">Account</a>
} @else {
<a routerLink="/login" [queryParams]="{ mode: 'login' }" class="text-white text-xl py-2">Log In</a>
<a routerLink="/login" [queryParams]="{ mode: 'register' }" class="text-white text-xl py-2">Register</a>
}
<button (click)="toggleMenu()" class="text-white mt-4">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12"></path>
<div class="hidden md:flex items-center space-x-8">
<a href="#" class="text-gray-800 hover:text-blue-600">Home</a>
<a routerLink="/businessListings" class="text-blue-700 hover:font-bold">Businesses</a>
<a href="#services" class="text-gray-800 hover:text-blue-600">Services</a>
<a href="#location" class="text-gray-800 hover:text-blue-600">Location</a>
<a href="#contact" class="text-gray-800 hover:text-blue-600">Contact</a>
</div>
<div class="md:hidden">
<button class="text-gray-800 focus:outline-none" (click)="toggleMobileMenu()">
<svg class="h-6 w-6 fill-current" viewBox="0 0 24 24">
<path d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z"></path>
</svg>
</button>
</div>
</div>
<!-- ==== ANPASSUNGEN START ==== -->
<!-- 1. px-4 für <main> (vorher px-2 sm:px-4) -->
<main class="flex flex-col items-center justify-center px-4 w-full flex-grow">
<div class="bg-cover-custom py-12 md:py-20 lg:py-32 flex flex-col w-full rounded-xl lg:rounded-2xl md:drop-shadow-custom-md lg:drop-shadow-custom-lg min-h-[calc(100vh_-_20rem)] lg:min-h-[calc(100vh_-_10rem)]">
<div class="flex justify-center w-full">
<!-- 3. Für Mobile: m-2 statt max-w-xs; ab sm: wieder max-width und kein Margin -->
<div class="w-full m-2 sm:m-0 sm:max-w-md md:max-w-xl lg:max-w-2xl xl:max-w-3xl">
<!-- Schriftgrößen angepasst (wie vorher) -->
<h1 class="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-blue-900 mb-3 sm:mb-4 text-center">Find businesses for sale.</h1>
<!-- Abstand unten angepasst (wie vorher) -->
<p class="text-base md:text-lg lg:text-xl text-blue-600 mb-6 md:mb-8 text-center">Unlocking Exclusive Opportunities - Empowering Entrepreneurial Dreams</p>
<!-- Restliche Anpassungen (Innenabstände, Button-Paddings etc.) bleiben wie im vorherigen Schritt -->
<div class="bg-white bg-opacity-80 pb-4 md:pb-6 pt-2 px-3 sm:px-4 md:px-6 rounded-lg shadow-lg w-full" [ngClass]="{ 'pt-6': aiSearch }">
@if(!aiSearch){
<div class="text-sm lg:text-base mb-1 text-center text-gray-500 border-gray-200 dark:text-gray-400 dark:border-gray-700 flex justify-between">
<ul class="flex flex-wrap -mb-px w-full">
<li class="w-[33%]">
<a
(click)="changeTab('business')"
[ngClass]="
activeTabAction === 'business'
? ['text-blue-600', 'border-blue-600', 'active', 'dark:text-blue-500', 'dark:border-blue-500']
: ['border-transparent', 'hover:text-gray-600', 'hover:border-gray-300', 'dark:hover:text-gray-300']
"
class="hover:cursor-pointer inline-block px-1 py-2 md:p-4 border-b-2 rounded-t-lg"
>Businesses</a
>
</li>
@if ((numberOfCommercial$ | async) > 0) {
<li class="w-[33%]">
<a
(click)="changeTab('commercialProperty')"
[ngClass]="
activeTabAction === 'commercialProperty'
? ['text-blue-600', 'border-blue-600', 'active', 'dark:text-blue-500', 'dark:border-blue-500']
: ['border-transparent', 'hover:text-gray-600', 'hover:border-gray-300', 'dark:hover:text-gray-300']
"
class="hover:cursor-pointer inline-block px-1 py-2 md:p-4 border-b-2 rounded-t-lg"
>Properties</a
>
</li>
} @if ((numberOfBroker$ | async) > 0) {
<li class="w-[33%]">
<a
(click)="changeTab('broker')"
[ngClass]="
activeTabAction === 'broker'
? ['text-blue-600', 'border-blue-600', 'active', 'dark:text-blue-500', 'dark:border-blue-500']
: ['border-transparent', 'hover:text-gray-600', 'hover:border-gray-300', 'dark:hover:text-gray-300']
"
class="hover:cursor-pointer inline-block px-1 py-2 md:p-4 border-b-2 rounded-t-lg"
>Professionals</a
>
</li>
}
</ul>
</div>
} @if(criteria && !aiSearch){
<div class="w-full max-w-3xl mx-auto bg-white rounded-lg flex flex-col md:flex-row md:border md:border-gray-300">
<div class="md:flex-none md:w-48 flex-1 md:border-r border-gray-300 overflow-hidden mb-2 md:mb-0">
<div class="relative max-sm:border border-gray-300 rounded-md">
<select
class="appearance-none bg-transparent w-full py-3 px-4 pr-8 focus:outline-none md:border-none rounded-md md:rounded-none"
[ngModel]="criteria.types"
(ngModelChange)="onTypesChange($event)"
[ngClass]="{ 'placeholder-selected': criteria.types.length === 0 }"
>
<option [value]="[]">{{ getPlaceholderLabel() }}</option>
@for(type of getTypes(); track type){
<option [value]="type.value">{{ type.name }}</option>
}
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<i class="fas fa-chevron-down text-xs"></i>
<!-- Mobile menu (only shows when toggleMobileMenu is true) -->
<div *ngIf="showMobileMenu" class="md:hidden bg-white py-2 px-4">
<a href="#" class="block py-2 text-gray-800 hover:text-blue-600">Home</a>
<a href="#services" class="block py-2 text-gray-800 hover:text-blue-600">Services</a>
<a href="#location" class="block py-2 text-gray-800 hover:text-blue-600">Location</a>
<a href="#contact" class="block py-2 text-gray-800 hover:text-blue-600">Contact</a>
</div>
</nav>
<!-- Hero Section (made narrower) -->
<section class="hero-section flex items-center px-[2rem] py-[5rem]">
<div class="container mx-auto px-6 flex flex-col">
<!-- max-w-5xl makes it narrower -->
<div class="flex flex-col md:flex-row items-center">
<div class="md:w-1/2 text-white">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold leading-tight mb-4">Connect with Your Ideal Business Opportunity</h1>
<p class="text-xl mb-8">BizMatch is your trusted partner in buying, selling, and valuing businesses in Texas.</p>
</div>
<div class="md:w-1/2 flex justify-center">
<img src="assets/images/corpusChristiSkyline.jpg" alt="Business handshake" class="rounded-lg shadow-2xl" />
</div>
</div>
<div class="flex justify-center mt-10">
<a routerLink="/businessListings" class="bg-green-500 md:text-2xl text-lg text-white font-semibold px-8 py-4 rounded-full shadow-lg hover:bg-green-600 transition duration-300"> View Available Businesses </a>
</div>
</div>
</section>
<!-- Services Section -->
<section id="services" class="py-20 bg-gray-50">
<div class="container mx-auto px-6">
<div class="text-center mb-16">
<h2 class="text-3xl font-bold text-blue-800 mb-4">Our Services</h2>
<p class="text-gray-600 max-w-2xl mx-auto">We offer comprehensive business brokerage services to help you navigate the complex process of buying or selling a business.</p>
</div>
<div class="flex flex-wrap -mx-4">
<!-- Service 1 -->
<div class="w-full md:w-1/3 px-4 mb-8">
<div class="service-card bg-white rounded-lg filter md:drop-shadow-custom-bg drop-shadow-custom-bg-mobile p-8 h-full">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-6 mx-auto">
<svg class="w-8 h-8 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path
d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"
></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-blue-800 mb-4 text-center">Business Sales</h3>
<p class="text-gray-600 text-center">We help business owners prepare and market their businesses to qualified buyers, ensuring confidentiality throughout the process.</p>
</div>
</div>
<!-- Service 2 -->
<div class="w-full md:w-1/3 px-4 mb-8">
<div class="service-card bg-white rounded-lg filter md:drop-shadow-custom-bg drop-shadow-custom-bg-mobile p-8 h-full">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-6 mx-auto">
<svg class="w-8 h-8 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 4a2 2 0 00-2 2v4a2 2 0 002 2V6h10a2 2 0 00-2-2H4zm2 6a2 2 0 012-2h8a2 2 0 012 2v4a2 2 0 01-2 2H8a2 2 0 01-2-2v-4zm6 4a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-blue-800 mb-4 text-center">Business Acquisitions</h3>
<p class="text-gray-600 text-center">We assist buyers in finding the right business opportunity, perform due diligence, and negotiate favorable terms for acquisition.</p>
</div>
</div>
<!-- Service 3 -->
<div class="w-full md:w-1/3 px-4 mb-8">
<div class="service-card bg-white rounded-lg filter md:drop-shadow-custom-bg drop-shadow-custom-bg-mobile p-8 h-full">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-6 mx-auto">
<svg class="w-8 h-8 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 01-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zM12 2a1 1 0 01.967.744L14.146 7.2 17.5 9.134a1 1 0 010 1.732l-3.354 1.935-1.18 4.455a1 1 0 01-1.933 0L9.854 12.8 6.5 10.866a1 1 0 010-1.732l3.354-1.935 1.18-4.455A1 1 0 0112 2z"
clip-rule="evenodd"
></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-blue-800 mb-4 text-center">Business Valuation</h3>
<p class="text-gray-600 text-center">Our expert team provides accurate business valuations based on industry standards, financial performance, and market conditions.</p>
</div>
</div>
</div>
<div class="md:flex-auto md:w-36 flex-grow md:border-r border-gray-300 mb-2 md:mb-0">
<div class="relative max-sm:border border-gray-300 rounded-md">
<ng-select
class="custom md:border-none rounded-md md:rounded-none"
[multiple]="false"
[hideSelected]="true"
[trackByFn]="trackByFn"
[minTermLength]="2"
[loading]="cityLoading"
typeToSearchText="Please enter 2 or more characters"
[typeahead]="cityInput$"
[ngModel]="cityOrState"
(ngModelChange)="setCityOrState($event)"
placeholder="Enter City or State ..."
groupBy="type"
<!-- Video Section -->
<div class="mt-16 text-center">
<h3 class="text-2xl font-semibold text-blue-800 mb-8">See How We Work</h3>
<div class="max-w-4xl mx-auto">
<video controls class="w-full rounded-lg shadow-xl" poster="assets/images/video-poster.png">
<source src="assets/videos/Bizmatch30Spot.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
</section>
<!-- Why Choose Us Section -->
<section class="py-20 bg-white">
<div class="container mx-auto px-6">
<div class="text-center mb-16">
<h2 class="text-3xl font-bold text-blue-800 mb-4">Why Choose BizMatch</h2>
<p class="text-gray-600 max-w-2xl mx-auto">With decades of experience in the business brokerage industry, we provide unparalleled service to our clients.</p>
</div>
<div class="flex flex-wrap -mx-4">
<!-- Feature 1 -->
<div class="w-full md:w-1/4 px-4 mb-8">
<div class="text-center">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-6 mx-auto">
<svg class="w-8 h-8 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-blue-800 mb-2">Experience</h3>
<p class="text-gray-600">Over 25 years of combined experience in business brokerage.</p>
</div>
</div>
<!-- Feature 2 -->
<div class="w-full md:w-1/4 px-4 mb-8">
<div class="text-center">
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mb-6 mx-auto">
<svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-blue-800 mb-2">Confidentiality</h3>
<p class="text-gray-600">We maintain strict confidentiality throughout the entire transaction process.</p>
</div>
</div>
<!-- Feature 3 -->
<div class="w-full md:w-1/4 px-4 mb-8">
<div class="text-center">
<div class="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mb-6 mx-auto">
<svg class="w-8 h-8 text-purple-600" fill="currentColor" viewBox="0 0 20 20">
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-blue-800 mb-2">Network</h3>
<p class="text-gray-600">Extensive network of qualified buyers and business owners throughout Texas.</p>
</div>
</div>
<!-- Feature 4 -->
<div class="w-full md:w-1/4 px-4 mb-8">
<div class="text-center">
<div class="w-16 h-16 bg-yellow-100 rounded-full flex items-center justify-center mb-6 mx-auto">
<svg class="w-8 h-8 text-yellow-600" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z"
clip-rule="evenodd"
></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-blue-800 mb-2">Personalized Approach</h3>
<p class="text-gray-600">Customized strategy for each client based on their unique business goals.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Location Section with Map -->
<section id="location" class="py-20 bg-gray-50">
<div class="container mx-auto px-6">
<div class="flex flex-wrap items-center">
<div class="w-full lg:w-2/5 mb-12 lg:mb-0">
<!-- Changed from 1/2 to 2/5 to make it smaller -->
<h2 class="text-3xl font-bold text-blue-800 mb-6">Visit Our Office</h2>
<p class="text-gray-600 mb-8 text-lg">Our team of business brokers is ready to assist you at our Corpus Christi location.</p>
<div class="bg-white p-6 rounded-lg shadow-lg">
<h3 class="text-xl font-semibold text-blue-800 mb-4">BizMatch Headquarters</h3>
<p class="text-gray-600 mb-2">1001 Blucher Street</p>
<p class="text-gray-600 mb-2">Corpus Christi, TX 78401</p>
<p class="text-gray-600 mb-6">United States</p>
<p class="text-gray-600 mb-2"><strong>Phone:</strong> (555) 123-4567</p>
<p class="text-gray-600"><strong>Email:</strong> info&#64;bizmatch.net</p>
</div>
</div>
<div class="w-full lg:w-3/5">
<!-- Changed from 1/2 to 3/5 for the map -->
<div class="rounded-lg overflow-hidden shadow-xl h-96">
<!-- Added fixed height -->
<!-- Google Maps integration -->
<!-- <google-map [options]="mapOptions" height="100%" width="100%">
<map-marker [position]="markerPosition"></map-marker>
</google-map> -->
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3331.7894679685755!2d-98.48527228476843!3d27.773756032788047!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x866c1e3b8a9d0c0b%3A0x8f2c1d4c1a5c5b2c!2s1001%20Blucher%20St%2C%20Corpus%20Christi%2C%20TX%2078401%2C%20USA!5e0!3m2!1sen!2sde!4v1672531192743!5m2!1sen!2sde"
width="100%"
height="417"
class="rounded-lg shadow-lg"
style="border: 0"
allowfullscreen=""
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
>
@for (city of cities$ | async; track city.id) { @let state = city.type==='city'?city.content.state:''; @let separator = city.type==='city'?' - ':'';
<ng-option [value]="city">{{ city.content.name }}{{ separator }}{{ state }}</ng-option>
}
</ng-select>
</div>
</div>
@if (criteria.radius && !aiSearch){
<div class="md:flex-none md:w-36 flex-1 md:border-r border-gray-300 mb-2 md:mb-0">
<div class="relative max-sm:border border-gray-300 rounded-md">
<select
class="appearance-none bg-transparent w-full py-3 px-4 pr-8 focus:outline-none md:border-none rounded-md md:rounded-none"
(ngModelChange)="onRadiusChange($event)"
[ngModel]="criteria.radius"
[ngClass]="{ 'placeholder-selected': !criteria.radius }"
>
<option [value]="null">City Radius</option>
@for(dist of selectOptions.distances; track dist){
<option [value]="dist.value">{{ dist.name }}</option>
}
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<i class="fas fa-chevron-down text-xs"></i>
</div>
</div>
</div>
}
<div class="bg-blue-600 hover:bg-blue-500 transition-colors duration-200 max-sm:rounded-md">
@if(getNumberOfFiltersSet()>0 && numberOfResults$){
<button class="w-full h-full text-white font-semibold py-2 px-4 md:py-3 md:px-6 focus:outline-none rounded-md md:rounded-none min-h-[48px]" (click)="search()">
Search ({{ numberOfResults$ | async }})
</button>
}@else {
<button class="w-full h-full text-white font-semibold py-2 px-4 md:py-3 md:px-6 focus:outline-none rounded-md md:rounded-none min-h-[48px]" (click)="search()">Search</button>
}
</div>
</div>
}
<!-- <div class="mt-4 flex items-center justify-center text-gray-700">
<span class="mr-2">AI-Search</span>
<span [attr.data-tooltip-target]="tooltipTargetBeta" class="bg-sky-300 text-teal-800 text-xs font-semibold px-2 py-1 rounded">BETA</span>
<app-tooltip [id]="tooltipTargetBeta" text="AI will convert your input into filter criteria. Please check them in the filter menu after search"></app-tooltip>
<span class="ml-2">- Try now</span>
<div class="ml-4 relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
<input (click)="toggleAiSearch()" type="checkbox" name="toggle" id="toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 border-gray-300 appearance-none cursor-pointer" />
<label for="toggle" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
</div>
</div> -->
</iframe>
</div>
</div>
</div>
</div>
</main>
<!-- ==== ANPASSUNGEN ENDE ==== -->
</section>
<!-- Contact Section -->
<section id="contact" class="py-20 bg-blue-700">
<div class="container mx-auto px-6 text-center">
<h2 class="text-3xl font-bold text-white mb-8">Ready to Get Started?</h2>
<p class="text-white text-xl mb-12 max-w-3xl mx-auto">Contact our team of experienced business brokers today for a confidential consultation about buying or selling a business.</p>
<a routerLink="/emailUs" class="bg-white text-blue-700 font-bold px-8 py-4 rounded-lg shadow-lg hover:bg-gray-100 transition duration-300 text-lg">Contact Us Now</a>
</div>
</section>
<!-- Footer -->

View File

@ -1,72 +1,45 @@
.bg-cover-custom {
background-image: url('/assets/images/index-bg.webp');
background-size: cover;
background-position: center;
border-radius: 20px;
// Hero section styles
.hero-section {
background: linear-gradient(135deg, #0046b5 0%, #00a0e9 100%);
// height: 70vh; // Made shorter as requested
// min-height: 500px; // Reduced from 600px
}
select:not([size]) {
background-image: unset;
}
[type='text'],
[type='email'],
[type='url'],
[type='password'],
[type='number'],
[type='date'],
[type='datetime-local'],
[type='month'],
[type='search'],
[type='tel'],
[type='time'],
[type='week'],
[multiple],
textarea,
select {
border: unset;
}
.toggle-checkbox:checked {
right: 0;
border-color: rgb(125 211 252);
}
.toggle-checkbox:checked + .toggle-label {
background-color: rgb(125 211 252);
}
:host ::ng-deep .ng-select.ng-select-single .ng-select-container {
height: 48px;
border: none;
background-color: transparent;
.ng-value-container .ng-input {
top: 10px;
}
span.ng-arrow-wrapper {
display: none;
// Button hover effects
.btn-primary {
background-color: #0046b5;
transition: all 0.3s ease;
&:hover {
background-color: #003492;
}
}
select {
color: #000; /* Standard-Textfarbe für das Dropdown */
// background-color: #fff; /* Hintergrundfarbe für das Dropdown */
// Service card animation
.service-card {
transition: all 0.3s ease;
&:hover {
transform: translateY(-10px);
}
}
select option {
color: #000; /* Textfarbe für Dropdown-Optionen */
// Responsive adjustments
@media (max-width: 768px) {
.hero-section {
height: auto;
padding: 4rem 0;
}
}
select.placeholder-selected {
color: #999; /* Farbe für den Platzhalter */
}
input::placeholder {
color: #555; /* Dunkleres Grau */
opacity: 1; /* Stellt sicher, dass die Deckkraft 100% ist */
// Make sure the Google Map is responsive
google-map {
display: block;
width: 100%;
}
/* Stellt sicher, dass die Optionen im Dropdown immer schwarz sind */
select:focus option,
select:hover option {
color: #000 !important;
}
input[type='text'][name='aiSearchText'] {
padding: 14px; /* Innerer Abstand */
font-size: 16px; /* Schriftgröße anpassen */
box-sizing: border-box; /* Padding und Border in die Höhe und Breite einrechnen */
height: 48px;
// Override Tailwind default styling for video
video {
max-width: 100%;
object-fit: cover;
}

View File

@ -1,313 +1,56 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component, ElementRef, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { NgSelectModule } from '@ng-select/ng-select';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { initFlowbite } from 'flowbite';
import { catchError, concat, debounceTime, distinctUntilChanged, lastValueFrom, Observable, of, Subject, Subscription, switchMap, tap } from 'rxjs';
import { BusinessListingCriteria, CityAndStateResult, CommercialPropertyListingCriteria, GeoResult, KeycloakUser, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
import { ModalService } from '../../components/search-modal/modal.service';
import { TooltipComponent } from '../../components/tooltip/tooltip.component';
import { AiService } from '../../services/ai.service';
import { AuthService } from '../../services/auth.service';
import { CriteriaChangeService } from '../../services/criteria-change.service';
import { GeoService } from '../../services/geo.service';
import { ListingsService } from '../../services/listings.service';
import { SearchService } from '../../services/search.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { UserService } from '../../services/user.service';
import {
assignProperties,
compareObjects,
createEmptyBusinessListingCriteria,
createEmptyCommercialPropertyListingCriteria,
createEmptyUserListingCriteria,
createEnhancedProxy,
getCriteriaStateObject,
map2User,
} from '../../utils/utils';
@UntilDestroy()
import { Component, OnInit } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-home',
standalone: true,
imports: [CommonModule, FormsModule, RouterModule, NgSelectModule, TooltipComponent],
templateUrl: './home.component.html',
styleUrl: './home.component.scss',
styleUrls: ['./home.component.scss'],
standalone: true,
imports: [CommonModule, RouterOutlet, RouterLink],
})
export class HomeComponent {
placeholders: string[] = ['Property close to Houston less than 10M', 'Franchise business in Austin price less than 500K'];
activeTabAction: 'business' | 'commercialProperty' | 'broker' = 'business';
type: string;
maxPrice: string;
minPrice: string;
criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
states = [];
isMenuOpen = false;
user: KeycloakUser;
prompt: string;
cities$: Observable<CityAndStateResult[]>;
cityLoading = false;
cityInput$ = new Subject<string>();
cityOrState = undefined;
private criteriaChangeSubscription: Subscription;
numberOfResults$: Observable<number>;
numberOfBroker$: Observable<number>;
numberOfCommercial$: Observable<number>;
aiSearch = false;
aiSearchText = '';
aiSearchFailed = false;
loadingAi = false;
@ViewChild('aiSearchInput', { static: false }) searchInput!: ElementRef;
typingSpeed: number = 100; // Geschwindigkeit des Tippens (ms)
pauseTime: number = 2000; // Pausezeit, bevor der Text verschwindet (ms)
index: number = 0;
charIndex: number = 0;
typingInterval: any;
showInput: boolean = true; // Steuerung der Anzeige des Eingabefelds
tooltipTargetBeta = 'tooltipTargetBeta';
public constructor(
private router: Router,
private modalService: ModalService,
private searchService: SearchService,
private activatedRoute: ActivatedRoute,
public selectOptions: SelectOptionsService,
export class HomeComponent implements OnInit {
showMobileMenu = false;
private criteriaChangeService: CriteriaChangeService,
private geoService: GeoService,
public cdRef: ChangeDetectorRef,
private listingService: ListingsService,
private userService: UserService,
private aiService: AiService,
private authService: AuthService,
) {}
async ngOnInit() {
setTimeout(() => {
initFlowbite();
}, 0);
this.numberOfBroker$ = this.userService.getNumberOfBroker(createEmptyUserListingCriteria());
this.numberOfCommercial$ = this.listingService.getNumberOfListings(createEmptyCommercialPropertyListingCriteria(), 'commercialProperty');
const token = await this.authService.getToken();
sessionStorage.removeItem('businessListings');
sessionStorage.removeItem('commercialPropertyListings');
sessionStorage.removeItem('brokerListings');
this.criteria = createEnhancedProxy(getCriteriaStateObject('businessListings'), this);
this.user = map2User(token);
this.loadCities();
this.setupCriteriaChangeListener();
}
async changeTab(tabname: 'business' | 'commercialProperty' | 'broker') {
this.activeTabAction = tabname;
this.cityOrState = null;
if ('business' === tabname) {
this.criteria = createEnhancedProxy(getCriteriaStateObject('businessListings'), this);
} else if ('commercialProperty' === tabname) {
this.criteria = createEnhancedProxy(getCriteriaStateObject('commercialPropertyListings'), this);
} else if ('broker' === tabname) {
this.criteria = createEnhancedProxy(getCriteriaStateObject('brokerListings'), this);
} else {
this.criteria = undefined;
}
// Google Maps configuration
// mapOptions: google.maps.MapOptions = {
// center: { lat: 27.777, lng: -97.396 }, // Corpus Christi coordinates
// zoom: 15,
// mapTypeId: 'roadmap',
// scrollwheel: false,
// disableDefaultUI: true,
// zoomControl: true,
// streetViewControl: true,
// };
// Marker position for office location
// markerPosition: google.maps.LatLngLiteral = {
// lat: 27.777,
// lng: -97.396,
// };
constructor() {}
ngOnInit(): void {
// Add smooth scrolling for anchor links
this.setupSmoothScrolling();
}
search() {
this.router.navigate([`${this.activeTabAction}Listings`]);
}
private setupCriteriaChangeListener() {
this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(untilDestroyed(this), debounceTime(400)).subscribe(() => this.setTotalNumberOfResults());
toggleMobileMenu(): void {
this.showMobileMenu = !this.showMobileMenu;
}
toggleMenu() {
this.isMenuOpen = !this.isMenuOpen;
}
onTypesChange(value) {
if (value === '') {
// Wenn keine Option ausgewählt ist, setzen Sie types zurück auf ein leeres Array
this.criteria.types = [];
} else {
this.criteria.types = [value];
}
}
onRadiusChange(value) {
if (value === 'null') {
// Wenn keine Option ausgewählt ist, setzen Sie types zurück auf ein leeres Array
this.criteria.radius = null;
} else {
this.criteria.radius = parseInt(value);
}
}
async openModal() {
const accepted = await this.modalService.showModal(this.criteria);
if (accepted) {
this.router.navigate([`${this.activeTabAction}Listings`]);
}
}
private loadCities() {
this.cities$ = concat(
of([]), // default items
this.cityInput$.pipe(
distinctUntilChanged(),
tap(() => (this.cityLoading = true)),
switchMap(term =>
//this.geoService.findCitiesStartingWith(term).pipe(
this.geoService.findCitiesAndStatesStartingWith(term).pipe(
catchError(() => of([])), // empty list on error
// map(cities => cities.map(city => city.city)), // transform the list of objects to a list of city names
tap(() => (this.cityLoading = false)),
),
),
),
);
}
trackByFn(item: GeoResult) {
return item.id;
}
setCityOrState(cityOrState: CityAndStateResult) {
if (cityOrState) {
if (cityOrState.type === 'state') {
this.criteria.state = cityOrState.content.state_code;
} else {
this.criteria.city = cityOrState.content as GeoResult;
this.criteria.state = cityOrState.content.state;
this.criteria.searchType = 'radius';
this.criteria.radius = 20;
}
} else {
this.criteria.state = null;
this.criteria.city = null;
this.criteria.radius = null;
this.criteria.searchType = 'exact';
}
}
getTypes() {
if (this.criteria.criteriaType === 'businessListings') {
return this.selectOptions.typesOfBusiness;
} else if (this.criteria.criteriaType === 'commercialPropertyListings') {
return this.selectOptions.typesOfCommercialProperty;
} else {
return this.selectOptions.customerSubTypes;
}
}
getPlaceholderLabel() {
if (this.criteria.criteriaType === 'businessListings') {
return 'Business Type';
} else if (this.criteria.criteriaType === 'commercialPropertyListings') {
return 'Property Type';
} else {
return 'Professional Type';
}
}
setTotalNumberOfResults() {
if (this.criteria) {
console.log(`Getting total number of results for ${this.criteria.criteriaType}`);
if (this.criteria.criteriaType === 'businessListings' || this.criteria.criteriaType === 'commercialPropertyListings') {
this.numberOfResults$ = this.listingService.getNumberOfListings(this.criteria, this.criteria.criteriaType === 'businessListings' ? 'business' : 'commercialProperty');
} else if (this.criteria.criteriaType === 'brokerListings') {
this.numberOfResults$ = this.userService.getNumberOfBroker(this.criteria);
} else {
this.numberOfResults$ = of();
}
}
}
getNumberOfFiltersSet() {
if (this.criteria?.criteriaType === 'brokerListings') {
return compareObjects(createEmptyUserListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']);
} else if (this.criteria?.criteriaType === 'businessListings') {
return compareObjects(createEmptyBusinessListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']);
} else if (this.criteria?.criteriaType === 'commercialPropertyListings') {
return compareObjects(createEmptyCommercialPropertyListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']);
} else {
return 0;
}
}
toggleAiSearch() {
this.aiSearch = !this.aiSearch;
this.aiSearchFailed = false;
if (!this.aiSearch) {
this.aiSearchText = '';
this.stopTypingEffect();
} else {
setTimeout(() => this.startTypingEffect(), 0);
}
}
ngOnDestroy(): void {
clearTimeout(this.typingInterval); // Stelle sicher, dass das Intervall gestoppt wird, wenn die Komponente zerstört wird
}
startTypingEffect(): void {
if (!this.aiSearchText) {
this.typePlaceholder();
}
}
stopTypingEffect(): void {
clearTimeout(this.typingInterval);
}
typePlaceholder(): void {
if (!this.searchInput || !this.searchInput.nativeElement) {
return; // Falls das Eingabefeld nicht verfügbar ist (z.B. durch ngIf)
}
if (this.aiSearchText) {
return; // Stoppe, wenn der Benutzer Text eingegeben hat
}
const inputField = this.searchInput.nativeElement as HTMLInputElement;
if (document.activeElement === inputField) {
this.stopTypingEffect();
return;
}
inputField.placeholder = this.placeholders[this.index].substring(0, this.charIndex);
if (this.charIndex < this.placeholders[this.index].length) {
this.charIndex++;
this.typingInterval = setTimeout(() => this.typePlaceholder(), this.typingSpeed);
} else {
// Nach dem vollständigen Tippen eine Pause einlegen
this.typingInterval = setTimeout(() => {
inputField.placeholder = ''; // Schlagartiges Löschen des Platzhalters
this.charIndex = 0;
this.index = (this.index + 1) % this.placeholders.length;
this.typingInterval = setTimeout(() => this.typePlaceholder(), this.typingSpeed);
}, this.pauseTime);
}
}
async generateAiResponse() {
this.loadingAi = true;
this.aiSearchFailed = false;
try {
const result = await this.aiService.generateAiReponse(this.aiSearchText);
let criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria | any;
if (result.criteriaType === 'businessListings') {
this.changeTab('business');
criteria = result as BusinessListingCriteria;
} else if (result.criteriaType === 'commercialPropertyListings') {
this.changeTab('commercialProperty');
criteria = result as CommercialPropertyListingCriteria;
} else {
this.changeTab('broker');
criteria = result as UserListingCriteria;
}
const city = criteria.city as string;
if (city && city.length > 0) {
let results = await lastValueFrom(this.geoService.findCitiesStartingWith(city, criteria.state));
if (results.length > 0) {
criteria.city = results[0];
} else {
criteria.city = null;
}
}
if (criteria.radius && criteria.radius.length > 0) {
criteria.radius = parseInt(criteria.radius);
}
this.loadingAi = false;
this.criteria = assignProperties(this.criteria, criteria);
this.search();
} catch (error) {
console.log(error);
this.aiSearchFailed = true;
this.loadingAi = false;
private setupSmoothScrolling(): void {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector((this as HTMLAnchorElement).getAttribute('href') || '');
if (target) {
target.scrollIntoView({
behavior: 'smooth',
});
}
});
});
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.