import { Anchor, Group, Stack, Text } from "@mantine/core"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Fragment } from "react"; import { Paperclip } from "lucide-react"; import { SmartFileInput } from "@edr/ui-common"; import type { ETradeBusinessOption, IFileUploadSetting } from "@edr/types"; import EtradeBusinessSelect from "@/components/onboarding/EtradeBusinessSelect"; import { api } from "@/services/api"; import { fetchViewableFile } from "@/services/files.service"; import type { LicenseFile } from "@/services/companies.service"; const ROLE_LABELS: Record = { importer: "Importer", exporter: "Exporter", freight_forwarder: "Freight Forwarder", dj_freight_forwarder: "DJ Freight Forwarder", transporter: "Transporter", }; /** Field key the synthesized per-profile upload setting is keyed on. */ const LICENSE_FILE_KEY = "business_license"; /** * Build a single-field upload setting so each profile's license input can reuse * the shared SmartFileInput (same dropzone + "uploaded" state as the documents * step), instead of a bespoke file picker. */ function buildLicenseSetting( profileId: string, profileName: string, ): IFileUploadSetting { return { id: `license-setting-${profileId}`, createdAt: "", updatedAt: "", deletedAt: null, code: "business_license", label: "Business license", description: null, entity: "customer", fields: [ { id: `${LICENSE_FILE_KEY}-${profileId}`, createdAt: "", updatedAt: "", deletedAt: null, settingId: `license-setting-${profileId}`, fileKey: LICENSE_FILE_KEY, fileLabel: `Upload ${profileName} Business license file(s)`, helpText: null, isRequired: true, isMultiple: true, maxFiles: 10, allowedExtensions: ["pdf", "png", "jpg", "jpeg"], maxSizeMb: 25, order: 1, }, ], }; } export interface RoleLicenseProfile { id: string; type: string; reference: string; /** License files already uploaded for this profile (rehydration). */ existingFiles: LicenseFile[]; /** The eTrade business already attached to this profile, if any. */ etradeBusiness?: ETradeBusinessOption | null; } interface RoleLicenseStepProps { /** One card per operational role/profile. */ profiles: RoleLicenseProfile[]; /** Newly-selected files per profile id (not yet uploaded). */ value: Record; onChange: (value: Record) => void; /** "Business license is required" style error, keyed by profile id. */ errors?: Record; /** "Choose a business" error, keyed by profile id. */ businessErrors?: Record; } /** * Final onboarding step: collect a business license (one or more files) for * each operational role the company holds. Each role gets its own SmartFileInput * dropzone; already-uploaded files are listed (with download links) for context * and surface the input's "uploaded" state. */ export default function RoleLicenseStep({ profiles, value, onChange, errors, businessErrors, }: RoleLicenseStepProps) { const queryClient = useQueryClient(); const setFiles = (profileId: string, files: File[]) => { onChange({ ...value, [profileId]: files }); }; // Attaching saves immediately rather than riding along with the step's // submit: the roles were created on the wizard's first step, so each already // has a row to attach to, and persisting on pick means a refresh or a resumed // draft keeps the choice. const attach = useMutation({ mutationFn: (vars: { profileId: string; licenceNumber: string }) => api.companies.attachEtradeBusiness.call(vars), onSuccess: () => { // getInfo FIRST: the wizard reads its role list (and each role's attached // business) from that query, so skipping it leaves the dropdown showing // blank right after a successful pick. queryClient.invalidateQueries({ queryKey: api.companies.getInfo.queryKey(), }); queryClient.invalidateQueries({ queryKey: api.companies.getProfile.queryKey(), }); queryClient.invalidateQueries({ queryKey: api.companies.onboardingRequirements.queryKey(), }); }, }); return ( For each operational profile, say which of your eTrade business licences it operates as, and upload that licence. You can attach more than one document per profile, and the same business can back more than one role. {profiles.map((profile) => { const label = ROLE_LABELS[profile.type] ?? profile.type; const selected = value[profile.id] ?? []; const hasExisting = profile.existingFiles.length > 0; return ( attach.mutate({ profileId: profile.id, licenceNumber }) } /> {hasExisting && ( {profile.existingFiles.map((f) => ( void fetchViewableFile(f.id, f.name).then((v) => window.open(v.url, "_blank"), ) } size="xs" > {f.name} ))} )} { const next = v[LICENSE_FILE_KEY]; const files = Array.isArray(next) ? next : next ? [next] : []; setFiles(profile.id, files); }} /> ); })} ); }