From 0165a64e76a236e3e112d02dca515305c4161af6 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Wed, 29 Jul 2026 12:03:45 +0000 Subject: [PATCH] feat(freight-portal): gate company step on etrade verification --- .../src/components/onboarding/ETradeInfo.tsx | 94 ++-- .../onboarding/OnboardingWizardDialog.tsx | 3 +- .../src/pages/accounts/CompanyProfileForm.tsx | 432 ++++++++---------- .../companyProfileForm/ETradeCompanyCard.tsx | 151 ++++++ .../companyProfileForm/StepSection.tsx | 83 ++++ .../accounts/companyProfileForm/helpers.ts | 3 - .../accounts/companyProfileForm/schema.ts | 2 - 7 files changed, 478 insertions(+), 290 deletions(-) create mode 100644 apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/ETradeCompanyCard.tsx create mode 100644 apps/edr-freight-web/portal/src/pages/accounts/companyProfileForm/StepSection.tsx diff --git a/apps/edr-freight-web/portal/src/components/onboarding/ETradeInfo.tsx b/apps/edr-freight-web/portal/src/components/onboarding/ETradeInfo.tsx index 6c38bba46..d36845dd9 100644 --- a/apps/edr-freight-web/portal/src/components/onboarding/ETradeInfo.tsx +++ b/apps/edr-freight-web/portal/src/components/onboarding/ETradeInfo.tsx @@ -1,19 +1,19 @@ -import { - Alert, - Button, - Group, - Loader, - Stack, - Text, - TextInput, -} from "@mantine/core"; +import { Alert, Button, Group, Loader, Stack, TextInput } from "@mantine/core"; import { useEffect, useRef } from "react"; import type { UseFormRegisterReturn } from "react-hook-form"; -import { AlertCircle, CheckCircle2, Download, Info } from "lucide-react"; +import { AlertCircle, Download } from "lucide-react"; import { useETradeData } from "@/hooks/useETradeData"; import { extractApiError } from "@/utils/result"; import type { CompanyRegistrationData } from "@edr/types"; +export type ETradeStatus = + | "idle" + | "loading" + | "verified" + | "not-found" + | "taken" + | "error"; + interface ETradeInfoProps { /** Current TIN value (drives button enablement). */ tin: string; @@ -22,6 +22,8 @@ interface ETradeInfoProps { /** Validation error for the TIN field, if any. */ error?: string; onDataLoaded: (data: CompanyRegistrationData) => void; + /** Reports the live lookup status so the parent step can gate on it. */ + onStatusChange?: (status: ETradeStatus) => void; } const isValidTin = (tin: string) => tin.length === 10; @@ -31,12 +33,11 @@ export default function ETradeInfo({ register, error, onDataLoaded, + onStatusChange, }: ETradeInfoProps) { const mutation = useETradeData(); const isLoading = mutation.isPending; const tinTaken = mutation.data?.tinTaken; - const hasData = - mutation.data && !mutation.data.tinTaken ? mutation.data : null; const handleFetch = async () => { if (!isValidTin(tin)) return; @@ -47,8 +48,10 @@ export default function ETradeInfo({ }; // Auto-fetch as soon as the TIN reaches its full 10-digit length — only - // once per distinct value, so retyping the same TIN doesn't refetch. - const lastFetchedTin = useRef(null); + // once per distinct value, so retyping the same TIN doesn't refetch. Seeded + // from the initial value so a resumed draft with an already-verified TIN + // doesn't refire the lookup the moment this mounts. + const lastFetchedTin = useRef(tin || null); useEffect(() => { if (isValidTin(tin) && lastFetchedTin.current !== tin) { lastFetchedTin.current = tin; @@ -61,16 +64,36 @@ export default function ETradeInfo({ mutation.isError && mutation.error ? extractApiError(mutation.error) : null; - // A 400 here means eTrade simply has no record for this TIN — not a - // failure. Soft-pedal it as an FYI, not a red error, so filling in - // manually doesn't feel like something went wrong. + // A 400 here means eTrade simply has no record for this TIN. const notFound = apiError?.statusCode === 400; const errorMessage = apiError && !notFound ? apiError.message || - "We couldn't reach eTrade to fetch your company information. Please try again, or fill in the details manually below." + "We couldn't reach eTrade to fetch your company information. Please try again." : null; + const status: ETradeStatus = isLoading + ? "loading" + : tinTaken + ? "taken" + : mutation.isSuccess && mutation.data && !mutation.data.tinTaken + ? "verified" + : notFound + ? "not-found" + : errorMessage + ? "error" + : "idle"; + + const lastReportedStatus = useRef(null); + useEffect(() => { + if (lastReportedStatus.current === status) return; + lastReportedStatus.current = status; + onStatusChange?.(status); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [status]); + + const showRetry = isValidTin(tin) && status !== "verified" && status !== "loading"; + return ( @@ -86,7 +109,7 @@ export default function ETradeInfo({ error={error} {...register} /> - {errorMessage && ( + {showRetry && (