- Fix vinyl add-on checkout: product line item was dropped when vinyl selected; entryUnitPrice also excluded base product price - Store vinyl per-letter price on cart entry so CartDrawer charges the config price, not hardcoded 65¢ - Fix two bare modifiers.find() calls (use optional chaining) to prevent checkout crash on bad data - Validate deliveryCents (must be non-negative integer) and customer name fields (no control chars) in checkout API - Validate rateOverride values are non-negative numbers in delivery-quote API - Add RFC 5545 iCalendar escaping to SUMMARY/LOCATION/DESCRIPTION fields to prevent calendar injection - Add public /api/hours route; pickup and delivery calendars now fetch admin-saved hours and pre-grey closed days - Reset delivery quote and slot when high-rate item is removed from cart - Change delivery window copy from 2 hours to 1 hour (DeliveryDatePicker + terms page) - Fix SVG paths: /color/images/ → /color-picker/images/ (balloon mask, shine, color backgrounds); was causing Safari ? placeholders - Enlarge padlock icon in PaymentForm from 11px to 14px for better alignment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import { geocode, calcDelivery, inferTier } from '@/lib/delivery'
|
|
import { readDeliveryRates } from '@/lib/delivery-rates'
|
|
|
|
export async function POST(request: Request) {
|
|
const { address, itemNames, rateOverride } = await request.json() as {
|
|
address: string
|
|
itemNames: string[]
|
|
rateOverride?: { base: number; perMile: number }
|
|
}
|
|
|
|
if (!address?.trim()) {
|
|
return NextResponse.json({ error: 'Address required' }, { status: 400 })
|
|
}
|
|
|
|
const coords = await geocode(address)
|
|
if (!coords) {
|
|
return NextResponse.json({ error: 'Address not found — please try a more specific address.' }, { status: 422 })
|
|
}
|
|
|
|
const tier = inferTier(itemNames ?? [])
|
|
const rates = readDeliveryRates()
|
|
|
|
// Apply per-item rate override if provided (overrides just base and perMile for the inferred tier)
|
|
if (rateOverride) {
|
|
if (typeof rateOverride.base !== 'number' || rateOverride.base < 0 ||
|
|
typeof rateOverride.perMile !== 'number' || rateOverride.perMile < 0) {
|
|
return NextResponse.json({ error: 'Invalid rate override' }, { status: 400 })
|
|
}
|
|
rates[tier] = {
|
|
...rates[tier],
|
|
base: rateOverride.base,
|
|
perMile: rateOverride.perMile,
|
|
}
|
|
}
|
|
|
|
const quote = await calcDelivery(coords.lat, coords.lng, tier, rates)
|
|
|
|
if (quote.miles > 40) {
|
|
return NextResponse.json(
|
|
{
|
|
error: `This address is ${quote.miles.toFixed(1)} miles away — online scheduling is available within 40 miles. Please contact us directly to arrange delivery.`,
|
|
tooFar: true,
|
|
},
|
|
{ status: 422 }
|
|
)
|
|
}
|
|
|
|
return NextResponse.json(quote)
|
|
}
|