diff --git a/apps/edr-freight-api/src/modules/companies/companies.service.ts b/apps/edr-freight-api/src/modules/companies/companies.service.ts index 02f77b2e0..62be578bb 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.service.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.service.ts @@ -1183,9 +1183,11 @@ export class CompaniesService { const { businessInfo } = await this.etradeService.resolveCompanyData(tin); if (!businessInfo) { throw new BadRequestException( - "No business license found for this TIN. Please check the number and try again.", + "We couldn't find a business license for this TIN with eTrade. Please double-check the number and try again.", ); } - return this.etradeService.extractRegistrationData(businessInfo); + const registrationData = this.etradeService.extractRegistrationData(businessInfo); + const tinTaken = await this.companiesRepo.existsByTin(tin); + return { ...registrationData, tinTaken }; } } diff --git a/apps/edr-freight-api/src/modules/companies/dto/create-company.dto.ts b/apps/edr-freight-api/src/modules/companies/dto/create-company.dto.ts index e5b686d11..a56ea5ad8 100644 --- a/apps/edr-freight-api/src/modules/companies/dto/create-company.dto.ts +++ b/apps/edr-freight-api/src/modules/companies/dto/create-company.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsNotEmpty, IsOptional, IsEnum, MaxLength, Length, Matches, IsEmail } from 'class-validator'; +import { IsString, IsNotEmpty, IsOptional, IsEnum, MaxLength, Length, IsEmail } from 'class-validator'; import { CompanyType, CompanyStatus } from '../entities/company.entity'; import { IsValidPhone } from '../../../common/validators/is-phone-number.validator'; @@ -17,10 +17,7 @@ export class CreateCompanyDto { @IsString() @IsNotEmpty() - @Length(10, 10) - @Matches(/^00\d{8}$/, { - message: 'TIN must be 10 digits starting with 00', - }) + @Length(10, 10, { message: 'TIN must be exactly 10 digits' }) tin!: string; @IsOptional() diff --git a/apps/edr-freight-api/src/modules/companies/dto/etrade-response.dto.ts b/apps/edr-freight-api/src/modules/companies/dto/etrade-response.dto.ts index 200b69fee..ef7eb2a21 100644 --- a/apps/edr-freight-api/src/modules/companies/dto/etrade-response.dto.ts +++ b/apps/edr-freight-api/src/modules/companies/dto/etrade-response.dto.ts @@ -17,6 +17,7 @@ export class ETradeResponseDto implements CompanyRegistrationData { managerName!: string; managerEmail?: string; managerPhone!: string; + tinTaken?: boolean; constructor(data: CompanyRegistrationData) { this.licenceNumber = data.licenceNumber; @@ -35,5 +36,6 @@ export class ETradeResponseDto implements CompanyRegistrationData { this.managerName = data.managerName; this.managerEmail = data.managerEmail; this.managerPhone = data.managerPhone; + this.tinTaken = data.tinTaken; } } diff --git a/apps/edr-freight-api/src/modules/companies/dto/update-profile.dto.ts b/apps/edr-freight-api/src/modules/companies/dto/update-profile.dto.ts index 316038dc9..9fd8f28ae 100644 --- a/apps/edr-freight-api/src/modules/companies/dto/update-profile.dto.ts +++ b/apps/edr-freight-api/src/modules/companies/dto/update-profile.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsOptional, IsEmail, MaxLength, Length, Matches, IsEnum } from 'class-validator'; +import { IsString, IsOptional, IsEmail, MaxLength, Length, IsEnum } from 'class-validator'; import { CompanyNationality } from '../entities/company.entity'; import { IsValidPhone } from '../../../common/validators/is-phone-number.validator'; @@ -34,10 +34,7 @@ export class UpdateProfileDto { @IsOptional() @IsString() - @Length(10, 10) - @Matches(/^00\d{8}$/, { - message: 'TIN must be 10 digits starting with 00', - }) + @Length(10, 10, { message: 'TIN must be exactly 10 digits' }) tin?: string; @IsOptional() 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 98efc2613..6c38bba46 100644 --- a/apps/edr-freight-web/portal/src/components/onboarding/ETradeInfo.tsx +++ b/apps/edr-freight-web/portal/src/components/onboarding/ETradeInfo.tsx @@ -7,9 +7,11 @@ import { Text, TextInput, } from "@mantine/core"; +import { useEffect, useRef } from "react"; import type { UseFormRegisterReturn } from "react-hook-form"; -import { AlertCircle, CheckCircle2, Download } from "lucide-react"; +import { AlertCircle, CheckCircle2, Download, Info } from "lucide-react"; import { useETradeData } from "@/hooks/useETradeData"; +import { extractApiError } from "@/utils/result"; import type { CompanyRegistrationData } from "@edr/types"; interface ETradeInfoProps { @@ -22,6 +24,8 @@ interface ETradeInfoProps { onDataLoaded: (data: CompanyRegistrationData) => void; } +const isValidTin = (tin: string) => tin.length === 10; + export default function ETradeInfo({ tin, register, @@ -30,53 +34,100 @@ export default function ETradeInfo({ }: ETradeInfoProps) { const mutation = useETradeData(); const isLoading = mutation.isPending; - const hasData = mutation.data; + const tinTaken = mutation.data?.tinTaken; + const hasData = + mutation.data && !mutation.data.tinTaken ? mutation.data : null; const handleFetch = async () => { - if (!tin || tin.length !== 10 || !tin.startsWith("00")) return; + if (!isValidTin(tin)) return; const result = await mutation.mutateAsync(tin); - if (result) { + if (result && !result.tinTaken) { onDataLoaded(result); } }; - const errorMessage = + // 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); + useEffect(() => { + if (isValidTin(tin) && lastFetchedTin.current !== tin) { + lastFetchedTin.current = tin; + handleFetch(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tin]); + + const apiError = mutation.isError && mutation.error - ? (mutation.error as any).message || - "Failed to fetch company information. Please try again." + ? 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. + 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." : null; return ( TIN Number (10 digits) *} + label={ + <> + TIN Number (10 digits){" "} + * + + } placeholder="0012345678" maxLength={10} error={error} {...register} /> - + {errorMessage && ( + + )} + {notFound && ( + } color="gray"> + We couldn't find a matching business record for this TIN โ€” no + problem, just fill in the details below. + + )} + {errorMessage && ( } color="red" - title="Failed to fetch data" + title="Couldn't fetch eTrade data" > - {errorMessage} You can still fill in the details manually below. + {errorMessage} + + )} + + {tinTaken && ( + } + color="red" + title="TIN already registered" + > + This TIN is already registered to another company account. Please + double-check the number, or contact support if you believe this is a + mistake. )} diff --git a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx index 85af9bfdd..3cce6d4f8 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx @@ -450,8 +450,6 @@ export default function CompanyProfileForm({ onDataLoaded={handleETradeDataLoaded} /> - - - +