Feature: Royal Mail tracking integration (hybrid approach)

Add ability to sync shipment tracking data from Royal Mail API:

- Create Royal Mail API service (src/lib/royalmail.ts) to fetch tracking status
- Add sync-tracking endpoint to pull latest status from Royal Mail
- Create SyncRoyalMailButton component for admin panel
- Add 'Sync from Royal Mail' button on order detail page
- Auto-update order status based on Royal Mail tracking status

Admin workflow:
1. Create shipping label in Royal Mail Click & Drop
2. Copy tracking number into tracking form
3. Click 'Sync from Royal Mail' to fetch and update status
4. System maps Royal Mail status to our order status (SHIPPED, DELIVERED, etc)

Uses Royal Mail's public tracking API - no special credentials required.
Supports manual label creation workflow (hybrid approach).
This commit is contained in:
Andymick
2026-07-18 20:36:40 +01:00
parent 440a735b8a
commit c476f92a66
5 changed files with 265 additions and 7 deletions
+71
View File
@@ -0,0 +1,71 @@
'use client';
import { useState } from 'react';
interface SyncRoyalMailButtonProps {
orderId: string;
trackingNumber?: string | null;
onSuccess?: () => void;
}
export default function SyncRoyalMailButton({ orderId, trackingNumber, onSuccess }: SyncRoyalMailButtonProps) {
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
const handleSync = async () => {
if (!trackingNumber) {
setMessage({ type: 'error', text: 'Please enter a tracking number first' });
return;
}
setLoading(true);
setMessage(null);
try {
const res = await fetch(`/api/orders/${orderId}/sync-tracking`, {
method: 'POST',
});
const data = await res.json();
if (!res.ok) {
setMessage({ type: 'error', text: data.error || 'Failed to sync tracking' });
return;
}
setMessage({
type: 'success',
text: `Tracking synced! Status: ${data.trackingData.status}`,
});
onSuccess?.();
} catch (err) {
setMessage({ type: 'error', text: 'Error syncing with Royal Mail' });
} finally {
setLoading(false);
}
};
return (
<div className="space-y-2">
<button
type="button"
onClick={handleSync}
disabled={loading || !trackingNumber}
className="bg-splash-blue px-4 py-2 text-sm font-medium text-paper hover:bg-splash-blue/90 disabled:opacity-60"
>
{loading ? 'Syncing...' : '🔄 Sync from Royal Mail'}
</button>
{message && (
<p
className={`text-sm ${
message.type === 'success' ? 'text-splash-blue' : 'text-splash-pink'
}`}
>
{message.text}
</p>
)}
</div>
);
}