diff --git a/src/components/settlement/SettlementReceipt.test.tsx b/src/components/settlement/SettlementReceipt.test.tsx new file mode 100644 index 00000000..d1f6c92f --- /dev/null +++ b/src/components/settlement/SettlementReceipt.test.tsx @@ -0,0 +1,32 @@ +/** @vitest-environment happy-dom */ + +import { describe, expect, it, vi } from 'vitest'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { buildReceiptFilename, SettlementReceipt } from './SettlementReceipt'; + +const data = { + commitmentId: 'commitment/1', + finalValue: '$100', + fees: '$2', + transactionHash: '0xabc', + settledAt: '2026-07-30T10:00:00.000Z', +}; + +describe('SettlementReceipt', () => { + it('sanitizes the printable filename', () => { + expect(buildReceiptFilename(data.commitmentId, data.settledAt)).toBe( + 'settlement-receipt-commitment-1-2026-07-30', + ); + }); + + it('renders only after success and prints from the accessible action', () => { + const print = vi.spyOn(window, 'print').mockImplementation(() => undefined); + const { rerender } = render(); + expect(screen.queryByRole('region', { name: 'Settlement receipt' })).not.toBeInTheDocument(); + + rerender(); + fireEvent.click(screen.getByRole('button', { name: 'Download receipt' })); + expect(print).toHaveBeenCalledOnce(); + print.mockRestore(); + }); +}); diff --git a/src/components/settlement/SettlementReceipt.tsx b/src/components/settlement/SettlementReceipt.tsx new file mode 100644 index 00000000..a94c4286 --- /dev/null +++ b/src/components/settlement/SettlementReceipt.tsx @@ -0,0 +1,54 @@ +'use client'; + +export interface SettlementReceiptData { + commitmentId: string; + finalValue: string; + fees: string; + transactionHash: string; + settledAt: string; +} + +export function buildReceiptFilename(commitmentId: string, settledAt: string): string { + const safeId = commitmentId.replace(/[^a-z0-9_-]/gi, '-').slice(0, 80) || 'commitment'; + const date = new Date(settledAt).toISOString().slice(0, 10); + return `settlement-receipt-${safeId}-${date}`; +} + +export function printSettlementReceipt(data: SettlementReceiptData): void { + const previousTitle = document.title; + document.title = buildReceiptFilename(data.commitmentId, data.settledAt); + window.print(); + window.setTimeout(() => { + document.title = previousTitle; + }, 0); +} + +export function SettlementReceipt({ + status, + data, +}: { + status: 'pending' | 'success'; + data: SettlementReceiptData; +}) { + if (status !== 'success') return null; + + return ( + + Settlement receipt + + Commitment{data.commitmentId} + Final value{data.finalValue} + Fees / penalty{data.fees} + Transaction{data.transactionHash} + Settled{data.settledAt} + + printSettlementReceipt(data)} + className="mt-5 rounded-lg bg-black px-4 py-2 text-sm text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400" + > + Download receipt + + + ); +}