From 9000ea0052d2968fbc54904b719c837a14cd1d98 Mon Sep 17 00:00:00 2001 From: kusowl Date: Mon, 9 Mar 2026 19:04:31 +0530 Subject: [PATCH 1/5] feature: fetch cart products from api - show total cart item count on header - fetch and show cart items on cart modal --- src/app/app.routes.ts | 1 - src/app/core/layouts/header/header.html | 16 +++++++- src/app/core/layouts/header/header.ts | 10 ++++- src/app/core/models/cart.model.ts | 15 +++++++ src/app/core/services/cart-service.spec.ts | 16 ++++++++ src/app/core/services/cart-service.ts | 39 +++++++++++++++++++ .../features/auth/services/auth-service.ts | 2 +- .../components/product-card/product-card.ts | 2 +- .../product/show-product/show-product.ts | 2 +- .../components/cart-item/cart-item.css} | 0 .../components/cart-item/cart-item.html | 24 ++++++++++++ .../components/cart-item/cart-item.spec.ts | 22 +++++++++++ .../shared/components/cart-item/cart-item.ts | 14 +++++++ src/app/shared/components/cart/cart.css | 0 src/app/shared/components/cart/cart.html | 18 +++++++++ src/app/shared/components/cart/cart.spec.ts | 22 +++++++++++ src/app/shared/components/cart/cart.ts | 17 ++++++++ .../favorite-button/favorite-button.css | 0 .../favorite-button/favorite-button.html | 0 .../favorite-button/favorite-button.spec.ts | 0 .../favorite-button/favorite-button.ts | 2 +- tsconfig.json | 9 ++++- 22 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 src/app/core/models/cart.model.ts create mode 100644 src/app/core/services/cart-service.spec.ts create mode 100644 src/app/core/services/cart-service.ts rename src/app/{src/app/shared/components/favorite-button/favorite-button.css => shared/components/cart-item/cart-item.css} (100%) create mode 100644 src/app/shared/components/cart-item/cart-item.html create mode 100644 src/app/shared/components/cart-item/cart-item.spec.ts create mode 100644 src/app/shared/components/cart-item/cart-item.ts create mode 100644 src/app/shared/components/cart/cart.css create mode 100644 src/app/shared/components/cart/cart.html create mode 100644 src/app/shared/components/cart/cart.spec.ts create mode 100644 src/app/shared/components/cart/cart.ts create mode 100644 src/app/shared/components/favorite-button/favorite-button.css rename src/app/{src/app => }/shared/components/favorite-button/favorite-button.html (100%) rename src/app/{src/app => }/shared/components/favorite-button/favorite-button.spec.ts (100%) rename src/app/{src/app => }/shared/components/favorite-button/favorite-button.ts (91%) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index c25961e..f6736f3 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -7,7 +7,6 @@ export const routes: Routes = [ { path: "", component: Home, - canActivate: [authGuard], }, { path: "", diff --git a/src/app/core/layouts/header/header.html b/src/app/core/layouts/header/header.html index 508f08a..cc34412 100644 --- a/src/app/core/layouts/header/header.html +++ b/src/app/core/layouts/header/header.html @@ -26,12 +26,18 @@ > - + + + diff --git a/src/app/core/layouts/header/header.ts b/src/app/core/layouts/header/header.ts index 3ec425e..e28db24 100644 --- a/src/app/core/layouts/header/header.ts +++ b/src/app/core/layouts/header/header.ts @@ -1,11 +1,13 @@ -import { Component, inject } from "@angular/core"; +import { Component, computed, inject } from "@angular/core"; import { LucideAngularModule, Search, ShoppingCart, User } from "lucide-angular"; import { RouterLink } from "@angular/router"; import { AuthService, AuthState } from "../../../features/auth/services/auth-service"; +import { CartService } from "@app/core/services/cart-service"; +import { Cart } from "@app/shared/components/cart/cart"; @Component({ selector: "app-header", - imports: [LucideAngularModule, RouterLink], + imports: [LucideAngularModule, RouterLink, Cart], templateUrl: "./header.html", styleUrl: "./header.css", }) @@ -14,5 +16,9 @@ export class Header { readonly CartIcon = ShoppingCart; readonly SearchIcon = Search; readonly authService = inject(AuthService); + readonly cartService = inject(CartService); protected readonly AuthState = AuthState; + + cartItem = this.cartService.cartItem; + cartItemCount = computed(() => this.cartItem().itemsCount ?? 0); } diff --git a/src/app/core/models/cart.model.ts b/src/app/core/models/cart.model.ts new file mode 100644 index 0000000..b37512f --- /dev/null +++ b/src/app/core/models/cart.model.ts @@ -0,0 +1,15 @@ +export interface CartItemModel { + id: number; + title: string; + quantity: number; + price: number; + subtotal: number; + image: string; +} + +export interface CartModel { + id: number; + itemsCount: number; + totalPrice: number; + items: CartItemModel[]; +} diff --git a/src/app/core/services/cart-service.spec.ts b/src/app/core/services/cart-service.spec.ts new file mode 100644 index 0000000..fa06308 --- /dev/null +++ b/src/app/core/services/cart-service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from "@angular/core/testing"; + +import { CartService } from "./cart-service"; + +describe("CartService", () => { + let service: CartService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(CartService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/core/services/cart-service.ts b/src/app/core/services/cart-service.ts new file mode 100644 index 0000000..3aba7d0 --- /dev/null +++ b/src/app/core/services/cart-service.ts @@ -0,0 +1,39 @@ +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { effect, inject, Injectable, signal } from "@angular/core"; +import { API_URL } from "../tokens/api-url-tokens"; +import { CartModel } from "../models/cart.model"; +import { AuthService, AuthState } from "@app/features/auth/services/auth-service"; + +@Injectable({ + providedIn: "root", +}) +export class CartService { + private authService = inject(AuthService); + // dependencies + private http = inject(HttpClient); + private apiUrl = inject(API_URL); + + cartItem = signal({} as CartModel); + + constructor() { + effect(() => { + if (this.authService.isAuthenticated()) { + this.fetchCart(); + } else { + this.cartItem.set({} as CartModel); + } + }); + } + + fetchCart() { + return this.http.get(this.apiUrl + "/cart").subscribe({ + next: (data) => this.cartItem.set(data), + error: (error: HttpErrorResponse) => { + if (error.status === 401) { + this.authService.purgeAuth(); + } + // show an error in toast + }, + }); + } +} diff --git a/src/app/features/auth/services/auth-service.ts b/src/app/features/auth/services/auth-service.ts index d973e8c..ca38560 100644 --- a/src/app/features/auth/services/auth-service.ts +++ b/src/app/features/auth/services/auth-service.ts @@ -104,7 +104,7 @@ export class AuthService { this.authState.set(AuthState.Authenticated); } - private purgeAuth() { + purgeAuth() { this.localStorage.removeItem(this.userKey); this.user.set(null); this.authState.set(AuthState.Unauthenticated); diff --git a/src/app/features/product/components/product-card/product-card.ts b/src/app/features/product/components/product-card/product-card.ts index 4f3e754..04e3263 100644 --- a/src/app/features/product/components/product-card/product-card.ts +++ b/src/app/features/product/components/product-card/product-card.ts @@ -1,7 +1,7 @@ import { Component, inject, Input } from "@angular/core"; import { ProductModel } from "../../../../core/models/product.model"; import { Router } from "@angular/router"; -import { FavoriteButton } from "../../../../src/app/shared/components/favorite-button/favorite-button"; +import { FavoriteButton } from "../../../../shared/components/favorite-button/favorite-button"; @Component({ selector: "app-product-card", diff --git a/src/app/features/product/show-product/show-product.ts b/src/app/features/product/show-product/show-product.ts index dd02023..881afb8 100644 --- a/src/app/features/product/show-product/show-product.ts +++ b/src/app/features/product/show-product/show-product.ts @@ -2,7 +2,7 @@ import { Component, inject, Input, signal, WritableSignal } from "@angular/core" import { ProductModel } from "../../../core/models/product.model"; import { ProductService } from "../services/product-service"; import { LucideAngularModule, Heart, ArrowRight, ArrowLeft } from "lucide-angular"; -import { FavoriteButton } from "../../../src/app/shared/components/favorite-button/favorite-button"; +import { FavoriteButton } from "../../../shared/components/favorite-button/favorite-button"; @Component({ selector: "app-show-product", diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.css b/src/app/shared/components/cart-item/cart-item.css similarity index 100% rename from src/app/src/app/shared/components/favorite-button/favorite-button.css rename to src/app/shared/components/cart-item/cart-item.css diff --git a/src/app/shared/components/cart-item/cart-item.html b/src/app/shared/components/cart-item/cart-item.html new file mode 100644 index 0000000..f7d33ca --- /dev/null +++ b/src/app/shared/components/cart-item/cart-item.html @@ -0,0 +1,24 @@ +
  • +
    +
    + +
    +
    +

    {{ cartItem.title }}

    +
    +

    Rs. {{ cartItem.price }} x {{ cartItem.quantity }}

    + +
    +
    +
    +

    Rs. {{ cartItem.subtotal }}

    +
    + +

    {{ cartItem.quantity }}

    + +
    +
    +
    +
  • diff --git a/src/app/shared/components/cart-item/cart-item.spec.ts b/src/app/shared/components/cart-item/cart-item.spec.ts new file mode 100644 index 0000000..f5e371e --- /dev/null +++ b/src/app/shared/components/cart-item/cart-item.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { CartItem } from "./cart-item"; + +describe("CartItem", () => { + let component: CartItem; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CartItem], + }).compileComponents(); + + fixture = TestBed.createComponent(CartItem); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/cart-item/cart-item.ts b/src/app/shared/components/cart-item/cart-item.ts new file mode 100644 index 0000000..404b364 --- /dev/null +++ b/src/app/shared/components/cart-item/cart-item.ts @@ -0,0 +1,14 @@ +import { Component, Input } from "@angular/core"; +import { CartItemModel } from "@app/core/models/cart.model"; +import { LucideAngularModule, Trash } from "lucide-angular"; + +@Component({ + selector: "app-cart-item", + imports: [LucideAngularModule], + templateUrl: "./cart-item.html", + styleUrl: "./cart-item.css", +}) +export class CartItem { + @Input() cartItem!: CartItemModel; + TrashIcon = Trash; +} diff --git a/src/app/shared/components/cart/cart.css b/src/app/shared/components/cart/cart.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/cart/cart.html b/src/app/shared/components/cart/cart.html new file mode 100644 index 0000000..b1aa159 --- /dev/null +++ b/src/app/shared/components/cart/cart.html @@ -0,0 +1,18 @@ +
      + @if (authService.authState() === AuthState.Unauthenticated) { +
    • Login to access cart
    • + } @else if (authService.authState() === AuthState.Loading) { +
    • Loading
    • + } @else { +
        + @for (item of cart.items; track item.id) { + + } +
      + +
      +

      Total

      +

      Rs. {{ cart.totalPrice }}

      +
      + } +
    diff --git a/src/app/shared/components/cart/cart.spec.ts b/src/app/shared/components/cart/cart.spec.ts new file mode 100644 index 0000000..0880605 --- /dev/null +++ b/src/app/shared/components/cart/cart.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { Cart } from "./cart"; + +describe("Cart", () => { + let component: Cart; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Cart], + }).compileComponents(); + + fixture = TestBed.createComponent(Cart); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/cart/cart.ts b/src/app/shared/components/cart/cart.ts new file mode 100644 index 0000000..03a9dde --- /dev/null +++ b/src/app/shared/components/cart/cart.ts @@ -0,0 +1,17 @@ +import { Component, computed, inject, Input } from "@angular/core"; +import { CartModel } from "@app/core/models/cart.model"; +import { CartItem } from "../cart-item/cart-item"; +import { AuthService, AuthState } from "@app/features/auth/services/auth-service"; + +@Component({ + selector: "app-cart", + imports: [CartItem], + templateUrl: "./cart.html", + styleUrl: "./cart.css", +}) +export class Cart { + @Input() cart!: CartModel; + + protected readonly authService = inject(AuthService); + protected readonly AuthState = AuthState; +} diff --git a/src/app/shared/components/favorite-button/favorite-button.css b/src/app/shared/components/favorite-button/favorite-button.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.html b/src/app/shared/components/favorite-button/favorite-button.html similarity index 100% rename from src/app/src/app/shared/components/favorite-button/favorite-button.html rename to src/app/shared/components/favorite-button/favorite-button.html diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts b/src/app/shared/components/favorite-button/favorite-button.spec.ts similarity index 100% rename from src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts rename to src/app/shared/components/favorite-button/favorite-button.spec.ts diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.ts b/src/app/shared/components/favorite-button/favorite-button.ts similarity index 91% rename from src/app/src/app/shared/components/favorite-button/favorite-button.ts rename to src/app/shared/components/favorite-button/favorite-button.ts index 733a074..71f95bd 100644 --- a/src/app/src/app/shared/components/favorite-button/favorite-button.ts +++ b/src/app/shared/components/favorite-button/favorite-button.ts @@ -1,6 +1,6 @@ import { Component, inject, Input } from "@angular/core"; import { HeartIcon, LucideAngularModule } from "lucide-angular"; -import { FavoriteService } from "../../../../../core/services/favorite-service"; +import { FavoriteService } from "../../../core/services/favorite-service"; @Component({ selector: "app-favorite-button", diff --git a/tsconfig.json b/tsconfig.json index 2ab7442..8929661 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,14 @@ "experimentalDecorators": true, "importHelpers": true, "target": "ES2022", - "module": "preserve" + "module": "preserve", + "baseUrl": "./src", // Paths are resolved relative to the baseUrl + "paths": { + "@app/*": ["app/*"], + "@shared/*": ["app/shared/*"], + "@core/*": ["app/core/*"], + "@env/*": ["environments/*"] + } }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, From 2b88cee10b7a830da4783c7d84866e9abb235bce Mon Sep 17 00:00:00 2001 From: kusowl Date: Tue, 10 Mar 2026 19:07:42 +0530 Subject: [PATCH 2/5] feature: user can change quantity and remove products --- src/app/core/models/cart.model.ts | 5 +++ src/app/core/services/cart-service.ts | 11 ++++++- .../components/cart-item/cart-item.html | 16 +++++----- .../shared/components/cart-item/cart-item.ts | 24 ++++++++++++-- src/app/shared/components/cart/cart.html | 12 +++++-- src/app/shared/components/cart/cart.ts | 31 +++++++++++++++++-- 6 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/app/core/models/cart.model.ts b/src/app/core/models/cart.model.ts index b37512f..dbccc0b 100644 --- a/src/app/core/models/cart.model.ts +++ b/src/app/core/models/cart.model.ts @@ -13,3 +13,8 @@ export interface CartModel { totalPrice: number; items: CartItemModel[]; } + +export interface CartItemRequest { + productId: number; + quantity: number; +} diff --git a/src/app/core/services/cart-service.ts b/src/app/core/services/cart-service.ts index 3aba7d0..1858e5c 100644 --- a/src/app/core/services/cart-service.ts +++ b/src/app/core/services/cart-service.ts @@ -1,8 +1,9 @@ import { HttpClient, HttpErrorResponse } from "@angular/common/http"; import { effect, inject, Injectable, signal } from "@angular/core"; import { API_URL } from "../tokens/api-url-tokens"; -import { CartModel } from "../models/cart.model"; +import { CartItemModel, CartItemRequest, CartModel } from "../models/cart.model"; import { AuthService, AuthState } from "@app/features/auth/services/auth-service"; +import { Cart } from "@app/shared/components/cart/cart"; @Injectable({ providedIn: "root", @@ -36,4 +37,12 @@ export class CartService { }, }); } + + updateCart(data: CartItemRequest) { + return this.http.patch(this.apiUrl + "/cart", data); + } + + removeFromCart(productId: number) { + return this.http.delete(this.apiUrl + "/cart", { body: { productId: productId } }); + } } diff --git a/src/app/shared/components/cart-item/cart-item.html b/src/app/shared/components/cart-item/cart-item.html index f7d33ca..d29f4e0 100644 --- a/src/app/shared/components/cart-item/cart-item.html +++ b/src/app/shared/components/cart-item/cart-item.html @@ -7,17 +7,19 @@

    {{ cartItem.title }}

    Rs. {{ cartItem.price }} x {{ cartItem.quantity }}

    -
    + +
    +
    +

    Rs. {{ cartItem.subtotal }}

    +
    -
    -
    -

    Rs. {{ cartItem.subtotal }}

    - -

    {{ cartItem.quantity }}

    - + +

    {{ cartItem.quantity }}

    +
    diff --git a/src/app/shared/components/cart-item/cart-item.ts b/src/app/shared/components/cart-item/cart-item.ts index 404b364..1a398be 100644 --- a/src/app/shared/components/cart-item/cart-item.ts +++ b/src/app/shared/components/cart-item/cart-item.ts @@ -1,5 +1,5 @@ -import { Component, Input } from "@angular/core"; -import { CartItemModel } from "@app/core/models/cart.model"; +import { Component, EventEmitter, Input, Output, signal } from "@angular/core"; +import { CartItemModel, CartItemRequest } from "@app/core/models/cart.model"; import { LucideAngularModule, Trash } from "lucide-angular"; @Component({ @@ -10,5 +10,25 @@ import { LucideAngularModule, Trash } from "lucide-angular"; }) export class CartItem { @Input() cartItem!: CartItemModel; + @Output() qtyChangeEvent = new EventEmitter(); + @Output() productDeleteEvent = new EventEmitter(); TrashIcon = Trash; + + incrementQty() { + if (this.cartItem.quantity < 10) { + this.cartItem.quantity += 1; + this.qtyChangeEvent.emit({ productId: this.cartItem.id, quantity: this.cartItem.quantity }); + } + } + + decrementQty() { + if (this.cartItem.quantity > 1) { + this.cartItem.quantity -= 1; + this.qtyChangeEvent.emit({ productId: this.cartItem.id, quantity: this.cartItem.quantity }); + } + } + + removeProduct() { + this.productDeleteEvent.emit(this.cartItem.id); + } } diff --git a/src/app/shared/components/cart/cart.html b/src/app/shared/components/cart/cart.html index b1aa159..df526c5 100644 --- a/src/app/shared/components/cart/cart.html +++ b/src/app/shared/components/cart/cart.html @@ -4,9 +4,17 @@ } @else if (authService.authState() === AuthState.Loading) {
  • Loading
  • } @else { -
      +
        @for (item of cart.items; track item.id) { - + }
      diff --git a/src/app/shared/components/cart/cart.ts b/src/app/shared/components/cart/cart.ts index 03a9dde..7286b4f 100644 --- a/src/app/shared/components/cart/cart.ts +++ b/src/app/shared/components/cart/cart.ts @@ -1,7 +1,9 @@ -import { Component, computed, inject, Input } from "@angular/core"; -import { CartModel } from "@app/core/models/cart.model"; +import { Component, computed, inject, Input, signal } from "@angular/core"; +import { CartItemModel, CartItemRequest, CartModel } from "@app/core/models/cart.model"; import { CartItem } from "../cart-item/cart-item"; import { AuthService, AuthState } from "@app/features/auth/services/auth-service"; +import { CartService } from "@app/core/services/cart-service"; +import { finalize, tap } from "rxjs"; @Component({ selector: "app-cart", @@ -12,6 +14,31 @@ import { AuthService, AuthState } from "@app/features/auth/services/auth-service export class Cart { @Input() cart!: CartModel; + isLoading = signal(false); + protected readonly authService = inject(AuthService); + protected readonly cartService = inject(CartService); protected readonly AuthState = AuthState; + + updateProductQty(cartItem: CartItemRequest) { + this.isLoading.set(true); + + this.cartService + .updateCart(cartItem) + .pipe(finalize(() => this.isLoading.set(false))) + .subscribe((cartdata) => { + this.cart = cartdata; + }); + } + + removeProduct(productId: number) { + this.isLoading.set(true); + + this.cartService + .removeFromCart(productId) + .pipe(finalize(() => this.isLoading.set(false))) + .subscribe((cartData) => { + this.cart = cartData; + }); + } } From 27a04c6458b4946d122467a498b73fd367e9ade5 Mon Sep 17 00:00:00 2001 From: kusowl Date: Wed, 11 Mar 2026 11:25:07 +0530 Subject: [PATCH 3/5] minor: some quick design and color changes make the dropdown hover color from gradient to simple gray shade make the button 3d change the product card design in home page add add to cart button in the home page change design of button ghost --- src/app/core/layouts/header/header.html | 2 +- .../components/product-card/product-card.html | 26 +++++++++++++------ .../components/product-card/product-card.ts | 4 ++- src/app/features/product/product.html | 2 +- .../product/show-product/show-product.html | 10 +++---- src/styles.css | 8 +++--- 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/app/core/layouts/header/header.html b/src/app/core/layouts/header/header.html index cc34412..c2c6a7e 100644 --- a/src/app/core/layouts/header/header.html +++ b/src/app/core/layouts/header/header.html @@ -3,7 +3,7 @@ class="bg-gray-50 wrapper py-4 flex gap-x-5 sm:gap-x-10 items-center shadow-lg shadow-gray-400/20" >
      - eKart + eKart
      diff --git a/src/app/features/product/components/product-card/product-card.html b/src/app/features/product/components/product-card/product-card.html index 73f1c6f..6a68712 100644 --- a/src/app/features/product/components/product-card/product-card.html +++ b/src/app/features/product/components/product-card/product-card.html @@ -5,15 +5,25 @@ class="absolute top-5 right-5" /> -
      +
      -

      {{ product.title }}

      -

      ⭐4.5

      -

      - Price: - {{ product.actualPrice }} - {{ product.listPrice }}/- -

      +
      +

      + {{ product.title }} +

      +

      ⭐4.5

      +
      +
      +
      +

      Rs. {{ product.listPrice }}

      +

      Rs. {{ product.actualPrice }}

      +
      +
      + +
      +
      diff --git a/src/app/features/product/components/product-card/product-card.ts b/src/app/features/product/components/product-card/product-card.ts index 04e3263..6abd996 100644 --- a/src/app/features/product/components/product-card/product-card.ts +++ b/src/app/features/product/components/product-card/product-card.ts @@ -2,15 +2,17 @@ import { Component, inject, Input } from "@angular/core"; import { ProductModel } from "../../../../core/models/product.model"; import { Router } from "@angular/router"; import { FavoriteButton } from "../../../../shared/components/favorite-button/favorite-button"; +import { LucideAngularModule, ShoppingCart } from "lucide-angular"; @Component({ selector: "app-product-card", standalone: true, - imports: [FavoriteButton], + imports: [FavoriteButton, LucideAngularModule], templateUrl: "./product-card.html", }) export class ProductCard { readonly router = inject(Router); + readonly ShoppingCartIcon = ShoppingCart; @Input() product!: ProductModel; diff --git a/src/app/features/product/product.html b/src/app/features/product/product.html index 67121c9..c94f682 100644 --- a/src/app/features/product/product.html +++ b/src/app/features/product/product.html @@ -4,7 +4,7 @@

      Our Product

      -
      +
      @for (product of products(); track product) { } diff --git a/src/app/features/product/show-product/show-product.html b/src/app/features/product/show-product/show-product.html index 8ccf6be..c90e589 100644 --- a/src/app/features/product/show-product/show-product.html +++ b/src/app/features/product/show-product/show-product.html @@ -23,15 +23,15 @@ />
      @@ -104,7 +104,7 @@ diff --git a/src/styles.css b/src/styles.css index 4ecbfd4..0f9fd15 100644 --- a/src/styles.css +++ b/src/styles.css @@ -25,15 +25,15 @@ body { } .btn-ghost { - @apply text-gray-600 border border-gray-300 hover:bg-gray-800 hover:text-gray-200; + @apply text-gray-600 border border-b-3 border-gray-300 hover:bg-gray-200/70 hover:text-gray-700; } .btn-black { - @apply text-gray-100 bg-gray-800 border border-gray-800 hover:bg-gray-200 hover:text-gray-800 hover:border-gray-400; + @apply text-gray-100 bg-gray-800 border border-b-3 border-gray-800 hover:bg-gray-200 hover:text-gray-800 hover:border-gray-400; } .btn-primary { - @apply text-gray-100 bg-blue-700 border border-blue-800 hover:bg-blue-200 hover:text-blue-800 hover:border-blue-400; + @apply text-blue-100 bg-blue-600 border border-b-3 border-blue-900 hover:bg-blue-700; } .card { @@ -70,7 +70,7 @@ body { } .dropdown li { - @apply rounded-lg hover:bg-linear-to-r hover:from-teal-300 hover:to-transparent px-5 py-1; + @apply rounded-lg hover:bg-linear-to-r hover:bg-gray-100 px-5 py-1; } h1, From ad957efcf0379d0e596fc7979a30c3264e1372f3 Mon Sep 17 00:00:00 2001 From: kusowl Date: Wed, 11 Mar 2026 19:00:24 +0530 Subject: [PATCH 4/5] feature: add to cart - make the cart service dependable on BehavorialSubject, migrated from siganls - implement add to cart service --- src/app/core/layouts/header/header.html | 4 ++-- src/app/core/layouts/header/header.ts | 11 +++++---- src/app/core/services/cart-service.ts | 23 ++++++++++++++---- .../components/product-card/product-card.html | 2 +- .../components/product-card/product-card.ts | 7 ++++++ .../product/show-product/show-product.html | 24 +++++-------------- .../product/show-product/show-product.ts | 7 ++++++ src/app/shared/components/cart/cart.ts | 8 ++----- 8 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/app/core/layouts/header/header.html b/src/app/core/layouts/header/header.html index c2c6a7e..3abd3eb 100644 --- a/src/app/core/layouts/header/header.html +++ b/src/app/core/layouts/header/header.html @@ -32,7 +32,7 @@ style="anchor-name: --anchor-2" > - {{ cartItemCount() }} + {{ cartItemCount | async }} @@ -53,7 +53,7 @@ this.cartItem().itemsCount ?? 0); + cartItem$ = this.cartService.cartItem$; + cartItemCount = this.cartItem$.pipe(map((cart: CartModel) => cart.itemsCount ?? 0)); } diff --git a/src/app/core/services/cart-service.ts b/src/app/core/services/cart-service.ts index 1858e5c..f58d354 100644 --- a/src/app/core/services/cart-service.ts +++ b/src/app/core/services/cart-service.ts @@ -4,6 +4,7 @@ import { API_URL } from "../tokens/api-url-tokens"; import { CartItemModel, CartItemRequest, CartModel } from "../models/cart.model"; import { AuthService, AuthState } from "@app/features/auth/services/auth-service"; import { Cart } from "@app/shared/components/cart/cart"; +import { BehaviorSubject, tap } from "rxjs"; @Injectable({ providedIn: "root", @@ -14,21 +15,23 @@ export class CartService { private http = inject(HttpClient); private apiUrl = inject(API_URL); - cartItem = signal({} as CartModel); + private _cartItem = new BehaviorSubject({} as CartModel); + + cartItem$ = this._cartItem.asObservable(); constructor() { effect(() => { if (this.authService.isAuthenticated()) { this.fetchCart(); } else { - this.cartItem.set({} as CartModel); + this._cartItem.next({} as CartModel); } }); } fetchCart() { return this.http.get(this.apiUrl + "/cart").subscribe({ - next: (data) => this.cartItem.set(data), + next: (data) => this._cartItem.next(data), error: (error: HttpErrorResponse) => { if (error.status === 401) { this.authService.purgeAuth(); @@ -38,11 +41,21 @@ export class CartService { }); } + addToCart(data: CartItemRequest) { + return this.http + .post(this.apiUrl + "/cart", data) + .pipe(tap((updatedCart: CartModel) => this._cartItem.next(updatedCart))); + } + updateCart(data: CartItemRequest) { - return this.http.patch(this.apiUrl + "/cart", data); + return this.http + .patch(this.apiUrl + "/cart", data) + .pipe(tap((updatedCart: CartModel) => this._cartItem.next(updatedCart))); } removeFromCart(productId: number) { - return this.http.delete(this.apiUrl + "/cart", { body: { productId: productId } }); + return this.http + .delete(this.apiUrl + "/cart", { body: { productId: productId } }) + .pipe(tap((updatedCart: CartModel) => this._cartItem.next(updatedCart))); } } diff --git a/src/app/features/product/components/product-card/product-card.html b/src/app/features/product/components/product-card/product-card.html index 6a68712..c4e3d1e 100644 --- a/src/app/features/product/components/product-card/product-card.html +++ b/src/app/features/product/components/product-card/product-card.html @@ -21,7 +21,7 @@

      Rs. {{ product.actualPrice }}

      -
      diff --git a/src/app/features/product/components/product-card/product-card.ts b/src/app/features/product/components/product-card/product-card.ts index 6abd996..c137804 100644 --- a/src/app/features/product/components/product-card/product-card.ts +++ b/src/app/features/product/components/product-card/product-card.ts @@ -3,6 +3,7 @@ import { ProductModel } from "../../../../core/models/product.model"; import { Router } from "@angular/router"; import { FavoriteButton } from "../../../../shared/components/favorite-button/favorite-button"; import { LucideAngularModule, ShoppingCart } from "lucide-angular"; +import { CartService } from "@app/core/services/cart-service"; @Component({ selector: "app-product-card", @@ -13,10 +14,16 @@ import { LucideAngularModule, ShoppingCart } from "lucide-angular"; export class ProductCard { readonly router = inject(Router); readonly ShoppingCartIcon = ShoppingCart; + readonly cartService = inject(CartService); @Input() product!: ProductModel; goToProductDetails() { this.router.navigate(["/products", this.product.slug]); } + + addToCart(event: Event) { + event.stopPropagation(); + this.cartService.addToCart({ productId: this.product.id, quantity: 1 }).subscribe(); + } } diff --git a/src/app/features/product/show-product/show-product.html b/src/app/features/product/show-product/show-product.html index c90e589..6809b38 100644 --- a/src/app/features/product/show-product/show-product.html +++ b/src/app/features/product/show-product/show-product.html @@ -90,28 +90,16 @@
      -
      - Rs.{{ product()?.actualPrice }} - Rs.{{ product()?.listPrice }} - +
      +

      + Rs.{{ product()?.listPrice }} +

      +

      Rs.{{ product()?.actualPrice }}

      -
      - - - -
      -
      - +
      diff --git a/src/app/features/product/show-product/show-product.ts b/src/app/features/product/show-product/show-product.ts index 881afb8..c923c86 100644 --- a/src/app/features/product/show-product/show-product.ts +++ b/src/app/features/product/show-product/show-product.ts @@ -3,6 +3,7 @@ import { ProductModel } from "../../../core/models/product.model"; import { ProductService } from "../services/product-service"; import { LucideAngularModule, Heart, ArrowRight, ArrowLeft } from "lucide-angular"; import { FavoriteButton } from "../../../shared/components/favorite-button/favorite-button"; +import { CartService } from "@app/core/services/cart-service"; @Component({ selector: "app-show-product", @@ -17,6 +18,7 @@ export class ShowProduct { ArrowRightIcon = ArrowRight; ArrowLeftIcon = ArrowLeft; productService = inject(ProductService); + cartService = inject(CartService); product = signal(null); activeImageIndex: WritableSignal = signal(0); totalImageCount: number = 0; @@ -29,9 +31,14 @@ export class ShowProduct { }); } + addToCart() { + this.cartService.addToCart({ productId: this.product()!.id, quantity: 1 }).subscribe(); + } + nextImage() { this.activeImageIndex.update((index) => (index + 1) % this.totalImageCount); } + prevImage() { this.activeImageIndex.update( (index) => diff --git a/src/app/shared/components/cart/cart.ts b/src/app/shared/components/cart/cart.ts index 7286b4f..121b0ef 100644 --- a/src/app/shared/components/cart/cart.ts +++ b/src/app/shared/components/cart/cart.ts @@ -26,9 +26,7 @@ export class Cart { this.cartService .updateCart(cartItem) .pipe(finalize(() => this.isLoading.set(false))) - .subscribe((cartdata) => { - this.cart = cartdata; - }); + .subscribe(); } removeProduct(productId: number) { @@ -37,8 +35,6 @@ export class Cart { this.cartService .removeFromCart(productId) .pipe(finalize(() => this.isLoading.set(false))) - .subscribe((cartData) => { - this.cart = cartData; - }); + .subscribe(); } } From 50c956c051dc4b2d11aaf72adc8fca753fbade69 Mon Sep 17 00:00:00 2001 From: kusowl Date: Thu, 12 Mar 2026 10:43:38 +0530 Subject: [PATCH 5/5] fix: make header logo navigation by router --- src/app/core/layouts/header/header.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/core/layouts/header/header.html b/src/app/core/layouts/header/header.html index 3abd3eb..70f585c 100644 --- a/src/app/core/layouts/header/header.html +++ b/src/app/core/layouts/header/header.html @@ -3,7 +3,7 @@ class="bg-gray-50 wrapper py-4 flex gap-x-5 sm:gap-x-10 items-center shadow-lg shadow-gray-400/20" >
      - eKart + eKart
      @@ -40,11 +40,11 @@