Compare commits
21 Commits
047c723364
...
timo-clean
| Author | SHA1 | Date | |
|---|---|---|---|
| fca746cef6 | |||
| 79098f59c6 | |||
| 345761da87 | |||
| 7e00b4d71b | |||
| 715220f6d5 | |||
| dc79ac3df7 | |||
| a545b84f6c | |||
| 2d293d8b12 | |||
| d008b50892 | |||
| 1a1eaa46ae | |||
| a9dcb66e5b | |||
| 33ea71dc12 | |||
| 91bcf3c2ed | |||
| 36ef7eb4bf | |||
| ae12eb87f0 | |||
| 0b4e4207d1 | |||
| 53537226cd | |||
| 00597a796a | |||
| 8b3c79b5ff | |||
| a7d3d2d958 | |||
| 49528a5c37 |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
@@ -1,19 +1,25 @@
|
|||||||
# Build Stage
|
# --- STAGE 1: Build ---
|
||||||
FROM node:18-alpine AS build
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
# HIER KEIN NODE_ENV=production setzen! Wir brauchen devDependencies zum Bauen.
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Runtime Stage
|
# --- STAGE 2: Runtime ---
|
||||||
FROM node:18-alpine
|
FROM node:22-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /app/dist /app/dist
|
|
||||||
COPY --from=build /app/package*.json /app/
|
|
||||||
|
|
||||||
RUN npm install --production
|
# HIER ist es richtig!
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
CMD ["node", "dist/main.js"]
|
COPY --from=builder /app/dist /app/dist
|
||||||
|
COPY --from=builder /app/package*.json /app/
|
||||||
|
|
||||||
|
# Installiert nur "dependencies" (Nest core, TypeORM, Helmet, Sharp etc.)
|
||||||
|
# "devDependencies" (TypeScript, Jest, ESLint) werden weggelassen.
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
# WICHTIG: Pfad prüfen (siehe Punkt 2 unten)
|
||||||
|
CMD ["node", "dist/src/main.js"]
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
services:
|
|
||||||
app:
|
|
||||||
image: node:22-alpine
|
|
||||||
container_name: bizmatch-app
|
|
||||||
working_dir: /app
|
|
||||||
volumes:
|
|
||||||
- ./:/app
|
|
||||||
- node_modules:/app/node_modules
|
|
||||||
ports:
|
|
||||||
- '3001:3001'
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=development
|
|
||||||
- DATABASE_URL
|
|
||||||
command: sh -c "if [ ! -f node_modules/.installed ]; then npm ci && touch node_modules/.installed; fi && npm run build && node dist/src/main.js"
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
networks:
|
|
||||||
- bizmatch
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
container_name: bizmatchdb
|
|
||||||
image: postgres:17-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- bizmatch-db-data:/var/lib/postgresql/data
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
ports:
|
|
||||||
- '5434:5432'
|
|
||||||
networks:
|
|
||||||
- bizmatch
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
bizmatch-db-data:
|
|
||||||
driver: local
|
|
||||||
node_modules:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
networks:
|
|
||||||
bizmatch:
|
|
||||||
external: true
|
|
||||||
@@ -14,7 +14,7 @@ async function bootstrap() {
|
|||||||
app.useLogger(logger);
|
app.useLogger(logger);
|
||||||
//app.use('/bizmatch/payment/webhook', bodyParser.raw({ type: 'application/json' }));
|
//app.use('/bizmatch/payment/webhook', bodyParser.raw({ type: 'application/json' }));
|
||||||
// Serve static files from pictures directory
|
// Serve static files from pictures directory
|
||||||
// app.use('/pictures', express.static('pictures'));
|
app.use('/pictures', express.static('pictures'));
|
||||||
|
|
||||||
app.setGlobalPrefix('bizmatch');
|
app.setGlobalPrefix('bizmatch');
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,41 @@
|
|||||||
|
# STAGE 1: Build
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
# Wir erstellen ein Arbeitsverzeichnis, das eine Ebene über dem Projekt liegt
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# 1. Wir kopieren die Backend-Models an die Stelle, wo Angular sie erwartet
|
||||||
|
# Deine Pfade suchen nach ../bizmatch-server, also legen wir es daneben.
|
||||||
|
COPY bizmatch-server/src/models ./bizmatch-server/src/models
|
||||||
|
|
||||||
|
# 2. Jetzt kümmern wir uns um das Frontend
|
||||||
|
# Wir kopieren erst die package Files für besseres Caching
|
||||||
|
COPY bizmatch/package*.json ./bizmatch/
|
||||||
|
|
||||||
|
# Wechseln in den Frontend Ordner zum Installieren
|
||||||
|
WORKDIR /usr/src/app/bizmatch
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# 3. Den Rest des Frontends kopieren
|
||||||
|
COPY bizmatch/ .
|
||||||
|
|
||||||
|
# 4. Bauen
|
||||||
|
RUN npm run build:ssr
|
||||||
|
|
||||||
|
# --- STAGE 2: Runtime ---
|
||||||
FROM node:22-alpine
|
FROM node:22-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# GANZEN dist-Ordner kopieren, nicht nur bizmatch
|
ENV NODE_ENV=production
|
||||||
COPY dist ./dist
|
ENV PORT=4000
|
||||||
COPY package*.json ./
|
|
||||||
|
# Kopiere das Ergebnis aus dem Builder (Pfad beachten!)
|
||||||
|
COPY --from=builder /usr/src/app/bizmatch/dist /app/dist
|
||||||
|
COPY --from=builder /usr/src/app/bizmatch/package*.json /app/
|
||||||
|
|
||||||
RUN npm ci --omit=dev
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
EXPOSE 4200
|
EXPOSE 4000
|
||||||
|
|
||||||
CMD ["node", "dist/bizmatch/server/server.mjs"]
|
CMD ["node", "dist/bizmatch/server/server.mjs"]
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
"outputPath": "dist/bizmatch",
|
"outputPath": "dist/bizmatch",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"browser": "src/main.ts",
|
"browser": "src/main.ts",
|
||||||
|
"server": "src/main.server.ts",
|
||||||
|
"prerender": false,
|
||||||
|
"ssr": {
|
||||||
|
"entry": "server.ts"
|
||||||
|
},
|
||||||
"allowedCommonJsDependencies": [
|
"allowedCommonJsDependencies": [
|
||||||
"quill-delta",
|
"quill-delta",
|
||||||
"leaflet",
|
"leaflet",
|
||||||
@@ -48,7 +53,10 @@
|
|||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
"src/styles/lazy-load.css"
|
"src/styles/lazy-load.css",
|
||||||
|
"node_modules/quill/dist/quill.snow.css",
|
||||||
|
"node_modules/leaflet/dist/leaflet.css",
|
||||||
|
"node_modules/ngx-sharebuttons/themes/default.scss"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
@@ -61,7 +69,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "anyComponentStyle",
|
"type": "anyComponentStyle",
|
||||||
"maximumWarning": "10kb",
|
"maximumWarning": "30kb",
|
||||||
"maximumError": "30kb"
|
"maximumError": "30kb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -70,7 +78,8 @@
|
|||||||
"development": {
|
"development": {
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"ssr": false
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"fileReplacements": [
|
"fileReplacements": [
|
||||||
@@ -92,7 +101,8 @@
|
|||||||
],
|
],
|
||||||
"optimization": true,
|
"optimization": true,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"outputHashing": "all"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "production"
|
"defaultConfiguration": "production"
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
services:
|
|
||||||
bizmatch-ssr:
|
|
||||||
build: .
|
|
||||||
image: bizmatch-ssr
|
|
||||||
container_name: bizmatch-ssr
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- '4200:4200'
|
|
||||||
environment:
|
|
||||||
NODE_ENV: DEVELOPMENT
|
|
||||||
@@ -104,6 +104,8 @@ export async function app(): Promise<express.Express> {
|
|||||||
// All regular routes use the Angular engine
|
// All regular routes use the Angular engine
|
||||||
server.get('*', async (req, res, next) => {
|
server.get('*', async (req, res, next) => {
|
||||||
console.log(`[SSR] Handling request: ${req.method} ${req.url}`);
|
console.log(`[SSR] Handling request: ${req.method} ${req.url}`);
|
||||||
|
// Cache SSR-rendered pages at CDN level
|
||||||
|
res.setHeader('Cache-Control', 'public, s-maxage=300, stale-while-revalidate=600');
|
||||||
try {
|
try {
|
||||||
const response = await angularApp.handle(req);
|
const response = await angularApp.handle(req);
|
||||||
if (response) {
|
if (response) {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@a
|
|||||||
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
|
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
|
||||||
import { getAuth, provideAuth } from '@angular/fire/auth';
|
import { getAuth, provideAuth } from '@angular/fire/auth';
|
||||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
|
import { GALLERY_CONFIG, GalleryConfig } from 'ng-gallery';
|
||||||
|
import { provideQuillConfig } from 'ngx-quill';
|
||||||
|
import { provideShareButtonsOptions, SharerMethods, withConfig } from 'ngx-sharebuttons';
|
||||||
|
import { shareIcons } from 'ngx-sharebuttons/icons';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { AuthInterceptor } from './interceptors/auth.interceptor';
|
import { AuthInterceptor } from './interceptors/auth.interceptor';
|
||||||
@@ -44,6 +48,13 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provide: 'TIMEOUT_DURATION',
|
provide: 'TIMEOUT_DURATION',
|
||||||
useValue: 5000, // Standard-Timeout von 5 Sekunden
|
useValue: 5000, // Standard-Timeout von 5 Sekunden
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: GALLERY_CONFIG,
|
||||||
|
useValue: {
|
||||||
|
autoHeight: true,
|
||||||
|
imageSize: 'cover',
|
||||||
|
} as GalleryConfig,
|
||||||
|
},
|
||||||
{ provide: ErrorHandler, useClass: GlobalErrorHandler }, // Registriere den globalen ErrorHandler
|
{ provide: ErrorHandler, useClass: GlobalErrorHandler }, // Registriere den globalen ErrorHandler
|
||||||
{
|
{
|
||||||
provide: IMAGE_CONFIG,
|
provide: IMAGE_CONFIG,
|
||||||
@@ -51,6 +62,13 @@ export const appConfig: ApplicationConfig = {
|
|||||||
disableImageSizeWarning: true,
|
disableImageSizeWarning: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
provideShareButtonsOptions(
|
||||||
|
shareIcons(),
|
||||||
|
withConfig({
|
||||||
|
debug: true,
|
||||||
|
sharerMethod: SharerMethods.Anchor,
|
||||||
|
}),
|
||||||
|
),
|
||||||
provideRouter(
|
provideRouter(
|
||||||
routes,
|
routes,
|
||||||
withEnabledBlockingInitialNavigation(),
|
withEnabledBlockingInitialNavigation(),
|
||||||
@@ -61,6 +79,18 @@ export const appConfig: ApplicationConfig = {
|
|||||||
),
|
),
|
||||||
...(environment.production ? [POSTHOG_INIT_PROVIDER] : []),
|
...(environment.production ? [POSTHOG_INIT_PROVIDER] : []),
|
||||||
provideAnimations(),
|
provideAnimations(),
|
||||||
|
provideQuillConfig({
|
||||||
|
modules: {
|
||||||
|
syntax: true,
|
||||||
|
toolbar: [
|
||||||
|
['bold', 'italic', 'underline'], // Einige Standardoptionen
|
||||||
|
[{ header: [1, 2, 3, false] }], // Benutzerdefinierte Header
|
||||||
|
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||||
|
[{ color: [] }], // Dropdown mit Standardfarben
|
||||||
|
['clean'], // Entfernt Formatierungen
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
|
provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
|
||||||
provideAuth(() => getAuth()),
|
provideAuth(() => getAuth()),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
import { RenderMode, ServerRoute } from '@angular/ssr';
|
import { RenderMode, ServerRoute } from '@angular/ssr';
|
||||||
|
|
||||||
export const serverRoutes: ServerRoute[] = [
|
export const serverRoutes: ServerRoute[] = [
|
||||||
{
|
{ path: 'home', renderMode: RenderMode.Server }, // Das hatten wir vorhin gefixt
|
||||||
path: '**',
|
|
||||||
renderMode: RenderMode.Server
|
// WICHTIG: Alle geschützten Routen nur im Browser rendern!
|
||||||
}
|
// Damit überspringt der Server den AuthGuard Check komplett und schickt
|
||||||
|
// nur eine leere Hülle (index.html), die der Browser dann füllt.
|
||||||
|
{ path: 'account', renderMode: RenderMode.Client },
|
||||||
|
{ path: 'account/**', renderMode: RenderMode.Client },
|
||||||
|
{ path: 'myListings', renderMode: RenderMode.Client },
|
||||||
|
{ path: 'myFavorites', renderMode: RenderMode.Client },
|
||||||
|
{ path: 'createBusinessListing', renderMode: RenderMode.Client },
|
||||||
|
{ path: 'createCommercialPropertyListing', renderMode: RenderMode.Client },
|
||||||
|
{ path: 'editBusinessListing/**', renderMode: RenderMode.Client },
|
||||||
|
{ path: 'editCommercialPropertyListing/**', renderMode: RenderMode.Client },
|
||||||
|
|
||||||
|
// Statische Seiten
|
||||||
|
{ path: 'terms-of-use', renderMode: RenderMode.Prerender },
|
||||||
|
{ path: 'privacy-statement', renderMode: RenderMode.Prerender },
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
{ path: '**', renderMode: RenderMode.Server }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -18,8 +18,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-col lg:flex-row items-center order-2 lg:order-3">
|
<div class="flex flex-col lg:flex-row items-center order-2 lg:order-3">
|
||||||
<div class="mb-4 lg:mb-0 lg:mr-6 text-center lg:text-right">
|
<div class="mb-4 lg:mb-0 lg:mr-6 text-center lg:text-right">
|
||||||
<p class="text-sm text-neutral-600 mb-1 lg:mb-2">BizMatch, Inc., 1001 Blucher Street, Corpus</p>
|
<p class="text-sm text-neutral-600 mb-1 lg:mb-2">Contact: BizMatch, Inc.</p>
|
||||||
<p class="text-sm text-neutral-600">Christi, Texas 78401</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 lg:mb-0 flex flex-col items-center lg:items-end">
|
<div class="mb-4 lg:mb-0 flex flex-col items-center lg:items-end">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
@if(isFilterUrl()){
|
@if(isFilterUrl()){
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<button type="button" id="sortDropdownButton"
|
<button type="button" id="sortDropdownButton" aria-label="Sort listings" aria-haspopup="listbox"
|
||||||
class="max-sm:hidden px-4 py-2 text-sm font-medium bg-white border border-neutral-200 rounded-lg hover:bg-neutral-100 hover:text-primary-600 dark:bg-neutral-800 dark:text-neutral-400 dark:border-neutral-600 dark:hover:text-white dark:hover:bg-neutral-700"
|
class="max-sm:hidden px-4 py-2 text-sm font-medium bg-white border border-neutral-200 rounded-lg hover:bg-neutral-100 hover:text-primary-600 dark:bg-neutral-800 dark:text-neutral-400 dark:border-neutral-600 dark:hover:text-white dark:hover:bg-neutral-700"
|
||||||
(click)="toggleSortDropdown()"
|
(click)="toggleSortDropdown()"
|
||||||
[ngClass]="{ 'text-primary-500': selectOptions.getSortByOption(sortBy) !== 'Sort', 'text-neutral-900': selectOptions.getSortByOption(sortBy) === 'Sort' }">
|
[ngClass]="{ 'text-primary-500': selectOptions.getSortByOption(sortBy) !== 'Sort', 'text-neutral-900': selectOptions.getSortByOption(sortBy) === 'Sort' }">
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Mobile filter button -->
|
<!-- Mobile filter button -->
|
||||||
<div class="md:hidden flex justify-center pb-4">
|
<div class="md:hidden flex justify-center pb-4">
|
||||||
<button (click)="toggleSortDropdown()" type="button" id="sortDropdownMobileButton"
|
<button (click)="toggleSortDropdown()" type="button" id="sortDropdownMobileButton" aria-label="Sort listings" aria-haspopup="listbox"
|
||||||
class="mx-4 w-1/2 px-4 py-2 text-sm font-medium bg-white border border-neutral-200 rounded-lg hover:bg-neutral-100 hover:text-primary-600 focus:ring-2 focus:ring-primary-600 focus:text-primary-600 dark:bg-neutral-800 dark:text-neutral-400 dark:border-neutral-600 dark:hover:text-white dark:hover:bg-neutral-700"
|
class="mx-4 w-1/2 px-4 py-2 text-sm font-medium bg-white border border-neutral-200 rounded-lg hover:bg-neutral-100 hover:text-primary-600 focus:ring-2 focus:ring-primary-600 focus:text-primary-600 dark:bg-neutral-800 dark:text-neutral-400 dark:border-neutral-600 dark:hover:text-white dark:hover:bg-neutral-700"
|
||||||
[ngClass]="{ 'text-primary-500': selectOptions.getSortByOption(sortBy) !== 'Sort', 'text-neutral-900': selectOptions.getSortByOption(sortBy) === 'Sort' }">
|
[ngClass]="{ 'text-primary-500': selectOptions.getSortByOption(sortBy) !== 'Sort', 'text-neutral-900': selectOptions.getSortByOption(sortBy) === 'Sort' }">
|
||||||
<i class="fas fa-sort mr-2"></i>{{ selectOptions.getSortByOption(sortBy) }}
|
<i class="fas fa-sort mr-2"></i>{{ selectOptions.getSortByOption(sortBy) }}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="p-6 space-y-6">
|
<div class="p-6 space-y-6">
|
||||||
<div class="flex space-x-4 mb-4">
|
<div class="flex space-x-4 mb-4">
|
||||||
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
|
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
|
||||||
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
|
<button type="button" aria-label="Clear all filters" data-tooltip-target="tooltip-light" class="flex self-center ml-2 cursor-pointer text-primary-500 p-1" (click)="clearFilter()"><i class="fa-solid fa-trash-can"></i></button>
|
||||||
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
||||||
Clear all Filter
|
Clear all Filter
|
||||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
@@ -25,22 +25,22 @@
|
|||||||
<!-- Display active filters as tags -->
|
<!-- Display active filters as tags -->
|
||||||
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
|
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
|
||||||
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.types?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.types?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Types: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Types: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Professional Name: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Professional Name: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.companyName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.companyName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Company: {{ criteria.companyName }} <button (click)="removeFilter('companyName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Company: {{ criteria.companyName }} <button (click)="removeFilter('companyName')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.counties?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.counties?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Areas Served: {{ criteria.counties.join(', ') }} <button (click)="removeFilter('counties')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Areas Served: {{ criteria.counties.join(', ') }} <button (click)="removeFilter('counties')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if(criteria.criteriaType==='brokerListings') {
|
@if(criteria.criteriaType==='brokerListings') {
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
<div *ngIf="!isModal" class="space-y-6 pb-10">
|
<div *ngIf="!isModal" class="space-y-6 pb-10">
|
||||||
<div class="flex space-x-4 mb-4">
|
<div class="flex space-x-4 mb-4">
|
||||||
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
|
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
|
||||||
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
|
<button type="button" aria-label="Clear all filters" data-tooltip-target="tooltip-light" class="flex self-center ml-2 cursor-pointer text-primary-500 p-1" (click)="clearFilter()"><i class="fa-solid fa-trash-can"></i></button>
|
||||||
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
||||||
Clear all Filter
|
Clear all Filter
|
||||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
@@ -150,22 +150,22 @@
|
|||||||
<!-- Display active filters as tags -->
|
<!-- Display active filters as tags -->
|
||||||
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
|
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
|
||||||
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.types?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.types?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Types: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Types: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Professional Name: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Professional Name: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.companyName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.companyName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Company: {{ criteria.companyName }} <button (click)="removeFilter('companyName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Company: {{ criteria.companyName }} <button (click)="removeFilter('companyName')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.counties?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.counties?.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Areas Served: {{ criteria.counties.join(', ') }} <button (click)="removeFilter('counties')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Areas Served: {{ criteria.counties.join(', ') }} <button (click)="removeFilter('counties')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if(criteria.criteriaType==='brokerListings') {
|
@if(criteria.criteriaType==='brokerListings') {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="p-6 space-y-6">
|
<div class="p-6 space-y-6">
|
||||||
<div class="flex space-x-4 mb-4">
|
<div class="flex space-x-4 mb-4">
|
||||||
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
|
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
|
||||||
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
|
<button type="button" aria-label="Clear all filters" data-tooltip-target="tooltip-light" class="flex self-center ml-2 cursor-pointer text-primary-500 p-1" (click)="clearFilter()"><i class="fa-solid fa-trash-can"></i></button>
|
||||||
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
||||||
Clear all Filter
|
Clear all Filter
|
||||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
@@ -25,22 +25,22 @@
|
|||||||
<!-- Display active filters as tags -->
|
<!-- Display active filters as tags -->
|
||||||
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
|
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
|
||||||
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if(criteria.criteriaType==='commercialPropertyListings') {
|
@if(criteria.criteriaType==='commercialPropertyListings') {
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
<div *ngIf="!isModal" class="space-y-6 pb-10">
|
<div *ngIf="!isModal" class="space-y-6 pb-10">
|
||||||
<div class="flex space-x-4 mb-4">
|
<div class="flex space-x-4 mb-4">
|
||||||
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
|
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
|
||||||
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
|
<button type="button" aria-label="Clear all filters" data-tooltip-target="tooltip-light" class="flex self-center ml-2 cursor-pointer text-primary-500 p-1" (click)="clearFilter()"><i class="fa-solid fa-trash-can"></i></button>
|
||||||
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
||||||
Clear all Filter
|
Clear all Filter
|
||||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
@@ -146,22 +146,22 @@
|
|||||||
<!-- Display active filters as tags -->
|
<!-- Display active filters as tags -->
|
||||||
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
|
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
|
||||||
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]" aria-label="Remove filter">×</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if(criteria.criteriaType==='commercialPropertyListings') {
|
@if(criteria.criteriaType==='commercialPropertyListings') {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="p-6 space-y-6">
|
<div class="p-6 space-y-6">
|
||||||
<div class="flex space-x-4 mb-4">
|
<div class="flex space-x-4 mb-4">
|
||||||
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
|
<button class="text-primary-600 font-medium border-b-2 border-primary-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
|
||||||
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
|
<button type="button" aria-label="Clear all filters" data-tooltip-target="tooltip-light" class="flex self-center ml-2 cursor-pointer text-primary-500 p-1" (click)="clearFilter()"><i class="fa-solid fa-trash-can"></i></button>
|
||||||
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
||||||
Clear all Filter
|
Clear all Filter
|
||||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
@@ -25,37 +25,37 @@
|
|||||||
<!-- Display active filters as tags -->
|
<!-- Display active filters as tags -->
|
||||||
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
|
<div class="flex flex-wrap gap-2 mb-4" *ngIf="hasActiveFilters()">
|
||||||
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
State: {{ criteria.state }} <button (click)="removeFilter('state')" aria-label="Remove state filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" aria-label="Remove city filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" aria-label="Remove price filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Revenue: {{ criteria.minRevenue || 'Any' }} - {{ criteria.maxRevenue || 'Any' }} <button (click)="removeFilter('revenue')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Revenue: {{ criteria.minRevenue || 'Any' }} - {{ criteria.maxRevenue || 'Any' }} <button (click)="removeFilter('revenue')" aria-label="Remove revenue filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Cashflow: {{ criteria.minCashFlow || 'Any' }} - {{ criteria.maxCashFlow || 'Any' }} <button (click)="removeFilter('cashflow')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Cashflow: {{ criteria.minCashFlow || 'Any' }} - {{ criteria.maxCashFlow || 'Any' }} <button (click)="removeFilter('cashflow')" aria-label="Remove cashflow filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Title: {{ criteria.title }} <button (click)="removeFilter('title')" aria-label="Remove title filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" aria-label="Remove categories filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="selectedPropertyType" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="selectedPropertyType" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Property Type: {{ getSelectedPropertyTypeName() }} <button (click)="removeFilter('propertyType')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Property Type: {{ getSelectedPropertyTypeName() }} <button (click)="removeFilter('propertyType')" aria-label="Remove property type filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Employees: {{ criteria.minNumberEmployees || 'Any' }} - {{ criteria.maxNumberEmployees || 'Any' }} <button (click)="removeFilter('employees')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Employees: {{ criteria.minNumberEmployees || 'Any' }} - {{ criteria.maxNumberEmployees || 'Any' }} <button (click)="removeFilter('employees')" aria-label="Remove employees filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.establishedMin" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.establishedMin" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Established: {{ criteria.establishedMin || 'Any' }} <button (click)="removeFilter('established')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Established: {{ criteria.establishedMin || 'Any' }} <button (click)="removeFilter('established')" aria-label="Remove established filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" aria-label="Remove broker filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-6">
|
<div class="grid grid-cols-1 gap-6">
|
||||||
@@ -148,6 +148,7 @@
|
|||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
[closeOnSelect]="true"
|
[closeOnSelect]="true"
|
||||||
placeholder="Select categories"
|
placeholder="Select categories"
|
||||||
|
aria-label="Category"
|
||||||
></ng-select>
|
></ng-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -160,6 +161,7 @@
|
|||||||
[ngModel]="selectedPropertyType"
|
[ngModel]="selectedPropertyType"
|
||||||
(ngModelChange)="onPropertyTypeChange($event)"
|
(ngModelChange)="onPropertyTypeChange($event)"
|
||||||
placeholder="Select property type"
|
placeholder="Select property type"
|
||||||
|
aria-label="Type of Property"
|
||||||
></ng-select>
|
></ng-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -220,7 +222,7 @@
|
|||||||
<div *ngIf="!isModal" class="space-y-6">
|
<div *ngIf="!isModal" class="space-y-6">
|
||||||
<div class="flex space-x-4 mb-4">
|
<div class="flex space-x-4 mb-4">
|
||||||
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
|
<h3 class="text-xl font-semibold text-neutral-900">Filter ({{ numberOfResults$ | async }})</h3>
|
||||||
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-primary-500" (click)="clearFilter()"></i>
|
<button type="button" aria-label="Clear all filters" data-tooltip-target="tooltip-light" class="flex self-center ml-2 cursor-pointer text-primary-500 p-1" (click)="clearFilter()"><i class="fa-solid fa-trash-can"></i></button>
|
||||||
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-neutral-900 bg-white border border-neutral-200 rounded-lg shadow-sm opacity-0 tooltip">
|
||||||
Clear all Filter
|
Clear all Filter
|
||||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
@@ -229,37 +231,37 @@
|
|||||||
<!-- Display active filters as tags -->
|
<!-- Display active filters as tags -->
|
||||||
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
|
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
|
||||||
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.state" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
State: {{ criteria.state }} <button (click)="removeFilter('state')" aria-label="Remove state filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.city" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
City: {{ criteria.city.name }} <button (click)="removeFilter('city')" aria-label="Remove city filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" aria-label="Remove price filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Revenue: {{ criteria.minRevenue || 'Any' }} - {{ criteria.maxRevenue || 'Any' }} <button (click)="removeFilter('revenue')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Revenue: {{ criteria.minRevenue || 'Any' }} - {{ criteria.maxRevenue || 'Any' }} <button (click)="removeFilter('revenue')" aria-label="Remove revenue filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Cashflow: {{ criteria.minCashFlow || 'Any' }} - {{ criteria.maxCashFlow || 'Any' }} <button (click)="removeFilter('cashflow')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Cashflow: {{ criteria.minCashFlow || 'Any' }} - {{ criteria.maxCashFlow || 'Any' }} <button (click)="removeFilter('cashflow')" aria-label="Remove cashflow filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.title" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Title: {{ criteria.title }} <button (click)="removeFilter('title')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Title: {{ criteria.title }} <button (click)="removeFilter('title')" aria-label="Remove title filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.types.length" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" aria-label="Remove categories filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="selectedPropertyType" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="selectedPropertyType" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Property Type: {{ getSelectedPropertyTypeName() }} <button (click)="removeFilter('propertyType')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Property Type: {{ getSelectedPropertyTypeName() }} <button (click)="removeFilter('propertyType')" aria-label="Remove property type filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Employees: {{ criteria.minNumberEmployees || 'Any' }} - {{ criteria.maxNumberEmployees || 'Any' }} <button (click)="removeFilter('employees')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Employees: {{ criteria.minNumberEmployees || 'Any' }} - {{ criteria.maxNumberEmployees || 'Any' }} <button (click)="removeFilter('employees')" aria-label="Remove employees filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.establishedMin" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.establishedMin" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Years established: {{ criteria.establishedMin || 'Any' }} <button (click)="removeFilter('established')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Years established: {{ criteria.establishedMin || 'Any' }} <button (click)="removeFilter('established')" aria-label="Remove established filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
<span *ngIf="criteria.brokerName" class="bg-neutral-200 text-neutral-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
|
||||||
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
|
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" aria-label="Remove broker filter" class="ml-1 text-red-500 hover:text-red-700 min-w-[24px] min-h-[24px]">×</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if(criteria.criteriaType==='businessListings') {
|
@if(criteria.criteriaType==='businessListings') {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, forwardRef } from '@angular/core';
|
import { Component, forwardRef, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
import { QuillModule } from 'ngx-quill';
|
import { QuillModule } from 'ngx-quill';
|
||||||
import { BaseInputComponent } from '../base-input/base-input.component';
|
import { BaseInputComponent } from '../base-input/base-input.component';
|
||||||
@@ -9,9 +9,11 @@ import { ValidationMessagesService } from '../validation-messages.service';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-validated-quill',
|
selector: 'app-validated-quill',
|
||||||
templateUrl: './validated-quill.component.html',
|
templateUrl: './validated-quill.component.html',
|
||||||
styles: `quill-editor {
|
styleUrls: ['../../../../node_modules/quill/dist/quill.snow.css'],
|
||||||
|
styles: [`quill-editor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}`,
|
}`],
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, FormsModule, QuillModule, TooltipComponent],
|
imports: [CommonModule, FormsModule, QuillModule, TooltipComponent],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, PLATFORM_ID, Inject } from '@angular/core';
|
||||||
import { CanActivate, Router } from '@angular/router';
|
import { CanActivate, Router } from '@angular/router';
|
||||||
import { AuthService } from '../services/auth.service';
|
import { AuthService } from '../services/auth.service';
|
||||||
import { createLogger } from '../utils/utils';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
const logger = createLogger('AuthGuard');
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthGuard implements CanActivate {
|
export class AuthGuard implements CanActivate {
|
||||||
constructor(private authService: AuthService, private router: Router) {}
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: Object
|
||||||
|
) {}
|
||||||
|
|
||||||
async canActivate(): Promise<boolean> {
|
async canActivate(): Promise<boolean> {
|
||||||
|
// 1. SSR CHECK: Wenn wir auf dem Server sind, immer erlauben!
|
||||||
|
// Der Server soll nicht redirecten, sondern einfach das HTML rendern.
|
||||||
|
if (!isPlatformBrowser(this.platformId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. CLIENT CHECK: Das läuft nur im Browser
|
||||||
const token = await this.authService.getToken();
|
const token = await this.authService.getToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg overflow-hidden relative">
|
<div class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg overflow-hidden relative">
|
||||||
<button (click)="historyService.goBack()"
|
<button (click)="historyService.goBack()" aria-label="Go back"
|
||||||
class="absolute top-4 right-4 bg-red-500 text-white rounded-full w-8 h-8 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50 print:hidden">
|
class="absolute top-4 right-4 bg-red-500 text-white rounded-full w-10 h-10 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50 print:hidden">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
@if(listing){
|
@if(listing){
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
@if(listing){
|
@if(listing){
|
||||||
<div class="p-6 relative">
|
<div class="p-6 relative">
|
||||||
<h1 class="text-3xl font-bold mb-4 break-words">{{ listing?.title }}</h1>
|
<h1 class="text-3xl font-bold mb-4 break-words">{{ listing?.title }}</h1>
|
||||||
<button (click)="historyService.goBack()"
|
<button (click)="historyService.goBack()" aria-label="Go back"
|
||||||
class="print:hidden absolute top-4 right-4 bg-red-500 text-white rounded-full w-8 h-8 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
|
class="print:hidden absolute top-4 right-4 bg-red-500 text-white rounded-full w-10 h-10 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="flex flex-col lg:flex-row">
|
<div class="flex flex-col lg:flex-row">
|
||||||
|
|||||||
@@ -42,8 +42,8 @@
|
|||||||
}
|
}
|
||||||
<!-- <img src="https://placehold.co/45x60" class="w-11 h-14" /> -->
|
<!-- <img src="https://placehold.co/45x60" class="w-11 h-14" /> -->
|
||||||
</div>
|
</div>
|
||||||
<button (click)="historyService.goBack()"
|
<button (click)="historyService.goBack()" aria-label="Go back"
|
||||||
class="absolute top-4 right-4 bg-red-500 text-white rounded-full w-8 h-8 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
|
class="absolute top-4 right-4 bg-red-500 text-white rounded-full w-10 h-10 flex items-center justify-center hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -136,44 +136,13 @@ export class HomeComponent {
|
|||||||
// FAQ content is preserved in component for future use when FAQ section is made visible
|
// FAQ content is preserved in component for future use when FAQ section is made visible
|
||||||
const organizationSchema = this.seoService.generateOrganizationSchema();
|
const organizationSchema = this.seoService.generateOrganizationSchema();
|
||||||
|
|
||||||
// Add HowTo schema for buying a business
|
|
||||||
const howToSchema = this.seoService.generateHowToSchema({
|
|
||||||
name: 'How to Buy a Business on BizMatch',
|
|
||||||
description: 'Step-by-step guide to finding and purchasing your ideal business through BizMatch marketplace',
|
|
||||||
totalTime: 'PT45M',
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
name: 'Browse Business Listings',
|
|
||||||
text: 'Search through thousands of verified business listings using our advanced filters. Filter by industry, location, price range, revenue, and more to find businesses that match your criteria.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Review Business Details',
|
|
||||||
text: 'Examine the business financials, including annual revenue, cash flow, asking price, and years established. Read the detailed business description and view photos of the operation.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Contact the Seller',
|
|
||||||
text: 'Use our secure messaging system to contact the seller or business broker directly. Request additional information, financial documents, or schedule a site visit to see the business in person.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Conduct Due Diligence',
|
|
||||||
text: 'Review all financial statements, tax returns, lease agreements, and legal documents. Verify the business information, inspect the physical location, and consult with legal and financial advisors.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Make an Offer',
|
|
||||||
text: 'Submit a formal offer based on your valuation and due diligence findings. Negotiate terms including purchase price, payment structure, transition period, and any contingencies.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Close the Transaction',
|
|
||||||
text: 'Work with attorneys and escrow services to finalize all legal documents, transfer ownership, and complete the purchase. The seller will transfer assets, train you on operations, and help with the transition.'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add SearchBox schema for Sitelinks Search
|
// Add SearchBox schema for Sitelinks Search
|
||||||
const searchBoxSchema = this.seoService.generateSearchBoxSchema();
|
const searchBoxSchema = this.seoService.generateSearchBoxSchema();
|
||||||
|
|
||||||
// Inject schemas (FAQ schema excluded - content not visible to users)
|
// Inject schemas (FAQ schema excluded - content not visible to users)
|
||||||
this.seoService.injectMultipleSchemas([organizationSchema, howToSchema, searchBoxSchema]);
|
this.seoService.injectMultipleSchemas([organizationSchema, searchBoxSchema]);
|
||||||
|
|
||||||
// Clear all filters and sort options on initial load
|
// Clear all filters and sort options on initial load
|
||||||
this.filterStateService.resetCriteria('businessListings');
|
this.filterStateService.resetCriteria('businessListings');
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SEO-optimized heading -->
|
<!-- SEO-optimized heading -->
|
||||||
<div class="mb-6">
|
<!-- <div class="mb-6">
|
||||||
<h1 class="text-3xl md:text-4xl font-bold text-neutral-900 mb-2">Professional Business Brokers & Advisors</h1>
|
<h1 class="text-3xl md:text-4xl font-bold text-neutral-900 mb-2">Professional Business Brokers & Advisors</h1>
|
||||||
<p class="text-lg text-neutral-600">Connect with licensed business brokers, CPAs, attorneys, and other
|
<p class="text-lg text-neutral-600">Connect with licensed business brokers, CPAs, attorneys, and other
|
||||||
professionals across the United States.</p>
|
professionals across the United States.</p>
|
||||||
<div class="mt-4 text-base text-neutral-700 max-w-4xl">
|
<div class="mt-4 text-base text-neutral-700 max-w-4xl">
|
||||||
<p>BizMatch connects business buyers and sellers with experienced professionals. Find qualified business brokers to help with your business sale or acquisition. Our platform features verified professionals including business brokers, M&A advisors, CPAs, and attorneys specializing in business transactions across the United States. Whether you're looking to buy or sell a business, our network of professionals can guide you through the process.</p>
|
<p>BizMatch connects business buyers and sellers with experienced professionals. Find qualified business brokers to help with your business sale or acquisition. Our platform features verified professionals including business brokers, M&A advisors, CPAs, and attorneys specializing in business transactions across the United States. Whether you're looking to buy or sell a business, our network of professionals can guide you through the process.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- Mobile Filter Button -->
|
<!-- Mobile Filter Button -->
|
||||||
<div class="md:hidden mb-4">
|
<div class="md:hidden mb-4">
|
||||||
@@ -44,14 +44,14 @@
|
|||||||
@if(currentUser) {
|
@if(currentUser) {
|
||||||
<button type="button" class="bg-white rounded-full p-2 shadow-lg transition-colors"
|
<button type="button" class="bg-white rounded-full p-2 shadow-lg transition-colors"
|
||||||
[class.bg-red-50]="isFavorite(user)"
|
[class.bg-red-50]="isFavorite(user)"
|
||||||
[title]="isFavorite(user) ? 'Remove from favorites' : 'Save to favorites'"
|
[attr.aria-label]="isFavorite(user) ? 'Remove from favorites' : 'Save to favorites'"
|
||||||
(click)="toggleFavorite($event, user)">
|
(click)="toggleFavorite($event, user)">
|
||||||
<i
|
<i
|
||||||
[class]="isFavorite(user) ? 'fas fa-heart text-red-500' : 'far fa-heart text-red-500 hover:scale-110 transition-transform'"></i>
|
[class]="isFavorite(user) ? 'fas fa-heart text-red-500' : 'far fa-heart text-red-500 hover:scale-110 transition-transform'"></i>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="bg-white rounded-full p-2 shadow-lg hover:bg-blue-50 transition-colors"
|
<button type="button" class="bg-white rounded-full p-2 shadow-lg hover:bg-blue-50 transition-colors"
|
||||||
title="Share professional" (click)="shareProfessional($event, user)">
|
aria-label="Share professional" (click)="shareProfessional($event, user)">
|
||||||
<i class="fas fa-share-alt text-blue-500 hover:scale-110 transition-transform"></i>
|
<i class="fas fa-share-alt text-blue-500 hover:scale-110 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
<!-- SEO-optimized heading -->
|
<!-- SEO-optimized heading -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-3xl md:text-4xl font-bold text-neutral-900 mb-2">Businesses for Sale - Find Your Next Business Opportunity</h1>
|
<h1 class="text-3xl md:text-4xl font-bold text-neutral-900 mb-2">Businesses for Sale - Find Your Next Business Opportunity</h1>
|
||||||
<p class="text-lg text-neutral-600">Discover profitable business opportunities across the United States. Browse
|
<p class="text-lg text-neutral-600">Discover profitable business opportunities across the State of Texas. Browse
|
||||||
verified listings from business owners and brokers.</p>
|
verified listings from business owners and brokers.</p>
|
||||||
<div class="mt-4 text-base text-neutral-700 max-w-4xl">
|
<div class="mt-4 text-base text-neutral-700 max-w-4xl">
|
||||||
<p>BizMatch features thousands of businesses for sale across all industries and price ranges. Browse restaurants, retail stores, franchises, service businesses, e-commerce operations, and manufacturing companies. Each listing includes financial details, years established, location information, and seller contact details. Our marketplace connects business buyers with sellers and brokers nationwide, making it easy to find your next business opportunity.</p>
|
<p>BizMatch features a curated selection of businesses for sale across diverse industries and price ranges. Browse opportunities in sectors like restaurants, retail, franchises, services, e-commerce, and manufacturing. Each listing includes financial details, years established, location information, and seller contact details. Our marketplace connects business buyers with sellers and brokers nationwide, making it easy to find your next business opportunity.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -68,14 +68,14 @@
|
|||||||
@if(user) {
|
@if(user) {
|
||||||
<button class="bg-white rounded-full p-2 shadow-lg transition-colors"
|
<button class="bg-white rounded-full p-2 shadow-lg transition-colors"
|
||||||
[class.bg-red-50]="isFavorite(listing)"
|
[class.bg-red-50]="isFavorite(listing)"
|
||||||
[title]="isFavorite(listing) ? 'Remove from favorites' : 'Save to favorites'"
|
[attr.aria-label]="isFavorite(listing) ? 'Remove from favorites' : 'Save to favorites'"
|
||||||
(click)="toggleFavorite($event, listing)">
|
(click)="toggleFavorite($event, listing)">
|
||||||
<i
|
<i
|
||||||
[class]="isFavorite(listing) ? 'fas fa-heart text-red-500' : 'far fa-heart text-red-500 hover:scale-110 transition-transform'"></i>
|
[class]="isFavorite(listing) ? 'fas fa-heart text-red-500' : 'far fa-heart text-red-500 hover:scale-110 transition-transform'"></i>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="bg-white rounded-full p-2 shadow-lg hover:bg-blue-50 transition-colors"
|
<button type="button" class="bg-white rounded-full p-2 shadow-lg hover:bg-blue-50 transition-colors"
|
||||||
title="Share listing" (click)="shareListing($event, listing)">
|
aria-label="Share listing" (click)="shareListing($event, listing)">
|
||||||
<i class="fas fa-share-alt text-blue-500 hover:scale-110 transition-transform"></i>
|
<i class="fas fa-share-alt text-blue-500 hover:scale-110 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
<!-- SEO-optimized heading -->
|
<!-- SEO-optimized heading -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-3xl md:text-4xl font-bold text-neutral-900 mb-2">Commercial Properties for Sale</h1>
|
<h1 class="text-3xl md:text-4xl font-bold text-neutral-900 mb-2">Commercial Properties for Sale</h1>
|
||||||
<p class="text-lg text-neutral-600">Find office buildings, retail spaces, warehouses, and industrial properties across the United States. Investment opportunities from verified sellers and commercial real estate brokers.</p>
|
<p class="text-lg text-neutral-600">Discover selected investment properties and commercial spaces. Connect with verified sellers and brokers to find your next asset.</p>
|
||||||
<div class="mt-4 text-base text-neutral-700 max-w-4xl">
|
<div class="mt-4 text-base text-neutral-700 max-w-4xl">
|
||||||
<p>BizMatch showcases commercial real estate listings including office buildings, retail spaces, warehouses, and industrial properties for sale or lease. Browse investment properties across the United States with detailed information on square footage, zoning, pricing, and location. Our platform connects property buyers and investors with sellers and commercial real estate brokers. Find shopping centers, medical buildings, land parcels, and mixed-use developments in your target market.</p>
|
<p>BizMatch presents commercial real estate opportunities for sale or lease. View investment properties with detailed information on square footage, zoning, pricing, and location. Our platform connects property buyers and investors directly with sellers and commercial real estate brokers, focusing on transparent and valuable transactions.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -33,13 +33,13 @@
|
|||||||
class="bg-white rounded-full p-2 shadow-lg transition-colors"
|
class="bg-white rounded-full p-2 shadow-lg transition-colors"
|
||||||
[class.bg-red-50]="isFavorite(listing)"
|
[class.bg-red-50]="isFavorite(listing)"
|
||||||
[class.opacity-100]="isFavorite(listing)"
|
[class.opacity-100]="isFavorite(listing)"
|
||||||
[title]="isFavorite(listing) ? 'Remove from favorites' : 'Save to favorites'"
|
[attr.aria-label]="isFavorite(listing) ? 'Remove from favorites' : 'Save to favorites'"
|
||||||
(click)="toggleFavorite($event, listing)">
|
(click)="toggleFavorite($event, listing)">
|
||||||
<i [class]="isFavorite(listing) ? 'fas fa-heart text-red-500' : 'far fa-heart text-red-500 hover:scale-110 transition-transform'"></i>
|
<i [class]="isFavorite(listing) ? 'fas fa-heart text-red-500' : 'far fa-heart text-red-500 hover:scale-110 transition-transform'"></i>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="bg-white rounded-full p-2 shadow-lg hover:bg-blue-50 transition-colors"
|
<button type="button" class="bg-white rounded-full p-2 shadow-lg hover:bg-blue-50 transition-colors"
|
||||||
title="Share property" (click)="shareProperty($event, listing)">
|
aria-label="Share property" (click)="shareProperty($event, listing)">
|
||||||
<i class="fas fa-share-alt text-blue-500 hover:scale-110 transition-transform"></i>
|
<i class="fas fa-share-alt text-blue-500 hover:scale-110 transition-transform"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,16 +18,16 @@
|
|||||||
<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">
|
||||||
@if(user?.hasCompanyLogo){
|
@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" />
|
||||||
<div
|
<button type="button" aria-label="Delete company logo"
|
||||||
class="absolute top-[-0.5rem] right-[0rem] bg-white rounded-full p-1 drop-shadow-custom-bg hover:cursor-pointer"
|
class="absolute top-[-0.5rem] right-[0rem] bg-white rounded-full p-2 drop-shadow-custom-bg cursor-pointer min-w-[32px] min-h-[32px] flex items-center justify-center"
|
||||||
(click)="deleteConfirm('logo')">
|
(click)="deleteConfirm('logo')">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
<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">
|
class="w-4 h-4 text-gray-600">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</button>
|
||||||
} @else {
|
} @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" alt="" width="80" height="80" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@@ -41,16 +41,16 @@
|
|||||||
<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">
|
||||||
@if(user?.hasProfile){
|
@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" />
|
||||||
<div
|
<button type="button" aria-label="Delete profile picture"
|
||||||
class="absolute top-[-0.5rem] right-[0rem] bg-white rounded-full p-1 drop-shadow-custom-bg hover:cursor-pointer"
|
class="absolute top-[-0.5rem] right-[0rem] bg-white rounded-full p-2 drop-shadow-custom-bg cursor-pointer min-w-[32px] min-h-[32px] flex items-center justify-center"
|
||||||
(click)="deleteConfirm('profile')">
|
(click)="deleteConfirm('profile')">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
<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">
|
class="w-4 h-4 text-gray-600">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</button>
|
||||||
} @else {
|
} @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" alt="" width="80" height="80" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@@ -129,23 +129,16 @@
|
|||||||
mask="(000) 000-0000"></app-validated-input>
|
mask="(000) 000-0000"></app-validated-input>
|
||||||
<app-validated-input label="Company Website" name="companyWebsite"
|
<app-validated-input label="Company Website" name="companyWebsite"
|
||||||
[(ngModel)]="user.companyWebsite"></app-validated-input>
|
[(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"
|
<app-validated-location label="Company Location" name="location"
|
||||||
[(ngModel)]="user.location"></app-validated-location>
|
[(ngModel)]="user.location"></app-validated-location>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<app-validated-quill label="Company Overview" name="companyOverview"
|
<app-validated-quill label="Company Overview" name="companyOverview"
|
||||||
[(ngModel)]="user.companyOverview"></app-validated-quill>
|
[(ngModel)]="user.companyOverview"></app-validated-quill>
|
||||||
</div>
|
</div>
|
||||||
<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"
|
<app-validated-quill label="Services We Offer" name="offeredServices"
|
||||||
[(ngModel)]="user.offeredServices"></app-validated-quill>
|
[(ngModel)]="user.offeredServices"></app-validated-quill>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ChangeDetectorRef, Component } from '@angular/core';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { APP_ICONS } from '../../../utils/fontawesome-icons';
|
import { APP_ICONS } from '../../../utils/fontawesome-icons';
|
||||||
import { NgSelectModule } from '@ng-select/ng-select';
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
import { QuillModule, provideQuillConfig } from 'ngx-quill';
|
import { QuillModule } from 'ngx-quill';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { AutoCompleteCompleteEvent, Invoice, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { AutoCompleteCompleteEvent, Invoice, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
@@ -47,19 +47,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
TitleCasePipe,
|
TitleCasePipe,
|
||||||
DatePipe,
|
DatePipe
|
||||||
provideQuillConfig({
|
|
||||||
modules: {
|
|
||||||
syntax: true,
|
|
||||||
toolbar: [
|
|
||||||
['bold', 'italic', 'underline'],
|
|
||||||
[{ header: [1, 2, 3, false] }],
|
|
||||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
||||||
[{ color: [] }],
|
|
||||||
['clean'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}) as any,
|
|
||||||
],
|
],
|
||||||
templateUrl: './account.component.html',
|
templateUrl: './account.component.html',
|
||||||
styleUrls: [
|
styleUrls: [
|
||||||
@@ -77,9 +65,6 @@ export class AccountComponent {
|
|||||||
editorModules = TOOLBAR_OPTIONS;
|
editorModules = TOOLBAR_OPTIONS;
|
||||||
env = environment;
|
env = environment;
|
||||||
faTrash = APP_ICONS.faTrash;
|
faTrash = APP_ICONS.faTrash;
|
||||||
quillModules = {
|
|
||||||
toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']],
|
|
||||||
};
|
|
||||||
uploadParams: UploadParams;
|
uploadParams: UploadParams;
|
||||||
validationMessages: ValidationMessage[] = [];
|
validationMessages: ValidationMessage[] = [];
|
||||||
customerTypeOptions: Array<{ value: string; label: string }> = [];
|
customerTypeOptions: Array<{ value: string; label: string }> = [];
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { QuillModule } from 'ngx-quill';
|
|||||||
|
|
||||||
import { NgSelectModule } from '@ng-select/ng-select';
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
import { NgxCurrencyDirective } from 'ngx-currency';
|
import { NgxCurrencyDirective } from 'ngx-currency';
|
||||||
import { provideQuillConfig } from 'ngx-quill';
|
|
||||||
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { AutoCompleteCompleteEvent, ImageProperty, createDefaultBusinessListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { AutoCompleteCompleteEvent, ImageProperty, createDefaultBusinessListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
|
||||||
@@ -48,20 +47,6 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
|||||||
ValidatedTextareaComponent,
|
ValidatedTextareaComponent,
|
||||||
ValidatedLocationComponent,
|
ValidatedLocationComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
|
||||||
provideQuillConfig({
|
|
||||||
modules: {
|
|
||||||
syntax: true,
|
|
||||||
toolbar: [
|
|
||||||
['bold', 'italic', 'underline'],
|
|
||||||
[{ header: [1, 2, 3, false] }],
|
|
||||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
||||||
[{ color: [] }],
|
|
||||||
['clean'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}) as any,
|
|
||||||
],
|
|
||||||
templateUrl: './edit-business-listing.component.html',
|
templateUrl: './edit-business-listing.component.html',
|
||||||
styleUrls: [
|
styleUrls: [
|
||||||
'./edit-business-listing.component.scss',
|
'./edit-business-listing.component.scss',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { APP_ICONS } from '../../../utils/fontawesome-icons';
|
|||||||
import { NgSelectModule } from '@ng-select/ng-select';
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
import { NgxCurrencyDirective } from 'ngx-currency';
|
import { NgxCurrencyDirective } from 'ngx-currency';
|
||||||
import { ImageCropperComponent } from 'ngx-image-cropper';
|
import { ImageCropperComponent } from 'ngx-image-cropper';
|
||||||
import { QuillModule, provideQuillConfig } from 'ngx-quill';
|
import { QuillModule } from 'ngx-quill';
|
||||||
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { AutoCompleteCompleteEvent, ImageProperty, UploadParams, createDefaultCommercialPropertyListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { AutoCompleteCompleteEvent, ImageProperty, UploadParams, createDefaultCommercialPropertyListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
|
||||||
@@ -53,20 +53,6 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
|||||||
ValidatedLocationComponent,
|
ValidatedLocationComponent,
|
||||||
ImageCropAndUploadComponent,
|
ImageCropAndUploadComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
|
||||||
provideQuillConfig({
|
|
||||||
modules: {
|
|
||||||
syntax: true,
|
|
||||||
toolbar: [
|
|
||||||
['bold', 'italic', 'underline'],
|
|
||||||
[{ header: [1, 2, 3, false] }],
|
|
||||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
||||||
[{ color: [] }],
|
|
||||||
['clean'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}) as any,
|
|
||||||
],
|
|
||||||
templateUrl: './edit-commercial-property-listing.component.html',
|
templateUrl: './edit-commercial-property-listing.component.html',
|
||||||
styleUrls: [
|
styleUrls: [
|
||||||
'./edit-commercial-property-listing.component.scss',
|
'./edit-commercial-property-listing.component.scss',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Build information, automatically generated by `the_build_script` :zwinkern:
|
// Build information, automatically generated by `the_build_script` :zwinkern:
|
||||||
const build = {
|
const build = {
|
||||||
timestamp: "GER: 05.02.2026 13:06 | TX: 02/05/2026 6:06 AM"
|
timestamp: "GER: 06.02.2026 12:50 | TX: 02/06/2026 5:50 AM"
|
||||||
};
|
};
|
||||||
|
|
||||||
export default build;
|
export default build;
|
||||||
@@ -1,8 +1,20 @@
|
|||||||
// SSR-safe: check if window exists (it doesn't on server-side)
|
// SSR-safe: check if window exists
|
||||||
const hostname = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
|
// Im Browser nehmen wir den aktuellen Host (z.B. localhost).
|
||||||
|
// Auf dem Server (SSR in Docker) nehmen wir 'bizmatch-app' (der Name des Backend-Containers).
|
||||||
|
const isBrowser = typeof window !== 'undefined' && window.navigator.userAgent !== 'node';
|
||||||
|
const hostname = isBrowser ? window.location.hostname : 'bizmatch-app';
|
||||||
|
// Im Server-Modus nutzen wir den internen Docker-Namen
|
||||||
|
const internalUrl = 'http://bizmatch-app:3001';
|
||||||
|
// Im Browser-Modus die öffentliche URL
|
||||||
|
const publicUrl = 'https://api.bizmatch.net';
|
||||||
|
const calculatedApiBaseUrl = isBrowser ? publicUrl : internalUrl;
|
||||||
|
// WICHTIG: Port anpassen!
|
||||||
|
// Lokal läuft das Backend auf 3001. Im Docker Container auch auf 3001.
|
||||||
|
// Deine alte Config hatte hier :4200 stehen, das war falsch (das ist das Frontend).
|
||||||
export const environment_base = {
|
export const environment_base = {
|
||||||
// apiBaseUrl: 'http://localhost:3000',
|
// apiBaseUrl: 'http://localhost:3000',
|
||||||
apiBaseUrl: `http://${hostname}:4200`,
|
// GETTER FUNCTION für die API URL (besser als statischer String für diesen Fall)
|
||||||
|
apiBaseUrl: calculatedApiBaseUrl,
|
||||||
imageBaseUrl: 'https://dev.bizmatch.net',
|
imageBaseUrl: 'https://dev.bizmatch.net',
|
||||||
buildVersion: '<BUILD_VERSION>',
|
buildVersion: '<BUILD_VERSION>',
|
||||||
mailinfoUrl: 'https://dev.bizmatch.net',
|
mailinfoUrl: 'https://dev.bizmatch.net',
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import { environment_base } from './environment.base';
|
import { environment_base } from './environment.base';
|
||||||
|
|
||||||
export const environment = environment_base;
|
export const environment = { ...environment_base }; // Kopie erstellen
|
||||||
|
|
||||||
environment.production = true;
|
environment.production = true;
|
||||||
environment.apiBaseUrl = 'https://api.bizmatch.net';
|
|
||||||
|
// WICHTIG: Diese Zeile auskommentieren, solange du lokal testest!
|
||||||
|
// Sonst greift er immer aufs Internet zu, statt auf deinen lokalen Docker-Container.
|
||||||
|
environment.apiBaseUrl = 'https://api.bizmatch.net';
|
||||||
|
|
||||||
environment.mailinfoUrl = 'https://www.bizmatch.net';
|
environment.mailinfoUrl = 'https://www.bizmatch.net';
|
||||||
environment.imageBaseUrl = 'https://api.bizmatch.net';
|
environment.imageBaseUrl = 'https://www.bizmatch.net';// Ggf. auch auskommentieren, wenn Bilder lokal liegen
|
||||||
|
|
||||||
environment.POSTHOG_KEY = 'phc_eUIcIq0UPVzEDtZLy78klKhGudyagBz3goDlKx8SQFe';
|
environment.POSTHOG_KEY = 'phc_eUIcIq0UPVzEDtZLy78klKhGudyagBz3goDlKx8SQFe';
|
||||||
environment.POSTHOG_HOST = 'https://eu.i.posthog.com';
|
environment.POSTHOG_HOST = 'https://eu.i.posthog.com';
|
||||||
@@ -62,52 +62,11 @@
|
|||||||
<link rel="icon" href="/assets/cropped-Favicon-192x192.png" sizes="192x192" />
|
<link rel="icon" href="/assets/cropped-Favicon-192x192.png" sizes="192x192" />
|
||||||
<link rel="apple-touch-icon" href="/assets/cropped-Favicon-180x180.png" />
|
<link rel="apple-touch-icon" href="/assets/cropped-Favicon-180x180.png" />
|
||||||
|
|
||||||
<!-- Schema.org Structured Data (Static) -->
|
<!-- Schema.org Structured Data -->
|
||||||
<script type="application/ld+json">
|
<!-- Note: Organization and WebSite schemas are now injected dynamically by SeoService -->
|
||||||
{
|
<!-- with more complete data (telephone, foundingDate, knowsAbout, dual search actions) -->
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "Organization",
|
|
||||||
"name": "BizMatch",
|
|
||||||
"url": "https://www.bizmatch.net",
|
|
||||||
"logo": "https://www.bizmatch.net/assets/images/bizmatch-logo.png",
|
|
||||||
"description": "Buy and sell businesses, commercial properties, and franchises. Browse thousands of verified listings across the United States.",
|
|
||||||
"address": {
|
|
||||||
"@type": "PostalAddress",
|
|
||||||
"streetAddress": "1001 Blucher Street",
|
|
||||||
"addressLocality": "Corpus Christi",
|
|
||||||
"addressRegion": "TX",
|
|
||||||
"postalCode": "78401",
|
|
||||||
"addressCountry": "US"
|
|
||||||
},
|
|
||||||
"contactPoint": {
|
|
||||||
"@type": "ContactPoint",
|
|
||||||
"contactType": "Customer Service",
|
|
||||||
"email": "info@bizmatch.net"
|
|
||||||
},
|
|
||||||
"sameAs": [
|
|
||||||
"https://www.facebook.com/bizmatch",
|
|
||||||
"https://www.linkedin.com/company/bizmatch",
|
|
||||||
"https://twitter.com/bizmatch"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "WebSite",
|
|
||||||
"name": "BizMatch",
|
|
||||||
"url": "https://www.bizmatch.net",
|
|
||||||
"potentialAction": {
|
|
||||||
"@type": "SearchAction",
|
|
||||||
"target": {
|
|
||||||
"@type": "EntryPoint",
|
|
||||||
"urlTemplate": "https://www.bizmatch.net/businessListings?search={search_term_string}"
|
|
||||||
},
|
|
||||||
"query-input": "required name=search_term_string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="flex flex-col min-h-screen">
|
<body class="flex flex-col min-h-screen">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// External CSS imports - these URL imports don't trigger deprecation warnings
|
// External CSS imports - these URL imports don't trigger deprecation warnings
|
||||||
// Using css2 API with specific weights for better performance
|
// Using css2 API with specific weights for better performance
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap');
|
||||||
|
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css');
|
||||||
|
|
||||||
// Local CSS files loaded as CSS (not SCSS) to avoid @import deprecation
|
// Local CSS files loaded as CSS (not SCSS) to avoid @import deprecation
|
||||||
// Note: These are loaded via angular.json styles array is the preferred approach,
|
// Note: These are loaded via angular.json styles array is the preferred approach,
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src/main.ts"
|
"src/main.ts",
|
||||||
|
"src/main.server.ts",
|
||||||
|
"server.ts"
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.d.ts"
|
"src/**/*.d.ts"
|
||||||
|
|||||||
62
docker-compose.yml
Normal file
62
docker-compose.yml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
services:
|
||||||
|
# --- FRONTEND ---
|
||||||
|
bizmatch-ssr:
|
||||||
|
build:
|
||||||
|
context: . # Pfad zum Angular Ordner
|
||||||
|
dockerfile: bizmatch/Dockerfile
|
||||||
|
image: bizmatch-ssr
|
||||||
|
container_name: bizmatch-ssr
|
||||||
|
extra_hosts:
|
||||||
|
- "localhost:host-gateway"
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '4200:4000' # Extern 4200 -> Intern 4000 (SSR)
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
volumes:
|
||||||
|
- ./bizmatch-server/pictures:/app/pictures
|
||||||
|
|
||||||
|
# --- BACKEND ---
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ./bizmatch-server # Pfad zum NestJS Ordner
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: bizmatch-server:latest
|
||||||
|
container_name: bizmatch-app
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '3001:3001'
|
||||||
|
env_file:
|
||||||
|
- ./bizmatch-server/.env # Pfad zur .env Datei
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
networks:
|
||||||
|
- bizmatch
|
||||||
|
# WICHTIG: Kein Volume Mapping für node_modules im Prod-Modus!
|
||||||
|
# Das Image bringt alles fertig mit.
|
||||||
|
|
||||||
|
# --- DATABASE ---
|
||||||
|
postgres:
|
||||||
|
container_name: bizmatchdb
|
||||||
|
image: postgres:17-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- bizmatch-db-data:/var/lib/postgresql/data
|
||||||
|
env_file:
|
||||||
|
- ./bizmatch-server/.env
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- '5434:5432'
|
||||||
|
networks:
|
||||||
|
- bizmatch
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
bizmatch-db-data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bizmatch:
|
||||||
|
external: false # Oder true, falls du es manuell erstellt hast
|
||||||
21
update.sh
Executable file
21
update.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🚀 Starte Update Prozess..."
|
||||||
|
|
||||||
|
# 1. Neuesten Code holen
|
||||||
|
echo "📥 Git Pull..."
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# 2. Docker Images neu bauen und Container neu starten
|
||||||
|
# --build: Zwingt Docker, die Images neu zu bauen (nutzt Multi-Stage)
|
||||||
|
# -d: Detached mode (im Hintergrund)
|
||||||
|
# --remove-orphans: Räumt alte Container auf, falls Services umbenannt wurden
|
||||||
|
echo "🐳 Baue und starte Container..."
|
||||||
|
docker compose up -d --build --remove-orphans
|
||||||
|
|
||||||
|
# 3. Aufräumen (Optional)
|
||||||
|
# Löscht alte Images ("dangling images"), die beim Build übrig geblieben sind, um Platz zu sparen
|
||||||
|
echo "🧹 Räume alte Images auf..."
|
||||||
|
docker image prune -f
|
||||||
|
|
||||||
|
echo "✅ Fertig! Die App läuft in der neuesten Version."
|
||||||
Reference in New Issue
Block a user