diff --git a/libs/blog-bff/banners/api/src/lib/dtos.ts b/libs/blog-bff/banners/api/src/lib/dtos.ts index 173c86c1..18e1ff23 100644 --- a/libs/blog-bff/banners/api/src/lib/dtos.ts +++ b/libs/blog-bff/banners/api/src/lib/dtos.ts @@ -1,13 +1,22 @@ export interface WPBannerDto { id: number; acf: { + is_slider_banner_displayed: boolean; display_time: string; - slides: - | { - slide_image: number /* slideId */; - slide_url: string /* url to navigate to after click */; - }[] - | null; + slides: { + slide_image_desktop: number /* slideId */; + slide_image_mobile: number /* slideId */; + slide_url: string /* url to navigate to after click */; + }[]; + + is_top_banner_displayed: boolean; + top_banner_image_desktop: number /* mediaId */; + top_banner_image_mobile: number /* mediaId */; + top_banner_image_url: string /* url to navigate to after click */; + + is_card_banner_displayed: boolean; + card_banner_image: number /* mediaId */; + card_banner_url: string /* url to navigate to after click */; }; } diff --git a/libs/blog-bff/banners/api/src/lib/mappers.ts b/libs/blog-bff/banners/api/src/lib/mappers.ts index c3e3ec10..d725af74 100644 --- a/libs/blog-bff/banners/api/src/lib/mappers.ts +++ b/libs/blog-bff/banners/api/src/lib/mappers.ts @@ -1,21 +1,46 @@ -import { Slider } from '@angular-love/blog/contracts/banners'; +import { Banners } from '@angular-love/blog/contracts/banners'; import { WPBannerDto, WPBannerMediaDto } from './dtos'; export const toBanner = ( dto: WPBannerDto, mediaDto: WPBannerMediaDto[], -): Slider => { +): Banners => { return { - slideDisplayTimeMs: +dto.acf.display_time, - slides: - dto.acf.slides?.map((slide) => { - const media = mediaDto.find((media) => media.id === slide.slide_image)!; - return { - url: media.guid.rendered, - alt: media.alt_text, - navigateTo: slide.slide_url, - }; - }) ?? [], + ...(dto.acf.is_slider_banner_displayed && { + slider: { + slideDisplayTimeMs: +dto.acf.display_time, + slides: dto.acf.slides.map((slide) => { + const media = mediaDto.find( + (media) => media.id === slide.slide_image_desktop, + )!; + return { + url: media?.guid.rendered, + alt: media?.alt_text, + navigateTo: slide.slide_url, + }; + }), + }, + }), + ...(dto.acf.is_top_banner_displayed && { + topBanner: { + url: mediaDto.find( + (media) => media.id === dto.acf.top_banner_image_desktop, + )?.guid.rendered, + alt: mediaDto.find( + (media) => media.id === dto.acf.top_banner_image_desktop, + )?.alt_text, + navigateTo: dto.acf.top_banner_image_url, + }, + }), + ...(dto.acf.is_card_banner_displayed && { + cardBanner: { + url: mediaDto.find((media) => media.id === dto.acf.card_banner_image) + ?.guid.rendered, + alt: mediaDto.find((media) => media.id === dto.acf.card_banner_image) + ?.alt_text, + navigateTo: dto.acf.card_banner_url, + }, + }), }; }; diff --git a/libs/blog-contracts/banners/src/lib/banners.ts b/libs/blog-contracts/banners/src/lib/banners.ts index ba9c194e..8e57f085 100644 --- a/libs/blog-contracts/banners/src/lib/banners.ts +++ b/libs/blog-contracts/banners/src/lib/banners.ts @@ -1,8 +1,26 @@ export interface Slider { slideDisplayTimeMs: number; slides: { - url: string; - alt: string; + url?: string; + alt?: string; navigateTo: string; }[]; } + +export interface TopBanner { + url?: string; + alt?: string; + navigateTo: string; +} + +export interface CardBanner { + url?: string; + alt?: string; + navigateTo: string; +} + +export interface Banners { + slider?: Slider; + topBanner?: TopBanner; + cardBanner?: CardBanner; +} diff --git a/libs/blog/ad-banner/data-access/src/lib/infrastructure/ad-banner.service.ts b/libs/blog/ad-banner/data-access/src/lib/infrastructure/ad-banner.service.ts index d6836f4b..e6e4787d 100644 --- a/libs/blog/ad-banner/data-access/src/lib/infrastructure/ad-banner.service.ts +++ b/libs/blog/ad-banner/data-access/src/lib/infrastructure/ad-banner.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; -import { Slider } from '@angular-love/blog/contracts/banners'; +import { Banners } from '@angular-love/blog/contracts/banners'; import { ConfigService } from '@angular-love/shared/config'; @Injectable({ providedIn: 'root' }) @@ -9,7 +9,7 @@ export class AdBannerService { private readonly _apiBaseUrl = inject(ConfigService).get('apiBaseUrl'); private readonly _http = inject(HttpClient); - getBannerSlider() { - return this._http.get(`${this._apiBaseUrl}/banners`); + getVisibleBanners() { + return this._http.get(`${this._apiBaseUrl}/banners`); } } diff --git a/libs/blog/ad-banner/data-access/src/lib/state/ad-banner.store.ts b/libs/blog/ad-banner/data-access/src/lib/state/ad-banner.store.ts index 30fb615b..6e7edc31 100644 --- a/libs/blog/ad-banner/data-access/src/lib/state/ad-banner.store.ts +++ b/libs/blog/ad-banner/data-access/src/lib/state/ad-banner.store.ts @@ -4,16 +4,16 @@ import { patchState, signalStore, withMethods, withState } from '@ngrx/signals'; import { rxMethod } from '@ngrx/signals/rxjs-interop'; import { pipe, switchMap } from 'rxjs'; -import { Slider } from '@angular-love/blog/contracts/banners'; +import { Banners } from '@angular-love/blog/contracts/banners'; import { AdBannerService } from '../infrastructure/ad-banner.service'; type AdBannerState = { - slider: Slider | null; + banners: Banners | null; }; const initialState: AdBannerState = { - slider: null, + banners: null, }; export const AdBannerStore = signalStore( @@ -23,10 +23,10 @@ export const AdBannerStore = signalStore( getData: rxMethod( pipe( switchMap(() => - adBannerService.getBannerSlider().pipe( + adBannerService.getVisibleBanners().pipe( tapResponse({ - next: (slider) => { - patchState(store, { slider }); + next: (banners) => { + patchState(store, { banners }); }, error: () => { patchState(store); diff --git a/libs/blog/ad-banner/ui/src/index.ts b/libs/blog/ad-banner/ui/src/index.ts index 09b611ad..40009e49 100644 --- a/libs/blog/ad-banner/ui/src/index.ts +++ b/libs/blog/ad-banner/ui/src/index.ts @@ -2,3 +2,4 @@ export * from './lib/ad-image-banner/ad-image-banner.component'; export * from './lib/ad-image-banner/ad-image-banner-data.interface'; export * from './lib/instances/al-indepth-banner.component'; export * from './lib/banner-carousel/al-banner-carousel.component'; +export * from './lib/newsletter-banner/al-newsletter-banner.component'; diff --git a/libs/blog/ad-banner/ui/src/lib/ad-image-banner/ad-image-banner.component.html b/libs/blog/ad-banner/ui/src/lib/ad-image-banner/ad-image-banner.component.html index e2913e7f..95d28440 100644 --- a/libs/blog/ad-banner/ui/src/lib/ad-image-banner/ad-image-banner.component.html +++ b/libs/blog/ad-banner/ui/src/lib/ad-image-banner/ad-image-banner.component.html @@ -4,6 +4,7 @@ role="button" class="!relative cursor-pointer" [attr.aria-label]="banner().alt" + [alt]="banner().alt" [ngSrc]="banner().url" (click)="navigateFromBanner()" (keydown.enter)="navigateFromBanner()" diff --git a/libs/blog/ad-banner/ui/src/lib/newsletter-banner/al-newsletter-banner.component.ts b/libs/blog/ad-banner/ui/src/lib/newsletter-banner/al-newsletter-banner.component.ts new file mode 100644 index 00000000..86541647 --- /dev/null +++ b/libs/blog/ad-banner/ui/src/lib/newsletter-banner/al-newsletter-banner.component.ts @@ -0,0 +1,44 @@ +import { NgOptimizedImage } from '@angular/common'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; + +import { CardBanner } from '@angular-love/blog/contracts/banners'; + +@Component({ + selector: 'al-newsletter-banner', + imports: [NgOptimizedImage], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + + + + `, +}) +export class AlNewsletterBannerComponent { + readonly cardBanner = input.required(); +} diff --git a/libs/blog/articles/feature-article/src/lib/article-details/article-details.component.html b/libs/blog/articles/feature-article/src/lib/article-details/article-details.component.html index bab625bb..0fa0e5c9 100644 --- a/libs/blog/articles/feature-article/src/lib/article-details/article-details.component.html +++ b/libs/blog/articles/feature-article/src/lib/article-details/article-details.component.html @@ -62,8 +62,8 @@