npm run serve:ssr funktioniert und Hamburger Menu bug fix
This commit is contained in:
parent
43027a54f7
commit
4f8fd77f7d
|
|
@ -26,6 +26,12 @@
|
|||
"ssr": {
|
||||
"entry": "server.ts"
|
||||
},
|
||||
"allowedCommonJsDependencies": [
|
||||
"quill-delta",
|
||||
"leaflet",
|
||||
"dayjs",
|
||||
"qs"
|
||||
],
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -3,17 +3,24 @@ import './src/ssr-dom-polyfill';
|
|||
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { AngularNodeAppEngine, createNodeRequestHandler, writeResponseToNodeResponse } from '@angular/ssr/node';
|
||||
import { ɵsetAngularAppEngineManifest as setAngularAppEngineManifest } from '@angular/ssr';
|
||||
import express from 'express';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
|
||||
// The Express app is exported so that it can be used by serverless Functions.
|
||||
export function app(): express.Express {
|
||||
export async function app(): Promise<express.Express> {
|
||||
const server = express();
|
||||
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
||||
const browserDistFolder = resolve(serverDistFolder, '../browser');
|
||||
const indexHtml = join(serverDistFolder, 'index.server.html');
|
||||
|
||||
// Explicitly load and set the Angular app engine manifest
|
||||
// This is required for environments where the manifest is not auto-loaded
|
||||
const manifestPath = join(serverDistFolder, 'angular-app-engine-manifest.mjs');
|
||||
const manifest = await import(manifestPath);
|
||||
setAngularAppEngineManifest(manifest.default);
|
||||
|
||||
const angularApp = new AngularNodeAppEngine();
|
||||
|
||||
server.set('view engine', 'html');
|
||||
|
|
@ -27,27 +34,46 @@ export function app(): express.Express {
|
|||
}));
|
||||
|
||||
// All regular routes use the Angular engine
|
||||
server.get('*', (req, res, next) => {
|
||||
angularApp
|
||||
.handle(req)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
writeResponseToNodeResponse(response, res);
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
})
|
||||
.catch((err) => next(err));
|
||||
server.get('*', async (req, res, next) => {
|
||||
console.log(`[SSR] Handling request: ${req.method} ${req.url}`);
|
||||
try {
|
||||
const response = await angularApp.handle(req);
|
||||
if (response) {
|
||||
console.log(`[SSR] Response received for ${req.url}, status: ${response.status}`);
|
||||
writeResponseToNodeResponse(response, res);
|
||||
} else {
|
||||
console.log(`[SSR] No response for ${req.url} - Angular engine returned null`);
|
||||
console.log(`[SSR] This usually means the route couldn't be rendered. Check for:
|
||||
1. Browser API usage in components
|
||||
2. Missing platform checks
|
||||
3. Errors during component initialization`);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[SSR] Error handling ${req.url}:`, err);
|
||||
console.error(`[SSR] Stack trace:`, err.stack);
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
function run(): void {
|
||||
// Global error handlers for debugging
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('[SSR] Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('[SSR] Uncaught Exception:', error);
|
||||
console.error('[SSR] Stack:', error.stack);
|
||||
});
|
||||
|
||||
async function run(): Promise<void> {
|
||||
const port = process.env['PORT'] || 4200;
|
||||
|
||||
// Start up the Node server
|
||||
const server = app();
|
||||
const server = await app();
|
||||
server.listen(port, () => {
|
||||
console.log(`Node Express server listening on http://localhost:${port}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { AfterViewInit, Component, HostListener } from '@angular/core';
|
||||
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||
import { AfterViewInit, Component, HostListener, PLATFORM_ID, inject } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/router';
|
||||
import { initFlowbite } from 'flowbite';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
|
@ -29,6 +29,8 @@ export class AppComponent implements AfterViewInit {
|
|||
build = build;
|
||||
title = 'bizmatch';
|
||||
actualRoute = '';
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private isBrowser = isPlatformBrowser(this.platformId);
|
||||
|
||||
public constructor(
|
||||
public loadingService: LoadingService,
|
||||
|
|
@ -48,9 +50,11 @@ export class AppComponent implements AfterViewInit {
|
|||
this.actualRoute = currentRoute.snapshot.url[0].path;
|
||||
|
||||
// Re-initialize Flowbite after navigation to ensure all components are ready
|
||||
setTimeout(() => {
|
||||
initFlowbite();
|
||||
}, 50);
|
||||
if (this.isBrowser) {
|
||||
setTimeout(() => {
|
||||
initFlowbite();
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
|
|
@ -60,7 +64,9 @@ export class AppComponent implements AfterViewInit {
|
|||
ngAfterViewInit() {
|
||||
// Initialize Flowbite for dropdowns, modals, and other interactive components
|
||||
// Note: Drawers work automatically with data-drawer-target attributes
|
||||
initFlowbite();
|
||||
if (this.isBrowser) {
|
||||
initFlowbite();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:keydown', ['$event'])
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||
import { provideServerRendering } from '@angular/platform-server';
|
||||
import { provideServerRouting } from '@angular/ssr';
|
||||
import { appConfig } from './app.config';
|
||||
import { serverRoutes } from './app.routes.server';
|
||||
|
||||
const serverConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideServerRendering()
|
||||
provideServerRendering(),
|
||||
provideServerRouting(serverRoutes)
|
||||
]
|
||||
};
|
||||
|
||||
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { IMAGE_CONFIG } from '@angular/common';
|
||||
import { APP_INITIALIZER, ApplicationConfig, ErrorHandler } from '@angular/core';
|
||||
import { IMAGE_CONFIG, isPlatformBrowser } from '@angular/common';
|
||||
import { APP_INITIALIZER, ApplicationConfig, ErrorHandler, PLATFORM_ID, inject } from '@angular/core';
|
||||
import { provideClientHydration } from '@angular/platform-browser';
|
||||
import { provideRouter, withEnabledBlockingInitialNavigation, withInMemoryScrolling } from '@angular/router';
|
||||
|
||||
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||
|
|
@ -19,10 +20,12 @@ import { GlobalErrorHandler } from './services/globalErrorHandler';
|
|||
import { POSTHOG_INIT_PROVIDER } from './services/posthog.factory';
|
||||
import { SelectOptionsService } from './services/select-options.service';
|
||||
import { createLogger } from './utils/utils';
|
||||
// provideClientHydration()
|
||||
|
||||
const logger = createLogger('ApplicationConfig');
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
// Temporarily disabled for SSR debugging
|
||||
// provideClientHydration(),
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
|
|
@ -90,7 +93,6 @@ export const appConfig: ApplicationConfig = {
|
|||
}),
|
||||
provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
|
||||
provideAuth(() => getAuth()),
|
||||
// provideFirestore(() => getFirestore()),
|
||||
],
|
||||
};
|
||||
function initServices(selectOptions: SelectOptionsService) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import { RenderMode, ServerRoute } from '@angular/ssr';
|
||||
|
||||
export const serverRoutes: ServerRoute[] = [
|
||||
{
|
||||
path: '**',
|
||||
renderMode: RenderMode.Server
|
||||
}
|
||||
];
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { Routes } from '@angular/router';
|
||||
import { LogoutComponent } from './components/logout/logout.component';
|
||||
import { NotFoundComponent } from './components/not-found/not-found.component';
|
||||
import { TestSsrComponent } from './components/test-ssr/test-ssr.component';
|
||||
|
||||
import { EmailAuthorizedComponent } from './components/email-authorized/email-authorized.component';
|
||||
import { EmailVerificationComponent } from './components/email-verification/email-verification.component';
|
||||
|
|
@ -26,6 +27,10 @@ import { TermsOfUseComponent } from './pages/legal/terms-of-use.component';
|
|||
import { PrivacyStatementComponent } from './pages/legal/privacy-statement.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: 'test-ssr',
|
||||
component: TestSsrComponent,
|
||||
},
|
||||
{
|
||||
path: 'businessListings',
|
||||
component: BusinessListingsComponent,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild, PLATFORM_ID, inject } from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
|
||||
|
||||
@Component({
|
||||
|
|
@ -23,6 +24,8 @@ export class DropdownComponent implements AfterViewInit, OnDestroy {
|
|||
|
||||
@HostBinding('class.hidden') isHidden: boolean = true;
|
||||
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private isBrowser = isPlatformBrowser(this.platformId);
|
||||
private popperInstance: PopperInstance | null = null;
|
||||
isVisible: boolean = false;
|
||||
private clickOutsideListener: any;
|
||||
|
|
@ -30,6 +33,8 @@ export class DropdownComponent implements AfterViewInit, OnDestroy {
|
|||
private hoverHideListener: any;
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
if (!this.triggerEl) {
|
||||
console.error('Trigger element is not provided to the dropdown component.');
|
||||
return;
|
||||
|
|
@ -58,6 +63,8 @@ export class DropdownComponent implements AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
private setupEventListeners() {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
if (this.triggerType === 'click') {
|
||||
this.triggerEl.addEventListener('click', () => this.toggle());
|
||||
} else if (this.triggerType === 'hover') {
|
||||
|
|
@ -74,6 +81,8 @@ export class DropdownComponent implements AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
private removeEventListeners() {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
if (this.triggerType === 'click') {
|
||||
this.triggerEl.removeEventListener('click', () => this.toggle());
|
||||
} else if (this.triggerType === 'hover') {
|
||||
|
|
@ -104,7 +113,7 @@ export class DropdownComponent implements AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
private handleClickOutside(event: MouseEvent) {
|
||||
if (!this.isVisible) return;
|
||||
if (!this.isVisible || !this.isBrowser) return;
|
||||
|
||||
const clickedElement = event.target as HTMLElement;
|
||||
if (this.ignoreClickOutsideClass) {
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@
|
|||
}
|
||||
<button type="button"
|
||||
class="flex text-sm bg-neutral-400 rounded-full md:me-0 focus:ring-4 focus:ring-neutral-300 dark:focus:ring-neutral-600"
|
||||
id="user-menu-button" aria-expanded="false" [attr.data-dropdown-toggle]="user ? 'user-login' : 'user-unknown'"
|
||||
data-dropdown-placement="bottom">
|
||||
id="user-menu-button" aria-expanded="false" [attr.data-dropdown-toggle]="user ? 'user-login' : 'user-unknown'">
|
||||
<span class="sr-only">Open user menu</span>
|
||||
@if(isProfessional || (authService.isAdmin() | async) && user?.hasProfile){
|
||||
<img class="w-8 h-8 rounded-full object-cover" src="{{ profileUrl }}"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component, HostListener, OnDestroy, OnInit, AfterViewInit } from '@angular/core';
|
||||
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||
import { Component, HostListener, OnDestroy, OnInit, AfterViewInit, PLATFORM_ID, inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NavigationEnd, Router, RouterModule } from '@angular/router';
|
||||
import { faUserGear } from '@fortawesome/free-solid-svg-icons';
|
||||
|
|
@ -42,6 +42,8 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
isMobile: boolean = false;
|
||||
private destroy$ = new Subject<void>();
|
||||
prompt: string;
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private isBrowser = isPlatformBrowser(this.platformId);
|
||||
|
||||
// Aktueller Listing-Typ basierend auf Route
|
||||
currentListingType: 'businessListings' | 'commercialPropertyListings' | 'brokerListings' | null = null;
|
||||
|
|
@ -74,6 +76,16 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
const excludedIds = ['sortDropdownButton', 'sortDropdownMobileButton', 'user-menu-button'];
|
||||
if (!excludedIds.includes(target.id) && !target.closest('#user-menu-button')) {
|
||||
this.sortDropdownVisible = false;
|
||||
|
||||
// Close User Menu if clicked outside
|
||||
// We check if the click was inside the menu containers
|
||||
const userLogin = document.getElementById('user-login');
|
||||
const userUnknown = document.getElementById('user-unknown');
|
||||
const clickedInsideMenu = (userLogin && userLogin.contains(target)) || (userUnknown && userUnknown.contains(target));
|
||||
|
||||
if (!clickedInsideMenu) {
|
||||
this.closeDropdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +115,7 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
const previousUser = this.user;
|
||||
this.user = u;
|
||||
// Re-initialize Flowbite if user logged in/out state changed
|
||||
if ((previousUser === null) !== (u === null)) {
|
||||
if ((previousUser === null) !== (u === null) && this.isBrowser) {
|
||||
setTimeout(() => initFlowbite(), 50);
|
||||
}
|
||||
});
|
||||
|
|
@ -223,6 +235,8 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
closeDropdown() {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
const dropdownButton = document.getElementById('user-menu-button');
|
||||
const dropdownMenu = this.user ? document.getElementById('user-login') : document.getElementById('user-unknown');
|
||||
|
||||
|
|
@ -233,6 +247,8 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
closeMobileMenu() {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
const targetElement = document.getElementById('navbar-user');
|
||||
const triggerElement = document.querySelector('[data-collapse-toggle="navbar-user"]');
|
||||
|
||||
|
|
@ -293,12 +309,10 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
// Initialize Flowbite after header DOM is fully rendered
|
||||
// This ensures all dropdown elements exist before initialization
|
||||
setTimeout(() => {
|
||||
initFlowbite();
|
||||
}, 0);
|
||||
// Flowbite initialization is now handled manually or via AppComponent
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-test-ssr',
|
||||
standalone: true,
|
||||
template: `
|
||||
<div>
|
||||
<h1>SSR Test Component</h1>
|
||||
<p>If you see this, SSR is working!</p>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
div {
|
||||
padding: 20px;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
h1 { color: green; }
|
||||
`]
|
||||
})
|
||||
export class TestSsrComponent {
|
||||
constructor() {
|
||||
console.log('[SSR] TestSsrComponent constructor called');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, SimpleChanges } from '@angular/core';
|
||||
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||
import { Component, Input, SimpleChanges, PLATFORM_ID, inject } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tooltip',
|
||||
|
|
@ -12,6 +12,9 @@ export class TooltipComponent {
|
|||
@Input() text: string;
|
||||
@Input() isVisible: boolean = false;
|
||||
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private isBrowser = isPlatformBrowser(this.platformId);
|
||||
|
||||
ngOnInit() {
|
||||
this.initializeTooltip();
|
||||
}
|
||||
|
|
@ -27,6 +30,8 @@ export class TooltipComponent {
|
|||
}
|
||||
|
||||
private updateTooltipVisibility() {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
const tooltipElement = document.getElementById(this.id);
|
||||
if (tooltipElement) {
|
||||
if (this.isVisible) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import { inject } from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { inject, PLATFORM_ID } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
import { KeycloakService } from '../services/keycloak.service';
|
||||
|
||||
export const authResolver: ResolveFn<boolean> = async (route, state) => {
|
||||
const keycloakService: KeycloakService = inject(KeycloakService);
|
||||
const platformId = inject(PLATFORM_ID);
|
||||
const isBrowser = isPlatformBrowser(platformId);
|
||||
|
||||
if (!keycloakService.isLoggedIn()) {
|
||||
if (!keycloakService.isLoggedIn() && isBrowser) {
|
||||
await keycloakService.login({
|
||||
redirectUri: window.location.href,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// auth.service.ts
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { HttpClient, HttpBackend, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { Injectable, inject, PLATFORM_ID } from '@angular/core';
|
||||
import { FirebaseApp } from '@angular/fire/app';
|
||||
import { GoogleAuthProvider, UserCredential, createUserWithEmailAndPassword, getAuth, signInWithCustomToken, signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth';
|
||||
import { BehaviorSubject, Observable, catchError, firstValueFrom, map, of, shareReplay, take, tap } from 'rxjs';
|
||||
|
|
@ -14,8 +15,10 @@ export type UserRole = 'admin' | 'pro' | 'guest';
|
|||
})
|
||||
export class AuthService {
|
||||
private app = inject(FirebaseApp);
|
||||
private auth = getAuth(this.app);
|
||||
private http = inject(HttpClient);
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private isBrowser = isPlatformBrowser(this.platformId);
|
||||
private auth = this.isBrowser ? getAuth(this.app) : null;
|
||||
private http = new HttpClient(inject(HttpBackend));
|
||||
private mailService = inject(MailService);
|
||||
// Add a BehaviorSubject to track the current user role
|
||||
private userRoleSubject = new BehaviorSubject<UserRole | null>(null);
|
||||
|
|
@ -31,6 +34,26 @@ export class AuthService {
|
|||
this.loadRoleFromToken();
|
||||
}
|
||||
|
||||
// Helper methods for localStorage access (only in browser)
|
||||
private setLocalStorageItem(key: string, value: string): void {
|
||||
if (this.isBrowser) {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private getLocalStorageItem(key: string): string | null {
|
||||
if (this.isBrowser) {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private removeLocalStorageItem(key: string): void {
|
||||
if (this.isBrowser) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
private loadRoleFromToken(): void {
|
||||
this.getToken().then(token => {
|
||||
if (token) {
|
||||
|
|
@ -54,18 +77,24 @@ export class AuthService {
|
|||
}
|
||||
// Registrierung mit Email und Passwort
|
||||
async registerWithEmail(email: string, password: string): Promise<UserCredential> {
|
||||
if (!this.isBrowser || !this.auth) {
|
||||
throw new Error('Auth is only available in browser context');
|
||||
}
|
||||
|
||||
// Bestimmen der aktuellen Umgebung/Domain für die Verifizierungs-URL
|
||||
let verificationUrl = '';
|
||||
let verificationUrl = 'https://www.bizmatch.net/email-authorized';
|
||||
|
||||
// Prüfen der aktuellen Umgebung basierend auf dem Host
|
||||
const currentHost = window.location.hostname;
|
||||
// Prüfen der aktuellen Umgebung basierend auf dem Host (nur im Browser)
|
||||
if (this.isBrowser) {
|
||||
const currentHost = window.location.hostname;
|
||||
|
||||
if (currentHost.includes('localhost')) {
|
||||
verificationUrl = 'http://localhost:4200/email-authorized';
|
||||
} else if (currentHost.includes('dev.bizmatch.net')) {
|
||||
verificationUrl = 'https://dev.bizmatch.net/email-authorized';
|
||||
} else {
|
||||
verificationUrl = 'https://www.bizmatch.net/email-authorized';
|
||||
if (currentHost.includes('localhost')) {
|
||||
verificationUrl = 'http://localhost:4200/email-authorized';
|
||||
} else if (currentHost.includes('dev.bizmatch.net')) {
|
||||
verificationUrl = 'https://dev.bizmatch.net/email-authorized';
|
||||
} else {
|
||||
verificationUrl = 'https://www.bizmatch.net/email-authorized';
|
||||
}
|
||||
}
|
||||
|
||||
// ActionCode-Einstellungen mit der dynamischen URL
|
||||
|
|
@ -93,10 +122,10 @@ export class AuthService {
|
|||
}
|
||||
|
||||
// const token = await userCredential.user.getIdToken();
|
||||
// localStorage.setItem('authToken', token);
|
||||
// localStorage.setItem('refreshToken', userCredential.user.refreshToken);
|
||||
// this.setLocalStorageItem('authToken', token);
|
||||
// this.setLocalStorageItem('refreshToken', userCredential.user.refreshToken);
|
||||
// if (userCredential.user.photoURL) {
|
||||
// localStorage.setItem('photoURL', userCredential.user.photoURL);
|
||||
// this.setLocalStorageItem('photoURL', userCredential.user.photoURL);
|
||||
// }
|
||||
|
||||
return userCredential;
|
||||
|
|
@ -104,13 +133,16 @@ export class AuthService {
|
|||
|
||||
// Login mit Email und Passwort
|
||||
loginWithEmail(email: string, password: string): Promise<UserCredential> {
|
||||
if (!this.isBrowser || !this.auth) {
|
||||
throw new Error('Auth is only available in browser context');
|
||||
}
|
||||
return signInWithEmailAndPassword(this.auth, email, password).then(async userCredential => {
|
||||
if (userCredential.user) {
|
||||
const token = await userCredential.user.getIdToken();
|
||||
localStorage.setItem('authToken', token);
|
||||
localStorage.setItem('refreshToken', userCredential.user.refreshToken);
|
||||
this.setLocalStorageItem('authToken', token);
|
||||
this.setLocalStorageItem('refreshToken', userCredential.user.refreshToken);
|
||||
if (userCredential.user.photoURL) {
|
||||
localStorage.setItem('photoURL', userCredential.user.photoURL);
|
||||
this.setLocalStorageItem('photoURL', userCredential.user.photoURL);
|
||||
}
|
||||
this.loadRoleFromToken();
|
||||
}
|
||||
|
|
@ -120,14 +152,17 @@ export class AuthService {
|
|||
|
||||
// Login mit Google
|
||||
loginWithGoogle(): Promise<UserCredential> {
|
||||
if (!this.isBrowser || !this.auth) {
|
||||
throw new Error('Auth is only available in browser context');
|
||||
}
|
||||
const provider = new GoogleAuthProvider();
|
||||
return signInWithPopup(this.auth, provider).then(async userCredential => {
|
||||
if (userCredential.user) {
|
||||
const token = await userCredential.user.getIdToken();
|
||||
localStorage.setItem('authToken', token);
|
||||
localStorage.setItem('refreshToken', userCredential.user.refreshToken);
|
||||
this.setLocalStorageItem('authToken', token);
|
||||
this.setLocalStorageItem('refreshToken', userCredential.user.refreshToken);
|
||||
if (userCredential.user.photoURL) {
|
||||
localStorage.setItem('photoURL', userCredential.user.photoURL);
|
||||
this.setLocalStorageItem('photoURL', userCredential.user.photoURL);
|
||||
}
|
||||
this.loadRoleFromToken();
|
||||
}
|
||||
|
|
@ -137,12 +172,15 @@ export class AuthService {
|
|||
|
||||
// Logout: Token, RefreshToken und photoURL entfernen
|
||||
logout(): Promise<void> {
|
||||
localStorage.removeItem('authToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('photoURL');
|
||||
this.removeLocalStorageItem('authToken');
|
||||
this.removeLocalStorageItem('refreshToken');
|
||||
this.removeLocalStorageItem('photoURL');
|
||||
this.clearRoleCache();
|
||||
this.userRoleSubject.next(null);
|
||||
return this.auth.signOut();
|
||||
if (this.auth) {
|
||||
return this.auth.signOut();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
isAdmin(): Observable<boolean> {
|
||||
return this.getUserRole().pipe(
|
||||
|
|
@ -202,10 +240,10 @@ export class AuthService {
|
|||
// Force refresh the token to get updated custom claims
|
||||
async refreshUserClaims(): Promise<void> {
|
||||
this.clearRoleCache();
|
||||
if (this.auth.currentUser) {
|
||||
if (this.auth && this.auth.currentUser) {
|
||||
await this.auth.currentUser.getIdToken(true);
|
||||
const token = await this.auth.currentUser.getIdToken();
|
||||
localStorage.setItem('authToken', token);
|
||||
this.setLocalStorageItem('authToken', token);
|
||||
this.loadRoleFromToken();
|
||||
}
|
||||
}
|
||||
|
|
@ -234,7 +272,12 @@ export class AuthService {
|
|||
}
|
||||
// Versucht, mit dem RefreshToken einen neuen Access Token zu erhalten
|
||||
async refreshToken(): Promise<string | null> {
|
||||
const storedRefreshToken = localStorage.getItem('refreshToken');
|
||||
const storedRefreshToken = this.getLocalStorageItem('refreshToken');
|
||||
// SSR protection: refreshToken should only run in browser
|
||||
if (!this.isBrowser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!storedRefreshToken) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -250,8 +293,8 @@ export class AuthService {
|
|||
// response enthält z. B. id_token, refresh_token, expires_in etc.
|
||||
const newToken = response.id_token;
|
||||
const newRefreshToken = response.refresh_token;
|
||||
localStorage.setItem('authToken', newToken);
|
||||
localStorage.setItem('refreshToken', newRefreshToken);
|
||||
this.setLocalStorageItem('authToken', newToken);
|
||||
this.setLocalStorageItem('refreshToken', newRefreshToken);
|
||||
return newToken;
|
||||
} catch (error) {
|
||||
console.error('Error refreshing token:', error);
|
||||
|
|
@ -266,7 +309,12 @@ export class AuthService {
|
|||
* Ist auch das nicht möglich, wird null zurückgegeben.
|
||||
*/
|
||||
async getToken(): Promise<string | null> {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const token = this.getLocalStorageItem('authToken');
|
||||
// SSR protection: return null on server
|
||||
if (!this.isBrowser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token && !this.isEMailVerified(token)) {
|
||||
return null;
|
||||
} else if (token && this.isTokenValid(token) && this.isEMailVerified(token)) {
|
||||
|
|
@ -278,6 +326,9 @@ export class AuthService {
|
|||
|
||||
// Add this new method to sign in with a custom token
|
||||
async signInWithCustomToken(token: string): Promise<void> {
|
||||
if (!this.isBrowser || !this.auth) {
|
||||
throw new Error('Auth is only available in browser context');
|
||||
}
|
||||
try {
|
||||
// Sign in to Firebase with the custom token
|
||||
const userCredential = await signInWithCustomToken(this.auth, token);
|
||||
|
|
@ -285,11 +336,11 @@ export class AuthService {
|
|||
// Store the authentication token
|
||||
if (userCredential.user) {
|
||||
const idToken = await userCredential.user.getIdToken();
|
||||
localStorage.setItem('authToken', idToken);
|
||||
localStorage.setItem('refreshToken', userCredential.user.refreshToken);
|
||||
this.setLocalStorageItem('authToken', idToken);
|
||||
this.setLocalStorageItem('refreshToken', userCredential.user.refreshToken);
|
||||
|
||||
if (userCredential.user.photoURL) {
|
||||
localStorage.setItem('photoURL', userCredential.user.photoURL);
|
||||
this.setLocalStorageItem('photoURL', userCredential.user.photoURL);
|
||||
}
|
||||
|
||||
// Load user role from the token
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { inject, Injectable, PLATFORM_ID } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { SortByOptions } from '../../../../bizmatch-server/src/models/db.model';
|
||||
import { BusinessListingCriteria, CommercialPropertyListingCriteria, UserListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
|
||||
|
|
@ -27,6 +28,8 @@ interface FilterState {
|
|||
export class FilterStateService {
|
||||
private state: FilterState;
|
||||
private stateSubjects: Map<ListingType, BehaviorSubject<any>> = new Map();
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private isBrowser = isPlatformBrowser(this.platformId);
|
||||
|
||||
constructor() {
|
||||
// Initialize state from sessionStorage or with defaults
|
||||
|
|
@ -125,10 +128,12 @@ export class FilterStateService {
|
|||
}
|
||||
|
||||
private saveToStorage(type: ListingType): void {
|
||||
if (!this.isBrowser) return;
|
||||
sessionStorage.setItem(type, JSON.stringify(this.state[type].criteria));
|
||||
}
|
||||
|
||||
private saveSortByToStorage(type: ListingType, sortBy: SortByOptions | null): void {
|
||||
if (!this.isBrowser) return;
|
||||
const sortByKey = type === 'businessListings' ? 'businessSortBy' : type === 'commercialPropertyListings' ? 'commercialSortBy' : 'professionalsSortBy';
|
||||
|
||||
if (sortBy) {
|
||||
|
|
@ -156,9 +161,11 @@ export class FilterStateService {
|
|||
}
|
||||
|
||||
private loadCriteriaFromStorage(key: ListingType): CriteriaType {
|
||||
const stored = sessionStorage.getItem(key);
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
if (this.isBrowser) {
|
||||
const stored = sessionStorage.getItem(key);
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
}
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
|
|
@ -172,6 +179,7 @@ export class FilterStateService {
|
|||
}
|
||||
|
||||
private loadSortByFromStorage(key: string): SortByOptions | null {
|
||||
if (!this.isBrowser) return null;
|
||||
const stored = sessionStorage.getItem(key);
|
||||
return stored && stored !== 'null' ? (stored as SortByOptions) : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, PLATFORM_ID, inject } from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { lastValueFrom, Observable, of } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { CityAndStateResult, CountyResult, GeoResult, IpInfo } from '../../../../bizmatch-server/src/models/main.model';
|
||||
|
|
@ -21,6 +22,8 @@ export class GeoService {
|
|||
private readonly storageKey = 'ipInfo';
|
||||
private readonly boundaryStoragePrefix = 'nominatim_boundary_';
|
||||
private readonly cacheExpiration = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private isBrowser = isPlatformBrowser(this.platformId);
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
|
|
@ -28,6 +31,8 @@ export class GeoService {
|
|||
* Get cached boundary data from localStorage
|
||||
*/
|
||||
private getCachedBoundary(cacheKey: string): any | null {
|
||||
if (!this.isBrowser) return null;
|
||||
|
||||
try {
|
||||
const cached = localStorage.getItem(this.boundaryStoragePrefix + cacheKey);
|
||||
if (!cached) {
|
||||
|
|
@ -54,6 +59,8 @@ export class GeoService {
|
|||
* Save boundary data to localStorage
|
||||
*/
|
||||
private setCachedBoundary(cacheKey: string, data: any): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
try {
|
||||
const cachedData: CachedBoundary = {
|
||||
data: data,
|
||||
|
|
@ -69,6 +76,8 @@ export class GeoService {
|
|||
* Clear all cached boundary data
|
||||
*/
|
||||
clearBoundaryCache(): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
try {
|
||||
const keys = Object.keys(localStorage);
|
||||
keys.forEach(key => {
|
||||
|
|
@ -136,9 +145,11 @@ export class GeoService {
|
|||
|
||||
async getIpInfo(): Promise<IpInfo | null> {
|
||||
// Versuche zuerst, die Daten aus dem sessionStorage zu holen
|
||||
const storedData = sessionStorage.getItem(this.storageKey);
|
||||
if (storedData) {
|
||||
return JSON.parse(storedData);
|
||||
if (this.isBrowser) {
|
||||
const storedData = sessionStorage.getItem(this.storageKey);
|
||||
if (storedData) {
|
||||
return JSON.parse(storedData);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -146,7 +157,9 @@ export class GeoService {
|
|||
const data = await lastValueFrom(this.http.get<IpInfo>(`${this.apiBaseUrl}/bizmatch/geo/ipinfo/georesult/wysiwyg`));
|
||||
|
||||
// Speichere die Daten im sessionStorage
|
||||
sessionStorage.setItem(this.storageKey, JSON.stringify(data));
|
||||
if (this.isBrowser) {
|
||||
sessionStorage.setItem(this.storageKey, JSON.stringify(data));
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject, Injectable, PLATFORM_ID } from '@angular/core';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { KeyValue, KeyValueAsSortBy, KeyValueStyle } from '../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
|
@ -9,20 +10,53 @@ import { environment } from '../../environments/environment';
|
|||
})
|
||||
export class SelectOptionsService {
|
||||
private apiBaseUrl = environment.apiBaseUrl;
|
||||
constructor(private http: HttpClient) {}
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
async init() {
|
||||
const allSelectOptions = await lastValueFrom(this.http.get<any>(`${this.apiBaseUrl}/bizmatch/select-options`));
|
||||
this.typesOfBusiness = allSelectOptions.typesOfBusiness;
|
||||
this.prices = allSelectOptions.prices;
|
||||
this.listingCategories = allSelectOptions.listingCategories;
|
||||
this.customerTypes = allSelectOptions.customerTypes;
|
||||
this.customerSubTypes = allSelectOptions.customerSubTypes;
|
||||
this.states = allSelectOptions.locations;
|
||||
this.gender = allSelectOptions.gender;
|
||||
this.typesOfCommercialProperty = allSelectOptions.typesOfCommercialProperty;
|
||||
this.distances = allSelectOptions.distances;
|
||||
this.sortByOptions = allSelectOptions.sortByOptions;
|
||||
// Skip HTTP call on server-side to avoid blocking SSR
|
||||
if (!isPlatformBrowser(this.platformId)) {
|
||||
console.log('[SSR] SelectOptionsService.init() - Skipping HTTP call on server');
|
||||
// Initialize with empty arrays - client will hydrate with real data
|
||||
this.typesOfBusiness = [];
|
||||
this.prices = [];
|
||||
this.listingCategories = [];
|
||||
this.customerTypes = [];
|
||||
this.customerSubTypes = [];
|
||||
this.states = [];
|
||||
this.gender = [];
|
||||
this.typesOfCommercialProperty = [];
|
||||
this.distances = [];
|
||||
this.sortByOptions = [];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const allSelectOptions = await lastValueFrom(this.http.get<any>(`${this.apiBaseUrl}/bizmatch/select-options`));
|
||||
this.typesOfBusiness = allSelectOptions.typesOfBusiness;
|
||||
this.prices = allSelectOptions.prices;
|
||||
this.listingCategories = allSelectOptions.listingCategories;
|
||||
this.customerTypes = allSelectOptions.customerTypes;
|
||||
this.customerSubTypes = allSelectOptions.customerSubTypes;
|
||||
this.states = allSelectOptions.locations;
|
||||
this.gender = allSelectOptions.gender;
|
||||
this.typesOfCommercialProperty = allSelectOptions.typesOfCommercialProperty;
|
||||
this.distances = allSelectOptions.distances;
|
||||
this.sortByOptions = allSelectOptions.sortByOptions;
|
||||
} catch (error) {
|
||||
console.error('[SelectOptionsService] Failed to load select options:', error);
|
||||
// Initialize with empty arrays as fallback
|
||||
this.typesOfBusiness = this.typesOfBusiness || [];
|
||||
this.prices = this.prices || [];
|
||||
this.listingCategories = this.listingCategories || [];
|
||||
this.customerTypes = this.customerTypes || [];
|
||||
this.customerSubTypes = this.customerSubTypes || [];
|
||||
this.states = this.states || [];
|
||||
this.gender = this.gender || [];
|
||||
this.typesOfCommercialProperty = this.typesOfCommercialProperty || [];
|
||||
this.distances = this.distances || [];
|
||||
this.sortByOptions = this.sortByOptions || [];
|
||||
}
|
||||
}
|
||||
public typesOfBusiness: Array<KeyValueStyle>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Injectable, inject } from '@angular/core';
|
||||
import { Injectable, inject, PLATFORM_ID } from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { Meta, Title } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
|
|
@ -19,6 +20,8 @@ export class SeoService {
|
|||
private meta = inject(Meta);
|
||||
private title = inject(Title);
|
||||
private router = inject(Router);
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private isBrowser = isPlatformBrowser(this.platformId);
|
||||
|
||||
private readonly defaultImage = 'https://biz-match.com/assets/images/bizmatch-og-image.jpg';
|
||||
private readonly siteName = 'BizMatch';
|
||||
|
|
@ -109,6 +112,8 @@ export class SeoService {
|
|||
* Update canonical URL
|
||||
*/
|
||||
private updateCanonicalUrl(url: string): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
let link: HTMLLinkElement | null = document.querySelector('link[rel="canonical"]');
|
||||
|
||||
if (link) {
|
||||
|
|
@ -267,6 +272,8 @@ export class SeoService {
|
|||
* Inject JSON-LD structured data into page
|
||||
*/
|
||||
injectStructuredData(schema: object): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
// Remove existing schema script
|
||||
const existingScript = document.querySelector('script[type="application/ld+json"]');
|
||||
if (existingScript) {
|
||||
|
|
@ -284,6 +291,8 @@ export class SeoService {
|
|||
* Clear all structured data
|
||||
*/
|
||||
clearStructuredData(): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
|
||||
scripts.forEach(script => script.remove());
|
||||
}
|
||||
|
|
@ -480,6 +489,8 @@ export class SeoService {
|
|||
* Inject multiple structured data schemas
|
||||
*/
|
||||
injectMultipleSchemas(schemas: object[]): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
// Remove existing schema scripts
|
||||
this.clearStructuredData();
|
||||
|
||||
|
|
@ -591,6 +602,8 @@ export class SeoService {
|
|||
* Inject pagination link elements (rel="next" and rel="prev")
|
||||
*/
|
||||
injectPaginationLinks(baseUrl: string, currentPage: number, totalPages: number): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
// Remove existing pagination links
|
||||
document.querySelectorAll('link[rel="next"], link[rel="prev"]').forEach(link => link.remove());
|
||||
|
||||
|
|
@ -615,6 +628,8 @@ export class SeoService {
|
|||
* Clear pagination links
|
||||
*/
|
||||
clearPaginationLinks(): void {
|
||||
if (!this.isBrowser) return;
|
||||
|
||||
document.querySelectorAll('link[rel="next"], link[rel="prev"]').forEach(link => link.remove());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,8 +159,10 @@ export function formatPhoneNumber(phone: string): string {
|
|||
}
|
||||
|
||||
export const getSessionStorageHandler = function (criteriaType, path, value, previous, applyData) {
|
||||
sessionStorage.setItem(`${criteriaType}_criteria`, JSON.stringify(this));
|
||||
console.log('Zusätzlicher Parameter:', criteriaType);
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem(`${criteriaType}_criteria`, JSON.stringify(this));
|
||||
console.log('Zusätzlicher Parameter:', criteriaType);
|
||||
}
|
||||
};
|
||||
export const getSessionStorageHandlerWrapper = param => {
|
||||
return function (path, value, previous, applyData) {
|
||||
|
|
@ -191,6 +193,11 @@ export function map2User(jwt: string | null): KeycloakUser {
|
|||
}
|
||||
export function getImageDimensions(imageUrl: string): Promise<{ width: number; height: number }> {
|
||||
return new Promise(resolve => {
|
||||
// Only use Image in browser context
|
||||
if (typeof Image === 'undefined') {
|
||||
resolve({ width: 0, height: 0 });
|
||||
return;
|
||||
}
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
resolve({ width: img.width, height: img.height });
|
||||
|
|
@ -295,9 +302,11 @@ export function checkAndUpdate(changed: boolean, condition: boolean, assignment:
|
|||
return changed || condition;
|
||||
}
|
||||
export function removeSortByStorage() {
|
||||
sessionStorage.removeItem('businessSortBy');
|
||||
sessionStorage.removeItem('commercialSortBy');
|
||||
sessionStorage.removeItem('professionalsSortBy');
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.removeItem('businessSortBy');
|
||||
sessionStorage.removeItem('commercialSortBy');
|
||||
sessionStorage.removeItem('professionalsSortBy');
|
||||
}
|
||||
}
|
||||
// -----------------------------
|
||||
// Criteria Proxy
|
||||
|
|
@ -311,8 +320,11 @@ export function getCriteriaStateObject(criteriaType: 'businessListings' | 'comme
|
|||
} else {
|
||||
initialState = createEmptyUserListingCriteria();
|
||||
}
|
||||
const storedState = sessionStorage.getItem(`${criteriaType}`);
|
||||
return storedState ? JSON.parse(storedState) : initialState;
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
const storedState = sessionStorage.getItem(`${criteriaType}`);
|
||||
return storedState ? JSON.parse(storedState) : initialState;
|
||||
}
|
||||
return initialState;
|
||||
}
|
||||
export function getCriteriaProxy(path: string, component: any): BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria {
|
||||
if ('businessListings' === path) {
|
||||
|
|
@ -327,7 +339,9 @@ export function getCriteriaProxy(path: string, component: any): BusinessListingC
|
|||
}
|
||||
export function createEnhancedProxy(obj: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria, component: any) {
|
||||
const sessionStorageHandler = function (path, value, previous, applyData) {
|
||||
sessionStorage.setItem(`${obj.criteriaType}`, JSON.stringify(this));
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.setItem(`${obj.criteriaType}`, JSON.stringify(this));
|
||||
}
|
||||
};
|
||||
|
||||
return onChange(obj, function (path, value, previous, applyData) {
|
||||
|
|
@ -341,16 +355,20 @@ export function createEnhancedProxy(obj: BusinessListingCriteria | CommercialPro
|
|||
});
|
||||
}
|
||||
export function getCriteriaByListingCategory(listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty') {
|
||||
if (typeof sessionStorage === 'undefined') return null;
|
||||
|
||||
const storedState =
|
||||
listingsCategory === 'business'
|
||||
? sessionStorage.getItem('businessListings')
|
||||
: listingsCategory === 'commercialProperty'
|
||||
? sessionStorage.getItem('commercialPropertyListings')
|
||||
: sessionStorage.getItem('brokerListings');
|
||||
return JSON.parse(storedState);
|
||||
return storedState ? JSON.parse(storedState) : null;
|
||||
}
|
||||
|
||||
export function getSortByListingCategory(listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty') {
|
||||
if (typeof sessionStorage === 'undefined') return null;
|
||||
|
||||
const storedSortBy =
|
||||
listingsCategory === 'business' ? sessionStorage.getItem('businessSortBy') : listingsCategory === 'commercialProperty' ? sessionStorage.getItem('commercialSortBy') : sessionStorage.getItem('professionalsSortBy');
|
||||
const sortBy = storedSortBy && storedSortBy !== 'null' ? (storedSortBy as SortByOptions) : null;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Build information, automatically generated by `the_build_script` :zwinkern:
|
||||
const build = {
|
||||
timestamp: "GER: 04.01.2026 12:21 | TX: 01/04/2026 5:21 AM"
|
||||
timestamp: "GER: 06.01.2026 22:33 | TX: 01/06/2026 3:33 PM"
|
||||
};
|
||||
|
||||
export default build;
|
||||
|
|
@ -1,10 +1,20 @@
|
|||
// IMPORTANT: DOM polyfill must be imported FIRST, before any browser-dependent libraries
|
||||
import './ssr-dom-polyfill';
|
||||
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { config } from './app/app.config.server';
|
||||
|
||||
const bootstrap = () => bootstrapApplication(AppComponent, config);
|
||||
const bootstrap = (context: BootstrapContext) => {
|
||||
console.log('[SSR] Bootstrap function called');
|
||||
const appRef = bootstrapApplication(AppComponent, config, context);
|
||||
appRef.then(() => {
|
||||
console.log('[SSR] Application bootstrapped successfully');
|
||||
}).catch((err) => {
|
||||
console.error('[SSR] Bootstrap error:', err);
|
||||
console.error('[SSR] Error stack:', err.stack);
|
||||
});
|
||||
return appRef;
|
||||
};
|
||||
|
||||
export default bootstrap;
|
||||
|
|
|
|||
Loading…
Reference in New Issue