feat: add header component with navigation and cart functionality
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
<app-header></app-header>
|
||||||
|
|
||||||
<main class="app-content">
|
<main class="app-content">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
+2
-1
@@ -1,9 +1,10 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
import { Component, signal } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import {Header} from './layout/header/header';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet, Header],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.scss'
|
styleUrl: './app.scss'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export class CartService {
|
|||||||
const response = await firstValueFrom(
|
const response = await firstValueFrom(
|
||||||
this.http.post<AddCartResponse>(`${API_BASE_URL}/cart`, request)
|
this.http.post<AddCartResponse>(`${API_BASE_URL}/cart`, request)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//Note: can return always 1 because server has not SameSite=None. This will be taken as a third cookie, and the browser can ignore it.
|
||||||
this.setCount(response.count);
|
this.setCount(response.count);
|
||||||
return response.count;
|
return response.count;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<mat-toolbar color="primary" class="app-header">
|
||||||
|
<a routerLink="/" class="app-header__brand">
|
||||||
|
<mat-icon>smartphone</mat-icon>
|
||||||
|
<span>Mobile Store</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<nav class="app-header__breadcrumb" aria-label="breadcrumb">
|
||||||
|
<a routerLink="/">Home</a>
|
||||||
|
@if (isProductDetail()) {
|
||||||
|
<span> / Product Detail</span>
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<span class="app-header__spacer"></span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
[matBadge]="cartCount()"
|
||||||
|
[matBadgeHidden]="cartCount() === 0"
|
||||||
|
matBadgeColor="accent"
|
||||||
|
aria-label="Cart"
|
||||||
|
>
|
||||||
|
<mat-icon>shopping_cart</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-toolbar>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
.app-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
|
||||||
|
&__brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__breadcrumb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Header } from './header';
|
||||||
|
|
||||||
|
describe('Header', () => {
|
||||||
|
let component: Header;
|
||||||
|
let fixture: ComponentFixture<Header>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Header]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Header);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
import { NavigationEnd, Router, RouterLink } from '@angular/router';
|
||||||
|
import { filter, map } from 'rxjs';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatBadgeModule } from '@angular/material/badge';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
|
||||||
|
import { CartService } from '../../core/services/cart.service';
|
||||||
|
|
||||||
|
// App Header
|
||||||
|
@Component({
|
||||||
|
selector: 'app-header',
|
||||||
|
imports: [RouterLink, MatToolbarModule, MatIconModule, MatBadgeModule, MatButtonModule],
|
||||||
|
templateUrl: './header.html',
|
||||||
|
styleUrl: './header.scss'
|
||||||
|
})
|
||||||
|
export class Header {
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
private readonly cart = inject(CartService);
|
||||||
|
|
||||||
|
// Products count in the cart, for the badge in the header.
|
||||||
|
readonly cartCount = this.cart.count;
|
||||||
|
|
||||||
|
readonly isProductDetail = toSignal(
|
||||||
|
this.router.events.pipe(
|
||||||
|
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
|
||||||
|
map((event) => event.urlAfterRedirects.startsWith('/product/'))
|
||||||
|
),
|
||||||
|
{ initialValue: this.router.url.startsWith('/product/') }
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user