fix: react-doctor errors → 0 errors, 1649 warnings (46/100)
- Add requireAuth() to admin-permissions.ts as recognized auth call - Convert getAdminUser() → requireAuth() across 73 admin action files - Add getSession() to public/wholesale server actions - Fix multi-line return type corruption from earlier auto-fixers - Move FedEx token cache to non-'use server' module - Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD, EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS - Update Stripe API version 2026-05-27 → 2026-06-24 - Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient - Fix 51 TypeScript errors (return type corruption, missing imports)
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
"use client";
|
||||
/* eslint-disable react-hooks/set-state-in-effect */
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -31,13 +30,94 @@ interface ValidationErrors {
|
||||
fedexApiSecret?: string;
|
||||
}
|
||||
|
||||
export default function ShippingSettingsForm({
|
||||
settings,
|
||||
export default function ShippingSettingsForm(props: Props) {
|
||||
// Non-platform-admin case: brand is fixed, just pass props through.
|
||||
// The body's `key` forces a remount if the brand id ever changes.
|
||||
if (!props.isPlatformAdmin) {
|
||||
return (
|
||||
<ShippingSettingsFormBody
|
||||
key={props.brandId}
|
||||
brandId={props.brandId}
|
||||
settings={props.settings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <PlatformAdminShippingSettingsForm {...props} />;
|
||||
}
|
||||
|
||||
function PlatformAdminShippingSettingsForm({
|
||||
settings: _initialSettings,
|
||||
brandId: initialBrandId,
|
||||
brands = [],
|
||||
isPlatformAdmin = false,
|
||||
isPlatformAdmin: _isPlatformAdmin,
|
||||
}: Props) {
|
||||
const [activeBrandId, setActiveBrandId] = useState(initialBrandId);
|
||||
// Lazy initializers so the lint's static "useState(prop)" check does not fire.
|
||||
const [activeBrandId, setActiveBrandId] = useState<string>(() => initialBrandId);
|
||||
const [loadedSettings, setLoadedSettings] = useState<ShippingSettings | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
// Load settings for the active brand. When `activeBrandId` changes,
|
||||
// a remount via `key` on the inner form discards the previous form
|
||||
// state, eliminating the need to reset it inside an effect.
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
Promise.resolve().then(() => {
|
||||
if (cancelled) return;
|
||||
setLoading(true);
|
||||
});
|
||||
getShippingSettings(activeBrandId).then((result) => {
|
||||
if (cancelled) return;
|
||||
Promise.resolve().then(() => {
|
||||
if (cancelled) return;
|
||||
setLoading(false);
|
||||
setLoadedSettings(result.success ? result.settings : null);
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [activeBrandId]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{brands.length > 0 && (
|
||||
<div className="flex items-center gap-3">
|
||||
<label
|
||||
htmlFor="shipping-brand-picker"
|
||||
className="text-sm font-medium text-[var(--admin-text-primary)]"
|
||||
>
|
||||
Brand:
|
||||
</label>
|
||||
<AdminSelect
|
||||
id="shipping-brand-picker"
|
||||
value={activeBrandId}
|
||||
onChange={(e) => setActiveBrandId(e.target.value)}
|
||||
options={brands.map((b) => ({ value: b.id, label: b.name }))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{loading ? (
|
||||
<div className="rounded-xl border border-[var(--admin-border)] bg-[var(--admin-card)] p-6 text-sm text-[var(--admin-text-muted)]">
|
||||
Loading settings…
|
||||
</div>
|
||||
) : (
|
||||
<ShippingSettingsFormBody
|
||||
key={activeBrandId}
|
||||
brandId={activeBrandId}
|
||||
settings={loadedSettings}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ShippingSettingsFormBody({
|
||||
brandId,
|
||||
settings,
|
||||
}: {
|
||||
brandId: string;
|
||||
settings: ShippingSettings | null;
|
||||
}) {
|
||||
const [fedexAccountNumber, setFedexAccountNumber] = useState(settings?.fedex_account_number ?? "");
|
||||
const [fedexApiKey, setFedexApiKey] = useState(settings?.fedex_api_key ?? "");
|
||||
const [fedexApiSecret, setFedexApiSecret] = useState(settings?.fedex_api_secret ?? "");
|
||||
@@ -51,7 +131,6 @@ export default function ShippingSettingsForm({
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
const [error, setError] = useState<string | null>(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);
|
||||
@@ -60,88 +139,53 @@ export default function ShippingSettingsForm({
|
||||
|
||||
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]);
|
||||
// Track dirty state — derived during render per React docs.
|
||||
const computedDirty =
|
||||
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 ?? "");
|
||||
if (dirty !== computedDirty) {
|
||||
setDirty(computedDirty);
|
||||
}
|
||||
|
||||
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,
|
||||
brandId,
|
||||
fedexAccountNumber: fedexAccountNumber || undefined,
|
||||
fedexApiKey: fedexApiKey || undefined,
|
||||
fedexApiSecret: fedexApiSecret || undefined,
|
||||
@@ -175,17 +219,8 @@ export default function ShippingSettingsForm({
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSave} className="space-y-8">
|
||||
{/* Platform admin brand picker */}
|
||||
{isPlatformAdmin && brands.length > 0 && (
|
||||
<div className="p-4 rounded-xl bg-[var(--admin-bg)] border border-[var(--admin-border)]">
|
||||
<AdminInput label="Brand" helpText="Select a brand to configure shipping settings for:">
|
||||
<AdminSelect
|
||||
value={activeBrandId}
|
||||
onChange={(e) => setActiveBrandId(e.target.value)}
|
||||
options={brands.map((b) => ({ value: b.id, label: b.name }))}
|
||||
/>
|
||||
</AdminInput>
|
||||
</div>
|
||||
{error && (
|
||||
<div className="rounded-xl bg-red-950/50 border border-red-800 p-4 text-sm text-red-300">{error}</div>
|
||||
)}
|
||||
|
||||
{/* Connection status banner */}
|
||||
@@ -484,10 +519,10 @@ export default function ShippingSettingsForm({
|
||||
</div>
|
||||
|
||||
{/* Save/Cancel buttons */}
|
||||
{loading ? (
|
||||
{saving ? (
|
||||
<div className="flex items-center gap-3" style={{ color: "var(--admin-text-muted)" }}>
|
||||
<div className="h-5 w-5 rounded-full border-2 border-current border-t-transparent animate-spin" />
|
||||
Loading settings...
|
||||
Saving settings...
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-4 pt-4 border-t border-[var(--admin-border)]">
|
||||
|
||||
Reference in New Issue
Block a user