Feature: Add WhatsApp notifications for collection-ready orders

- Implement WhatsApp Business API integration via Meta Cloud API
- Add phoneNumber field to Order model for storing customer phone numbers
- Update admin order detail page with phone number input for collection orders
- Send WhatsApp notification when admin marks order as ready for collection
- Add WhatsApp service module with phone number formatting and message sending
- Create database migrations for collection-related fields
- Add WhatsApp credentials to .env configuration

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Andymick
2026-07-19 09:29:09 +01:00
co-authored by Claude Haiku 4.5
parent a105fdf660
commit 36cc6dddff
26 changed files with 553 additions and 80 deletions
+16 -6
View File
@@ -10,15 +10,17 @@ function formatPrice(cents: number) {
return `£${(cents / 100).toFixed(2)}`;
}
function statusLabel(status: string) {
if (status === 'PAID') return 'Confirmed';
function statusLabel(status: string, trackingNumber?: string | null, collectedByCustomer?: boolean) {
if (status === 'DELIVERED') return collectedByCustomer ? 'Collected' : 'Delivered';
if (status === 'PAID') return trackingNumber ? 'Dispatched' : 'Awaiting dispatch';
if (status === 'FAILED') return 'Failed';
if (status === 'PENDING') return 'Waiting payment';
return 'Processing';
}
function statusClasses(status: string) {
if (status === 'PAID') return 'bg-splash-blue/10 text-splash-blue';
function statusClasses(status: string, trackingNumber?: string | null) {
if (status === 'DELIVERED') return 'bg-green-500/10 text-green-600';
if (status === 'PAID') return trackingNumber ? 'bg-splash-blue/10 text-splash-blue' : 'bg-splash-orange/10 text-splash-orange';
if (status === 'FAILED') return 'bg-splash-pink/10 text-splash-pink';
return 'bg-splash-orange/10 text-splash-orange';
}
@@ -114,13 +116,21 @@ export default async function AccountPage() {
{new Date(order.createdAt).toLocaleDateString(undefined, { dateStyle: 'medium' })} ·{' '}
{order.items.length} item{order.items.length === 1 ? '' : 's'}
</p>
<span className={`tag-label mt-1 inline-block rounded-full px-3 py-1 ${statusClasses(order.status)}`}>
{statusLabel(order.status)}
<span className={`tag-label mt-1 inline-block rounded-full px-3 py-1 ${statusClasses(order.status, order.trackingNumber)}`}>
{statusLabel(order.status, order.trackingNumber, order.collectedByCustomer)}
</span>
</div>
<span className="font-mono text-sm">{formatPrice(order.total)}</span>
</Link>
{/* Collection Ready Notification */}
{order.isCollectionPickup && order.readyForCollection && (
<div className="mt-3 border-t border-line pt-3 bg-green-500/5 p-3 rounded border border-green-500/30 text-sm">
<p className="text-green-700 font-medium"> Your order is ready for collection</p>
<p className="text-muted text-xs mt-1">Please come collect your order at your earliest convenience</p>
</div>
)}
{/* Tracking Information */}
{order.carrier && order.trackingNumber && (
<div className="mt-3 border-t border-line pt-3 text-sm">