feature/refactor-login #19

Merged
igorpropisnov merged 26 commits from feature/refactor-login into main 2024-09-19 13:58:12 +02:00
4 changed files with 279 additions and 63 deletions
Showing only changes of commit e17e4191b7 - Show all commits

View File

@ -46,6 +46,11 @@ const protectedRoutes: Routes = [
}, },
], ],
}, },
{
path: 'foo',
loadComponent: () =>
import('./pages/foo-root/foo.component').then((m) => m.FooComponent),
},
]; ];
export const routes: Routes = [ export const routes: Routes = [

View File

@ -1,14 +1,16 @@
<!-- app.component.html -->
<div class="flex flex-col h-screen overflow-hidden"> <div class="flex flex-col h-screen overflow-hidden">
<!-- Header --> <!-- Header -->
<header class="w-full navbar bg-primary text-primary-content z-20"> <header class="w-full navbar bg-primary text-primary-content z-40">
<div class="flex-1"> <div class="flex-1">
<a class="btn btn-ghost normal-case text-xl text-primary-content"> <a class="btn btn-ghost normal-case text-xl text-primary-content">
[APP-NAME] [APP-NAME]
</a> </a>
</div> </div>
<!-- Der Button wird nur auf mobilen Geräten angezeigt -->
<div class="flex-none lg:hidden"> <div class="flex-none lg:hidden">
<label for="my-drawer" class="btn btn-square btn-ghost drawer-button"> <button
(click)="toggleDrawer()"
class="btn btn-square btn-ghost drawer-button">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
@ -20,65 +22,119 @@
stroke-width="2" stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"></path> d="M4 6h16M4 12h16M4 18h16"></path>
</svg> </svg>
</label> </button>
</div>
<div class="flex-none hidden lg:block">
<!-- Zusätzliche Header-Elemente für große Bildschirme -->
</div> </div>
</header> </header>
<!-- Hauptcontainer --> <!-- Hauptcontainer -->
<div class="drawer lg:drawer-open flex-1 overflow-hidden"> <div class="flex-1 flex overflow-hidden relative">
<input id="my-drawer" type="checkbox" class="drawer-toggle" /> <!-- Backdrop -->
<div
<!-- Hauptinhalt --> *ngIf="isDrawerOpen"
<div class="drawer-content flex flex-col bg-base-100"> class="fixed inset-0 bg-black bg-opacity-50 z-20 lg:hidden"
<main class="flex-1 overflow-y-auto p-4"> (click)="toggleDrawer()"></div>
<div class="w-full h-full flex items-center justify-center">
<div class="w-full max-w-5xl">
<router-outlet></router-outlet>
</div>
</div>
</main>
</div>
<!-- Drawer --> <!-- Drawer -->
<div class="drawer-side h-full"> <div
<label for="my-drawer" class="drawer-overlay"></label> class="h-full transition-transform duration-300 ease-in-out bg-primary text-primary-content w-64 flex flex-col lg:translate-x-0"
<aside class="bg-primary text-primary-content w-64 h-full flex flex-col"> [ngClass]="{
<div class="flex-1 overflow-y-auto py-4 pt-0"> 'translate-x-0': isDrawerOpen,
'-translate-x-full': !isDrawerOpen,
'fixed lg:relative': true,
'top-0 left-0 z-30': true
}">
<aside class="h-full flex flex-col">
<!-- Drawer-Inhalt -->
<div class="flex-1 overflow-y-auto pt-16 lg:pt-0">
<ul class="w-full p-0 m-0 [&_li>*]:rounded-none"> <ul class="w-full p-0 m-0 [&_li>*]:rounded-none">
<ng-container *ngFor="let item of menuItems"> <ng-container *ngFor="let item of menuItems">
<li class="w-full"> <li class="w-full">
<a <ng-container *ngIf="!item.subitems; else submenu">
[routerLink]="item.route" <a
routerLinkActive="bg-base-100 text-base-content" [routerLink]="item.route"
[routerLinkActiveOptions]="{ exact: false }" [class.active]="item.active"
class="flex items-center w-full px-4 py-3 text-base-content transition-colors duration-200 ease-in-out focus:outline-none" class="flex items-center w-full px-4 py-3 transition-colors duration-200 ease-in-out focus:outline-none hover:bg-base-300 hover:text-primary"
role="menuitem" [class.bg-base-100]="item.active"
tabindex="0" [class.text-base-content]="item.active"
(keydown.enter)=" [class.text-primary]="item.active"
$event.preventDefault(); navigateTo(item.route) [class.font-semibold]="item.active"
"> role="menuitem"
<span tabindex="0"
class="flex-shrink-0 w-6 h-6 mr-3" (click)="onLinkClick()">
[innerHTML]="item.icon"></span> <span
<span class="flex-grow">{{ item.name }}</span> class="flex-shrink-0 w-6 h-6 mr-3"
</a> [innerHTML]="item.icon"></span>
<span class="flex-grow">{{ item.name }}</span>
</a>
</ng-container>
<ng-template #submenu>
<div
(click)="toggleSubmenu(item, $event)"
class="flex items-center w-full px-4 py-3 transition-colors duration-200 ease-in-out focus:outline-none hover:bg-base-300 hover:text-primary cursor-pointer"
[class.bg-base-300]="item.active"
[class.text-primary]="item.active"
[class.font-semibold]="item.active"
[class.bg-base-100]="item.isOpen"
[class.text-base-content]="item.isOpen">
<span
class="flex-shrink-0 w-6 h-6 mr-3"
[innerHTML]="item.icon"></span>
<span class="flex-grow">{{ item.name }}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4 transition-transform"
[class.rotate-180]="item.isOpen">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
</div>
<div
[@submenuAnimation]="item.isOpen ? 'open' : 'closed'"
class="overflow-hidden">
<ul class="bg-base-100">
<li *ngFor="let subItem of item.subitems" class="w-full">
<a
[routerLink]="subItem.route"
[class.active]="subItem.active"
class="flex items-center w-full px-4 pl-8 py-2 transition-colors duration-200 ease-in-out focus:outline-none hover:bg-base-300 hover:text-primary"
[class.bg-base-300]="subItem.active"
[class.text-primary]="subItem.active"
[class.font-semibold]="subItem.active"
[class.bg-base-100]="item.isOpen"
[class.text-base-content]="item.isOpen"
role="menuitem"
tabindex="0"
(click)="onLinkClick()">
<span
class="flex-shrink-0 w-5 h-5 mr-2"
[innerHTML]="subItem.icon"></span>
{{ subItem.name }}
</a>
</li>
</ul>
</div>
</ng-template>
</li> </li>
</ng-container> </ng-container>
</ul> </ul>
</div> </div>
<div class="p-4"> <hr class="border-t border-base-100" />
<ul class="menu w-full">
<div style="background-color: rgba(0, 0, 0, 0.2)">
<ul class="w-full px-2 py-4">
<ng-container *ngFor="let item of bottomMenuItems"> <ng-container *ngFor="let item of bottomMenuItems">
<li> <li>
<button <button
(click)="executeAction(item)" (click)="executeAction(item)"
(keydown.enter)="executeAction(item)" (keydown.enter)="executeAction(item)"
class="flex items-center space-x-2 hover:bg-primary-focus focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-focus w-full text-left" class="py-2 px-4 rounded flex items-center space-x-2 bg-base-100 text-base-content hover:text-primary hover:font-semibold w-full text-left">
role="menuitem"
tabindex="0">
<span [innerHTML]="item.icon"></span> <span [innerHTML]="item.icon"></span>
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
</button> </button>
@ -88,5 +144,16 @@
</div> </div>
</aside> </aside>
</div> </div>
<!-- Hauptinhalt -->
<div class="flex-1 overflow-y-auto p-4 bg-base-100">
<main class="flex-1">
<div class="w-full h-full flex items-center justify-center">
<div class="w-full max-w-5xl">
<router-outlet></router-outlet>
</div>
</div>
</main>
</div>
</div> </div>
</div> </div>

View File

@ -1,8 +1,16 @@
import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
ElementRef, ElementRef,
OnDestroy,
OnInit, OnInit,
} from '@angular/core'; } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@ -14,7 +22,7 @@ import {
RouterOutlet, RouterOutlet,
} from '@angular/router'; } from '@angular/router';
import { filter } from 'rxjs'; import { filter, Subject, takeUntil } from 'rxjs';
import { SuccessDtoApiModel } from '../../api'; import { SuccessDtoApiModel } from '../../api';
import { BackgroundPatternService, ThemeService } from '../../shared/service'; import { BackgroundPatternService, ThemeService } from '../../shared/service';
@ -23,8 +31,17 @@ import { AuthService } from '../../shared/service/auth.service';
interface TopMenuItem { interface TopMenuItem {
name: string; name: string;
icon: SafeHtml; icon: SafeHtml;
route?: string;
active?: boolean;
subitems?: SubMenuItem[];
isOpen?: boolean;
}
interface SubMenuItem {
name: string;
route: string; route: string;
active?: boolean; active?: boolean;
icon?: SafeHtml;
} }
interface BottomMenuItem { interface BottomMenuItem {
@ -39,12 +56,32 @@ interface BottomMenuItem {
providers: [], providers: [],
imports: [RouterOutlet, CommonModule, RouterModule], imports: [RouterOutlet, CommonModule, RouterModule],
templateUrl: './layout.component.html', templateUrl: './layout.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.Default,
animations: [
trigger('submenuAnimation', [
state(
'closed',
style({
height: '0',
opacity: '0',
})
),
state(
'open',
style({
height: '*',
opacity: '1',
})
),
transition('closed <=> open', [animate('200ms ease-in-out')]),
]),
],
}) })
export class LayoutComponent implements OnInit { export class LayoutComponent implements OnInit, OnDestroy {
public isCollapsed: boolean = false; public isCollapsed: boolean = false;
public isDesktopCollapsed: boolean = false; public isDesktopCollapsed: boolean = false;
public showMobileMenu: boolean = false; public showMobileMenu: boolean = false;
public isDrawerOpen: boolean = true;
public menuItems: TopMenuItem[] = [ public menuItems: TopMenuItem[] = [
{ {
name: 'Dashboard', name: 'Dashboard',
@ -56,11 +93,41 @@ export class LayoutComponent implements OnInit {
}, },
{ {
name: 'Event', name: 'Event',
route: '/event',
icon: this.sanitizer icon: this.sanitizer
.bypassSecurityTrustHtml(`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> .bypassSecurityTrustHtml(`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 0 1 0 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 0 1 0-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 0 1 0 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 0 1 0-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375Z" />
</svg>`), </svg>`),
route: '/event',
},
{
name: 'Profile',
route: '/profile',
icon: this.sanitizer
.bypassSecurityTrustHtml(`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
</svg>`),
subitems: [
{
name: 'Edit Profile',
route: '/foo',
icon: this.sanitizer.bypassSecurityTrustHtml(`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536-8.678 8.678H6.554v-3.536l8.678-8.678z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M16.368 4.096a1.5 1.5 0 0 1 2.121 0l1.415 1.415a1.5 1.5 0 0 1 0 2.121l-.707.707-3.536-3.536.707-.707z" />
</svg>
`),
},
{
name: 'Delete Profile',
route: '/foo-1',
icon: this.sanitizer.bypassSecurityTrustHtml(`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7H5m3-4h8m1 4v12a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V7h12z" />
</svg>
`),
},
],
isOpen: false,
}, },
]; ];
public bottomMenuItems: BottomMenuItem[] = [ public bottomMenuItems: BottomMenuItem[] = [
@ -75,6 +142,7 @@ export class LayoutComponent implements OnInit {
]; ];
public mainContent: { 'background-image': string } | null = null; public mainContent: { 'background-image': string } | null = null;
public navigation: { 'background-image': string } | null = null; public navigation: { 'background-image': string } | null = null;
private destroy$: Subject<void> = new Subject<void>();
public constructor( public constructor(
private readonly sanitizer: DomSanitizer, private readonly sanitizer: DomSanitizer,
@ -87,14 +155,53 @@ export class LayoutComponent implements OnInit {
) {} ) {}
public ngOnInit(): void { public ngOnInit(): void {
this.router.events this.adjustDrawerState(window.innerWidth);
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(() => {
this.setActiveItemBasedOnRoute();
});
// Initial set window.addEventListener('resize', () => {
this.setActiveItemBasedOnRoute(); this.adjustDrawerState(window.innerWidth);
});
this.updateMenuState(this.router.url);
this.router.events
.pipe(
filter(
(event): event is NavigationEnd => event instanceof NavigationEnd
),
takeUntil(this.destroy$)
)
.subscribe((event: NavigationEnd) => {
this.updateMenuState(event.urlAfterRedirects);
});
}
public onResize(): void {
this.adjustDrawerState(window.innerWidth);
}
public onLinkClick(): void {
if (window.innerWidth < 1024 && this.isDrawerOpen) {
this.isDrawerOpen = false;
}
}
public ngOnDestroy(): void {
window.removeEventListener('resize', this.onResize.bind(this));
this.destroy$.next();
this.destroy$.complete();
}
public toggleDrawer(): void {
this.isDrawerOpen = !this.isDrawerOpen;
}
public adjustDrawerState(width: number): void {
// Hier wird geprüft, ob wir uns im mobilen Bereich befinden.
if (width < 1024) {
this.toggleDrawer();
} else {
this.toggleDrawer();
}
} }
public navigateTo(route: string): void { public navigateTo(route: string): void {
@ -107,6 +214,38 @@ export class LayoutComponent implements OnInit {
} }
} }
public toggleSubmenu(item: TopMenuItem, event: Event): void {
event.preventDefault();
event.stopPropagation();
item.isOpen = !item.isOpen;
}
private updateMenuState(currentRoute: string): void {
this.menuItems.forEach((item: TopMenuItem) => {
// Set top-level items active state
if (item.route) {
item.active = currentRoute.startsWith(item.route);
}
// Handle subitems
if (item.subitems) {
// Check if any subitem matches the current route
const activeSubItem = item.subitems.some((subItem) =>
currentRoute.startsWith(subItem.route)
);
// Set the parent item and subitem active state
item.active = activeSubItem;
item.isOpen = activeSubItem;
// Set active states for all subitems
item.subitems.forEach((subItem) => {
subItem.active = currentRoute.startsWith(subItem.route);
});
}
});
}
// @HostListener('window:resize', ['$event']) // @HostListener('window:resize', ['$event'])
// public onResize(): void { // public onResize(): void {
// if (window.innerWidth >= 768) { // if (window.innerWidth >= 768) {
@ -177,14 +316,6 @@ export class LayoutComponent implements OnInit {
// } // }
// } // }
private setActiveItemBasedOnRoute(): void {
const currentRoute = this.router.url;
this.menuItems.forEach((item: TopMenuItem) => {
item.active = currentRoute.startsWith(item.route);
});
}
private signout(): void { private signout(): void {
this.authService.signout().subscribe((response: SuccessDtoApiModel) => { this.authService.signout().subscribe((response: SuccessDtoApiModel) => {
if (response.success) { if (response.success) {

View File

@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterOutlet, RouterModule } from '@angular/router';
@Component({
selector: 'app-foo',
standalone: true,
template: '<h1>Foo</h1>',
providers: [],
imports: [RouterOutlet, CommonModule, RouterModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FooComponent {}