From 601bf256b846d427befe347ebab2dd1a820cd91a Mon Sep 17 00:00:00 2001 From: DavideViolante Date: Tue, 3 Feb 2026 11:40:30 +0100 Subject: [PATCH 01/52] chore: add prettier config in package, remove angular animations and lang service --- package-lock.json | 44 ++++++++------------------------------------ package.json | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8689cd1d..05f76200 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "21.1.2", "license": "MIT", "dependencies": { - "@angular/animations": "^21.1.2", "@angular/common": "^21.1.2", "@angular/compiler": "^21.1.2", "@angular/core": "^21.1.2", @@ -34,14 +33,13 @@ "@angular/build": "^21.1.2", "@angular/cli": "^21.1.2", "@angular/compiler-cli": "^21.1.2", - "@angular/language-service": "^21.1.2", "@types/bcryptjs": "^2.4.6", "@types/express": "^4.17.23", "@types/jasmine": "~5.1.0", "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", - "@types/node": "^22.15.29", + "@types/node": "^24", "@types/supertest": "^6.0.3", "angular-eslint": "21.2.0", "concurrently": "^9.2.1", @@ -678,22 +676,6 @@ "typescript": "*" } }, - "node_modules/@angular/animations": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.2.tgz", - "integrity": "sha512-8lVSH3y/Pq22ND9ng80UQwQRiIPIE7oD3vuV98Wufld59+s5g4PdJNqPhEVD5dkYD0gYQcm3jTIXSeYuOfpsUg==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/core": "21.1.2" - } - }, "node_modules/@angular/build": { "version": "21.1.2", "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.2.tgz", @@ -1253,16 +1235,6 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular/language-service": { - "version": "21.1.2", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.2.tgz", - "integrity": "sha512-/2VXz08k0BVQoYiDv/AyQgDY9AVzFuo29I/OAh28za58ReiXkT/WOWgP4el1rewX4uxWnM+BEpYxC3hcc+Ls0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, "node_modules/@angular/platform-browser": { "version": "21.1.2", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.2.tgz", @@ -6724,14 +6696,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz", - "integrity": "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==", + "version": "24.10.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.10.tgz", + "integrity": "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/qs": { @@ -17060,9 +17032,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index a1dd6c24..9bd91eeb 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,20 @@ "lint:fix": "ng lint --fix", "prepare": "husky || true" }, + "prettier": { + "printWidth": 100, + "singleQuote": true, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ] + }, "private": true, "dependencies": { - "@angular/animations": "^21.1.2", "@angular/common": "^21.1.2", "@angular/compiler": "^21.1.2", "@angular/core": "^21.1.2", @@ -50,14 +61,13 @@ "@angular/build": "^21.1.2", "@angular/cli": "^21.1.2", "@angular/compiler-cli": "^21.1.2", - "@angular/language-service": "^21.1.2", "@types/bcryptjs": "^2.4.6", "@types/express": "^4.17.23", "@types/jasmine": "~5.1.0", "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/morgan": "^1.9.10", - "@types/node": "^22.15.29", + "@types/node": "^24", "@types/supertest": "^6.0.3", "angular-eslint": "21.2.0", "concurrently": "^9.2.1", From d9f3719633aaacca33c7103aea29919433c5cf8e Mon Sep 17 00:00:00 2001 From: DavideViolante Date: Tue, 3 Feb 2026 15:25:37 +0100 Subject: [PATCH 02/52] chore: update gitignore and editorconfig --- .editorconfig | 1 + .gitignore | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index 59d9a3a3..f166060d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,7 @@ trim_trailing_whitespace = true [*.ts] quote_type = single +ij_typescript_use_double_quotes = false [*.md] max_line_length = off diff --git a/.gitignore b/.gitignore index c05f21da..31688422 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ yarn-error.log !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +!.vscode/mcp.json .history/* # Miscellaneous @@ -36,6 +37,7 @@ yarn-error.log /libpeerconnection.log testem.log /typings +__screenshots__/ # System files .DS_Store From 06877e723d59a9dac96f7dc9849f26c85a1cebe7 Mon Sep 17 00:00:00 2001 From: DavideViolante Date: Tue, 3 Feb 2026 15:25:49 +0100 Subject: [PATCH 03/52] chore: add agents.md and copilot istructions --- .github/copilot-instructions.md | 56 +++++++++++++++++++++++++++++++++ AGENTS.md | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 AGENTS.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..34c3b360 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,56 @@ + +You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices. + +## TypeScript Best Practices + +- Use strict type checking +- Prefer type inference when the type is obvious +- Avoid the `any` type; use `unknown` when type is uncertain + +## Angular Best Practices + +- Always use standalone components over NgModules +- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+. +- Use signals for state management +- Implement lazy loading for feature routes +- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead +- Use `NgOptimizedImage` for all static images. + - `NgOptimizedImage` does not work for inline base64 images. + +## Accessibility Requirements + +- It MUST pass all AXE checks. +- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes. + +### Components + +- Keep components small and focused on a single responsibility +- Use `input()` and `output()` functions instead of decorators +- Use `computed()` for derived state +- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator +- Prefer inline templates for small components +- Prefer Reactive forms instead of Template-driven ones +- Do NOT use `ngClass`, use `class` bindings instead +- Do NOT use `ngStyle`, use `style` bindings instead +- When using external templates/styles, use paths relative to the component TS file. + +## State Management + +- Use signals for local component state +- Use `computed()` for derived state +- Keep state transformations pure and predictable +- Do NOT use `mutate` on signals, use `update` or `set` instead + +## Templates + +- Keep templates simple and avoid complex logic +- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch` +- Use the async pipe to handle observables +- Do not assume globals like (`new Date()`) are available. +- Do not write arrow functions in templates (they are not supported). + +## Services + +- Design services around a single responsibility +- Use the `providedIn: 'root'` option for singleton services +- Use the `inject()` function instead of constructor injection diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..34c3b360 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ + +You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices. + +## TypeScript Best Practices + +- Use strict type checking +- Prefer type inference when the type is obvious +- Avoid the `any` type; use `unknown` when type is uncertain + +## Angular Best Practices + +- Always use standalone components over NgModules +- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+. +- Use signals for state management +- Implement lazy loading for feature routes +- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead +- Use `NgOptimizedImage` for all static images. + - `NgOptimizedImage` does not work for inline base64 images. + +## Accessibility Requirements + +- It MUST pass all AXE checks. +- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes. + +### Components + +- Keep components small and focused on a single responsibility +- Use `input()` and `output()` functions instead of decorators +- Use `computed()` for derived state +- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator +- Prefer inline templates for small components +- Prefer Reactive forms instead of Template-driven ones +- Do NOT use `ngClass`, use `class` bindings instead +- Do NOT use `ngStyle`, use `style` bindings instead +- When using external templates/styles, use paths relative to the component TS file. + +## State Management + +- Use signals for local component state +- Use `computed()` for derived state +- Keep state transformations pure and predictable +- Do NOT use `mutate` on signals, use `update` or `set` instead + +## Templates + +- Keep templates simple and avoid complex logic +- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch` +- Use the async pipe to handle observables +- Do not assume globals like (`new Date()`) are available. +- Do not write arrow functions in templates (they are not supported). + +## Services + +- Design services around a single responsibility +- Use the `providedIn: 'root'` option for singleton services +- Use the `inject()` function instead of constructor injection From cb434c12eca3fb8aa1ceea503d85aeb5bb25d066 Mon Sep 17 00:00:00 2001 From: DavideViolante Date: Tue, 3 Feb 2026 15:36:12 +0100 Subject: [PATCH 04/52] chore: ignore sass warnings during build, replace import with use --- angular.json | 7 ++++++- client/styles.scss | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/angular.json b/angular.json index 60ba86d2..73406a11 100644 --- a/angular.json +++ b/angular.json @@ -35,7 +35,12 @@ ], "scripts": [ "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" - ] + ], + "stylePreprocessorOptions": { + "sass": { + "silenceDeprecations": ["color-functions", "global-builtin", "import", "if-function"] + } + } }, "configurations": { "production": { diff --git a/client/styles.scss b/client/styles.scss index ea1bc393..c36a9a8d 100644 --- a/client/styles.scss +++ b/client/styles.scss @@ -3,7 +3,7 @@ // Add Bootstrap style overrides here $primary: #026fbd; -@import '../node_modules/bootstrap/scss/bootstrap'; +@use '../node_modules/bootstrap/scss/bootstrap'; // Add global app styles here body { From 37a272eb2677615c1224a0cf4554d514cac32e91 Mon Sep 17 00:00:00 2001 From: DavideViolante Date: Tue, 3 Feb 2026 16:19:14 +0100 Subject: [PATCH 05/52] refactor: migrate to new angular apps structure * replace main.ts * add app.config * add app.routes * use standalone components * replace input decorator --- client/app/about/about.component.ts | 1 - client/app/account/account.component.html | 2 +- client/app/account/account.component.ts | 5 +- .../add-cat-form/add-cat-form.component.ts | 5 +- client/app/admin/admin.component.html | 2 +- client/app/admin/admin.component.ts | 3 +- client/app/app.config.ts | 35 +++++++++++ client/app/{app.component.html => app.html} | 0 client/app/app.module.ts | 59 ------------------- .../{app-routing.module.ts => app.routes.ts} | 14 +---- client/app/{app.component.ts => app.ts} | 12 ++-- client/app/cats/cats.component.html | 2 +- client/app/cats/cats.component.ts | 6 +- client/app/login/login.component.html | 2 +- client/app/login/login.component.ts | 5 +- client/app/logout/logout.component.ts | 2 +- client/app/not-found/not-found.component.ts | 1 - client/app/register/register.component.html | 2 +- client/app/register/register.component.ts | 5 +- client/app/services/auth.service.ts | 6 +- .../app/shared/loading/loading.component.html | 2 +- .../app/shared/loading/loading.component.ts | 5 +- client/app/shared/shared.module.ts | 33 ----------- client/app/shared/toast/toast.component.html | 10 ++-- client/app/shared/toast/toast.component.ts | 13 ++-- client/main.ts | 11 ++-- package-lock.json | 19 ------ package.json | 2 +- 28 files changed, 95 insertions(+), 169 deletions(-) create mode 100644 client/app/app.config.ts rename client/app/{app.component.html => app.html} (100%) delete mode 100644 client/app/app.module.ts rename client/app/{app-routing.module.ts => app.routes.ts} (82%) rename client/app/{app.component.ts => app.ts} (53%) delete mode 100644 client/app/shared/shared.module.ts diff --git a/client/app/about/about.component.ts b/client/app/about/about.component.ts index dfe4549f..ccf946df 100644 --- a/client/app/about/about.component.ts +++ b/client/app/about/about.component.ts @@ -4,7 +4,6 @@ import { Component } from '@angular/core'; selector: 'app-about', templateUrl: './about.component.html', styleUrls: ['./about.component.scss'], - standalone: false }) export class AboutComponent { diff --git a/client/app/account/account.component.html b/client/app/account/account.component.html index d6502238..bdff75c6 100644 --- a/client/app/account/account.component.html +++ b/client/app/account/account.component.html @@ -1,6 +1,6 @@ - + @if (!isLoading) {
diff --git a/client/app/account/account.component.ts b/client/app/account/account.component.ts index 319d7bb8..0fa1c7bd 100644 --- a/client/app/account/account.component.ts +++ b/client/app/account/account.component.ts @@ -1,5 +1,8 @@ import { Component, OnInit, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; + import { ToastComponent } from '../shared/toast/toast.component'; +import { LoadingComponent } from '../shared/loading/loading.component'; import { AuthService } from '../services/auth.service'; import { UserService } from '../services/user.service'; import { User } from '../shared/models/user.model'; @@ -7,7 +10,7 @@ import { User } from '../shared/models/user.model'; @Component({ selector: 'app-account', templateUrl: './account.component.html', - standalone: false + imports: [FormsModule, ToastComponent, LoadingComponent], }) export class AccountComponent implements OnInit { private auth = inject(AuthService); diff --git a/client/app/add-cat-form/add-cat-form.component.ts b/client/app/add-cat-form/add-cat-form.component.ts index 881f7f38..fe95748c 100644 --- a/client/app/add-cat-form/add-cat-form.component.ts +++ b/client/app/add-cat-form/add-cat-form.component.ts @@ -1,5 +1,6 @@ import { Component, Input, inject } from '@angular/core'; -import { UntypedFormGroup, UntypedFormControl, Validators, UntypedFormBuilder } from '@angular/forms'; +import { UntypedFormGroup, UntypedFormControl, Validators, UntypedFormBuilder, ReactiveFormsModule } from '@angular/forms'; + import { CatService } from '../services/cat.service'; import { ToastComponent } from '../shared/toast/toast.component'; import { Cat } from '../shared/models/cat.model'; @@ -8,7 +9,7 @@ import { Cat } from '../shared/models/cat.model'; selector: 'app-add-cat-form', templateUrl: './add-cat-form.component.html', styleUrls: ['./add-cat-form.component.scss'], - standalone: false + imports: [ReactiveFormsModule] }) export class AddCatFormComponent { diff --git a/client/app/admin/admin.component.html b/client/app/admin/admin.component.html index 92742063..b59f22ce 100644 --- a/client/app/admin/admin.component.html +++ b/client/app/admin/admin.component.html @@ -1,6 +1,6 @@ - + @if (!isLoading) {
diff --git a/client/app/admin/admin.component.ts b/client/app/admin/admin.component.ts index 938a77fb..720afb71 100644 --- a/client/app/admin/admin.component.ts +++ b/client/app/admin/admin.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, inject } from '@angular/core'; import { ToastComponent } from '../shared/toast/toast.component'; +import { LoadingComponent } from '../shared/loading/loading.component'; import { AuthService } from '../services/auth.service'; import { UserService } from '../services/user.service'; import { User } from '../shared/models/user.model'; @@ -8,7 +9,7 @@ import { User } from '../shared/models/user.model'; @Component({ selector: 'app-admin', templateUrl: './admin.component.html', - standalone: false + imports: [ToastComponent, LoadingComponent], }) export class AdminComponent implements OnInit { auth = inject(AuthService); diff --git a/client/app/app.config.ts b/client/app/app.config.ts new file mode 100644 index 00000000..122dcc4a --- /dev/null +++ b/client/app/app.config.ts @@ -0,0 +1,35 @@ +import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { JwtModule } from '@auth0/angular-jwt'; + +// Routes +import { routes } from './app.routes'; +// Services +import { CatService } from './services/cat.service'; +import { UserService } from './services/user.service'; +import { AuthService } from './services/auth.service'; +import { AuthGuardLogin } from './services/auth-guard-login.service'; +import { AuthGuardAdmin } from './services/auth-guard-admin.service'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideRouter(routes), + importProvidersFrom( + JwtModule.forRoot({ + config: { + tokenGetter: () => localStorage.getItem('token'), + // allowedDomains: ['example.com'], + // disallowedRoutes: ['http://example.com/examplebadroute/'], + }, + }), + ), + provideHttpClient(withInterceptorsFromDi()), + AuthService, + AuthGuardLogin, + AuthGuardAdmin, + CatService, + UserService, + ], +}; diff --git a/client/app/app.component.html b/client/app/app.html similarity index 100% rename from client/app/app.component.html rename to client/app/app.html diff --git a/client/app/app.module.ts b/client/app/app.module.ts deleted file mode 100644 index b84087a2..00000000 --- a/client/app/app.module.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Angular -import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { JwtModule } from '@auth0/angular-jwt'; -// Modules -import { AppRoutingModule } from './app-routing.module'; -import { SharedModule } from './shared/shared.module'; -// Services -import { CatService } from './services/cat.service'; -import { UserService } from './services/user.service'; -import { AuthService } from './services/auth.service'; -import { AuthGuardLogin } from './services/auth-guard-login.service'; -import { AuthGuardAdmin } from './services/auth-guard-admin.service'; -// Components -import { AppComponent } from './app.component'; -import { CatsComponent } from './cats/cats.component'; -import { AddCatFormComponent } from './add-cat-form/add-cat-form.component'; -import { AboutComponent } from './about/about.component'; -import { RegisterComponent } from './register/register.component'; -import { LoginComponent } from './login/login.component'; -import { LogoutComponent } from './logout/logout.component'; -import { AccountComponent } from './account/account.component'; -import { AdminComponent } from './admin/admin.component'; -import { NotFoundComponent } from './not-found/not-found.component'; - -@NgModule({ - declarations: [ - AppComponent, - CatsComponent, - AddCatFormComponent, - AboutComponent, - RegisterComponent, - LoginComponent, - LogoutComponent, - AccountComponent, - AdminComponent, - NotFoundComponent - ], - imports: [ - AppRoutingModule, - SharedModule, - JwtModule.forRoot({ - config: { - tokenGetter: (): string | null => localStorage.getItem('token'), - // allowedDomains: ['localhost:3000', 'localhost:4200'] - } - }) - ], - providers: [ - AuthService, - AuthGuardLogin, - AuthGuardAdmin, - CatService, - UserService - ], - schemas: [CUSTOM_ELEMENTS_SCHEMA], - bootstrap: [AppComponent] -}) - -export class AppModule { } diff --git a/client/app/app-routing.module.ts b/client/app/app.routes.ts similarity index 82% rename from client/app/app-routing.module.ts rename to client/app/app.routes.ts index ff833a1d..30c1eaec 100644 --- a/client/app/app-routing.module.ts +++ b/client/app/app.routes.ts @@ -1,6 +1,5 @@ -// Angular -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { Routes } from '@angular/router'; + // Services import { AuthGuardLogin } from './services/auth-guard-login.service'; import { AuthGuardAdmin } from './services/auth-guard-admin.service'; @@ -14,7 +13,7 @@ import { AccountComponent } from './account/account.component'; import { AdminComponent } from './admin/admin.component'; import { NotFoundComponent } from './not-found/not-found.component'; -const routes: Routes = [ +export const routes: Routes = [ { path: '', component: AboutComponent }, { path: 'cats', component: CatsComponent }, { path: 'register', component: RegisterComponent }, @@ -25,10 +24,3 @@ const routes: Routes = [ { path: 'notfound', component: NotFoundComponent }, { path: '**', redirectTo: '/notfound' }, ]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) - -export class AppRoutingModule {} diff --git a/client/app/app.component.ts b/client/app/app.ts similarity index 53% rename from client/app/app.component.ts rename to client/app/app.ts index 48b7161d..fb6a4b48 100644 --- a/client/app/app.component.ts +++ b/client/app/app.ts @@ -1,16 +1,20 @@ import { AfterViewChecked, ChangeDetectorRef, Component, inject } from '@angular/core'; +import { RouterModule, RouterOutlet } from '@angular/router'; + import { AuthService } from './services/auth.service'; +import { ToastComponent } from './shared/toast/toast.component'; +import { LoadingComponent } from './shared/loading/loading.component'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - standalone: false + imports: [RouterOutlet, RouterModule], + providers: [AuthService, ToastComponent, LoadingComponent], + templateUrl: './app.html', }) -export class AppComponent implements AfterViewChecked { +export class App implements AfterViewChecked { auth = inject(AuthService); private changeDetector = inject(ChangeDetectorRef); - // This fixes: https://github.com/DavideViolante/Angular-Full-Stack/issues/105 ngAfterViewChecked(): void { this.changeDetector.detectChanges(); diff --git a/client/app/cats/cats.component.html b/client/app/cats/cats.component.html index 9a1668c0..9ac5547c 100644 --- a/client/app/cats/cats.component.html +++ b/client/app/cats/cats.component.html @@ -1,6 +1,6 @@ - + @if (!isLoading) {
diff --git a/client/app/cats/cats.component.ts b/client/app/cats/cats.component.ts index 70b89392..f92a78fd 100644 --- a/client/app/cats/cats.component.ts +++ b/client/app/cats/cats.component.ts @@ -1,20 +1,22 @@ import { Component, OnInit, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { CatService } from '../services/cat.service'; +import { LoadingComponent } from '../shared/loading/loading.component'; import { ToastComponent } from '../shared/toast/toast.component'; +import { AddCatFormComponent } from '../add-cat-form/add-cat-form.component'; import { Cat } from '../shared/models/cat.model'; @Component({ selector: 'app-cats', templateUrl: './cats.component.html', styleUrls: ['./cats.component.scss'], - standalone: false + imports: [FormsModule, AddCatFormComponent, ToastComponent, LoadingComponent], }) export class CatsComponent implements OnInit { private catService = inject(CatService); toast = inject(ToastComponent); - cat = new Cat(); cats: Cat[] = []; isLoading = true; diff --git a/client/app/login/login.component.html b/client/app/login/login.component.html index 5163727e..b499727d 100644 --- a/client/app/login/login.component.html +++ b/client/app/login/login.component.html @@ -1,4 +1,4 @@ - +

Login

diff --git a/client/app/login/login.component.ts b/client/app/login/login.component.ts index 91f7718e..541d546c 100644 --- a/client/app/login/login.component.ts +++ b/client/app/login/login.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; +import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { AuthService } from '../services/auth.service'; import { ToastComponent } from '../shared/toast/toast.component'; @@ -8,7 +9,7 @@ import { ToastComponent } from '../shared/toast/toast.component'; @Component({ selector: 'app-login', templateUrl: './login.component.html', - standalone: false + imports: [CommonModule, ReactiveFormsModule, ToastComponent], }) export class LoginComponent implements OnInit { private auth = inject(AuthService); diff --git a/client/app/logout/logout.component.ts b/client/app/logout/logout.component.ts index b57f40a7..3fb2081d 100644 --- a/client/app/logout/logout.component.ts +++ b/client/app/logout/logout.component.ts @@ -1,10 +1,10 @@ import { Component, OnInit, inject } from '@angular/core'; + import { AuthService } from '../services/auth.service'; @Component({ selector: 'app-logout', template: '', - standalone: false }) export class LogoutComponent implements OnInit { private auth = inject(AuthService); diff --git a/client/app/not-found/not-found.component.ts b/client/app/not-found/not-found.component.ts index d2d33f8e..6246b379 100644 --- a/client/app/not-found/not-found.component.ts +++ b/client/app/not-found/not-found.component.ts @@ -3,7 +3,6 @@ import { Component } from '@angular/core'; @Component({ selector: 'app-not-found', templateUrl: './not-found.component.html', - standalone: false }) export class NotFoundComponent { diff --git a/client/app/register/register.component.html b/client/app/register/register.component.html index e7031097..ad077858 100644 --- a/client/app/register/register.component.html +++ b/client/app/register/register.component.html @@ -1,4 +1,4 @@ - +

Register

diff --git a/client/app/register/register.component.ts b/client/app/register/register.component.ts index 88b91bde..a1adc5ca 100644 --- a/client/app/register/register.component.ts +++ b/client/app/register/register.component.ts @@ -1,6 +1,7 @@ import { Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; +import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { UserService } from '../services/user.service'; import { ToastComponent } from '../shared/toast/toast.component'; @@ -8,7 +9,7 @@ import { ToastComponent } from '../shared/toast/toast.component'; @Component({ selector: 'app-register', templateUrl: './register.component.html', - standalone: false + imports: [CommonModule, ReactiveFormsModule, ToastComponent] }) export class RegisterComponent { private formBuilder = inject(UntypedFormBuilder); diff --git a/client/app/services/auth.service.ts b/client/app/services/auth.service.ts index 33d17e94..267e5e7f 100644 --- a/client/app/services/auth.service.ts +++ b/client/app/services/auth.service.ts @@ -4,7 +4,7 @@ import { Router } from '@angular/router'; import { JwtHelperService } from '@auth0/angular-jwt'; import { UserService } from './user.service'; -import { ToastComponent } from '../shared/toast/toast.component'; +// import { ToastComponent } from '../shared/toast/toast.component'; import { User } from '../shared/models/user.model'; @Injectable() @@ -12,7 +12,7 @@ export class AuthService { private userService = inject(UserService); private router = inject(Router); private jwtHelper = inject(JwtHelperService); - toast = inject(ToastComponent); + // toast = inject(ToastComponent); loggedIn = false; isAdmin = false; @@ -36,7 +36,7 @@ export class AuthService { this.loggedIn = true; this.router.navigate(['/']); }, - error: () => this.toast.setMessage('Invalid email or password!', 'danger') + error: () => console.error() // this.toast.setMessage('Invalid email or password!', 'danger') }); } diff --git a/client/app/shared/loading/loading.component.html b/client/app/shared/loading/loading.component.html index 1277f1b1..5d2341f5 100644 --- a/client/app/shared/loading/loading.component.html +++ b/client/app/shared/loading/loading.component.html @@ -1,4 +1,4 @@ -@if (condition) { +@if (condition()) {

Loading...

diff --git a/client/app/shared/loading/loading.component.ts b/client/app/shared/loading/loading.component.ts index f97451d4..ffdf76ad 100644 --- a/client/app/shared/loading/loading.component.ts +++ b/client/app/shared/loading/loading.component.ts @@ -1,10 +1,9 @@ -import { Component, Input } from '@angular/core'; +import { Component, input } from '@angular/core'; @Component({ selector: 'app-loading', templateUrl: './loading.component.html', - standalone: false }) export class LoadingComponent { - @Input() condition = false; + condition = input(false); } diff --git a/client/app/shared/shared.module.ts b/client/app/shared/shared.module.ts deleted file mode 100644 index 38963d36..00000000 --- a/client/app/shared/shared.module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; - -import { ToastComponent } from './toast/toast.component'; -import { LoadingComponent } from './loading/loading.component'; - -@NgModule({ - exports: [ - // Shared Modules - BrowserModule, - FormsModule, - ReactiveFormsModule, - // Shared Components - ToastComponent, - LoadingComponent, - ], - declarations: [ - ToastComponent, - LoadingComponent - ], - imports: [ - BrowserModule, - FormsModule, - ReactiveFormsModule - ], - providers: [ - ToastComponent, - provideHttpClient(withInterceptorsFromDi()) - ], -}) -export class SharedModule {} diff --git a/client/app/shared/toast/toast.component.html b/client/app/shared/toast/toast.component.html index 76934867..462c358b 100644 --- a/client/app/shared/toast/toast.component.html +++ b/client/app/shared/toast/toast.component.html @@ -1,9 +1,9 @@ -@if (message.body) { -