feat: Add comprehensive user authentication, listing management, and core UI components.
|
|
@ -47,6 +47,7 @@
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"groq-sdk": "^0.5.0",
|
"groq-sdk": "^0.5.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
"nest-winston": "^1.9.4",
|
"nest-winston": "^1.9.4",
|
||||||
"nestjs-cls": "^5.4.0",
|
"nestjs-cls": "^5.4.0",
|
||||||
"nodemailer": "^7.0.12",
|
"nodemailer": "^7.0.12",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { LoggerService } from '@nestjs/common';
|
import { LoggerService } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
import helmet from 'helmet';
|
||||||
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
|
@ -22,6 +23,37 @@ async function bootstrap() {
|
||||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
allowedHeaders: 'Content-Type, Accept, Authorization, x-hide-loading',
|
allowedHeaders: 'Content-Type, Accept, Authorization, x-hide-loading',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Security Headers with helmet
|
||||||
|
app.use(
|
||||||
|
helmet({
|
||||||
|
contentSecurityPolicy: {
|
||||||
|
directives: {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "https://fonts.googleapis.com"],
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
|
||||||
|
imgSrc: ["'self'", "data:", "https:", "blob:"],
|
||||||
|
connectSrc: ["'self'", "https://api.bizmatch.net", "https://*.firebaseapp.com", "https://*.googleapis.com"],
|
||||||
|
fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"],
|
||||||
|
objectSrc: ["'none'"],
|
||||||
|
mediaSrc: ["'self'"],
|
||||||
|
frameSrc: ["'self'"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
crossOriginEmbedderPolicy: false, // Disable for now to avoid breaking existing functionality
|
||||||
|
hsts: {
|
||||||
|
maxAge: 31536000,
|
||||||
|
includeSubDomains: true,
|
||||||
|
preload: true,
|
||||||
|
},
|
||||||
|
frameguard: {
|
||||||
|
action: 'sameorigin', // Allow same-origin framing
|
||||||
|
},
|
||||||
|
crossOriginOpenerPolicy: { policy: 'same-origin-allow-popups' }, // Allow popups for OAuth
|
||||||
|
crossOriginResourcePolicy: { policy: 'cross-origin' }, // Allow cross-origin resources
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
await app.listen(process.env.PORT || 3001);
|
await app.listen(process.env.PORT || 3001);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,6 @@
|
||||||
"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",
|
||||||
|
|
@ -53,10 +48,7 @@
|
||||||
],
|
],
|
||||||
"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": {
|
||||||
|
|
@ -69,8 +61,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "anyComponentStyle",
|
"type": "anyComponentStyle",
|
||||||
"maximumWarning": "2kb",
|
"maximumWarning": "10kb",
|
||||||
"maximumError": "4kb"
|
"maximumError": "30kb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputHashing": "all"
|
"outputHashing": "all"
|
||||||
|
|
@ -78,8 +70,7 @@
|
||||||
"development": {
|
"development": {
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true
|
||||||
"ssr": false
|
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"fileReplacements": [
|
"fileReplacements": [
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@ 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';
|
||||||
|
|
@ -48,13 +44,6 @@ 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,
|
||||||
|
|
@ -62,13 +51,6 @@ export const appConfig: ApplicationConfig = {
|
||||||
disableImageSizeWarning: true,
|
disableImageSizeWarning: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
provideShareButtonsOptions(
|
|
||||||
shareIcons(),
|
|
||||||
withConfig({
|
|
||||||
debug: true,
|
|
||||||
sharerMethod: SharerMethods.Anchor,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
provideRouter(
|
provideRouter(
|
||||||
routes,
|
routes,
|
||||||
withEnabledBlockingInitialNavigation(),
|
withEnabledBlockingInitialNavigation(),
|
||||||
|
|
@ -79,18 +61,6 @@ 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()),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { ConfirmationService } from './confirmation.service';
|
||||||
<button
|
<button
|
||||||
(click)="confirmationService.reject()"
|
(click)="confirmationService.reject()"
|
||||||
type="button"
|
type="button"
|
||||||
class="absolute top-3 end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
class="absolute top-3 end-2.5 text-gray-600 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||||
|
|
@ -21,11 +21,11 @@ import { ConfirmationService } from './confirmation.service';
|
||||||
<span class="sr-only">Close modal</span>
|
<span class="sr-only">Close modal</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="p-4 md:p-5 text-center">
|
<div class="p-4 md:p-5 text-center">
|
||||||
<svg class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
<svg class="mx-auto mb-4 text-gray-600 w-12 h-12 dark:text-gray-200" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||||
</svg>
|
</svg>
|
||||||
@let confirmation = (confirmationService.confirmation$ | async);
|
@let confirmation = (confirmationService.confirmation$ | async);
|
||||||
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ confirmation?.message }}</h3>
|
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-600">{{ confirmation?.message }}</h3>
|
||||||
@if(confirmation?.buttons==='both'){
|
@if(confirmation?.buttons==='both'){
|
||||||
<button
|
<button
|
||||||
(click)="confirmationService.accept()"
|
(click)="confirmationService.accept()"
|
||||||
|
|
@ -37,7 +37,7 @@ import { ConfirmationService } from './confirmation.service';
|
||||||
<button
|
<button
|
||||||
(click)="confirmationService.reject()"
|
(click)="confirmationService.reject()"
|
||||||
type="button"
|
type="button"
|
||||||
class="py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
class="py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-600 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||||
>
|
>
|
||||||
No, cancel
|
No, cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<button
|
<button
|
||||||
(click)="eMailService.reject()"
|
(click)="eMailService.reject()"
|
||||||
type="button"
|
type="button"
|
||||||
class="end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
class="end-2.5 text-gray-600 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
<svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="flex flex-col lg:flex-row items-center mb-4 lg:mb-0">
|
<div class="flex flex-col lg:flex-row items-center mb-4 lg:mb-0">
|
||||||
<!-- <img src="/assets/images/header-logo.png" alt="BizMatch Logo" class="h-8 mb-2 lg:mb-0 lg:mr-4" /> -->
|
<!-- <img src="/assets/images/header-logo.png" alt="BizMatch Logo" class="h-8 mb-2 lg:mb-0 lg:mr-4" /> -->
|
||||||
<a routerLink="/home" class="flex items-center space-x-3 rtl:space-x-reverse">
|
<a routerLink="/home" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||||
<img src="/assets/images/header-logo.png" class="h-8 mb-2 lg:mb-0 lg:mr-4" alt="BizMatch Logo" />
|
<img src="/assets/images/header-logo.png" class="h-8 mb-2 lg:mb-0 lg:mr-4" alt="BizMatch Logo" width="120" height="32" />
|
||||||
</a>
|
</a>
|
||||||
<p class="text-sm text-neutral-600 text-center lg:text-left">© {{ currentYear }} Bizmatch All rights reserved.</p>
|
<p class="text-sm text-neutral-600 text-center lg:text-left">© {{ currentYear }} Bizmatch All rights reserved.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
||||||
<a routerLink="/home" class="flex items-center space-x-3 rtl:space-x-reverse">
|
<a routerLink="/home" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||||
<img src="/assets/images/header-logo.png" class="h-10 w-auto"
|
<img src="/assets/images/header-logo.png" class="h-10 w-auto"
|
||||||
alt="BizMatch - Business Marketplace for Buying and Selling Businesses" />
|
alt="BizMatch - Business Marketplace for Buying and Selling Businesses" width="150" height="40" />
|
||||||
</a>
|
</a>
|
||||||
<div class="flex items-center md:order-2 space-x-3 rtl:space-x-reverse">
|
<div class="flex items-center md:order-2 space-x-3 rtl:space-x-reverse">
|
||||||
<!-- Filter button -->
|
<!-- Filter button -->
|
||||||
|
|
@ -192,7 +192,7 @@
|
||||||
class="inline-flex items-center py-2 px-3 rounded hover:bg-neutral-100 md:hover:bg-transparent md:hover:text-primary-600 md:p-0 dark:text-white md:dark:hover:text-primary-500 dark:hover:bg-neutral-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-neutral-700"
|
class="inline-flex items-center py-2 px-3 rounded hover:bg-neutral-100 md:hover:bg-transparent md:hover:text-primary-600 md:p-0 dark:text-white md:dark:hover:text-primary-500 dark:hover:bg-neutral-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-neutral-700"
|
||||||
(click)="closeMenusAndSetCriteria('brokerListings')">
|
(click)="closeMenusAndSetCriteria('brokerListings')">
|
||||||
<img src="/assets/images/icon_professionals.png" alt="Professionals"
|
<img src="/assets/images/icon_professionals.png" alt="Professionals"
|
||||||
class="w-5 h-5 mr-2 object-contain bg-transparent" style="mix-blend-mode: darken;" />
|
class="w-5 h-5 mr-2 object-contain bg-transparent" style="mix-blend-mode: darken;" width="20" height="20" />
|
||||||
<span>Professionals</span>
|
<span>Professionals</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||||
import { Component, HostListener, OnDestroy, OnInit, AfterViewInit, PLATFORM_ID, inject } from '@angular/core';
|
import { Component, HostListener, OnDestroy, OnInit, AfterViewInit, PLATFORM_ID, inject } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { NavigationEnd, Router, RouterModule } from '@angular/router';
|
import { NavigationEnd, Router, RouterModule } from '@angular/router';
|
||||||
import { faUserGear } from '@fortawesome/free-solid-svg-icons';
|
import { APP_ICONS } from '../../utils/fontawesome-icons';
|
||||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
import { Collapse, Dropdown, initFlowbite } from 'flowbite';
|
import { Collapse, Dropdown, initFlowbite } from 'flowbite';
|
||||||
import { filter, Observable, Subject, takeUntil } from 'rxjs';
|
import { filter, Observable, Subject, takeUntil } from 'rxjs';
|
||||||
|
|
@ -35,7 +35,7 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
keycloakUser: KeycloakUser;
|
keycloakUser: KeycloakUser;
|
||||||
user: User;
|
user: User;
|
||||||
activeItem;
|
activeItem;
|
||||||
faUserGear = faUserGear;
|
faUserGear = APP_ICONS.faUserGear;
|
||||||
profileUrl: string;
|
profileUrl: string;
|
||||||
env = environment;
|
env = environment;
|
||||||
private filterDropdown: Dropdown | null = null;
|
private filterDropdown: Dropdown | null = null;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
placeholder="Please enter E-Mail Address"
|
placeholder="Please enter E-Mail Address"
|
||||||
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
/>
|
/>
|
||||||
<fa-icon [icon]="envelope" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
|
<fa-icon [icon]="envelope" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-600"></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
placeholder="Please enter Password"
|
placeholder="Please enter Password"
|
||||||
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
/>
|
/>
|
||||||
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
|
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-600"></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
placeholder="Repeat Password"
|
placeholder="Repeat Password"
|
||||||
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
/>
|
/>
|
||||||
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
|
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-600"></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Component } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
import { faArrowRight, faEnvelope, faLock, faUserPlus } from '@fortawesome/free-solid-svg-icons';
|
import { APP_ICONS } from '../../utils/fontawesome-icons';
|
||||||
import { AuthService } from '../../services/auth.service';
|
import { AuthService } from '../../services/auth.service';
|
||||||
import { LoadingService } from '../../services/loading.service';
|
import { LoadingService } from '../../services/loading.service';
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -18,10 +18,10 @@ export class LoginRegisterComponent {
|
||||||
confirmPassword: string = '';
|
confirmPassword: string = '';
|
||||||
isLoginMode: boolean = true; // true: Login, false: Registration
|
isLoginMode: boolean = true; // true: Login, false: Registration
|
||||||
errorMessage: string = '';
|
errorMessage: string = '';
|
||||||
envelope = faEnvelope;
|
envelope = APP_ICONS.faEnvelope;
|
||||||
lock = faLock;
|
lock = APP_ICONS.faLock;
|
||||||
arrowRight = faArrowRight;
|
arrowRight = APP_ICONS.faArrowRight;
|
||||||
userplus = faUserPlus;
|
userplus = APP_ICONS.faUserPlus;
|
||||||
constructor(private authService: AuthService, private route: ActivatedRoute, private router: Router, private loadingService: LoadingService) {}
|
constructor(private authService: AuthService, private route: ActivatedRoute, private router: Router, private loadingService: LoadingService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
||||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
<ng-select id="state-select" aria-label="Location - State" class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||||
|
|
@ -172,7 +172,7 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
||||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
<ng-select id="state-select" aria-label="Location - State" class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
||||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
<ng-select id="state-select" aria-label="Location - State" class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||||
|
|
@ -168,7 +168,7 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
||||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
<ng-select id="state-select" aria-label="Location - State" class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@
|
||||||
<div class="grid grid-cols-1 gap-6">
|
<div class="grid grid-cols-1 gap-6">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
<label for="state-select" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
||||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
<ng-select id="state-select" class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state" aria-label="Location - State"></ng-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||||
|
|
@ -266,7 +266,7 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
<label for="state" class="block mb-2 text-sm font-medium text-neutral-900">Location - State</label>
|
||||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
<ng-select id="state-select" aria-label="Location - State" class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-neutral-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,27 @@ import dayjs from 'dayjs';
|
||||||
import { AuthService } from '../../../services/auth.service';
|
import { AuthService } from '../../../services/auth.service';
|
||||||
import { BaseDetailsComponent } from '../base-details.component';
|
import { BaseDetailsComponent } from '../base-details.component';
|
||||||
import { ShareButton } from 'ngx-sharebuttons/button';
|
import { ShareButton } from 'ngx-sharebuttons/button';
|
||||||
|
import { provideShareButtonsOptions, SharerMethods, withConfig } from 'ngx-sharebuttons';
|
||||||
|
import { shareIcons } from 'ngx-sharebuttons/icons';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details-business-listing',
|
selector: 'app-details-business-listing',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ValidatedNgSelectComponent, LeafletModule, BreadcrumbsComponent, ShareButton, NgOptimizedImage],
|
imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ValidatedNgSelectComponent, LeafletModule, BreadcrumbsComponent, ShareButton, NgOptimizedImage],
|
||||||
providers: [],
|
providers: [
|
||||||
|
provideShareButtonsOptions(
|
||||||
|
shareIcons(),
|
||||||
|
withConfig({
|
||||||
|
debug: false,
|
||||||
|
sharerMethod: SharerMethods.Anchor,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
templateUrl: './details-business-listing.component.html',
|
templateUrl: './details-business-listing.component.html',
|
||||||
styleUrl: '../details.scss',
|
styleUrls: [
|
||||||
|
'../details.scss',
|
||||||
|
'../../../../../node_modules/leaflet/dist/leaflet.css',
|
||||||
|
'../../../../../node_modules/ngx-sharebuttons/themes/default.scss'
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class DetailsBusinessListingComponent extends BaseDetailsComponent {
|
export class DetailsBusinessListingComponent extends BaseDetailsComponent {
|
||||||
// listings: Array<BusinessListing>;
|
// listings: Array<BusinessListing>;
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import { NgOptimizedImage } from '@angular/common';
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { LeafletModule } from '@bluehalo/ngx-leaflet';
|
import { LeafletModule } from '@bluehalo/ngx-leaflet';
|
||||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
import { APP_ICONS } from '../../../utils/fontawesome-icons';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { GalleryModule, ImageItem } from 'ng-gallery';
|
import { GALLERY_CONFIG, GalleryConfig, GalleryModule, ImageItem } from 'ng-gallery';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { CommercialPropertyListing, EventTypeEnum, ShareByEMail, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { CommercialPropertyListing, EventTypeEnum, ShareByEMail, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { CommercialPropertyListingCriteria, ErrorResponse, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { CommercialPropertyListingCriteria, ErrorResponse, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
|
@ -30,14 +30,35 @@ import { createMailInfo, map2User } from '../../../utils/utils';
|
||||||
import { BaseDetailsComponent } from '../base-details.component';
|
import { BaseDetailsComponent } from '../base-details.component';
|
||||||
import { BreadcrumbItem, BreadcrumbsComponent } from '../../../components/breadcrumbs/breadcrumbs.component';
|
import { BreadcrumbItem, BreadcrumbsComponent } from '../../../components/breadcrumbs/breadcrumbs.component';
|
||||||
import { ShareButton } from 'ngx-sharebuttons/button';
|
import { ShareButton } from 'ngx-sharebuttons/button';
|
||||||
|
import { provideShareButtonsOptions, SharerMethods, withConfig } from 'ngx-sharebuttons';
|
||||||
|
import { shareIcons } from 'ngx-sharebuttons/icons';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details-commercial-property-listing',
|
selector: 'app-details-commercial-property-listing',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ValidatedNgSelectComponent, GalleryModule, LeafletModule, BreadcrumbsComponent, ShareButton, NgOptimizedImage],
|
imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ValidatedNgSelectComponent, GalleryModule, LeafletModule, BreadcrumbsComponent, ShareButton, NgOptimizedImage],
|
||||||
providers: [],
|
providers: [
|
||||||
|
provideShareButtonsOptions(
|
||||||
|
shareIcons(),
|
||||||
|
withConfig({
|
||||||
|
debug: false,
|
||||||
|
sharerMethod: SharerMethods.Anchor,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
{
|
||||||
|
provide: GALLERY_CONFIG,
|
||||||
|
useValue: {
|
||||||
|
autoHeight: true,
|
||||||
|
imageSize: 'cover',
|
||||||
|
} as GalleryConfig,
|
||||||
|
},
|
||||||
|
],
|
||||||
templateUrl: './details-commercial-property-listing.component.html',
|
templateUrl: './details-commercial-property-listing.component.html',
|
||||||
styleUrl: '../details.scss',
|
styleUrls: [
|
||||||
|
'../details.scss',
|
||||||
|
'../../../../../node_modules/leaflet/dist/leaflet.css',
|
||||||
|
'../../../../../node_modules/ngx-sharebuttons/themes/default.scss'
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class DetailsCommercialPropertyListingComponent extends BaseDetailsComponent {
|
export class DetailsCommercialPropertyListingComponent extends BaseDetailsComponent {
|
||||||
responsiveOptions = [
|
responsiveOptions = [
|
||||||
|
|
@ -69,7 +90,7 @@ export class DetailsCommercialPropertyListingComponent extends BaseDetailsCompon
|
||||||
ts = new Date().getTime();
|
ts = new Date().getTime();
|
||||||
env = environment;
|
env = environment;
|
||||||
errorResponse: ErrorResponse;
|
errorResponse: ErrorResponse;
|
||||||
faTimes = faTimes;
|
faTimes = APP_ICONS.faTimes;
|
||||||
propertyDetails = [];
|
propertyDetails = [];
|
||||||
images: Array<ImageItem> = [];
|
images: Array<ImageItem> = [];
|
||||||
relatedListings: CommercialPropertyListing[] = [];
|
relatedListings: CommercialPropertyListing[] = [];
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,27 @@ import { UserService } from '../../../services/user.service';
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
import { formatPhoneNumber, map2User } from '../../../utils/utils';
|
import { formatPhoneNumber, map2User } from '../../../utils/utils';
|
||||||
import { ShareButton } from 'ngx-sharebuttons/button';
|
import { ShareButton } from 'ngx-sharebuttons/button';
|
||||||
|
import { provideShareButtonsOptions, SharerMethods, withConfig } from 'ngx-sharebuttons';
|
||||||
|
import { shareIcons } from 'ngx-sharebuttons/icons';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details-user',
|
selector: 'app-details-user',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule, BreadcrumbsComponent, NgOptimizedImage, ShareButton],
|
imports: [SharedModule, BreadcrumbsComponent, NgOptimizedImage, ShareButton],
|
||||||
|
providers: [
|
||||||
|
provideShareButtonsOptions(
|
||||||
|
shareIcons(),
|
||||||
|
withConfig({
|
||||||
|
debug: false,
|
||||||
|
sharerMethod: SharerMethods.Anchor,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
templateUrl: './details-user.component.html',
|
templateUrl: './details-user.component.html',
|
||||||
styleUrl: '../details.scss',
|
styleUrls: [
|
||||||
|
'../details.scss',
|
||||||
|
'../../../../../node_modules/ngx-sharebuttons/themes/default.scss'
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class DetailsUserComponent {
|
export class DetailsUserComponent {
|
||||||
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { DatePipe, TitleCasePipe } from '@angular/common';
|
import { DatePipe, TitleCasePipe } from '@angular/common';
|
||||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
import { APP_ICONS } from '../../../utils/fontawesome-icons';
|
||||||
import { NgSelectModule } from '@ng-select/ng-select';
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
import { QuillModule } from 'ngx-quill';
|
import { QuillModule, provideQuillConfig } 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';
|
||||||
|
|
@ -45,9 +45,27 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
ValidatedCountyComponent,
|
ValidatedCountyComponent,
|
||||||
ValidatedLocationComponent,
|
ValidatedLocationComponent,
|
||||||
],
|
],
|
||||||
providers: [TitleCasePipe, DatePipe],
|
providers: [
|
||||||
|
TitleCasePipe,
|
||||||
|
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',
|
||||||
styleUrl: './account.component.scss',
|
styleUrls: [
|
||||||
|
'./account.component.scss',
|
||||||
|
'../../../../../node_modules/quill/dist/quill.snow.css'
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AccountComponent {
|
export class AccountComponent {
|
||||||
id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||||
|
|
@ -58,7 +76,7 @@ export class AccountComponent {
|
||||||
environment = environment;
|
environment = environment;
|
||||||
editorModules = TOOLBAR_OPTIONS;
|
editorModules = TOOLBAR_OPTIONS;
|
||||||
env = environment;
|
env = environment;
|
||||||
faTrash = faTrash;
|
faTrash = APP_ICONS.faTrash;
|
||||||
quillModules = {
|
quillModules = {
|
||||||
toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']],
|
toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { map2User, routeListingWithState } from '../../../utils/utils';
|
import { map2User, routeListingWithState } from '../../../utils/utils';
|
||||||
|
|
||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
import { APP_ICONS } from '../../../utils/fontawesome-icons';
|
||||||
import { QuillModule } from 'ngx-quill';
|
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';
|
||||||
|
|
||||||
|
|
@ -47,9 +48,25 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
ValidatedTextareaComponent,
|
ValidatedTextareaComponent,
|
||||||
ValidatedLocationComponent,
|
ValidatedLocationComponent,
|
||||||
],
|
],
|
||||||
providers: [],
|
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',
|
||||||
styleUrl: './edit-business-listing.component.scss',
|
styleUrls: [
|
||||||
|
'./edit-business-listing.component.scss',
|
||||||
|
'../../../../../node_modules/quill/dist/quill.snow.css'
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class EditBusinessListingComponent {
|
export class EditBusinessListingComponent {
|
||||||
listingsCategory = 'business';
|
listingsCategory = 'business';
|
||||||
|
|
@ -64,7 +81,7 @@ export class EditBusinessListingComponent {
|
||||||
config = { aspectRatio: 16 / 9 };
|
config = { aspectRatio: 16 / 9 };
|
||||||
editorModules = TOOLBAR_OPTIONS;
|
editorModules = TOOLBAR_OPTIONS;
|
||||||
draggedImage: ImageProperty;
|
draggedImage: ImageProperty;
|
||||||
faTrash = faTrash;
|
faTrash = APP_ICONS.faTrash;
|
||||||
data: CommercialPropertyListing;
|
data: CommercialPropertyListing;
|
||||||
typesOfBusiness = [];
|
typesOfBusiness = [];
|
||||||
quillModules = {
|
quillModules = {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
(click)="uploadPropertyPicture()"
|
(click)="uploadPropertyPicture()"
|
||||||
class="flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
class="flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||||
>
|
>
|
||||||
<svg class="mr-2 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="mr-2 h-5 w-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Upload
|
Upload
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ import { map2User, routeListingWithState } from '../../../utils/utils';
|
||||||
|
|
||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
import { ViewportRuler } from '@angular/cdk/scrolling';
|
import { ViewportRuler } from '@angular/cdk/scrolling';
|
||||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
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 } from 'ngx-quill';
|
import { QuillModule, 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, UploadParams, createDefaultCommercialPropertyListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { AutoCompleteCompleteEvent, ImageProperty, UploadParams, createDefaultCommercialPropertyListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
|
||||||
|
|
@ -53,9 +53,25 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
ValidatedLocationComponent,
|
ValidatedLocationComponent,
|
||||||
ImageCropAndUploadComponent,
|
ImageCropAndUploadComponent,
|
||||||
],
|
],
|
||||||
providers: [],
|
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',
|
||||||
styleUrl: './edit-commercial-property-listing.component.scss',
|
styleUrls: [
|
||||||
|
'./edit-commercial-property-listing.component.scss',
|
||||||
|
'../../../../../node_modules/quill/dist/quill.snow.css'
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class EditCommercialPropertyListingComponent {
|
export class EditCommercialPropertyListingComponent {
|
||||||
@ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;
|
@ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;
|
||||||
|
|
@ -91,7 +107,7 @@ export class EditCommercialPropertyListingComponent {
|
||||||
editorModules = TOOLBAR_OPTIONS;
|
editorModules = TOOLBAR_OPTIONS;
|
||||||
|
|
||||||
draggedImage: ImageProperty;
|
draggedImage: ImageProperty;
|
||||||
faTrash = faTrash;
|
faTrash = APP_ICONS.faTrash;
|
||||||
suggestions: string[] | undefined;
|
suggestions: string[] | undefined;
|
||||||
data: BusinessListing;
|
data: BusinessListing;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import {
|
||||||
|
faArrowRight,
|
||||||
|
faEnvelope,
|
||||||
|
faLock,
|
||||||
|
faTimes,
|
||||||
|
faTrash,
|
||||||
|
faUserGear,
|
||||||
|
faUserPlus
|
||||||
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export const APP_ICONS = {
|
||||||
|
faArrowRight,
|
||||||
|
faEnvelope,
|
||||||
|
faLock,
|
||||||
|
faTimes,
|
||||||
|
faTrash,
|
||||||
|
faUserGear,
|
||||||
|
faUserPlus,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
// Development: use browser-bunyan for rich logging
|
||||||
|
// Production: use lightweight console wrapper to avoid loading 50KB+ library
|
||||||
|
export const createLogger = environment.production
|
||||||
|
? (name: string) => ({
|
||||||
|
info: (...args: any[]) => console.log(`[${name}]`, ...args),
|
||||||
|
warn: (...args: any[]) => console.warn(`[${name}]`, ...args),
|
||||||
|
error: (...args: any[]) => console.error(`[${name}]`, ...args),
|
||||||
|
debug: () => {}, // no-op in production
|
||||||
|
})
|
||||||
|
: require('browser-bunyan').createLogger;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ConsoleFormattedStream, INFO, createLogger as _createLogger, stdSerializers } from 'browser-bunyan';
|
import { createLogger as _createLogger } from './logger';
|
||||||
import { jwtDecode } from 'jwt-decode';
|
import { jwtDecode } from 'jwt-decode';
|
||||||
import onChange from 'on-change';
|
import onChange from 'on-change';
|
||||||
import { SortByOptions, User } from '../../../../bizmatch-server/src/models/db.model';
|
import { SortByOptions, User } from '../../../../bizmatch-server/src/models/db.model';
|
||||||
|
|
@ -141,14 +141,8 @@ export function createMailInfo(user?: User): MailInfo {
|
||||||
listing: null,
|
listing: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function createLogger(name: string, level: number = INFO, options: any = {}) {
|
export function createLogger(name: string, level?: number, options?: any) {
|
||||||
return _createLogger({
|
return _createLogger(name);
|
||||||
name,
|
|
||||||
streams: [{ level, stream: new ConsoleFormattedStream() }],
|
|
||||||
serializers: stdSerializers,
|
|
||||||
src: true,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
export function formatPhoneNumber(phone: string): string {
|
export function formatPhoneNumber(phone: string): string {
|
||||||
const cleaned = ('' + phone).replace(/\D/g, '');
|
const cleaned = ('' + phone).replace(/\D/g, '');
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 662 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
|
@ -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: 04.02.2026 15:43 | TX: 02/04/2026 8:43 AM"
|
timestamp: "GER: 04.02.2026 21:20 | TX: 02/04/2026 2:20 PM"
|
||||||
};
|
};
|
||||||
|
|
||||||
export default build;
|
export default build;
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
// 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,
|
||||||
|
|
@ -122,7 +121,7 @@ p-menubarsub ul {
|
||||||
|
|
||||||
input::placeholder,
|
input::placeholder,
|
||||||
textarea::placeholder {
|
textarea::placeholder {
|
||||||
color: #999 !important;
|
color: #757575 !important; /* 4.54:1 contrast - WCAG AA compliant */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix für Marker-Icons in Leaflet */
|
/* Fix für Marker-Icons in Leaflet */
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src/main.ts",
|
"src/main.ts"
|
||||||
"src/main.server.ts",
|
|
||||||
"server.ts"
|
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.d.ts"
|
"src/**/*.d.ts"
|
||||||
|
|
|
||||||