"use client"; import { useMemo, useState, type FormEvent } from "react"; import { SelectField } from "@/components/ui/select-field"; import { FLOCK_LIMITS, FLOCK_VALIDATION_MESSAGES, } from "@/lib/constants/flock-limits"; import { describeDimEquivalent, describeWeightEquivalent, parseCargoInputNumber, } from "@/lib/frontend/cargo-form-units"; import type { UnitDim, UnitWeight } from "@/lib/frontend/types"; import { FLOCK_UI } from "@/lib/flock/ui-labels"; import { defaultFlockPickupDateIso, flockPickupDateValidationMessageFromIso, minFlockPickupDateIso, } from "@/lib/flock/pickup-date"; import { MothershipWeekdayDatePicker } from "@/components/mothership/mothership-weekday-date-picker"; import { defaultFlockPickupDate } from "@/modules/flock/validation"; import { cmToIn, kgToLb, toAxelWholeInches, toAxelWholePounds, } from "@/modules/quote/unit-converter"; export type FlockFormSubmitPayload = { pickup_date: string; pickup_zip: string; delivery_zip: string; pallet_count: number; total_weight: { value: number; unit: "lb" | "kg" }; dimensions: { length: number; width: number; height: number; unit: "in" | "cm"; }; registration?: { first_name?: string; last_name?: string; company?: string; email?: string; phone?: string; shipments_per_month?: string; }; }; export interface FlockQuoteFormProps { disabled?: boolean; submitLocked?: boolean; onSubmit: (payload: FlockFormSubmitPayload) => void; } function toMmDdYyyy(isoDate: string): string { const [y, m, d] = isoDate.split("-"); if (!y || !m || !d) return defaultFlockPickupDate(); return `${m}/${d}/${y}`; } /** Flock 查价表单:单位切换不改数值,服务端自动换算进一(对齐 MotherShip) */ export function FlockQuoteForm({ disabled, submitLocked, onSubmit, }: FlockQuoteFormProps) { const [weightUnit, setWeightUnit] = useState("lb"); const [dimUnit, setDimUnit] = useState("in"); const [pickupDate, setPickupDate] = useState(defaultFlockPickupDateIso); const [pickupZip, setPickupZip] = useState(""); const [deliveryZip, setDeliveryZip] = useState(""); const [palletCount, setPalletCount] = useState("6"); const [totalWeight, setTotalWeight] = useState("3500"); const [length, setLength] = useState( String(FLOCK_LIMITS.defaultDimsIn.length), ); const [width, setWidth] = useState(String(FLOCK_LIMITS.defaultDimsIn.width)); const [height, setHeight] = useState( String(FLOCK_LIMITS.defaultDimsIn.height), ); const [localError, setLocalError] = useState(null); const [showRegistration, setShowRegistration] = useState(false); const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [company, setCompany] = useState(""); const [workEmail, setWorkEmail] = useState(""); const [phone, setPhone] = useState(""); const [shipmentsPerMonth, setShipmentsPerMonth] = useState("1-25"); const weightHint = useMemo(() => { const v = parseCargoInputNumber(totalWeight); return v == null ? null : describeWeightEquivalent(v, weightUnit); }, [totalWeight, weightUnit]); const dimHint = useMemo(() => { const l = parseCargoInputNumber(length); const w = parseCargoInputNumber(width); const h = parseCargoInputNumber(height); if (l == null || w == null || h == null) return null; return describeDimEquivalent(l, w, h, dimUnit); }, [length, width, height, dimUnit]); const handleSubmit = (e: FormEvent) => { e.preventDefault(); setLocalError(null); if (!pickupDate) { setLocalError(FLOCK_VALIDATION_MESSAGES.pickupDate); return; } const pickupDateError = flockPickupDateValidationMessageFromIso(pickupDate); if (pickupDateError) { setLocalError(pickupDateError); return; } if (!pickupZip.trim()) { setLocalError(FLOCK_VALIDATION_MESSAGES.pickupZip); return; } if (!deliveryZip.trim()) { setLocalError(FLOCK_VALIDATION_MESSAGES.deliveryZip); return; } const pallets = Number(palletCount); if ( !Number.isInteger(pallets) || pallets < FLOCK_LIMITS.palletCount.min || pallets > FLOCK_LIMITS.palletCount.max ) { setLocalError(FLOCK_VALIDATION_MESSAGES.palletCount); return; } const weightVal = parseCargoInputNumber(totalWeight); const l = parseCargoInputNumber(length); const w = parseCargoInputNumber(width); const h = parseCargoInputNumber(height); if (weightVal == null || l == null || w == null || h == null) { setLocalError("请完整填写重量与尺寸"); return; } const totalWeightLb = toAxelWholePounds( weightUnit === "kg" ? kgToLb(weightVal) : weightVal, ); if (totalWeightLb > FLOCK_LIMITS.totalWeightLbMax) { setLocalError(FLOCK_VALIDATION_MESSAGES.totalWeight); return; } const toIn = (v: number) => toAxelWholeInches(dimUnit === "cm" ? cmToIn(v) : v); const lengthIn = toIn(l); const widthIn = toIn(w); const heightIn = toIn(h); if (lengthIn > FLOCK_LIMITS.dimIn.lengthMax) { setLocalError(FLOCK_VALIDATION_MESSAGES.length); return; } if (widthIn > FLOCK_LIMITS.dimIn.widthMax) { setLocalError(FLOCK_VALIDATION_MESSAGES.width); return; } if (heightIn > FLOCK_LIMITS.dimIn.heightMax) { setLocalError(FLOCK_VALIDATION_MESSAGES.height); return; } let registration: FlockFormSubmitPayload["registration"]; if (showRegistration) { const regEmail = workEmail.trim(); const regPhone = phone.replace(/\D/g, ""); if (regEmail && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(regEmail)) { setLocalError("请填写有效的工作邮箱"); return; } if (regPhone && regPhone.slice(-10).length !== 10) { setLocalError("请填写有效的 10 位美国电话号码"); return; } const reg = { first_name: firstName.trim() || undefined, last_name: lastName.trim() || undefined, company: company.trim() || undefined, email: regEmail || undefined, phone: regPhone ? regPhone.slice(-10) : undefined, shipments_per_month: shipmentsPerMonth || undefined, }; if ( reg.first_name || reg.last_name || reg.company || reg.email || reg.phone ) { registration = reg; } } onSubmit({ pickup_date: toMmDdYyyy(pickupDate), pickup_zip: pickupZip.trim(), delivery_zip: deliveryZip.trim(), pallet_count: pallets, total_weight: { value: weightVal, unit: weightUnit }, dimensions: { length: l, width: w, height: h, unit: dimUnit, }, registration, }); }; const fieldClass = "w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-primary"; const labelClass = "mb-1 block text-sm font-medium text-text-primary"; return (

{FLOCK_UI.formTitle}

{FLOCK_UI.formHint}

{FLOCK_UI.pickupDateHint}

setPickupZip(e.target.value)} placeholder="例如 90001" inputMode="numeric" required />
setDeliveryZip(e.target.value)} placeholder="例如 75201" inputMode="numeric" required />
setPalletCount(e.target.value)} inputMode="numeric" required />

{FLOCK_UI.palletCountHint}

setTotalWeight(e.target.value)} inputMode="decimal" required />

{FLOCK_UI.totalWeightHint}

{weightHint && (

{weightHint}

)}
setWeightUnit(e.target.value as UnitWeight)} />

{FLOCK_UI.dimsTitle}({dimUnit})

setLength(e.target.value)} inputMode="decimal" />

{FLOCK_VALIDATION_MESSAGES.length}

setWidth(e.target.value)} inputMode="decimal" />

{FLOCK_VALIDATION_MESSAGES.width}

setHeight(e.target.value)} inputMode="decimal" />

{FLOCK_VALIDATION_MESSAGES.height}

setDimUnit(e.target.value as UnitDim)} />
{dimHint &&

{dimHint}

}

{FLOCK_UI.unitSwitchHint}

{showRegistration && ( <>

{FLOCK_UI.registrationSectionHint}

setFirstName(e.target.value)} autoComplete="given-name" />
setLastName(e.target.value)} autoComplete="family-name" />
setCompany(e.target.value)} autoComplete="organization" />
setWorkEmail(e.target.value)} placeholder="例如 name@company.com" autoComplete="email" />
setPhone(e.target.value)} placeholder="例如 7536407420" inputMode="tel" autoComplete="tel" />

{FLOCK_UI.phoneHint}

setShipmentsPerMonth(e.target.value)} />
)}
{localError && (

{localError}

)}
); }