diff --git a/apiGateway/src/main/resources/application.yml b/apiGateway/src/main/resources/application.yml index c041e40..1c1e6ae 100644 --- a/apiGateway/src/main/resources/application.yml +++ b/apiGateway/src/main/resources/application.yml @@ -7,7 +7,7 @@ spring: oauth2: resourceserver: jwt: - issuer-uri: http://keycloak:8080/realms/e-library + issuer-uri: http://localhost:8181/realms/e-library jwk-set-uri: http://localhost:8181/realms/e-library/protocol/openid-connect/certs services: diff --git a/book-bazaar/package-lock.json b/book-bazaar/package-lock.json index d881649..f61482b 100644 --- a/book-bazaar/package-lock.json +++ b/book-bazaar/package-lock.json @@ -17,6 +17,8 @@ "@angular/platform-browser": "^20.3.0", "@angular/router": "^20.3.0", "@tailwindcss/postcss": "^4.1.16", + "keycloak-angular": "^20.0.0", + "keycloak-js": "^26.2.1", "rxjs": "~7.8.0", "tslib": "^2.3.0" }, @@ -639,6 +641,7 @@ "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.10.tgz", "integrity": "sha512-Z03cfH1jgQ7XMDJj4R8qAGqivcvhdG3wYBwaiN1K1ODBgPhbFKNeD4stKqYp7xBNtswmM2O2jMxrL/Djwju4Gg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -6859,6 +6862,32 @@ "node": ">=10" } }, + "node_modules/keycloak-angular": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/keycloak-angular/-/keycloak-angular-20.0.0.tgz", + "integrity": "sha512-p9ThVUN8TNz15M2dd11VRDdHzgEDRSSxvyRGtK4N45lTbfs52DeNK+YXcpgt8ZX0/YN27GjU9GjiB4odI4/A2Q==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/common": "^20", + "@angular/core": "^20", + "@angular/router": "^20", + "keycloak-js": "^18 || ^19 || ^20 || ^21 || ^22 || ^23 || ^24 || ^25 || ^26", + "rxjs": "^7" + } + }, + "node_modules/keycloak-js": { + "version": "26.2.1", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.2.1.tgz", + "integrity": "sha512-bZt6fQj/TLBAmivXSxSlqAJxBx/knNZDQGJIW4ensGYGN4N6tUKV8Zj3Y7/LOV8eIpvWsvqV70fbACihK8Ze0Q==", + "license": "Apache-2.0", + "peer": true, + "workspaces": [ + "test" + ] + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", diff --git a/book-bazaar/package.json b/book-bazaar/package.json index fe2ae82..5488764 100644 --- a/book-bazaar/package.json +++ b/book-bazaar/package.json @@ -31,6 +31,8 @@ "@angular/platform-browser": "^20.3.0", "@angular/router": "^20.3.0", "@tailwindcss/postcss": "^4.1.16", + "keycloak-angular": "^20.0.0", + "keycloak-js": "^26.2.1", "rxjs": "~7.8.0", "tslib": "^2.3.0" }, diff --git a/book-bazaar/public/hero-books-illustration.jpg b/book-bazaar/public/hero-books-illustration.jpg new file mode 100644 index 0000000..0037619 Binary files /dev/null and b/book-bazaar/public/hero-books-illustration.jpg differ diff --git a/book-bazaar/public/silent-check-sso.html b/book-bazaar/public/silent-check-sso.html new file mode 100644 index 0000000..96b3cf9 --- /dev/null +++ b/book-bazaar/public/silent-check-sso.html @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/book-bazaar/src/app/app.config.ts b/book-bazaar/src/app/app.config.ts index a4001de..f5b4220 100644 --- a/book-bazaar/src/app/app.config.ts +++ b/book-bazaar/src/app/app.config.ts @@ -1,14 +1,47 @@ -import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core'; -import {provideRouter, withViewTransitions} from '@angular/router'; +import { ApplicationConfig, inject, provideAppInitializer, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core'; +import { provideRouter, withViewTransitions } from '@angular/router'; import { routes } from './app.routes'; -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { + provideKeycloak, + INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + includeBearerTokenInterceptor, + createInterceptorCondition +} from 'keycloak-angular'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideZonelessChangeDetection(), provideRouter(routes, withViewTransitions()), - provideHttpClient(), + + provideHttpClient(withInterceptors([includeBearerTokenInterceptor])), + + provideKeycloak({ + config: { + url: 'http://localhost:8181', + realm: 'e-library', + clientId: 'e-library-client' + }, + initOptions: { + // 'check-sso': перевіряє сесію тихо. Якщо юзер залогінений - пускає, ні - лишає анонімом. + // 'login-required': відразу кидає на сторінку логіну (вам це НЕ підходить). + onLoad: 'check-sso', + silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html', + checkLoginIframe: false // Вимикаємо, щоб уникнути проблем з cookies у сучасних браузерах + }, + // Автоматично додавати токен (Bearer) до запитів + }), + { + provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + useValue: [ + createInterceptorCondition({ + urlPattern: /^(http:\/\/localhost:9000|http:\/\/localhost:8080)(\/.*)?$/i, // Регулярка для ваших сервісів + bearerPrefix: 'Bearer' + }), + // Можна додати інші умови, якщо є інші бекенди + ] + } ] }; diff --git a/book-bazaar/src/app/components/book-card/book-card.html b/book-bazaar/src/app/components/book-card/book-card.html index e293b4e..54aa45f 100644 --- a/book-bazaar/src/app/components/book-card/book-card.html +++ b/book-bazaar/src/app/components/book-card/book-card.html @@ -1,43 +1,51 @@ - +
- + -
- +
-
- +
+ {{ book().title }} - + {{ book().author?.name }} -
- +
+ + @if(book().price) { +
+ {{ book().price | currency }} +
+ } + +
@if(book().category) { - {{ book().category?.name }} - + } - @for (genre of book().bookGenres; track genre.id) { - {{ genre.name }} - + } - +
diff --git a/book-bazaar/src/app/components/book-card/book-card.ts b/book-bazaar/src/app/components/book-card/book-card.ts index 24052b7..73ac458 100644 --- a/book-bazaar/src/app/components/book-card/book-card.ts +++ b/book-bazaar/src/app/components/book-card/book-card.ts @@ -6,10 +6,11 @@ import { MatChipSet, MatChip } from "@angular/material/chips"; import { MatTooltipModule } from '@angular/material/tooltip'; import { Category } from '../../model/category'; import { Genre } from '../../model/genre'; +import { CurrencyPipe } from '@angular/common'; @Component({ selector: 'app-book-card', - imports: [MatCard, RouterModule, MatCardTitleGroup, MatCardTitle, MatCardSubtitle, MatChipSet, MatChip, MatTooltipModule], + imports: [MatCard, RouterModule, MatCardTitleGroup, MatCardTitle, MatCardSubtitle, MatChipSet, MatChip, MatTooltipModule, CurrencyPipe], templateUrl: './book-card.html', styleUrl: './book-card.css', }) diff --git a/book-bazaar/src/app/components/filter-panel/filter-panel.css b/book-bazaar/src/app/components/filter-panel/filter-panel.css index 8ca6c9c..015e905 100644 --- a/book-bazaar/src/app/components/filter-panel/filter-panel.css +++ b/book-bazaar/src/app/components/filter-panel/filter-panel.css @@ -20,4 +20,14 @@ .custom-scrollbar { scrollbar-width: thin; scrollbar-color: #e5e7eb transparent; +} + +/* Зменшуємо відступи у чекбоксів, щоб вони виглядали компактніше */ +::ng-deep .mat-mdc-checkbox .mdc-form-field { + padding-right: 0 !important; +} + +::ng-deep .mat-mdc-checkbox-touch-target { + width: 32px !important; + height: 32px !important; } \ No newline at end of file diff --git a/book-bazaar/src/app/components/filter-panel/filter-panel.html b/book-bazaar/src/app/components/filter-panel/filter-panel.html index 8243011..9e2c707 100644 --- a/book-bazaar/src/app/components/filter-panel/filter-panel.html +++ b/book-bazaar/src/app/components/filter-panel/filter-panel.html @@ -1,33 +1,60 @@ -
+
-

Filters

- +
+

+ tune + Filters +

+ @if (hasSelectedFilters()) { + + }
- @for (filter of filters(); track filter.name) { -

{{ filter.label }}

+
+ @for (filter of filters(); track filter.name) { +
+ +

+ {{ filter.label }} + + {{ filter.options.length }} + +

+ +
+ @for (option of visibleOptions(filter); track option) { + + } +
-
- @for (option of visibleOptions(filter); track option) { - + @if (filter.options.length > filter.defaultVisibleCount) { + }
- - -
- } + } +
\ No newline at end of file diff --git a/book-bazaar/src/app/components/filter-panel/filter-panel.ts b/book-bazaar/src/app/components/filter-panel/filter-panel.ts index 985bd5c..3a54a8e 100644 --- a/book-bazaar/src/app/components/filter-panel/filter-panel.ts +++ b/book-bazaar/src/app/components/filter-panel/filter-panel.ts @@ -1,15 +1,17 @@ -import { Component, effect, input, output, signal, SimpleChange, SimpleChanges } from '@angular/core'; +import { Component, computed, effect, input, output, signal, SimpleChange, SimpleChanges } from '@angular/core'; import {Filter} from '../../utils/filter'; import {NgIf} from '@angular/common'; import {MatCheckbox} from '@angular/material/checkbox'; import {MatButton} from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; @Component({ selector: 'app-filter-panel', imports: [ NgIf, MatCheckbox, - MatButton + MatButton, + MatIcon ], templateUrl: './filter-panel.html', styleUrl: './filter-panel.css', @@ -24,6 +26,11 @@ export class FilterPanel { changed = output>(); + hasSelectedFilters = computed(() => { + const selected = this.currentSelected(); + return Object.values(selected).some(arr => arr && arr.length > 0); + }); + constructor() { effect(() => { if (this.selectedFilters) { diff --git a/book-bazaar/src/app/components/header/header.html b/book-bazaar/src/app/components/header/header.html index 4957c8e..b3dba3e 100644 --- a/book-bazaar/src/app/components/header/header.html +++ b/book-bazaar/src/app/components/header/header.html @@ -1,15 +1,45 @@ -
-
+
+
- + @if (userService.isLoggedIn()) { + + + + + + + + } @else { +
+ + +
+ }
-
+
\ No newline at end of file diff --git a/book-bazaar/src/app/components/header/header.ts b/book-bazaar/src/app/components/header/header.ts index fa99e20..556e25d 100644 --- a/book-bazaar/src/app/components/header/header.ts +++ b/book-bazaar/src/app/components/header/header.ts @@ -1,17 +1,24 @@ -import { Component } from '@angular/core'; +import { Component, inject, signal } from '@angular/core'; import {MatButton} from '@angular/material/button'; import {RouterLink} from '@angular/router'; +import { KeycloakProfile } from 'keycloak-js'; +import { MatIcon } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import Keycloak from 'keycloak-js'; +import { UserService } from '../../services/user/userService'; @Component({ selector: 'app-header', imports: [ MatButton, - RouterLink - - ], + RouterLink, + MatIcon, + MatMenuModule +], templateUrl: './header.html', styleUrl: './header.css', }) export class Header { - + protected userService = inject(UserService); } + diff --git a/book-bazaar/src/app/components/home/home.html b/book-bazaar/src/app/components/home/home.html index 23c5ee0..d724614 100644 --- a/book-bazaar/src/app/components/home/home.html +++ b/book-bazaar/src/app/components/home/home.html @@ -1,28 +1,75 @@
-
-
-

Discover Your Next Great Read

-

Explore thousands of books, read reviews, and connect with fellow book - lovers in our digital library.

-
-
- - - -
+
+
+ +
+
+

+ Discover Your + Next Great Read +

+ +

+ Explore thousands of books, read authentic reviews, and connect with fellow book lovers in our digital library. +

+ +
+
+
+ + +
+
+ +
+ Books Illustration +
+
-
-
-

Browse by Categories

-
- @for (genre of browsingGenres(); track $index) { -
+
+
+ +
+

Browse by Genre

+
+
+ +
+ @for (genre of browsingGenres(); track $index) { + - } + } +
+
@@ -30,12 +77,12 @@

Browse by Categories

Popular this week

-
+
@for (book of popularBooks(); track $index) { - + }
- + \ No newline at end of file diff --git a/book-bazaar/src/app/components/home/home.ts b/book-bazaar/src/app/components/home/home.ts index 27239ca..cb4a9d7 100644 --- a/book-bazaar/src/app/components/home/home.ts +++ b/book-bazaar/src/app/components/home/home.ts @@ -41,7 +41,7 @@ export class Home { ngOnInit() { this.bookService.getBooks({}, 0, 5) .subscribe(page => this.popularBooks.set(page.items)); - this.genreService.getGenres(0, 5) + this.genreService.getGenres(0, 6) .subscribe(page => this.browsingGenres.set(page.items)); } diff --git a/book-bazaar/src/app/components/search-books/search-books.html b/book-bazaar/src/app/components/search-books/search-books.html index ea55350..b864182 100644 --- a/book-bazaar/src/app/components/search-books/search-books.html +++ b/book-bazaar/src/app/components/search-books/search-books.html @@ -1,67 +1,90 @@ -
-