This commit is contained in:
Andreas Knuth 2025-04-06 21:49:44 +02:00
parent 7d64ee11bf
commit 466e1dcdce
44 changed files with 1780 additions and 1520 deletions

View File

@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --host 0.0.0.0",
"start": "ng serve --host 0.0.0.0 & http-server ../bizmatch-server",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
@ -12,6 +12,7 @@
"private": true,
"dependencies": {
"@angular/animations": "^19.2.5",
"@angular/cdk": "^19.2.8",
"@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0",
@ -33,7 +34,6 @@
"browser-bunyan": "^1.8.0",
"dayjs": "^1.11.13",
"express": "^4.18.2",
"flowbite": "^3.1.2",
"jwt-decode": "^4.0.0",
"ngx-currency": "^19.0.0",
"ngx-image-cropper": "^9.1.5",
@ -55,6 +55,7 @@
"@types/express": "^4.17.17",
"@types/jasmine": "~5.1.0",
"@types/node": "^18.18.0",
"http-server": "^14.1.1",
"jasmine-core": "~5.6.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",

View File

@ -3,12 +3,14 @@ import { APP_INITIALIZER, ApplicationConfig, provideZoneChangeDetection } from '
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { provideRouter, withEnabledBlockingInitialNavigation, withInMemoryScrolling } from '@angular/router';
import { provideShareButtonsOptions } from 'ngx-sharebuttons';
import { shareIcons } from 'ngx-sharebuttons/icons';
import { environment } from '../environments/environment';
import { routes } from './app.routes';
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { LoadingInterceptor } from './interceptors/loading.interceptor';
import { TimeoutInterceptor } from './interceptors/timeout.interceptor';
import { SelectOptionsService } from './services/select-options.service';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptorsFromDi()),
@ -28,6 +30,8 @@ export const appConfig: ApplicationConfig = {
useClass: TimeoutInterceptor,
multi: true,
},
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
provideShareButtonsOptions(shareIcons()),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(
routes,

View File

@ -1,14 +1,43 @@
:host {
width: 100%;
// Drawer animation styles
.translate-x-0 {
transform: translateX(0);
}
@media (max-width: 1023px) {
.order-2 {
order: 2;
.translate-x-full {
transform: translateX(100%);
}
// Custom scrollbar for drawers
.overflow-y-auto {
&::-webkit-scrollbar {
width: 6px;
}
.order-3 {
order: 3;
&::-webkit-scrollbar-track {
background-color: #f1f1f1;
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background-color: #888;
border-radius: 10px;
}
&::-webkit-scrollbar-thumb:hover {
background-color: #555;
}
}
// Fix for iOS Safari elastic scroll behavior
.fixed {
-webkit-overflow-scrolling: touch;
}
// Focus styles for accessibility
button:focus,
a:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
section p {
display: block;

View File

@ -1,9 +1,8 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { initFlowbite } from 'flowbite';
@Component({
selector: 'app-footer',
@ -12,17 +11,62 @@ import { initFlowbite } from 'flowbite';
templateUrl: './footer.component.html',
styleUrl: './footer.component.scss',
})
export class FooterComponent {
export class FooterComponent implements OnInit {
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) {
initFlowbite();
this.isHomeRoute = event.url === '/home';
}
});
// Listen for escape key to close drawers
document.addEventListener('keydown', event => {
if (event.key === 'Escape') {
this.closeDrawers();
}
});
}
// Toggle privacy drawer
togglePrivacy() {
this.termsVisible = false; // Close other drawer if open
this.privacyVisible = !this.privacyVisible;
this.toggleBodyScroll();
}
// Toggle terms drawer
toggleTerms() {
this.privacyVisible = false; // Close other drawer if open
this.termsVisible = !this.termsVisible;
this.toggleBodyScroll();
}
// Close all drawers
closeDrawers() {
this.privacyVisible = false;
this.termsVisible = false;
this.toggleBodyScroll();
}
// Prevent body scroll when drawer is open
private toggleBodyScroll() {
if (this.privacyVisible || this.termsVisible) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
}
// Clean up event listener on component destroy
ngOnDestroy() {
document.removeEventListener('keydown', () => {});
document.body.style.overflow = ''; // Ensure body scroll is restored
}
}

View File

@ -1,7 +1,7 @@
<nav class="bg-white border-gray-200 dark:bg-gray-900 print:hidden">
<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" />
<img src="assets/images/header-logo.png" class="h-10" alt="Logo" />
</a>
<div class="flex items-center md:order-2 space-x-3 rtl:space-x-reverse">
<!-- Filter button -->
@ -36,8 +36,7 @@
</ul>
</div>
</div>
}
<!-- Menu Button ohne Flowbite -->
} @if(!isEmailUsUrl()){
<div class="relative">
<button type="button" class="relative inline-flex justify-center items-center w-8 h-8 rounded-full bg-gray-400 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600" id="user-menu-button" aria-expanded="false" (click)="toggleUserMenu()">
<span class="sr-only">Open user menu</span>
@ -110,6 +109,7 @@
</div>
}
</div>
}
</div>
</div>
<!-- Mobile filter button -->

View File

@ -12,7 +12,6 @@ import { environment } from '../../../environments/environment';
import { SharedService } from '../../services/shared.service';
import { Collapse, Dropdown } from 'flowbite';
import { AuthService } from '../../services/auth.service';
import { CriteriaChangeService } from '../../services/criteria-change.service';
import { ListingsService } from '../../services/listings.service';
@ -37,7 +36,6 @@ export class HeaderComponent {
faUserGear = faUserGear;
profileUrl: string;
env = environment;
private filterDropdown: Dropdown | null = null;
isMobile: boolean = false;
private destroy$ = new Subject<void>();
prompt: string;
@ -158,34 +156,6 @@ export class HeaderComponent {
isProfessionalListing(): boolean {
return ['/brokerListings'].includes(this.router.url);
}
// isSortingUrl(): boolean {
// return ['/businessListings', '/commercialPropertyListings'].includes(this.router.url);
// }
closeDropdown() {
const dropdownButton = document.getElementById('user-menu-button');
const dropdownMenu = this.user ? document.getElementById('user-login') : document.getElementById('user-unknown');
if (dropdownButton && dropdownMenu) {
const dropdown = new Dropdown(dropdownMenu, dropdownButton);
dropdown.hide();
}
}
closeMobileMenu() {
const targetElement = document.getElementById('navbar-user');
const triggerElement = document.querySelector('[data-collapse-toggle="navbar-user"]');
if (targetElement instanceof HTMLElement && triggerElement instanceof HTMLElement) {
const collapse = new Collapse(targetElement, triggerElement);
collapse.collapse();
}
}
closeMenusAndSetCriteria(path: string) {
this.closeDropdown();
this.closeMobileMenu();
const criteria = getCriteriaProxy(path, this);
criteria.page = 1;
criteria.start = 0;
}
ngOnDestroy() {
this.destroy$.next();

View File

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { Component, forwardRef, Input } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask';
import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
import { BaseInputComponent } from '../base-input/base-input.component';
import { TooltipComponent } from '../tooltip/tooltip.component';
import { ValidationMessagesService } from '../validation-messages.service';
@ -10,7 +10,7 @@ import { ValidationMessagesService } from '../validation-messages.service';
selector: 'app-validated-input',
templateUrl: './validated-input.component.html',
standalone: true,
imports: [CommonModule, FormsModule, TooltipComponent, NgxMaskDirective, NgxMaskPipe],
imports: [CommonModule, FormsModule, TooltipComponent, NgxMaskDirective],
providers: [
{
provide: NG_VALUE_ACCESSOR,

View File

@ -1,16 +1 @@
<div>
<label for="type" class="block text-sm font-bold text-gray-700 mb-1 relative w-fit"
>{{ label }} @if(validationMessage){
<div
attr.data-tooltip-target="tooltip-{{ name }}"
class="absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 dark:border-gray-900 hover:cursor-pointer"
(click)="toggleTooltip($event)"
(touchstart)="toggleTooltip($event)"
>
!
</div>
<app-tooltip id="tooltip-{{ name }}" [text]="validationMessage" [isVisible]="isTooltipVisible"></app-tooltip>
}
</label>
<ng-select [items]="items" bindLabel="name" bindValue="value" [(ngModel)]="value" (ngModelChange)="onInputChange($event)" name="type"> </ng-select>
</div>
<ng-select [items]="items" bindLabel="name" bindValue="value" [(ngModel)]="value" (ngModelChange)="onInputChange($event)" name="type"> </ng-select>

View File

@ -1,55 +1,66 @@
<div class="container mx-auto p-4">
@if (user){
<div class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg p-6">
<form #accountForm="ngForm" class="space-y-4">
<h2 class="text-2xl font-bold mb-4">Account Details</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<form #accountForm="ngForm" class="space-y-6">
<h2 class="text-2xl font-bold mb-6">Account Details</h2>
<!-- E-Mail und Bilder -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="md:col-span-2">
<label for="email" class="block text-sm font-medium text-gray-700">E-mail (required)</label>
<input type="email" id="email" name="email" [(ngModel)]="user.email" disabled class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
<label for="email" class="block text-gray-700 mb-2 font-medium">E-mail (required)</label>
<div class="relative">
<input type="email" id="email" name="email" [(ngModel)]="user.email" disabled class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg bg-gray-100 focus:outline-none focus:border-black focus:ring-0" />
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
<p class="text-xs text-gray-500 mt-1">You can only modify your email by contacting us at support&#64;bizmatch.net</p>
</div>
@if (isProfessional || (authService.isAdmin() | async)){
<div class="flex flex-row items-center justify-around md:space-x-4">
<!-- Company Logo -->
<div class="flex h-full justify-between flex-col">
<p class="text-sm font-medium text-gray-700 mb-1">Company Logo</p>
<div class="w-20 h-20 w-full rounded-md flex items-center justify-center relative">
<div class="w-20 h-20 w-full rounded-md flex items-center justify-center relative border border-gray-200 overflow-hidden">
@if(user?.hasCompanyLogo){
<img src="{{ companyLogoUrl }}" alt="Company logo" class="max-w-full max-h-full" />
<img src="{{ companyLogoUrl }}" alt="Company logo" class="max-w-full max-h-full object-cover" />
<div class="absolute top-[-0.5rem] right-[0rem] bg-white rounded-full p-1 drop-shadow-custom-bg hover:cursor-pointer" (click)="deleteConfirm('logo')">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="w-4 h-4 text-gray-600">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
} @else {
<img src="assets/images/placeholder.png" class="max-w-full max-h-full" />
<img src="assets/images/placeholder.png" class="max-w-full max-h-full object-cover" />
}
</div>
<button
type="button"
class="mt-2 w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
class="mt-2 w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:border-black focus:ring-0 transition-colors duration-200"
(click)="uploadCompanyLogo()"
>
Upload
</button>
</div>
<!-- Profile Picture -->
<div class="flex h-full justify-between flex-col">
<p class="text-sm font-medium text-gray-700 mb-1">Your Profile Picture</p>
<div class="w-20 h-20 w-full rounded-md flex items-center justify-center relative">
<div class="w-20 h-20 w-full rounded-md flex items-center justify-center relative border border-gray-200 overflow-hidden">
@if(user?.hasProfile){
<img src="{{ profileUrl }}" alt="Profile picture" class="max-w-full max-h-full" />
<img src="{{ profileUrl }}" alt="Profile picture" class="max-w-full max-h-full object-cover" />
<div class="absolute top-[-0.5rem] right-[0rem] bg-white rounded-full p-1 drop-shadow-custom-bg hover:cursor-pointer" (click)="deleteConfirm('profile')">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="w-4 h-4 text-gray-600">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
} @else {
<img src="assets/images/placeholder.png" class="max-w-full max-h-full" />
<img src="assets/images/placeholder.png" class="max-w-full max-h-full object-cover" />
}
</div>
<button
type="button"
class="mt-2 w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
class="mt-2 w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:border-black focus:ring-0 transition-colors duration-200"
(click)="uploadProfile()"
>
Upload
@ -59,258 +70,481 @@
}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-input label="First Name" name="firstname" [(ngModel)]="user.firstname"></app-validated-input>
<app-validated-input label="Last Name" name="lastname" [(ngModel)]="user.lastname"></app-validated-input>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- <div>
<label for="customerType" class="block text-sm font-medium text-gray-700">Customer Type</label>
<select id="customerType" name="customerType" [(ngModel)]="user.customerType" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option *ngFor="let type of customerTypes" [value]="type">{{ type | titlecase }}</option>
</select>
</div> -->
@if ((authService.isAdmin() | async) && !id){
<!-- Name Fields -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- First Name -->
<div>
<label for="customerType" class="block text-sm font-medium text-gray-700">User Type</label>
<span class="bg-blue-100 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">ADMIN</span>
</div>
}
<app-validated-select [disabled]="true" label="Customer Type" name="customerType" [(ngModel)]="user.customerType" [options]="customerTypeOptions"></app-validated-select>
@if (isProfessional){
<!-- <div>
<label for="customerSubType" class="block text-sm font-medium text-gray-700">Professional Type</label>
<select id="customerSubType" name="customerSubType" [(ngModel)]="user.customerSubType" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
<option *ngFor="let subType of customerSubTypes" [value]="subType">{{ subType | titlecase }}</option>
</select>
</div> -->
<app-validated-select label="Professional Type" name="customerSubType" [(ngModel)]="user.customerSubType" [options]="customerSubTypeOptions"></app-validated-select>
}
</div>
@if (isProfessional){
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- <div>
<label for="companyName" class="block text-sm font-medium text-gray-700">Company Name</label>
<input type="text" id="companyName" name="companyName" [(ngModel)]="user.companyName" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
</div> -->
<!-- <div>
<label for="description" class="block text-sm font-medium text-gray-700">Describe yourself</label>
<input type="text" id="description" name="description" [(ngModel)]="user.description" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
</div> -->
<app-validated-input label="Company Name" name="companyName" [(ngModel)]="user.companyName"></app-validated-input>
<app-validated-input label="Describe Yourself" name="description" [(ngModel)]="user.description"></app-validated-input>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- <div>
<label for="phoneNumber" class="block text-sm font-medium text-gray-700">Your Phone Number</label>
<input type="tel" id="phoneNumber" name="phoneNumber" [(ngModel)]="user.phoneNumber" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
</div>
<div>
<label for="companyWebsite" class="block text-sm font-medium text-gray-700">Company Website</label>
<input type="url" id="companyWebsite" name="companyWebsite" [(ngModel)]="user.companyWebsite" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
</div>
<div>
<label for="companyLocation" class="block text-sm font-medium text-gray-700">Company Location</label>
<input type="text" id="companyLocation" name="companyLocation" [(ngModel)]="user.companyLocation" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
</div> -->
<app-validated-input label="Your Phone Number" name="phoneNumber" [(ngModel)]="user.phoneNumber" mask="(000) 000-0000"></app-validated-input>
<app-validated-input label="Company Website" name="companyWebsite" [(ngModel)]="user.companyWebsite"></app-validated-input>
<!-- <app-validated-input label="Company Location" name="companyLocation" [(ngModel)]="user.companyLocation"></app-validated-input> -->
<!-- <app-validated-city label="Company Location" name="location" [(ngModel)]="user.location"></app-validated-city> -->
<app-validated-location label="Company Location" name="location" [(ngModel)]="user.location"></app-validated-location>
</div>
<!-- <div>
<label for="companyOverview" class="block text-sm font-medium text-gray-700">Company Overview</label>
<quill-editor [(ngModel)]="user.companyOverview" name="companyOverview" [modules]="quillModules"></quill-editor>
</div> -->
<div>
<app-validated-quill label="Company Overview" name="companyOverview" [(ngModel)]="user.companyOverview"></app-validated-quill>
</div>
<div>
<!-- <label for="offeredServices" class="block text-sm font-medium text-gray-700">Services We Offer</label>
<quill-editor [(ngModel)]="user.offeredServices" name="offeredServices" [modules]="quillModules"></quill-editor> -->
<app-validated-quill label="Services We Offer" name="offeredServices" [(ngModel)]="user.offeredServices"></app-validated-quill>
</div>
<div>
<h3 class="text-lg font-medium text-gray-700 mb-2 relative w-fit">
Areas We Serve @if(getValidationMessage('areasServed')){
<div
[attr.data-tooltip-target]="tooltipTargetAreasServed"
class="absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 dark:border-gray-900 hover:cursor-pointer"
>
!
</div>
<app-tooltip [id]="tooltipTargetAreasServed" [text]="getValidationMessage('areasServed')"></app-tooltip>
}
</h3>
<div class="grid grid-cols-12 gap-4">
<div class="col-span-6">
<label for="state" class="block text-sm font-medium text-gray-700">State</label>
</div>
<div class="col-span-5">
<label for="county" class="block text-sm font-medium text-gray-700">County</label>
</div>
</div>
@for (areasServed of user.areasServed; track areasServed; let i=$index){
<div class="grid grid-cols-12 md:gap-4 gap-1 mb-3 md:mb-1">
<div class="col-span-6">
<ng-select [items]="selectOptions?.states" bindLabel="name" bindValue="value" [(ngModel)]="areasServed.state" (ngModelChange)="setState(i, $event)" name="areasServed_state{{ i }}"> </ng-select>
</div>
<div class="col-span-5">
<!-- <input type="text" id="county{{ i }}" name="county{{ i }}" [(ngModel)]="areasServed.county" class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" /> -->
<app-validated-county name="county{{ i }}" [(ngModel)]="areasServed.county" labelClasses="text-gray-900 font-medium" [state]="areasServed.state" [readonly]="!areasServed.state"></app-validated-county>
</div>
<div class="col-span-1">
<button type="button" class="px-2 py-1 bg-red-500 text-white rounded-md h-[42px] w-8" (click)="removeArea(i)">-</button>
</div>
</div>
}
<div class="mt-2">
<button type="button" class="px-2 py-1 bg-green-500 text-white rounded-md mr-2 h-[42px] w-8" (click)="addArea()">+</button>
<span class="text-sm text-gray-500 ml-2">[Add more Areas or remove existing ones.]</span>
</div>
</div>
<div>
<h3 class="text-lg font-medium text-gray-700 mb-2 relative">
Licensed In@if(getValidationMessage('licensedIn')){
<div
[attr.data-tooltip-target]="tooltipTargetLicensed"
class="absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 dark:border-gray-900 hover:cursor-pointer"
>
!
</div>
<app-tooltip [id]="tooltipTargetLicensed" [text]="getValidationMessage('licensedIn')"></app-tooltip>
}
</h3>
<div class="grid grid-cols-12 gap-4">
<div class="col-span-6">
<label for="state" class="block text-sm font-medium text-gray-700">State</label>
</div>
<div class="col-span-5">
<label for="county" class="block text-sm font-medium text-gray-700">License Number</label>
</div>
</div>
@for (licensedIn of user.licensedIn; track licensedIn; let i=$index){
<div class="grid grid-cols-12 md:gap-4 gap-1 mb-3 md:mb-1">
<div class="col-span-6">
<ng-select [items]="selectOptions?.states" bindLabel="name" bindValue="value" [(ngModel)]="licensedIn.state" name="licensedIn_state{{ i }}"> </ng-select>
</div>
<div class="col-span-5">
<label for="firstname" class="block text-gray-700 mb-2 font-medium flex items-center">
First Name
<div *ngIf="validationService.hasMessage('firstname')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('firstname')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="licenseNumber{{ i }}"
name="licenseNumber{{ i }}"
[(ngModel)]="licensedIn.registerNo"
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
id="firstname"
name="firstname"
[(ngModel)]="user.firstname"
placeholder="Enter your first name"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('firstname')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
<button type="button" class="px-2 py-1 bg-red-500 text-white rounded-md h-[42px] w-8" (click)="removeLicence(i)">-</button>
</div>
<!-- Last Name -->
<div>
<label for="lastname" class="block text-gray-700 mb-2 font-medium flex items-center">
Last Name
<div *ngIf="validationService.hasMessage('lastname')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('lastname')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="lastname"
name="lastname"
[(ngModel)]="user.lastname"
placeholder="Enter your last name"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('lastname')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
</div>
</div>
<!-- Customer Type Fields -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@if ((authService.isAdmin() | async) && !id){
<div>
<label for="customerType" class="block text-gray-700 mb-2 font-medium">User Type</label>
<span class="bg-blue-100 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">ADMIN</span>
</div>
}
<div class="mt-2">
<button type="button" class="px-2 py-1 bg-green-500 text-white rounded-md mr-2 h-[42px] w-8" (click)="addLicence()">+</button>
<span class="text-sm text-gray-500 ml-2">[Add more licenses or remove existing ones.]</span>
<!-- Customer Type -->
<div>
<label for="customerType" class="block text-gray-700 mb-2 font-medium flex items-center">
Customer Type
<div *ngIf="validationService.hasMessage('customerType')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('customerType')?.message }}
</div>
</div>
</label>
<div class="relative">
<select
id="customerType"
name="customerType"
[(ngModel)]="user.customerType"
disabled
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg bg-gray-100 focus:outline-none focus:border-black focus:ring-0 appearance-none"
[class.border-red-500]="validationService.hasMessage('customerType')"
>
<option *ngFor="let option of customerTypeOptions" [value]="option.value">{{ option.label }}</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
@if (isProfessional){
<!-- Professional Type -->
<div>
<label for="customerSubType" class="block text-gray-700 mb-2 font-medium flex items-center">
Professional Type
<div *ngIf="validationService.hasMessage('customerSubType')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('customerSubType')?.message }}
</div>
</div>
</label>
<div class="relative">
<select
id="customerSubType"
name="customerSubType"
[(ngModel)]="user.customerSubType"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0 appearance-none"
[class.border-red-500]="validationService.hasMessage('customerSubType')"
>
<option *ngFor="let option of customerSubTypeOptions" [value]="option.value">{{ option.label }}</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
}
</div>
@if (isProfessional){
<!-- Company Info -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Company Name -->
<div>
<label for="companyName" class="block text-gray-700 mb-2 font-medium flex items-center">
Company Name
<div *ngIf="validationService.hasMessage('companyName')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('companyName')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="companyName"
name="companyName"
[(ngModel)]="user.companyName"
placeholder="Enter company name"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('companyName')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
</div>
<!-- Description -->
<div>
<label for="description" class="block text-gray-700 mb-2 font-medium flex items-center">
Describe Yourself
<div *ngIf="validationService.hasMessage('description')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('description')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="description"
name="description"
[(ngModel)]="user.description"
placeholder="Short description about yourself"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('description')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div>
<!-- Contact Info -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Phone Number -->
<div>
<label for="phoneNumber" class="block text-gray-700 mb-2 font-medium flex items-center">
Your Phone Number
<div *ngIf="validationService.hasMessage('phoneNumber')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('phoneNumber')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="tel"
id="phoneNumber"
name="phoneNumber"
[(ngModel)]="user.phoneNumber"
mask="(000) 000-0000"
placeholder="(123) 456-7890"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('phoneNumber')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
/>
</svg>
</div>
</div>
<!-- Company Website -->
<div>
<label for="companyWebsite" class="block text-gray-700 mb-2 font-medium flex items-center">
Company Website
<div *ngIf="validationService.hasMessage('companyWebsite')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('companyWebsite')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="url"
id="companyWebsite"
name="companyWebsite"
[(ngModel)]="user.companyWebsite"
placeholder="https://example.com"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('companyWebsite')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
</div>
<!-- Company Location -->
<div>
<label for="location" class="block text-gray-700 mb-2 font-medium flex items-center">
Company Location
<div *ngIf="validationService.hasMessage('location')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('location')?.message }}
</div>
</div>
</label>
<div class="relative">
<app-validated-location id="location" name="location" [(ngModel)]="user.location" [class.border-red-500]="validationService.hasMessage('location')"></app-validated-location>
</div>
</div>
</div>
<!-- Rich Text Editors -->
<div>
<label for="companyOverview" class="block text-gray-700 mb-2 font-medium flex items-center">
Company Overview
<div *ngIf="validationService.hasMessage('companyOverview')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('companyOverview')?.message }}
</div>
</div>
</label>
<app-validated-quill id="companyOverview" name="companyOverview" [(ngModel)]="user.companyOverview" [class.border-red-500]="validationService.hasMessage('companyOverview')"></app-validated-quill>
</div>
<div>
<label for="offeredServices" class="block text-gray-700 mb-2 font-medium flex items-center">
Services We Offer
<div *ngIf="validationService.hasMessage('offeredServices')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('offeredServices')?.message }}
</div>
</div>
</label>
<app-validated-quill id="offeredServices" name="offeredServices" [(ngModel)]="user.offeredServices" [class.border-red-500]="validationService.hasMessage('offeredServices')"></app-validated-quill>
</div>
<!-- Areas We Serve Section -->
<div>
<h3 class="text-lg font-medium text-gray-700 mb-4 relative">
Areas We Serve
<div *ngIf="validationService.hasMessage('areasServed')" class="inline-flex items-center justify-center w-6 h-6 ml-2 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full hover:cursor-pointer relative group">
!
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('areasServed')?.message }}
</div>
</div>
</h3>
<div class="grid grid-cols-12 gap-4 mb-2">
<div class="col-span-6">
<span class="block text-sm font-medium text-gray-700">State</span>
</div>
<div class="col-span-5">
<span class="block text-sm font-medium text-gray-700">County</span>
</div>
<div class="col-span-1"></div>
</div>
@for (areasServed of user.areasServed; track areasServed; let i=$index){
<div class="grid grid-cols-12 gap-4 mb-4">
<div class="col-span-6">
<div class="relative">
<ng-select
[items]="selectOptions?.states"
bindLabel="name"
bindValue="value"
[(ngModel)]="areasServed.state"
(ngModelChange)="setState(i, $event)"
name="areasServed_state{{ i }}"
[class.border-red-500]="validationService.hasMessage('areasServed')"
class="area-state-select"
placeholder="Select state"
></ng-select>
</div>
</div>
<div class="col-span-5">
<app-validated-county name="county{{ i }}" [(ngModel)]="areasServed.county" [state]="areasServed.state" [readonly]="!areasServed.state" [class.border-red-500]="validationService.hasMessage('areasServed')"></app-validated-county>
</div>
<div class="col-span-1 flex items-center">
<button type="button" class="h-10 w-10 bg-red-500 text-white rounded-lg flex items-center justify-center hover:bg-red-600 focus:outline-none focus:border-black focus:ring-0" (click)="removeArea(i)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4" />
</svg>
</button>
</div>
</div>
}
<div class="mt-2 flex items-center">
<button type="button" class="h-10 w-10 bg-green-500 text-white rounded-lg flex items-center justify-center hover:bg-green-600 focus:outline-none focus:border-black focus:ring-0" (click)="addArea()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</button>
<span class="text-sm text-gray-500 ml-3">[Add more Areas or remove existing ones.]</span>
</div>
</div>
<!-- Licensed In Section -->
<div>
<h3 class="text-lg font-medium text-gray-700 mb-4 relative">
Licensed In
<div *ngIf="validationService.hasMessage('licensedIn')" class="inline-flex items-center justify-center w-6 h-6 ml-2 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full hover:cursor-pointer relative group">
!
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('licensedIn')?.message }}
</div>
</div>
</h3>
<div class="grid grid-cols-12 gap-4 mb-2">
<div class="col-span-6">
<span class="block text-sm font-medium text-gray-700">State</span>
</div>
<div class="col-span-5">
<span class="block text-sm font-medium text-gray-700">License Number</span>
</div>
<div class="col-span-1"></div>
</div>
@for (licensedIn of user.licensedIn; track licensedIn; let i=$index){
<div class="grid grid-cols-12 gap-4 mb-4">
<div class="col-span-6">
<div class="relative">
<ng-select
[items]="selectOptions?.states"
bindLabel="name"
bindValue="value"
[(ngModel)]="licensedIn.state"
name="licensedIn_state{{ i }}"
[class.border-red-500]="validationService.hasMessage('licensedIn')"
class="license-state-select"
placeholder="Select state"
></ng-select>
</div>
</div>
<div class="col-span-5">
<div class="relative">
<input
type="text"
id="licenseNumber{{ i }}"
name="licenseNumber{{ i }}"
[(ngModel)]="licensedIn.registerNo"
placeholder="Enter license number"
class="w-full px-3 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('licensedIn')"
/>
</div>
</div>
<div class="col-span-1 flex items-center">
<button type="button" class="h-10 w-10 bg-red-500 text-white rounded-lg flex items-center justify-center hover:bg-red-600 focus:outline-none focus:border-black focus:ring-0" (click)="removeLicence(i)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4" />
</svg>
</button>
</div>
</div>
}
<div class="mt-2 flex items-center">
<button type="button" class="h-10 w-10 bg-green-500 text-white rounded-lg flex items-center justify-center hover:bg-green-600 focus:outline-none focus:border-black focus:ring-0" (click)="addLicence()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</button>
<span class="text-sm text-gray-500 ml-3">[Add more licenses or remove existing ones.]</span>
</div>
</div>
}
<!-- <div class="flex items-center !my-8">
<label class="flex items-center cursor-pointer">
<div class="relative">
<input type="checkbox" [(ngModel)]="user.showInDirectory" name="showInDirectory" class="hidden" />
<div class="toggle-bg block w-12 h-6 rounded-full bg-gray-600 transition"></div>
</div>
<div class="ml-3 text-gray-700 font-medium">Show your profile in Professional Directory</div>
</label>
</div> -->
<!-- Submit Button -->
<div class="flex justify-start">
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" (click)="updateProfile(user)">
<button type="submit" class="px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:border-black focus:ring-0 transition-colors duration-200 flex items-center" (click)="updateProfile(user)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
Update Profile
</button>
</div>
</form>
<!-- <div class="mt-8 max-lg:hidden">
<h3 class="text-lg font-medium text-gray-700 mb-2">Membership Level</h3>
<div class="overflow-x-auto">
<div class="inline-block min-w-full">
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Level</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Start Date</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">End Date</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Next Settlement</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@for (subscription of subscriptions; track subscriptions; let i=$index){
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getLevel(i) }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getStartDate(i) }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getEndDate(i) }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getNextSettlement(i) }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ getStatus(i) }}</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="mt-8 sm:hidden">
<h3 class="text-lg font-medium text-gray-700 mb-1">Membership Level</h3>
<div class="space-y-2">
@for (subscription of subscriptions; track subscriptions; let i=$index){
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<dl class="grid grid-cols-1 gap-x-4 gap-y-2 sm:grid-cols-2">
<div class="sm:col-span-1 flex">
<dt class="text-sm font-bold text-gray-500 mr-2">Level</dt>
<dd class="text-sm text-gray-900">{{ getLevel(i) }}</dd>
</div>
<div class="sm:col-span-1 flex">
<dt class="text-sm font-bold text-gray-500 mr-2">Start Date</dt>
<dd class="text-sm text-gray-900">{{ getStartDate(i) }}</dd>
</div>
<div class="sm:col-span-1 flex">
<dt class="text-sm font-bold text-gray-500 mr-2">End Date</dt>
<dd class="text-sm text-gray-900">{{ getEndDate(i) }}</dd>
</div>
<div class="sm:col-span-1 flex">
<dt class="text-sm font-bold text-gray-500 mr-2">Next Settlement</dt>
<dd class="text-sm text-gray-900">{{ getNextSettlement(i) }}</dd>
</div>
<div class="sm:col-span-1 flex">
<dt class="text-sm font-bold text-gray-500 mr-2">Status</dt>
<dd class="text-sm text-gray-900">{{ getStatus(i) }}</dd>
</div>
</dl>
</div>
</div>
}
</div>
</div> -->
<!-- @if(user.subscriptionPlan==='free'){
<div class="flex justify-start">
<button
routerLink="/pricing"
class="py-2.5 px-5 me-2 mb-2 text-sm font-medium text-white focus:outline-none bg-green-500 rounded-lg border border-gray-400 hover:bg-green-600 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
>
Upgrade Subscription Plan
</button>
</div>
} -->
</div>
}
</div>

View File

@ -38,5 +38,5 @@ quill-editor {
width: 100%;
}
:host ::ng-deep .ng-select.ng-select-single .ng-select-container {
height: 42px;
height: 50px;
}

View File

@ -6,8 +6,6 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { NgSelectModule } from '@ng-select/ng-select';
import { NgxCurrencyDirective } from 'ngx-currency';
import { ImageCropperComponent } from 'ngx-image-cropper';
import { QuillModule } from 'ngx-quill';
import { lastValueFrom } from 'rxjs';
import { User } from '../../../../../bizmatch-server/src/models/db.model';
@ -15,15 +13,10 @@ import { AutoCompleteCompleteEvent, Invoice, UploadParams, emailToDirName } from
import { environment } from '../../../environments/environment';
import { ConfirmationComponent } from '../../components/confirmation.component';
import { ImageCropAndUploadComponent, UploadReponse } from '../../components/image-crop-and-upload/image-crop-and-upload.component';
import { MessageComponent } from '../../components/message/message.component';
import { TooltipComponent } from '../../components/tooltip/tooltip.component';
import { ValidatedCityComponent } from '../../components/validated-city/validated-city.component';
import { ValidatedCountyComponent } from '../../components/validated-county/validated-county.component';
import { ValidatedInputComponent } from '../../components/validated-input/validated-input.component';
import { ValidatedLocationComponent } from '../../components/validated-location/validated-location.component';
import { ValidatedQuillComponent } from '../../components/validated-quill/validated-quill.component';
import { ValidatedSelectComponent } from '../../components/validated-select/validated-select.component';
import { ValidationMessage, ValidationMessagesService } from '../../components/validation-messages.service';
import { ValidationMessage } from '../../components/validation-messages.service';
import { AuthService } from '../../services/auth.service';
import { ConfirmationService } from '../../services/confirmation.service';
import { GeoService } from '../../services/geo.service';
@ -33,31 +26,13 @@ import { MessageService } from '../../services/message.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { SharedService } from '../../services/shared.service';
import { UserService } from '../../services/user.service';
import { ValidationService } from '../../services/validation.service';
import { TOOLBAR_OPTIONS, map2User } from '../../utils/utils';
@Component({
selector: 'app-account',
standalone: true,
imports: [
CommonModule,
FormsModule,
RouterModule,
FontAwesomeModule,
QuillModule,
NgxCurrencyDirective,
NgSelectModule,
ImageCropperComponent,
ConfirmationComponent,
ImageCropAndUploadComponent,
MessageComponent,
ValidatedInputComponent,
ValidatedSelectComponent,
ValidatedQuillComponent,
ValidatedCityComponent,
TooltipComponent,
ValidatedCountyComponent,
ValidatedLocationComponent,
],
imports: [CommonModule, FormsModule, RouterModule, FontAwesomeModule, QuillModule, NgSelectModule, ConfirmationComponent, ImageCropAndUploadComponent, ValidatedQuillComponent, ValidatedCountyComponent, ValidatedLocationComponent],
providers: [TitleCasePipe, DatePipe],
templateUrl: './account.component.html',
styleUrl: './account.component.scss',
@ -95,7 +70,7 @@ export class AccountComponent {
private messageService: MessageService,
private sharedService: SharedService,
private titleCasePipe: TitleCasePipe,
private validationMessagesService: ValidationMessagesService,
public validationService: ValidationService,
// private subscriptionService: SubscriptionsService,
private datePipe: DatePipe,
private router: Router,
@ -133,7 +108,7 @@ export class AccountComponent {
}
ngOnDestroy() {
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
this.validationService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
}
printInvoice(invoice: Invoice) {}
@ -142,7 +117,7 @@ export class AccountComponent {
await this.userService.save(this.user);
this.userService.changeUser(this.user);
this.messageService.addMessage({ severity: 'success', text: 'Account changes have been persisted', duration: 3000 });
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
this.validationService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
this.validationMessages = [];
} catch (error) {
this.messageService.addMessage({
@ -150,10 +125,7 @@ export class AccountComponent {
text: 'An error occurred while saving the profile - Please check your inputs',
duration: 5000,
});
if (error.error && Array.isArray(error.error?.message)) {
this.validationMessagesService.updateMessages(error.error.message);
this.validationMessages = error.error.message;
}
this.validationService.handleApiError(error.error);
}
}

View File

@ -10,7 +10,7 @@
<img src="assets/images/{{ listing.type }}.png" alt="Image" class="w-full h-18 object-contain object-left" />
<!-- <i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="mr-2 text-xl"></i> -->
<!-- Icon vergrößert -->
<span class="color-{{ listing.type }} w-full text-right font-bold text-2xl">{{ selectOptions.getBusiness(listing.type) }}</span>
<span [class]="selectOptions.getTextColorType(listing.type)" class="w-full text-right font-bold text-2xl">{{ selectOptions.getBusiness(listing.type) }}</span>
<!-- Schriftgröße erhöht -->
</div>
<h2 class="text-xl font-semibold mb-4">

View File

@ -29,14 +29,24 @@
</div>
<div class="py-4 print:hidden">
@if(listing && listingUser && (listingUser?.email===user?.email || (authService.isAdmin() | async))){
<div class="inline">
<!-- <div class="inline">
<button class="share share-edit text-white font-bold text-xs py-1.5 px-2 inline-flex items-center" [routerLink]="['/editBusinessListing', listing.id]">
<i class="fa-regular fa-pen-to-square"></i>
<span class="ml-2">Edit</span>
</button>
</div> -->
<div class="inline">
<button [routerLink]="['/editBusinessListing', listing.id]" class="sb-wrapper sb-show-icon sb-show-text" style="--button-color: #0088cc" aria-label="Share by email">
<div class="sb-content">
<div class="sb-icon">
<i class="fa-solid fa-envelope fa-fw"></i>
</div>
<div class="sb-text">Edit</div>
</div>
</button>
</div>
} @if(user){
<div class="inline">
<!-- <div class="inline">
<button class="share share-save text-white font-bold text-xs py-1.5 px-2 inline-flex items-center" (click)="save()" [disabled]="listing.favoritesForUser.includes(user.email)">
<i class="fa-regular fa-heart"></i>
@if(listing.favoritesForUser.includes(user.email)){
@ -45,14 +55,31 @@
<span class="ml-2">Save</span>
}
</button>
</div> -->
<div class="inline">
<button class="sb-wrapper sb-show-icon sb-show-text" style="--button-color: #e60023" (click)="save()" aria-label="Share by email">
<div class="sb-content">
<div class="sb-icon">
<i class="fa-solid fa-envelope fa-fw"></i>
</div>
@if(listing.favoritesForUser.includes(user.email)){
<div class="sb-text">Saved ...</div>
}@else {
<div class="sb-text">Save</div>
}
</div>
</button>
</div>
}
<share-button button="print" showText="true" (click)="createEvent('print')"></share-button>
<!-- <share-button button="email" showText="true"></share-button> -->
<div class="inline">
<button class="share share-email text-white font-bold text-xs py-1.5 px-2 inline-flex items-center" (click)="showShareByEMail()">
<i class="fa-solid fa-envelope"></i>
<span class="ml-2">Email</span>
<button class="sb-wrapper sb-show-icon sb-show-text" style="--button-color: #ff961c" (click)="showShareByEMail()" aria-label="Share by email">
<div class="sb-content">
<div class="sb-icon">
<i class="fa-solid fa-envelope fa-fw"></i>
</div>
<div class="sb-text">Email</div>
</div>
</button>
</div>
@ -68,26 +95,180 @@
</div>
<!-- Right column -->
<!-- Kontaktformular mit konsistenten Höhen und Breiten -->
<!-- Kontaktformular mit konsistenten Höhen und Breiten -->
<div class="w-full lg:w-1/2 mt-6 lg:mt-0 print:hidden">
<!-- <h2 class="text-lg font-semibold my-4">Contact the Author of this Listing</h2> -->
<div class="md:mt-8 mb-4 text-2xl font-bold mb-4">Contact the Author of this Listing</div>
<p class="text-sm mb-4">Please include your contact info below</p>
<form class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-input label="Your Name" name="name" [(ngModel)]="mailinfo.sender.name"></app-validated-input>
<app-validated-input label="Your Email" name="email" [(ngModel)]="mailinfo.sender.email" kind="email"></app-validated-input>
<div class="md:mt-8 mb-4 text-2xl font-bold">Contact the Author of this Listing</div>
<p class="text-sm mb-6">Please include your contact info below</p>
<form class="space-y-6">
<!-- Erste Zeile: Name und E-Mail -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Name Eingabe -->
<div>
<label for="name" class="block text-gray-700 mb-2 font-medium flex items-center">
Your Name
<div *ngIf="validationService.hasMessage('name')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('name')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
id="name"
name="name"
type="text"
[(ngModel)]="mailinfo.sender.name"
placeholder="Enter your name"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('name')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
</div>
<!-- E-Mail Eingabe -->
<div>
<label for="email" class="block text-gray-700 mb-2 font-medium flex items-center">
Your Email
<div *ngIf="validationService.hasMessage('email')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('email')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
id="email"
name="email"
type="email"
[(ngModel)]="mailinfo.sender.email"
placeholder="Enter your email"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('email')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-input label="Phone Number" name="phoneNumber" [(ngModel)]="mailinfo.sender.phoneNumber" mask="(000) 000-0000"></app-validated-input>
<!-- <app-validated-input label="Country/State" name="state" [(ngModel)]="mailinfo.sender.state"></app-validated-input> -->
<app-validated-ng-select label="State" name="state" [(ngModel)]="mailinfo.sender.state" [items]="selectOptions?.states"></app-validated-ng-select>
<!-- Zweite Zeile: Telefon und Bundesland -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Telefon Eingabe -->
<div>
<label for="phone" class="block text-gray-700 mb-2 font-medium flex items-center">
Phone Number
<div *ngIf="validationService.hasMessage('phoneNumber')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('phoneNumber')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
id="phone"
name="phoneNumber"
type="tel"
[(ngModel)]="mailinfo.sender.phoneNumber"
mask="(000) 000-0000"
placeholder="(123) 456-7890"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('phoneNumber')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
/>
</svg>
</div>
</div>
<!-- Bundesland Select -->
<div>
<label for="state" class="block text-gray-700 mb-2 font-medium flex items-center">
State
<div *ngIf="validationService.hasMessage('state')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('state')?.message }}
</div>
</div>
</label>
<div class="relative">
<select
id="state"
name="state"
[(ngModel)]="mailinfo.sender.state"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg bg-white focus:outline-none focus:border-black focus:ring-0 appearance-none"
[class.border-red-500]="validationService.hasMessage('state')"
>
<option value="" disabled selected>Select your state</option>
<option *ngFor="let state of selectOptions?.states" [value]="state.value">{{ state.name }}</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>
<!-- Kommentarfeld -->
<div>
<app-validated-textarea label="Questions/Comments" name="comments" [(ngModel)]="mailinfo.sender.comments"></app-validated-textarea>
<label for="comments" class="block text-gray-700 mb-2 font-medium flex items-center">
Questions/Comments
<div *ngIf="validationService.hasMessage('comments')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('comments')?.message }}
</div>
</div>
</label>
<textarea
id="comments"
name="comments"
[(ngModel)]="mailinfo.sender.comments"
placeholder="Type your questions or comments here..."
rows="4"
class="w-full px-3 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('comments')"
></textarea>
</div>
<!-- Submit Button -->
<div>
<button (click)="mail()" class="w-full sm:w-auto px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-colors duration-200">
<span class="flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
Submit
</span>
</button>
</div>
<button (click)="mail()" class="w-full sm:w-auto px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">Submit</button>
</form>
</div>
</div>

View File

@ -14,10 +14,6 @@ import { BusinessListing, EventTypeEnum, ShareByEMail, User } from '../../../../
import { KeycloakUser, MailInfo } from '../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../environments/environment';
import { EMailService } from '../../components/email/email.service';
import { ValidatedInputComponent } from '../../components/validated-input/validated-input.component';
import { ValidatedNgSelectComponent } from '../../components/validated-ng-select/validated-ng-select.component';
import { ValidatedTextareaComponent } from '../../components/validated-textarea/validated-textarea.component';
import { ValidationMessagesService } from '../../components/validation-messages.service';
import { AuditService } from '../../services/audit.service';
import { AuthService } from '../../services/auth.service';
import { GeoService } from '../../services/geo.service';
@ -27,14 +23,56 @@ import { MailService } from '../../services/mail.service';
import { MessageService } from '../../services/message.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { UserService } from '../../services/user.service';
import { ValidationService } from '../../services/validation.service';
import { createMailInfo, map2User } from '../../utils/utils';
@Component({
selector: 'app-details-business-listing',
standalone: true,
imports: [CommonModule, FormsModule, RouterModule, FontAwesomeModule, ValidatedInputComponent, ValidatedTextareaComponent, ShareButton, ValidatedNgSelectComponent],
imports: [CommonModule, FormsModule, RouterModule, FontAwesomeModule, ShareButton],
providers: [],
templateUrl: './details-business-listing.component.html',
styles: `
.inline .sb-wrapper {
margin: var(--sb-margin, .3125em);
padding: var(--sb-padding, 0);
min-width: var(--sb-min-width, 4.125em);
height: var(--sb-height, 2.5em);
color: var(--sb-color, #fff);
background: var(--sb-background);
font-size: var(--sb-font-size, 13px);
line-height: var(--sb-line-height, 2.571em);
border: var(--sb-border);
border-radius: var(--sb-border-radius, 4px);
transition: var(--sb-transition);
box-shadow: var(--sb-box-shadow);
text-shadow: var(--sb-text-shadow);
overflow: var(--sb-overflow, hidden);
--sb-color: #fff;
--sb-background: var(--button-color);
cursor: pointer;
position: relative;
outline: 0;
}
.inline .sb-content {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 0 1em;
}
.inline .sb-icon {
display: flex;
align-items: center;
}
.inline .sb-text {
margin-left: 0.5em;
font-weight: bold;
}
`,
})
export class DetailsBusinessListingComponent {
// listings: Array<BusinessListing>;
@ -76,13 +114,13 @@ export class DetailsBusinessListingComponent {
private mailService: MailService,
private sanitizer: DomSanitizer,
public historyService: HistoryService,
private validationMessagesService: ValidationMessagesService,
private messageService: MessageService,
private auditService: AuditService,
public emailService: EMailService,
private geoService: GeoService,
public authService: AuthService,
private cdref: ChangeDetectorRef,
public validationService: ValidationService,
) {
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
@ -112,7 +150,7 @@ export class DetailsBusinessListingComponent {
}
ngOnDestroy() {
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
this.validationService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
}
async mail() {
@ -120,7 +158,7 @@ export class DetailsBusinessListingComponent {
this.mailinfo.email = this.listingUser.email;
this.mailinfo.listing = this.listing;
await this.mailService.mail(this.mailinfo);
this.validationMessagesService.clearMessages();
this.validationService.clearMessages();
this.auditService.createEvent(this.listing.id, 'contact', this.user?.email, this.mailinfo.sender);
this.messageService.addMessage({ severity: 'success', text: 'Your message has been sent to the creator of the listing', duration: 3000 });
this.mailinfo = createMailInfo(this.user);
@ -130,9 +168,7 @@ export class DetailsBusinessListingComponent {
text: 'An error occurred while sending the request - Please check your inputs',
duration: 5000,
});
if (error.error && Array.isArray(error.error?.message)) {
this.validationMessagesService.updateMessages(error.error.message);
}
this.validationService.handleApiError(error.error);
}
}
get listingDetails() {

View File

@ -1,382 +1,520 @@
<div class="container mx-auto p-4">
<div class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg p-6">
<h1 class="text-2xl font-semibold mb-6">Edit Listing</h1>
<h1 class="text-2xl font-bold mb-6">Edit Listing</h1>
@if(listing){
<form #listingForm="ngForm" class="space-y-4">
<div class="mb-4">
<label for="listingsCategory" class="block text-sm font-bold text-gray-700 mb-1">Listing category</label>
<ng-select
[readonly]="true"
[items]="selectOptions?.listingCategories"
bindLabel="name"
bindValue="value"
[(ngModel)]="listing.listingsCategory"
(ngModelChange)="changeListingCategory($event)"
name="listingsCategory"
>
</ng-select>
</div>
<!-- <div class="mb-4">
<label for="title" class="block text-sm font-bold text-gray-700 mb-1">Title of Listing</label>
<input type="text" id="title" [(ngModel)]="listing.title" name="title" class="w-full p-2 border border-gray-300 rounded-md" />
</div> -->
<form #listingForm="ngForm" class="space-y-6">
<!-- Listing Category -->
<div>
<app-validated-input label="Title of Listing" name="title" [(ngModel)]="listing.title"></app-validated-input>
</div>
<!-- <div class="mb-4">
<label for="description" class="block text-sm font-bold text-gray-700 mb-1">Description</label>
<quill-editor [(ngModel)]="listing.description" name="description" [modules]="quillModules"></quill-editor>
</div> -->
<div>
<app-validated-quill label="Description" name="description" [(ngModel)]="listing.description"></app-validated-quill>
</div>
<!-- <div class="mb-4">
<label for="type" class="block text-sm font-bold text-gray-700 mb-1">Type of business</label>
<ng-select [items]="typesOfBusiness" bindLabel="name" bindValue="value" [(ngModel)]="listing.type" name="type"> </ng-select>
</div> -->
<div>
<app-validated-ng-select label="Type of business" name="type" [(ngModel)]="listing.type" [items]="typesOfBusiness"></app-validated-ng-select>
</div>
<!-- <div class="flex mb-4 space-x-4">
<div class="w-1/2">
<label for="state" class="block text-sm font-bold text-gray-700 mb-1">State</label>
<ng-select [items]="selectOptions?.states" bindLabel="name" bindValue="value" [(ngModel)]="listing.state" name="state"> </ng-select>
<label for="listingsCategory" class="block text-gray-700 mb-2 font-medium flex items-center">
Listing Category
<div *ngIf="validationService.hasMessage('listingsCategory')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('listingsCategory')?.message }}
</div>
</div>
</label>
<div class="relative">
<ng-select
[readonly]="true"
[items]="selectOptions?.listingCategories"
bindLabel="name"
bindValue="value"
[(ngModel)]="listing.listingsCategory"
(ngModelChange)="changeListingCategory($event)"
name="listingsCategory"
[class.border-red-500]="validationService.hasMessage('listingsCategory')"
class="listing-category-select"
placeholder="Select listing category"
></ng-select>
</div>
<div class="w-1/2">
<label for="city" class="block text-sm font-bold text-gray-700 mb-1">City</label>
<input type="text" id="city" [(ngModel)]="listing.city" name="city" class="w-full p-2 border border-gray-300 rounded-md" />
</div>
</div> -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- <app-validated-ng-select label="State" name="state" [(ngModel)]="listing.location.state" [items]="selectOptions?.states"></app-validated-ng-select>
<app-validated-input label="City" name="city" [(ngModel)]="listing.location.city"></app-validated-input> -->
<!-- <app-validated-city label="Location" name="location" [(ngModel)]="listing.location"></app-validated-city> -->
<app-validated-location label="Location" name="location" [(ngModel)]="listing.location"></app-validated-location>
<app-validated-price label="Price" name="price" [(ngModel)]="listing.price"></app-validated-price>
</div>
<!-- <div class="flex mb-4 space-x-4">
<div class="w-1/2">
<label for="price" class="block text-sm font-bold text-gray-700 mb-1">Price</label>
<!-- Title -->
<div>
<label for="title" class="block text-gray-700 mb-2 font-medium flex items-center">
Title of Listing
<div *ngIf="validationService.hasMessage('title')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('title')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="price"
[(ngModel)]="listing.price"
name="price"
class="w-full p-2 border border-gray-300 rounded-md"
[options]="{ prefix: '$', thousands: ',', decimal: '.', precision: 0, align: 'left' }"
currencyMask
id="title"
name="title"
[(ngModel)]="listing.title"
placeholder="Enter listing title"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('title')"
/>
</div>
<div class="w-1/2">
<label for="salesRevenue" class="block text-sm font-bold text-gray-700 mb-1">Sales Revenue</label>
<input
type="text"
id="salesRevenue"
[(ngModel)]="listing.salesRevenue"
name="salesRevenue"
class="w-full p-2 border border-gray-300 rounded-md"
[options]="{ prefix: '$', thousands: ',', decimal: '.', precision: 0, align: 'left' }"
currencyMask
/>
</div>
</div> -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-price label="Sales Revenue" name="salesRevenue" [(ngModel)]="listing.salesRevenue"></app-validated-price>
<app-validated-price label="Cash Flow" name="cashFlow" [(ngModel)]="listing.cashFlow"></app-validated-price>
</div>
<!-- <div class="mb-4">
<label for="cashFlow" class="block text-sm font-bold text-gray-700 mb-1">Cash Flow</label>
<input
type="text"
id="cashFlow"
[(ngModel)]="listing.cashFlow"
name="cashFlow"
class="w-full p-2 border border-gray-300 rounded-md"
[options]="{ prefix: '$', thousands: ',', decimal: '.', precision: 0, align: 'left' }"
currencyMask
/>
</div> -->
<!-- <div>
</div> -->
<!-- <div class="flex mb-4 space-x-4">
<div class="w-1/2">
<label for="established" class="block text-sm font-bold text-gray-700 mb-1">Years Established Since</label>
<input type="number" id="established" [(ngModel)]="listing.established" name="established" class="w-full p-2 border border-gray-300 rounded-md" />
</div>
<div class="w-1/2">
<label for="employees" class="block text-sm font-bold text-gray-700 mb-1">Employees</label>
<input type="number" id="employees" [(ngModel)]="listing.employees" name="employees" class="w-full p-2 border border-gray-300 rounded-md" />
</div>
</div> -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-input label="Established In" name="established" [(ngModel)]="listing.established" mask="0000" kind="number"></app-validated-input>
<app-validated-input label="Employees" name="employees" [(ngModel)]="listing.employees" mask="0000" kind="number"></app-validated-input>
</div>
<div class="flex mb-4 space-x-4">
<div class="flex items-center">
<input
type="checkbox"
id="realEstateIncluded"
[(ngModel)]="listing.realEstateIncluded"
(ngModelChange)="onCheckboxChange('realEstateIncluded', $event)"
name="realEstateIncluded"
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 mr-2"
/>
<label for="realEstateIncluded" class="text-sm font-bold text-gray-700">Real Estate Included</label>
</div>
<div class="flex items-center">
<input
type="checkbox"
id="leasedLocation"
[(ngModel)]="listing.leasedLocation"
(ngModelChange)="onCheckboxChange('leasedLocation', $event)"
name="leasedLocation"
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 mr-2"
/>
<label for="leasedLocation" class="text-sm font-bold text-gray-700">Leased Location</label>
</div>
<div class="flex items-center">
<input
type="checkbox"
id="franchiseResale"
[(ngModel)]="listing.franchiseResale"
(ngModelChange)="onCheckboxChange('franchiseResale', $event)"
name="franchiseResale"
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 mr-2"
/>
<label for="franchiseResale" class="text-sm font-bold text-gray-700">Franchise Re-Sale</label>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<!-- <div class="mb-4">
<label for="supportAndTraining" class="block text-sm font-bold text-gray-700 mb-1">Support & Training</label>
<input type="text" id="supportAndTraining" [(ngModel)]="listing.supportAndTraining" name="supportAndTraining" class="w-full p-2 border border-gray-300 rounded-md" />
</div>
<div class="mb-4">
<label for="reasonForSale" class="block text-sm font-bold text-gray-700 mb-1">Reason for Sale</label>
<input type="text" id="reasonForSale" [(ngModel)]="listing.reasonForSale" name="reasonForSale" class="w-full p-2 border border-gray-300 rounded-md" />
</div>-->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-input label="Support & Training" name="supportAndTraining" [(ngModel)]="listing.supportAndTraining"></app-validated-input>
<app-validated-input label="Reason for Sale" name="reasonForSale" [(ngModel)]="listing.reasonForSale"></app-validated-input>
<!-- Description -->
<div>
<label for="description" class="block text-gray-700 mb-2 font-medium flex items-center">
Description
<div *ngIf="validationService.hasMessage('description')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('description')?.message }}
</div>
</div>
</label>
<app-validated-quill id="description" name="description" [(ngModel)]="listing.description" [class.border-red-500]="validationService.hasMessage('description')"></app-validated-quill>
</div>
<!-- <div class="flex mb-4 space-x-4">
<div class="w-1/2">
<label for="brokerLicencing" class="block text-sm font-bold text-gray-700 mb-1">Broker Licensing</label>
<input type="text" id="brokerLicencing" [(ngModel)]="listing.brokerLicencing" name="brokerLicencing" class="w-full p-2 border border-gray-300 rounded-md" />
<!-- Type of Business -->
<div>
<label for="type" class="block text-gray-700 mb-2 font-medium flex items-center">
Type of Business
<div *ngIf="validationService.hasMessage('type')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('type')?.message }}
</div>
</div>
</label>
<div class="relative">
<app-validated-ng-select id="type" name="type" [(ngModel)]="listing.type" [items]="typesOfBusiness" [class.border-red-500]="validationService.hasMessage('type')" placeholder="Select business type"></app-validated-ng-select>
</div>
<div class="w-1/2">
<label for="internalListingNumber" class="block text-sm font-bold text-gray-700 mb-1">Internal Listing Number</label>
<input type="number" id="internalListingNumber" [(ngModel)]="listing.internalListingNumber" name="internalListingNumber" class="w-full p-2 border border-gray-300 rounded-md" />
</div>
</div> -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
</div>
<!-- Location and Price -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Location -->
<div>
<!-- <app-validated-input label="Broker Licensing" name="brokerLicencing" [(ngModel)]="listing.brokerLicencing"></app-validated-input> -->
<label for="brokerLicencing" class="block text-sm font-bold text-gray-700 mb-1">Broker Licensing (please maintain your license in your account)</label>
<!-- @if(listingUser){ -->
<ng-select [(ngModel)]="listing.brokerLicencing" name="brokerLicencing">
@for (licensedIn of listingUser?.licensedIn; track listingUser?.licensedIn) {
<ng-option [value]="licensedIn.registerNo">{{ licensedIn.state }} {{ licensedIn.registerNo }}</ng-option>
}
</ng-select>
<label for="location" class="block text-gray-700 mb-2 font-medium flex items-center">
Location
<div *ngIf="validationService.hasMessage('location')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('location')?.message }}
</div>
</div>
</label>
<app-validated-location id="location" name="location" [(ngModel)]="listing.location" [class.border-red-500]="validationService.hasMessage('location')"></app-validated-location>
</div>
<!-- Price -->
<div>
<label for="price" class="block text-gray-700 mb-2 font-medium flex items-center">
Price
<div *ngIf="validationService.hasMessage('price')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('price')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="price"
name="price"
[(ngModel)]="listing.price"
currencyMask
[options]="{ prefix: '$', thousands: ',', decimal: '.', precision: 0, align: 'left' }"
placeholder="Enter listing price"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('price')"
autocomplete="off"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
</div>
<!-- } -->
<app-validated-input label="Internal Listing Number" name="internalListingNumber" [(ngModel)]="listing.internalListingNumber" kind="number" mask="00000000000000000000"></app-validated-input>
</div>
<!-- <div class="mb-4">
<label for="internals" class="block text-sm font-bold text-gray-700 mb-1">Internal Notes (Will not be shown on the listing, for your records only.)</label>
<textarea id="internals" [(ngModel)]="listing.internals" name="internals" class="w-full p-2 border border-gray-300 rounded-md" rows="3"></textarea>
</div> -->
<!-- Sales Revenue and Cash Flow -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Sales Revenue -->
<div>
<label for="salesRevenue" class="block text-gray-700 mb-2 font-medium flex items-center">
Sales Revenue
<div *ngIf="validationService.hasMessage('salesRevenue')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('salesRevenue')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="salesRevenue"
name="salesRevenue"
[(ngModel)]="listing.salesRevenue"
currencyMask
[options]="{ prefix: '$', thousands: ',', decimal: '.', precision: 0, align: 'left' }"
placeholder="Enter annual sales revenue"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('salesRevenue')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
</div>
<!-- Cash Flow -->
<div>
<label for="cashFlow" class="block text-gray-700 mb-2 font-medium flex items-center">
Cash Flow
<div *ngIf="validationService.hasMessage('cashFlow')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('cashFlow')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="cashFlow"
name="cashFlow"
[(ngModel)]="listing.cashFlow"
currencyMask
[options]="{ prefix: '$', thousands: ',', decimal: '.', precision: 0, align: 'left' }"
placeholder="Enter annual cash flow"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('cashFlow')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2z" />
</svg>
</div>
</div>
</div>
<!-- Established and Employees -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Established In -->
<div>
<label for="established" class="block text-gray-700 mb-2 font-medium flex items-center">
Established In
<div *ngIf="validationService.hasMessage('established')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('established')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="established"
name="established"
[(ngModel)]="listing.established"
mask="0000"
kind="number"
placeholder="Enter year (e.g. 2010)"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('established')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
</div>
<!-- Employees -->
<div>
<label for="employees" class="block text-gray-700 mb-2 font-medium flex items-center">
Employees
<div *ngIf="validationService.hasMessage('employees')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('employees')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="employees"
name="employees"
[(ngModel)]="listing.employees"
mask="0000"
kind="number"
placeholder="Number of employees"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('employees')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
/>
</svg>
</div>
</div>
</div>
<!-- Property Checkboxes -->
<div>
<app-validated-textarea label="Internal Notes (Will not be shown on the listing, for your records only.)" name="internals" [(ngModel)]="listing.internals"></app-validated-textarea>
<label class="block text-gray-700 mb-3 font-medium">Property Details</label>
<div class="flex flex-wrap gap-6">
<!-- Real Estate Included -->
<div class="flex items-center">
<input
type="checkbox"
id="realEstateIncluded"
[(ngModel)]="listing.realEstateIncluded"
(ngModelChange)="onCheckboxChange('realEstateIncluded', $event)"
name="realEstateIncluded"
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-0 focus:border-black mr-2"
/>
<label for="realEstateIncluded" class="text-gray-700">Real Estate Included</label>
</div>
<!-- Leased Location -->
<div class="flex items-center">
<input
type="checkbox"
id="leasedLocation"
[(ngModel)]="listing.leasedLocation"
(ngModelChange)="onCheckboxChange('leasedLocation', $event)"
name="leasedLocation"
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-0 focus:border-black mr-2"
/>
<label for="leasedLocation" class="text-gray-700">Leased Location</label>
</div>
<!-- Franchise Re-Sale -->
<div class="flex items-center">
<input
type="checkbox"
id="franchiseResale"
[(ngModel)]="listing.franchiseResale"
(ngModelChange)="onCheckboxChange('franchiseResale', $event)"
name="franchiseResale"
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-0 focus:border-black mr-2"
/>
<label for="franchiseResale" class="text-gray-700">Franchise Re-Sale</label>
</div>
</div>
</div>
<div class="flex items-center mb-4">
<!-- Support and Reason for Sale -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Support & Training -->
<div>
<label for="supportAndTraining" class="block text-gray-700 mb-2 font-medium flex items-center">
Support & Training
<div *ngIf="validationService.hasMessage('supportAndTraining')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('supportAndTraining')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="supportAndTraining"
name="supportAndTraining"
[(ngModel)]="listing.supportAndTraining"
placeholder="Describe support & training offered"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('supportAndTraining')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
</div>
</div>
<!-- Reason for Sale -->
<div>
<label for="reasonForSale" class="block text-gray-700 mb-2 font-medium flex items-center">
Reason for Sale
<div *ngIf="validationService.hasMessage('reasonForSale')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('reasonForSale')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="reasonForSale"
name="reasonForSale"
[(ngModel)]="listing.reasonForSale"
placeholder="Why are you selling this business?"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('reasonForSale')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div>
<!-- Broker Licensing and Internal Number -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Broker Licensing -->
<div>
<label for="brokerLicencing" class="block text-gray-700 mb-2 font-medium flex items-center">
Broker Licensing
<span class="text-sm font-normal text-gray-500 ml-1">(please maintain your license in your account)</span>
<div *ngIf="validationService.hasMessage('brokerLicencing')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('brokerLicencing')?.message }}
</div>
</div>
</label>
<div class="relative">
<ng-select [(ngModel)]="listing.brokerLicencing" name="brokerLicencing" placeholder="Select your broker license" [class.border-red-500]="validationService.hasMessage('brokerLicencing')" class="broker-license-select">
@for (licensedIn of listingUser?.licensedIn; track listingUser?.licensedIn) {
<ng-option [value]="licensedIn.registerNo">{{ licensedIn.state }} {{ licensedIn.registerNo }}</ng-option>
}
</ng-select>
</div>
</div>
<!-- Internal Listing Number -->
<div>
<label for="internalListingNumber" class="block text-gray-700 mb-2 font-medium flex items-center">
Internal Listing Number
<div *ngIf="validationService.hasMessage('internalListingNumber')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('internalListingNumber')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
type="text"
id="internalListingNumber"
name="internalListingNumber"
[(ngModel)]="listing.internalListingNumber"
kind="number"
mask="00000000000000000000"
placeholder="Enter reference number"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('internalListingNumber')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14" />
</svg>
</div>
</div>
</div>
<!-- Internal Notes -->
<div>
<label for="internals" class="block text-gray-700 mb-2 font-medium flex items-center">
Internal Notes
<span class="text-sm font-normal text-gray-500 ml-1">(Will not be shown on the listing, for your records only.)</span>
<div *ngIf="validationService.hasMessage('internals')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('internals')?.message }}
</div>
</div>
</label>
<div class="relative">
<textarea
id="internals"
name="internals"
[(ngModel)]="listing.internals"
rows="4"
placeholder="Enter your private notes about this listing..."
class="w-full px-3 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('internals')"
></textarea>
</div>
</div>
<!-- Draft Mode Toggle -->
<div class="flex items-center">
<label class="flex items-center cursor-pointer">
<div class="relative">
<input type="checkbox" [(ngModel)]="listing.draft" name="draft" class="hidden" />
<div class="toggle-bg block w-12 h-6 rounded-full bg-gray-600 transition"></div>
<input type="checkbox" [(ngModel)]="listing.draft" name="draft" class="sr-only" />
<div class="w-14 h-7 bg-gray-300 rounded-full"></div>
<div class="dot absolute left-1 top-1 bg-white w-5 h-5 rounded-full transition-transform duration-300 ease-in-out" [ngClass]="{ 'translate-x-7': listing.draft }"></div>
</div>
<div class="ml-3 text-gray-700">
Draft Mode
<span class="text-sm font-normal text-gray-500">(Will not be shown as public listing)</span>
</div>
<div class="ml-3 text-gray-700 font-medium">Draft Mode (Will not be shown as public listing)</div>
</label>
</div>
@if (mode==='create'){
<button (click)="save()" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">Post Listing</button>
} @else {
<button (click)="save()" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">Update Listing</button>
}
<!-- Submit Button -->
<div class="flex justify-start">
@if (mode==='create'){
<button (click)="save()" class="px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:border-black focus:ring-0 transition-colors duration-200 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Post Listing
</button>
} @else {
<button (click)="save()" class="px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:border-black focus:ring-0 transition-colors duration-200 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Update Listing
</button>
}
</div>
</form>
}
</div>
</div>
<!-- <div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
<div class="p-fluid flex flex-column lg:flex-row">
<menu-account></menu-account>
<p-toast></p-toast>
<div *ngIf="listing" class="surface-card p-5 shadow-2 border-round flex-auto">
<div class="text-900 font-semibold text-lg mt-3">{{ mode === 'create' ? 'New' : 'Edit' }} Listing</div>
<p-divider></p-divider>
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
<div class="mb-4">
<label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label>
<p-dropdown
id="listingCategory"
[options]="selectOptions?.listingCategories"
[ngModel]="listingsCategory"
optionLabel="name"
optionValue="value"
(ngModelChange)="changeListingCategory($event)"
placeholder="Listing category"
[disabled]="mode === 'edit'"
[style]="{ width: '100%' }"
></p-dropdown>
</div>
<div class="mb-4">
<label for="email" class="block font-medium text-900 mb-2">Title of Listing</label>
<input id="email" type="text" pInputText [(ngModel)]="listing.title" />
</div>
<div>
<div class="mb-4">
<label for="description" class="block font-medium text-900 mb-2">Description</label>
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
<ng-template pTemplate="header"></ng-template>
</p-editor>
</div>
</div>
<div class="mb-4">
<label for="type" class="block font-medium text-900 mb-2">Type of business</label>
<p-dropdown
id="type"
[filter]="true"
filterBy="name"
[options]="typesOfBusiness"
[(ngModel)]="listing.type"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="Type of business"
[style]="{ width: '100%' }"
></p-dropdown>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="listingCategory" class="block font-medium text-900 mb-2">State</label>
<p-dropdown
id="listingCategory"
[filter]="true"
filterBy="name"
[options]="selectOptions?.states"
[(ngModel)]="listing.state"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="State"
[style]="{ width: '100%' }"
></p-dropdown>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="listingCategory" class="block font-medium text-900 mb-2">City</label>
<p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
</div>
</div>
</div>
</div>
<p-divider></p-divider>
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="price" class="block font-medium text-900 mb-2">Price</label>
<p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></p-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
<p-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></p-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
<p-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></p-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="established" class="block font-medium text-900 mb-2">Years Established Since</label>
<p-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established" [useGrouping]="false"></p-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
<p-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees" [useGrouping]="false"></p-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-4">
<p-checkbox [binary]="true" [(ngModel)]="listing.realEstateIncluded"></p-checkbox>
<span class="ml-2 text-900">Real Estate Included</span>
</div>
<div class="mb-4 col-12 md:col-4">
<p-checkbox [binary]="true" [(ngModel)]="listing.leasedLocation"></p-checkbox>
<span class="ml-2 text-900">Leased Location</span>
</div>
<div class="mb-4 col-12 md:col-4">
<p-checkbox [binary]="true" [(ngModel)]="listing.franchiseResale"></p-checkbox>
<span class="ml-2 text-900">Franchise Re-Sale</span>
</div>
</div>
<div class="mb-4">
<label for="supportAndTraining" class="block font-medium text-900 mb-2">Support & Training</label>
<input id="supportAndTraining" type="text" pInputText [(ngModel)]="listing.supportAndTraining" />
</div>
<div class="mb-4">
<label for="reasonForSale" class="block font-medium text-900 mb-2">Reason for Sale</label>
<textarea id="reasonForSale" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.reasonForSale"></textarea>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="brokerLicensing" class="block font-medium text-900 mb-2">Broker Licensing</label>
<input id="brokerLicensing" type="text" pInputText [(ngModel)]="listing.brokerLicencing" />
</div>
<div class="mb-4 col-12 md:col-6">
<label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing Number</label>
<p-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber" [useGrouping]="false"></p-inputNumber>
</div>
</div>
<div class="mb-4">
<label for="internalListing" class="block font-medium text-900 mb-2">Internal Notes (Will not be shown on the listing, for your records only.)</label>
<input id="internalListing" type="text" pInputText [(ngModel)]="listing.internals" />
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<p-inputSwitch inputId="draft" [(ngModel)]="listing.draft"></p-inputSwitch>
<span class="ml-2 text-900 absolute translate-y-5">Draft Mode (Will not be shown as public listing)</span>
</div>
</div>
<div>
@if (mode==='create'){
<button pButton pRipple label="Post Listing" class="w-auto" (click)="save()"></button>
} @else {
<button pButton pRipple label="Update Listing" class="w-auto" (click)="save()"></button>
}
</div>
</div>
</div>
</div>
</div>
</div>
<p-toast></p-toast>
<p-confirmDialog></p-confirmDialog> -->

View File

@ -27,7 +27,7 @@
transition: all 0.3s ease-in-out;
}
::ng-deep .ng-select.ng-select-single .ng-select-container {
height: 42px;
height: 50px;
}
quill-editor {
width: 100%;

View File

@ -14,15 +14,12 @@ import { NgxCurrencyDirective } from 'ngx-currency';
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, createDefaultBusinessListing, emailToDirName, ImageProperty } from '../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../environments/environment';
import { ValidatedCityComponent } from '../../components/validated-city/validated-city.component';
import { ValidatedInputComponent } from '../../components/validated-input/validated-input.component';
import { ValidatedLocationComponent } from '../../components/validated-location/validated-location.component';
import { ValidatedNgSelectComponent } from '../../components/validated-ng-select/validated-ng-select.component';
import { ValidatedPriceComponent } from '../../components/validated-price/validated-price.component';
import { ValidatedQuillComponent } from '../../components/validated-quill/validated-quill.component';
import { ValidatedTextareaComponent } from '../../components/validated-textarea/validated-textarea.component';
import { ValidationMessagesService } from '../../components/validation-messages.service';
import { ArrayToStringPipe } from '../../pipes/array-to-string.pipe';
import { AuthService } from '../../services/auth.service';
import { GeoService } from '../../services/geo.service';
import { ImageService } from '../../services/image.service';
@ -31,6 +28,7 @@ import { LoadingService } from '../../services/loading.service';
import { MessageService } from '../../services/message.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { UserService } from '../../services/user.service';
import { ValidationService } from '../../services/validation.service';
import { map2User, routeListingWithState, TOOLBAR_OPTIONS } from '../../utils/utils';
@Component({
@ -41,18 +39,16 @@ import { map2User, routeListingWithState, TOOLBAR_OPTIONS } from '../../utils/ut
FormsModule,
RouterModule,
FontAwesomeModule,
ArrayToStringPipe,
DragDropModule,
QuillModule,
NgxCurrencyDirective,
NgSelectModule,
ValidatedInputComponent,
ValidatedQuillComponent,
ValidatedNgSelectComponent,
ValidatedPriceComponent,
ValidatedTextareaComponent,
ValidatedCityComponent,
ValidatedLocationComponent,
NgxCurrencyDirective,
],
providers: [],
templateUrl: './edit-business-listing.component.html',
@ -89,7 +85,7 @@ export class EditBusinessListingComponent {
private loadingService: LoadingService,
private messageService: MessageService,
private route: ActivatedRoute,
private validationMessagesService: ValidationMessagesService,
public validationService: ValidationService,
private authService: AuthService,
) {
this.router.events.subscribe(event => {
@ -124,23 +120,21 @@ export class EditBusinessListingComponent {
}
}
ngOnDestroy() {
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
this.validationService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
}
async save() {
try {
this.listing = await this.listingsService.save(this.listing);
this.router.navigate(['editBusinessListing', this.listing.id]);
this.messageService.addMessage({ severity: 'success', text: 'Listing changes have been persisted', duration: 3000 });
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
this.validationService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
} catch (error) {
this.messageService.addMessage({
severity: 'danger',
text: 'An error occurred while saving the profile - Please check your inputs',
duration: 5000,
});
if (error.error && Array.isArray(error.error?.message)) {
this.validationMessagesService.updateMessages(error.error.message);
}
this.validationService.handleApiError(error.error);
}
}

View File

@ -1,27 +1,172 @@
<div class="container mx-auto p-4">
<div class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg p-6">
<h2 class="text-2xl font-semibold mb-6">Contact Us</h2>
<form #contactForm="ngForm" class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-input label="Your Name" name="name" [(ngModel)]="mailinfo.sender.name"></app-validated-input>
<app-validated-input label="Your Email" name="email" [(ngModel)]="mailinfo.sender.email" kind="email"></app-validated-input>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-input label="Phone Number" name="phoneNumber" [(ngModel)]="mailinfo.sender.phoneNumber" mask="(000) 000-0000"></app-validated-input>
<form class="space-y-6">
<!-- Erste Zeile: Name und E-Mail -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Name Eingabe -->
<div>
<app-validated-ng-select label="State" name="state" [(ngModel)]="mailinfo.sender.state" [items]="selectOptions?.states"></app-validated-ng-select>
<label for="name" class="block text-gray-700 mb-2 font-medium flex items-center">
Your Name
<div *ngIf="validationService.hasMessage('name')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('name')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
id="name"
name="name"
type="text"
[(ngModel)]="mailinfo.sender.name"
placeholder="Enter your name"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('name')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
</div>
<!-- E-Mail Eingabe -->
<div>
<label for="email" class="block text-gray-700 mb-2 font-medium flex items-center">
Your Email
<div *ngIf="validationService.hasMessage('email')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('email')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
id="email"
name="email"
type="email"
[(ngModel)]="mailinfo.sender.email"
placeholder="Enter your email"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('email')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
</div>
</div>
<div>
<app-validated-textarea label="Questions/Comments" name="comments" [(ngModel)]="mailinfo.sender.comments"></app-validated-textarea>
<!-- Zweite Zeile: Telefon und Bundesland -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Telefon Eingabe -->
<div>
<label for="phone" class="block text-gray-700 mb-2 font-medium flex items-center">
Phone Number
<div *ngIf="validationService.hasMessage('phoneNumber')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('phoneNumber')?.message }}
</div>
</div>
</label>
<div class="relative">
<input
id="phone"
name="phoneNumber"
type="tel"
[(ngModel)]="mailinfo.sender.phoneNumber"
mask="(000) 000-0000"
placeholder="(123) 456-7890"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('phoneNumber')"
/>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
/>
</svg>
</div>
</div>
<!-- Bundesland Select -->
<div>
<label for="state" class="block text-gray-700 mb-2 font-medium flex items-center">
State
<div *ngIf="validationService.hasMessage('state')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('state')?.message }}
</div>
</div>
</label>
<div class="relative">
<select
id="state"
name="state"
[(ngModel)]="mailinfo.sender.state"
class="w-full px-3 py-3 pl-10 border border-gray-300 rounded-lg bg-white focus:outline-none focus:border-black focus:ring-0 appearance-none"
[class.border-red-500]="validationService.hasMessage('state')"
>
<option value="" disabled selected>Select your state</option>
<option *ngFor="let state of selectOptions?.states" [value]="state.value">{{ state.name }}</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>
<!-- Kommentarfeld -->
<div>
<button
type="submit"
class="w-full md:w-auto px-6 py-2 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
(click)="mail()"
>
Submit
<label for="comments" class="block text-gray-700 mb-2 font-medium flex items-center">
Questions/Comments
<div *ngIf="validationService.hasMessage('comments')" class="ml-2 relative group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
{{ validationService.getMessage('comments')?.message }}
</div>
</div>
</label>
<textarea
id="comments"
name="comments"
[(ngModel)]="mailinfo.sender.comments"
placeholder="Type your questions or comments here..."
rows="4"
class="w-full px-3 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-black focus:ring-0"
[class.border-red-500]="validationService.hasMessage('comments')"
></textarea>
</div>
<!-- Submit Button -->
<div>
<button (click)="mail()" class="w-full sm:w-auto px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-colors duration-200">
<span class="flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
Submit
</span>
</button>
</div>
</form>

View File

@ -5,22 +5,19 @@ import { RouterModule } from '@angular/router';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { User } from '../../../../../bizmatch-server/src/models/db.model';
import { ErrorResponse, KeycloakUser, MailInfo } from '../../../../../bizmatch-server/src/models/main.model';
import { ValidatedInputComponent } from '../../components/validated-input/validated-input.component';
import { ValidatedNgSelectComponent } from '../../components/validated-ng-select/validated-ng-select.component';
import { ValidatedTextareaComponent } from '../../components/validated-textarea/validated-textarea.component';
import { ValidationMessagesService } from '../../components/validation-messages.service';
import { AuditService } from '../../services/audit.service';
import { AuthService } from '../../services/auth.service';
import { MailService } from '../../services/mail.service';
import { MessageService } from '../../services/message.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { UserService } from '../../services/user.service';
import { ValidationService } from '../../services/validation.service';
import { createMailInfo, map2User } from '../../utils/utils';
@Component({
selector: 'app-email-us',
standalone: true,
imports: [CommonModule, FormsModule, RouterModule, FontAwesomeModule, ValidatedInputComponent, ValidatedTextareaComponent, ValidatedNgSelectComponent],
imports: [CommonModule, FormsModule, RouterModule, FontAwesomeModule],
providers: [],
templateUrl: './email-us.component.html',
styleUrl: './email-us.component.scss',
@ -33,7 +30,7 @@ export class EmailUsComponent {
constructor(
private mailService: MailService,
private userService: UserService,
private validationMessagesService: ValidationMessagesService,
public validationService: ValidationService,
private messageService: MessageService,
public selectOptions: SelectOptionsService,
private auditService: AuditService,
@ -50,11 +47,11 @@ export class EmailUsComponent {
}
}
ngOnDestroy() {
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
this.validationService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
}
async mail() {
try {
this.validationMessagesService.updateMessages([]);
this.validationService.clearMessages();
this.mailinfo.email = 'support@bizmatch.net';
await this.mailService.mail(this.mailinfo);
this.messageService.addMessage({ severity: 'success', text: 'Your request has been forwarded to the support team of bizmatch.', duration: 3000 });
@ -66,9 +63,7 @@ export class EmailUsComponent {
text: 'Please check your inputs',
duration: 5000,
});
if (error.error && Array.isArray(error.error?.message)) {
this.validationMessagesService.updateMessages(error.error.message);
}
this.validationService.handleApiError(error.error);
}
}
containsError(fieldname: string) {

View File

@ -118,10 +118,13 @@
<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 class="relative pb-[66.67%]">
<!-- 2/3 = 66.67% für 3:2 Seitenverhältnis -->
<video controls poster="assets/images/video-poster1.png" class="absolute inset-0 w-full h-full rounded-lg shadow-xl object-cover">
<source src="assets/videos/Bizmatch30Spot.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import { RouterLink } from '@angular/router';
import { User } from '../../../../../bizmatch-server/src/models/db.model';
import { KeycloakUser } from '../../../../../bizmatch-server/src/models/main.model';
import { AuthService } from '../../services/auth.service';
@ -12,7 +12,7 @@ import { map2User } from '../../utils/utils';
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
standalone: true,
imports: [CommonModule, RouterOutlet, RouterLink],
imports: [CommonModule, RouterLink],
})
export class HomeComponent implements OnInit {
showMobileMenu = false;

View File

@ -4,7 +4,6 @@
{{ isLoginMode ? 'Login' : 'Sign Up' }}
</h2>
<!-- Toggle Switch mit Flowbite -->
<div class="flex items-center justify-center mb-6">
<span class="mr-3 text-gray-700 font-medium">Login</span>
<label for="toggle-switch" class="inline-flex relative items-center cursor-pointer">
@ -20,13 +19,7 @@
<div class="mb-4">
<label for="email" class="block text-gray-700 mb-2 font-medium">E-Mail</label>
<div class="relative">
<input
id="email"
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"
/>
<input id="email" 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" />
<fa-icon [icon]="envelope" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>
</div>
@ -35,13 +28,7 @@
<div class="mb-4">
<label for="password" class="block text-gray-700 mb-2 font-medium">Password</label>
<div class="relative">
<input
id="password"
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"
/>
<input id="password" 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" />
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>
</div>
@ -50,13 +37,7 @@
<div *ngIf="!isLoginMode" class="mb-6">
<label for="confirmPassword" class="block text-gray-700 mb-2 font-medium">Confirm Password</label>
<div class="relative">
<input
id="confirmPassword"
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"
/>
<input id="confirmPassword" 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" />
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>
</div>

View File

@ -6,7 +6,6 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { User } from '../../../../../bizmatch-server/src/models/db.model';
import { ListingType } from '../../../../../bizmatch-server/src/models/main.model';
import { ConfirmationComponent } from '../../components/confirmation.component';
import { MessageComponent } from '../../components/message/message.component';
import { AuthService } from '../../services/auth.service';
import { ConfirmationService } from '../../services/confirmation.service';
import { ListingsService } from '../../services/listings.service';
@ -18,7 +17,7 @@ import { map2User } from '../../utils/utils';
@Component({
selector: 'app-my-listing',
standalone: true,
imports: [CommonModule, FormsModule, RouterModule, FontAwesomeModule, ConfirmationComponent, MessageComponent],
imports: [CommonModule, FormsModule, RouterModule, FontAwesomeModule, ConfirmationComponent],
providers: [],
templateUrl: './my-listing.component.html',
styleUrl: './my-listing.component.scss',

View File

@ -34,7 +34,11 @@ export class ListingsService {
return lastValueFrom(this.http.get<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/listings/business/favorites/all`));
}
async save(listing: any) {
return await lastValueFrom(this.http.put<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/business`, listing));
if (listing.id) {
return await lastValueFrom(this.http.put<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/business`, listing));
} else {
return await lastValueFrom(this.http.post<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/business`, listing));
}
}
async deleteBusinessListing(id: string) {
await lastValueFrom(this.http.delete<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/business/listing/${id}`));

View File

@ -0,0 +1,60 @@
import { Injectable } from '@angular/core';
export interface ValidationMessage {
field: string;
message: string;
}
@Injectable({
providedIn: 'root',
})
export class ValidationService {
private messages: ValidationMessage[] = [];
constructor() {}
/**
* Fügt Validierungsnachrichten hinzu oder aktualisiert bestehende
* @param messages Array von Validierungsnachrichten
*/
setMessages(messages: ValidationMessage[]): void {
this.messages = messages;
}
/**
* Löscht alle Validierungsmeldungen
*/
clearMessages(): void {
this.messages = [];
}
/**
* Prüft, ob für ein bestimmtes Feld eine Validierungsmeldung existiert
* @param field Name des Feldes
* @returns true, wenn eine Meldung existiert
*/
hasMessage(field: string): boolean {
return this.messages.some(message => message.field === field);
}
/**
* Gibt die Validierungsmeldung für ein bestimmtes Feld zurück
* @param field Name des Feldes
* @returns ValidationMessage oder null, wenn keine Meldung existiert
*/
getMessage(field: string): ValidationMessage | null {
return this.messages.find(message => message.field === field) || null;
}
/**
* Hilfsmethode zur Verarbeitung von API-Fehlermeldungen
* @param error API-Fehler mit message-Array
*/
handleApiError(error: any): void {
if (error && error.message && Array.isArray(error.message)) {
this.setMessages(error.message);
} else {
this.clearMessages();
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

View File

@ -2,15 +2,9 @@ export const hostname = window.location.hostname;
export const environment = {
// apiBaseUrl: 'http://localhost:3000',
apiBaseUrl: `http://${hostname}:4200`,
imageBaseUrl: 'https://dev.bizmatch.net',
imageBaseUrl: `http://${hostname}:4200`,
buildVersion: '<BUILD_VERSION>',
mailinfoUrl: 'https://dev.bizmatch.net',
keycloak: {
url: 'https://auth.bizmatch.net',
realm: 'bizmatch-dev',
clientId: 'bizmatch-dev',
redirectUri: 'https://dev.bizmatch.net',
},
ipinfo_token: '7029590fb91214',
firebaseConfig: {
apiKey: 'AIzaSyBqVutQqdgUzwD9tKiKJrJq2Q6rD1hNdzw',

View File

@ -1,27 +1,14 @@
@import '@ng-select/ng-select/themes/default.theme.css';
@use 'ngx-sharebuttons/themes/default';
@use 'tailwindcss';
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
@import '@ng-select/ng-select/themes/default.theme.css';
@import '@fortawesome/fontawesome-free/css/all.min.css';
@import 'tailwindcss';
@import 'ngx-sharebuttons/themes/default';
@config "../tailwind.config.js";
.color-automotive {
color: #00bc7d;
}
.color-franchise {
color: #c27aff;
}
.color-service {
color: #16bdca;
}
.color-foodAndRestaurant {
color: #b45309;
}
.color-manufacturing {
color: #f98080;
}
:root {
--text-color-secondary: rgba(255, 255, 255);
--wrapper-width: 1491px;
@ -45,15 +32,6 @@ app-root {
flex-direction: column;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
}
body,
input,
button,
@ -124,3 +102,22 @@ input::placeholder,
textarea::placeholder {
color: #999 !important;
}
button.share {
font-size: 13px;
transform: translateY(-2px) scale(1.03);
margin-right: 4px;
margin-left: 2px;
border-radius: 4px;
i {
font-size: 15px;
}
}
.share-edit {
background-color: #0088cc;
}
.share-save {
background-color: #e60023;
}
.share-email {
background-color: #ff961c;
}

View File

@ -19,7 +19,6 @@ module.exports = {
],
content: [
"./src/**/*.{html,ts}",
"./node_modules/flowbite/**/*.js" // add this line
],
theme: {
extend: {
@ -43,9 +42,6 @@ module.exports = {
// ... andere benutzerdefinierte Schatten, falls vorhanden
}
},
},
plugins: [
require('flowbite/plugin') // add this line
],
}
}

View File

@ -5,7 +5,7 @@ import { ImageType, KeyValue, KeyValueAsSortBy, KeyValueStyle } from '../models/
export class SelectOptionsService {
constructor() {}
public typesOfBusiness: Array<KeyValueStyle> = [
{ name: 'Automotive', value: 'automotive', oldValue: '1', icon: 'fa-solid fa-car', textColorClass: 'text-green-400' },
{ name: 'Automotive', value: 'automotive', oldValue: '1', icon: 'fa-solid fa-car', textColorClass: 'text-green-500' },
{ name: 'Industrial Services', value: 'industrialServices', oldValue: '2', icon: 'fa-solid fa-industry', textColorClass: 'text-yellow-400' },
{ name: 'Food and Restaurant', value: 'foodAndRestaurant', oldValue: '13', icon: 'fa-solid fa-utensils', textColorClass: 'text-amber-700' },
{ name: 'Real Estate', value: 'realEstate', oldValue: '3', icon: 'fa-solid fa-building', textColorClass: 'text-blue-400' },

View File

@ -1,7 +1,7 @@
<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">
<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" />
<img src="assets/images/header-logo.png" class="h-10" alt="Flowbite Logo" />
</a>
<div class="flex items-center md:order-2 space-x-3 rtl:space-x-reverse">
<!-- Filter button -->