Added View Orders and Order Details to Users

This commit is contained in:
Charles Showalter 2022-05-26 14:26:27 -07:00
parent 7cdf78794d
commit 829e76ca68
21 changed files with 352 additions and 60 deletions

View File

@ -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'}
];

View File

@ -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';

View File

@ -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: () => {

View File

@ -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>

View File

@ -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
};
}
}

View File

@ -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: [],

View File

@ -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>

View File

@ -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 {
}

View File

@ -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>

View File

@ -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[]) => {

View File

@ -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>

View File

@ -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'); }
})
}
}

View 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 { }

View 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>

View 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'); }
});
}
}

View 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 { }

View 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);
}
}

View File

@ -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>

View 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;
}