From 9bdde05d9dffad58b24680703cd01d42b43eda61 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 17 Jul 2026 15:19:57 -0400 Subject: [PATCH] Toast refactor, hts tweaks --- inventory/package-lock.json | 35 ---- inventory/package.json | 1 - .../components/dashboard/PinProtection.jsx | 21 +- .../src/components/product-import/config.ts | 4 +- .../SelectHeaderStep/SelectHeaderStep.tsx | 13 +- .../product-import/steps/UploadFlow.tsx | 11 +- .../steps/UploadStep/UploadStep.tsx | 148 +++++-------- .../steps/UploadStep/components/DropZone.tsx | 7 +- .../src/components/settings/UserForm.tsx | 11 +- .../components/settings/UserManagement.tsx | 12 +- .../components/settings/VendorSettings.tsx | 37 +--- inventory/src/components/ui/toast.tsx | 127 ------------ inventory/src/components/ui/toaster.tsx | 33 --- inventory/src/hooks/use-toast.ts | 194 ------------------ inventory/src/pages/DiscountSimulator.tsx | 7 +- inventory/src/pages/HtsLookup.tsx | 25 +-- inventory/src/pages/Products.tsx | 11 +- inventory/src/pages/SpecLookup.tsx | 15 +- inventory/tsconfig.tsbuildinfo | 2 +- 19 files changed, 118 insertions(+), 596 deletions(-) delete mode 100644 inventory/src/components/ui/toast.tsx delete mode 100644 inventory/src/components/ui/toaster.tsx delete mode 100644 inventory/src/hooks/use-toast.ts diff --git a/inventory/package-lock.json b/inventory/package-lock.json index d574bf0..6d38adb 100644 --- a/inventory/package-lock.json +++ b/inventory/package-lock.json @@ -31,7 +31,6 @@ "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-tabs": "^1.1.2", - "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.1.6", @@ -2434,40 +2433,6 @@ } } }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz", - "integrity": "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-collection": "1.1.2", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.5", - "@radix-ui/react-portal": "1.1.4", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-toggle": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", diff --git a/inventory/package.json b/inventory/package.json index 74de27b..f5238a6 100644 --- a/inventory/package.json +++ b/inventory/package.json @@ -35,7 +35,6 @@ "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-tabs": "^1.1.2", - "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.1.6", diff --git a/inventory/src/components/dashboard/PinProtection.jsx b/inventory/src/components/dashboard/PinProtection.jsx index 368772b..8e6c25f 100644 --- a/inventory/src/components/dashboard/PinProtection.jsx +++ b/inventory/src/components/dashboard/PinProtection.jsx @@ -7,7 +7,7 @@ import { import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Lock, Delete } from "lucide-react"; -import { useToast } from "@/hooks/use-toast"; +import { toast } from "sonner"; const MAX_ATTEMPTS = 3; const LOCKOUT_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds @@ -27,8 +27,6 @@ const PinProtection = ({ onSuccess }) => { } return 0; }); - const { toast } = useToast(); - useEffect(() => { let timer; if (lockoutTime > 0) { @@ -59,34 +57,25 @@ const PinProtection = ({ onSuccess }) => { if (newAttempts >= MAX_ATTEMPTS) { setLockoutTime(LOCKOUT_DURATION); - toast({ - title: "Too many attempts", + toast.error("Too many attempts", { description: `Please try again in ${Math.ceil(LOCKOUT_DURATION / 60000)} minutes`, - variant: "destructive", }); setPin(""); return; } if (value === "892312") { - toast({ - title: "Success", - description: "PIN accepted", - }); + toast.success("PIN accepted"); // Reset attempts on success setAttempts(0); localStorage.removeItem('pinAttempts'); localStorage.removeItem('lastAttemptTime'); onSuccess(); } else { - toast({ - title: "Error", - description: `Incorrect PIN. ${MAX_ATTEMPTS - newAttempts} attempts remaining`, - variant: "destructive", - }); + toast.error(`Incorrect PIN. ${MAX_ATTEMPTS - newAttempts} attempts remaining`); setPin(""); } - }, [attempts, lockoutTime, onSuccess, toast]); + }, [attempts, lockoutTime, onSuccess]); const handleKeyPress = (value) => { if (pin.length < 6) { diff --git a/inventory/src/components/product-import/config.ts b/inventory/src/components/product-import/config.ts index b637ed4..d51804d 100644 --- a/inventory/src/components/product-import/config.ts +++ b/inventory/src/components/product-import/config.ts @@ -229,8 +229,8 @@ export const BASE_IMPORT_FIELDS = [ { label: "Weight", key: "weight", - description: "Product weight (in lbs)", - alternateMatches: ["weight (lbs.)"], + description: "Product weight (in oz — convert lbs ×16, kg ×35.274)", + alternateMatches: ["weight (lbs.)", "weight (oz)", "weight (oz.)"], fieldType: { type: "input" }, width: 100, validations: [ diff --git a/inventory/src/components/product-import/steps/SelectHeaderStep/SelectHeaderStep.tsx b/inventory/src/components/product-import/steps/SelectHeaderStep/SelectHeaderStep.tsx index 79f6bbd..a857410 100644 --- a/inventory/src/components/product-import/steps/SelectHeaderStep/SelectHeaderStep.tsx +++ b/inventory/src/components/product-import/steps/SelectHeaderStep/SelectHeaderStep.tsx @@ -3,7 +3,7 @@ import { SelectHeaderTable } from "./components/SelectHeaderTable" import { useRsi } from "../../hooks/useRsi" import type { RawData } from "../../types" import { Button } from "@/components/ui/button" -import { useToast } from "@/hooks/use-toast" +import { toast } from "sonner" type SelectHeaderProps = { data: RawData[] @@ -24,7 +24,6 @@ const isRowCompletelyEmpty = (row: RawData): boolean => { export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps) => { const { translations } = useRsi() - const { toast } = useToast() const [selectedRows, setSelectedRows] = useState>(new Set([0])) const [isLoading, setIsLoading] = useState(false) @@ -133,19 +132,15 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps setLocalData(filteredRows); setSelectedRows(new Set([newSelectedIndex])); - toast({ - title: "Rows removed", + toast.success("Rows removed", { description: `Removed ${localData.length - filteredRows.length} empty, single-value, or duplicate rows`, - variant: "default" }); } else { - toast({ - title: "No rows removed", + toast.info("No rows removed", { description: "No empty, single-value, or duplicate rows were found", - variant: "default" }); } - }, [localData, selectedRows, toast]); + }, [localData, selectedRows]); return (
diff --git a/inventory/src/components/product-import/steps/UploadFlow.tsx b/inventory/src/components/product-import/steps/UploadFlow.tsx index 1cbf47b..9370a95 100644 --- a/inventory/src/components/product-import/steps/UploadFlow.tsx +++ b/inventory/src/components/product-import/steps/UploadFlow.tsx @@ -13,7 +13,7 @@ import { exceedsMaxRecords } from "../utils/exceedsMaxRecords" import { useRsi } from "../hooks/useRsi" import type { RawData, Data } from "../types" import { Progress } from "@/components/ui/progress" -import { useToast } from "@/hooks/use-toast" +import { toast } from "sonner" import { addErrorsAndRunHooks } from "./ValidationStep/utils/dataMutations" import { computeMappingSignature, type MappingSignature } from "./ValidationStep/utils/mappingSignature" import { useValidationStore } from "./ValidationStep/store/validationStore" @@ -88,7 +88,6 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => { tableHook, onSubmit } = useRsi() const [uploadedFile, setUploadedFile] = useState(null) - const { toast } = useToast() const queryClient = useQueryClient() const resetValidationStore = useValidationStore((state) => state.reset) @@ -116,13 +115,9 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => { }, [queryClient, resetValidationStore]); const errorToast = useCallback( (description: string) => { - toast({ - variant: "destructive", - title: translations.alerts.toast.error, - description, - }) + toast.error(translations.alerts.toast.error, { description }) }, - [toast, translations], + [translations], ) // Keep track of global selections across steps diff --git a/inventory/src/components/product-import/steps/UploadStep/UploadStep.tsx b/inventory/src/components/product-import/steps/UploadStep/UploadStep.tsx index 208ad3a..db81956 100644 --- a/inventory/src/components/product-import/steps/UploadStep/UploadStep.tsx +++ b/inventory/src/components/product-import/steps/UploadStep/UploadStep.tsx @@ -6,14 +6,6 @@ import { StepType } from "../UploadFlow" import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { AuthContext } from "@/contexts/AuthContext" -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog" import { Textarea } from "@/components/ui/textarea" import { Label } from "@/components/ui/label" import { Bug } from "lucide-react" @@ -27,17 +19,23 @@ type UploadProps = { onRestoreSession?: (session: ImportSession) => void } +const OrSeparator = () => ( +
+ + OR + +
+) + export const UploadStep = ({ onContinue, setInitialState, onRestoreSession }: UploadProps) => { const [isLoading, setIsLoading] = useState(false) const { translations } = useRsi() const { user } = useContext(AuthContext) const hasDebugPermission = Boolean(user?.is_admin || user?.permissions?.includes("admin:debug")) - // Debug import state - const [debugDialogOpen, setDebugDialogOpen] = useState(false) - const [debugJsonInput, setDebugJsonInput] = useState("") - const [debugError, setDebugError] = useState(null) - + const [jsonInput, setJsonInput] = useState("") + const [jsonError, setJsonError] = useState(null) + const handleOnContinue = useCallback( async (data: XLSX.WorkBook, file: File) => { setIsLoading(true) @@ -46,23 +44,23 @@ export const UploadStep = ({ onContinue, setInitialState, onRestoreSession }: Up }, [onContinue], ) - + const handleStartFromScratch = useCallback(() => { if (setInitialState) { setInitialState({ type: StepType.validateData, data: [{}], isFromScratch: true }) } }, [setInitialState]) - const handleDebugImport = useCallback(() => { - setDebugError(null) + const handleJsonImport = useCallback(() => { + setJsonError(null) try { - const parsed = JSON.parse(debugJsonInput) + const parsed = JSON.parse(jsonInput) // Handle both array and object with products property - let products: any[] = Array.isArray(parsed) ? parsed : parsed.products + const products: any[] = Array.isArray(parsed) ? parsed : parsed.products if (!Array.isArray(products) || products.length === 0) { - setDebugError("JSON must be an array of products or an object with a 'products' array") + setJsonError("JSON must be an array of products or an object with a 'products' array") return } @@ -79,47 +77,59 @@ export const UploadStep = ({ onContinue, setInitialState, onRestoreSession }: Up isFromScratch: true }) } - - setDebugDialogOpen(false) - setDebugJsonInput("") } catch (e) { - setDebugError(`Invalid JSON: ${e instanceof Error ? e.message : "Parse error"}`) + setJsonError(`Invalid JSON: ${e instanceof Error ? e.message : "Parse error"}`) } - }, [debugJsonInput, setInitialState]) - + }, [jsonInput, setInitialState]) + return (
-

{translations.uploadStep.title}

- {hasDebugPermission && ( +
+ {hasDebugPermission && ( <> - - -
- + +

+ Paste product data in the same JSON format as the API submission. The data will be loaded into the validation step. +

+