Description
Invoices approaching their due date provide no proactive visual urgency to the payer or creator; the due date is a static field. This issue adds a dynamic expiry countdown banner on both the creator's /invoice/[id] view and the public payer page that shows a live countdown (days, hours, minutes) when the invoice is within 72 hours of its due date, transitions to a red "Overdue" state after the due date passes, and provides a snooze action that suppresses the banner for a configurable duration without modifying the invoice's actual due date.
Technical Context
Create components/invoice/ExpiryCountdownBanner.tsx that receives dueDate: string and computes time remaining using Date.now() inside a setInterval updating every minute. Display { days, hours, minutes } using a countdown format. Apply color transitions: amber for 72 hours or fewer remaining, red for overdue. The snooze action stores { invoiceId, snoozedUntil } in localStorage and suppresses the banner until snoozedUntil. The snooze duration options are: 1 hour, 4 hours, until tomorrow. On the creator side, wire the banner into app/invoice/[id]/page.tsx above the invoice header. On the public page (/pay/[invoiceId]), the same component is rendered but without the snooze option; payers always see the live countdown. Clear snooze state when the invoice is paid.
Acceptance Criteria
Description
Invoices approaching their due date provide no proactive visual urgency to the payer or creator; the due date is a static field. This issue adds a dynamic expiry countdown banner on both the creator's
/invoice/[id]view and the public payer page that shows a live countdown (days, hours, minutes) when the invoice is within 72 hours of its due date, transitions to a red "Overdue" state after the due date passes, and provides a snooze action that suppresses the banner for a configurable duration without modifying the invoice's actual due date.Technical Context
Create
components/invoice/ExpiryCountdownBanner.tsxthat receivesdueDate: stringand computes time remaining usingDate.now()inside asetIntervalupdating every minute. Display{ days, hours, minutes }using a countdown format. Apply color transitions: amber for 72 hours or fewer remaining, red for overdue. The snooze action stores{ invoiceId, snoozedUntil }inlocalStorageand suppresses the banner untilsnoozedUntil. The snooze duration options are: 1 hour, 4 hours, until tomorrow. On the creator side, wire the banner intoapp/invoice/[id]/page.tsxabove the invoice header. On the public page (/pay/[invoiceId]), the same component is rendered but without the snooze option; payers always see the live countdown. Clear snooze state when the invoice is paid.Acceptance Criteria
localStorage; returning to the page within the snooze window keeps the banner hidden