diff --git a/src/app/core/layout/store-layout/store-layout.component.spec.ts b/src/app/core/layout/store-layout/store-layout.component.spec.ts index cfed07f..5b86850 100644 --- a/src/app/core/layout/store-layout/store-layout.component.spec.ts +++ b/src/app/core/layout/store-layout/store-layout.component.spec.ts @@ -26,6 +26,7 @@ import { StoreHeaderComponent } from './store-header/store-header.component'; import { CheckoutService } from '../../services/checkout.service'; import { CheckoutCountdownService } from '../../services/checkout-countdown.service'; import { TenantUrlSerializer } from '../../services/tenant-url.serializer'; +import { EventDateNoticeService } from '../../services/event-date-notice.service'; const tenant: Tenant = { id: 1, @@ -224,6 +225,12 @@ describe('StoreLayoutComponent', () => { remainingSeconds: checkoutRemainingSecondsState.asReadonly(), }, }, + { + provide: EventDateNoticeService, + useValue: { + claim: vi.fn().mockReturnValue(of({ data: [] })), + }, + }, ], }).compileComponents(); }); @@ -574,17 +581,13 @@ describe('StoreLayoutComponent', () => { expect(authService.logout).toHaveBeenCalled(); expect(cartService.clearCart).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith(['/']); - expect(TestBed.inject(ToastService).info).toHaveBeenCalledWith( - 'Sesión cerrada correctamente.', - ); + expect(TestBed.inject(ToastService).info).toHaveBeenCalledWith('Sesión cerrada correctamente.'); }); it('shows a danger toast when logout fails', async () => { const authService = TestBed.inject(AuthService); const message = 'La sesión no pudo cerrarse en el servidor.'; - (authService.logout as any).mockReturnValue( - throwError(() => ({ error: { message } })), - ); + (authService.logout as any).mockReturnValue(throwError(() => ({ error: { message } }))); const fixture = TestBed.createComponent(StoreLayoutComponent); fixture.detectChanges(); diff --git a/src/app/core/layout/store-layout/store-layout.component.ts b/src/app/core/layout/store-layout/store-layout.component.ts index 9a40985..803f6c1 100644 --- a/src/app/core/layout/store-layout/store-layout.component.ts +++ b/src/app/core/layout/store-layout/store-layout.component.ts @@ -1,5 +1,15 @@ +import { isPlatformBrowser } from '@angular/common'; import { HttpErrorResponse } from '@angular/common/http'; -import { Component, computed, DestroyRef, inject, OnInit, signal } from '@angular/core'; +import { + Component, + computed, + DestroyRef, + effect, + inject, + OnInit, + PLATFORM_ID, + signal, +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, @@ -8,7 +18,7 @@ import { Router, RouterOutlet, } from '@angular/router'; -import { filter } from 'rxjs'; +import { filter, firstValueFrom } from 'rxjs'; import { TenantService } from '../../services/tenant.service'; import { CartService } from '../../services/cart/cart.service'; import { StoreFooterComponent, StoreFooterSection } from './store-footer/store-footer.component'; @@ -25,6 +35,9 @@ import { import { ToastService } from '../../services/toast.service'; import { Category } from '../../services/tenant.interface'; import { CheckoutCountdownService } from '../../services/checkout-countdown.service'; +import { EventDateNotice, EventDateNoticeService } from '../../services/event-date-notice.service'; +import { ModalService } from '../../services/modal.service'; +import { EventDateNoticeModalComponent } from '../../../shared/components/event-date-notice-modal/event-date-notice-modal.component'; @Component({ selector: 'app-store-layout', @@ -48,6 +61,10 @@ export class StoreLayoutComponent implements OnInit { private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly destroyRef = inject(DestroyRef); + private readonly platformId = inject(PLATFORM_ID); + private readonly eventDateNoticeService = inject(EventDateNoticeService); + private readonly modalService = inject(ModalService); + private claimedNoticeUserId: number | null = null; protected readonly isCartOpen = signal(false); protected readonly isCheckoutRoute = signal(this.isCheckoutUrl(this.router.url)); @@ -68,6 +85,27 @@ export class StoreLayoutComponent implements OnInit { () => this.cartEditingPolicy()?.allow_update_variant ?? false, ); + constructor() { + effect(() => { + const user = this.authService.user(); + + if (!isPlatformBrowser(this.platformId) || !user) { + this.claimedNoticeUserId = null; + return; + } + + if (this.claimedNoticeUserId === user.id) { + return; + } + + this.claimedNoticeUserId = user.id; + this.eventDateNoticeService.claim().subscribe({ + next: ({ data }) => void this.showEventDateNotices(data, user.id), + error: (error) => console.error('Error loading event date notices', error), + }); + }); + } + protected readonly cartSubtotal = computed(() => { const cart = this.cartService.cart(); return cart ? parseFloat(cart.subtotal) : 0; @@ -207,6 +245,28 @@ export class StoreLayoutComponent implements OnInit { this.isCartOpen.update((isOpen) => !isOpen); } + private async showEventDateNotices(notices: EventDateNotice[], userId: number): Promise { + for (const notice of notices) { + if (this.authService.user()?.id !== userId) { + return; + } + + const modalRef = this.modalService.open( + EventDateNoticeModalComponent, + { + size: 'sm', + data: notice, + }, + ); + + await firstValueFrom(modalRef.afterClosed$); + + if (modalRef.dismissReason() === 'replaced') { + return; + } + } + } + protected onSearch(term: string): void { void this.router.navigate(['/buscar'], { queryParams: { q: term, page: 1 }, diff --git a/src/app/core/services/event-date-notice.service.ts b/src/app/core/services/event-date-notice.service.ts index d6b8b66..d1ba947 100644 --- a/src/app/core/services/event-date-notice.service.ts +++ b/src/app/core/services/event-date-notice.service.ts @@ -1,32 +1,39 @@ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; -import { EventDateNoticeModalComponent } from '../../shared/components/event-date-notice-modal/event-date-notice-modal.component'; -import { ModalService } from './modal.service'; -import { TenantEventDateNotice } from './tenant.interface'; +import { environment } from '../../../environments/environment'; +import { ApiResponse } from './api-response.interface'; +import { BaseApiService } from './base-api.service'; +import { TenantService } from './tenant.service'; + +export type EventDateNoticeType = 'rescheduled' | 'suspended'; + +export interface EventDateNoticeSegment { + text: string; + bold: boolean; +} + +export interface EventDateNotice { + type: EventDateNoticeType; + change_ids: number[]; + title: string; + message: EventDateNoticeSegment[]; +} @Injectable({ providedIn: 'root' }) -export class EventDateNoticeService { - private readonly modalService = inject(ModalService); +export class EventDateNoticeService extends BaseApiService { + private readonly tenantService = inject(TenantService); - show(notices: readonly TenantEventDateNotice[]): void { - this.showNext(notices, 0); - } + claim(): Observable> { + const tenant = this.tenantService.getTenant(); - private showNext(notices: readonly TenantEventDateNotice[], index: number): void { - const notice = notices[index]; - - if (!notice) { - return; + if (!tenant) { + throw new Error('No se pudo resolver el tenant activo.'); } - this.modalService - .open( - EventDateNoticeModalComponent, - { - size: 'sm', - data: notice, - }, - ) - .afterClosed$.subscribe(() => this.showNext(notices, index + 1)); + return this.withoutLoading().http.post>( + `${environment.url}tenants/${tenant.codigo}/event-date-notices/claim`, + {}, + ); } } diff --git a/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.html b/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.html index 470e4d5..96fb0ba 100644 --- a/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.html +++ b/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.html @@ -1,17 +1,15 @@ -
-

{{ data.title }}

+
+

{{ notice.title }}

-

- @for (part of data.message; track $index) { - @if (part.bold) { - {{ part.text }} +

+ @for (segment of notice.message; track $index) { + @if (segment.bold) { + {{ segment.text }} } @else { - {{ part.text }} + {{ segment.text }} } }

-
- Cerrar -
-
+ Cerrar + diff --git a/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.scss b/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.scss index cb00217..9832096 100644 --- a/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.scss +++ b/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.scss @@ -1,33 +1,28 @@ -.event-date-notice { +.event-date-notice-modal { display: grid; - gap: 20px; - justify-items: center; + gap: 1.5rem; + justify-items: stretch; text-align: center; } -.event-date-notice__title, -.event-date-notice__description { +h2, +p { margin: 0; } -.event-date-notice__title { - color: var(--color-danger, #dc3545); +h2 { + color: var(--tenant-danger); font-size: 16px; font-weight: 700; line-height: 1.25; } -.event-date-notice__description { +p { color: #666666; font-size: 15px; - font-weight: 400; - line-height: 1.4; + line-height: 1.35; } -.event-date-notice__description strong { +b { font-weight: 700; } - -.event-date-notice__actions { - width: 100%; -} diff --git a/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.ts b/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.ts index caf6f87..4ad9e5d 100644 --- a/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.ts +++ b/src/app/shared/components/event-date-notice-modal/event-date-notice-modal.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { EventDateNotice } from '../../../core/services/event-date-notice.service'; import { MODAL_DATA, ModalRef } from '../../../core/services/modal.service'; -import { TenantEventDateNotice } from '../../../core/services/tenant.interface'; import { ButtonComponent } from '../button/button.component'; @Component({ @@ -12,7 +12,7 @@ import { ButtonComponent } from '../button/button.component'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class EventDateNoticeModalComponent { - protected readonly data = inject(MODAL_DATA); + protected readonly notice = inject(MODAL_DATA); private readonly modalRef = inject>(ModalRef); protected close(): void {