Added Shopping Cart
This commit is contained in:
parent
bcbad19601
commit
495ac6a8be
5
client/package-lock.json
generated
5
client/package-lock.json
generated
@ -24,6 +24,7 @@
|
|||||||
"ngx-toastr": "^14.3.0",
|
"ngx-toastr": "^14.3.0",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
"xng-breadcrumb": "^7.2.0",
|
"xng-breadcrumb": "^7.2.0",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.11.4"
|
||||||
},
|
},
|
||||||
@ -11213,7 +11214,6 @@
|
|||||||
"version": "8.3.2",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
@ -19945,8 +19945,7 @@
|
|||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "8.3.2",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"validate-npm-package-name": {
|
"validate-npm-package-name": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"ngx-toastr": "^14.3.0",
|
"ngx-toastr": "^14.3.0",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
"xng-breadcrumb": "^7.2.0",
|
"xng-breadcrumb": "^7.2.0",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.11.4"
|
||||||
},
|
},
|
||||||
|
@ -11,6 +11,7 @@ const routes: Routes = [
|
|||||||
{path: 'server-error', component: ServerErrorComponent, data: {breadcrumb: 'Server Error'}},
|
{path: 'server-error', component: ServerErrorComponent, data: {breadcrumb: 'Server Error'}},
|
||||||
{path: 'not-found', component: NotFoundComponent, data: {breadcrumb: 'Not Found'}},
|
{path: 'not-found', component: NotFoundComponent, data: {breadcrumb: 'Not Found'}},
|
||||||
{path: 'shop', loadChildren: ()=> import('./shop/shop.module').then(mod => mod.ShopModule), data: {breadcrumb: 'Shop'}},
|
{path: 'shop', loadChildren: ()=> import('./shop/shop.module').then(mod => mod.ShopModule), data: {breadcrumb: 'Shop'}},
|
||||||
|
{path: 'basket', loadChildren: ()=> import('./basket/basket.module').then(mod => mod.BasketModule), data: {breadcrumb: 'Shopping Cart'}},
|
||||||
{path: '**', redirectTo: 'not-found', pathMatch: 'full'}
|
{path: '**', redirectTo: 'not-found', pathMatch: 'full'}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<ngx-spinner>
|
<ngx-spinner type="ball-clip-rotate-multiple">
|
||||||
<h3>Loading...</h3>
|
<h3>Loading...</h3>
|
||||||
</ngx-spinner>
|
</ngx-spinner>
|
||||||
<app-nav-bar></app-nav-bar>
|
<app-nav-bar></app-nav-bar>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { BasketService } from './basket/basket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -6,9 +7,18 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
styleUrls: ['./app.component.scss']
|
styleUrls: ['./app.component.scss']
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
title = 'E-Commerce';
|
title = 'SkiNet';
|
||||||
|
|
||||||
constructor() {}
|
constructor(private basketService: BasketService) {}
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {
|
||||||
|
const basketId = localStorage.getItem('basket_id');
|
||||||
|
if(basketId){
|
||||||
|
this.basketService.getBasket(basketId).subscribe({
|
||||||
|
next: () => { console.log('initialized basket') },
|
||||||
|
error: (e: any) => { console.log(e) },
|
||||||
|
complete: () => { console.log('Complete') }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
16
client/src/app/basket/basket-routing.module.ts
Normal file
16
client/src/app/basket/basket-routing.module.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { BasketComponent } from './basket.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{path: '', component: BasketComponent}
|
||||||
|
]
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes)
|
||||||
|
],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class BasketRoutingModule { }
|
74
client/src/app/basket/basket.component.html
Normal file
74
client/src/app/basket/basket.component.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<div class="container mt-5">
|
||||||
|
<div *ngIf="(basket$ | async) === null">
|
||||||
|
<P>You're shopping cart is currently empty!</P>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="basket$ | async">
|
||||||
|
<div class="pb-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 py-3 mb-1">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover table-light">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">
|
||||||
|
<div class="p-2 px-3 text-uppercase">Product</div>
|
||||||
|
</th>
|
||||||
|
<th class="text-center" scope="col">
|
||||||
|
<div class="p-2 px-3 text-uppercase">Price</div>
|
||||||
|
</th>
|
||||||
|
<th class="text-center" scope="col">
|
||||||
|
<div class="p-2 px-3 text-uppercase">quantity</div>
|
||||||
|
</th>
|
||||||
|
<th class="text-center" scope="col">
|
||||||
|
<div class="p-2 px-3 text-uppercase">Total</div>
|
||||||
|
</th>
|
||||||
|
<th class="text-center" scope="col">
|
||||||
|
<div class="p-2 px-3 text-uppercase">Remove</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let item of (basket$ | async).items">
|
||||||
|
<th scope="row">
|
||||||
|
<div class="p-2">
|
||||||
|
<img class="img-fluid" style="max-height: 50px" src="{{item.pictureUrl}}" alt="{{item.productName}}">
|
||||||
|
<div class="ms-3 d-inline-block align-middle">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<a class="text-dark" routerLink="/shop/{{item.id}}">{{item.productName}}</a>
|
||||||
|
</h5>
|
||||||
|
<span class="text-muted font-weight-normal font-italic d-block">Type: {{item.type}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td class="align-middle text-center">{{item.price | currency}}</td>
|
||||||
|
<td class="align-middle text-center">
|
||||||
|
<div class="d-flex-align-items-center">
|
||||||
|
<i class="fa fa-minus-circle text-warning me-2" style="cursor: pointer;"></i>
|
||||||
|
<span class="font-weight-bold">{{item.quantity}}</span>
|
||||||
|
<i class="fa fa-plus-circle text-warning mx-2" style="cursor: pointer;"></i>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle text-center">{{item.price * item.quantity | currency}}</td>
|
||||||
|
<td class="align-middle text-center">
|
||||||
|
<a class="text-danger" style="cursor: pointer">
|
||||||
|
<i class="fa fa-trash" style="font-size: 2em"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 offset-6">
|
||||||
|
<app-order-totals></app-order-totals>
|
||||||
|
<a routerLink="/checkout" class="btn btn-outline-primary w-100">Checkout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
0
client/src/app/basket/basket.component.scss
Normal file
0
client/src/app/basket/basket.component.scss
Normal file
20
client/src/app/basket/basket.component.ts
Normal file
20
client/src/app/basket/basket.component.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { IBasket } from '../shared/models/baskset';
|
||||||
|
import { BasketService } from './basket.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-basket',
|
||||||
|
templateUrl: './basket.component.html',
|
||||||
|
styleUrls: ['./basket.component.scss']
|
||||||
|
})
|
||||||
|
export class BasketComponent implements OnInit {
|
||||||
|
basket$: Observable<IBasket>;
|
||||||
|
|
||||||
|
constructor(private basketService: BasketService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.basket$ = this.basketService.basket$;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
19
client/src/app/basket/basket.module.ts
Normal file
19
client/src/app/basket/basket.module.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { BasketComponent } from './basket.component';
|
||||||
|
import { BasketRoutingModule } from './basket-routing.module';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
BasketComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
BasketRoutingModule,
|
||||||
|
SharedModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class BasketModule { }
|
93
client/src/app/basket/basket.service.ts
Normal file
93
client/src/app/basket/basket.service.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, map } from 'rxjs';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
import { Basket, IBasket, IBasketItem, IBasketTotals } from '../shared/models/baskset';
|
||||||
|
import { IProduct } from '../shared/models/product';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class BasketService {
|
||||||
|
baseUrl = environment.apiUrl;
|
||||||
|
private basketSource = new BehaviorSubject<IBasket>(null);
|
||||||
|
basket$ = this.basketSource.asObservable();
|
||||||
|
private basketTotalSource = new BehaviorSubject<IBasketTotals>(null);
|
||||||
|
basketTotal$ = this.basketTotalSource.asObservable();
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
|
getBasket(id: string){
|
||||||
|
return this.http.get(this.baseUrl + 'basket?id=' + id).pipe(
|
||||||
|
map((basket: IBasket) => {
|
||||||
|
this.basketSource.next(basket);
|
||||||
|
this.calculateTotals();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBasket(basket: IBasket){
|
||||||
|
return this.http.post(this.baseUrl + 'basket', basket).subscribe({
|
||||||
|
next: (response: IBasket) => {
|
||||||
|
this.basketSource.next(response);
|
||||||
|
this.calculateTotals();
|
||||||
|
},
|
||||||
|
error: (e: any) => { console.log(e); },
|
||||||
|
complete: () => { console.log('complete'); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentBasketValue(){
|
||||||
|
return this.basketSource.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
addItemToBasket(item: IProduct, quantity = 1){
|
||||||
|
const itemToAdd: IBasketItem = this.mapProductItemToBasketItem(item, quantity);
|
||||||
|
const basket = this.getCurrentBasketValue() ?? this.createBasket();
|
||||||
|
basket.items = this.addOrUpdateItem(basket.items, itemToAdd, quantity);
|
||||||
|
this.setBasket(basket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateTotals(){
|
||||||
|
const basket = this.getCurrentBasketValue();
|
||||||
|
const shipping = 0;
|
||||||
|
const subtotal = basket.items.reduce((a, b) => (b.price * b.quantity) + a, 0);
|
||||||
|
const total = subtotal + shipping;
|
||||||
|
this.basketTotalSource.next({
|
||||||
|
shipping,
|
||||||
|
total,
|
||||||
|
subtotal
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private addOrUpdateItem(items: IBasketItem[], itemToAdd: IBasketItem, quantity: number): IBasketItem[] {
|
||||||
|
const index = items.findIndex(i => i.id === itemToAdd.id);
|
||||||
|
if(index === -1){
|
||||||
|
itemToAdd.quantity = quantity;
|
||||||
|
items.push(itemToAdd);
|
||||||
|
} else {
|
||||||
|
items[index].quantity += quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBasket(): IBasket {
|
||||||
|
const basket = new Basket();
|
||||||
|
localStorage.setItem('basket_id', basket.id);
|
||||||
|
return basket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapProductItemToBasketItem(item: IProduct, quantity: number): IBasketItem {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
productName: item.name,
|
||||||
|
price: item.price,
|
||||||
|
pictureUrl: item.pictureUrl,
|
||||||
|
quantity,
|
||||||
|
brand: item.productBrand,
|
||||||
|
type: item.productType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<div class="d-flex flex-column flex-md-row align-items-center justify-content-between p-3 px-md-4 mb-3 border-bottom border-dark fixed-top" style="background-color: #0d0d0e;">
|
<div class="d-flex flex-column flex-md-row align-items-center justify-content-between bg-light p-3 px-md-4 mb-3 border-bottom border-dark fixed-top">
|
||||||
<img class="logo" src="/assets/images/logo.png" style="max-height: 70px;" alt="logo" routerLink="/">
|
<img class="logo" src="/assets/images/logo.png" style="max-height: 70px;" alt="logo" routerLink="/">
|
||||||
<nav class="me-3 my-md-0 mr-md-3 text-uppercase" style="font-size: larger;">
|
<nav class="me-3 my-md-0 mr-md-3 text-uppercase" style="font-size: larger;">
|
||||||
<a class="me-3 py-2" [routerLink]="['/']" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a>
|
<a class="me-3 py-2" [routerLink]="['/']" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a>
|
||||||
@ -6,9 +6,9 @@
|
|||||||
<a class="me-3 py-2" routerLink="/test-error" routerLinkActive="active">Errors</a>
|
<a class="me-3 py-2" routerLink="/test-error" routerLinkActive="active">Errors</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<a class="position-relative">
|
<a routerLink="/basket" class="position-relative">
|
||||||
<i class="fa fa-shopping-cart fa-2x me-3 text-dark"></i>
|
<i class="fa fa-shopping-cart fa-2x me-3 text-dark"></i>
|
||||||
<div class="cart-no">5</div>
|
<div *ngIf="(basket$ | async) as basket" class="cart-no">{{basket.items.length}}</div>
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-outline-secondary me-3" href="#">Login</a>
|
<a class="btn btn-outline-secondary me-3" href="#">Login</a>
|
||||||
<a class="btn btn-outline-secondary me-3" href="#">Sign up</a>
|
<a class="btn btn-outline-secondary me-3" href="#">Sign up</a>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #343a40;
|
color: white;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: orange;
|
color: orange;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { BasketService } from 'src/app/basket/basket.service';
|
||||||
|
import { IBasket } from 'src/app/shared/models/baskset';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-nav-bar',
|
selector: 'app-nav-bar',
|
||||||
@ -6,10 +9,12 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
styleUrls: ['./nav-bar.component.scss']
|
styleUrls: ['./nav-bar.component.scss']
|
||||||
})
|
})
|
||||||
export class NavBarComponent implements OnInit {
|
export class NavBarComponent implements OnInit {
|
||||||
|
basket$ : Observable<IBasket>
|
||||||
|
|
||||||
constructor() { }
|
constructor(private basketService: BasketService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.basket$ = this.basketService.basket$;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<ng-container *ngIf="(breadcrumb$ | async) as breadcrumbs">
|
<ng-container *ngIf="(breadcrumb$ | async) as breadcrumbs">
|
||||||
<section *ngIf="breadcrumbs.length > 0 && breadcrumbs[breadcrumbs.length-1].label !== 'Home'" class="py-5" style="margin-top: 105px;">
|
<section *ngIf="breadcrumbs.length > 0 && breadcrumbs[breadcrumbs.length-1].label !== 'Home'" class="py-5" style="background-color: rgb(43, 43, 43); margin-top: 105px;">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row d-flex align-item-center">
|
<div class="row d-flex align-item-center">
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
<div class="bg-light px-4 py-3 text-uppercase font-weight-bold">
|
||||||
|
Order Summary
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<p class="font-italic mb-4">Shipping costs will be added during checkout</p>
|
||||||
|
<ul class="list-unstyled mb-4" *ngIf="(basketTotal$ | async) as totals">
|
||||||
|
<li class="d-flex justify-content-between py-3 border-bottom">
|
||||||
|
<strong class="text-muted">Subtotal</strong>
|
||||||
|
<strong>{{totals.subtotal | currency}}</strong>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex justify-content-between py-3 border-bottom">
|
||||||
|
<strong class="text-muted">Shipping and Handling</strong>
|
||||||
|
<strong>{{totals.shipping | currency}}</strong>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex justify-content-between py-3 border-bottom">
|
||||||
|
<strong class="text-muted">Total</strong>
|
||||||
|
<strong>{{totals.total | currency}}</strong>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
@ -0,0 +1,20 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { BasketService } from 'src/app/basket/basket.service';
|
||||||
|
import { IBasketTotals } from '../../models/baskset';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-order-totals',
|
||||||
|
templateUrl: './order-totals.component.html',
|
||||||
|
styleUrls: ['./order-totals.component.scss']
|
||||||
|
})
|
||||||
|
export class OrderTotalsComponent implements OnInit {
|
||||||
|
basketTotal$: Observable<IBasketTotals>;
|
||||||
|
|
||||||
|
constructor(private basketService: BasketService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.basketTotal$ = this.basketService.basketTotal$;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
client/src/app/shared/models/baskset.ts
Normal file
28
client/src/app/shared/models/baskset.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
export interface IBasketItem {
|
||||||
|
id: number;
|
||||||
|
productName: string;
|
||||||
|
price: number;
|
||||||
|
quantity: number;
|
||||||
|
pictureUrl: string;
|
||||||
|
brand: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBasket {
|
||||||
|
id: string;
|
||||||
|
items: IBasketItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Basket implements IBasket {
|
||||||
|
id = uuidv4();
|
||||||
|
items: IBasketItem[] = [];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBasketTotals {
|
||||||
|
shipping: number;
|
||||||
|
subtotal: number;
|
||||||
|
total: number;
|
||||||
|
}
|
@ -4,13 +4,15 @@ import { PaginationModule } from 'ngx-bootstrap/pagination';
|
|||||||
import { CarouselModule } from 'ngx-bootstrap/carousel';
|
import { CarouselModule } from 'ngx-bootstrap/carousel';
|
||||||
import { PagingHeaderComponent } from './components/paging-header/paging-header.component';
|
import { PagingHeaderComponent } from './components/paging-header/paging-header.component';
|
||||||
import { PagerComponent } from './components/pager/pager.component';
|
import { PagerComponent } from './components/pager/pager.component';
|
||||||
|
import { OrderTotalsComponent } from './components/order-totals/order-totals.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
PagingHeaderComponent,
|
PagingHeaderComponent,
|
||||||
PagerComponent
|
PagerComponent,
|
||||||
|
OrderTotalsComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -21,7 +23,8 @@ import { PagerComponent } from './components/pager/pager.component';
|
|||||||
PaginationModule,
|
PaginationModule,
|
||||||
PagingHeaderComponent,
|
PagingHeaderComponent,
|
||||||
PagerComponent,
|
PagerComponent,
|
||||||
CarouselModule
|
CarouselModule,
|
||||||
|
OrderTotalsComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SharedModule { }
|
export class SharedModule { }
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<i class="fa fa-minus-circle text-warning me-2" style="cursor: pointer; font-size: 2em"></i>
|
<i class="fa fa-minus-circle text-warning me-2" style="cursor: pointer; font-size: 2em"></i>
|
||||||
<span class="font-weight-bold" style="font-size: 1.5em;">2</span>
|
<span class="font-weight-bold" style="font-size: 1.5em;">2</span>
|
||||||
<i class="fa fa-plus-circle text-warning mx-2" style="cursor: pointer; font-size: 2em"></i>
|
<i class="fa fa-plus-circle text-warning mx-2" style="cursor: pointer; font-size: 2em"></i>
|
||||||
<button class="btn btn-outline-secondary btn-lg ms-4">Ad to Cart</button>
|
<button class="btn btn-outline-secondary btn-lg ms-4">Add to Cart</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="image position-relative" style="cursor: pointer;">
|
<div class="image position-relative" style="cursor: pointer;">
|
||||||
<img src="{{product.pictureUrl}}" alt="{{product.name}}" class="img-fluid bg-light">
|
<img src="{{product.pictureUrl}}" alt="{{product.name}}" class="img-fluid bg-light">
|
||||||
<div class="d-flex align-items-center justify-content-center hover-overlay">
|
<div class="d-flex align-items-center justify-content-center hover-overlay">
|
||||||
<button type="button" class="btn btn-sm btn-primary fa fa-shopping-cart me-2"></button>
|
<button (click)="addItemToBasket()" type="button" class="btn btn-sm btn-primary fa fa-shopping-cart me-2"></button>
|
||||||
<button routerLink="/shop/{{product.id}}" type="button" class="btn btn-sm btn-primary">View</button>
|
<button routerLink="/shop/{{product.id}}" type="button" class="btn btn-sm btn-primary">View</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { BasketService } from 'src/app/basket/basket.service';
|
||||||
import { IProduct } from 'src/app/shared/models/product';
|
import { IProduct } from 'src/app/shared/models/product';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -9,9 +10,13 @@ import { IProduct } from 'src/app/shared/models/product';
|
|||||||
export class ProductItemComponent implements OnInit {
|
export class ProductItemComponent implements OnInit {
|
||||||
@Input() product: IProduct
|
@Input() product: IProduct
|
||||||
|
|
||||||
constructor() { }
|
constructor(private basketService: BasketService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addItemToBasket(){
|
||||||
|
this.basketService.addItemToBasket(this.product);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>E-Commerce</title>
|
<title>SkiNet</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
@ -19,9 +19,9 @@ label.xng-breadcrumb-trail {
|
|||||||
|
|
||||||
.xng-breadcrumb-separator {
|
.xng-breadcrumb-separator {
|
||||||
padding: 0 0;
|
padding: 0 0;
|
||||||
color: #343a40;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xng-breadcrumb-link, a.xng-breadcrumb-link {
|
.xng-breadcrumb-link, a.xng-breadcrumb-link {
|
||||||
color: #343a40;
|
color: white;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user