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;
|
email: string | null;
|
||||||
items_source: 'purchase';
|
items_source: 'purchase';
|
||||||
items: PurchaseDetailItemResponse[];
|
items: PurchaseDetailItemResponse[];
|
||||||
|
tickets_count?: number;
|
||||||
|
has_generated_tickets?: boolean;
|
||||||
subtotal: string;
|
subtotal: string;
|
||||||
total: string;
|
total: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,18 +16,34 @@
|
|||||||
<hr class="status-content__divider" />
|
<hr class="status-content__divider" />
|
||||||
|
|
||||||
<div class="status-content__section status-content__section--secondary">
|
<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
|
<app-button
|
||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="primary"
|
||||||
hostClass="status-content__button d-block"
|
hostClass="status-content__button d-block"
|
||||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
||||||
(click)="openWhatsApp()"
|
(click)="goToTickets()"
|
||||||
>
|
>
|
||||||
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
<span>Mis tickets</span>
|
||||||
<span>WhatsApp</span>
|
</app-button>
|
||||||
</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>
|
</div>
|
||||||
} @else if (status() === 'pending') {
|
} @else if (status() === 'pending') {
|
||||||
<div class="status-content__section status-content__section--primary">
|
<div class="status-content__section status-content__section--primary">
|
||||||
@@ -71,18 +87,20 @@
|
|||||||
<div class="status-content__section status-content__section--secondary">
|
<div class="status-content__section status-content__section--secondary">
|
||||||
<p class="status-content__message">Si ya pagaste, escribinos para que podamos revisarlo.</p>
|
<p class="status-content__message">Si ya pagaste, escribinos para que podamos revisarlo.</p>
|
||||||
|
|
||||||
<div class="status-content__actions">
|
@if (whatsappUrl()) {
|
||||||
<app-button
|
<div class="status-content__actions">
|
||||||
type="button"
|
<app-button
|
||||||
variant="secondary"
|
type="button"
|
||||||
hostClass="status-content__button d-block"
|
variant="secondary"
|
||||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
hostClass="status-content__button d-block"
|
||||||
(click)="openWhatsApp()"
|
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>
|
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||||
</app-button>
|
<span>WhatsApp</span>
|
||||||
</div>
|
</app-button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="status-content__section status-content__section--primary">
|
<div class="status-content__section status-content__section--primary">
|
||||||
@@ -98,18 +116,20 @@
|
|||||||
<div class="status-content__section status-content__section--secondary">
|
<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>
|
<p class="status-content__message">Volvé a ingresar más tarde. Si el problema sigue, comunicate con nosotros.</p>
|
||||||
|
|
||||||
<div class="status-content__actions">
|
@if (whatsappUrl()) {
|
||||||
<app-button
|
<div class="status-content__actions">
|
||||||
type="button"
|
<app-button
|
||||||
variant="secondary"
|
type="button"
|
||||||
hostClass="status-content__button d-block"
|
variant="secondary"
|
||||||
buttonClass="status-content__button-control d-inline-flex align-items-center justify-content-center gap-2 py-2"
|
hostClass="status-content__button d-block"
|
||||||
(click)="openWhatsApp()"
|
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>
|
<i class="fa-brands fa-whatsapp status-content__button-icon"></i>
|
||||||
</app-button>
|
<span>WhatsApp</span>
|
||||||
</div>
|
</app-button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 325;
|
font-weight: 325;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
color: var(--color-text);
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__hint {
|
&__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 { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { CheckoutService, PurchaseStatusResponse } from '../../../../core/services/checkout.service';
|
import { CheckoutService, PurchaseStatusResponse } from '../../../../core/services/checkout.service';
|
||||||
|
import { findMenu } from '../../../../core/services/menu.utils';
|
||||||
import { TenantService } from '../../../../core/services/tenant.service';
|
import { TenantService } from '../../../../core/services/tenant.service';
|
||||||
import { ButtonComponent } from '../../../../shared/components/button/button.component';
|
import { ButtonComponent } from '../../../../shared/components/button/button.component';
|
||||||
|
|
||||||
@@ -26,6 +27,20 @@ export class PurchaseStatusPageComponent implements OnInit {
|
|||||||
|
|
||||||
protected readonly isLoading = signal(true);
|
protected readonly isLoading = signal(true);
|
||||||
protected readonly status = signal<PurchaseStatusView>('pending');
|
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 {
|
ngOnInit(): void {
|
||||||
const purchaseId = this.route.snapshot.paramMap.get('id');
|
const purchaseId = this.route.snapshot.paramMap.get('id');
|
||||||
@@ -50,6 +65,7 @@ export class PurchaseStatusPageComponent implements OnInit {
|
|||||||
try {
|
try {
|
||||||
const purchase = await this.checkoutService.getPurchase(this.tenantCode, this.purchaseId);
|
const purchase = await this.checkoutService.getPurchase(this.tenantCode, this.purchaseId);
|
||||||
this.status.set(this.resolveStatus(purchase));
|
this.status.set(this.resolveStatus(purchase));
|
||||||
|
this.hasGeneratedTickets.set(purchase.has_generated_tickets === true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch purchase status:', error);
|
console.error('Failed to fetch purchase status:', error);
|
||||||
this.status.set('error');
|
this.status.set('error');
|
||||||
@@ -74,13 +90,19 @@ export class PurchaseStatusPageComponent implements OnInit {
|
|||||||
return 'pending';
|
return 'pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getWhatsAppLink(): string {
|
protected goToTickets(): void {
|
||||||
const phone = '5493416658247';
|
const route = this.ticketsRoute();
|
||||||
const message = encodeURIComponent('Hola! Mi compra fue realizada con \u00E9xito.');
|
|
||||||
return `https://wa.me/${phone}?text=${message}`;
|
if (route) {
|
||||||
|
void this.router.navigateByUrl(route);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected openWhatsApp(): void {
|
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