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,