feat: enhance purchase status page with ticket handling and WhatsApp integration
This commit is contained in:
@@ -68,6 +68,8 @@ export interface PurchaseDetailResponse extends PurchaseStatusResponse {
|
||||
email: string | null;
|
||||
items_source: 'purchase';
|
||||
items: PurchaseDetailItemResponse[];
|
||||
tickets_count?: number;
|
||||
has_generated_tickets?: boolean;
|
||||
subtotal: string;
|
||||
total: string;
|
||||
}
|
||||
|
||||
@@ -16,18 +16,34 @@
|
||||
<hr class="status-content__divider" />
|
||||
|
||||
<div class="status-content__section status-content__section--secondary">
|
||||
<p class="status-content__message">Comunicate con nosotros para coordinar el envío.</p>
|
||||
@if (ticketsRoute()) {
|
||||
<p class="status-content__message">A continuación, vas a poder ver los tickets que debés presentar en el evento.</p>
|
||||
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="openWhatsApp()"
|
||||
>
|
||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||
<span>WhatsApp</span>
|
||||
</app-button>
|
||||
<app-button
|
||||
type="button"
|
||||
variant="primary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="goToTickets()"
|
||||
>
|
||||
<span>Mis tickets</span>
|
||||
</app-button>
|
||||
} @else if (whatsappUrl()) {
|
||||
<p class="status-content__message">Comunicate con nosotros para coordinar el envío.</p>
|
||||
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="openWhatsApp()"
|
||||
>
|
||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||
<span>WhatsApp</span>
|
||||
</app-button>
|
||||
} @else {
|
||||
<p class="status-content__message">Tu compra quedó registrada correctamente.</p>
|
||||
}
|
||||
</div>
|
||||
} @else if (status() === 'pending') {
|
||||
<div class="status-content__section status-content__section--primary">
|
||||
@@ -71,18 +87,20 @@
|
||||
<div class="status-content__section status-content__section--secondary">
|
||||
<p class="status-content__message">Si ya pagaste, escribinos para que podamos revisarlo.</p>
|
||||
|
||||
<div class="status-content__actions">
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="openWhatsApp()"
|
||||
>
|
||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||
<span>WhatsApp</span>
|
||||
</app-button>
|
||||
</div>
|
||||
@if (whatsappUrl()) {
|
||||
<div class="status-content__actions">
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="openWhatsApp()"
|
||||
>
|
||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||
<span>WhatsApp</span>
|
||||
</app-button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="status-content__section status-content__section--primary">
|
||||
@@ -98,18 +116,20 @@
|
||||
<div class="status-content__section status-content__section--secondary">
|
||||
<p class="status-content__message">Volvé a ingresar más tarde. Si el problema sigue, comunicate con nosotros.</p>
|
||||
|
||||
<div class="status-content__actions">
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="openWhatsApp()"
|
||||
>
|
||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||
<span>WhatsApp</span>
|
||||
</app-button>
|
||||
</div>
|
||||
@if (whatsappUrl()) {
|
||||
<div class="status-content__actions">
|
||||
<app-button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
hostClass="status-content__button d-block"
|
||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||
(click)="openWhatsApp()"
|
||||
>
|
||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||
<span>WhatsApp</span>
|
||||
</app-button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
font-size: 13px;
|
||||
font-weight: 325;
|
||||
line-height: 1.45;
|
||||
color: var(--color-text);
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, convertToParamMap, Router } from '@angular/router';
|
||||
|
||||
import { CheckoutService, PurchaseDetailResponse } from '../../../../core/services/checkout.service';
|
||||
import { Tenant } from '../../../../core/services/tenant.interface';
|
||||
import { TenantService } from '../../../../core/services/tenant.service';
|
||||
import { PurchaseStatusPageComponent } from './purchase-status-page.component';
|
||||
|
||||
const tenant: Tenant = {
|
||||
id: 1,
|
||||
codigo: 'tickets-tenant',
|
||||
nombre: 'Tickets tenant',
|
||||
dominio: 'tickets.local',
|
||||
primary_color: '#000000',
|
||||
secondary_color: '#000000',
|
||||
danger_color: '#000000',
|
||||
success_color: '#000000',
|
||||
header_bg_color: '#000000',
|
||||
footer_bg_color: '#000000',
|
||||
header_logo: '',
|
||||
footer_logo: '',
|
||||
categories: [],
|
||||
social_media: [
|
||||
{
|
||||
code: 'whatsapp',
|
||||
icon: 'whatsapp',
|
||||
name: 'WhatsApp',
|
||||
url: 'https://wa.me/543411234567',
|
||||
},
|
||||
],
|
||||
menues: [
|
||||
{
|
||||
id: 1,
|
||||
code: 'account',
|
||||
label: 'Mi cuenta',
|
||||
parent_menu_code: null,
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta',
|
||||
submenues: [
|
||||
{
|
||||
id: 2,
|
||||
code: 'account.tickets',
|
||||
label: 'Mis tickets',
|
||||
parent_menu_code: 'account',
|
||||
content_type: 'dynamic',
|
||||
route: '/mi-cuenta/tickets',
|
||||
submenues: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const purchase = (hasGeneratedTickets: boolean): PurchaseDetailResponse =>
|
||||
({
|
||||
status: 'paid',
|
||||
has_generated_tickets: hasGeneratedTickets,
|
||||
tickets_count: hasGeneratedTickets ? 1 : 0,
|
||||
}) as PurchaseDetailResponse;
|
||||
|
||||
describe('PurchaseStatusPageComponent', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
async function render(hasGeneratedTickets: boolean) {
|
||||
const checkoutService = {
|
||||
getPurchase: vi.fn().mockResolvedValue(purchase(hasGeneratedTickets)),
|
||||
};
|
||||
const router = {
|
||||
navigate: vi.fn().mockResolvedValue(true),
|
||||
navigateByUrl: vi.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PurchaseStatusPageComponent],
|
||||
providers: [
|
||||
{ provide: CheckoutService, useValue: checkoutService },
|
||||
{ provide: TenantService, useValue: { tenant: () => tenant } },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: { snapshot: { paramMap: convertToParamMap({ id: '42' }) } },
|
||||
},
|
||||
{ provide: Router, useValue: router },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(PurchaseStatusPageComponent);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
return {
|
||||
fixture,
|
||||
element: fixture.nativeElement as HTMLElement,
|
||||
checkoutService,
|
||||
router,
|
||||
};
|
||||
}
|
||||
|
||||
it('shows the tickets action when this purchase generated tickets', async () => {
|
||||
const { element, checkoutService, router } = await render(true);
|
||||
|
||||
expect(checkoutService.getPurchase).toHaveBeenCalledOnce();
|
||||
expect(element.textContent).toContain('Ver mis tickets');
|
||||
expect(element.textContent).not.toContain('WhatsApp');
|
||||
|
||||
element.querySelector<HTMLButtonElement>('app-button button')?.click();
|
||||
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/mi-cuenta/tickets');
|
||||
});
|
||||
|
||||
it('shows the tenant WhatsApp action when this purchase did not generate tickets', async () => {
|
||||
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
||||
const { element } = await render(false);
|
||||
|
||||
expect(element.textContent).toContain('WhatsApp');
|
||||
expect(element.textContent).not.toContain('Ver mis tickets');
|
||||
|
||||
element.querySelector<HTMLButtonElement>('app-button button')?.click();
|
||||
|
||||
expect(openSpy).toHaveBeenCalledWith(
|
||||
'https://wa.me/543411234567',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
|
||||
openSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit, inject, signal } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, OnInit, computed, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { CheckoutService, PurchaseStatusResponse } from '../../../../core/services/checkout.service';
|
||||
import { findMenu } from '../../../../core/services/menu.utils';
|
||||
import { TenantService } from '../../../../core/services/tenant.service';
|
||||
import { ButtonComponent } from '../../../../shared/components/button/button.component';
|
||||
|
||||
@@ -26,6 +27,20 @@ export class PurchaseStatusPageComponent implements OnInit {
|
||||
|
||||
protected readonly isLoading = signal(true);
|
||||
protected readonly status = signal<PurchaseStatusView>('pending');
|
||||
protected readonly hasGeneratedTickets = signal(false);
|
||||
protected readonly ticketsRoute = computed(() => {
|
||||
if (!this.hasGeneratedTickets()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return findMenu(this.tenantService.tenant()?.menues ?? [], 'account.tickets')?.route ?? null;
|
||||
});
|
||||
protected readonly whatsappUrl = computed(
|
||||
() =>
|
||||
this.tenantService
|
||||
.tenant()
|
||||
?.social_media?.find((socialMedia) => socialMedia.code === 'whatsapp')?.url ?? null,
|
||||
);
|
||||
|
||||
ngOnInit(): void {
|
||||
const purchaseId = this.route.snapshot.paramMap.get('id');
|
||||
@@ -50,6 +65,7 @@ export class PurchaseStatusPageComponent implements OnInit {
|
||||
try {
|
||||
const purchase = await this.checkoutService.getPurchase(this.tenantCode, this.purchaseId);
|
||||
this.status.set(this.resolveStatus(purchase));
|
||||
this.hasGeneratedTickets.set(purchase.has_generated_tickets === true);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch purchase status:', error);
|
||||
this.status.set('error');
|
||||
@@ -74,13 +90,19 @@ export class PurchaseStatusPageComponent implements OnInit {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
protected getWhatsAppLink(): string {
|
||||
const phone = '5493416658247';
|
||||
const message = encodeURIComponent('Hola! Mi compra fue realizada con \u00E9xito.');
|
||||
return `https://wa.me/${phone}?text=${message}`;
|
||||
protected goToTickets(): void {
|
||||
const route = this.ticketsRoute();
|
||||
|
||||
if (route) {
|
||||
void this.router.navigateByUrl(route);
|
||||
}
|
||||
}
|
||||
|
||||
protected openWhatsApp(): void {
|
||||
window.open(this.getWhatsAppLink(), '_blank', 'noopener,noreferrer');
|
||||
const url = this.whatsappUrl();
|
||||
|
||||
if (url) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user