Added View Orders and Order Details to Users
This commit is contained in:
parent
7cdf78794d
commit
829e76ca68
@ -14,6 +14,7 @@ const routes: Routes = [
|
||||
{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: 'checkout', canActivate: [AuthGuard], loadChildren: ()=> import('./checkout/checkout.module').then(mod => mod.CheckoutModule), data: {breadcrumb: 'Checkout'}},
|
||||
{path: 'orders', canActivate: [AuthGuard], loadChildren: ()=> import('./orders/orders.module').then(mod => mod.OrdersModule), data: {breadcrumb: 'Orders'}},
|
||||
{path: 'account', loadChildren: ()=> import('./account/account.module').then(mod => mod.AccountModule), data: {breadcrumb: {skip: true}}},
|
||||
{path: '**', redirectTo: 'not-found', pathMatch: 'full'}
|
||||
];
|
||||
|
@ -6,7 +6,6 @@ import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { ShopModule } from './shop/shop.module';
|
||||
import { HomeModule } from './home/home.module';
|
||||
import { ErrorInterceptor } from './core/interceptors/error.interceptor';
|
||||
import { NgxSpinnerModule } from 'ngx-spinner';
|
||||
|
@ -85,6 +85,12 @@ export class BasketService {
|
||||
}
|
||||
}
|
||||
|
||||
deleteLocalBasket(id: string){
|
||||
this.basketSource.next(null);
|
||||
this.basketTotalSource.next(null);
|
||||
localStorage.removeItem('basket_id');
|
||||
}
|
||||
|
||||
deleteBasket(basket: IBasket) {
|
||||
return this.http.delete(this.baseUrl + 'basket?id=' + basket.id).subscribe({
|
||||
next: () => {
|
||||
|
@ -3,7 +3,7 @@
|
||||
<button class="btn btn-outline-primary" cdkStepperPrevious>
|
||||
<i class="fa fa-angle-left"></i> Back to Review
|
||||
</button>
|
||||
<button class="btn btn-primary">
|
||||
<button class="btn btn-primary" (click)="submitOrder()">
|
||||
Complete Checkout <i class="fa fa-angle-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { NavigationExtras, Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { BasketService } from 'src/app/basket/basket.service';
|
||||
import { IBasket } from 'src/app/shared/models/baskset';
|
||||
import { IOrder } from 'src/app/shared/models/order';
|
||||
import { CheckoutService } from '../checkout.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-checkout-payments',
|
||||
@ -6,10 +13,36 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./checkout-payments.component.scss']
|
||||
})
|
||||
export class CheckoutPaymentsComponent implements OnInit {
|
||||
@Input() checkoutForm: FormGroup;
|
||||
|
||||
constructor() { }
|
||||
constructor(private basketService: BasketService, private checkoutService: CheckoutService, private toastr: ToastrService, private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
submitOrder(){
|
||||
const basket = this.basketService.getCurrentBasketValue();
|
||||
const orderToCreate = this.getOrderToCreate(basket);
|
||||
this.checkoutService.createOrder(orderToCreate).subscribe({
|
||||
next: (order: IOrder) => {
|
||||
this.toastr.success('Order created successfully');
|
||||
this.basketService.deleteLocalBasket(basket.id);
|
||||
const navigationExtras: NavigationExtras = {state: order};
|
||||
this.router.navigate(['checkout/success'], navigationExtras);
|
||||
},
|
||||
error: (e: any) => {
|
||||
this.toastr.error(e.message);
|
||||
console.log(e);
|
||||
},
|
||||
complete: () => { console.log('completed') }
|
||||
});
|
||||
}
|
||||
|
||||
private getOrderToCreate(basket: IBasket) {
|
||||
return {
|
||||
basketId: basket.id,
|
||||
deliveryMethodId: +this.checkoutForm.get('deliveryForm').get('deliveryMethod').value,
|
||||
shipToAddress: this.checkoutForm.get('addressForm').value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CheckoutComponent } from './checkout.component';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CheckoutSuccessComponent } from './checkout-success/checkout-success.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: CheckoutComponent}
|
||||
]
|
||||
{path: '', component: CheckoutComponent},
|
||||
{path: 'success', component: CheckoutSuccessComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
|
@ -1 +1,9 @@
|
||||
<p>checkout-success works!</p>
|
||||
<div class="container mt-5">
|
||||
<div>
|
||||
<i class="fa fa-check-circle fa-5x" style="color: green;"></i>
|
||||
</div>
|
||||
<h2>This you. Your order is confirmed</h2>
|
||||
<p class="mb-4">Your order number is: {{order?.id}}</p>
|
||||
<button *ngIf="order" class="btn btn-outline-success" routerLink="/orders/{{order?.id}}">View Your Order</button>
|
||||
<button *ngIf="!order" class="btn btn-outline-success" routerLink="/orders">View Orders</button>
|
||||
</div>
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { IOrder } from 'src/app/shared/models/order';
|
||||
|
||||
@Component({
|
||||
selector: 'app-checkout-success',
|
||||
@ -6,8 +8,13 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./checkout-success.component.scss']
|
||||
})
|
||||
export class CheckoutSuccessComponent implements OnInit {
|
||||
order: IOrder;
|
||||
|
||||
constructor() { }
|
||||
constructor(private router: Router) {
|
||||
const navigation = this.router.getCurrentNavigation();
|
||||
const state = navigation && navigation.extras && navigation.extras.state;
|
||||
if(state) { this.order = state as IOrder; }
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
<app-checkout-review></app-checkout-review>
|
||||
</cdk-step>
|
||||
<cdk-step [label]="'Payment'">
|
||||
<app-checkout-payments></app-checkout-payments>
|
||||
<app-checkout-payments [checkoutForm]="checkoutForm"></app-checkout-payments>
|
||||
</cdk-step>
|
||||
</app-stepper>
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
import { map } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { IDeliveryMethods } from '../shared/models/deliveryMethods';
|
||||
import { IOrderToCreate } from '../shared/models/order';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -12,6 +13,10 @@ export class CheckoutService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
createOrder(order: IOrderToCreate){
|
||||
return this.http.post(this.baseUrl + 'orders', order);
|
||||
}
|
||||
|
||||
getDeliveryMethods(){
|
||||
return this.http.get(this.baseUrl + 'orders/deliveryMethods').pipe(
|
||||
map((dm: IDeliveryMethods[]) => {
|
||||
|
@ -0,0 +1,66 @@
|
||||
<div class="container mt-5">
|
||||
<div class="row" *ngIf="order">
|
||||
<div class="col-8">
|
||||
<div>
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of order.orderItems">
|
||||
<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">{{item.productName}}</a>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<td class="align-middle text-center">{{item.price | currency}}</td>
|
||||
<td class="align-middle text-center">{{item.quantity}}</td>
|
||||
<td class="align-middle text-center">{{item.price * item.quantity | currency}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="bg-light px-4 py-3 text-uppercase font-weight-bold">
|
||||
Order Summary
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<ul class="list-unstyled mb-4">
|
||||
<li class="d-flex justify-content-between py-3 border-bottom">
|
||||
<strong class="text-muted">Subtotal</strong>
|
||||
<strong>{{order.subtotal | currency}}</strong>
|
||||
</li>
|
||||
<li class="d-flex justify-content-between py-3 border-bottom">
|
||||
<strong class="text-muted">Shipping and Handling</strong>
|
||||
<strong>{{order.shippingPrice | currency}}</strong>
|
||||
</li>
|
||||
<li class="d-flex justify-content-between py-3 border-bottom">
|
||||
<strong class="text-muted">Total</strong>
|
||||
<strong>{{order.total | currency}}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,29 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { IOrder } from 'src/app/shared/models/order';
|
||||
import { BreadcrumbService } from 'xng-breadcrumb';
|
||||
import { OrdersService } from '../orders.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-order-details',
|
||||
templateUrl: './order-details.component.html',
|
||||
styleUrls: ['./order-details.component.scss']
|
||||
})
|
||||
export class OrderDetailsComponent implements OnInit {
|
||||
order: IOrder;
|
||||
|
||||
constructor(private route: ActivatedRoute, private breadCrumbService: BreadcrumbService, private orderService: OrdersService) {
|
||||
this.breadCrumbService.set('@OrderDetails', ' ');
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.orderService.getOrderDetails(+this.route.snapshot.paramMap.get('id')).subscribe({
|
||||
next: (order: IOrder) => {
|
||||
this.order = order;
|
||||
this.breadCrumbService.set('@OrderDetails', `Order# ${order.id} - ${order.status}`);
|
||||
},
|
||||
error: (e: any) => { console.log(e); },
|
||||
complete: () => { console.log('completed'); }
|
||||
})
|
||||
}
|
||||
}
|
15
client/src/app/orders/orders-routing.module.ts
Normal file
15
client/src/app/orders/orders-routing.module.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { OrderDetailsComponent } from './order-details/order-details.component';
|
||||
import { OrdersComponent } from './orders.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: OrdersComponent},
|
||||
{path: ':id', component: OrderDetailsComponent, data: {breadcrumb: {alias: 'OrderDetails'}}},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class OrdersRoutingModule { }
|
24
client/src/app/orders/orders.component.html
Normal file
24
client/src/app/orders/orders.component.html
Normal file
@ -0,0 +1,24 @@
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<table class="table table-cover" style="cursor: pointer;">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Order</th>
|
||||
<th>Total</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let order of orders" routerLink="/orders/{{order.id}}">
|
||||
<th># {{order.id}}</th>
|
||||
<td>{{order.orderDate | date: 'medium'}}</td>
|
||||
<td>{{order.total | currency}}</td>
|
||||
<td>{{order.status}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
0
client/src/app/orders/orders.component.scss
Normal file
0
client/src/app/orders/orders.component.scss
Normal file
27
client/src/app/orders/orders.component.ts
Normal file
27
client/src/app/orders/orders.component.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { IOrder } from '../shared/models/order';
|
||||
import { OrdersService } from './orders.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-orders',
|
||||
templateUrl: './orders.component.html',
|
||||
styleUrls: ['./orders.component.scss']
|
||||
})
|
||||
export class OrdersComponent implements OnInit {
|
||||
orders: IOrder[];
|
||||
|
||||
constructor(private orderService: OrdersService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getOrders();
|
||||
}
|
||||
|
||||
getOrders(){
|
||||
this.orderService.getOrdersForUser().subscribe({
|
||||
next: (orders: IOrder[]) => { this.orders = orders; },
|
||||
error: (e: any) => { console.log(e); },
|
||||
complete: () => { console.log('Orders'); }
|
||||
});
|
||||
}
|
||||
|
||||
}
|
19
client/src/app/orders/orders.module.ts
Normal file
19
client/src/app/orders/orders.module.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { OrderDetailsComponent } from './order-details/order-details.component';
|
||||
import { OrdersComponent } from './orders.component';
|
||||
import { OrdersRoutingModule } from './orders-routing.module';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
OrdersComponent,
|
||||
OrderDetailsComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
OrdersRoutingModule
|
||||
]
|
||||
})
|
||||
export class OrdersModule { }
|
20
client/src/app/orders/orders.service.ts
Normal file
20
client/src/app/orders/orders.service.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class OrdersService {
|
||||
baseUrl = environment.apiUrl;
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getOrdersForUser(){
|
||||
return this.http.get(this.baseUrl + 'orders');
|
||||
}
|
||||
|
||||
getOrderDetails(id: number){
|
||||
return this.http.get(this.baseUrl + 'orders/' + id);
|
||||
}
|
||||
}
|
@ -1,52 +1,55 @@
|
||||
<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>
|
||||
<ng-container *ngIf="basket$ | async">
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</ng-container>
|
||||
|
||||
|
28
client/src/app/shared/models/order.ts
Normal file
28
client/src/app/shared/models/order.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { IAddress } from "./address";
|
||||
|
||||
export interface IOrderToCreate {
|
||||
basketId: string;
|
||||
deliveryMethodId: number;
|
||||
shipToAddress: IAddress;
|
||||
}
|
||||
|
||||
export interface IOrderItem {
|
||||
productId: number;
|
||||
productName: string;
|
||||
pictureUrl: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface IOrder {
|
||||
id: number;
|
||||
buyerEmail: string;
|
||||
orderDate: Date;
|
||||
shipToAddress: IAddress;
|
||||
deliveryMethod: string;
|
||||
shippingPrice: number;
|
||||
orderItems: IOrderItem[];
|
||||
subtotal: number;
|
||||
total: number;
|
||||
status: string;
|
||||
}
|
Loading…
Reference in New Issue
Block a user