Started Client Checkout
This commit is contained in:
parent
344ecb8762
commit
7cdf78794d
40
client/package-lock.json
generated
40
client/package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@angular/animations": "~13.3.0",
|
||||
"@angular/cdk": "^13.3.7",
|
||||
"@angular/common": "~13.3.0",
|
||||
"@angular/compiler": "~13.3.0",
|
||||
"@angular/core": "~13.3.0",
|
||||
@ -355,6 +356,28 @@
|
||||
"@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": {
|
||||
"version": "13.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.5.tgz",
|
||||
@ -11988,6 +12011,23 @@
|
||||
"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": {
|
||||
"version": "13.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.5.tgz",
|
||||
|
@ -11,6 +11,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~13.3.0",
|
||||
"@angular/cdk": "^13.3.7",
|
||||
"@angular/common": "~13.3.0",
|
||||
"@angular/compiler": "~13.3.0",
|
||||
"@angular/core": "~13.3.0",
|
||||
|
@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { map, of, ReplaySubject } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { IAddress } from '../shared/models/address';
|
||||
import { IUser } from '../shared/models/user';
|
||||
|
||||
@Injectable({
|
||||
@ -64,4 +65,12 @@ export class AccountService {
|
||||
checkEmailExists(email: string){
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { HomeModule } from './home/home.module';
|
||||
import { ErrorInterceptor } from './core/interceptors/error.interceptor';
|
||||
import { NgxSpinnerModule } from 'ngx-spinner';
|
||||
import { LoadingInterceptor } from './core/interceptors/loading.interceptor';
|
||||
import { JwtInterceptor } from './core/interceptors/jwt.interceptor';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -27,7 +28,8 @@ import { LoadingInterceptor } from './core/interceptors/loading.interceptor';
|
||||
],
|
||||
providers: [
|
||||
{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]
|
||||
})
|
||||
|
@ -7,58 +7,11 @@
|
||||
<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 (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>
|
||||
<app-basket-summary
|
||||
(decrement)="decrementItemQuantity($event)"
|
||||
(increment)="incrementItemQuantity($event)"
|
||||
(remove)="removeBasketItem($event)"
|
||||
></app-basket-summary>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@ -3,6 +3,7 @@ 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 { IDeliveryMethods } from '../shared/models/deliveryMethods';
|
||||
import { IProduct } from '../shared/models/product';
|
||||
|
||||
@Injectable({
|
||||
@ -14,9 +15,15 @@ export class BasketService {
|
||||
basket$ = this.basketSource.asObservable();
|
||||
private basketTotalSource = new BehaviorSubject<IBasketTotals>(null);
|
||||
basketTotal$ = this.basketTotalSource.asObservable();
|
||||
shipping = 0;
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
setShippingPrice(deliveryMethod: IDeliveryMethods){
|
||||
this.shipping = deliveryMethod.price;
|
||||
this.calculateTotals();
|
||||
}
|
||||
|
||||
getBasket(id: string){
|
||||
return this.http.get(this.baseUrl + 'basket?id=' + id).pipe(
|
||||
map((basket: IBasket) => {
|
||||
@ -92,7 +99,7 @@ export class BasketService {
|
||||
|
||||
private calculateTotals(){
|
||||
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 total = subtotal + shipping;
|
||||
this.basketTotalSource.next({
|
||||
|
@ -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>
|
@ -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'); }
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
<p>checkout-success works!</p>
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,23 @@
|
||||
<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>
|
||||
|
@ -1,4 +1,7 @@
|
||||
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({
|
||||
selector: 'app-checkout',
|
||||
@ -6,10 +9,44 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./checkout.component.scss']
|
||||
})
|
||||
export class CheckoutComponent implements OnInit {
|
||||
checkoutForm: FormGroup;
|
||||
|
||||
constructor() { }
|
||||
constructor(private fb: FormBuilder, private accountService: AccountService) { }
|
||||
|
||||
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') }
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,16 +2,28 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CheckoutComponent } from './checkout.component';
|
||||
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({
|
||||
declarations: [
|
||||
CheckoutComponent
|
||||
CheckoutComponent,
|
||||
CheckoutAddressComponent,
|
||||
CheckoutDeliveryComponent,
|
||||
CheckoutReviewComponent,
|
||||
CheckoutPaymentsComponent,
|
||||
CheckoutSuccessComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CheckoutRoutingModule
|
||||
CheckoutRoutingModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class CheckoutModule { }
|
||||
|
@ -1,9 +1,22 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { IDeliveryMethods } from '../shared/models/deliveryMethods';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
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);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
19
client/src/app/core/interceptors/jwt.interceptor.ts
Normal file
19
client/src/app/core/interceptors/jwt.interceptor.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
7
client/src/app/shared/models/deliveryMethods.ts
Normal file
7
client/src/app/shared/models/deliveryMethods.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface IDeliveryMethods {
|
||||
id: number;
|
||||
shortName: string;
|
||||
deliveryTime: string;
|
||||
description: string;
|
||||
price: number;
|
||||
}
|
@ -8,6 +8,10 @@ import { PagerComponent } from './components/pager/pager.component';
|
||||
import { OrderTotalsComponent } from './components/order-totals/order-totals.component';
|
||||
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
|
||||
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({
|
||||
@ -15,14 +19,18 @@ import { TextInputsComponent } from './components/text-inputs/text-inputs.compon
|
||||
PagingHeaderComponent,
|
||||
PagerComponent,
|
||||
OrderTotalsComponent,
|
||||
TextInputsComponent
|
||||
TextInputsComponent,
|
||||
StepperComponent,
|
||||
BasketSummaryComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
PaginationModule.forRoot(),
|
||||
CarouselModule.forRoot(),
|
||||
BsDropdownModule.forRoot(),
|
||||
ReactiveFormsModule
|
||||
ReactiveFormsModule,
|
||||
CdkStepperModule,
|
||||
RouterModule
|
||||
],
|
||||
exports: [
|
||||
PaginationModule,
|
||||
@ -32,7 +40,10 @@ import { TextInputsComponent } from './components/text-inputs/text-inputs.compon
|
||||
OrderTotalsComponent,
|
||||
ReactiveFormsModule,
|
||||
BsDropdownModule,
|
||||
TextInputsComponent
|
||||
TextInputsComponent,
|
||||
CdkStepperModule,
|
||||
StepperComponent,
|
||||
BasketSummaryComponent
|
||||
]
|
||||
})
|
||||
export class SharedModule { }
|
||||
|
Loading…
Reference in New Issue
Block a user