"use client"; import { useState, useEffect } from "react"; import { getShippingSettings, saveShippingSettings, testFedExConnection, type ShippingSettings, } from "@/actions/shipping/settings"; import { AdminInput, AdminTextInput, AdminTextarea, AdminSelect, AdminButton } from "./design-system"; import { AdminToggle } from "./design-system/AdminToggle"; const SERVICE_OPTIONS = [ { value: "FEDEX_OVERNIGHT", label: "FedEx Overnight" }, { value: "FEDEX_2_DAY_AIR", label: "FedEx 2-Day Air" }, { value: "FEDEX_EXPRESS_SAVER", label: "FedEx Express Saver" }, { value: "FEDEX_GROUND", label: "FedEx Ground" }, ]; type Props = { settings: ShippingSettings | null; brandId: string; brands?: { id: string; name: string }[]; isPlatformAdmin?: boolean; }; interface ValidationErrors { fedexAccountNumber?: string; fedexApiKey?: string; fedexApiSecret?: string; } export default function ShippingSettingsForm({ settings, brandId: initialBrandId, brands = [], isPlatformAdmin = false, }: Props) { const [activeBrandId, setActiveBrandId] = useState(initialBrandId); const [fedexAccountNumber, setFedexAccountNumber] = useState(settings?.fedex_account_number ?? ""); const [fedexApiKey, setFedexApiKey] = useState(settings?.fedex_api_key ?? ""); const [fedexApiSecret, setFedexApiSecret] = useState(settings?.fedex_api_secret ?? ""); const [fedexUseProduction, setFedexUseProduction] = useState(settings?.fedex_use_production ?? false); const [defaultServiceType, setDefaultServiceType] = useState(settings?.default_service_type ?? "FEDEX_GROUND"); const [refrigeratedHandlingNotes, setRefrigeratedHandlingNotes] = useState( settings?.refrigerated_handling_notes ?? "Keep refrigerated. Do not freeze. Handle with care — contains fresh sweet corn and/or onions." ); const [fragileHandlingNotes, setFragileHandlingNotes] = useState(settings?.fragile_handling_notes ?? ""); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const [showSecret, setShowSecret] = useState(false); const [dirty, setDirty] = useState(false); const [errors, setErrors] = useState({}); const isConfigured = !!settings?.fedex_api_key && !!settings?.fedex_api_secret && !!settings?.fedex_account_number; // Track dirty state useEffect(() => { if (settings) { const hasChanges = fedexAccountNumber !== (settings.fedex_account_number ?? "") || fedexApiKey !== (settings.fedex_api_key ?? "") || fedexApiSecret !== (settings.fedex_api_secret ?? "") || fedexUseProduction !== (settings.fedex_use_production ?? false) || defaultServiceType !== (settings.default_service_type ?? "FEDEX_GROUND") || refrigeratedHandlingNotes !== (settings.refrigerated_handling_notes ?? "") || fragileHandlingNotes !== (settings.fragile_handling_notes ?? ""); setDirty(hasChanges); } }, [fedexAccountNumber, fedexApiKey, fedexApiSecret, fedexUseProduction, defaultServiceType, refrigeratedHandlingNotes, fragileHandlingNotes, settings]); // Reload settings when brand changes useEffect(() => { if (!isPlatformAdmin) return; setLoading(true); setError(null); setDirty(false); getShippingSettings(activeBrandId).then((result) => { setLoading(false); if (result.success && result.settings) { const s = result.settings; setFedexAccountNumber(s.fedex_account_number ?? ""); setFedexApiKey(s.fedex_api_key ?? ""); setFedexApiSecret(s.fedex_api_secret ?? ""); setFedexUseProduction(s.fedex_use_production); setDefaultServiceType(s.default_service_type); setRefrigeratedHandlingNotes(s.refrigerated_handling_notes ?? ""); setFragileHandlingNotes(s.fragile_handling_notes ?? ""); setErrors({}); } else { // Reset form for new brand setFedexAccountNumber(""); setFedexApiKey(""); setFedexApiSecret(""); setFedexUseProduction(false); setDefaultServiceType("FEDEX_GROUND"); setRefrigeratedHandlingNotes("Keep refrigerated. Do not freeze. Handle with care — contains fresh sweet corn and/or onions."); setFragileHandlingNotes(""); setErrors({}); } }); }, [activeBrandId, isPlatformAdmin]); function validate(): boolean { const newErrors: ValidationErrors = {}; if (fedexAccountNumber && fedexAccountNumber.length !== 12) { newErrors.fedexAccountNumber = "FedEx account number must be 12 digits"; } if (fedexApiKey && fedexApiKey.length < 10) { newErrors.fedexApiKey = "API Key appears too short - verify it from FedEx developer portal"; } if (fedexApiSecret && fedexApiSecret.length < 10) { newErrors.fedexApiSecret = "API Secret appears too short - verify it from FedEx developer portal"; } setErrors(newErrors); return Object.keys(newErrors).length === 0; } async function handleSave(e: React.FormEvent) { e.preventDefault(); // Validate before saving if (!validate()) { setError("Please correct the errors below before saving."); return; } setSaving(true); setError(null); setSaved(false); setTestResult(null); const result = await saveShippingSettings({ brandId: activeBrandId, fedexAccountNumber: fedexAccountNumber || undefined, fedexApiKey: fedexApiKey || undefined, fedexApiSecret: fedexApiSecret || undefined, fedexUseProduction, defaultServiceType, refrigeratedHandlingNotes: refrigeratedHandlingNotes || undefined, fragileHandlingNotes: fragileHandlingNotes || undefined, }); setSaving(false); if (!result.success) { setError(result.error ?? "Failed to save"); } else { setSaved(true); setDirty(false); setTimeout(() => setSaved(false), 4000); } } async function handleTestConnection() { if (!fedexApiKey || !fedexApiSecret) { setTestResult({ success: false, message: "Please enter both API Key and API Secret before testing." }); return; } setTesting(true); setTestResult(null); const result = await testFedExConnection(fedexApiKey, fedexApiSecret, fedexUseProduction); setTesting(false); setTestResult({ success: result.success, message: result.success ? result.message : result.error ?? "Connection failed" }); } return (
{/* Platform admin brand picker */} {isPlatformAdmin && brands.length > 0 && (
setActiveBrandId(e.target.value)} options={brands.map((b) => ({ value: b.id, label: b.name }))} />
)} {/* Connection status banner */}
{isConfigured ? (
FedEx Connected — {settings?.fedex_use_production ? "Production Mode" : "Sandbox/Test Mode"}
) : ( FedEx Not Configured — enter credentials below to enable shipping rates )}
{/* Error/Success messages */} {error && (
{error}
)} {saved && (
Shipping settings saved successfully!
)} {testResult && (
{testResult.success ? ( ) : ( )} {testResult.message}
)} {/* FedEx credentials */}

FedEx API Credentials

Get your credentials from{" "} developer.fedex.com . These are used for shipping fresh produce.

{ setFedexAccountNumber(e.target.value); setDirty(true); if (errors.fedexAccountNumber) { setErrors({ ...errors, fedexAccountNumber: undefined }); } }} placeholder="000000000000" maxLength={12} /> { setFedexApiKey(e.target.value); setDirty(true); if (errors.fedexApiKey) { setErrors({ ...errors, fedexApiKey: undefined }); } }} placeholder="Your FedEx API key" />
{ setFedexApiSecret(e.target.value); setDirty(true); if (errors.fedexApiSecret) { setErrors({ ...errors, fedexApiSecret: undefined }); } }} placeholder="Your FedEx API secret" />
{/* Production toggle */}
{ setFedexUseProduction(checked); setDirty(true); }} label="Use Production Mode" description={fedexUseProduction ? "Live rates, real labels" : "Sandbox/test mode — safe for testing"} />
{/* Test connection */}
{testing ? ( Testing... ) : ( <> Test Connection )} {testResult?.success && ( Connection verified )}
{/* Default shipping options */}

Default Shipping Service

Applied automatically for non-perishable orders. Perishable orders always require Overnight or 2-Day Air.

{ setDefaultServiceType(e.target.value); setDirty(true); }} options={SERVICE_OPTIONS} />
{/* Handling notes */}

Handling Instructions

These notes are attached to shipments for perishable or fragile items. Appear on carrier labels and in warehouse.

{ setRefrigeratedHandlingNotes(e.target.value); setDirty(true); }} rows={3} placeholder="e.g. Keep refrigerated. Do not freeze. Contains fresh sweet corn and/or onions." /> { setFragileHandlingNotes(e.target.value); setDirty(true); }} rows={2} placeholder="e.g. Fragile — handle with care. Do not stack heavy items on top." />
{/* Save/Cancel buttons */} {loading ? (
Loading settings...
) : (
{saving ? ( Saving... ) : ( <> Save Shipping Settings )} {dirty && !saving && ( You have unsaved changes )}
)} ); }