npm run serve:ssr funktioniert und Hamburger Menu bug fix

This commit is contained in:
2026-01-06 22:36:14 +01:00
parent 43027a54f7
commit 4f8fd77f7d
21 changed files with 371 additions and 111 deletions

View File

@@ -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) {

View File

@@ -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 }}"

View File

@@ -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() {

View File

@@ -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');
}
}

View File

@@ -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) {