diff --git a/src/app/admin/import/ImportCenterClient.tsx b/src/app/admin/import/ImportCenterClient.tsx index e59c5f8..6f48b87 100644 --- a/src/app/admin/import/ImportCenterClient.tsx +++ b/src/app/admin/import/ImportCenterClient.tsx @@ -29,7 +29,10 @@ type Props = { export default function ImportCenterClient({ brandId: initialBrandId, brandName: initialBrandName, brands = [], isPlatformAdmin = false }: Props) { const [activeBrandId, setActiveBrandId] = useState(initialBrandId); - const [activeBrandName, setActiveBrandName] = useState(initialBrandName); + // Brand name is derived from the brands list — no separate state, so + // updates to it don't trigger an extra render of the whole component. + const activeBrandName = + brands.find((b) => b.id === activeBrandId)?.name ?? initialBrandName; const [fileName, setFileName] = useState(""); const [base64Data, setBase64Data] = useState(""); const [analysis, setAnalysis] = useState(null); @@ -45,8 +48,6 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: function handleBrandChange(newBrandId: string) { setActiveBrandId(newBrandId); - const found = brands.find((b) => b.id === newBrandId); - if (found) setActiveBrandName(found.name); } function handleFile(file: File) { @@ -178,9 +179,17 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: {step === "upload" && (
{ e.preventDefault(); }} onDrop={handleDrop} onClick={() => inputRef.current?.click()} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + inputRef.current?.click(); + } + }} className="flex flex-col items-center justify-center gap-3 rounded-2xl border-2 border-dashed border-[var(--admin-border)] bg-white p-12 cursor-pointer hover:border-[var(--admin-accent)] hover:bg-[var(--admin-bg)] transition-colors" > diff --git a/src/app/admin/settings/integrations/IntegrationsClient.tsx b/src/app/admin/settings/integrations/IntegrationsClient.tsx index a2b8f6e..8c050c0 100644 --- a/src/app/admin/settings/integrations/IntegrationsClient.tsx +++ b/src/app/admin/settings/integrations/IntegrationsClient.tsx @@ -473,9 +473,6 @@ export default function IntegrationsClient({ brandId, brands, isPlatformAdmin, p const [showAddModal, setShowAddModal] = useState(false); const [addSearch, setAddSearch] = useState(""); const [customIntegrations, setCustomIntegrations] = useState([]); - const [showCustomForm, setShowCustomForm] = useState(false); - const [newCustomName, setNewCustomName] = useState(""); - const [newCustomType, setNewCustomType] = useState<"ai_provider" | "payment_processor" | "other">("other"); // Integrations shown in the grid — OpenAI card is hidden since it's now handled by AIProviderPanel const gridIntegrations = INTEGRATIONS.filter((i) => i.id !== "openai"); @@ -498,19 +495,17 @@ export default function IntegrationsClient({ brandId, brands, isPlatformAdmin, p setAddSearch(""); } - function handleAddCustom() { - if (!newCustomName.trim()) return; + function handleAddCustom(name: string, type: "ai_provider" | "payment_processor" | "other") { + if (!name.trim()) return; const newInt: CustomIntegration = { id: `custom_${Date.now()}`, - name: newCustomName, - type: newCustomType, + name: name.trim(), + type, enabled: false, credentials: [], syncOptions: [], }; setCustomIntegrations((prev) => [...prev, newInt]); - setNewCustomName(""); - setShowCustomForm(false); } return ( @@ -608,7 +603,16 @@ export default function IntegrationsClient({ brandId, brands, isPlatformAdmin, p {/* Add integration modal */} {showAddModal && ( -
setShowAddModal(false)}> +
setShowAddModal(false)} + onKeyDown={(e) => { + if (e.key === "Escape") setShowAddModal(false); + }} + >
e.stopPropagation()}>

Add Integration

diff --git a/src/app/admin/water-log/headgates/HeadgatesManager.tsx b/src/app/admin/water-log/headgates/HeadgatesManager.tsx index 00978fe..fffba94 100644 --- a/src/app/admin/water-log/headgates/HeadgatesManager.tsx +++ b/src/app/admin/water-log/headgates/HeadgatesManager.tsx @@ -372,7 +372,16 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) { {/* Edit Modal */} {editHg && ( -
setEditHg(null)}> +
setEditHg(null)} + onKeyDown={(e) => { + if (e.key === "Escape") setEditHg(null); + }} + >
e.stopPropagation()}>

Edit Headgate

@@ -512,7 +521,16 @@ function QRModal({ hg, onClose, onRegenerate }: { hg: Headgate; onClose: () => v } return ( -
+
{ + if (e.key === "Escape") onClose(); + }} + >
e.stopPropagation()}> {/* Header */} diff --git a/src/components/admin/design-system/AdminBadge.tsx b/src/components/admin/design-system/AdminBadge.tsx index 22ea2b1..6491fca 100644 --- a/src/components/admin/design-system/AdminBadge.tsx +++ b/src/components/admin/design-system/AdminBadge.tsx @@ -138,56 +138,3 @@ export default function AdminBadge({ ); } - -// Status badge with predefined statuses -type AdminStatusBadgeProps = { - status: "active" | "inactive" | "pending" | "draft" | "completed" | "cancelled"; - className?: string; -}; - -// Reuse the new `tone` prop directly so the status pill text style always -// matches the active AdminBadge palette. -const statusConfig: Record = { - active: { tone: "success", dot: true, label: "Active" }, - inactive: { tone: "neutral", dot: true, label: "Inactive" }, - pending: { tone: "warning", dot: true, label: "Pending" }, - draft: { tone: "neutral", dot: true, label: "Draft" }, - completed: { tone: "success", dot: true, label: "Completed" }, - cancelled: { tone: "danger", dot: true, label: "Cancelled" }, -}; - -export function AdminStatusBadge({ status, className = "" }: AdminStatusBadgeProps) { - const config = statusConfig[status] || statusConfig.inactive; - return ( - - {config.label} - - ); -} - -// Count badge (circular) -type AdminCountBadgeProps = { - count: number; - tone?: BadgeTone; - /** @deprecated Use `tone` instead. */ - variant?: BadgeVariant; - className?: string; -}; - -export function AdminCountBadge({ - count, - tone, - variant = "default", - className = "", -}: AdminCountBadgeProps) { - const resolvedTone: BadgeTone = tone ?? variantToTone[variant]; - const { bg, text } = toneStyles[resolvedTone]; - return ( - - {count} - - ); -} diff --git a/src/components/admin/design-system/index.tsx b/src/components/admin/design-system/index.tsx index 885ecc5..d5654b4 100644 --- a/src/components/admin/design-system/index.tsx +++ b/src/components/admin/design-system/index.tsx @@ -10,7 +10,7 @@ export { default as AdminEmptyState } from "./AdminEmptyState"; export { default as AdminDeleteConfirm, useDeleteConfirm } from "./AdminDeleteConfirm"; export { default as AdminActionMenu, AdminActionButton } from "./AdminActionMenu"; export { default as AdminPagination, AdminSimplePagination } from "./AdminPagination"; -export { default as AdminBadge, AdminStatusBadge, AdminCountBadge } from "./AdminBadge"; +export { default as AdminBadge } from "./AdminBadge"; // Phase 2 pattern primitives (admin-level, not in design-system/ folder) // Re-exported here so callers can `import { KPIStat, EmptyState, LoadingState } from "@/components/admin/design-system"`.