feat(account-page): implement dynamic account navigation menus in sidebar and user dropdown
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
<header class="store-layout__header">
|
||||
<div class="store-layout__header-main">
|
||||
<div class="container-xl px-3 px-md-4 py-3 py-lg-4 d-flex flex-row flex-wrap align-items-center justify-content-between gap-3">
|
||||
<div
|
||||
class="container-xl px-3 px-md-4 py-3 py-lg-4 d-flex flex-row flex-wrap align-items-center justify-content-between gap-3"
|
||||
>
|
||||
<a
|
||||
routerLink="/"
|
||||
class="store-layout__brand-slot d-inline-flex align-items-center gap-3 flex-shrink-0 text-decoration-none"
|
||||
@@ -16,7 +18,9 @@
|
||||
}
|
||||
</a>
|
||||
|
||||
<div class="d-flex flex-row align-items-center gap-3 ms-auto flex-nowrap flex-grow-1 justify-content-between justify-content-md-end">
|
||||
<div
|
||||
class="d-flex flex-row align-items-center gap-3 ms-auto flex-nowrap flex-grow-1 justify-content-between justify-content-md-end"
|
||||
>
|
||||
<div class="input-group store-layout__search" role="search">
|
||||
<label class="visually-hidden" for="store-search-input">Buscar productos</label>
|
||||
<input
|
||||
@@ -33,7 +37,10 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-end gap-1" aria-label="Acciones de usuario">
|
||||
<div
|
||||
class="d-flex align-items-center justify-content-end gap-1"
|
||||
aria-label="Acciones de usuario"
|
||||
>
|
||||
<app-cart-icon
|
||||
[quantity]="cartQuantity()"
|
||||
ariaLabel="Carrito de compras"
|
||||
@@ -54,6 +61,7 @@
|
||||
<app-user-dropdown
|
||||
id="store-user-dropdown"
|
||||
[user]="user()"
|
||||
[accountNavigationMenus]="accountNavigationMenus()"
|
||||
(logoutClick)="onLogoutClick()"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import { AuthUser } from '../../../services/auth/auth.interfaces';
|
||||
import { CartIconComponent } from '../../../../shared/components/cart-icon/cart-icon.component';
|
||||
import { IconButtonComponent } from '../../../../shared/components/icon-button/icon-button.component';
|
||||
import { UserDropdownComponent } from './user-dropdown/user-dropdown.component';
|
||||
import { Menu } from '../../../services/tenant.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-store-header',
|
||||
imports: [CartIconComponent, IconButtonComponent, RouterLink, UserDropdownComponent],
|
||||
templateUrl: './store-header.component.html',
|
||||
styleUrl: './store-header.component.scss'
|
||||
styleUrl: './store-header.component.scss',
|
||||
})
|
||||
export class StoreHeaderComponent {
|
||||
private readonly elementRef = inject(ElementRef<HTMLElement>);
|
||||
@@ -18,6 +19,7 @@ export class StoreHeaderComponent {
|
||||
readonly cartQuantity = input<number>(0);
|
||||
readonly user = input<AuthUser | null>(null);
|
||||
readonly isAuthenticated = input<boolean>(false);
|
||||
readonly accountNavigationMenus = input<readonly Menu[]>([]);
|
||||
readonly cartClick = output<void>();
|
||||
readonly loginClick = output<void>();
|
||||
readonly logoutClick = output<void>();
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
|
||||
<div class="user-dropdown__divider"></div>
|
||||
|
||||
<a routerLink="/mi-cuenta/datos-personales" class="user-dropdown__item" role="menuitem" data-testid="user-dropdown-account">
|
||||
Mi cuenta
|
||||
</a>
|
||||
<a routerLink="/mi-cuenta/compras" class="user-dropdown__item" role="menuitem" data-testid="user-dropdown-purchases">
|
||||
Mis compras
|
||||
</a>
|
||||
@for (menu of accountNavigationMenus(); track menu.code) {
|
||||
<a
|
||||
[routerLink]="menu.route"
|
||||
class="user-dropdown__item"
|
||||
role="menuitem"
|
||||
[attr.data-testid]="'user-dropdown-' + menu.code"
|
||||
>
|
||||
{{ menuLabel(menu) }}
|
||||
</a>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="user-dropdown__item"
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import { Component, computed, input, output } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { AuthUser } from '../../../../services/auth/auth.interfaces';
|
||||
import { getAccountNavigationLabel } from '../../../../services/menu.utils';
|
||||
import { Menu } from '../../../../services/tenant.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-dropdown',
|
||||
standalone: true,
|
||||
imports: [RouterLink],
|
||||
templateUrl: './user-dropdown.component.html',
|
||||
styleUrl: './user-dropdown.component.scss'
|
||||
styleUrl: './user-dropdown.component.scss',
|
||||
})
|
||||
export class UserDropdownComponent {
|
||||
readonly user = input<AuthUser | null>(null);
|
||||
readonly accountNavigationMenus = input<readonly Menu[]>([]);
|
||||
readonly logoutClick = output<void>();
|
||||
|
||||
protected readonly displayName = computed(() => this.user()?.nombre_apellido || 'Mi cuenta');
|
||||
|
||||
protected menuLabel(menu: Menu): string {
|
||||
return getAccountNavigationLabel(menu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[logoUrl]="tenant()?.header_logo ?? null"
|
||||
[user]="user()"
|
||||
[isAuthenticated]="isAuthenticated()"
|
||||
[accountNavigationMenus]="accountNavigationMenus()"
|
||||
(cartClick)="isCartOpen.set(!isCartOpen())"
|
||||
(loginClick)="onLoginClick()"
|
||||
(logoutClick)="onLogoutClick()"
|
||||
@@ -21,8 +22,12 @@
|
||||
[backgroundColor]="'#ffffff'"
|
||||
(closed)="isCartOpen.set(false)"
|
||||
>
|
||||
<app-button variant="secondary" class="flex-grow-1" (click)="isCartOpen.set(false)">Seguir comprando</app-button>
|
||||
<app-button variant="primary" class="flex-grow-1" (click)="onCheckoutClick()">Comprar</app-button>
|
||||
<app-button variant="secondary" class="flex-grow-1" (click)="isCartOpen.set(false)"
|
||||
>Seguir comprando</app-button
|
||||
>
|
||||
<app-button variant="primary" class="flex-grow-1" (click)="onCheckoutClick()"
|
||||
>Comprar</app-button
|
||||
>
|
||||
</app-cart>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -27,6 +27,39 @@ const tenant: Tenant = {
|
||||
header_logo: 'https://example.com/header.png',
|
||||
footer_logo: 'https://example.com/footer.png',
|
||||
menues: [
|
||||
{
|
||||
id: 4,
|
||||
code: 'account',
|
||||
parent_menu_code: null,
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta',
|
||||
submenues: [
|
||||
{
|
||||
id: 5,
|
||||
code: 'profile',
|
||||
parent_menu_code: 'account',
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta/datos-personales',
|
||||
submenues: [],
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
code: 'purchases',
|
||||
parent_menu_code: 'account',
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta/compras',
|
||||
submenues: [],
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
code: 'tickets',
|
||||
parent_menu_code: 'account',
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta/tickets',
|
||||
submenues: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
code: 'help',
|
||||
@@ -97,17 +130,19 @@ describe('StoreLayoutComponent', () => {
|
||||
tenant: tenantState.asReadonly(),
|
||||
status: signal<'ready'>('ready').asReadonly(),
|
||||
getTenant: () => tenantState(),
|
||||
bootstrap: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
bootstrap: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CartService,
|
||||
useValue: {
|
||||
cart: signal(null).asReadonly(),
|
||||
loadCart: () => of({ id: 1, items: [], subtotal: '0' }),
|
||||
updateItemQuantity: vi.fn().mockReturnValue(of({ data: { id: 1, items: [], subtotal: '0' } })),
|
||||
removeItem: vi.fn().mockReturnValue(of({ data: { id: 1, items: [], subtotal: '0' } }))
|
||||
}
|
||||
updateItemQuantity: vi
|
||||
.fn()
|
||||
.mockReturnValue(of({ data: { id: 1, items: [], subtotal: '0' } })),
|
||||
removeItem: vi.fn().mockReturnValue(of({ data: { id: 1, items: [], subtotal: '0' } })),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ToastService,
|
||||
@@ -116,18 +151,18 @@ describe('StoreLayoutComponent', () => {
|
||||
info: vi.fn(),
|
||||
danger: vi.fn(),
|
||||
success: vi.fn(),
|
||||
dismiss: vi.fn()
|
||||
}
|
||||
dismiss: vi.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AuthService,
|
||||
useValue: {
|
||||
user: authUserState,
|
||||
isAuthenticated: isAuthenticatedState,
|
||||
logout: vi.fn().mockReturnValue(of(void 0))
|
||||
}
|
||||
}
|
||||
]
|
||||
logout: vi.fn().mockReturnValue(of(void 0)),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
@@ -152,10 +187,14 @@ describe('StoreLayoutComponent', () => {
|
||||
expect(compiled.querySelector('[data-testid="store-header-brand-slot"]')).not.toBeNull();
|
||||
expect(compiled.querySelector('[data-testid="store-footer-brand-slot"]')).not.toBeNull();
|
||||
expect(
|
||||
compiled.querySelector(`app-store-header img.store-layout__brand-logo[src="${tenant.header_logo}"]`)
|
||||
compiled.querySelector(
|
||||
`app-store-header img.store-layout__brand-logo[src="${tenant.header_logo}"]`,
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
compiled.querySelector(`app-store-footer img.store-layout__brand-logo[src="${tenant.footer_logo}"]`)
|
||||
compiled.querySelector(
|
||||
`app-store-footer img.store-layout__brand-logo[src="${tenant.footer_logo}"]`,
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(compiled.querySelector('.fa-cart-shopping')).not.toBeNull();
|
||||
expect(compiled.querySelector('.fa-circle-user')).not.toBeNull();
|
||||
@@ -176,7 +215,9 @@ describe('StoreLayoutComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
const userButton = compiled.querySelector('app-icon-button[variant="user"] button') as HTMLButtonElement;
|
||||
const userButton = compiled.querySelector(
|
||||
'app-icon-button[variant="user"] button',
|
||||
) as HTMLButtonElement;
|
||||
expect(userButton).not.toBeNull();
|
||||
userButton.click();
|
||||
fixture.detectChanges();
|
||||
@@ -194,20 +235,25 @@ describe('StoreLayoutComponent', () => {
|
||||
(authService.user as any).set({
|
||||
id: 1,
|
||||
nombre_apellido: 'Ada Lovelace',
|
||||
email: 'ada@example.com'
|
||||
email: 'ada@example.com',
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(StoreLayoutComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
const userButton = compiled.querySelector('app-icon-button[variant="user"] button') as HTMLButtonElement;
|
||||
const userButton = compiled.querySelector(
|
||||
'app-icon-button[variant="user"] button',
|
||||
) as HTMLButtonElement;
|
||||
expect(userButton).not.toBeNull();
|
||||
userButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(compiled.querySelector('app-user-dropdown')).not.toBeNull();
|
||||
expect(compiled.textContent).toContain('Ada Lovelace');
|
||||
expect(compiled.querySelector('[data-testid="user-dropdown-profile"]')).not.toBeNull();
|
||||
expect(compiled.querySelector('[data-testid="user-dropdown-purchases"]')).not.toBeNull();
|
||||
expect(compiled.querySelector('[data-testid="user-dropdown-tickets"]')).toBeNull();
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -220,18 +266,22 @@ describe('StoreLayoutComponent', () => {
|
||||
(authService.user as any).set({
|
||||
id: 1,
|
||||
nombre_apellido: 'Ada Lovelace',
|
||||
email: 'ada@example.com'
|
||||
email: 'ada@example.com',
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(StoreLayoutComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
const userButton = compiled.querySelector('app-icon-button[variant="user"] button') as HTMLButtonElement;
|
||||
const userButton = compiled.querySelector(
|
||||
'app-icon-button[variant="user"] button',
|
||||
) as HTMLButtonElement;
|
||||
userButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const logoutButton = compiled.querySelector('[data-testid="user-dropdown-logout"]') as HTMLButtonElement;
|
||||
const logoutButton = compiled.querySelector(
|
||||
'[data-testid="user-dropdown-logout"]',
|
||||
) as HTMLButtonElement;
|
||||
expect(logoutButton).not.toBeNull();
|
||||
logoutButton.click();
|
||||
fixture.detectChanges();
|
||||
@@ -252,14 +302,16 @@ describe('StoreLayoutComponent', () => {
|
||||
const footerLinks = Array.from(
|
||||
compiled.querySelectorAll<HTMLAnchorElement>('app-store-footer a.store-layout__footer-link'),
|
||||
);
|
||||
const accountLink = footerLinks.find((link) => link.textContent?.trim() === 'Mi cuenta');
|
||||
const profileLink = footerLinks.find((link) => link.textContent?.trim() === 'Mi cuenta');
|
||||
const purchasesLink = footerLinks.find((link) => link.textContent?.trim() === 'Mis compras');
|
||||
const ticketsLink = footerLinks.find((link) => link.textContent?.trim() === 'Mis tickets');
|
||||
const logoutButton = Array.from(
|
||||
compiled.querySelectorAll<HTMLButtonElement>('app-store-footer button'),
|
||||
).find((button) => button.textContent?.trim() === 'Cerrar sesion');
|
||||
|
||||
expect(accountLink?.getAttribute('href')).toBe('/mi-cuenta/datos-personales');
|
||||
expect(profileLink?.getAttribute('href')).toBe('/mi-cuenta/datos-personales');
|
||||
expect(purchasesLink?.getAttribute('href')).toBe('/mi-cuenta/compras');
|
||||
expect(ticketsLink).toBeUndefined();
|
||||
expect(logoutButton).toBeDefined();
|
||||
|
||||
logoutButton!.click();
|
||||
@@ -275,11 +327,11 @@ describe('StoreLayoutComponent', () => {
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
const footerLinks = Array.from(
|
||||
compiled.querySelectorAll<HTMLAnchorElement>(
|
||||
'app-store-footer a.store-layout__footer-link',
|
||||
),
|
||||
compiled.querySelectorAll<HTMLAnchorElement>('app-store-footer a.store-layout__footer-link'),
|
||||
);
|
||||
const helpLinks = footerLinks.filter((link) =>
|
||||
link.getAttribute('href')?.startsWith('/ayuda/'),
|
||||
);
|
||||
const helpLinks = footerLinks.filter((link) => link.getAttribute('href')?.startsWith('/ayuda/'));
|
||||
|
||||
expect(helpLinks.map((link) => link.textContent?.trim())).toEqual([
|
||||
'Contacto',
|
||||
@@ -303,8 +355,9 @@ describe('StoreLayoutComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
const buyButton = Array.from(compiled.querySelectorAll('app-button button'))
|
||||
.find((button) => button.textContent?.trim() === 'Comprar') as HTMLButtonElement | undefined;
|
||||
const buyButton = Array.from(compiled.querySelectorAll('app-button button')).find(
|
||||
(button) => button.textContent?.trim() === 'Comprar',
|
||||
) as HTMLButtonElement | undefined;
|
||||
|
||||
expect(buyButton).toBeDefined();
|
||||
buyButton!.click();
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CartComponent, CartItemMock } from '../../../shared/components/cart/car
|
||||
import { ButtonComponent } from '../../../shared/components/button/button.component';
|
||||
import { CartItem } from '../../services/cart/cart.interface';
|
||||
import { AuthService } from '../../services/auth/auth.service';
|
||||
import { findMenu, getMenuLabel } from '../../services/menu.utils';
|
||||
import { findMenu, getAccountNavigationLabel, getMenuLabel } from '../../services/menu.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-store-layout',
|
||||
@@ -79,6 +79,11 @@ export class StoreLayoutComponent implements OnInit {
|
||||
protected readonly tenant = this.tenantService.tenant;
|
||||
protected readonly user = this.authService.user;
|
||||
protected readonly isAuthenticated = this.authService.isAuthenticated;
|
||||
protected readonly accountNavigationMenus = computed(() => {
|
||||
const accountSubmenues = findMenu(this.tenant()?.menues ?? [], 'account')?.submenues ?? [];
|
||||
|
||||
return accountSubmenues.filter((menu) => ['profile', 'purchases'].includes(menu.code));
|
||||
});
|
||||
|
||||
protected readonly cartQuantity = computed(() => {
|
||||
const cart = this.cartService.cart();
|
||||
@@ -109,15 +114,16 @@ export class StoreLayoutComponent implements OnInit {
|
||||
}
|
||||
|
||||
protected readonly footerSections = computed<StoreFooterSection[]>(() => {
|
||||
const helpSubmenues =
|
||||
findMenu(this.tenant()?.menues ?? [], 'help')?.submenues ?? [];
|
||||
const helpSubmenues = findMenu(this.tenant()?.menues ?? [], 'help')?.submenues ?? [];
|
||||
|
||||
return [
|
||||
{
|
||||
heading: 'Cuenta',
|
||||
links: [
|
||||
{ label: 'Mi cuenta', routerLink: '/mi-cuenta/datos-personales' },
|
||||
{ label: 'Mis compras', routerLink: '/mi-cuenta/compras' },
|
||||
...this.accountNavigationMenus().map((menu) => ({
|
||||
label: getAccountNavigationLabel(menu),
|
||||
routerLink: menu.route,
|
||||
})),
|
||||
{ label: 'Cerrar sesion', action: 'logout' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Menu } from './tenant.interface';
|
||||
|
||||
const MENU_LABELS: Record<string, string> = {
|
||||
account: 'Mi cuenta',
|
||||
profile: 'Datos personales',
|
||||
purchases: 'Mis compras',
|
||||
tickets: 'Mis tickets',
|
||||
'help.faq': 'Preguntas frecuentes',
|
||||
'help.contact': 'Contacto',
|
||||
'help.payment-methods': 'Medios de pago',
|
||||
@@ -35,3 +39,7 @@ export function getMenuLabel(menu: Menu): string {
|
||||
|
||||
return label.charAt(0).toUpperCase() + label.slice(1);
|
||||
}
|
||||
|
||||
export function getAccountNavigationLabel(menu: Menu): string {
|
||||
return menu.code === 'profile' ? 'Mi cuenta' : getMenuLabel(menu);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="sidebar-container">
|
||||
<h2 class="sidebar-title">MI CUENTA</h2>
|
||||
<div class="divider"></div>
|
||||
<nav class="sidebar-nav">
|
||||
<a routerLink="/mi-cuenta/datos-personales" routerLinkActive="active" class="nav-link"
|
||||
>Datos Personales</a
|
||||
>
|
||||
<a routerLink="/mi-cuenta/compras" routerLinkActive="active" class="nav-link">Mis compras</a>
|
||||
<a routerLink="/mi-cuenta/tickets" routerLinkActive="active" class="nav-link">Mis tickets</a>
|
||||
<nav class="sidebar-nav" aria-label="Secciones de mi cuenta">
|
||||
@for (menu of submenues(); track menu.code) {
|
||||
<a [routerLink]="menu.route" routerLinkActive="active" class="nav-link">
|
||||
{{ menuLabel(menu) }}
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { Tenant } from '../../../../../../core/services/tenant.interface';
|
||||
import { TenantService } from '../../../../../../core/services/tenant.service';
|
||||
import { AccountSidebar } from './account-sidebar';
|
||||
|
||||
describe('AccountSidebar', () => {
|
||||
it('renders only the account submenus configured for the tenant', async () => {
|
||||
const tenant = signal<Tenant>({
|
||||
id: 1,
|
||||
codigo: 'test',
|
||||
nombre: 'Test',
|
||||
dominio: 'localhost',
|
||||
primary_color: '#000000',
|
||||
secondary_color: '#000000',
|
||||
danger_color: '#000000',
|
||||
success_color: '#000000',
|
||||
header_bg_color: '#ffffff',
|
||||
footer_bg_color: '#ffffff',
|
||||
header_logo: '',
|
||||
footer_logo: '',
|
||||
menues: [
|
||||
{
|
||||
id: 1,
|
||||
code: 'account',
|
||||
parent_menu_code: null,
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta',
|
||||
submenues: [
|
||||
{
|
||||
id: 2,
|
||||
code: 'profile',
|
||||
parent_menu_code: 'account',
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta/datos-personales',
|
||||
submenues: [],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
code: 'purchases',
|
||||
parent_menu_code: 'account',
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta/compras',
|
||||
submenues: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AccountSidebar],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: TenantService,
|
||||
useValue: { tenant: tenant.asReadonly() },
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(AccountSidebar);
|
||||
fixture.detectChanges();
|
||||
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
const links = Array.from(element.querySelectorAll<HTMLAnchorElement>('.nav-link'));
|
||||
|
||||
expect(links.map((link) => link.textContent?.trim())).toEqual([
|
||||
'Datos personales',
|
||||
'Mis compras',
|
||||
]);
|
||||
expect(links.map((link) => link.getAttribute('href'))).toEqual([
|
||||
'/mi-cuenta/datos-personales',
|
||||
'/mi-cuenta/compras',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router';
|
||||
|
||||
import { findMenu, getMenuLabel } from '../../../../../../core/services/menu.utils';
|
||||
import { Menu } from '../../../../../../core/services/tenant.interface';
|
||||
import { TenantService } from '../../../../../../core/services/tenant.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-account-sidebar',
|
||||
standalone: true,
|
||||
@@ -8,4 +12,14 @@ import { RouterLink, RouterLinkActive } from '@angular/router';
|
||||
templateUrl: './account-sidebar.html',
|
||||
styleUrl: './account-sidebar.scss',
|
||||
})
|
||||
export class AccountSidebar {}
|
||||
export class AccountSidebar {
|
||||
private readonly tenantService = inject(TenantService);
|
||||
|
||||
protected readonly submenues = computed(
|
||||
() => findMenu(this.tenantService.tenant()?.menues ?? [], 'account')?.submenues ?? [],
|
||||
);
|
||||
|
||||
protected menuLabel(menu: Menu): string {
|
||||
return getMenuLabel(menu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +89,7 @@ export const routes: Routes = [
|
||||
path: 'preguntas-frecuentes',
|
||||
canActivate: [hasMenuGuard('help.faq')],
|
||||
loadComponent: () =>
|
||||
import('./pages/help-page/pages/faq-page/faq-page').then(
|
||||
(m) => m.FaqPage,
|
||||
),
|
||||
import('./pages/help-page/pages/faq-page/faq-page').then((m) => m.FaqPage),
|
||||
},
|
||||
{
|
||||
path: 'contacto',
|
||||
@@ -117,11 +115,9 @@ export const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'mi-cuenta',
|
||||
canActivate: [authGuard],
|
||||
canActivate: [authGuard, hasMenuGuard('account')],
|
||||
loadComponent: () =>
|
||||
import('./pages/account-page/sidebar-layout/sidebar-layout').then(
|
||||
(m) => m.SidebarLayout,
|
||||
),
|
||||
import('./pages/account-page/sidebar-layout/sidebar-layout').then((m) => m.SidebarLayout),
|
||||
children: [
|
||||
{
|
||||
path: 'datos-personales',
|
||||
|
||||
Reference in New Issue
Block a user