From 4161ed18cd7fda7f307dee5e6189ece5d6be5102 Mon Sep 17 00:00:00 2001 From: Pooja933119 Date: Tue, 23 Dec 2025 22:45:48 +0530 Subject: [PATCH 1/2] First changes Done --- package-lock.json | 59 +++- package.json | 5 + src/app/app.component.ts | 2 +- src/app/layout/default-layout/_nav.ts | 2 +- .../default-header.component.html | 37 ++- .../default-header.component.ts | 39 ++- .../default-layout.component.html | 5 +- .../default-layout.component.ts | 7 +- src/app/model/auth.model.ts | 10 + src/app/model/product.model.ts | 8 + src/app/services/auth.service.ts | 78 +++++ src/app/services/product.service.ts | 76 +++++ .../breadcrumbs/breadcrumbs.component.html | 2 +- src/app/views/base/cards/cards.component.html | 2 +- .../base/carousels/carousels.component.html | 2 +- .../base/collapses/collapses.component.html | 2 +- .../list-groups/list-groups.component.html | 2 +- src/app/views/base/navs/navs.component.html | 2 +- src/app/views/base/navs/navs.component.ts | 4 +- .../paginations/paginations.component.html | 2 +- .../base/paginations/paginations.component.ts | 4 +- .../placeholders/placeholders.component.html | 2 +- .../placeholders/placeholders.component.ts | 4 +- .../views/dashboard/dashboard.component.ts | 12 + .../form-controls.component.html | 294 +++++------------- .../form-controls/form-controls.component.ts | 83 ++++- .../views/pages/login/login.component.html | 14 +- src/app/views/pages/login/login.component.ts | 30 +- .../pages/register/register.component.html | 57 +++- .../pages/register/register.component.ts | 48 ++- src/app/views/theme/colors.component.html | 40 +-- src/app/views/theme/colors.component.ts | 16 +- .../widgets-dropdown.component.html | 148 ++------- .../widgets-dropdown.component.ts | 62 +++- src/assets/brand/brandlogo.jpg | Bin 0 -> 5715 bytes src/assets/images/avatars/noProduct.png | Bin 0 -> 3276 bytes src/assets/images/avatars/teacher.png | Bin 0 -> 27482 bytes src/assets/images/avatars/user.png | Bin 0 -> 9509 bytes src/index.html | 6 +- tsconfig.json | 5 +- 40 files changed, 723 insertions(+), 448 deletions(-) create mode 100644 src/app/model/auth.model.ts create mode 100644 src/app/model/product.model.ts create mode 100644 src/app/services/auth.service.ts create mode 100644 src/app/services/product.service.ts create mode 100644 src/assets/brand/brandlogo.jpg create mode 100644 src/assets/images/avatars/noProduct.png create mode 100644 src/assets/images/avatars/teacher.png create mode 100644 src/assets/images/avatars/user.png diff --git a/package-lock.json b/package-lock.json index 786849d93..30233ae16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/platform-browser": "^21.0.6", "@angular/platform-browser-dynamic": "^21.0.6", "@angular/router": "^21.0.6", + "@auth0/angular-jwt": "^5.2.0", "@coreui/angular": "~5.6.4", "@coreui/angular-chartjs": "~5.6.4", "@coreui/chartjs": "~4.1.0", @@ -27,8 +28,12 @@ "@coreui/icons": "^3.0.1", "@coreui/icons-angular": "~5.6.4", "@coreui/utils": "^2.0.2", + "@types/mime-types": "^3.0.1", "chart.js": "^4.5.1", + "jwt-decode": "^4.0.0", "lodash-es": "^4.17.22", + "mime": "^4.1.0", + "mime-types": "^3.0.2", "ngx-scrollbar": "^13.0.3", "rxjs": "~7.8.2", "tslib": "^2.8.1", @@ -701,6 +706,17 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@auth0/angular-jwt": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-5.2.0.tgz", + "integrity": "sha512-9FS2L0QwGNlxA/zgeehCcsR9CZscouyXkoIj1fODM36A8BLfdzg9k9DWAXUQ2Drjk0AypGAFzeNZR4vsLMhdeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -4063,6 +4079,11 @@ "@types/lodash": "*" } }, + "node_modules/@types/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==" + }, "node_modules/@types/node": { "version": "24.10.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", @@ -6465,6 +6486,14 @@ ], "license": "MIT" }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", @@ -6745,6 +6774,18 @@ "node": ">= 0.6" } }, + "node_modules/karma/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/karma/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -7252,23 +7293,23 @@ } }, "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "license": "MIT", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], "bin": { - "mime": "cli.js" + "mime": "bin/cli.js" }, "engines": { - "node": ">=4.0.0" + "node": ">=16" } }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7278,8 +7319,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, diff --git a/package.json b/package.json index a4518dc72..c2d7b5f3c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@angular/platform-browser": "^21.0.6", "@angular/platform-browser-dynamic": "^21.0.6", "@angular/router": "^21.0.6", + "@auth0/angular-jwt": "^5.2.0", "@coreui/angular": "~5.6.4", "@coreui/angular-chartjs": "~5.6.4", "@coreui/chartjs": "~4.1.0", @@ -37,8 +38,12 @@ "@coreui/icons": "^3.0.1", "@coreui/icons-angular": "~5.6.4", "@coreui/utils": "^2.0.2", + "@types/mime-types": "^3.0.1", "chart.js": "^4.5.1", + "jwt-decode": "^4.0.0", "lodash-es": "^4.17.22", + "mime": "^4.1.0", + "mime-types": "^3.0.2", "ngx-scrollbar": "^13.0.3", "rxjs": "~7.8.2", "tslib": "^2.8.1", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3514d8698..2efa9ff46 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -14,7 +14,7 @@ import { iconSubset } from './icons/icon-subset'; imports: [RouterOutlet] }) export class AppComponent implements OnInit { - title = 'CoreUI Angular Admin Template'; + title = 'Ecomm'; readonly #destroyRef: DestroyRef = inject(DestroyRef); readonly #activatedRoute: ActivatedRoute = inject(ActivatedRoute); diff --git a/src/app/layout/default-layout/_nav.ts b/src/app/layout/default-layout/_nav.ts index db48a851b..8ef53bcfb 100644 --- a/src/app/layout/default-layout/_nav.ts +++ b/src/app/layout/default-layout/_nav.ts @@ -15,7 +15,7 @@ export const navItems: INavData[] = [ name: 'Theme' }, { - name: 'Colors', + name: 'Product Details', url: '/theme/colors', iconComponent: { name: 'cil-drop' } }, diff --git a/src/app/layout/default-layout/default-header/default-header.component.html b/src/app/layout/default-layout/default-header/default-header.component.html index 34fcecfc2..6a6a08a10 100644 --- a/src/app/layout/default-layout/default-header/default-header.component.html +++ b/src/app/layout/default-layout/default-header/default-header.component.html @@ -16,11 +16,21 @@ Dashboard + + + + + @@ -46,12 +56,15 @@ + - + + + @@ -59,15 +72,27 @@ +

{{decodedEmail}}

  • @@ -97,7 +122,7 @@
    42
  • -
  • +
  • @@ -150,7 +175,7 @@
    + Logout diff --git a/src/app/layout/default-layout/default-header/default-header.component.ts b/src/app/layout/default-layout/default-header/default-header.component.ts index 6f4d6954e..56926c0f6 100644 --- a/src/app/layout/default-layout/default-header/default-header.component.ts +++ b/src/app/layout/default-layout/default-header/default-header.component.ts @@ -1,6 +1,6 @@ -import { NgTemplateOutlet } from '@angular/common'; -import { Component, computed, inject, input } from '@angular/core'; -import { RouterLink, RouterLinkActive } from '@angular/router'; +import { CommonModule, NgTemplateOutlet } from '@angular/common'; +import { Component, computed, inject, input, OnInit } from '@angular/core'; +import { Router, RouterLink, RouterLinkActive } from '@angular/router'; import { AvatarComponent, @@ -23,17 +23,23 @@ import { } from '@coreui/angular'; import { IconDirective } from '@coreui/icons-angular'; +import { AuthService } from '../../../services/auth.service'; +import { FormsModule } from '@angular/forms'; +import { ProductService } from '../../../services/product.service'; @Component({ selector: 'app-default-header', templateUrl: './default-header.component.html', - imports: [ContainerComponent, HeaderTogglerDirective, SidebarToggleDirective, IconDirective, HeaderNavComponent, NavItemComponent, NavLinkDirective, RouterLink, RouterLinkActive, NgTemplateOutlet, BreadcrumbRouterComponent, DropdownComponent, DropdownToggleDirective, AvatarComponent, DropdownMenuDirective, DropdownHeaderDirective, DropdownItemDirective, BadgeComponent, DropdownDividerDirective] + imports: [ContainerComponent,FormsModule, HeaderTogglerDirective, SidebarToggleDirective, IconDirective, HeaderNavComponent, NavItemComponent, NavLinkDirective, RouterLink, RouterLinkActive, NgTemplateOutlet, BreadcrumbRouterComponent, DropdownComponent, DropdownToggleDirective, AvatarComponent, DropdownMenuDirective, DropdownHeaderDirective, DropdownItemDirective, BadgeComponent, DropdownDividerDirective] }) -export class DefaultHeaderComponent extends HeaderComponent { +export class DefaultHeaderComponent extends HeaderComponent{ readonly #colorModeService = inject(ColorModeService); readonly colorMode = this.#colorModeService.colorMode; - + decodedImage:any; + decodedEmail:any; + decodeToken:any; + searchTxt = ''; readonly colorModes = [ { name: 'light', text: 'Light', icon: 'cilSun' }, { name: 'dark', text: 'Dark', icon: 'cilMoon' }, @@ -45,10 +51,29 @@ export class DefaultHeaderComponent extends HeaderComponent { return this.colorModes.find(mode => mode.name === currentMode)?.icon ?? 'cilSun'; }); - constructor() { + constructor(private authService:AuthService, + private router:Router,private productService:ProductService) { super(); } + ngOnInit() { + let email = ''; + this.decodeToken = this.authService.getDecodeToken(); + let decodedTxt = JSON.parse(this.decodeToken); + this.decodedImage = decodedTxt?.image; + this.decodedEmail = decodedTxt?.email; + } + + handleLogout(){ + this.authService.removeToken(); + this.router.navigateByUrl('/login') + } + + //search + getSearchProduct(searchTxt:string){ + return this.productService.getSearchProduct(searchTxt) + } + sidebarId = input('sidebar1'); public newMessages = [ diff --git a/src/app/layout/default-layout/default-layout.component.html b/src/app/layout/default-layout/default-layout.component.html index f7ae8e188..bf859773c 100644 --- a/src/app/layout/default-layout/default-layout.component.html +++ b/src/app/layout/default-layout/default-layout.component.html @@ -7,10 +7,11 @@ visible > - + Logo + diff --git a/src/app/layout/default-layout/default-layout.component.ts b/src/app/layout/default-layout/default-layout.component.ts index 2e01ea849..4b39ba4c2 100644 --- a/src/app/layout/default-layout/default-layout.component.ts +++ b/src/app/layout/default-layout/default-layout.component.ts @@ -1,12 +1,10 @@ import { Component } from '@angular/core'; -import { RouterLink, RouterOutlet } from '@angular/router'; +import { RouterOutlet } from '@angular/router'; import { NgScrollbar } from 'ngx-scrollbar'; -import { IconDirective } from '@coreui/icons-angular'; import { ContainerComponent, ShadowOnScrollDirective, - SidebarBrandComponent, SidebarComponent, SidebarFooterComponent, SidebarHeaderComponent, @@ -32,7 +30,6 @@ function isOverflown(element: HTMLElement) { imports: [ SidebarComponent, SidebarHeaderComponent, - SidebarBrandComponent, SidebarNavComponent, SidebarFooterComponent, SidebarToggleDirective, @@ -40,10 +37,8 @@ function isOverflown(element: HTMLElement) { ContainerComponent, DefaultFooterComponent, DefaultHeaderComponent, - IconDirective, NgScrollbar, RouterOutlet, - RouterLink, ShadowOnScrollDirective ] }) diff --git a/src/app/model/auth.model.ts b/src/app/model/auth.model.ts new file mode 100644 index 000000000..2209c83dc --- /dev/null +++ b/src/app/model/auth.model.ts @@ -0,0 +1,10 @@ +export class UserRegister{ + username!:string; + password!:string; + email!:string; + image!:string; +} +export class UserLogin{ + email!:string; + password!:string; +} diff --git a/src/app/model/product.model.ts b/src/app/model/product.model.ts new file mode 100644 index 000000000..c09828762 --- /dev/null +++ b/src/app/model/product.model.ts @@ -0,0 +1,8 @@ +export class Product{ + title!:string; + description!:string; + rating!:string; + price!:string; + image!:string; + category!:string; +} diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 000000000..da6a83c14 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,78 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { UserLogin, UserRegister } from '../model/auth.model'; +import { Subject } from 'rxjs'; +import { JwtHelperService } from '@auth0/angular-jwt'; + +@Injectable({ + providedIn:'root' +}) +export class AuthService implements OnInit{ + private authStatusListener = new Subject(); + constructor(public http:HttpClient,private router:Router){} + token:any; + ngOnInit(): void { + + } + + registerUser(username:string,password:string,email:string,image:File){ + const form = new FormData(); + form.append('username',username); + form.append('password',password); + form.append('email',email); + form.append('image',image); + + this.http.post<{message:string,userReg:UserRegister}> + ('http://localhost:8000/api/auth/register',form) + .subscribe(responseData=>{ + alert("Registration Successfully"); + this.router.navigateByUrl('/login') + },error=>{ + if(error.status === 400){ + this.authStatusListener.next(false); + console.log(error) + alert(error.error.message) + } + + }) + } + + login(email:string,password:string){ + const form = new FormData(); + form.append('email',email); + form.append('password',password); + + this.http.post<{ + token: any;message:string,userLogin:UserLogin + }> + ('http://localhost:8000/api/auth/login',form) + .subscribe(responseData=>{ + this.token = responseData.token; + localStorage.setItem('token', responseData.token); + const helper = new JwtHelperService(); + const decodedToken= helper.decodeToken(this.token); + localStorage.setItem('userInfo', JSON.stringify(decodedToken)); + console.log(decodedToken); + alert("Login Successfully"); + this.router.navigateByUrl('/'); + },error=>{ + if(error.status === 400){ + this.authStatusListener.next(false); + console.log(error) + alert(error.error.message) + } + }) + } + getAuthenticationToken(){ + return localStorage.getItem('token'); + } + getDecodeToken(){ + return localStorage.getItem('userInfo'); + } + // Remove token + public removeToken(): void { + localStorage.removeItem('token'); + localStorage.removeItem('userInfo'); + } +} diff --git a/src/app/services/product.service.ts b/src/app/services/product.service.ts new file mode 100644 index 000000000..2b82a5372 --- /dev/null +++ b/src/app/services/product.service.ts @@ -0,0 +1,76 @@ +import { Injectable, signal } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { Product } from '../model/product.model'; +import { BehaviorSubject, map, retry } from 'rxjs'; +@Injectable({ + providedIn:'root' +}) + +export class ProductService { + productInfo = signal([]); + form:any; + private searchTxt = new BehaviorSubject(''); + seacrhProduct = this.searchTxt.asObservable(); + constructor(private http:HttpClient,private route:Router){} + + createProduct(title:string,description:string,rating:string,price:string,image:File,category:string){ + this.form = new FormData(); + this.form.append('title',title); + this.form.append('description',description); + this.form.append('rating',rating); + this.form.append('price',price), + this.form.append('image',image) + this.form.append('category',category); + console.log(this.form) + + this.http.post<{message:string,product:Product}> + ('http://localhost:8000/api/products/create-product',this.form) + .subscribe(responseData=>{ + const data = JSON.stringify(responseData.product); + console.log(data) + + alert("Add Product Successfully") + this.getAllProducts() + this.route.navigateByUrl('/'); + },error=>{ + alert("Error"); + }) + } + + getAllProducts(){ + return this.http.get('http://localhost:8000/api/products/get-products') + } + + deleteProductById(id:any){ + return this.http.delete(`http://localhost:8000/api/products/delete-product/${id}`) + } + + updateProductById(id:any,title:string,description:string,rating:string,price:string,image:File,category:string){ + this.form = new FormData(); + this.form.append('title',title); + this.form.append('description',description); + this.form.append('rating',rating); + this.form.append('price',price), + this.form.append('image',image) + this.form.append('category',category); + console.log(this.form) + return this.http.put(`http://localhost:8000/api/products/update-product/${id}`,this.form) + .subscribe(responseData=>{ + console.log(responseData) + alert("Update Product Successfully") + this.getAllProducts(); + this.route.navigateByUrl('/'); + },error=>{ + alert("Error"); + }) + } + + getProductById(id:string){ + return this.http.get(`http://localhost:8000/api/products/get-product/${id}`) + } + + getSearchProduct(searchNewTxt:any){ + this.searchTxt.next(searchNewTxt) + } +} diff --git a/src/app/views/base/breadcrumbs/breadcrumbs.component.html b/src/app/views/base/breadcrumbs/breadcrumbs.component.html index eeb12b547..e14aa3a9c 100644 --- a/src/app/views/base/breadcrumbs/breadcrumbs.component.html +++ b/src/app/views/base/breadcrumbs/breadcrumbs.component.html @@ -1,6 +1,6 @@ - + Angular Breadcrumbs diff --git a/src/app/views/base/cards/cards.component.html b/src/app/views/base/cards/cards.component.html index 1221b1686..35b61fc54 100644 --- a/src/app/views/base/cards/cards.component.html +++ b/src/app/views/base/cards/cards.component.html @@ -1,6 +1,6 @@ - + Angular Card diff --git a/src/app/views/base/carousels/carousels.component.html b/src/app/views/base/carousels/carousels.component.html index f90214ce1..1ef6be0ed 100644 --- a/src/app/views/base/carousels/carousels.component.html +++ b/src/app/views/base/carousels/carousels.component.html @@ -1,6 +1,6 @@ - + Angular Carousel Slide only diff --git a/src/app/views/base/collapses/collapses.component.html b/src/app/views/base/collapses/collapses.component.html index 8c42b1016..62257eabc 100644 --- a/src/app/views/base/collapses/collapses.component.html +++ b/src/app/views/base/collapses/collapses.component.html @@ -1,6 +1,6 @@ - + Angular Collapse diff --git a/src/app/views/base/list-groups/list-groups.component.html b/src/app/views/base/list-groups/list-groups.component.html index 5e86cac28..e740552f3 100644 --- a/src/app/views/base/list-groups/list-groups.component.html +++ b/src/app/views/base/list-groups/list-groups.component.html @@ -1,6 +1,6 @@ - + Angular List Group Basic example diff --git a/src/app/views/base/navs/navs.component.html b/src/app/views/base/navs/navs.component.html index a95e631cf..e76b54336 100644 --- a/src/app/views/base/navs/navs.component.html +++ b/src/app/views/base/navs/navs.component.html @@ -1,6 +1,6 @@ - + Angular Navs Base navs diff --git a/src/app/views/base/navs/navs.component.ts b/src/app/views/base/navs/navs.component.ts index b26b40279..1dd2f70d2 100644 --- a/src/app/views/base/navs/navs.component.ts +++ b/src/app/views/base/navs/navs.component.ts @@ -14,11 +14,11 @@ import { NavLinkDirective, RowComponent } from '@coreui/angular'; -import { DocsComponentsComponent, DocsExampleComponent } from '@docs-components/public-api'; +import { DocsExampleComponent } from '@docs-components/public-api'; @Component({ selector: 'app-navs', templateUrl: './navs.component.html', - imports: [RowComponent, ColComponent, CardComponent, CardHeaderComponent, CardBodyComponent, DocsExampleComponent, NavComponent, NavItemComponent, NavLinkDirective, RouterLink, DropdownComponent, DropdownToggleDirective, DropdownMenuDirective, DropdownItemDirective, DocsComponentsComponent] + imports: [RowComponent, ColComponent, CardComponent, CardHeaderComponent, CardBodyComponent, DocsExampleComponent, NavComponent, NavItemComponent, NavLinkDirective, RouterLink, DropdownComponent, DropdownToggleDirective, DropdownMenuDirective, DropdownItemDirective] }) export class NavsComponent {} diff --git a/src/app/views/base/paginations/paginations.component.html b/src/app/views/base/paginations/paginations.component.html index d734de2e6..c75249b47 100644 --- a/src/app/views/base/paginations/paginations.component.html +++ b/src/app/views/base/paginations/paginations.component.html @@ -1,6 +1,6 @@ - + Angular Pagination diff --git a/src/app/views/base/paginations/paginations.component.ts b/src/app/views/base/paginations/paginations.component.ts index 4036cb6da..2edc2b946 100644 --- a/src/app/views/base/paginations/paginations.component.ts +++ b/src/app/views/base/paginations/paginations.component.ts @@ -10,11 +10,11 @@ import { PaginationComponent, RowComponent } from '@coreui/angular'; -import { DocsComponentsComponent, DocsExampleComponent } from '@docs-components/public-api'; +import { DocsExampleComponent } from '@docs-components/public-api'; @Component({ selector: 'app-paginations', templateUrl: './paginations.component.html', - imports: [RowComponent, ColComponent, CardComponent, CardHeaderComponent, CardBodyComponent, DocsExampleComponent, PaginationComponent, PageItemComponent, PageLinkDirective, RouterLink, DocsComponentsComponent] + imports: [RowComponent, ColComponent, CardComponent, CardHeaderComponent, CardBodyComponent, DocsExampleComponent, PaginationComponent, PageItemComponent, PageLinkDirective, RouterLink] }) export class PaginationsComponent {} diff --git a/src/app/views/base/placeholders/placeholders.component.html b/src/app/views/base/placeholders/placeholders.component.html index 5ed4c4ef2..8356141a7 100644 --- a/src/app/views/base/placeholders/placeholders.component.html +++ b/src/app/views/base/placeholders/placeholders.component.html @@ -1,6 +1,6 @@ - + Angular Placeholder diff --git a/src/app/views/base/placeholders/placeholders.component.ts b/src/app/views/base/placeholders/placeholders.component.ts index 0349c9caf..adc712dc4 100644 --- a/src/app/views/base/placeholders/placeholders.component.ts +++ b/src/app/views/base/placeholders/placeholders.component.ts @@ -15,11 +15,11 @@ import { PlaceholderDirective, RowComponent } from '@coreui/angular'; -import { DocsComponentsComponent, DocsExampleComponent } from '@docs-components/public-api'; +import { DocsExampleComponent } from '@docs-components/public-api'; @Component({ selector: 'app-placeholders', templateUrl: './placeholders.component.html', - imports: [RowComponent, ColComponent, CardComponent, CardHeaderComponent, CardBodyComponent, DocsExampleComponent, CardImgDirective, CardTitleDirective, CardTextDirective, ButtonDirective, ColDirective, RouterLink, PlaceholderAnimationDirective, PlaceholderDirective, BgColorDirective, DocsComponentsComponent] + imports: [RowComponent, ColComponent, CardComponent, CardHeaderComponent, CardBodyComponent, DocsExampleComponent, CardImgDirective, CardTitleDirective, CardTextDirective, ButtonDirective, ColDirective, RouterLink, PlaceholderAnimationDirective, PlaceholderDirective, BgColorDirective] }) export class PlaceholdersComponent {} diff --git a/src/app/views/dashboard/dashboard.component.ts b/src/app/views/dashboard/dashboard.component.ts index 7c087fc1d..2fdd9b266 100644 --- a/src/app/views/dashboard/dashboard.component.ts +++ b/src/app/views/dashboard/dashboard.component.ts @@ -1,3 +1,4 @@ + import { Component, DestroyRef, DOCUMENT, effect, inject, OnInit, Renderer2, signal, WritableSignal } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { ChartOptions } from 'chart.js'; @@ -22,6 +23,9 @@ import { IconDirective } from '@coreui/icons-angular'; import { WidgetsBrandComponent } from '../widgets/widgets-brand/widgets-brand.component'; import { WidgetsDropdownComponent } from '../widgets/widgets-dropdown/widgets-dropdown.component'; import { DashboardChartsData, IChartProps } from './dashboard-charts-data'; +import { AuthService } from '../../services/auth.service'; +import { JwtHelperService } from '@auth0/angular-jwt'; + interface IUser { name: string; @@ -132,6 +136,9 @@ export class DashboardComponent implements OnInit { public mainChart: IChartProps = { type: 'line' }; public mainChartRef: WritableSignal = signal(undefined); + token:any; + constructor(public authService:AuthService){} + #mainChartRefEffect = effect(() => { if (this.mainChartRef()) { this.setChartStyles(); @@ -145,6 +152,11 @@ export class DashboardComponent implements OnInit { ngOnInit(): void { this.initCharts(); this.updateChartOnColorModeChange(); + // const token = this.authService.getAuthenticationToken(); + // console.log(token); + // const helper = new JwtHelperService(); + // const decoded= helper.decodeToken(token); + // console.log(decoded); } initCharts(): void { diff --git a/src/app/views/forms/form-controls/form-controls.component.html b/src/app/views/forms/form-controls/form-controls.component.html index 55cdfe980..fbe8f90d8 100644 --- a/src/app/views/forms/form-controls/form-controls.component.html +++ b/src/app/views/forms/form-controls/form-controls.component.html @@ -1,239 +1,91 @@ - + - Angular Form Control +

    {{id ? "Edit Product" : "Add Product"}}

    - -
    + +
    - + + +
    + +
    +
    +
    - - -
    -
    -
    -
    -
    -
    - - - - Angular Form Control Sizing - - -

    - Set heights using sizing property like sizing="lg" and - sizing="sm". -

    - - -
    - -
    - -
    -
    -
    -
    - - - - Angular Form Control Disabled - - -

    - Add the disabled boolean attribute on an input to give it a grayed out - appearance and remove pointer events. -

    - - -
    - -
    -
    -
    -
    -
    - - - - Angular Form Control Readonly - - -

    - Add the readOnly boolean attribute on an input to prevent modification of - the input's value. Read-only inputs appear lighter (just like disabled inputs), - but retain the standard cursor. -

    - - - -
    -
    -
    - - - - Angular Form Control Readonly plain text - - -

    - If you want to have <input readonly> elements in your form styled - as plain text, use the plainText boolean property to remove the default - form field styling and preserve the correct margin and padding. -

    - - - - - - - - - - - - - - - -
    - - - Price + - - - - - - - - + + +
    + + +
    + +
    + + +
    + +
    + +
    +
    + + +
    + @if (!imagePreview) { + + } + @if (imagePreview !== '' && imagePreview && form.get('image').valid) { +
    + +
    + }@else if(updatedImagePreview){ +
    + +
    + } + +
    + +
    + +
    + +
    -
    -
    -
    -
    - - - - Angular Form Control File input - - - -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    - - - - Angular Form Control Color - - - - - - - - - - - -
    -
    - - {{ favoriteColor() }} - -
    -
    +
    +
    diff --git a/src/app/views/forms/form-controls/form-controls.component.ts b/src/app/views/forms/form-controls/form-controls.component.ts index f6b3256a6..393110a3a 100644 --- a/src/app/views/forms/form-controls/form-controls.component.ts +++ b/src/app/views/forms/form-controls/form-controls.component.ts @@ -1,29 +1,96 @@ -import { Component, signal } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { Component, OnInit, signal } from '@angular/core'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { ButtonDirective, CardBodyComponent, CardComponent, CardHeaderComponent, ColComponent, - ColDirective, FormControlDirective, FormDirective, FormLabelDirective, - GutterDirective, RowComponent, RowDirective } from '@coreui/angular'; -import { DocsComponentsComponent, DocsExampleComponent } from '@docs-components/public-api'; +import { DocsExampleComponent } from '@docs-components/public-api'; +import { ProductService } from '../../../services/product.service'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-form-controls', templateUrl: './form-controls.component.html', styleUrls: ['./form-controls.component.scss'], - imports: [RowComponent, ColComponent, CardComponent, CardHeaderComponent, CardBodyComponent, DocsExampleComponent, ReactiveFormsModule, FormsModule, FormDirective, FormLabelDirective, FormControlDirective, ButtonDirective, RowDirective, GutterDirective, ColDirective, DocsComponentsComponent] + imports: [RowComponent, ColComponent, CardComponent, CardHeaderComponent, CardBodyComponent, DocsExampleComponent, ReactiveFormsModule, FormsModule, FormDirective, FormLabelDirective, FormControlDirective, ButtonDirective, RowDirective] }) -export class FormControlsComponent { - +export class FormControlsComponent implements OnInit{ + form:FormGroup | any; + imagePreview!: string | ArrayBuffer | any; + updatedImagePreview!: string | ArrayBuffer | any; + paramsObject:any; public favoriteColor = signal('#26ab3c'); + id:any; + title:any;description:any;price:any;rating:any;image:any;category:any; + constructor(private productService:ProductService,private route: ActivatedRoute){} + + ngOnInit(): void { + this.form = new FormGroup({ + title:new FormControl(null,{validators:[Validators.required]}), + description: new FormControl(null,{validators:[Validators.required]}), + rating:new FormControl(null,{validators:[Validators.required]}), + price:new FormControl(null,{validators:[Validators.required]}), + category:new FormControl(null,{validators:[Validators.required]}), + image:new FormControl(null,{validators:[Validators.required]}) + }); + this.route.queryParamMap.subscribe(params => { + this.paramsObject = params; + this.id = params.get('id') + if(params.get('id')){ + this.form.get('title')?.setValue(params.get('title')); + this.form.get('description')?.setValue(params.get('description')); + this.form.get('price')?.setValue(params.get('price')); + this.form.get('rating')?.setValue(params.get('rating')); + this.form.get('category')?.setValue(params.get('category')); + this.form.get('image')?.setValue(params.get('image')); + this.updatedImagePreview = params.get('image'); + console.log(this.updatedImagePreview) + }else{ + this.form.get('title')?.setValue(''); + this.form.get('description')?.setValue(''); + this.form.get('price')?.setValue(''); + this.form.get('rating')?.setValue(''); + this.form.get('category')?.setValue(''); + this.form.get('image')?.setValue(''); + this.updatedImagePreview = ''; + } + }) + } + + onImagePicked(event:any){ + const file = event.target.files[0]; + this.form.patchValue({image:file}); + this.form.get('image').updateValueAndValidity(); + const reader = new FileReader(); + reader.onload = () => { + this.imagePreview = reader.result; + } + reader.readAsDataURL(file) + } + + handleEditAddProduct(){ + // alert("***********" + this.id) + // if(this.form.invalid){ + // return + // } + if(!this.id){ + this.productService.createProduct(this.form.value.title, + this.form.value.description,this.form.value.rating, + this.form.value.price,this.form.value.image,this.form.value.category) + }else{ + this.productService.updateProductById(this.id,this.form.value.title, + this.form.value.description,this.form.value.rating, + this.form.value.price,this.form.value.image,this.form.value.category) + + } + } } diff --git a/src/app/views/pages/login/login.component.html b/src/app/views/pages/login/login.component.html index a1cf9b4d6..f8e7ece04 100644 --- a/src/app/views/pages/login/login.component.html +++ b/src/app/views/pages/login/login.component.html @@ -5,14 +5,17 @@ -
    +

    Login

    Sign In to your account

    - + @@ -22,17 +25,18 @@

    Login

    autoComplete="current-password" cFormControl placeholder="Password" + formControlName="password" type="password" />
    - - @@ -48,7 +52,7 @@

    Sign up

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

    - diff --git a/src/app/views/pages/login/login.component.ts b/src/app/views/pages/login/login.component.ts index e9ab25b01..b992f9744 100644 --- a/src/app/views/pages/login/login.component.ts +++ b/src/app/views/pages/login/login.component.ts @@ -1,5 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { IconDirective } from '@coreui/icons-angular'; +import { AuthService } from '../../../services/auth.service'; import { ButtonDirective, CardBodyComponent, @@ -13,10 +14,33 @@ import { InputGroupTextDirective, RowComponent } from '@coreui/angular'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; + @Component({ selector: 'app-login', templateUrl: './login.component.html', - imports: [ContainerComponent, RowComponent, ColComponent, CardGroupComponent, CardComponent, CardBodyComponent, FormDirective, InputGroupComponent, InputGroupTextDirective, IconDirective, FormControlDirective, ButtonDirective] + imports: [ContainerComponent,ReactiveFormsModule, RowComponent, ColComponent, CardGroupComponent, CardComponent, CardBodyComponent, FormDirective, InputGroupComponent, InputGroupTextDirective, IconDirective, FormControlDirective, ButtonDirective] }) -export class LoginComponent {} +export class LoginComponent implements OnInit{ + form:FormGroup | any; + constructor(private authService:AuthService,private router:Router){ + + } + ngOnInit(): void { + this.form = new FormGroup({ + email: new FormControl(null,{validators:[Validators.required]}), + password: new FormControl(null,{validators:[Validators.required]}), + }) + } + handleLogin(){ + if(this.form.invalid){ + return + } + this.authService.login(this.form.value.email,this.form.value.password); + } + navigateToRegister(){ + this.router.navigate(['/register']) + } +} diff --git a/src/app/views/pages/register/register.component.html b/src/app/views/pages/register/register.component.html index 68bffd177..725e59595 100644 --- a/src/app/views/pages/register/register.component.html +++ b/src/app/views/pages/register/register.component.html @@ -4,33 +4,78 @@ - +

    Register

    Create your account

    + + +
    +
    + + +
    + @if (!imagePreview) { + + } + @if (imagePreview !== '' && imagePreview && form.get('image').valid) { +
    + +
    + } + +
    + + - + @ - + + - + + - +
    - +
    diff --git a/src/app/views/pages/register/register.component.ts b/src/app/views/pages/register/register.component.ts index c19b54418..074ad78fc 100644 --- a/src/app/views/pages/register/register.component.ts +++ b/src/app/views/pages/register/register.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { IconDirective } from '@coreui/icons-angular'; import { ButtonDirective, @@ -12,10 +12,52 @@ import { InputGroupTextDirective, RowComponent } from '@coreui/angular'; +import { CommonModule } from '@angular/common'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { AuthService } from '../../../services/auth.service'; + + @Component({ selector: 'app-register', templateUrl: './register.component.html', - imports: [ContainerComponent, RowComponent, ColComponent, CardComponent, CardBodyComponent, FormDirective, InputGroupComponent, InputGroupTextDirective, IconDirective, FormControlDirective, ButtonDirective] + imports: [CommonModule,ContainerComponent,RowComponent, + ColComponent, CardComponent, CardBodyComponent, + FormDirective, InputGroupComponent, InputGroupTextDirective, + IconDirective, FormControlDirective, ButtonDirective,ReactiveFormsModule] }) -export class RegisterComponent {} +export class RegisterComponent implements OnInit{ + form:FormGroup | any; + imagePreview!: string | ArrayBuffer | any; + + constructor(private authService:AuthService){} + ngOnInit(): void { + this.form = new FormGroup({ + username: new FormControl(null,{validators:[Validators.required]}), + email: new FormControl(null,{validators:[Validators.required]}), + password: new FormControl(null,{validators:[Validators.required]}), + image: new FormControl(null,{validators:[Validators.required]}) + }); + } + + + onImagePicked(event:any){ + const file = event.target.files[0]; + this.form.patchValue({image:file}); + this.form.get('image').updateValueAndValidity(); + const reader = new FileReader(); + reader.onload = () => { + this.imagePreview = reader.result; + } + reader.readAsDataURL(file) + } + + handleRegistration(){ + if(this.form.invalid){ + return + } + this.authService.registerUser(this.form.value.username, + this.form.value.password,this.form.value.email,this.form.value.image + ) + } +} diff --git a/src/app/views/theme/colors.component.html b/src/app/views/theme/colors.component.html index 159b16bc2..171fe78f6 100644 --- a/src/app/views/theme/colors.component.html +++ b/src/app/views/theme/colors.component.html @@ -1,33 +1,21 @@ - Theme colors + Product Detail - -
    Brand Primary Color
    -
    - -
    Brand Secondary Color
    -
    - -
    Brand Success Color
    -
    - -
    Brand Danger Color
    -
    - -
    Brand Warning Color
    -
    - -
    Brand Info Color
    -
    - -
    Brand Light Color
    -
    - -
    Brand Dark Color
    -
    -
    +
    + @if(title === null || description === null || price === null || rating === null || category === null){ +

    No Product Detail Found

    + }@else { + +
    Product Title: {{title}}
    +
    Product Description: {{description}}
    +
    Product Price: {{price}}
    +
    Product Rating: {{rating}}
    +
    Product Category: {{category}}
    + } +
    +
    diff --git a/src/app/views/theme/colors.component.ts b/src/app/views/theme/colors.component.ts index c3a389235..3ed52d793 100644 --- a/src/app/views/theme/colors.component.ts +++ b/src/app/views/theme/colors.component.ts @@ -2,6 +2,7 @@ import { AfterViewInit, Component, computed, DOCUMENT, forwardRef, inject, input import { getStyle, rgbToHex } from '@coreui/utils'; import { CardBodyComponent, CardComponent, CardHeaderComponent, ColComponent, RowComponent } from '@coreui/angular'; +import { ActivatedRoute } from '@angular/router'; @Component({ templateUrl: 'colors.component.html', @@ -10,7 +11,8 @@ import { CardBodyComponent, CardComponent, CardHeaderComponent, ColComponent, Ro export class ColorsComponent implements OnInit, AfterViewInit { private document = inject(DOCUMENT); private renderer = inject(Renderer2); - + title:any;description:any;price:any;rating:any;image:any;category:any; + constructor(private route: ActivatedRoute) {} public themeColors(): void { Array.from(this.document.querySelectorAll('.theme-color')).forEach( (element: Element) => { @@ -36,7 +38,17 @@ export class ColorsComponent implements OnInit, AfterViewInit { ); } - ngOnInit(): void {} + ngOnInit(): void { + this.route.queryParamMap.subscribe(params => { + const id = params.get('id'); + this.image = params.get('image'); + this.title = params.get('title'); + this.description = params.get('description') + this.price = params.get('price'); + this.rating = params.get('rating'); + this.category = params.get('category'); + }); + } ngAfterViewInit(): void { this.themeColors(); diff --git a/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html b/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html index 2f1062523..ead735f59 100644 --- a/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html +++ b/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html @@ -1,123 +1,31 @@ - - - - 26K - - (-12.4% ) - - - - - - - - - - - - - - - - - $6.200 - - (40.9%) - - - - - - - - - - - - - - - - - 2.49 - - (84.7% ) - - - - - - - - - - - - - - - - - 44K - - (-23.6% ) - - - - - - - - - - - - + @if(searchProductTxt){ +

    You want to search : {{searchProductTxt}}

    + } +@for (product of resObject; track product._id) { + + + @if(searchProductTxt === '' || + product.category.toLowerCase().includes(searchProductTxt) || + product.title.toLowerCase().includes(searchProductTxt)){ +
    + +

    Title : {{product.title}}

    +

    Price : {{product.price}}

    +

    Rating: {{product.rating}}

    + +
    + + + + +
    +
    + } + +
    + } @empty { +

    No Product found.

    + }
    diff --git a/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.ts b/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.ts index dee287481..6aaa06a6a 100644 --- a/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.ts +++ b/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.ts @@ -1,7 +1,7 @@ import { AfterContentInit, AfterViewInit, ChangeDetectorRef, Component, inject, OnInit, viewChild } from '@angular/core'; import { getStyle } from '@coreui/utils'; import { ChartjsComponent } from '@coreui/angular-chartjs'; -import { RouterLink } from '@angular/router'; +import { Router, RouterLink } from '@angular/router'; import { IconDirective } from '@coreui/icons-angular'; import { ButtonDirective, @@ -15,6 +15,7 @@ import { TemplateIdDirective, WidgetStatAComponent } from '@coreui/angular'; +import { ProductService } from '../../../services/product.service'; @Component({ selector: 'app-widgets-dropdown', @@ -23,9 +24,10 @@ import { }) export class WidgetsDropdownComponent implements OnInit, AfterContentInit { private changeDetectorRef = inject(ChangeDetectorRef); - + resObject:any; data: any[] = []; options: any[] = []; + searchProductTxt = ''; labels = [ 'January', 'February', @@ -120,8 +122,64 @@ export class WidgetsDropdownComponent implements OnInit, AfterContentInit { } }; + constructor(public productService:ProductService,private router:Router){} ngOnInit(): void { this.setData(); + this.getAllProductsData(); + this.productService.seacrhProduct.subscribe(searchProductTxt => this.searchProductTxt = searchProductTxt) + } + + getAllProductsData(){ + this.productService.getAllProducts().subscribe((products:any)=>{ + if (products.hasOwnProperty('products')) { + this.resObject = products['products']; + console.log(this.resObject) + } + }) + } + //delete product + handleDelete(id:any){ + this.productService.deleteProductById(id) + .subscribe(()=>{ + //alert("Product Deleted Successfully") + this.getAllProductsData(); + + }) + } + + handleEdit(id:any){ + this.productService.getProductById(id) + .subscribe((responseData:any)=>{ + console.log(responseData) + this.router.navigate(['/forms/form-control'],{ + queryParams: { id:responseData._id, + title: responseData.title, + description:responseData.description, + price:responseData.price, + rating:responseData.rating, + image:responseData.image, + category:responseData.category + } + }) + }) + } + + //getproductbyID + handleShow(id:any){ + this.productService.getProductById(id) + .subscribe((responseData:any)=>{ + console.log(responseData) + this.router.navigate(['/theme/colors'],{ + queryParams: { title: responseData.title, + description:responseData.description, + price:responseData.price, + rating:responseData.rating, + image:responseData.image, + category:responseData.category + } + }) + + }) } ngAfterContentInit(): void { diff --git a/src/assets/brand/brandlogo.jpg b/src/assets/brand/brandlogo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..539bd008f347d9d04b151aca21abc95622158b9f GIT binary patch literal 5715 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δc3go z#}(hTY2mG(R8kD=uT^Oi9Gv}ddxtHc*J`0L3VU=U8d_%@h=icMCr=?uiAz&{)USQl z*o#f8Zj|+WZaVl}c?-AO__Z#ip4_v>VI5(Ul2Dy7-u_4>TLy34f4l6$V$v$G#tBtg zg4wG5cDxc}JU=h9Ww*63>X!4wgR0l%j*HdCw=AbP(`rN9LG322FPhqP|F(O}=Vdq* z)iSwTd>RWWeUrdYVJu5#F>Fd z^4Y;MhIi9Prg?OxP8W72#1+=+nX4Q(ui(EXn}@KaBqs&N%Xu$tZ%sFsT&=C4FhTK> zr6S0Z!>&d!siDM7#MIhA5Rh6*Yf{FJF5@1*HbuPmY>r2drM|>mAt=7Z(XQKaXl*C` z8BUl?EM!md12UFJDIqT;XmsP2ojy;WYolfzpZ|%d{MKa5*oo{;ML@#`!wjp%uB@Q< zFG~Y`m{`WDuLdTZg0^k$%TyT1`fd!Y{2IF{FP~i#f3RWoXu-$6_U25}+RXZ(cfv%@ zqs<>WbOzYR>xjGP&e#W?D!&HDhc;6-B^@(n(Z8l=Bf^*ZPRCc|{^rk~Q`iJ%x3;g> z`st1eMz-t@4wf`NP(Cj>?-`4DGO;J+8`q=)V*FDQTbE5`sYZ^Z4+Ls($()`iObn?+ckK5yw7+Kp~27b3dV zKU7-%byBV}qj#{_E4hNSx`+v8nc5tc7TyW(saaZyYw{RdRXih`9Vrc^L&~2H4GwMG zZn=|mh@Q2RDZYYwYg#Im#@C`kFtCvgZshMRj~PFB^EW9HT`RagU~yzoEMn+8Js7zJ zF`yjYke`sbowR~4d-`k8ed$@b%Hh!^5U*5TIdA+{lqRSyTbfu{OmX`Ll zSV*j6Y3$t^akI15vg|HFg>7u29piyv{?q(tu1=*K_DFUa%13&Ele#WA*O*kw--{q{ zl#FLs_BsuY_$mXi&kYlNz!sedO&X>YwTQ_Hz=688}mw z_Sq@PqF>sZxrhsxDY5HYG~97fz9^BBW|h8aTRDLa#)W~?kgNCVhL>wr7P|C|vqsE` z?(O}xipxzpzkj8ei{8R`plu1}eryxq)3gU}T3ZGJ%{9}lH#;jwYIF&c1&SNur61~} z<#y|r{f;5{*cVJ6g0#+8F&?3@K~`u&xo*cm<;9RYGHW)@M`@Wv&GNG)T-j^7RSTY- zyt}D9hG#u95n4ABc0OF|ydN<&qhCk~$vpxQ@5?T9?hX+p&u&=x@THe0aBp!-GhYH# zgdtvRvJRDaci-HjW7go&^JLY>JBxmM$NU+R`0Yos9I97*j}puVW3p#unkxXB_)L9zzR$G1aVIs_btT+Ah`675lKL5cvxjMmek#$ z%-1yFY`Fq_q^%$=O3n0|5LN0m0T^i+7|n;elgq3Q2g6fI%teg^rY3>agJN~n*q(%R4C)|J(X;Tu%NMQi!T6 literal 0 HcmV?d00001 diff --git a/src/assets/images/avatars/noProduct.png b/src/assets/images/avatars/noProduct.png new file mode 100644 index 0000000000000000000000000000000000000000..8c6de308dd233cecf312ddffb4552d5004d22f1a GIT binary patch literal 3276 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 KMNUMnLSTaNByi{e literal 0 HcmV?d00001 diff --git a/src/assets/images/avatars/teacher.png b/src/assets/images/avatars/teacher.png new file mode 100644 index 0000000000000000000000000000000000000000..187088282d2d996f468bdb4b1571905887a06a68 GIT binary patch literal 27482 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/avatars/user.png b/src/assets/images/avatars/user.png new file mode 100644 index 0000000000000000000000000000000000000000..a3fb639a7f9a692f2cf3d125fdd43e9818a20d97 GIT binary patch literal 9509 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 E0EZ0*wEzGB literal 0 HcmV?d00001 diff --git a/src/index.html b/src/index.html index b80d2944e..63c610d46 100644 --- a/src/index.html +++ b/src/index.html @@ -18,15 +18,15 @@ name="keyword" /> - CoreUI Free Angular Admin Template + E-commerce -
    + diff --git a/tsconfig.json b/tsconfig.json index c47455c0c..5b2031c9d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "esModuleInterop": true, - "strict": true, + "strict": false, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, @@ -21,7 +21,8 @@ "importHelpers": true, "target": "ES2022", "module": "preserve", - "useDefineForClassFields": false + "useDefineForClassFields": false, + "strictPropertyInitialization": false, }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, From 6bbf877c93a279aae80efbc8b0bc8795a4f605f7 Mon Sep 17 00:00:00 2001 From: Pooja933119 Date: Wed, 31 Dec 2025 17:03:59 +0530 Subject: [PATCH 2/2] New Changes --- angular.json | 5 +- package-lock.json | 104 +++++++++- package.json | 3 + src/app/Interceptor/auth.interceptor.ts | 18 ++ src/app/RouteGuard/AuthGuard.ts | 28 +++ src/app/app.config.ts | 6 +- src/app/layout/default-layout/_nav.ts | 8 +- .../default-header.component.html | 7 +- .../default-header.component.ts | 7 +- .../default-layout.component.html | 10 +- .../default-layout.component.ts | 23 +++ src/app/model/product.model.ts | 6 +- src/app/services/auth.service.ts | 12 +- src/app/services/product.service.ts | 102 +++++++--- .../form-controls.component.html | 6 +- .../form-controls/form-controls.component.ts | 8 +- src/app/views/forms/routes.ts | 4 +- src/app/views/theme/routes.ts | 2 +- src/app/views/theme/typography.component.html | 190 +++--------------- src/app/views/theme/typography.component.ts | 41 +++- .../widgets-dropdown.component.html | 20 +- .../widgets-dropdown.component.ts | 63 +++++- src/scss/styles.scss | 30 +++ 23 files changed, 471 insertions(+), 232 deletions(-) create mode 100644 src/app/Interceptor/auth.interceptor.ts create mode 100644 src/app/RouteGuard/AuthGuard.ts diff --git a/angular.json b/angular.json index 3667153ac..713797410 100644 --- a/angular.json +++ b/angular.json @@ -37,7 +37,10 @@ "src/assets" ], "styles": [ - "src/scss/styles.scss" + "src/scss/styles.scss", + "node_modules/primeng/resources/themes/lara-light-blue/theme.css", + "node_modules/primeng/resources/primeng.min.css", + "node_modules/primeicons/primeicons.css" ], "scripts": [], "allowedCommonJsDependencies": [], diff --git a/package-lock.json b/package-lock.json index 30233ae16..bac02451c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,9 @@ "mime": "^4.1.0", "mime-types": "^3.0.2", "ngx-scrollbar": "^13.0.3", + "primeicons": "^7.0.0", + "primeng": "^21.0.2", + "react-spinners": "^0.17.0", "rxjs": "~7.8.2", "tslib": "^2.8.1", "zone.js": "~0.16.0" @@ -1005,7 +1008,6 @@ "version": "5.6.4", "resolved": "https://registry.npmjs.org/@coreui/angular/-/angular-5.6.4.tgz", "integrity": "sha512-iOelu602tuEoQLj0esOx/HqJMxquQKiMvm6heeYTtk/GB4BegSPlqUVdbNkfK+MlTlxrYdORfE5Ymz0zvRDo3g==", - "license": "MIT", "dependencies": { "@popperjs/core": "~2.11.8", "tslib": "^2.3.0" @@ -3273,6 +3275,44 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@primeuix/motion": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@primeuix/motion/-/motion-0.0.10.tgz", + "integrity": "sha512-PsZwOPq79Scp7/ionshRcQ5xKVf9+zuLcyY5mf6onK8chHT5C9JGphmcIZ4CzcqxuGEpsm8AIbTGy+zS3RtzLA==", + "dependencies": { + "@primeuix/utils": "^0.6.3" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primeuix/styled": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.7.4.tgz", + "integrity": "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==", + "dependencies": { + "@primeuix/utils": "^0.6.1" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primeuix/styles": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-2.0.2.tgz", + "integrity": "sha512-LNtkJsTonNHF5ag+9s3+zQzm00+LRmffw68QRIHy6S/dam1JpdrrAnUzNYlWbaY7aE2EkZvQmx7Np7+PyHn+ow==", + "dependencies": { + "@primeuix/styled": "^0.7.4" + } + }, + "node_modules/@primeuix/utils": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.6.3.tgz", + "integrity": "sha512-/SLNQSKQ73WbBIsflKVqbpVjCfFYvQO3Sf1LMheXyxh8JqxO4M63dzP56wwm9OPGuCQ6MYOd2AHgZXz+g7PZcg==", + "engines": { + "node": ">=12.11.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.47.tgz", @@ -8254,6 +8294,32 @@ "dev": true, "license": "MIT" }, + "node_modules/primeicons": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz", + "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==" + }, + "node_modules/primeng": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/primeng/-/primeng-21.0.2.tgz", + "integrity": "sha512-suf+xK3V3z2aG9OmQMAriqhMt79JM3jBSoRDu4XQKCiTiWDRnOvoVdCZCjByT32sE5tYqhrLBsCH0lT7RLcosg==", + "dependencies": { + "@primeuix/motion": "^0.0.10", + "@primeuix/styled": "^0.7.4", + "@primeuix/styles": "^2.0.2", + "@primeuix/utils": "^0.6.3", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "^21.0.0", + "@angular/common": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/forms": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/router": "^21.0.0", + "rxjs": "^6.0.0 || ^7.8.1" + } + }, "node_modules/proc-log": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", @@ -8351,6 +8417,36 @@ "node": ">= 0.10" } }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-spinners": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.17.0.tgz", + "integrity": "sha512-L/8HTylaBmIWwQzIjMq+0vyaRXuoAevzWoD35wKpNTxxtYXWZp+xtgkfD7Y4WItuX0YvdxMPU79+7VhhmbmuTQ==", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -8616,6 +8712,12 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "peer": true + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", diff --git a/package.json b/package.json index c2d7b5f3c..a8ffefda3 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,9 @@ "mime": "^4.1.0", "mime-types": "^3.0.2", "ngx-scrollbar": "^13.0.3", + "primeicons": "^7.0.0", + "primeng": "^21.0.2", + "react-spinners": "^0.17.0", "rxjs": "~7.8.2", "tslib": "^2.8.1", "zone.js": "~0.16.0" diff --git a/src/app/Interceptor/auth.interceptor.ts b/src/app/Interceptor/auth.interceptor.ts new file mode 100644 index 000000000..4be101bc9 --- /dev/null +++ b/src/app/Interceptor/auth.interceptor.ts @@ -0,0 +1,18 @@ +import { HttpInterceptorFn, HttpRequest, HttpEvent, HttpHandlerFn } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export const authInterceptor: HttpInterceptorFn = (req: HttpRequest, next: HttpHandlerFn): Observable> => { + // Get token logic... + const token = localStorage.getItem('token'); // Replace with actual token retrieval + + if (token) { + const authReq = req.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + return next(authReq); + } + + return next(req); +}; \ No newline at end of file diff --git a/src/app/RouteGuard/AuthGuard.ts b/src/app/RouteGuard/AuthGuard.ts new file mode 100644 index 000000000..7ea7bb0fd --- /dev/null +++ b/src/app/RouteGuard/AuthGuard.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthService } from '../services/auth.service'; // Assume you have an authentication service + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + decodeToken:any; + decodedTxt:any; + constructor(private authService: AuthService, private router: Router) {} + + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Observable | Promise | boolean { + this.decodeToken = this.authService.getDecodeToken(); + this.decodedTxt = JSON.parse(this.decodeToken); + if (this.decodedTxt?.isAdmin) { + console.log("Admin") + this.router.navigate(['/forms/form-control']); + return true + } else { + console.log("User") + return false; // Block navigation + } + } +} diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 8196731ab..5f133429d 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -10,9 +10,13 @@ import { } from '@angular/router'; import { IconSetService } from '@coreui/icons-angular'; import { routes } from './app.routes'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import {authInterceptor} from './Interceptor/auth.interceptor'; +import { provideAnimations } from '@angular/platform-browser/animations'; export const appConfig: ApplicationConfig = { providers: [ + provideHttpClient(withInterceptors([authInterceptor])), provideRouter(routes, withRouterConfig({ onSameUrlNavigation: 'reload' @@ -26,7 +30,7 @@ export const appConfig: ApplicationConfig = { withHashLocation() ), IconSetService, - provideAnimationsAsync() + provideAnimations() ] }; diff --git a/src/app/layout/default-layout/_nav.ts b/src/app/layout/default-layout/_nav.ts index 8ef53bcfb..b5322f70f 100644 --- a/src/app/layout/default-layout/_nav.ts +++ b/src/app/layout/default-layout/_nav.ts @@ -1,4 +1,6 @@ import { INavData } from '@coreui/angular'; +import { CanActivate } from '@angular/router'; +import { AuthGuard } from '../../RouteGuard/AuthGuard'; export const navItems: INavData[] = [ { @@ -20,7 +22,7 @@ export const navItems: INavData[] = [ iconComponent: { name: 'cil-drop' } }, { - name: 'Typography', + name: 'AddToCart', url: '/theme/typography', linkProps: { fragment: 'headings' }, iconComponent: { name: 'cil-pencil' } @@ -189,9 +191,9 @@ export const navItems: INavData[] = [ attributes: { target: '_blank' } }, { - name: 'Form Control', + name: 'Add Product', url: '/forms/form-control', - icon: 'nav-icon-bullet' + icon: 'nav-icon-bullet', }, { name: 'Checks & Radios', diff --git a/src/app/layout/default-layout/default-header/default-header.component.html b/src/app/layout/default-layout/default-header/default-header.component.html index 6a6a08a10..173488013 100644 --- a/src/app/layout/default-layout/default-header/default-header.component.html +++ b/src/app/layout/default-layout/default-header/default-header.component.html @@ -72,7 +72,7 @@ -

    {{decodedEmail}}

    +
    +

    {{decodedEmail}}

    +

    {{decodedTxt?.isAdmin ? 'Admin' : 'User'}}

    +
    • diff --git a/src/app/layout/default-layout/default-header/default-header.component.ts b/src/app/layout/default-layout/default-header/default-header.component.ts index 56926c0f6..e5d412bc9 100644 --- a/src/app/layout/default-layout/default-header/default-header.component.ts +++ b/src/app/layout/default-layout/default-header/default-header.component.ts @@ -39,6 +39,7 @@ export class DefaultHeaderComponent extends HeaderComponent{ decodedImage:any; decodedEmail:any; decodeToken:any; + decodedTxt:any; searchTxt = ''; readonly colorModes = [ { name: 'light', text: 'Light', icon: 'cilSun' }, @@ -59,9 +60,9 @@ export class DefaultHeaderComponent extends HeaderComponent{ ngOnInit() { let email = ''; this.decodeToken = this.authService.getDecodeToken(); - let decodedTxt = JSON.parse(this.decodeToken); - this.decodedImage = decodedTxt?.image; - this.decodedEmail = decodedTxt?.email; + this.decodedTxt = JSON.parse(this.decodeToken); + this.decodedImage = this.decodedTxt?.image; + this.decodedEmail = this.decodedTxt?.email; } handleLogout(){ diff --git a/src/app/layout/default-layout/default-layout.component.html b/src/app/layout/default-layout/default-layout.component.html index bf859773c..3d2f2465b 100644 --- a/src/app/layout/default-layout/default-layout.component.html +++ b/src/app/layout/default-layout/default-layout.component.html @@ -13,10 +13,14 @@ --> + + + + - - - @if (!sidebar1.narrow) { diff --git a/src/app/layout/default-layout/default-layout.component.ts b/src/app/layout/default-layout/default-layout.component.ts index 4b39ba4c2..fac218603 100644 --- a/src/app/layout/default-layout/default-layout.component.ts +++ b/src/app/layout/default-layout/default-layout.component.ts @@ -15,6 +15,7 @@ import { import { DefaultFooterComponent, DefaultHeaderComponent } from './'; import { navItems } from './_nav'; +import { AuthService } from '../../services/auth.service'; function isOverflown(element: HTMLElement) { return ( @@ -44,4 +45,26 @@ function isOverflown(element: HTMLElement) { }) export class DefaultLayoutComponent { public navItems = [...navItems]; + decodeToken:any;decodedTxt:any; + isItemFound = true; + matchedItem:any; + constructor(private authService:AuthService){} + + ngOnInit() { + this.navItems.filter(item => { + // console.log(item.children) + + this.decodeToken = this.authService.getDecodeToken(); + this.decodedTxt = JSON.parse(this.decodeToken); + if (!this.decodedTxt?.isAdmin) { + + console.log("No Name") + this.matchedItem = item.children?.filter(item => item.name === 'Add Product'); + console.log(this.matchedItem); + // this.isItemFound = !!match; + //return this.isItemFound; + } + }) + } + } diff --git a/src/app/model/product.model.ts b/src/app/model/product.model.ts index c09828762..ad509f8b8 100644 --- a/src/app/model/product.model.ts +++ b/src/app/model/product.model.ts @@ -1,8 +1,8 @@ export class Product{ title!:string; description!:string; - rating!:string; - price!:string; - image!:string; + rating!:number; + price!:number; + image!:File; category!:string; } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index da6a83c14..1d22e117d 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -9,6 +9,7 @@ import { JwtHelperService } from '@auth0/angular-jwt'; providedIn:'root' }) export class AuthService implements OnInit{ + decodedToken:any; private authStatusListener = new Subject(); constructor(public http:HttpClient,private router:Router){} token:any; @@ -51,9 +52,9 @@ export class AuthService implements OnInit{ this.token = responseData.token; localStorage.setItem('token', responseData.token); const helper = new JwtHelperService(); - const decodedToken= helper.decodeToken(this.token); - localStorage.setItem('userInfo', JSON.stringify(decodedToken)); - console.log(decodedToken); + this.decodedToken= helper.decodeToken(this.token); + localStorage.setItem('userInfo', JSON.stringify(this.decodedToken)); + console.log(this.decodedToken); alert("Login Successfully"); this.router.navigateByUrl('/'); },error=>{ @@ -64,6 +65,11 @@ export class AuthService implements OnInit{ } }) } + + adminAuthentication(){ + this.http.get(``) + } + getAuthenticationToken(){ return localStorage.getItem('token'); } diff --git a/src/app/services/product.service.ts b/src/app/services/product.service.ts index 2b82a5372..816ed620d 100644 --- a/src/app/services/product.service.ts +++ b/src/app/services/product.service.ts @@ -1,5 +1,5 @@ import { Injectable, signal } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Router } from '@angular/router'; import { Product } from '../model/product.model'; import { BehaviorSubject, map, retry } from 'rxjs'; @@ -8,46 +8,74 @@ import { BehaviorSubject, map, retry } from 'rxjs'; }) export class ProductService { + TOKEN = localStorage.getItem('token'); productInfo = signal([]); form:any; private searchTxt = new BehaviorSubject(''); seacrhProduct = this.searchTxt.asObservable(); constructor(private http:HttpClient,private route:Router){} + // createProduct(title:string,description:string,rating:string,price:string,image:File,category:string){ + // const headers = new HttpHeaders().set('Authorization',`Bearer ${localStorage.getItem('token')}`); + // this.form = new FormData(); + // this.form.append('title',title); + // this.form.append('description',description); + // this.form.append('rating',rating); + // this.form.append('price',price), + // this.form.append('image',image) + // this.form.append('category',category); + + // this.http.post<{message:string,product:Product}> + // ('http://localhost:8000/api/products/create-product', + // {headers},this.form).subscribe((resp:any)=>{ + // console.log(this.form) + // alert(resp) + // },error=>{ + // alert(error.message) + // }) + + // } + createProduct(title:string,description:string,rating:string,price:string,image:File,category:string){ - this.form = new FormData(); + // const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('token')}`); + this.form = new FormData(); this.form.append('title',title); this.form.append('description',description); this.form.append('rating',rating); this.form.append('price',price), this.form.append('image',image) this.form.append('category',category); - console.log(this.form) - + console.log(title) + console.log(description) + console.log(rating) + console.log(price) + console.log(image) + console.log(category) this.http.post<{message:string,product:Product}> - ('http://localhost:8000/api/products/create-product',this.form) - .subscribe(responseData=>{ - const data = JSON.stringify(responseData.product); - console.log(data) - - alert("Add Product Successfully") - this.getAllProducts() - this.route.navigateByUrl('/'); - },error=>{ - alert("Error"); - }) - } + (`http://localhost:8000/api/products/create-product`, + this.form).subscribe((responseData:any)=>{ + console.log(responseData) + alert("Add Product Successfully") + this.getAllProducts(); + this.route.navigateByUrl('/'); + },error=>{ + alert(error.message) + }) + } getAllProducts(){ return this.http.get('http://localhost:8000/api/products/get-products') } deleteProductById(id:any){ - return this.http.delete(`http://localhost:8000/api/products/delete-product/${id}`) + // const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('token')}`); + return this.http.delete(`http://localhost:8000/api/products/delete-product/${id}`,) + } updateProductById(id:any,title:string,description:string,rating:string,price:string,image:File,category:string){ - this.form = new FormData(); + // const headers = new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('token')}`); + this.form = new FormData(); this.form.append('title',title); this.form.append('description',description); this.form.append('rating',rating); @@ -55,15 +83,16 @@ export class ProductService { this.form.append('image',image) this.form.append('category',category); console.log(this.form) - return this.http.put(`http://localhost:8000/api/products/update-product/${id}`,this.form) - .subscribe(responseData=>{ - console.log(responseData) - alert("Update Product Successfully") - this.getAllProducts(); - this.route.navigateByUrl('/'); - },error=>{ - alert("Error"); - }) + this.http.put(`http://localhost:8000/api/products/update-product/${id}`,this.form) + .subscribe((response:any)=>{ + //console.log + alert("Product Updated Successfully"); + //this.getAllProducts(); + this.route.navigateByUrl('/'); + },error=>{ + alert("Product Updated Failure"); + }) + } getProductById(id:string){ @@ -73,4 +102,23 @@ export class ProductService { getSearchProduct(searchNewTxt:any){ this.searchTxt.next(searchNewTxt) } + + getAddToCartProduct(product:any){ + this.form = new FormData(); + this.form.append('title',product?.title); + this.form.append('price',product?.price), + this.form.append('image',product?.image) + console.log(this.form) + return this.http.post(`http://localhost:8000/api/products/create-addToCart`,this.form) + } + + getRemoveAddToCart(id:any){ + return this.http.delete(`http://localhost:8000/api/products/deleteToCart/${id}`) + + } + + getAllAddToCartProduct(){ + return this.http.get(`http://localhost:8000/api/products/getToProductCart`) + } + } diff --git a/src/app/views/forms/form-controls/form-controls.component.html b/src/app/views/forms/form-controls/form-controls.component.html index fbe8f90d8..fa3a9fb8c 100644 --- a/src/app/views/forms/form-controls/form-controls.component.html +++ b/src/app/views/forms/form-controls/form-controls.component.html @@ -47,7 +47,7 @@
      - @@ -62,9 +62,9 @@
      - @if (!imagePreview) { + @if (imagePreview !== '' && imagePreview && form.get('image').valid) {
      diff --git a/src/app/views/forms/form-controls/form-controls.component.ts b/src/app/views/forms/form-controls/form-controls.component.ts index 393110a3a..8d7bb48e3 100644 --- a/src/app/views/forms/form-controls/form-controls.component.ts +++ b/src/app/views/forms/form-controls/form-controls.component.ts @@ -78,9 +78,10 @@ export class FormControlsComponent implements OnInit{ handleEditAddProduct(){ // alert("***********" + this.id) - // if(this.form.invalid){ - // return - // } + if(this.form.invalid){ + return + } + console.log(this.form.value.category); if(!this.id){ this.productService.createProduct(this.form.value.title, this.form.value.description,this.form.value.rating, @@ -89,7 +90,6 @@ export class FormControlsComponent implements OnInit{ this.productService.updateProductById(this.id,this.form.value.title, this.form.value.description,this.form.value.rating, this.form.value.price,this.form.value.image,this.form.value.category) - } } diff --git a/src/app/views/forms/routes.ts b/src/app/views/forms/routes.ts index e413478a1..3207ac196 100644 --- a/src/app/views/forms/routes.ts +++ b/src/app/views/forms/routes.ts @@ -1,4 +1,5 @@ import { Routes } from '@angular/router'; +import { AuthGuard } from '../../RouteGuard/AuthGuard'; export const routes: Routes = [ { @@ -6,6 +7,7 @@ export const routes: Routes = [ data: { title: 'Forms' }, + children: [ { path: '', @@ -16,7 +18,7 @@ export const routes: Routes = [ path: 'form-control', loadComponent: () => import('./form-controls/form-controls.component').then(m => m.FormControlsComponent), data: { - title: 'Form Control' + title: 'Add Product' } }, { diff --git a/src/app/views/theme/routes.ts b/src/app/views/theme/routes.ts index 44f2aa98b..c92173a6c 100644 --- a/src/app/views/theme/routes.ts +++ b/src/app/views/theme/routes.ts @@ -23,7 +23,7 @@ export const routes: Routes = [ path: 'typography', loadComponent: () => import('./typography.component').then(m => m.TypographyComponent), data: { - title: 'Typography' + title: 'AddToCart' } } ] diff --git a/src/app/views/theme/typography.component.html b/src/app/views/theme/typography.component.html index 2bea088f5..eac54b286 100644 --- a/src/app/views/theme/typography.component.html +++ b/src/app/views/theme/typography.component.html @@ -1,166 +1,36 @@ +@if(isLoading){ +
      +
      +
      +} - Headings + Shopping Cart -

      Documentation and examples for Bootstrap typography, including global settings, headings, body text, lists, and - more.

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      HeadingExample
      -

      <h1></h1>

      -

      h1. Bootstrap heading

      -

      <h2></h2>

      -

      h2. Bootstrap heading

      -

      <h3></h3>

      -

      h3. Bootstrap heading

      -

      <h4></h4>

      -

      h4. Bootstrap heading

      -

      <h5></h5>

      -
      h5. Bootstrap heading
      -

      <h6></h6>

      -
      h6. Bootstrap heading
      -
      -
      - - - Headings - - -

      .h1 through .h6 classes are - also available, for when you want to match the font styling of a heading but cannot use the associated HTML - element.

      -
      -

      h1. Bootstrap heading

      -

      h2. Bootstrap heading

      -

      h3. Bootstrap heading

      -

      h4. Bootstrap heading

      -

      h5. Bootstrap heading

      -

      h6. Bootstrap heading

      -
      -
      -
      - - - Display headings - - -

      Traditional heading elements are designed to work best in the meat of your page content. When you need a heading - to stand out, consider using a display heading—a larger, slightly more opinionated heading style. -

      -
      - - - - - - - - - - - - - - - -
      Display 1
      Display 2
      Display 3
      Display 4
      -
      -
      -
      - - - Inline text elements - - -

      Traditional heading elements are designed to work best in the meat of your page content. When you need a heading - to stand out, consider using a display heading—a larger, slightly more opinionated heading style. -

      -
      -

      You can use the mark tag to - highlight - text. -

      -

      - This line of text is meant to be treated as deleted text. -

      -

      This line of text is meant to be treated as no longer accurate.

      -

      - This line of text is meant to be treated as an addition to the document. -

      -

      This line of text will render as underlined

      -

      This line of text is meant to be treated as fine print.

      -

      This line rendered as bold text.

      -

      This line rendered as italicized text.

      -
      -
      -
      - - - Description list alignment - - -

      Align terms and descriptions horizontally by using our grid system’s predefined classes (or semantic mixins). For - longer terms, you can optionally add a .text-truncate class to truncate - the text with an ellipsis.

      -
      -
      -
      Description lists
      -
      A description list is perfect for defining terms.
      - -
      Euismod
      -
      -

      Vestibulum id ligula porta felis euismod semper eget lacinia odio sem nec elit.

      -

      Donec id elit non mi porta gravida at eget metus.

      -
      - -
      Malesuada porta
      -
      Etiam porta sem malesuada magna mollis euismod.
      - -
      Truncated term is truncated with d-block
      -
      Fusce dapibus, tellus ac cursus commodo, tortor mauris - condimentum nibh, ut fermentum massa justo sit amet risus. -
      - -
      Nesting
      -
      -
      -
      Nested definition list
      -
      Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc.
      -
      -
      -
      -
      + + @for(product of products; track product._id){ + @if(product){ +
      + +
      +

      Title: {{product.title}}

      +

      Price : {{product.price}}

      +
      + +
      + }@else{ +

      No Product Found!!

      + } + }
      + @if(products && totalPrice){ +
      +
      Subtotal ({{products?.length}} items) + {{"\u20B9"}} {{totalPrice}}
      + +
      + }@else{ + + }
      diff --git a/src/app/views/theme/typography.component.ts b/src/app/views/theme/typography.component.ts index 5644b2c55..91eef2b19 100644 --- a/src/app/views/theme/typography.component.ts +++ b/src/app/views/theme/typography.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { CardBodyComponent, CardComponent, CardHeaderComponent } from '@coreui/angular'; +import { ProductService } from '../../services/product.service'; @Component({ templateUrl: 'typography.component.html', @@ -9,4 +10,42 @@ import { CardBodyComponent, CardComponent, CardHeaderComponent } from '@coreui/a CardBodyComponent ] }) -export class TypographyComponent {} +export class TypographyComponent{ + products:any; + totalPrice: number = 0; + prod:any; + isLoading = false; + constructor(private productService:ProductService){ + + } + ngOnInit() { + this.isLoading = true; + this.productService.getAllAddToCartProduct() + .subscribe((response:any)=>{ + this.products = response.products; + this.calculateTotal(this.products) + this.isLoading = false; + },error=>{ + console.log(error) + this.isLoading = false; + }); + + } + handleRemoveProduct(id:any){ + this.isLoading = true; + this.productService.getRemoveAddToCart(id).subscribe((response:any)=>{ + alert(response.message) + this.isLoading = false; + this.productService.getAllAddToCartProduct(); + },error=>{ + alert(error) + this.isLoading = false; + }) + } + calculateTotal(products:any): void { + console.log(products) + this.totalPrice = products.reduce((acc:any, item:any) => { + return acc + (item.price); + }, 0); + } +} diff --git a/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html b/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html index ead735f59..e8f408fc4 100644 --- a/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html +++ b/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.html @@ -1,3 +1,8 @@ +@if(isLoading){ +
      +
      +
      +} @if(searchProductTxt){

      You want to search : {{searchProductTxt}}

      @@ -14,12 +19,15 @@

      Price : {{product.price}}

      Rating: {{product.rating}}

      -
      - - - - -
      + @if(decodedTxt?.isAdmin){ +
      + + + +
      + }@else{ + + }
      } diff --git a/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.ts b/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.ts index 6aaa06a6a..eb1f81764 100644 --- a/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.ts +++ b/src/app/views/widgets/widgets-dropdown/widgets-dropdown.component.ts @@ -16,6 +16,7 @@ import { WidgetStatAComponent } from '@coreui/angular'; import { ProductService } from '../../../services/product.service'; +import { AuthService } from '../../../services/auth.service'; @Component({ selector: 'app-widgets-dropdown', @@ -23,11 +24,15 @@ import { ProductService } from '../../../services/product.service'; imports: [RowComponent, ColComponent, WidgetStatAComponent, TemplateIdDirective, IconDirective, DropdownComponent, ButtonDirective, DropdownToggleDirective, DropdownMenuDirective, DropdownItemDirective, RouterLink, DropdownDividerDirective, ChartjsComponent] }) export class WidgetsDropdownComponent implements OnInit, AfterContentInit { + private changeDetectorRef = inject(ChangeDetectorRef); resObject:any; data: any[] = []; options: any[] = []; searchProductTxt = ''; + decodeToken:any; + decodedTxt:any; + isLoading = false; labels = [ 'January', 'February', @@ -122,35 +127,54 @@ export class WidgetsDropdownComponent implements OnInit, AfterContentInit { } }; - constructor(public productService:ProductService,private router:Router){} + constructor(public authService:AuthService, + public productService:ProductService,private router:Router){} ngOnInit(): void { this.setData(); this.getAllProductsData(); - this.productService.seacrhProduct.subscribe(searchProductTxt => this.searchProductTxt = searchProductTxt) + this.decodeToken = this.authService.getDecodeToken(); + this.decodedTxt = JSON.parse(this.decodeToken); + //console.log(this.decodedTxt.isAdmin) + this.productService.seacrhProduct.subscribe(searchProductTxt => this.searchProductTxt = searchProductTxt) } getAllProductsData(){ - this.productService.getAllProducts().subscribe((products:any)=>{ + this.isLoading = true; + this.productService.getAllProducts() + .subscribe((products:any)=>{ if (products.hasOwnProperty('products')) { this.resObject = products['products']; console.log(this.resObject) + this.isLoading = false; } + },error=>{ + alert(error) + this.isLoading = false; }) } //delete product handleDelete(id:any){ + this.isLoading = true; this.productService.deleteProductById(id) - .subscribe(()=>{ - //alert("Product Deleted Successfully") - this.getAllProductsData(); - + .subscribe((response:any)=>{ + try{ + console.log(response) + alert("Product Deleted Successfully"); + this.getAllProductsData(); + this.isLoading = false; + }catch(error){ + alert(error) + this.isLoading = false + } }) } handleEdit(id:any){ + this.isLoading = true; this.productService.getProductById(id) .subscribe((responseData:any)=>{ console.log(responseData) + this.isLoading = false; this.router.navigate(['/forms/form-control'],{ queryParams: { id:responseData._id, title: responseData.title, @@ -161,14 +185,20 @@ export class WidgetsDropdownComponent implements OnInit, AfterContentInit { category:responseData.category } }) + },error=>{ + alert(error) + this.isLoading = false; }) - } + + } //getproductbyID handleShow(id:any){ + this.isLoading = true; this.productService.getProductById(id) .subscribe((responseData:any)=>{ - console.log(responseData) + console.log(responseData); + this.isLoading = false; this.router.navigate(['/theme/colors'],{ queryParams: { title: responseData.title, description:responseData.description, @@ -179,9 +209,24 @@ export class WidgetsDropdownComponent implements OnInit, AfterContentInit { } }) + },error=>{ + alert(error) + this.isLoading = false; }) } + handleAddToCart(product:any) { + this.isLoading = true; + this.productService.getAddToCartProduct(product) + .subscribe(response=>{ + alert("Product Added Successfully") + this.router.navigateByUrl('/theme/typography'); + this.isLoading = false; + },error=>{ + alert(error) + this.isLoading = false; + }) + } ngAfterContentInit(): void { this.changeDetectorRef.detectChanges(); diff --git a/src/scss/styles.scss b/src/scss/styles.scss index 04f4cc5ac..e04079e12 100644 --- a/src/scss/styles.scss +++ b/src/scss/styles.scss @@ -21,3 +21,33 @@ // We use those styles to show code examples, you should remove them in your application. @use "examples"; +/* The overlay container that covers the whole screen */ +.spinner-overlay { + position: fixed; /* Positions the overlay relative to the viewport */ + top: 0; + left: 0; + width: 100%; + height: 100vh; /* Viewport height ensures it covers the entire screen */ + display: flex; /* Use Flexbox for easy centering */ + justify-content: center; /* Centers horizontally */ + align-items: center; /* Centers vertically */ + background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black background */ + z-index: 9999; /* Ensures it's above all other content */ +} + +/* The spinner itself */ +.loader { + border: 5px solid transparent; /* Transparent border for most of the circle */ + border-top: 5px solid #ffffff; /* Colored border for the spinning part */ + border-radius: 50%; /* Makes it a circle */ + width: 50px; + height: 50px; + animation: spin 1s linear infinite; /* Animation to make it spin */ + /* The loader itself has no background by default, so it's transparent */ +} + +/* Keyframes for the spin animation */ +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} \ No newline at end of file