feat(event): display pending date change notices
This commit is contained in:
parent
17dbf52181
commit
f87f7f55a9
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
for (const notice of notices) {
|
||||
if (this.authService.user()?.id !== userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modalRef = this.modalService.open<EventDateNoticeModalComponent, void, EventDateNotice>(
|
||||
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 },
|
||||
|
|
|
|||
|
|
@ -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<ApiResponse<EventDateNotice[]>> {
|
||||
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, void, TenantEventDateNotice>(
|
||||
EventDateNoticeModalComponent,
|
||||
{
|
||||
size: 'sm',
|
||||
data: notice,
|
||||
},
|
||||
)
|
||||
.afterClosed$.subscribe(() => this.showNext(notices, index + 1));
|
||||
return this.withoutLoading().http.post<ApiResponse<EventDateNotice[]>>(
|
||||
`${environment.url}tenants/${tenant.codigo}/event-date-notices/claim`,
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
<article class="event-date-notice">
|
||||
<h2 class="event-date-notice__title">{{ data.title }}</h2>
|
||||
<div class="event-date-notice-modal">
|
||||
<h2>{{ notice.title }}</h2>
|
||||
|
||||
<p class="event-date-notice__description">
|
||||
@for (part of data.message; track $index) {
|
||||
@if (part.bold) {
|
||||
<strong>{{ part.text }}</strong>
|
||||
<p>
|
||||
@for (segment of notice.message; track $index) {
|
||||
@if (segment.bold) {
|
||||
<b>{{ segment.text }}</b>
|
||||
} @else {
|
||||
{{ part.text }}
|
||||
<span>{{ segment.text }}</span>
|
||||
}
|
||||
}
|
||||
</p>
|
||||
|
||||
<div class="event-date-notice__actions">
|
||||
<app-button (click)="close()">Cerrar</app-button>
|
||||
</div>
|
||||
</article>
|
||||
<app-button (click)="close()">Cerrar</app-button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TenantEventDateNotice>(MODAL_DATA);
|
||||
protected readonly notice = inject<EventDateNotice>(MODAL_DATA);
|
||||
private readonly modalRef = inject<ModalRef<void>>(ModalRef);
|
||||
|
||||
protected close(): void {
|
||||
|
|
|
|||
Loading…
Reference in New Issue