feat(account-page): implement sidebar layout with account sidebar and routing
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<footer class="store-layout__footer mt-auto text-light">
|
<footer class="store-layout__footer mt-auto text-light">
|
||||||
<div class="container-xl px-3 px-md-4 py-4 py-md-5">
|
<div class="container-xl px-3 px-md-4 py-4 py-md-5">
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<div class="col-12 col-md-6 col-xl-5 d-grid gap-3 align-content-start">
|
<div class="col-12">
|
||||||
<div
|
<div
|
||||||
class="store-layout__brand-slot d-inline-flex align-items-center gap-3"
|
class="store-layout__brand-slot d-inline-flex align-items-center gap-3"
|
||||||
data-testid="store-footer-brand-slot"
|
data-testid="store-footer-brand-slot"
|
||||||
@@ -15,13 +15,15 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 col-xl-5 d-grid gap-3 align-content-start">
|
||||||
<div class="d-grid gap-2">
|
<div class="d-grid gap-2">
|
||||||
<div class="d-flex align-items-center gap-2 text-light-emphasis">
|
<div class="d-flex align-items-center gap-2 ">
|
||||||
<i class="fa-solid fa-location-dot" aria-hidden="true"></i>
|
<i class="fa-solid fa-location-dot" aria-hidden="true"></i>
|
||||||
<span>Av. San Lorenzo 1542, Rosario</span>
|
<span>Av. San Lorenzo 1542, Rosario</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2 text-light-emphasis">
|
<div class="d-flex align-items-center gap-2 ">
|
||||||
<i class="fa-solid fa-phone" aria-hidden="true"></i>
|
<i class="fa-solid fa-phone" aria-hidden="true"></i>
|
||||||
<span>54 9 (0341) 6658247</span>
|
<span>54 9 (0341) 6658247</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,18 +33,33 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@for (section of footerSections; track section.heading) {
|
@for (section of footerSections; track section.heading) {
|
||||||
<div class="col-12 col-md-6 col-xl-2 d-grid gap-3 align-content-start">
|
<div class="col-12 col-md-6 col-xl-2 d-grid align-content-start">
|
||||||
<h2 class="store-layout__footer-heading">{{ section.heading }}</h2>
|
|
||||||
<div class="d-grid gap-2">
|
<div class="d-grid gap-2">
|
||||||
@for (link of section.links; track link) {
|
@for (link of section.links; track link.label) {
|
||||||
<span class="store-layout__muted-text">{{ link }}</span>
|
@if (link.routerLink) {
|
||||||
|
<a
|
||||||
|
class="store-layout__footer-link"
|
||||||
|
[routerLink]="link.routerLink"
|
||||||
|
>
|
||||||
|
{{ link.label }}
|
||||||
|
</a>
|
||||||
|
} @else if (link.action === 'logout') {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="store-layout__footer-link store-layout__footer-action"
|
||||||
|
(click)="logoutClick.emit()"
|
||||||
|
>
|
||||||
|
{{ link.label }}
|
||||||
|
</button>
|
||||||
|
} @else {
|
||||||
|
<span class="store-layout__muted-text">{{ link.label }}</span>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="col-12 col-md-6 col-xl-3 d-grid gap-3 align-content-start">
|
<div class="col-12 col-md-6 col-xl-3 d-grid align-content-start">
|
||||||
<h2 class="store-layout__footer-heading">Seguinos:</h2>
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
@for (social of socialMedia; track social.code) {
|
@for (social of socialMedia; track social.code) {
|
||||||
<app-social-media
|
<app-social-media
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.store-layout__brand-slot {
|
.store-layout__brand-slot {
|
||||||
|
width: min(100%, 12rem);
|
||||||
min-width: 10rem;
|
min-width: 10rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 2.75rem;
|
max-height: 2.75rem;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
object-position: left center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-layout__brand-logo-skeleton {
|
.store-layout__brand-logo-skeleton {
|
||||||
@@ -55,17 +57,30 @@
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-layout__footer-heading {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-layout__muted-text {
|
.store-layout__muted-text {
|
||||||
color: rgba(255, 255, 255, 0.72);
|
color: rgba(255, 255, 255, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.store-layout__footer-link {
|
||||||
|
width: fit-content;
|
||||||
|
color: rgba(255, 255, 255, 0.72);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-layout__footer-action {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
font: inherit;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes store-footer-logo-skeleton {
|
@keyframes store-footer-logo-skeleton {
|
||||||
from {
|
from {
|
||||||
background-position: 200% 0;
|
background-position: 200% 0;
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input, output } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
import { SocialMedia } from '../../../services/tenant.interface';
|
import { SocialMedia } from '../../../services/tenant.interface';
|
||||||
import { SocialMediaComponent } from '../../../../shared/components/social-media/social-media.component';
|
import { SocialMediaComponent } from '../../../../shared/components/social-media/social-media.component';
|
||||||
|
|
||||||
|
export type StoreFooterLink = {
|
||||||
|
label: string;
|
||||||
|
routerLink?: string;
|
||||||
|
action?: 'logout';
|
||||||
|
};
|
||||||
|
|
||||||
export type StoreFooterSection = {
|
export type StoreFooterSection = {
|
||||||
heading: string;
|
heading: string;
|
||||||
links: string[];
|
links: StoreFooterLink[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-store-footer',
|
selector: 'app-store-footer',
|
||||||
imports: [SocialMediaComponent],
|
imports: [RouterLink, SocialMediaComponent],
|
||||||
templateUrl: './store-footer.component.html',
|
templateUrl: './store-footer.component.html',
|
||||||
styleUrl: './store-footer.component.scss',
|
styleUrl: './store-footer.component.scss',
|
||||||
})
|
})
|
||||||
@@ -18,4 +25,5 @@ export class StoreFooterComponent {
|
|||||||
@Input({ required: true }) footerSections: StoreFooterSection[] = [];
|
@Input({ required: true }) footerSections: StoreFooterSection[] = [];
|
||||||
@Input({ required: true }) socialMedia: SocialMedia[] = [];
|
@Input({ required: true }) socialMedia: SocialMedia[] = [];
|
||||||
@Input() logoUrl: string | null = null;
|
@Input() logoUrl: string | null = null;
|
||||||
|
readonly logoutClick = output<void>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,5 +40,6 @@
|
|||||||
[footerSections]="footerSections"
|
[footerSections]="footerSections"
|
||||||
[socialMedia]="tenant()?.social_media ?? []"
|
[socialMedia]="tenant()?.social_media ?? []"
|
||||||
[logoUrl]="tenant()?.footer_logo ?? null"
|
[logoUrl]="tenant()?.footer_logo ?? null"
|
||||||
|
(logoutClick)="onLogoutClick()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -213,6 +213,35 @@ describe('StoreLayoutComponent', () => {
|
|||||||
expect(router.navigate).toHaveBeenCalledWith(['/login']);
|
expect(router.navigate).toHaveBeenCalledWith(['/login']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('provides account actions from the footer', () => {
|
||||||
|
const authService = TestBed.inject(AuthService);
|
||||||
|
const router = TestBed.inject(Router);
|
||||||
|
vi.spyOn(router, 'navigate');
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(StoreLayoutComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const compiled = fixture.nativeElement as HTMLElement;
|
||||||
|
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 purchasesLink = footerLinks.find((link) => link.textContent?.trim() === 'Mis compras');
|
||||||
|
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(purchasesLink?.getAttribute('href')).toBe('/mi-cuenta/compras');
|
||||||
|
expect(logoutButton).toBeDefined();
|
||||||
|
|
||||||
|
logoutButton!.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(authService.logout).toHaveBeenCalled();
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith(['/login']);
|
||||||
|
});
|
||||||
|
|
||||||
it('redirects to /checkout when cart buy button is clicked', () => {
|
it('redirects to /checkout when cart buy button is clicked', () => {
|
||||||
const router = TestBed.inject(Router);
|
const router = TestBed.inject(Router);
|
||||||
vi.spyOn(router, 'navigate');
|
vi.spyOn(router, 'navigate');
|
||||||
|
|||||||
@@ -110,11 +110,19 @@ export class StoreLayoutComponent implements OnInit {
|
|||||||
protected readonly footerSections: StoreFooterSection[] = [
|
protected readonly footerSections: StoreFooterSection[] = [
|
||||||
{
|
{
|
||||||
heading: 'Cuenta',
|
heading: 'Cuenta',
|
||||||
links: ['Mi cuenta', 'Mis compras', 'Cerrar sesion'],
|
links: [
|
||||||
|
{ label: 'Mi cuenta', routerLink: '/mi-cuenta/datos-personales' },
|
||||||
|
{ label: 'Mis compras', routerLink: '/mi-cuenta/compras' },
|
||||||
|
{ label: 'Cerrar sesion', action: 'logout' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: 'Ayuda',
|
heading: 'Ayuda',
|
||||||
links: ['Contacto', 'Ayuda', 'Preguntas frecuentes'],
|
links: [
|
||||||
|
{ label: 'Contacto' },
|
||||||
|
{ label: 'Ayuda' },
|
||||||
|
{ label: 'Preguntas frecuentes' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="account-layout">
|
<div class="sidebar-layout">
|
||||||
<app-account-sidebar class="account-sidebar"></app-account-sidebar>
|
<app-account-sidebar class="account-sidebar"></app-account-sidebar>
|
||||||
<div class="account-content">
|
<div class="account-content">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
.account-layout {
|
.sidebar-layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
gap: 100px;
|
gap: 100px;
|
||||||
@@ -3,10 +3,10 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
import { AccountSidebar } from '../components/account-sidebar/account-sidebar';
|
import { AccountSidebar } from '../components/account-sidebar/account-sidebar';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account-layout',
|
selector: 'app-sidebar-layout',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet, AccountSidebar],
|
imports: [RouterOutlet, AccountSidebar],
|
||||||
templateUrl: './account-layout.html',
|
templateUrl: './sidebar-layout.html',
|
||||||
styleUrl: './account-layout.scss',
|
styleUrl: './sidebar-layout.scss',
|
||||||
})
|
})
|
||||||
export class AccountLayout {}
|
export class SidebarLayout {}
|
||||||
@@ -81,7 +81,9 @@ export const routes: Routes = [
|
|||||||
path: 'mi-cuenta',
|
path: 'mi-cuenta',
|
||||||
canActivate: [authGuard],
|
canActivate: [authGuard],
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./pages/account-page/account-layout/account-layout').then((m) => m.AccountLayout),
|
import('./pages/account-page/sidebar-layout/sidebar-layout').then(
|
||||||
|
(m) => m.SidebarLayout,
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'datos-personales',
|
path: 'datos-personales',
|
||||||
|
|||||||
Reference in New Issue
Block a user