Sidebar Bottom Menu #16

Merged
igorpropisnov merged 3 commits from feature/adding-bottom-menu into main 2024-07-18 22:53:50 +02:00
3 changed files with 96 additions and 34 deletions
Showing only changes of commit 1786cc3077 - Show all commits

View File

@ -1,5 +1,4 @@
<div class="flex h-screen overflow-hidden"> <div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<div <div
[ngStyle]="navigation" [ngStyle]="navigation"
[class]=" [class]="
@ -8,10 +7,10 @@
: showMobileMenu : showMobileMenu
? 'bg-primary w-64 transition-all duration-300 ease-in-out' ? 'bg-primary w-64 transition-all duration-300 ease-in-out'
: isDesktopCollapsed : isDesktopCollapsed
? 'bg-primary w-48 md:w-16 transition-all duration-300 ease-in-out' ? 'bg-primary w-48 md:w-14 transition-all duration-300 ease-in-out'
: 'bg-primary w-48 md:w-48 transition-all duration-300 ease-in-out' : 'bg-primary w-48 md:w-48 transition-all duration-300 ease-in-out'
" "
class="transform h-full z-20 overflow-y-auto fixed md:relative"> class="transform h-full z-20 overflow-y-auto fixed md:relative flex flex-col">
<div <div
[ngClass]="showMobileMenu ? 'justify-center' : 'justify-between'" [ngClass]="showMobileMenu ? 'justify-center' : 'justify-between'"
[ngStyle]="navigation" [ngStyle]="navigation"
@ -19,7 +18,7 @@
<div class="flex items-center justify-center h-full w-full"> <div class="flex items-center justify-center h-full w-full">
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
@if (!isCollapsed && !isDesktopCollapsed) { @if (!isCollapsed && !isDesktopCollapsed) {
<div class="text-primary">Logo</div> <div class="text-primary">LOGO</div>
} }
@if (!isCollapsed && !showMobileMenu) { @if (!isCollapsed && !showMobileMenu) {
@ -58,15 +57,44 @@
</div> </div>
<ul class="m-1"> <ul class="m-1">
<li <li
class="cursor-pointer rounded-btn" class="cursor-pointer rounded-btn mt-2"
[ngClass]="{ [ngClass]="{
'bg-base-100 text-primary': item.active, 'bg-base-100 text-primary': item.active,
'text-primary-content hover:text-base-content': !item.active 'text-primary-content hover:text-base-content': !item.active
}" }"
(click)="setActive(item)" (click)="setActive(item)"
(keydown.enter)="setActive(item)"
(keydown.space)="setActive(item)"
tabindex="0"
role="button"
*ngFor="let item of menuItems"> *ngFor="let item of menuItems">
<div <div
class="flex justify-center p-2" class="flex justify-center p-1"
*ngIf="isDesktopCollapsed && !showMobileMenu">
<span class="p-1" [innerHTML]="item.icon"></span>
</div>
<div
class="flex items-center rounded-btn justify-between cursor-pointer px-1 py-2"
*ngIf="!isDesktopCollapsed || showMobileMenu">
<div class="flex items-center">
<span [innerHTML]="item.icon" class="mx-2"></span>
<span>{{ item.name }}</span>
</div>
</div>
</li>
</ul>
<ul class="m-1 mt-auto">
<li
class="cursor-pointer bg-base-100 text-base-content rounded-btn mb-2"
*ngFor="let item of bottomMenuItems"
(click)="item.action ? item.action() : null"
(keydown.enter)="item.action ? item.action() : null"
(keydown.space)="item.action ? item.action() : null"
tabindex="0"
role="button">
<div
class="flex justify-center p-1"
*ngIf="isDesktopCollapsed && !showMobileMenu"> *ngIf="isDesktopCollapsed && !showMobileMenu">
<span class="p-1" [innerHTML]="item.icon"></span> <span class="p-1" [innerHTML]="item.icon"></span>
</div> </div>

View File

@ -9,15 +9,23 @@ import {
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Router, RouterOutlet } from '@angular/router'; import { Router, RouterOutlet } from '@angular/router';
import { SuccessDtoApiModel } from '../../api';
import { BackgroundPatternService, ThemeService } from '../../shared/service'; import { BackgroundPatternService, ThemeService } from '../../shared/service';
import { AuthService } from '../../shared/service/auth.service';
interface MenuItem { interface TopMenuItem {
name: string; name: string;
icon: SafeHtml; icon: SafeHtml;
route: string; route: string;
active?: boolean; active?: boolean;
} }
interface BottomMenuItem {
name: string;
icon: SafeHtml;
action?: () => void;
}
@Component({ @Component({
selector: 'app-layout', selector: 'app-layout',
standalone: true, standalone: true,
@ -31,7 +39,7 @@ export class LayoutComponent implements OnInit {
public isDesktopCollapsed: boolean = false; public isDesktopCollapsed: boolean = false;
public showMobileMenu: boolean = false; public showMobileMenu: boolean = false;
public userHasInteracted: boolean = false; public userHasInteracted: boolean = false;
public menuItems: MenuItem[] = [ public menuItems: TopMenuItem[] = [
{ {
name: 'Dashboard', name: 'Dashboard',
route: '/dashboard', route: '/dashboard',
@ -41,6 +49,16 @@ export class LayoutComponent implements OnInit {
</svg>`), </svg>`),
}, },
]; ];
public bottomMenuItems: BottomMenuItem[] = [
{
name: 'Logout',
action: () => this.signout(),
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="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15m-3 0-3-3m0 0 3-3m-3 3H15" />
</svg>`),
},
];
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;
@ -49,7 +67,8 @@ export class LayoutComponent implements OnInit {
private readonly router: Router, private readonly router: Router,
private readonly backgroundPatternService: BackgroundPatternService, private readonly backgroundPatternService: BackgroundPatternService,
private readonly themeService: ThemeService, private readonly themeService: ThemeService,
private readonly el: ElementRef private readonly el: ElementRef,
private readonly authService: AuthService
) {} ) {}
public ngOnInit(): void { public ngOnInit(): void {
@ -122,8 +141,8 @@ export class LayoutComponent implements OnInit {
this.isDesktopCollapsed = !this.isDesktopCollapsed; this.isDesktopCollapsed = !this.isDesktopCollapsed;
} }
public setActive(item: MenuItem): void { public setActive(item: TopMenuItem): void {
this.menuItems.forEach((menu: MenuItem) => { this.menuItems.forEach((menu: TopMenuItem) => {
menu.active = false; menu.active = false;
}); });
item.active = true; item.active = true;
@ -132,8 +151,18 @@ export class LayoutComponent implements OnInit {
private setActiveItemBasedOnRoute(): void { private setActiveItemBasedOnRoute(): void {
const url = this.router.url; const url = this.router.url;
this.menuItems.forEach((item: MenuItem) => { this.menuItems.forEach((item: TopMenuItem) => {
item.active = url.startsWith(item.route); item.active = url.startsWith(item.route);
}); });
} }
private signout(): void {
this.authService.signout().subscribe((response: SuccessDtoApiModel) => {
if (response.success) {
this.router.navigate(['/welcome'], {
queryParams: { signedOut: true },
});
}
});
}
} }

View File

@ -25,7 +25,7 @@ import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox'; import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext'; import { InputTextModule } from 'primeng/inputtext';
import { PasswordModule } from 'primeng/password'; import { PasswordModule } from 'primeng/password';
import { delay, finalize, tap } from 'rxjs'; import { delay, finalize, takeWhile, tap } from 'rxjs';
import { import {
Configuration, Configuration,
@ -78,6 +78,7 @@ export class RegisterRootComponent implements OnInit {
public verified: InputSignal<boolean> = input<boolean>(false); public verified: InputSignal<boolean> = input<boolean>(false);
public login: InputSignal<boolean> = input<boolean>(false); public login: InputSignal<boolean> = input<boolean>(false);
public email: InputSignal<string> = input<string>(''); public email: InputSignal<string> = input<string>('');
public signedOut: InputSignal<boolean> = input<boolean>(true);
public form!: FormGroup; public form!: FormGroup;
public rememberMe: FormControl = new FormControl(false); public rememberMe: FormControl = new FormControl(false);
public isSigninSignal: WritableSignal<boolean> = signal(false); public isSigninSignal: WritableSignal<boolean> = signal(false);
@ -107,30 +108,10 @@ export class RegisterRootComponent implements OnInit {
this.clearRouteParams(); this.clearRouteParams();
} }
}); });
const rememberMe = this.localStorageService.getItem<boolean>('remember-me');
if (rememberMe) {
this.authService
.status()
.pipe(delay(1500))
.subscribe({
next: (response: SuccessDtoApiModel) => {
if (response.success) {
this.router.navigate(['/dashboard']);
} else {
this.displaySkeleton.set(false);
}
},
error: () => {
this.displaySkeleton.set(false);
},
});
} else {
this.displaySkeleton.set(false);
}
} }
public ngOnInit(): void { public ngOnInit(): void {
this.autologin();
this.setBackground(); this.setBackground();
this.initializeForm(); this.initializeForm();
this.setupValueChanges(); this.setupValueChanges();
@ -141,6 +122,30 @@ export class RegisterRootComponent implements OnInit {
} }
} }
public autologin(): void {
const rememberMe = this.localStorageService.getItem<boolean>('remember-me');
if (rememberMe && !this.signedOut()) {
this.authService
.status()
.pipe(
delay(1500),
takeWhile((response: SuccessDtoApiModel) => response.success, true),
tap({
next: (response: SuccessDtoApiModel) => {
if (response.success) {
this.router.navigate(['/dashboard']);
}
},
finalize: () => this.displaySkeleton.set(false),
})
)
.subscribe();
} else {
this.displaySkeleton.set(false);
}
}
public setBackground(): void { public setBackground(): void {
const theme = this.themeService.getTheme(); const theme = this.themeService.getTheme();
let opacity: number; let opacity: number;