Started Client Checkout

This commit is contained in:
Charles Showalter 2022-05-25 16:16:50 -07:00
parent 344ecb8762
commit 7cdf78794d
34 changed files with 506 additions and 62 deletions

View File

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@angular/animations": "~13.3.0", "@angular/animations": "~13.3.0",
"@angular/cdk": "^13.3.7",
"@angular/common": "~13.3.0", "@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0", "@angular/compiler": "~13.3.0",
"@angular/core": "~13.3.0", "@angular/core": "~13.3.0",
@ -355,6 +356,28 @@
"@angular/core": "13.3.7" "@angular/core": "13.3.7"
} }
}, },
"node_modules/@angular/cdk": {
"version": "13.3.7",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.7.tgz",
"integrity": "sha512-HtGqlrt4+ikbpzooF0LT/uMW6fgRJxLRUoOwkTY1oHhfNXhQaE2p8XEUH2qshl28aCIF8r8zrb6jpd4VqC+tyg==",
"dependencies": {
"tslib": "^2.3.0"
},
"optionalDependencies": {
"parse5": "^5.0.0"
},
"peerDependencies": {
"@angular/common": "^13.0.0 || ^14.0.0-0",
"@angular/core": "^13.0.0 || ^14.0.0-0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/cdk/node_modules/parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
},
"node_modules/@angular/cli": { "node_modules/@angular/cli": {
"version": "13.3.5", "version": "13.3.5",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.5.tgz", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.5.tgz",
@ -11988,6 +12011,23 @@
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"@angular/cdk": {
"version": "13.3.7",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.7.tgz",
"integrity": "sha512-HtGqlrt4+ikbpzooF0LT/uMW6fgRJxLRUoOwkTY1oHhfNXhQaE2p8XEUH2qshl28aCIF8r8zrb6jpd4VqC+tyg==",
"requires": {
"parse5": "^5.0.0",
"tslib": "^2.3.0"
},
"dependencies": {
"parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
}
}
},
"@angular/cli": { "@angular/cli": {
"version": "13.3.5", "version": "13.3.5",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.5.tgz", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.5.tgz",

View File

@ -11,6 +11,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~13.3.0", "@angular/animations": "~13.3.0",
"@angular/cdk": "^13.3.7",
"@angular/common": "~13.3.0", "@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0", "@angular/compiler": "~13.3.0",
"@angular/core": "~13.3.0", "@angular/core": "~13.3.0",

View File

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { map, of, ReplaySubject } from 'rxjs'; import { map, of, ReplaySubject } from 'rxjs';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { IAddress } from '../shared/models/address';
import { IUser } from '../shared/models/user'; import { IUser } from '../shared/models/user';
@Injectable({ @Injectable({
@ -64,4 +65,12 @@ export class AccountService {
checkEmailExists(email: string){ checkEmailExists(email: string){
return this.http.get(this.baseUrl + 'account/emailexists?email=' + email); return this.http.get(this.baseUrl + 'account/emailexists?email=' + email);
} }
getUserAddress(){
return this.http.get<IAddress>(this.baseUrl + 'account/address');
}
updateUserAddress(address: IAddress){
return this.http.put<IAddress>(this.baseUrl + 'account/address', address);
}
} }

View File

@ -11,6 +11,7 @@ import { HomeModule } from './home/home.module';
import { ErrorInterceptor } from './core/interceptors/error.interceptor'; import { ErrorInterceptor } from './core/interceptors/error.interceptor';
import { NgxSpinnerModule } from 'ngx-spinner'; import { NgxSpinnerModule } from 'ngx-spinner';
import { LoadingInterceptor } from './core/interceptors/loading.interceptor'; import { LoadingInterceptor } from './core/interceptors/loading.interceptor';
import { JwtInterceptor } from './core/interceptors/jwt.interceptor';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -27,7 +28,8 @@ import { LoadingInterceptor } from './core/interceptors/loading.interceptor';
], ],
providers: [ providers: [
{provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: LoadingInterceptor, multi: true} {provide: HTTP_INTERCEPTORS, useClass: LoadingInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true}
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -7,58 +7,11 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 py-3 mb-1"> <div class="col-12 py-3 mb-1">
<div class="table-responsive"> <app-basket-summary
<table class="table table-striped table-hover table-light"> (decrement)="decrementItemQuantity($event)"
<thead> (increment)="incrementItemQuantity($event)"
<tr> (remove)="removeBasketItem($event)"
<th scope="col"> ></app-basket-summary>
<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 (click)="decrementItemQuantity(item)" class="fa fa-minus-circle text-warning me-2" style="cursor: pointer;"></i>
<span class="font-weight-bold">{{item.quantity}}</span>
<i (click)="incrementItemQuantity(item)" 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 (click)="removeBasketItem(item)" class="fa fa-trash" style="font-size: 2em"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">

View File

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject, map } from 'rxjs'; import { BehaviorSubject, map } from 'rxjs';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { Basket, IBasket, IBasketItem, IBasketTotals } from '../shared/models/baskset'; import { Basket, IBasket, IBasketItem, IBasketTotals } from '../shared/models/baskset';
import { IDeliveryMethods } from '../shared/models/deliveryMethods';
import { IProduct } from '../shared/models/product'; import { IProduct } from '../shared/models/product';
@Injectable({ @Injectable({
@ -14,9 +15,15 @@ export class BasketService {
basket$ = this.basketSource.asObservable(); basket$ = this.basketSource.asObservable();
private basketTotalSource = new BehaviorSubject<IBasketTotals>(null); private basketTotalSource = new BehaviorSubject<IBasketTotals>(null);
basketTotal$ = this.basketTotalSource.asObservable(); basketTotal$ = this.basketTotalSource.asObservable();
shipping = 0;
constructor(private http: HttpClient) { } constructor(private http: HttpClient) { }
setShippingPrice(deliveryMethod: IDeliveryMethods){
this.shipping = deliveryMethod.price;
this.calculateTotals();
}
getBasket(id: string){ getBasket(id: string){
return this.http.get(this.baseUrl + 'basket?id=' + id).pipe( return this.http.get(this.baseUrl + 'basket?id=' + id).pipe(
map((basket: IBasket) => { map((basket: IBasket) => {
@ -92,7 +99,7 @@ export class BasketService {
private calculateTotals(){ private calculateTotals(){
const basket = this.getCurrentBasketValue(); const basket = this.getCurrentBasketValue();
const shipping = 0; const shipping = this.shipping;
const subtotal = basket.items.reduce((a, b) => (b.price * b.quantity) + a, 0); const subtotal = basket.items.reduce((a, b) => (b.price * b.quantity) + a, 0);
const total = subtotal + shipping; const total = subtotal + shipping;
this.basketTotalSource.next({ this.basketTotalSource.next({

View File

@ -0,0 +1,39 @@
<div class="mt-4" [formGroup]="checkoutForm">
<div class="d-flex justify-content-between align-items-center">
<h4>Shipping Address</h4>
<button
(click)="saveUserAddress()"
[disabled]="!checkoutForm.get('addressForm').valid || !checkoutForm.get('addressForm').dirty"
class="btn btn-outline-success mb-3">
Save as default address
</button>
</div>
<div class="row" formGroupName="addressForm">
<div class="form-group col-6">
<app-text-inputs [label]="'First Name'" formControlName="firstName"></app-text-inputs>
</div>
<div class="form-group col-6">
<app-text-inputs [label]="'Last Name'" formControlName="lastName"></app-text-inputs>
</div>
<div class="form-group col-6">
<app-text-inputs [label]="'Street'" formControlName="street"></app-text-inputs>
</div>
<div class="form-group col-6">
<app-text-inputs [label]="'City'" formControlName="city"></app-text-inputs>
</div>
<div class="form-group col-6">
<app-text-inputs [label]="'State'" formControlName="state"></app-text-inputs>
</div>
<div class="form-group col-6">
<app-text-inputs [label]="'Zip Code'" formControlName="zipCode"></app-text-inputs>
</div>
</div>
</div>
<div class="float-none d-flex justify-content-between flex-column flex-lg-row mb-5">
<button class="btn btn-outline-primary" routerLink="/basket">
<i class="fa fa-angle-left"></i> Back to Shopping Cart
</button>
<button class="btn btn-primary" cdkStepperNext>
Delivery Options <i class="fa fa-angle-right"></i>
</button>
</div>

View File

@ -0,0 +1,29 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { AccountService } from 'src/app/account/account.service';
@Component({
selector: 'app-checkout-address',
templateUrl: './checkout-address.component.html',
styleUrls: ['./checkout-address.component.scss']
})
export class CheckoutAddressComponent implements OnInit {
@Input() checkoutForm: FormGroup;
constructor(private accountService: AccountService, private toaster: ToastrService) { }
ngOnInit(): void {
}
saveUserAddress(){
this.accountService.updateUserAddress(this.checkoutForm.get('addressForm').value).subscribe({
next: () => { this.toaster.success('Address Saved'); },
error: (e: any) => {
this.toaster.error(e.message);
},
complete: () => { console.log('completed'); }
});
}
}

View File

@ -0,0 +1,27 @@
<div class="mt-4" [formGroup]="checkoutForm">
<h4 class="mb-3">Choose your delivery method.</h4>
<div class="row ms-5" formGroupName="deliveryForm">
<div class="col-5 form-group" *ngFor="let method of deliveryMethods">
<input type="radio"
id="{{method.id}}"
(click)="setShippingPrice(method)"
value="{{method.id}}"
formControlName="deliveryMethod"
class="custom-control-input m-3"
>
<label for="{{method.id}}" class="custom-control-label">
<strong>{{method.shortName}} - {{method.price | currency}}</strong>
<br>
<span class="label-description">{{method.description}}</span>
</label>
</div>
</div>
</div>
<div class="float-none d-flex justify-content-between flex-column flex-lg-row m-3">
<button class="btn btn-outline-primary" cdkStepperPrevious>
<i class="fa fa-angle-left"></i> Back to Address
</button>
<button class="btn btn-primary" cdkStepperNext>
Review Order <i class="fa fa-angle-right"></i>
</button>
</div>

View File

@ -0,0 +1,30 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { BasketService } from 'src/app/basket/basket.service';
import { IDeliveryMethods } from 'src/app/shared/models/deliveryMethods';
import { CheckoutService } from '../checkout.service';
@Component({
selector: 'app-checkout-delivery',
templateUrl: './checkout-delivery.component.html',
styleUrls: ['./checkout-delivery.component.scss']
})
export class CheckoutDeliveryComponent implements OnInit {
@Input() checkoutForm: FormGroup;
deliveryMethods: IDeliveryMethods[];
constructor(private checkoutService: CheckoutService, private basketService: BasketService) { }
ngOnInit(): void {
this.checkoutService.getDeliveryMethods().subscribe({
next: (dm: IDeliveryMethods[]) => { this.deliveryMethods = dm; },
error: (e: any) => { console.log(e); },
complete: () => { console.log('completed') }
});
}
setShippingPrice(deliveryMethod: IDeliveryMethods){
this.basketService.setShippingPrice(deliveryMethod);
}
}

View File

@ -0,0 +1,9 @@
<p>checkout-payments works!</p>
<div class="float-none d-flex justify-content-between flex-column flex-lg-row mb-5">
<button class="btn btn-outline-primary" cdkStepperPrevious>
<i class="fa fa-angle-left"></i> Back to Review
</button>
<button class="btn btn-primary">
Complete Checkout <i class="fa fa-angle-right"></i>
</button>
</div>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-checkout-payments',
templateUrl: './checkout-payments.component.html',
styleUrls: ['./checkout-payments.component.scss']
})
export class CheckoutPaymentsComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,11 @@
<div class="container mt-4">
<app-basket-summary [isBasket]=false></app-basket-summary>
</div>
<div class="float-none d-flex justify-content-between flex-column flex-lg-row mb-5">
<button class="btn btn-outline-primary" cdkStepperPrevious>
<i class="fa fa-angle-left"></i> Back to Delivery
</button>
<button class="btn btn-primary" cdkStepperNext>
Payment Options <i class="fa fa-angle-right"></i>
</button>
</div>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-checkout-review',
templateUrl: './checkout-review.component.html',
styleUrls: ['./checkout-review.component.scss']
})
export class CheckoutReviewComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1 @@
<p>checkout-success works!</p>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-checkout-success',
templateUrl: './checkout-success.component.html',
styleUrls: ['./checkout-success.component.scss']
})
export class CheckoutSuccessComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -1,3 +1,23 @@
<div class="container mt-5"> <div class="container mt-5">
<h1>Only authorized users should be able to see this.</h1> <div class="row">
<div class="col-8">
<app-stepper [linearModeSelected]="false" #appStepper>
<cdk-step [label]="'Address'" [completed]="(checkoutForm.get('addressForm')).valid">
<app-checkout-address [checkoutForm]="checkoutForm"></app-checkout-address>
</cdk-step>
<cdk-step [label]="'Delivery'" [completed]="(checkoutForm.get('deliveryForm')).valid">
<app-checkout-delivery [checkoutForm]="checkoutForm"></app-checkout-delivery>
</cdk-step>
<cdk-step [label]="'Review'">
<app-checkout-review></app-checkout-review>
</cdk-step>
<cdk-step [label]="'Payment'">
<app-checkout-payments></app-checkout-payments>
</cdk-step>
</app-stepper>
</div>
<div class="col-3">
<app-order-totals></app-order-totals>
</div>
</div>
</div> </div>

View File

@ -1,4 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AccountService } from '../account/account.service';
import { IAddress } from '../shared/models/address';
@Component({ @Component({
selector: 'app-checkout', selector: 'app-checkout',
@ -6,10 +9,44 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./checkout.component.scss'] styleUrls: ['./checkout.component.scss']
}) })
export class CheckoutComponent implements OnInit { export class CheckoutComponent implements OnInit {
checkoutForm: FormGroup;
constructor() { } constructor(private fb: FormBuilder, private accountService: AccountService) { }
ngOnInit(): void { ngOnInit(): void {
this.createCheckoutForm();
this.getAddressFormValues();
}
createCheckoutForm(){
this.checkoutForm = this.fb.group({
addressForm: this.fb.group({
firstName: [null, Validators.required],
lastName: [null, Validators.required],
street: [null, Validators.required],
city: [null, Validators.required],
state: [null, Validators.required],
zipCode: [null, Validators.required]
}),
deliveryForm: this.fb.group({
deliveryMethod: [null, Validators.required]
}),
paymenForm: this.fb.group({
nameOnCard: [null, Validators.required]
})
});
}
getAddressFormValues(){
this.accountService.getUserAddress().subscribe({
next: (address: any) => {
if(address){
this.checkoutForm.get('addressForm').patchValue(address);
}
},
error: (e: any) => { console.log(e) },
complete: () => { console.log('completed') }
});
} }
} }

View File

@ -2,16 +2,28 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CheckoutComponent } from './checkout.component'; import { CheckoutComponent } from './checkout.component';
import { CheckoutRoutingModule } from './checkout-routing.module'; import { CheckoutRoutingModule } from './checkout-routing.module';
import { SharedModule } from '../shared/shared.module';
import { CheckoutAddressComponent } from './checkout-address/checkout-address.component';
import { CheckoutDeliveryComponent } from './checkout-delivery/checkout-delivery.component';
import { CheckoutReviewComponent } from './checkout-review/checkout-review.component';
import { CheckoutPaymentsComponent } from './checkout-payments/checkout-payments.component';
import { CheckoutSuccessComponent } from './checkout-success/checkout-success.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
CheckoutComponent CheckoutComponent,
CheckoutAddressComponent,
CheckoutDeliveryComponent,
CheckoutReviewComponent,
CheckoutPaymentsComponent,
CheckoutSuccessComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
CheckoutRoutingModule CheckoutRoutingModule,
SharedModule
] ]
}) })
export class CheckoutModule { } export class CheckoutModule { }

View File

@ -1,9 +1,22 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { map } from 'rxjs';
import { environment } from 'src/environments/environment';
import { IDeliveryMethods } from '../shared/models/deliveryMethods';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class CheckoutService { export class CheckoutService {
baseUrl = environment.apiUrl;
constructor() { } constructor(private http: HttpClient) { }
getDeliveryMethods(){
return this.http.get(this.baseUrl + 'orders/deliveryMethods').pipe(
map((dm: IDeliveryMethods[]) => {
return dm.sort((a, b) => b.price - a.price);
})
);
}
} }

View File

@ -0,0 +1,19 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = localStorage.getItem('token');
if(token) {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next.handle(req);
}
}

View File

@ -0,0 +1,52 @@
<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 *ngIf="isBasket" 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 *ngIf="isBasket" (click)="decrementItemQuantity(item)" class="fa fa-minus-circle text-warning me-2" style="cursor: pointer;"></i>
<span class="font-weight-bold">{{item.quantity}}</span>
<i *ngIf="isBasket" (click)="incrementItemQuantity(item)" 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 *ngIf="isBasket" class="align-middle text-center">
<a class="text-danger" style="cursor: pointer">
<i (click)="removeBasketItem(item)" class="fa fa-trash" style="font-size: 2em"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,35 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Observable } from 'rxjs';
import { BasketService } from 'src/app/basket/basket.service';
import { IBasket, IBasketItem } from '../models/baskset';
@Component({
selector: 'app-basket-summary',
templateUrl: './basket-summary.component.html',
styleUrls: ['./basket-summary.component.scss']
})
export class BasketSummaryComponent implements OnInit {
basket$: Observable<IBasket>;
@Output() decrement: EventEmitter<IBasketItem> = new EventEmitter<IBasketItem>();
@Output() increment: EventEmitter<IBasketItem> = new EventEmitter<IBasketItem>();
@Output() remove: EventEmitter<IBasketItem> = new EventEmitter<IBasketItem>();
@Input() isBasket = true;
constructor(private basketService: BasketService) { }
ngOnInit(): void {
this.basket$ = this.basketService.basket$;
}
decrementItemQuantity(item: IBasketItem){
this.decrement.emit(item);
}
incrementItemQuantity(item: IBasketItem){
this.increment.emit(item);
}
removeBasketItem(item: IBasketItem){
this.remove.emit(item);
}
}

View File

@ -0,0 +1,15 @@
<div class="container">
<ul class="nav nav-pills nav-justified">
<li class="nav-item" *ngFor="let step of steps; let i = index">
<button
(click)="onClick(i)"
[class.active]="selectedIndex === i"
class="nav-link py-3 text-uppercase font-weight-bold btn-block">
{{step.label}}
</button>
</li>
</ul>
<div>
<ng-container [ngTemplateOutlet]="selected.content"></ng-container>
</div>
</div>

View File

@ -0,0 +1,20 @@
import { Component, Input, OnInit } from '@angular/core';
import { CdkStepper } from '@angular/cdk/stepper';
@Component({
selector: 'app-stepper',
templateUrl: './stepper.component.html',
styleUrls: ['./stepper.component.scss'],
providers: [{provide: CdkStepper, useExisting: StepperComponent}]
})
export class StepperComponent extends CdkStepper implements OnInit {
@Input() linearModeSelected: boolean;
ngOnInit(): void {
this.linear = this.linearModeSelected;
}
onClick(index: number){
this.selectedIndex = index;
}
}

View File

@ -0,0 +1,7 @@
export interface IDeliveryMethods {
id: number;
shortName: string;
deliveryTime: string;
description: string;
price: number;
}

View File

@ -8,6 +8,10 @@ import { PagerComponent } from './components/pager/pager.component';
import { OrderTotalsComponent } from './components/order-totals/order-totals.component'; import { OrderTotalsComponent } from './components/order-totals/order-totals.component';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TextInputsComponent } from './components/text-inputs/text-inputs.component'; import { TextInputsComponent } from './components/text-inputs/text-inputs.component';
import { CdkStepperModule } from '@angular/cdk/stepper';
import { StepperComponent } from './components/stepper/stepper.component';
import { BasketSummaryComponent } from './basket-summary/basket-summary.component'
import { RouterModule } from '@angular/router';
@NgModule({ @NgModule({
@ -15,14 +19,18 @@ import { TextInputsComponent } from './components/text-inputs/text-inputs.compon
PagingHeaderComponent, PagingHeaderComponent,
PagerComponent, PagerComponent,
OrderTotalsComponent, OrderTotalsComponent,
TextInputsComponent TextInputsComponent,
StepperComponent,
BasketSummaryComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
PaginationModule.forRoot(), PaginationModule.forRoot(),
CarouselModule.forRoot(), CarouselModule.forRoot(),
BsDropdownModule.forRoot(), BsDropdownModule.forRoot(),
ReactiveFormsModule ReactiveFormsModule,
CdkStepperModule,
RouterModule
], ],
exports: [ exports: [
PaginationModule, PaginationModule,
@ -32,7 +40,10 @@ import { TextInputsComponent } from './components/text-inputs/text-inputs.compon
OrderTotalsComponent, OrderTotalsComponent,
ReactiveFormsModule, ReactiveFormsModule,
BsDropdownModule, BsDropdownModule,
TextInputsComponent TextInputsComponent,
CdkStepperModule,
StepperComponent,
BasketSummaryComponent
] ]
}) })
export class SharedModule { } export class SharedModule { }