Feature: Add database-driven shipping rates management
- Add ShippingRate model to Prisma schema with service name, base price, per-kg surcharge, estimated delivery days, and international flag - Create migration to add shipping_rates table - Update seed.ts to populate initial Royal Mail rates (domestic and international) - Refactor getRoyalMailShippingQuotes to fetch rates from database with fallback to defaults - Add /admin/shipping-rates page for managing rates - Add ShippingRatesForm component with inline editing and add/remove functionality - Add server actions for saving and deleting rates - Add "Shipping rates" link to admin navigation Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
617a5acbb2
commit
f770428556
@@ -0,0 +1,68 @@
|
||||
'use server';
|
||||
|
||||
import { redirect } from 'next/navigation';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export async function saveShippingRates(formData: FormData) {
|
||||
const entries = formData.getAll('rates') as string[];
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
redirect('/admin/shipping-rates?error=no_rates');
|
||||
}
|
||||
|
||||
try {
|
||||
for (const entry of entries) {
|
||||
const rate = JSON.parse(entry) as {
|
||||
id?: string;
|
||||
service: string;
|
||||
basePrice: number;
|
||||
pricePerKg: number;
|
||||
estimatedDays: number;
|
||||
isInternational: boolean;
|
||||
};
|
||||
|
||||
if (rate.id) {
|
||||
// Update existing rate
|
||||
await prisma.shippingRate.update({
|
||||
where: { id: rate.id },
|
||||
data: {
|
||||
service: rate.service,
|
||||
basePrice: Math.round(rate.basePrice),
|
||||
pricePerKg: Math.round(rate.pricePerKg),
|
||||
estimatedDays: rate.estimatedDays,
|
||||
isInternational: rate.isInternational,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Create new rate
|
||||
await prisma.shippingRate.create({
|
||||
data: {
|
||||
service: rate.service,
|
||||
basePrice: Math.round(rate.basePrice),
|
||||
pricePerKg: Math.round(rate.pricePerKg),
|
||||
estimatedDays: rate.estimatedDays,
|
||||
isInternational: rate.isInternational,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✓ Shipping rates updated');
|
||||
redirect('/admin/shipping-rates?success=rates_saved');
|
||||
} catch (error) {
|
||||
console.error('Error saving shipping rates:', error);
|
||||
redirect('/admin/shipping-rates?error=save_failed');
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteShippingRate(id: string) {
|
||||
try {
|
||||
await prisma.shippingRate.delete({
|
||||
where: { id },
|
||||
});
|
||||
redirect('/admin/shipping-rates?success=rate_deleted');
|
||||
} catch (error) {
|
||||
console.error('Error deleting shipping rate:', error);
|
||||
redirect('/admin/shipping-rates?error=delete_failed');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { isAdminAuthenticated } from '@/lib/adminAuth';
|
||||
import AdminNav from '@/components/AdminNav';
|
||||
import ShippingRatesForm from '@/components/ShippingRatesForm';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default async function ShippingRatesPage() {
|
||||
const isAdmin = await isAdminAuthenticated();
|
||||
if (!isAdmin) {
|
||||
return <div className="px-6 py-12 text-center">Unauthorized</div>;
|
||||
}
|
||||
|
||||
const shippingRates = await prisma.shippingRate.findMany({
|
||||
orderBy: { isInternational: 'asc' },
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl px-6 py-12">
|
||||
<AdminNav />
|
||||
<div className="flex items-baseline justify-between gap-3">
|
||||
<h1 className="font-display text-3xl">Shipping Rates</h1>
|
||||
<Link href="/admin/dashboard" className="text-sm text-clay hover:text-clay-dark">
|
||||
Back to dashboard
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<p className="mt-2 text-sm text-muted">
|
||||
Manage Royal Mail shipping rates. Changes apply immediately to checkout.
|
||||
</p>
|
||||
|
||||
<ShippingRatesForm existingRates={shippingRates} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user