feature: show order summary on address page

This commit is contained in:
kusowl 2026-03-17 16:10:43 +05:30
parent 419e8281e2
commit bb3aafd89e
7 changed files with 111 additions and 16 deletions

View File

@ -0,0 +1,12 @@
{
"mcpServers": {
"angular-cli": {
"command": "npx",
"args": [
"-y",
"@angular/cli",
"mcp"
]
}
}
}

View File

@ -4,12 +4,45 @@
<div class="grid grid-cols-3 gap-x-10"> <div class="grid grid-cols-3 gap-x-10">
<div class="col-span-2 flex flex-col space-y-4"> <div class="col-span-2 flex flex-col space-y-4">
@for (address of addresses(); track address.id) { @for (address of addresses(); track address.id) {
<app-address-select [address]="address" (addressUpdated)="updateAddress($event)" /> <div class="flex space-x-2">
<input
type="radio"
name="address"
value="{{address.id}}"
[formControl]="addressIdControl"
/>
<app-address-select
class="flex-1"
[address]="address"
(addressUpdated)="updateAddress($event)"
/>
</div>
} }
<app-address-form (submitAddress)="createNewAddress($event)" /> <app-address-form class="ml-5" (submitAddress)="createNewAddress($event)" />
</div> </div>
<div> <div>
<app-order-summery /> <app-order-summery />
<div class="card mt-4">
<fieldset class="fieldset">
<legend class="fieldset-legend">Have any coupon ?</legend>
<div class="flex items-center space-x-2">
<input placeholder="Enter coupon here" type="text" class="input" />
<button class="btn btn-ghost px-4">Apply</button>
</div>
</fieldset>
@if (addressIdControl.invalid && addressIdControl.touched) {
<div class="text-red-500 text-sm p-4 mt-4 rounded-xl bg-red-50">
Please select an address
</div>
}
<button
class="btn btn-primary w-full mt-4"
(click)="proceedToPayment()"
[disabled]="addressIdControl.invalid && addressIdControl.touched"
>
Proceed to payment
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, inject, OnInit, signal } from "@angular/core"; import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core";
import { AddressForm } from "../components/address-form/address-form"; import { AddressForm } from "../components/address-form/address-form";
import { GoBack } from "@app/shared/components/go-back/go-back"; import { GoBack } from "@app/shared/components/go-back/go-back";
import { AddressSelect } from "../components/address-select/address-select"; import { AddressSelect } from "../components/address-select/address-select";
@ -10,19 +10,27 @@ import {
} from "@app/features/checkout/services/address-service"; } from "@app/features/checkout/services/address-service";
import { AuthService } from "@core/services/auth-service"; import { AuthService } from "@core/services/auth-service";
import { User } from "@core/models/user.model"; import { User } from "@core/models/user.model";
import { switchMap } from "rxjs"; import { of, switchMap } from "rxjs";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormControl, ReactiveFormsModule, Validators } from "@angular/forms";
@Component({ @Component({
selector: "app-address", selector: "app-address",
imports: [AddressSelect, AddressForm, GoBack, OrderSummery], imports: [AddressSelect, AddressForm, GoBack, OrderSummery, ReactiveFormsModule],
templateUrl: "./address.html", templateUrl: "./address.html",
styleUrl: "./address.css", styleUrl: "./address.css",
}) })
export class Address implements OnInit { export class Address implements OnInit {
addressService = inject(AddressService); addressService = inject(AddressService);
authService = inject(AuthService); authService = inject(AuthService);
private user: User | undefined;
// I am subscribing to the observable instead of using toSignal(),
// i have to destroy the subscription manually.
destroyRef = inject(DestroyRef);
protected addresses = signal<AddressResponse[]>([]); protected addresses = signal<AddressResponse[]>([]);
protected addressIdControl = new FormControl<number | null>(null, Validators.required);
private user: User | undefined;
ngOnInit(): void { ngOnInit(): void {
this.authService this.authService
@ -33,8 +41,9 @@ export class Address implements OnInit {
if (user?.id) { if (user?.id) {
return this.addressService.fetchAddresses(user.id); return this.addressService.fetchAddresses(user.id);
} }
return []; return of({ data: [] });
}), }),
takeUntilDestroyed(this.destroyRef),
) )
.subscribe({ .subscribe({
next: (addresses) => { next: (addresses) => {
@ -43,12 +52,12 @@ export class Address implements OnInit {
}); });
} }
createNewAddress(addressData: AddressRequest) { protected createNewAddress(addressData: AddressRequest) {
this.addressService.createAddress(this.user!.id, addressData).subscribe({ this.addressService.createAddress(this.user!.id, addressData).subscribe({
next: (address) => this.addresses.update((addresses) => [...addresses, address]), next: (address) => this.addresses.update((addresses) => [...addresses, address]),
}); });
} }
updateAddress(addressData: AddressResponse) { protected updateAddress(addressData: AddressResponse) {
console.log(addressData); console.log(addressData);
this.addressService.updateAddress(addressData.id, addressData).subscribe({ this.addressService.updateAddress(addressData.id, addressData).subscribe({
next: (address) => next: (address) =>
@ -57,4 +66,11 @@ export class Address implements OnInit {
), ),
}); });
} }
protected proceedToPayment() {
if (this.addressIdControl.invalid) {
this.addressIdControl.markAsTouched();
return;
}
}
} }

View File

@ -1,4 +1,8 @@
<details class="card p-0!" title="Click to add a new address"> <details
class="card p-0!"
title="Click to add a new address"
[open]="isEditing()"
>
<summary class="p-6"> <summary class="p-6">
<label for="currentAddress" class="font-medium text-gray-600 ml-2" <label for="currentAddress" class="font-medium text-gray-600 ml-2"
>{{isEditing() ? 'Update address' : 'Add new address'}}</label >{{isEditing() ? 'Update address' : 'Add new address'}}</label

View File

@ -1,7 +1,6 @@
@if (!isEditing()) { @if (!isEditing()) {
<div class="flex justify-between card"> <div class="flex justify-between card">
<div class="flex space-x-4 items-center"> <div class="flex space-x-4 items-center">
<input type="radio" name="address" />
<p class="text-gray-600 font-medium">{{[address.firstName, address.lastName] | fullname}}</p> <p class="text-gray-600 font-medium">{{[address.firstName, address.lastName] | fullname}}</p>
<p class="text-gray-400 text-sm"> <p class="text-gray-400 text-sm">
{{`${address.street}, ${address.city}, ${address.pinCode}`}} {{`${address.street}, ${address.city}, ${address.pinCode}`}}

View File

@ -1,3 +1,24 @@
<div class="card h-155"> <div class="card">
<p class="text-gray-600 font-medium">Order Summery</p> <p class="text-gray-800 font-medium text-xl">Order Summery</p>
@if (cartItems | async; as cart) { @for (item of cart.items; track item.id) {
<article
class="mt-4 pb-4 border-b border-b-gray-400 border-dashed flex justify-between items-center"
>
<div class="flex space-x-4">
<div class="w-15 h-15 rounded-lg aspect-square relative overflow-hidden">
<img ngSrc="{{item.image}}" fill class="object-cover" alt="product image" />
</div>
<article>
<h2>{{item.title}}</h2>
<p class="text-gray-600 text-sm">Rs. {{item.price}} x {{item.quantity}}</p>
</article>
</div>
<p class="text-gray-800 font-medium">Rs. {{item.subtotal}}</p>
</article>
}
<article class="mt-4 flex justify-between items-center text-lg">
<p class="text-gray-800 font-medium">Total</p>
<p class="text-gray-800 font-medium">Rs. {{cart.totalPrice}}</p>
</article>
}
</div> </div>

View File

@ -1,9 +1,19 @@
import { Component } from "@angular/core"; import { Component, inject, OnInit } from "@angular/core";
import { CartService } from "@core/services/cart-service";
import { CartModel } from "@core/models/cart.model";
import { Observable } from "rxjs";
import { AsyncPipe, NgOptimizedImage } from "@angular/common";
@Component({ @Component({
selector: "app-order-summery", selector: "app-order-summery",
imports: [], imports: [AsyncPipe, NgOptimizedImage],
templateUrl: "./order-summery.html", templateUrl: "./order-summery.html",
styleUrl: "./order-summery.css", styleUrl: "./order-summery.css",
}) })
export class OrderSummery {} export class OrderSummery implements OnInit {
cartService = inject(CartService);
cartItems: Observable<CartModel> | undefined;
ngOnInit(): void {
this.cartItems = this.cartService.cartItems$;
}
}