From f5393f51100666d825beea579830527ccd45f31e Mon Sep 17 00:00:00 2001 From: kusowl Date: Mon, 2 Mar 2026 18:47:43 +0530 Subject: [PATCH 1/3] feature: show products on the home page and add individual product page --- src/app/app.config.ts | 4 +- src/app/app.routes.ts | 2 +- src/app/app.ts | 2 +- src/app/core/layouts/footer/footer.html | 2 +- src/app/core/models/paginated.model.ts | 25 ++++ src/app/core/models/product.model.ts | 16 +++ src/app/features/home/home.ts | 4 +- .../componets/product-card/product-card.html | 17 --- .../componets/product-card/product-card.ts | 12 -- src/app/features/home/products/products.html | 14 -- src/app/features/home/products/products.ts | 9 -- .../product/add-product/add-product.html | 4 +- .../components/product-card/product-card.html | 21 +++ .../product-card/product-card.spec.ts | 0 .../components/product-card/product-card.ts | 22 +++ src/app/features/product/product.html | 12 +- src/app/features/product/product.routes.ts | 5 + src/app/features/product/product.ts | 23 ++- .../product/services/category-service.ts | 2 +- .../product/services/product-service.spec.ts | 16 +++ .../product/services/product-service.ts | 21 +++ .../show-product.css} | 0 .../product/show-product/show-product.html | 132 ++++++++++++++++++ .../show-product/show-product.spec.ts} | 12 +- .../product/show-product/show-product.ts | 40 ++++++ src/styles.css | 4 + 26 files changed, 347 insertions(+), 74 deletions(-) create mode 100644 src/app/core/models/paginated.model.ts create mode 100644 src/app/core/models/product.model.ts delete mode 100644 src/app/features/home/products/componets/product-card/product-card.html delete mode 100644 src/app/features/home/products/componets/product-card/product-card.ts delete mode 100644 src/app/features/home/products/products.html delete mode 100644 src/app/features/home/products/products.ts create mode 100644 src/app/features/product/components/product-card/product-card.html rename src/app/features/{home/products/componets => product/components}/product-card/product-card.spec.ts (100%) create mode 100644 src/app/features/product/components/product-card/product-card.ts create mode 100644 src/app/features/product/services/product-service.spec.ts create mode 100644 src/app/features/product/services/product-service.ts rename src/app/features/product/{product.css => show-product/show-product.css} (100%) create mode 100644 src/app/features/product/show-product/show-product.html rename src/app/features/{home/products/products.spec.ts => product/show-product/show-product.spec.ts} (58%) create mode 100644 src/app/features/product/show-product/show-product.ts diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 17362d3..0c81531 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,5 +1,5 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from "@angular/core"; -import { provideRouter } from "@angular/router"; +import { provideRouter, withComponentInputBinding } from "@angular/router"; import { routes } from "./app.routes"; import { provideHttpClient, withFetch, withInterceptors } from "@angular/common/http"; @@ -8,7 +8,7 @@ import { csrfInterceptor } from "./core/interceptors/csrf-interceptor"; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), - provideRouter(routes), + provideRouter(routes, withComponentInputBinding()), provideHttpClient(withFetch(), withInterceptors([csrfInterceptor])), ], }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index fe77d90..c25961e 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -18,6 +18,6 @@ export const routes: Routes = [ loadChildren: () => import("./features/product/product.routes").then((routes) => routes.productRoutes), canActivate: [authGuard, roleGuard], - data: { roles: ["broker"] }, + data: { roles: ["admin", "broker"] }, }, ]; diff --git a/src/app/app.ts b/src/app/app.ts index 780b269..9eb83b9 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,6 +1,6 @@ import { Component, signal } from "@angular/core"; import { RouterOutlet } from "@angular/router"; -import { Products } from "./features/home/products/products"; +import { Product } from "./features/product/product"; import { Footer } from "./core/layouts/footer/footer"; import { Header } from "./core/layouts/header/header"; diff --git a/src/app/core/layouts/footer/footer.html b/src/app/core/layouts/footer/footer.html index 068c3d5..aa791bb 100644 --- a/src/app/core/layouts/footer/footer.html +++ b/src/app/core/layouts/footer/footer.html @@ -18,7 +18,7 @@ diff --git a/src/app/core/models/paginated.model.ts b/src/app/core/models/paginated.model.ts new file mode 100644 index 0000000..9dfb658 --- /dev/null +++ b/src/app/core/models/paginated.model.ts @@ -0,0 +1,25 @@ +export interface PaginatedResponse { + data: T[]; + links: { + next: string | null; + prev: string | null; + last: string | null; + first: string | null; + }; + meta: { + total: number; + per_page: number; + current_page: number; + last_page: number; + from: number; + to: number; + links: links[]; + }; +} + +interface links { + url: string | null; + label: string; + active: boolean; + page: number | null; +} diff --git a/src/app/core/models/product.model.ts b/src/app/core/models/product.model.ts new file mode 100644 index 0000000..b509785 --- /dev/null +++ b/src/app/core/models/product.model.ts @@ -0,0 +1,16 @@ +import { PaginatedResponse } from "./paginated.model"; +import { Category } from "../../features/product/services/category-service"; + +export interface ProductModel { + id: number; + title: string; + slug: string; + description: string; + actualPrice: number; + listPrice: number; + category: Category; + productImages: string[]; + updatedAt: string; +} + +export interface ProductCollection extends PaginatedResponse {} diff --git a/src/app/features/home/home.ts b/src/app/features/home/home.ts index cbc2461..f5e5889 100644 --- a/src/app/features/home/home.ts +++ b/src/app/features/home/home.ts @@ -1,9 +1,9 @@ import { Component } from "@angular/core"; -import { Products } from "./products/products"; +import { Product } from "../product/product"; @Component({ selector: "app-home", - imports: [Products], + imports: [Product], templateUrl: "./home.html", styleUrl: "./home.css", }) diff --git a/src/app/features/home/products/componets/product-card/product-card.html b/src/app/features/home/products/componets/product-card/product-card.html deleted file mode 100644 index 7e8b73d..0000000 --- a/src/app/features/home/products/componets/product-card/product-card.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - - - -
- -
- -

Product Name

-

⭐4.5

-

Price: 4999/-

-
diff --git a/src/app/features/home/products/componets/product-card/product-card.ts b/src/app/features/home/products/componets/product-card/product-card.ts deleted file mode 100644 index b609c53..0000000 --- a/src/app/features/home/products/componets/product-card/product-card.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; -import { LucideAngularModule, Heart } from "lucide-angular"; - -@Component({ - selector: "app-product-card", - standalone: true, - imports: [LucideAngularModule], - templateUrl: "./product-card.html", -}) -export class ProductCard { - readonly HeartIcon = Heart; -} diff --git a/src/app/features/home/products/products.html b/src/app/features/home/products/products.html deleted file mode 100644 index c9a866f..0000000 --- a/src/app/features/home/products/products.html +++ /dev/null @@ -1,14 +0,0 @@ -
-

Our Products

-
- -
- - - - - - -
diff --git a/src/app/features/home/products/products.ts b/src/app/features/home/products/products.ts deleted file mode 100644 index ef2c403..0000000 --- a/src/app/features/home/products/products.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from "@angular/core"; -import { ProductCard } from "./componets/product-card/product-card"; - -@Component({ - selector: "app-products", - imports: [ProductCard], - templateUrl: "./products.html", -}) -export class Products {} diff --git a/src/app/features/product/add-product/add-product.html b/src/app/features/product/add-product/add-product.html index fc29349..a7afa68 100644 --- a/src/app/features/product/add-product/add-product.html +++ b/src/app/features/product/add-product/add-product.html @@ -62,8 +62,8 @@ Select category diff --git a/src/app/features/product/components/product-card/product-card.html b/src/app/features/product/components/product-card/product-card.html new file mode 100644 index 0000000..43e1fff --- /dev/null +++ b/src/app/features/product/components/product-card/product-card.html @@ -0,0 +1,21 @@ +
+ + + + +
+ +
+ +

{{product.title}}

+

⭐4.5

+

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

+
diff --git a/src/app/features/home/products/componets/product-card/product-card.spec.ts b/src/app/features/product/components/product-card/product-card.spec.ts similarity index 100% rename from src/app/features/home/products/componets/product-card/product-card.spec.ts rename to src/app/features/product/components/product-card/product-card.spec.ts diff --git a/src/app/features/product/components/product-card/product-card.ts b/src/app/features/product/components/product-card/product-card.ts new file mode 100644 index 0000000..edcd9e0 --- /dev/null +++ b/src/app/features/product/components/product-card/product-card.ts @@ -0,0 +1,22 @@ +import { Component, inject, Input } from "@angular/core"; +import { LucideAngularModule, Heart } from "lucide-angular"; +import { ProductModel } from "../../../../core/models/product.model"; +import { BACKEND_URL } from "../../../../core/tokens/api-url-tokens"; +import { Router } from "@angular/router"; + +@Component({ + selector: "app-product-card", + standalone: true, + imports: [LucideAngularModule], + templateUrl: "./product-card.html", +}) +export class ProductCard { + readonly HeartIcon = Heart; + readonly router = inject(Router); + + @Input() product!: ProductModel; + + goToProductDetails() { + this.router.navigate(["/products", this.product.slug]); + } +} diff --git a/src/app/features/product/product.html b/src/app/features/product/product.html index 772b623..67121c9 100644 --- a/src/app/features/product/product.html +++ b/src/app/features/product/product.html @@ -1 +1,11 @@ -

product works!

+
+

Our Product

+
+ +
+ @for (product of products(); track product) { + + } +
diff --git a/src/app/features/product/product.routes.ts b/src/app/features/product/product.routes.ts index c11716c..bd85c26 100644 --- a/src/app/features/product/product.routes.ts +++ b/src/app/features/product/product.routes.ts @@ -1,9 +1,14 @@ import { Routes } from "@angular/router"; import { AddProduct } from "./add-product/add-product"; +import { ShowProduct } from "./show-product/show-product"; export const productRoutes: Routes = [ { path: "create", component: AddProduct, }, + { + path: ":slug", + component: ShowProduct, + }, ]; diff --git a/src/app/features/product/product.ts b/src/app/features/product/product.ts index 9c169a0..aade0bf 100644 --- a/src/app/features/product/product.ts +++ b/src/app/features/product/product.ts @@ -1,9 +1,22 @@ -import { Component } from "@angular/core"; +import { Component, inject, signal } from "@angular/core"; +import { ProductCard } from "./components/product-card/product-card"; +import { HttpClient } from "@angular/common/http"; +import { API_URL } from "../../core/tokens/api-url-tokens"; +import { ProductModel } from "../../core/models/product.model"; +import { ProductService } from "./services/product-service"; @Component({ - selector: "app-product", - imports: [], + selector: "app-products", + imports: [ProductCard], templateUrl: "./product.html", - styleUrl: "./product.css", }) -export class Product {} +export class Product { + productService = inject(ProductService); + products = signal([]); + + ngOnInit() { + this.productService.getProducts().subscribe((data) => { + this.products.set(data.data); + }); + } +} diff --git a/src/app/features/product/services/category-service.ts b/src/app/features/product/services/category-service.ts index 02319a8..7ffc3de 100644 --- a/src/app/features/product/services/category-service.ts +++ b/src/app/features/product/services/category-service.ts @@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http"; import { API_URL } from "../../../core/tokens/api-url-tokens"; export interface Category { - id: number; + id?: number; name: string; slug: string; } diff --git a/src/app/features/product/services/product-service.spec.ts b/src/app/features/product/services/product-service.spec.ts new file mode 100644 index 0000000..dae1d10 --- /dev/null +++ b/src/app/features/product/services/product-service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from "@angular/core/testing"; + +import { ProductService } from "./product-service"; + +describe("ProductService", () => { + let service: ProductService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ProductService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/features/product/services/product-service.ts b/src/app/features/product/services/product-service.ts new file mode 100644 index 0000000..df0123c --- /dev/null +++ b/src/app/features/product/services/product-service.ts @@ -0,0 +1,21 @@ +import { inject, Injectable } from "@angular/core"; +import { HttpClient } from "@angular/common/http"; +import { API_URL } from "../../../core/tokens/api-url-tokens"; +import { ProductCollection, ProductModel } from "../../../core/models/product.model"; +import { map } from "rxjs"; + +@Injectable({ + providedIn: "root", +}) +export class ProductService { + http = inject(HttpClient); + apiUrl = inject(API_URL); + + getProducts() { + return this.http.get(`${this.apiUrl}/products`); + } + + getProduct(slug: string) { + return this.http.get<{ data: ProductModel }>(`${this.apiUrl}/products/${slug}`); + } +} diff --git a/src/app/features/product/product.css b/src/app/features/product/show-product/show-product.css similarity index 100% rename from src/app/features/product/product.css rename to src/app/features/product/show-product/show-product.css diff --git a/src/app/features/product/show-product/show-product.html b/src/app/features/product/show-product/show-product.html new file mode 100644 index 0000000..cd333a5 --- /dev/null +++ b/src/app/features/product/show-product/show-product.html @@ -0,0 +1,132 @@ +
+
+ + +
+
+
+ Product Image + + + +
+ +
+ @for (image of product()?.productImages; track image) { + + + } +
+
+ +
+
+

{{ product()?.title }}

+ + + +
+
+ +
+ 4.8 + + 112 Reviews +
+
+
+

Product Description

+

{{ product()?.description }}

+
+ +
+

Customer Reviews

+
    +
  • + "Rich in condensed nutrients using daily." + +
  • +
  • "Seedless & Eco-Peel it"
  • +
  • + "Longer Shelf Life has encounant" +
  • +
+
+
+ +
+
+
+
+ Rs.{{ product()?.actualPrice }} + Rs.{{ product()?.listPrice }} + +
+ +
+ +
+ + + +
+ +
+ + +
+
+
+
+
+
diff --git a/src/app/features/home/products/products.spec.ts b/src/app/features/product/show-product/show-product.spec.ts similarity index 58% rename from src/app/features/home/products/products.spec.ts rename to src/app/features/product/show-product/show-product.spec.ts index 0129469..31465ea 100644 --- a/src/app/features/home/products/products.spec.ts +++ b/src/app/features/product/show-product/show-product.spec.ts @@ -1,17 +1,17 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { Products } from "./products"; +import { ShowProduct } from "./show-product"; -describe("Products", () => { - let component: Products; - let fixture: ComponentFixture; +describe("ShowProduct", () => { + let component: ShowProduct; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [Products], + imports: [ShowProduct], }).compileComponents(); - fixture = TestBed.createComponent(Products); + fixture = TestBed.createComponent(ShowProduct); component = fixture.componentInstance; await fixture.whenStable(); }); diff --git a/src/app/features/product/show-product/show-product.ts b/src/app/features/product/show-product/show-product.ts new file mode 100644 index 0000000..3581d38 --- /dev/null +++ b/src/app/features/product/show-product/show-product.ts @@ -0,0 +1,40 @@ +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"; + +@Component({ + selector: "app-show-product", + imports: [LucideAngularModule], + templateUrl: "./show-product.html", + styleUrl: "./show-product.css", +}) +export class ShowProduct { + @Input() slug?: string; + + HeartIcon = Heart; + ArrowRightIcon = ArrowRight; + ArrowLeftIcon = ArrowLeft; + productService = inject(ProductService); + product = signal(null); + activeImageIndex: WritableSignal = signal(0); + totalImageCount: number = 0; + + ngOnInit() { + if (!this.slug) return; + this.productService.getProduct(this.slug).subscribe((data) => { + this.product.set(data.data); + this.totalImageCount = this.product()?.productImages?.length || 0; + }); + } + + nextImage() { + this.activeImageIndex.update((index) => (index + 1) % this.totalImageCount); + } + prevImage() { + this.activeImageIndex.update( + (index) => + (index - 1 + this.product()?.productImages.length!) % this.product()?.productImages.length!, + ); + } +} diff --git a/src/styles.css b/src/styles.css index b38fa58..4ecbfd4 100644 --- a/src/styles.css +++ b/src/styles.css @@ -32,6 +32,10 @@ body { @apply text-gray-100 bg-gray-800 border 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; +} + .card { @apply bg-gray-50 rounded-xl border border-gray-300 hover:border-gray-400 p-4 hover:shadow-xl transition-all duration-300 ease-in-out; } From 553637d8e23425dc3e0ed2e3eefd1230b0a0b5ac Mon Sep 17 00:00:00 2001 From: kusowl Date: Tue, 3 Mar 2026 17:27:30 +0530 Subject: [PATCH 2/3] fix: commit whole changes --- .../product/product.css => .ai/mcp/mcp.json | 0 angular.json | 3 +- src/app/app.config.ts | 4 +- src/app/app.routes.ts | 2 +- src/app/app.ts | 2 +- src/app/core/layouts/footer/footer.html | 2 +- src/app/core/models/paginated.model.ts | 25 ++++ src/app/core/models/product.model.ts | 16 +++ .../core/services/favorite-service.spec.ts | 16 +++ src/app/core/services/favorite-service.ts | 20 +++ src/app/features/home/home.ts | 4 +- .../componets/product-card/product-card.html | 17 --- .../componets/product-card/product-card.ts | 12 -- src/app/features/home/products/products.html | 14 -- src/app/features/home/products/products.ts | 9 -- .../product/add-product/add-product.html | 4 +- .../components/product-card/product-card.html | 15 ++ .../product-card/product-card.spec.ts | 0 .../components/product-card/product-card.ts | 20 +++ src/app/features/product/product.html | 12 +- src/app/features/product/product.routes.ts | 5 + src/app/features/product/product.ts | 23 ++- .../product/services/category-service.ts | 2 +- .../product/services/product-service.spec.ts | 16 +++ .../product/services/product-service.ts | 20 +++ .../product/show-product/show-product.css | 0 .../product/show-product/show-product.html | 132 ++++++++++++++++++ .../show-product/show-product.spec.ts} | 12 +- .../product/show-product/show-product.ts | 40 ++++++ .../favorite-button/favorite-button.css | 0 .../favorite-button/favorite-button.html | 7 + .../favorite-button/favorite-button.spec.ts | 23 +++ .../favorite-button/favorite-button.ts | 34 +++++ src/styles.css | 4 + 34 files changed, 440 insertions(+), 75 deletions(-) rename src/app/features/product/product.css => .ai/mcp/mcp.json (100%) create mode 100644 src/app/core/models/paginated.model.ts create mode 100644 src/app/core/models/product.model.ts create mode 100644 src/app/core/services/favorite-service.spec.ts create mode 100644 src/app/core/services/favorite-service.ts delete mode 100644 src/app/features/home/products/componets/product-card/product-card.html delete mode 100644 src/app/features/home/products/componets/product-card/product-card.ts delete mode 100644 src/app/features/home/products/products.html delete mode 100644 src/app/features/home/products/products.ts create mode 100644 src/app/features/product/components/product-card/product-card.html rename src/app/features/{home/products/componets => product/components}/product-card/product-card.spec.ts (100%) create mode 100644 src/app/features/product/components/product-card/product-card.ts create mode 100644 src/app/features/product/services/product-service.spec.ts create mode 100644 src/app/features/product/services/product-service.ts create mode 100644 src/app/features/product/show-product/show-product.css create mode 100644 src/app/features/product/show-product/show-product.html rename src/app/features/{home/products/products.spec.ts => product/show-product/show-product.spec.ts} (58%) create mode 100644 src/app/features/product/show-product/show-product.ts create mode 100644 src/app/src/app/shared/components/favorite-button/favorite-button.css create mode 100644 src/app/src/app/shared/components/favorite-button/favorite-button.html create mode 100644 src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts create mode 100644 src/app/src/app/shared/components/favorite-button/favorite-button.ts diff --git a/src/app/features/product/product.css b/.ai/mcp/mcp.json similarity index 100% rename from src/app/features/product/product.css rename to .ai/mcp/mcp.json diff --git a/angular.json b/angular.json index 77a0468..2131bae 100644 --- a/angular.json +++ b/angular.json @@ -2,7 +2,8 @@ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "cli": { - "packageManager": "npm" + "packageManager": "npm", + "analytics": false }, "newProjectRoot": "projects", "projects": { diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 17362d3..0c81531 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,5 +1,5 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from "@angular/core"; -import { provideRouter } from "@angular/router"; +import { provideRouter, withComponentInputBinding } from "@angular/router"; import { routes } from "./app.routes"; import { provideHttpClient, withFetch, withInterceptors } from "@angular/common/http"; @@ -8,7 +8,7 @@ import { csrfInterceptor } from "./core/interceptors/csrf-interceptor"; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), - provideRouter(routes), + provideRouter(routes, withComponentInputBinding()), provideHttpClient(withFetch(), withInterceptors([csrfInterceptor])), ], }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index fe77d90..c25961e 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -18,6 +18,6 @@ export const routes: Routes = [ loadChildren: () => import("./features/product/product.routes").then((routes) => routes.productRoutes), canActivate: [authGuard, roleGuard], - data: { roles: ["broker"] }, + data: { roles: ["admin", "broker"] }, }, ]; diff --git a/src/app/app.ts b/src/app/app.ts index 780b269..9eb83b9 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,6 +1,6 @@ import { Component, signal } from "@angular/core"; import { RouterOutlet } from "@angular/router"; -import { Products } from "./features/home/products/products"; +import { Product } from "./features/product/product"; import { Footer } from "./core/layouts/footer/footer"; import { Header } from "./core/layouts/header/header"; diff --git a/src/app/core/layouts/footer/footer.html b/src/app/core/layouts/footer/footer.html index 068c3d5..aa791bb 100644 --- a/src/app/core/layouts/footer/footer.html +++ b/src/app/core/layouts/footer/footer.html @@ -18,7 +18,7 @@ diff --git a/src/app/core/models/paginated.model.ts b/src/app/core/models/paginated.model.ts new file mode 100644 index 0000000..9dfb658 --- /dev/null +++ b/src/app/core/models/paginated.model.ts @@ -0,0 +1,25 @@ +export interface PaginatedResponse { + data: T[]; + links: { + next: string | null; + prev: string | null; + last: string | null; + first: string | null; + }; + meta: { + total: number; + per_page: number; + current_page: number; + last_page: number; + from: number; + to: number; + links: links[]; + }; +} + +interface links { + url: string | null; + label: string; + active: boolean; + page: number | null; +} diff --git a/src/app/core/models/product.model.ts b/src/app/core/models/product.model.ts new file mode 100644 index 0000000..b509785 --- /dev/null +++ b/src/app/core/models/product.model.ts @@ -0,0 +1,16 @@ +import { PaginatedResponse } from "./paginated.model"; +import { Category } from "../../features/product/services/category-service"; + +export interface ProductModel { + id: number; + title: string; + slug: string; + description: string; + actualPrice: number; + listPrice: number; + category: Category; + productImages: string[]; + updatedAt: string; +} + +export interface ProductCollection extends PaginatedResponse {} diff --git a/src/app/core/services/favorite-service.spec.ts b/src/app/core/services/favorite-service.spec.ts new file mode 100644 index 0000000..9bf2abe --- /dev/null +++ b/src/app/core/services/favorite-service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { FavoriteService } from './favorite-service'; + +describe('FavoriteService', () => { + let service: FavoriteService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(FavoriteService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/core/services/favorite-service.ts b/src/app/core/services/favorite-service.ts new file mode 100644 index 0000000..0df02da --- /dev/null +++ b/src/app/core/services/favorite-service.ts @@ -0,0 +1,20 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { API_URL } from '../tokens/api-url-tokens'; + +export interface FavoriteResponse { + message: string; + isFavorite: boolean; +} + +@Injectable({ + providedIn: 'root', +}) +export class FavoriteService { + http = inject(HttpClient); + apiUrl = inject(API_URL); + + toggle(productId: number) { + return this.http.post(`${this.apiUrl}/products/${productId}/favorite`, {}); + } +} diff --git a/src/app/features/home/home.ts b/src/app/features/home/home.ts index cbc2461..f5e5889 100644 --- a/src/app/features/home/home.ts +++ b/src/app/features/home/home.ts @@ -1,9 +1,9 @@ import { Component } from "@angular/core"; -import { Products } from "./products/products"; +import { Product } from "../product/product"; @Component({ selector: "app-home", - imports: [Products], + imports: [Product], templateUrl: "./home.html", styleUrl: "./home.css", }) diff --git a/src/app/features/home/products/componets/product-card/product-card.html b/src/app/features/home/products/componets/product-card/product-card.html deleted file mode 100644 index 7e8b73d..0000000 --- a/src/app/features/home/products/componets/product-card/product-card.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - - - -
- -
- -

Product Name

-

⭐4.5

-

Price: 4999/-

-
diff --git a/src/app/features/home/products/componets/product-card/product-card.ts b/src/app/features/home/products/componets/product-card/product-card.ts deleted file mode 100644 index b609c53..0000000 --- a/src/app/features/home/products/componets/product-card/product-card.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; -import { LucideAngularModule, Heart } from "lucide-angular"; - -@Component({ - selector: "app-product-card", - standalone: true, - imports: [LucideAngularModule], - templateUrl: "./product-card.html", -}) -export class ProductCard { - readonly HeartIcon = Heart; -} diff --git a/src/app/features/home/products/products.html b/src/app/features/home/products/products.html deleted file mode 100644 index c9a866f..0000000 --- a/src/app/features/home/products/products.html +++ /dev/null @@ -1,14 +0,0 @@ -
-

Our Products

-
- -
- - - - - - -
diff --git a/src/app/features/home/products/products.ts b/src/app/features/home/products/products.ts deleted file mode 100644 index ef2c403..0000000 --- a/src/app/features/home/products/products.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from "@angular/core"; -import { ProductCard } from "./componets/product-card/product-card"; - -@Component({ - selector: "app-products", - imports: [ProductCard], - templateUrl: "./products.html", -}) -export class Products {} diff --git a/src/app/features/product/add-product/add-product.html b/src/app/features/product/add-product/add-product.html index fc29349..a7afa68 100644 --- a/src/app/features/product/add-product/add-product.html +++ b/src/app/features/product/add-product/add-product.html @@ -62,8 +62,8 @@ Select category diff --git a/src/app/features/product/components/product-card/product-card.html b/src/app/features/product/components/product-card/product-card.html new file mode 100644 index 0000000..82ef41a --- /dev/null +++ b/src/app/features/product/components/product-card/product-card.html @@ -0,0 +1,15 @@ +
+ + +
+ +
+ +

{{ product.title }}

+

⭐4.5

+

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

+
diff --git a/src/app/features/home/products/componets/product-card/product-card.spec.ts b/src/app/features/product/components/product-card/product-card.spec.ts similarity index 100% rename from src/app/features/home/products/componets/product-card/product-card.spec.ts rename to src/app/features/product/components/product-card/product-card.spec.ts diff --git a/src/app/features/product/components/product-card/product-card.ts b/src/app/features/product/components/product-card/product-card.ts new file mode 100644 index 0000000..cca01d7 --- /dev/null +++ b/src/app/features/product/components/product-card/product-card.ts @@ -0,0 +1,20 @@ +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'; + +@Component({ + selector: 'app-product-card', + standalone: true, + imports: [FavoriteButton], + templateUrl: './product-card.html', +}) +export class ProductCard { + readonly router = inject(Router); + + @Input() product!: ProductModel; + + goToProductDetails() { + this.router.navigate(['/products', this.product.slug]); + } +} diff --git a/src/app/features/product/product.html b/src/app/features/product/product.html index 772b623..67121c9 100644 --- a/src/app/features/product/product.html +++ b/src/app/features/product/product.html @@ -1 +1,11 @@ -

product works!

+
+

Our Product

+
+ +
+ @for (product of products(); track product) { + + } +
diff --git a/src/app/features/product/product.routes.ts b/src/app/features/product/product.routes.ts index c11716c..bd85c26 100644 --- a/src/app/features/product/product.routes.ts +++ b/src/app/features/product/product.routes.ts @@ -1,9 +1,14 @@ import { Routes } from "@angular/router"; import { AddProduct } from "./add-product/add-product"; +import { ShowProduct } from "./show-product/show-product"; export const productRoutes: Routes = [ { path: "create", component: AddProduct, }, + { + path: ":slug", + component: ShowProduct, + }, ]; diff --git a/src/app/features/product/product.ts b/src/app/features/product/product.ts index 9c169a0..aade0bf 100644 --- a/src/app/features/product/product.ts +++ b/src/app/features/product/product.ts @@ -1,9 +1,22 @@ -import { Component } from "@angular/core"; +import { Component, inject, signal } from "@angular/core"; +import { ProductCard } from "./components/product-card/product-card"; +import { HttpClient } from "@angular/common/http"; +import { API_URL } from "../../core/tokens/api-url-tokens"; +import { ProductModel } from "../../core/models/product.model"; +import { ProductService } from "./services/product-service"; @Component({ - selector: "app-product", - imports: [], + selector: "app-products", + imports: [ProductCard], templateUrl: "./product.html", - styleUrl: "./product.css", }) -export class Product {} +export class Product { + productService = inject(ProductService); + products = signal([]); + + ngOnInit() { + this.productService.getProducts().subscribe((data) => { + this.products.set(data.data); + }); + } +} diff --git a/src/app/features/product/services/category-service.ts b/src/app/features/product/services/category-service.ts index 02319a8..7ffc3de 100644 --- a/src/app/features/product/services/category-service.ts +++ b/src/app/features/product/services/category-service.ts @@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http"; import { API_URL } from "../../../core/tokens/api-url-tokens"; export interface Category { - id: number; + id?: number; name: string; slug: string; } diff --git a/src/app/features/product/services/product-service.spec.ts b/src/app/features/product/services/product-service.spec.ts new file mode 100644 index 0000000..dae1d10 --- /dev/null +++ b/src/app/features/product/services/product-service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from "@angular/core/testing"; + +import { ProductService } from "./product-service"; + +describe("ProductService", () => { + let service: ProductService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ProductService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/features/product/services/product-service.ts b/src/app/features/product/services/product-service.ts new file mode 100644 index 0000000..b56e5f1 --- /dev/null +++ b/src/app/features/product/services/product-service.ts @@ -0,0 +1,20 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { API_URL } from '../../../core/tokens/api-url-tokens'; +import { ProductCollection, ProductModel } from '../../../core/models/product.model'; + +@Injectable({ + providedIn: 'root', +}) +export class ProductService { + http = inject(HttpClient); + apiUrl = inject(API_URL); + + getProducts() { + return this.http.get(`${this.apiUrl}/products`); + } + + getProduct(slug: string) { + return this.http.get<{ data: ProductModel }>(`${this.apiUrl}/products/${slug}`); + } +} diff --git a/src/app/features/product/show-product/show-product.css b/src/app/features/product/show-product/show-product.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/features/product/show-product/show-product.html b/src/app/features/product/show-product/show-product.html new file mode 100644 index 0000000..cd333a5 --- /dev/null +++ b/src/app/features/product/show-product/show-product.html @@ -0,0 +1,132 @@ +
+
+ + +
+
+
+ Product Image + + + +
+ +
+ @for (image of product()?.productImages; track image) { + + + } +
+
+ +
+
+

{{ product()?.title }}

+ + + +
+
+ +
+ 4.8 + + 112 Reviews +
+
+
+

Product Description

+

{{ product()?.description }}

+
+ +
+

Customer Reviews

+
    +
  • + "Rich in condensed nutrients using daily." + +
  • +
  • "Seedless & Eco-Peel it"
  • +
  • + "Longer Shelf Life has encounant" +
  • +
+
+
+ +
+
+
+
+ Rs.{{ product()?.actualPrice }} + Rs.{{ product()?.listPrice }} + +
+ +
+ +
+ + + +
+ +
+ + +
+
+
+
+
+
diff --git a/src/app/features/home/products/products.spec.ts b/src/app/features/product/show-product/show-product.spec.ts similarity index 58% rename from src/app/features/home/products/products.spec.ts rename to src/app/features/product/show-product/show-product.spec.ts index 0129469..31465ea 100644 --- a/src/app/features/home/products/products.spec.ts +++ b/src/app/features/product/show-product/show-product.spec.ts @@ -1,17 +1,17 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { Products } from "./products"; +import { ShowProduct } from "./show-product"; -describe("Products", () => { - let component: Products; - let fixture: ComponentFixture; +describe("ShowProduct", () => { + let component: ShowProduct; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [Products], + imports: [ShowProduct], }).compileComponents(); - fixture = TestBed.createComponent(Products); + fixture = TestBed.createComponent(ShowProduct); component = fixture.componentInstance; await fixture.whenStable(); }); diff --git a/src/app/features/product/show-product/show-product.ts b/src/app/features/product/show-product/show-product.ts new file mode 100644 index 0000000..3581d38 --- /dev/null +++ b/src/app/features/product/show-product/show-product.ts @@ -0,0 +1,40 @@ +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"; + +@Component({ + selector: "app-show-product", + imports: [LucideAngularModule], + templateUrl: "./show-product.html", + styleUrl: "./show-product.css", +}) +export class ShowProduct { + @Input() slug?: string; + + HeartIcon = Heart; + ArrowRightIcon = ArrowRight; + ArrowLeftIcon = ArrowLeft; + productService = inject(ProductService); + product = signal(null); + activeImageIndex: WritableSignal = signal(0); + totalImageCount: number = 0; + + ngOnInit() { + if (!this.slug) return; + this.productService.getProduct(this.slug).subscribe((data) => { + this.product.set(data.data); + this.totalImageCount = this.product()?.productImages?.length || 0; + }); + } + + nextImage() { + this.activeImageIndex.update((index) => (index + 1) % this.totalImageCount); + } + prevImage() { + this.activeImageIndex.update( + (index) => + (index - 1 + this.product()?.productImages.length!) % this.product()?.productImages.length!, + ); + } +} diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.css b/src/app/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/src/app/shared/components/favorite-button/favorite-button.html new file mode 100644 index 0000000..cf185bf --- /dev/null +++ b/src/app/src/app/shared/components/favorite-button/favorite-button.html @@ -0,0 +1,7 @@ + + diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts b/src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts new file mode 100644 index 0000000..7f14936 --- /dev/null +++ b/src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FavoriteButton } from './favorite-button'; + +describe('FavoriteButton', () => { + let component: FavoriteButton; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FavoriteButton] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FavoriteButton); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.ts b/src/app/src/app/shared/components/favorite-button/favorite-button.ts new file mode 100644 index 0000000..437e8d0 --- /dev/null +++ b/src/app/src/app/shared/components/favorite-button/favorite-button.ts @@ -0,0 +1,34 @@ +import { Component, inject, Input } from '@angular/core'; +import { HeartIcon, LucideAngularModule } from 'lucide-angular'; +import { FavoriteService } from '../../../../../core/services/favorite-service'; + +@Component({ + selector: 'app-favorite-button', + imports: [LucideAngularModule], + templateUrl: './favorite-button.html', + styleUrl: './favorite-button.css', +}) +export class FavoriteButton { + @Input({ required: true }) productId!: number; + @Input() isFavorite = true; + + favoriteService = inject(FavoriteService); + + HeartIcon = HeartIcon; + + toggleFavorite(event: Event) { + event.stopPropagation(); + this.isFavorite = !this.isFavorite; + + this.favoriteService.toggle(this.productId).subscribe({ + next: (response) => { + this.isFavorite = response.isFavorite; + }, + error: (err) => { + console.error(err); + // Revert the state incase of error + this.isFavorite = !this.isFavorite; + }, + }); + } +} diff --git a/src/styles.css b/src/styles.css index b38fa58..4ecbfd4 100644 --- a/src/styles.css +++ b/src/styles.css @@ -32,6 +32,10 @@ body { @apply text-gray-100 bg-gray-800 border 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; +} + .card { @apply bg-gray-50 rounded-xl border border-gray-300 hover:border-gray-400 p-4 hover:shadow-xl transition-all duration-300 ease-in-out; } From 7e1ecf35b942dd5cc2cef6c7e9c3bafaad21cf2e Mon Sep 17 00:00:00 2001 From: kusowl Date: Thu, 5 Mar 2026 13:34:37 +0530 Subject: [PATCH 3/3] make favorite state persistant with api --- .rgignore | 1 + src/app/core/models/product.model.ts | 1 + src/app/core/services/favorite-service.spec.ts | 8 ++++---- src/app/core/services/favorite-service.ts | 8 ++++---- .../components/product-card/product-card.html | 12 ++++++++---- .../components/product-card/product-card.ts | 12 ++++++------ .../features/product/services/product-service.ts | 10 +++++----- .../favorite-button/favorite-button.html | 10 ++++++++-- .../favorite-button/favorite-button.spec.ts | 13 ++++++------- .../components/favorite-button/favorite-button.ts | 14 +++++++------- 10 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 .rgignore diff --git a/.rgignore b/.rgignore new file mode 100644 index 0000000..21df648 --- /dev/null +++ b/.rgignore @@ -0,0 +1 @@ +backend/ diff --git a/src/app/core/models/product.model.ts b/src/app/core/models/product.model.ts index b509785..85160b4 100644 --- a/src/app/core/models/product.model.ts +++ b/src/app/core/models/product.model.ts @@ -11,6 +11,7 @@ export interface ProductModel { category: Category; productImages: string[]; updatedAt: string; + isFavorite: boolean; } export interface ProductCollection extends PaginatedResponse {} diff --git a/src/app/core/services/favorite-service.spec.ts b/src/app/core/services/favorite-service.spec.ts index 9bf2abe..6e7bc54 100644 --- a/src/app/core/services/favorite-service.spec.ts +++ b/src/app/core/services/favorite-service.spec.ts @@ -1,8 +1,8 @@ -import { TestBed } from '@angular/core/testing'; +import { TestBed } from "@angular/core/testing"; -import { FavoriteService } from './favorite-service'; +import { FavoriteService } from "./favorite-service"; -describe('FavoriteService', () => { +describe("FavoriteService", () => { let service: FavoriteService; beforeEach(() => { @@ -10,7 +10,7 @@ describe('FavoriteService', () => { service = TestBed.inject(FavoriteService); }); - it('should be created', () => { + it("should be created", () => { expect(service).toBeTruthy(); }); }); diff --git a/src/app/core/services/favorite-service.ts b/src/app/core/services/favorite-service.ts index 0df02da..fdde44e 100644 --- a/src/app/core/services/favorite-service.ts +++ b/src/app/core/services/favorite-service.ts @@ -1,6 +1,6 @@ -import { HttpClient } from '@angular/common/http'; -import { inject, Injectable } from '@angular/core'; -import { API_URL } from '../tokens/api-url-tokens'; +import { HttpClient } from "@angular/common/http"; +import { inject, Injectable } from "@angular/core"; +import { API_URL } from "../tokens/api-url-tokens"; export interface FavoriteResponse { message: string; @@ -8,7 +8,7 @@ export interface FavoriteResponse { } @Injectable({ - providedIn: 'root', + providedIn: "root", }) export class FavoriteService { http = inject(HttpClient); 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 c209551..73f1c6f 100644 --- a/src/app/features/product/components/product-card/product-card.html +++ b/src/app/features/product/components/product-card/product-card.html @@ -1,15 +1,19 @@
- +
-

{{product.title}}

+

{{ product.title }}

⭐4.5

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

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 a7b28b6..4f3e754 100644 --- a/src/app/features/product/components/product-card/product-card.ts +++ b/src/app/features/product/components/product-card/product-card.ts @@ -1,13 +1,13 @@ -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 { 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"; @Component({ - selector: 'app-product-card', + selector: "app-product-card", standalone: true, imports: [FavoriteButton], - templateUrl: './product-card.html', + templateUrl: "./product-card.html", }) export class ProductCard { readonly router = inject(Router); diff --git a/src/app/features/product/services/product-service.ts b/src/app/features/product/services/product-service.ts index b56e5f1..9613f86 100644 --- a/src/app/features/product/services/product-service.ts +++ b/src/app/features/product/services/product-service.ts @@ -1,10 +1,10 @@ -import { inject, Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { API_URL } from '../../../core/tokens/api-url-tokens'; -import { ProductCollection, ProductModel } from '../../../core/models/product.model'; +import { inject, Injectable } from "@angular/core"; +import { HttpClient } from "@angular/common/http"; +import { API_URL } from "../../../core/tokens/api-url-tokens"; +import { ProductCollection, ProductModel } from "../../../core/models/product.model"; @Injectable({ - providedIn: 'root', + providedIn: "root", }) export class ProductService { http = inject(HttpClient); diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.html b/src/app/src/app/shared/components/favorite-button/favorite-button.html index cf185bf..1380952 100644 --- a/src/app/src/app/shared/components/favorite-button/favorite-button.html +++ b/src/app/src/app/shared/components/favorite-button/favorite-button.html @@ -1,7 +1,13 @@ diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts b/src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts index 7f14936..227ca91 100644 --- a/src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts +++ b/src/app/src/app/shared/components/favorite-button/favorite-button.spec.ts @@ -1,23 +1,22 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { FavoriteButton } from './favorite-button'; +import { FavoriteButton } from "./favorite-button"; -describe('FavoriteButton', () => { +describe("FavoriteButton", () => { let component: FavoriteButton; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FavoriteButton] - }) - .compileComponents(); + imports: [FavoriteButton], + }).compileComponents(); fixture = TestBed.createComponent(FavoriteButton); component = fixture.componentInstance; await fixture.whenStable(); }); - it('should create', () => { + it("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/src/app/src/app/shared/components/favorite-button/favorite-button.ts b/src/app/src/app/shared/components/favorite-button/favorite-button.ts index 437e8d0..733a074 100644 --- a/src/app/src/app/shared/components/favorite-button/favorite-button.ts +++ b/src/app/src/app/shared/components/favorite-button/favorite-button.ts @@ -1,16 +1,16 @@ -import { Component, inject, Input } from '@angular/core'; -import { HeartIcon, LucideAngularModule } from 'lucide-angular'; -import { FavoriteService } from '../../../../../core/services/favorite-service'; +import { Component, inject, Input } from "@angular/core"; +import { HeartIcon, LucideAngularModule } from "lucide-angular"; +import { FavoriteService } from "../../../../../core/services/favorite-service"; @Component({ - selector: 'app-favorite-button', + selector: "app-favorite-button", imports: [LucideAngularModule], - templateUrl: './favorite-button.html', - styleUrl: './favorite-button.css', + templateUrl: "./favorite-button.html", + styleUrl: "./favorite-button.css", }) export class FavoriteButton { @Input({ required: true }) productId!: number; - @Input() isFavorite = true; + @Input() isFavorite = false; favoriteService = inject(FavoriteService);