diff --git a/client/angular.json b/client/angular.json
index d195332..6a13e6b 100644
--- a/client/angular.json
+++ b/client/angular.json
@@ -94,5 +94,8 @@
}
}
}
+ },
+ "cli": {
+ "analytics": false
}
}
diff --git a/client/package-lock.json b/client/package-lock.json
index 197cac3..60f6aa2 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -322,6 +322,7 @@
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.14.tgz",
"integrity": "sha512-Kp/MWShoYYO+R3lrrZbZgszbbLGVXHB+39mdJZwnIuZMDkeL3JsIBlSOzyJRTnpS1vITc+9jgHvP/6uKbMrW1Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -467,6 +468,7 @@
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz",
"integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -518,6 +520,7 @@
"resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.14.tgz",
"integrity": "sha512-ZPRswzaVRiqcfZoowuAM22Hr2/z10ajWOUoFDoQ9tWqz/fH/773kJv2F9VvePIekgNPCzaizqv9gF6tGNqaAwg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -534,6 +537,7 @@
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.14.tgz",
"integrity": "sha512-Mpq3v/mztQzGAQAAFV+wAI1hlXxZ0m8eDBgaN2kD3Ue+r4S6bLm1Vlryw0iyUnt05PcFIdxPT6xkcphq5pl6lw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -555,6 +559,7 @@
"integrity": "sha512-BmmjyrFSBSYkm0tBSqpu4cwnJX/b/XvhM36mj2k8jah3tNS5zLDDx5w6tyHmaPJa/1D95MlXx2h6u7K9D+Mhew==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/core": "7.25.2",
"@jridgewell/sourcemap-codec": "^1.4.14",
@@ -661,6 +666,7 @@
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.14.tgz",
"integrity": "sha512-BIPrCs93ZZTY9ym7yfoTgAQ5rs706yoYeAdrgc8kh/bDbM9DawxKlgeKBx2FLt09Y0YQ1bFhKVp0cV4gDEaMxQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -677,6 +683,7 @@
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.14.tgz",
"integrity": "sha512-fZVwXctmBJa5VdopJae/T9MYKPXNd04+6j4k/6X819y+9fiyWLJt2QicSc5Rc+YD9mmhXag3xaljlrnotf9VGA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -713,6 +720,7 @@
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.14.tgz",
"integrity": "sha512-W+JTxI25su3RiZVZT3Yrw6KNUCmOIy7OZIZ+612skPgYK2f2qil7VclnW1oCwG896h50cMJU/lnAfxZxefQgyQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -797,6 +805,7 @@
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
@@ -3080,6 +3089,7 @@
"integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@inquirer/checkbox": "^2.4.7",
"@inquirer/confirm": "^3.1.22",
@@ -4503,6 +4513,7 @@
"integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -4833,6 +4844,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4909,6 +4921,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -5359,6 +5372,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741",
@@ -8396,7 +8410,8 @@
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.2.0.tgz",
"integrity": "sha512-tSAtdrvWybZkQmmaIoDgnvHG8ORUNw5kEVlO5CvrXj02Jjr9TZrmjFq7FUiOUzJiOP2wLGYT6PgrQgQF4R1xiw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/jest-worker": {
"version": "27.5.1",
@@ -8535,6 +8550,7 @@
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@colors/colors": "1.5.0",
"body-parser": "^1.19.0",
@@ -8806,6 +8822,7 @@
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
@@ -10773,6 +10790,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.1",
@@ -11422,6 +11440,7 @@
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -11478,6 +11497,7 @@
"integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@@ -12573,6 +12593,7 @@
"integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -12712,7 +12733,8 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
+ "license": "0BSD",
+ "peer": true
},
"node_modules/tuf-js": {
"version": "2.2.1",
@@ -12769,6 +12791,7 @@
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -13029,6 +13052,7 @@
"integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@@ -13599,6 +13623,7 @@
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.12.1",
@@ -13676,6 +13701,7 @@
"integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/bonjour": "^3.5.13",
"@types/connect-history-api-fallback": "^1.5.4",
@@ -13828,6 +13854,7 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -14162,7 +14189,8 @@
"version": "0.14.10",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
"integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
}
}
}
diff --git a/client/src/app/app.config.ts b/client/src/app/app.config.ts
index 11ea685..b855b9b 100644
--- a/client/src/app/app.config.ts
+++ b/client/src/app/app.config.ts
@@ -4,7 +4,7 @@ import {provideRouter} from '@angular/router';
import {routes} from './app.routes';
import {provideHttpClient, withInterceptors} from '@angular/common/http';
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
-import {authTokenInterceptor} from './interceptors/authToken/auth-token.interceptor';
+import {authTokenInterceptor} from './interceptors/auth-token.interceptor';
import {AuthService} from './services/auth/auth.service';
import {catchError, firstValueFrom, of} from 'rxjs';
diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts
index fb99875..61efc96 100644
--- a/client/src/app/app.routes.ts
+++ b/client/src/app/app.routes.ts
@@ -3,10 +3,10 @@ import {HomeComponent} from './pages/home/home.component';
import {RegisterComponent} from './pages/register/register.component';
import {LoginComponent} from './pages/login/login.component';
import {ProfileComponent} from './pages/profile/profile.component';
-import {guestOnlyCanActivate, guestOnlyCanMatch} from './guards/guest-only/guest-only.guard';
-import {authOnlyCanActivate, authOnlyCanMatch} from './guards/auth-only/auth-only.guard';
+import {guestOnlyCanActivate, guestOnlyCanMatch} from './guards/guest-only.guard';
+import {authOnlyCanActivate, authOnlyCanMatch} from './guards/auth-only.guard';
import {AdminComponent} from './pages/admin/admin.component';
-import {adminOnlyCanActivate, adminOnlyCanMatch} from './guards/admin-only/admin-only.guard';
+import {adminOnlyCanActivate, adminOnlyCanMatch} from './guards/admin-only.guard';
import {AddProductComponent} from './pages/add-product/add-product.component';
export const routes: Routes = [
diff --git a/client/src/app/components/admin-navbar/admin-navbar.component.css b/client/src/app/components/admin-navbar/admin-navbar.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/client/src/app/components/admin-navbar/admin-navbar.component.html b/client/src/app/components/admin-navbar/admin-navbar.component.html
new file mode 100644
index 0000000..ec470ab
--- /dev/null
+++ b/client/src/app/components/admin-navbar/admin-navbar.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+ Catégories
+
diff --git a/client/src/app/components/admin-navbar/admin-navbar.component.ts b/client/src/app/components/admin-navbar/admin-navbar.component.ts
new file mode 100644
index 0000000..818dea2
--- /dev/null
+++ b/client/src/app/components/admin-navbar/admin-navbar.component.ts
@@ -0,0 +1,28 @@
+import { Component } from '@angular/core';
+import {MatAnchor, MatButton} from "@angular/material/button";
+import {MatIcon} from '@angular/material/icon';
+import {RouterLink, RouterLinkActive} from '@angular/router';
+import {MatTab, MatTabGroup} from '@angular/material/tabs';
+import {BrandsListComponent} from '../brands-list/brands-list.component';
+import {PlatformsListComponent} from '../platforms-list/platforms-list.component';
+
+@Component({
+ selector: 'app-admin-navbar',
+ standalone: true,
+ imports: [
+ MatButton,
+ MatIcon,
+ RouterLinkActive,
+ RouterLink,
+ MatAnchor,
+ MatTabGroup,
+ MatTab,
+ BrandsListComponent,
+ PlatformsListComponent
+ ],
+ templateUrl: './admin-navbar.component.html',
+ styleUrl: './admin-navbar.component.css'
+})
+export class AdminNavbarComponent {
+
+}
diff --git a/client/src/app/components/brand-dialog/brand-dialog.component.css b/client/src/app/components/brand-dialog/brand-dialog.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/client/src/app/components/brand-dialog/brand-dialog.component.html b/client/src/app/components/brand-dialog/brand-dialog.component.html
new file mode 100644
index 0000000..ca584db
--- /dev/null
+++ b/client/src/app/components/brand-dialog/brand-dialog.component.html
@@ -0,0 +1,13 @@
+
{{ brandExists ? 'Modifier la marque' : 'Nouvelle marque' }}
+
+
+
+ Nom
+
+
+
+
+
+
+
+
diff --git a/client/src/app/components/brand-dialog/brand-dialog.component.ts b/client/src/app/components/brand-dialog/brand-dialog.component.ts
new file mode 100644
index 0000000..8d9ce7d
--- /dev/null
+++ b/client/src/app/components/brand-dialog/brand-dialog.component.ts
@@ -0,0 +1,51 @@
+import { Component, Inject } from '@angular/core';
+import {
+ MatDialogRef,
+ MAT_DIALOG_DATA,
+ MatDialogTitle,
+ MatDialogContent,
+ MatDialogActions
+} from '@angular/material/dialog';
+import { Brand } from '../../interfaces/brand';
+import {MatFormField, MatLabel} from '@angular/material/form-field';
+import {MatInput} from '@angular/material/input';
+import {FormsModule} from '@angular/forms';
+import {MatButton} from '@angular/material/button';
+
+@Component({
+ selector: 'app-brand-dialog',
+ standalone: true,
+ imports: [
+ MatDialogTitle,
+ MatDialogContent,
+ MatFormField,
+ MatLabel,
+ MatInput,
+ FormsModule,
+ MatDialogActions,
+ MatButton
+ ],
+ templateUrl: './brand-dialog.component.html'
+})
+export class BrandDialogComponent {
+ brand: Brand = { id: '', name: '' }
+
+ constructor(
+ private readonly dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: { brand: Brand }
+ ) {
+ this.brand = { ...(data?.brand || { id: '', name: '' }) };
+ }
+
+ get brandExists(): boolean {
+ return !!this.data?.brand?.id;
+ }
+
+ save() {
+ this.dialogRef.close(this.brand);
+ }
+
+ cancel() {
+ this.dialogRef.close();
+ }
+}
diff --git a/client/src/app/components/brands-list/brands-list.component.css b/client/src/app/components/brands-list/brands-list.component.css
new file mode 100644
index 0000000..87a4b85
--- /dev/null
+++ b/client/src/app/components/brands-list/brands-list.component.css
@@ -0,0 +1,65 @@
+:host {
+ display: block;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.container {
+ max-width: 900px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+.toolbar {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+}
+
+.filter {
+ max-width: 240px;
+ width: 100%;
+}
+
+table {
+ width: 100%;
+ overflow: auto;
+}
+
+td, th {
+ word-break: break-word;
+ white-space: normal;
+}
+
+.actions-cell {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+ min-width: 120px;
+}
+
+button.mat-icon-button {
+ width: 40px;
+ height: 40px;
+}
+
+.no-brands {
+ text-align: center;
+ margin-top: 16px;
+ color: rgba(0,0,0,0.6);
+ padding: 8px 12px;
+}
+
+@media (max-width: 600px) {
+ .toolbar {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 8px;
+ }
+
+ .actions-cell {
+ min-width: 0;
+ }
+}
diff --git a/client/src/app/components/brands-list/brands-list.component.html b/client/src/app/components/brands-list/brands-list.component.html
new file mode 100644
index 0000000..1ed48ec
--- /dev/null
+++ b/client/src/app/components/brands-list/brands-list.component.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Nom |
+ {{ brand.name }} |
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+ @if (!brands || brands.length === 0) {
+
+ Aucune marque trouvée.
+
+ }
+
diff --git a/client/src/app/components/brands-list/brands-list.component.ts b/client/src/app/components/brands-list/brands-list.component.ts
new file mode 100644
index 0000000..ee01363
--- /dev/null
+++ b/client/src/app/components/brands-list/brands-list.component.ts
@@ -0,0 +1,138 @@
+import {
+ Component,
+ Input,
+ Output,
+ EventEmitter,
+ ViewChild,
+ AfterViewInit,
+ OnChanges,
+ SimpleChanges,
+ OnInit,
+ inject
+} from '@angular/core';
+import {
+ MatCell, MatCellDef,
+ MatColumnDef,
+ MatHeaderCell,
+ MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef,
+ MatTable,
+ MatTableDataSource
+} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {MatSort} from '@angular/material/sort';
+import {Brand} from '../../interfaces/brand';
+import {MatButton, MatIconButton} from '@angular/material/button';
+import {MatIcon} from '@angular/material/icon';
+import {MatFormField} from '@angular/material/form-field';
+import {MatInput} from '@angular/material/input';
+import {BrandService} from '../../services/brand/brand.service';
+import {MatDialog} from '@angular/material/dialog';
+import { BrandDialogComponent } from '../brand-dialog/brand-dialog.component';
+
+@Component({
+ selector: 'app-brands-list',
+ templateUrl: './brands-list.component.html',
+ standalone: true,
+ imports: [
+ MatButton,
+ MatIcon,
+ MatFormField,
+ MatInput,
+ MatTable,
+ MatColumnDef,
+ MatHeaderCell,
+ MatCell,
+ MatHeaderCellDef,
+ MatCellDef,
+ MatSort,
+ MatIconButton,
+ MatHeaderRow,
+ MatRow,
+ MatHeaderRowDef,
+ MatRowDef,
+ MatPaginator
+ ],
+ styleUrls: ['./brands-list.component.css']
+})
+export class BrandsListComponent implements OnInit, AfterViewInit, OnChanges {
+
+ @Input() brands: Brand[] = [];
+ @Output() add = new EventEmitter();
+ @Output() edit = new EventEmitter();
+ @Output() delete = new EventEmitter();
+
+ displayedColumns: string[] = ['name', 'actions'];
+ dataSource = new MatTableDataSource([]);
+
+ @ViewChild(MatPaginator) paginator!: MatPaginator;
+ @ViewChild(MatSort) sort!: MatSort;
+
+ private readonly brandService: BrandService = inject(BrandService);
+ private readonly dialog = inject(MatDialog);
+
+ ngOnInit(): void {
+ if (!this.brands || this.brands.length === 0) {
+ this.loadBrands();
+ } else {
+ this.dataSource.data = this.brands;
+ }
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['brands']) {
+ this.dataSource.data = this.brands || [];
+ }
+ }
+
+ ngAfterViewInit(): void {
+ this.dataSource.paginator = this.paginator;
+ this.dataSource.sort = this.sort;
+ }
+
+ loadBrands() {
+ this.brandService.getBrands().subscribe({
+ next: (brands:Brand[]) => {
+ this.brands = brands || []
+ this.dataSource.data = this.brands;
+ },
+ error: () => this.brands = []
+ });
+ }
+
+ onAdd(): void {
+ const ref = this.dialog.open(BrandDialogComponent, {
+ data: { brand: { id: '', name: '' } },
+ width: '420px'
+ });
+
+ ref.afterClosed().subscribe((result?: Brand) => {
+ if (result) {
+ this.add.emit(result);
+ this.brandService.addBrand(result).subscribe(() => this.loadBrands());
+ }
+ });
+ }
+
+ onEdit(brand: Brand): void {
+ const ref = this.dialog.open(BrandDialogComponent, {
+ data: { brand: { ...brand } },
+ width: '420px'
+ });
+
+ ref.afterClosed().subscribe((result?: Brand) => {
+ if (result) {
+ this.edit.emit(result);
+ this.brandService.updateBrand((brand as any).id, result).subscribe(() => this.loadBrands());
+ }
+ });
+ }
+
+ onDelete(brand: Brand): void {
+ this.delete.emit(brand);
+ this.brandService.deleteBrand((brand as any).id).subscribe(() => this.loadBrands());
+ }
+
+ applyFilter(value: string): void {
+ this.dataSource.filter = (value || '').trim().toLowerCase();
+ }
+}
diff --git a/client/src/app/components/platform-dialog/platform-dialog.component.css b/client/src/app/components/platform-dialog/platform-dialog.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/client/src/app/components/platform-dialog/platform-dialog.component.html b/client/src/app/components/platform-dialog/platform-dialog.component.html
new file mode 100644
index 0000000..e236f44
--- /dev/null
+++ b/client/src/app/components/platform-dialog/platform-dialog.component.html
@@ -0,0 +1,21 @@
+{{ platformExists ? 'Modifier la plateforme' : 'Nouvelle plateforme' }}
+
+
+
+ Nom
+
+
+
+ Marque
+
+ @for (brand of brands; track brand.id) {
+ {{ brand.name }}
+ }
+
+
+
+
+
+
+
+
diff --git a/client/src/app/components/platform-dialog/platform-dialog.component.ts b/client/src/app/components/platform-dialog/platform-dialog.component.ts
new file mode 100644
index 0000000..2d93833
--- /dev/null
+++ b/client/src/app/components/platform-dialog/platform-dialog.component.ts
@@ -0,0 +1,74 @@
+import {Component, inject, Inject, OnInit} from '@angular/core';
+import {MatButton} from "@angular/material/button";
+import {
+ MAT_DIALOG_DATA,
+ MatDialogActions,
+ MatDialogContent,
+ MatDialogRef,
+ MatDialogTitle
+} from "@angular/material/dialog";
+import {MatFormField, MatLabel} from "@angular/material/form-field";
+import {MatInput} from "@angular/material/input";
+import {FormsModule, ReactiveFormsModule} from "@angular/forms";
+import {Brand} from '../../interfaces/brand';
+import {Platform} from '../../interfaces/platform';
+import {MatOption} from '@angular/material/core';
+import {MatSelect} from '@angular/material/select';
+import {BrandService} from '../../services/brand/brand.service';
+
+@Component({
+ selector: 'app-platform-dialog',
+ standalone: true,
+ imports: [
+ MatButton,
+ MatDialogActions,
+ MatDialogContent,
+ MatDialogTitle,
+ MatFormField,
+ MatInput,
+ MatLabel,
+ ReactiveFormsModule,
+ FormsModule,
+ MatOption,
+ MatSelect
+ ],
+ templateUrl: './platform-dialog.component.html',
+ styleUrl: './platform-dialog.component.css'
+})
+export class PlatformDialogComponent implements OnInit {
+
+ private readonly brandService: BrandService = inject(BrandService);
+
+ platform: Platform = { id: '', name: '', brand: undefined };
+ brands: Brand[] = [];
+
+ constructor(
+ private readonly dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: { platform: Platform }
+ ) {
+ this.platform = { ...data.platform };
+ }
+
+ ngOnInit(): void {
+ this.loadBrands();
+ }
+
+ get platformExists(): boolean {
+ return !!this.data?.platform?.id;
+ }
+
+ loadBrands() {
+ this.brandService.getBrands().subscribe({
+ next: (brands:Brand[]) => this.brands = brands || [],
+ error: () => this.brands = []
+ });
+ }
+
+ save() {
+ this.dialogRef.close(this.platform);
+ }
+
+ cancel() {
+ this.dialogRef.close();
+ }
+}
diff --git a/client/src/app/components/platforms-list/platforms-list.component.css b/client/src/app/components/platforms-list/platforms-list.component.css
new file mode 100644
index 0000000..87a4b85
--- /dev/null
+++ b/client/src/app/components/platforms-list/platforms-list.component.css
@@ -0,0 +1,65 @@
+:host {
+ display: block;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.container {
+ max-width: 900px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+.toolbar {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+}
+
+.filter {
+ max-width: 240px;
+ width: 100%;
+}
+
+table {
+ width: 100%;
+ overflow: auto;
+}
+
+td, th {
+ word-break: break-word;
+ white-space: normal;
+}
+
+.actions-cell {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+ min-width: 120px;
+}
+
+button.mat-icon-button {
+ width: 40px;
+ height: 40px;
+}
+
+.no-brands {
+ text-align: center;
+ margin-top: 16px;
+ color: rgba(0,0,0,0.6);
+ padding: 8px 12px;
+}
+
+@media (max-width: 600px) {
+ .toolbar {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 8px;
+ }
+
+ .actions-cell {
+ min-width: 0;
+ }
+}
diff --git a/client/src/app/components/platforms-list/platforms-list.component.html b/client/src/app/components/platforms-list/platforms-list.component.html
new file mode 100644
index 0000000..01dc89f
--- /dev/null
+++ b/client/src/app/components/platforms-list/platforms-list.component.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Nom |
+ {{ platform.name }} |
+
+
+
+
+ Marque |
+ {{ platform.brand.name }} |
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+ @if (!platforms || platforms.length === 0) {
+
+ Aucune plateforme trouvée.
+
+ }
+
diff --git a/client/src/app/components/platforms-list/platforms-list.component.ts b/client/src/app/components/platforms-list/platforms-list.component.ts
new file mode 100644
index 0000000..ea689fe
--- /dev/null
+++ b/client/src/app/components/platforms-list/platforms-list.component.ts
@@ -0,0 +1,139 @@
+import {
+ Component,
+ Input,
+ Output,
+ EventEmitter,
+ ViewChild,
+ AfterViewInit,
+ OnChanges,
+ SimpleChanges,
+ OnInit,
+ inject
+} from '@angular/core';
+import {
+ MatCell, MatCellDef,
+ MatColumnDef,
+ MatHeaderCell,
+ MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef,
+ MatTable,
+ MatTableDataSource
+} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {MatSort} from '@angular/material/sort';
+import {Platform} from '../../interfaces/platform';
+import {MatButton, MatIconButton} from '@angular/material/button';
+import {MatIcon} from '@angular/material/icon';
+import {MatFormField} from '@angular/material/form-field';
+import {MatInput} from '@angular/material/input';
+import {PlatformService} from '../../services/platform/platform.service';
+import {MatDialog} from '@angular/material/dialog';
+import { PlatformDialogComponent } from '../platform-dialog/platform-dialog.component';
+
+@Component({
+ selector: 'app-platforms-list',
+ templateUrl: './platforms-list.component.html',
+ standalone: true,
+ imports: [
+ MatButton,
+ MatIcon,
+ MatFormField,
+ MatInput,
+ MatTable,
+ MatColumnDef,
+ MatHeaderCell,
+ MatCell,
+ MatHeaderCellDef,
+ MatCellDef,
+ MatSort,
+ MatIconButton,
+ MatHeaderRow,
+ MatRow,
+ MatHeaderRowDef,
+ MatRowDef,
+ MatPaginator
+ ],
+ styleUrls: ['./platforms-list.component.css']
+})
+export class PlatformsListComponent implements OnInit, AfterViewInit, OnChanges {
+
+ @Input() platforms: Platform[] = [];
+ @Output() add = new EventEmitter();
+ @Output() edit = new EventEmitter();
+ @Output() delete = new EventEmitter();
+
+ displayedColumns: string[] = ['name', 'brand', 'actions'];
+ dataSource = new MatTableDataSource([]);
+
+ @ViewChild(MatPaginator) paginator!: MatPaginator;
+ @ViewChild(MatSort) sort!: MatSort;
+
+ private readonly platformService: PlatformService = inject(PlatformService);
+ private readonly dialog = inject(MatDialog);
+
+ ngOnInit(): void {
+ if (!this.platforms || this.platforms.length === 0) {
+ this.loadPlatforms();
+ } else {
+ this.dataSource.data = this.platforms;
+ }
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['platforms']) {
+ this.dataSource.data = this.platforms || [];
+ }
+ }
+
+ ngAfterViewInit(): void {
+ this.dataSource.paginator = this.paginator;
+ this.dataSource.sort = this.sort;
+ }
+
+ loadPlatforms() {
+ this.platformService.getPlatforms().subscribe({
+ next: (platforms:Platform[]) => {
+ this.platforms = platforms || []
+ this.dataSource.data = this.platforms;
+ console.log("Fetched platforms:", this.platforms);
+ },
+ error: () => this.platforms = []
+ });
+ }
+
+ onAdd(): void {
+ const ref = this.dialog.open(PlatformDialogComponent, {
+ data: { platform: { id: '', name: '', brand: undefined } },
+ width: '420px'
+ });
+
+ ref.afterClosed().subscribe((result?: Platform) => {
+ if (result) {
+ this.add.emit(result);
+ this.platformService.addPlatform(result).subscribe(() => this.loadPlatforms());
+ }
+ });
+ }
+
+ onEdit(platform: Platform): void {
+ const ref = this.dialog.open(PlatformDialogComponent, {
+ data: { platform: { ...platform } },
+ width: '420px'
+ });
+
+ ref.afterClosed().subscribe((result?: Platform) => {
+ if (result) {
+ this.edit.emit(result);
+ this.platformService.updatePlatform((platform as any).id, result).subscribe(() => this.loadPlatforms());
+ }
+ });
+ }
+
+ onDelete(platform: Platform): void {
+ this.delete.emit(platform);
+ this.platformService.deletePlatform((platform as any).id).subscribe(() => this.loadPlatforms());
+ }
+
+ applyFilter(value: string): void {
+ this.dataSource.filter = (value || '').trim().toLowerCase();
+ }
+}
diff --git a/client/src/app/guards/admin-only/admin-only.guard.ts b/client/src/app/guards/admin-only.guard.ts
similarity index 94%
rename from client/src/app/guards/admin-only/admin-only.guard.ts
rename to client/src/app/guards/admin-only.guard.ts
index cfee37b..498aa06 100644
--- a/client/src/app/guards/admin-only/admin-only.guard.ts
+++ b/client/src/app/guards/admin-only.guard.ts
@@ -1,6 +1,6 @@
import { inject } from '@angular/core';
import { CanActivateFn, CanMatchFn, Router, UrlTree, ActivatedRouteSnapshot, Route } from '@angular/router';
-import { AuthService } from '../../services/auth/auth.service';
+import { AuthService } from '../services/auth/auth.service';
function requireAdmin(url?: string): boolean | UrlTree {
const authService: AuthService = inject(AuthService);
diff --git a/client/src/app/guards/admin-only/admin-only.guard.spec.ts b/client/src/app/guards/admin-only/admin-only.guard.spec.ts
deleted file mode 100644
index d0e6ca2..0000000
--- a/client/src/app/guards/admin-only/admin-only.guard.spec.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { CanActivateFn } from '@angular/router';
-
-import { adminOnlyGuard } from './admin-only.guard';
-
-describe('adminOnlyGuard', () => {
- const executeGuard: CanActivateFn = (...guardParameters) =>
- TestBed.runInInjectionContext(() => adminOnlyGuard(...guardParameters));
-
- beforeEach(() => {
- TestBed.configureTestingModule({});
- });
-
- it('should be created', () => {
- expect(executeGuard).toBeTruthy();
- });
-});
diff --git a/client/src/app/guards/auth-only/auth-only.guard.ts b/client/src/app/guards/auth-only.guard.ts
similarity index 92%
rename from client/src/app/guards/auth-only/auth-only.guard.ts
rename to client/src/app/guards/auth-only.guard.ts
index 921dc6d..9e6473c 100644
--- a/client/src/app/guards/auth-only/auth-only.guard.ts
+++ b/client/src/app/guards/auth-only.guard.ts
@@ -1,6 +1,6 @@
import { inject } from '@angular/core';
import { CanActivateFn, CanMatchFn, Router, UrlTree, ActivatedRouteSnapshot, Route } from '@angular/router';
-import { AuthService } from '../../services/auth/auth.service';
+import { AuthService } from '../services/auth/auth.service';
function requireAuth(url?: string): boolean | UrlTree {
const authService = inject(AuthService);
diff --git a/client/src/app/guards/auth-only/auth-only.guard.spec.ts b/client/src/app/guards/auth-only/auth-only.guard.spec.ts
deleted file mode 100644
index 05865b2..0000000
--- a/client/src/app/guards/auth-only/auth-only.guard.spec.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { CanMatchFn } from '@angular/router';
-
-import { authOnlyGuard } from './auth-only.guard';
-
-describe('authOnlyGuard', () => {
- const executeGuard: CanMatchFn = (...guardParameters) =>
- TestBed.runInInjectionContext(() => authOnlyGuard(...guardParameters));
-
- beforeEach(() => {
- TestBed.configureTestingModule({});
- });
-
- it('should be created', () => {
- expect(executeGuard).toBeTruthy();
- });
-});
diff --git a/client/src/app/guards/guest-only/guest-only.guard.ts b/client/src/app/guards/guest-only.guard.ts
similarity index 89%
rename from client/src/app/guards/guest-only/guest-only.guard.ts
rename to client/src/app/guards/guest-only.guard.ts
index 9d23ec1..e37c6f0 100644
--- a/client/src/app/guards/guest-only/guest-only.guard.ts
+++ b/client/src/app/guards/guest-only.guard.ts
@@ -1,6 +1,6 @@
import { inject } from '@angular/core';
import { Router, UrlTree, CanActivateFn, CanMatchFn } from '@angular/router';
-import { AuthService } from '../../services/auth/auth.service';
+import { AuthService } from '../services/auth/auth.service';
function redirectIfLoggedIn(): boolean | UrlTree {
const authService = inject(AuthService);
diff --git a/client/src/app/guards/guest-only/guest-only.guard.spec.ts b/client/src/app/guards/guest-only/guest-only.guard.spec.ts
deleted file mode 100644
index c26b600..0000000
--- a/client/src/app/guards/guest-only/guest-only.guard.spec.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { CanMatchFn } from '@angular/router';
-
-import { guestOnlyGuard } from './guest-only.guard';
-
-describe('guestOnlyGuard', () => {
- const executeGuard: CanMatchFn = (...guardParameters) =>
- TestBed.runInInjectionContext(() => guestOnlyGuard(...guardParameters));
-
- beforeEach(() => {
- TestBed.configureTestingModule({});
- });
-
- it('should be created', () => {
- expect(executeGuard).toBeTruthy();
- });
-});
diff --git a/client/src/app/interceptors/authToken/auth-token.interceptor.ts b/client/src/app/interceptors/auth-token.interceptor.ts
similarity index 94%
rename from client/src/app/interceptors/authToken/auth-token.interceptor.ts
rename to client/src/app/interceptors/auth-token.interceptor.ts
index a2fe627..e219fc1 100644
--- a/client/src/app/interceptors/authToken/auth-token.interceptor.ts
+++ b/client/src/app/interceptors/auth-token.interceptor.ts
@@ -1,6 +1,6 @@
import {HttpErrorResponse, HttpInterceptorFn} from '@angular/common/http';
import {inject} from '@angular/core';
-import {AuthService} from '../../services/auth/auth.service';
+import {AuthService} from '../services/auth/auth.service';
import {catchError, switchMap, throwError} from 'rxjs';
let isRefreshing = false;
@@ -19,7 +19,7 @@ export const authTokenInterceptor: HttpInterceptorFn = (req, next) => {
catchError((error: any) => {
const is401 = error instanceof HttpErrorResponse && error.status === 401;
- // si 401 et pas déjà en refresh, tente un refresh puis rejoue la requête 1 fois
+ // si 401 et pas déjà en refresh, tente un refresh puis rejoue la requête une fois
if (is401 && !isRefreshing) {
isRefreshing = true;
return inject(AuthService).refresh().pipe(
diff --git a/client/src/app/interceptors/authToken/auth-token.interceptor.spec.ts b/client/src/app/interceptors/authToken/auth-token.interceptor.spec.ts
deleted file mode 100644
index 26fa57c..0000000
--- a/client/src/app/interceptors/authToken/auth-token.interceptor.spec.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { HttpInterceptorFn } from '@angular/common/http';
-
-import { authTokenInterceptor } from './auth-token.interceptor';
-
-describe('authTokenInterceptor', () => {
- const interceptor: HttpInterceptorFn = (req, next) =>
- TestBed.runInInjectionContext(() => authTokenInterceptor(req, next));
-
- beforeEach(() => {
- TestBed.configureTestingModule({});
- });
-
- it('should be created', () => {
- expect(interceptor).toBeTruthy();
- });
-});
diff --git a/client/src/app/interfaces/brand.ts b/client/src/app/interfaces/brand.ts
new file mode 100644
index 0000000..0f6d120
--- /dev/null
+++ b/client/src/app/interfaces/brand.ts
@@ -0,0 +1,4 @@
+export interface Brand {
+ id: string | number;
+ name: string;
+}
diff --git a/client/src/app/interfaces/credentials/credentials.ts b/client/src/app/interfaces/credentials.ts
similarity index 100%
rename from client/src/app/interfaces/credentials/credentials.ts
rename to client/src/app/interfaces/credentials.ts
diff --git a/client/src/app/interfaces/credentials/credentials.spec.ts b/client/src/app/interfaces/credentials/credentials.spec.ts
deleted file mode 100644
index 387c546..0000000
--- a/client/src/app/interfaces/credentials/credentials.spec.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Credentials } from './credentials';
-
-describe('Credentials', () => {
- it('should create an instance', () => {
- expect(new Credentials()).toBeTruthy();
- });
-});
diff --git a/client/src/app/interfaces/platform.ts b/client/src/app/interfaces/platform.ts
new file mode 100644
index 0000000..9155fcb
--- /dev/null
+++ b/client/src/app/interfaces/platform.ts
@@ -0,0 +1,7 @@
+import {Brand} from './brand';
+
+export interface Platform {
+ id: string | number;
+ name: string;
+ brand: Brand | undefined;
+}
diff --git a/client/src/app/interfaces/user.ts b/client/src/app/interfaces/user.ts
new file mode 100644
index 0000000..0f9a309
--- /dev/null
+++ b/client/src/app/interfaces/user.ts
@@ -0,0 +1,7 @@
+export interface User {
+ firstName: string;
+ lastName: string;
+ username: string;
+ email: string ;
+ role: string;
+}
diff --git a/client/src/app/models/brand/brand.spec.ts b/client/src/app/models/brand/brand.spec.ts
deleted file mode 100644
index 75cd13b..0000000
--- a/client/src/app/models/brand/brand.spec.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Brand } from './brand';
-
-describe('Brand', () => {
- it('should create an instance', () => {
- expect(new Brand()).toBeTruthy();
- });
-});
diff --git a/client/src/app/models/brand/brand.ts b/client/src/app/models/brand/brand.ts
deleted file mode 100644
index 8408f28..0000000
--- a/client/src/app/models/brand/brand.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export class Brand {
- name: string = '';
-}
diff --git a/client/src/app/models/user/user.model.spec.ts b/client/src/app/models/user/user.model.spec.ts
deleted file mode 100644
index c5ec2e7..0000000
--- a/client/src/app/models/user/user.model.spec.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { UserModel } from './user.model';
-
-describe('UserModel', () => {
- it('should create an instance', () => {
- expect(new UserModel()).toBeTruthy();
- });
-});
diff --git a/client/src/app/models/user/user.model.ts b/client/src/app/models/user/user.model.ts
deleted file mode 100644
index 94188b4..0000000
--- a/client/src/app/models/user/user.model.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export class User {
- firstName: string = '';
- lastName: string = '';
- username: string = '';
- email: string = '';
- role: string = '';
-}
diff --git a/client/src/app/pages/add-product/add-product.component.html b/client/src/app/pages/add-product/add-product.component.html
index 41ab698..84487e4 100644
--- a/client/src/app/pages/add-product/add-product.component.html
+++ b/client/src/app/pages/add-product/add-product.component.html
@@ -1,8 +1,7 @@
- Gestion des produits
- Create a new account
+ Ajouter un produit
@@ -61,7 +60,7 @@
Marque
- @for (brand of brands; track brand.name) {
+ @for (brand of brands; track brand.id) {
{{ brand.name }}
}
@@ -71,9 +70,9 @@
Plateforme
- Option 1
- Option 2
- Option 3
+ @for (platform of platforms; track platform.id) {
+ {{ platform.name }}
+ }
diff --git a/client/src/app/pages/add-product/add-product.component.spec.ts b/client/src/app/pages/add-product/add-product.component.spec.ts
deleted file mode 100644
index b8d7fed..0000000
--- a/client/src/app/pages/add-product/add-product.component.spec.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { AddProductComponent } from './add-product.component';
-
-describe('AddProductComponent', () => {
- let component: AddProductComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [AddProductComponent]
- })
- .compileComponents();
-
- fixture = TestBed.createComponent(AddProductComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/client/src/app/pages/add-product/add-product.component.ts b/client/src/app/pages/add-product/add-product.component.ts
index acc6d1d..e9e4f9c 100644
--- a/client/src/app/pages/add-product/add-product.component.ts
+++ b/client/src/app/pages/add-product/add-product.component.ts
@@ -12,7 +12,6 @@ import {
MatCardActions,
MatCardContent,
MatCardHeader,
- MatCardSubtitle,
MatCardTitle
} from "@angular/material/card";
import {MatCheckbox} from "@angular/material/checkbox";
@@ -24,7 +23,9 @@ import {MatOption, MatSelect} from '@angular/material/select';
import {Router, RouterLink} from '@angular/router';
import {Subscription} from 'rxjs';
import {BrandService} from '../../services/brand/brand.service';
-import {Brand} from '../../models/brand/brand';
+import {Brand} from '../../interfaces/brand';
+import {PlatformService} from '../../services/platform/platform.service';
+import {Platform} from '../../interfaces/platform';
@Component({
selector: 'app-add-product',
@@ -36,7 +37,6 @@ import {Brand} from '../../models/brand/brand';
MatCardActions,
MatCardContent,
MatCardHeader,
- MatCardSubtitle,
MatCardTitle,
MatCheckbox,
MatDivider,
@@ -60,11 +60,13 @@ export class AddProductComponent implements OnInit, OnDestroy {
isLoading = false;
brands: Brand[] = [];
+ platforms: Platform[] = [];
private readonly router: Router = inject(Router);
- private addProductSubscription: Subscription | null = null;
+ private readonly addProductSubscription: Subscription | null = null;
private readonly brandService: BrandService = inject(BrandService);
+ private readonly platformService = inject(PlatformService);
constructor(private readonly formBuilder: FormBuilder) {
this.addProductForm = this.formBuilder.group({
@@ -126,6 +128,18 @@ export class AddProductComponent implements OnInit, OnDestroy {
console.log('Finished fetching brands:', this.brands);
}
});
+
+ this.platformService.getPlatforms().subscribe({
+ next: (platforms) => {
+ this.platforms = platforms;
+ },
+ error: (error) => {
+ console.error('Error fetching platforms:', error);
+ },
+ complete: () => {
+ console.log('Finished fetching platforms:', this.platforms);
+ }
+ });
}
ngOnDestroy(): void {
diff --git a/client/src/app/pages/admin/admin.component.html b/client/src/app/pages/admin/admin.component.html
index 49659db..3481ed4 100644
--- a/client/src/app/pages/admin/admin.component.html
+++ b/client/src/app/pages/admin/admin.component.html
@@ -1 +1 @@
-admin works!
+
diff --git a/client/src/app/pages/admin/admin.component.spec.ts b/client/src/app/pages/admin/admin.component.spec.ts
deleted file mode 100644
index 617b55b..0000000
--- a/client/src/app/pages/admin/admin.component.spec.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { AdminComponent } from './admin.component';
-
-describe('AdminComponent', () => {
- let component: AdminComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [AdminComponent]
- })
- .compileComponents();
-
- fixture = TestBed.createComponent(AdminComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/client/src/app/pages/admin/admin.component.ts b/client/src/app/pages/admin/admin.component.ts
index 087b4f4..8d59fcd 100644
--- a/client/src/app/pages/admin/admin.component.ts
+++ b/client/src/app/pages/admin/admin.component.ts
@@ -1,12 +1,15 @@
import { Component } from '@angular/core';
+import {AdminNavbarComponent} from '../../components/admin-navbar/admin-navbar.component';
@Component({
selector: 'app-admin',
- standalone: true,
- imports: [],
templateUrl: './admin.component.html',
- styleUrl: './admin.component.css'
+ standalone: true,
+ imports: [
+ AdminNavbarComponent
+ ],
+ styleUrls: ['./admin.component.scss']
})
-export class AdminComponent {
+export class AdminComponent{
}
diff --git a/client/src/app/pages/login/login.component.ts b/client/src/app/pages/login/login.component.ts
index 586e8f5..847f8e2 100644
--- a/client/src/app/pages/login/login.component.ts
+++ b/client/src/app/pages/login/login.component.ts
@@ -4,8 +4,8 @@ import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
import {AuthService} from '../../services/auth/auth.service';
import {Router} from '@angular/router';
import {Subscription} from 'rxjs';
-import {Credentials} from '../../interfaces/credentials/credentials';
-import {User} from '../../models/user/user.model';
+import {Credentials} from '../../interfaces/credentials';
+import {User} from '../../interfaces/user';
import {MatInput} from '@angular/material/input';
import {MatButton} from '@angular/material/button';
diff --git a/client/src/app/pages/profile/profile.component.ts b/client/src/app/pages/profile/profile.component.ts
index 44b7d58..1529198 100644
--- a/client/src/app/pages/profile/profile.component.ts
+++ b/client/src/app/pages/profile/profile.component.ts
@@ -10,7 +10,7 @@ import {
import {MatIcon} from '@angular/material/icon';
import {MatButton} from '@angular/material/button';
import {AuthService} from '../../services/auth/auth.service';
-import {User} from '../../models/user/user.model';
+import {User} from '../../interfaces/user';
import {Router} from '@angular/router';
@Component({
diff --git a/client/src/app/services/auth/auth.service.spec.ts b/client/src/app/services/auth/auth.service.spec.ts
deleted file mode 100644
index f37fac4..0000000
--- a/client/src/app/services/auth/auth.service.spec.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-
-import { AuthService } from './auth.service';
-
-describe('LoginService', () => {
- let service: AuthService;
-
- beforeEach(() => {
- TestBed.configureTestingModule({});
- service = TestBed.inject(AuthService);
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-});
diff --git a/client/src/app/services/auth/auth.service.ts b/client/src/app/services/auth/auth.service.ts
index 8db180a..aedd398 100644
--- a/client/src/app/services/auth/auth.service.ts
+++ b/client/src/app/services/auth/auth.service.ts
@@ -1,8 +1,8 @@
import {inject, Injectable, signal} from '@angular/core';
import {catchError, map, of, switchMap, tap} from 'rxjs';
-import {Credentials} from '../../interfaces/credentials/credentials';
+import {Credentials} from '../../interfaces/credentials';
import {HttpClient} from '@angular/common/http';
-import {User} from '../../models/user/user.model';
+import {User} from '../../interfaces/user';
@Injectable({
providedIn: 'root'
diff --git a/client/src/app/services/brand/brand.service.spec.ts b/client/src/app/services/brand/brand.service.spec.ts
deleted file mode 100644
index 9b88b26..0000000
--- a/client/src/app/services/brand/brand.service.spec.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-
-import { BrandService } from './brand.service';
-
-describe('BrandService', () => {
- let service: BrandService;
-
- beforeEach(() => {
- TestBed.configureTestingModule({});
- service = TestBed.inject(BrandService);
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-});
diff --git a/client/src/app/services/brand/brand.service.ts b/client/src/app/services/brand/brand.service.ts
index 51f8096..2c43e9c 100644
--- a/client/src/app/services/brand/brand.service.ts
+++ b/client/src/app/services/brand/brand.service.ts
@@ -1,6 +1,6 @@
import {inject, Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
-import {Brand} from '../../models/brand/brand';
+import {Brand} from '../../interfaces/brand';
@Injectable({
providedIn: 'root'
@@ -8,9 +8,24 @@ import {Brand} from '../../models/brand/brand';
export class BrandService {
private readonly http = inject(HttpClient);
- private readonly BASE_URL = 'http://localhost:3000/brands';
+ private readonly BASE_URL = 'http://localhost:3000/api/brands';
getBrands() {
return this.http.get(this.BASE_URL, {withCredentials: true});
}
+
+ addBrand(brand: Brand) {
+ console.log("Adding brand:", brand);
+ return this.http.post(this.BASE_URL, brand, {withCredentials: true});
+ }
+
+ updateBrand(id: string, brand: Brand) {
+ console.log("Updating brand:", id, brand);
+ return this.http.put(`${this.BASE_URL}/${id}`, brand, {withCredentials: true});
+ }
+
+ deleteBrand(id: string) {
+ console.log("Deleting brand:", id);
+ return this.http.delete(`${this.BASE_URL}/${id}`, {withCredentials: true});
+ }
}
diff --git a/client/src/app/services/platform/platform.service.ts b/client/src/app/services/platform/platform.service.ts
new file mode 100644
index 0000000..b717eac
--- /dev/null
+++ b/client/src/app/services/platform/platform.service.ts
@@ -0,0 +1,31 @@
+import {inject, Injectable} from '@angular/core';
+import {HttpClient} from '@angular/common/http';
+import {Platform} from '../../interfaces/platform';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class PlatformService {
+
+ private readonly http = inject(HttpClient);
+ private readonly BASE_URL = 'http://localhost:3000/api/platforms';
+
+ getPlatforms() {
+ return this.http.get(this.BASE_URL, {withCredentials: true});
+ }
+
+ addPlatform(platform: Platform) {
+ console.log("Adding platform:", platform);
+ return this.http.post(this.BASE_URL, platform, {withCredentials: true});
+ }
+
+ updatePlatform(id: string, platform: Platform) {
+ console.log("Updating platform:", id, platform);
+ return this.http.put(`${this.BASE_URL}/${id}`, platform, {withCredentials: true});
+ }
+
+ deletePlatform(id: string) {
+ console.log("Deleting platform:", id);
+ return this.http.delete(`${this.BASE_URL}/${id}`, {withCredentials: true});
+ }
+}
diff --git a/client/src/app/services/product/product.service.spec.ts b/client/src/app/services/product/product.service.spec.ts
deleted file mode 100644
index d5c493e..0000000
--- a/client/src/app/services/product/product.service.spec.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-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();
- });
-});