From 8ddd166dca1b89a39eb47606f99b0d47b08f7e2c Mon Sep 17 00:00:00 2001 From: ghost2023 Date: Thu, 4 Jun 2026 14:30:49 +0300 Subject: [PATCH] feat: setup the ui for document uploading in accounts onboarding --- .../src/pages/accounts/CompanyProfileForm.tsx | 230 +++++--- .../src/pages/accounts/DjiboutiAgentForm.tsx | 207 ++++++-- .../ForwarderForm.tsx} | 412 ++++++++++----- .../src/pages/accounts/OnboardingPage.tsx | 30 +- .../src/pages/accounts/TransporterForm.tsx | 493 ++++++++++++------ 5 files changed, 981 insertions(+), 391 deletions(-) rename apps/edr-freight-web/portal/src/pages/{customers/on_boarding/CustomerOnboardingPage.tsx => accounts/ForwarderForm.tsx} (58%) 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 eb8d5a343..3f22d6179 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx @@ -14,10 +14,8 @@ import { ChevronLeft, UploadCloud, } from "lucide-react"; -import type { OnboardingUserType } from "./types"; import type { AuthUser } from "@/types/auth"; import type { CreateCompanyPayload } from "@/services/companies.service"; -import type { FileUploadSetting } from "@/types/fileUploadSettings"; import PhoneInput from "@/components/auth/PhoneInput"; import { Button, @@ -30,7 +28,7 @@ import { } from "@edr/ui-common"; import { api } from "@/services/api"; -type CompanyStep = "company" | "personnel" | "poa" | "documents"; +type CompanyStep = "company" | "personnel" | "poa" | "documents" | "confirm"; const onboardingSchema = z.object({ companyName: z.string().min(1, "Company name is required"), @@ -85,6 +83,7 @@ const stepFields: Record = { ], poa: [], documents: [], + confirm: [], }; function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { @@ -116,13 +115,13 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { } export default function CompanyProfileForm({ - userType, + documentSettingCode, user, onSubmit, isPending, onBack, }: { - userType: OnboardingUserType; + documentSettingCode: string; user: AuthUser; onSubmit: (data: CreateCompanyPayload) => void; isPending: boolean; @@ -133,9 +132,9 @@ export default function CompanyProfileForm({ Record >({}); - const { data: uploadSettings = [], isLoading: loadingDocuments } = useQuery( - api.fileUploadSettings.getByEntity.queryOptions({ - input: { entity: "customer" }, + const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( + api.fileUploadSettings.getByCode.queryOptions({ + input: { code: documentSettingCode }, refetchOnMount: false, }), ); @@ -144,6 +143,7 @@ export default function CompanyProfileForm({ register, handleSubmit, trigger, + watch, formState: { errors }, } = useForm({ resolver: zodResolver(onboardingSchema), @@ -173,18 +173,20 @@ export default function CompanyProfileForm({ }, }); - const hasDocuments = uploadSettings.length > 0; + const formValues = watch(); + const hasDocuments = Boolean(uploadSetting?.fields?.length); + const totalSteps = 5; const nextStep = async () => { if (step === "poa") { - if (hasDocuments) { - setStep("documents"); - } else { - handleSubmit((data) => onSubmit(buildPayload(data, user)))(); - } + setStep("documents"); return; } if (step === "documents") { + setStep("confirm"); + return; + } + if (step === "confirm") { handleSubmit((data) => onSubmit(buildPayload(data, user)))(); return; } @@ -201,8 +203,10 @@ export default function CompanyProfileForm({ setStep("company"); } else if (step === "poa") { setStep("personnel"); - } else { + } else if (step === "documents") { setStep("poa"); + } else { + setStep("documents"); } }; @@ -228,31 +232,41 @@ export default function CompanyProfileForm({ } active={step === "personnel"} - completed={step === "poa"} + completed={ + step === "poa" || step === "documents" || step === "confirm" + } /> } active={step === "poa"} - completed={hasDocuments ? step === "documents" : step === "personnel"} + completed={step === "documents" || step === "confirm"} + /> + } + active={step === "documents"} + completed={step === "confirm"} + /> + } + active={step === "confirm"} + completed={false} /> - {hasDocuments && ( - } - active={step === "documents"} - completed={false} - /> - )}

- {step === "company" && `Step 1 of ${hasDocuments ? 4 : 3} — Company Information`} - {step === "personnel" && `Step 2 of ${hasDocuments ? 4 : 3} — Personnel Details`} - {step === "poa" && `Step 3 of ${hasDocuments ? 4 : 3} — Power of Attorney (Optional)`} - {step === "documents" && "Step 4 of 4 — Upload Documents (Optional)"} + {step === "company" && + `Step 1 of ${totalSteps} — Company Information`} + {step === "personnel" && + `Step 2 of ${totalSteps} — Personnel Details`} + {step === "poa" && + `Step 3 of ${totalSteps} — Power of Attorney (Optional)`} + {step === "documents" && + `Step 4 of ${totalSteps} — Upload Documents (Optional)`} + {step === "confirm" && `Step 5 of ${totalSteps} — Review & Confirm`}

onSubmit(buildPayload(data, user)))} + onSubmit={(e) => e.preventDefault()} className="flex flex-col gap-4" > @@ -353,11 +367,6 @@ export default function CompanyProfileForm({ {step === "personnel" && ( <> -

- Personal details are pulled from your account. Contact and - management info is collected below. -

-

Contact Person @@ -500,62 +509,155 @@ export default function CompanyProfileForm({ {step === "documents" && ( <> -

- Upload required documents for your registration. You can skip - this step and upload later from your account settings. -

- {loadingDocuments ? (
- ) : uploadSettings.length === 0 ? ( + ) : !uploadSetting ? (

No document requirements found for your account type.

) : (
- {uploadSettings.map((setting) => ( - - ))} +
)} )} + + {step === "confirm" && ( +
+
+

+ Review your registration +

+

+ Confirm the company details below before saving. +

+
+ +
+ + + + + + + + + + + + + + + + + +
+
+ )}
- +
+ +
); } +function ReviewRow({ label, value }: { label: string; value?: string | null }) { + return ( +
+
+ {label} +
+
+ {value?.trim() ? value : "Not provided"} +
+
+ ); +} + function StepIcon({ icon, active, diff --git a/apps/edr-freight-web/portal/src/pages/accounts/DjiboutiAgentForm.tsx b/apps/edr-freight-web/portal/src/pages/accounts/DjiboutiAgentForm.tsx index 9422dd3e2..e0d002858 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/DjiboutiAgentForm.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/DjiboutiAgentForm.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; +import { useQuery } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { @@ -10,6 +11,7 @@ import { CheckCircle2, Loader2, ChevronLeft, + UploadCloud, } from "lucide-react"; import type { AuthUser } from "@/types/auth"; import type { CreateCompanyPayload } from "@/services/companies.service"; @@ -21,9 +23,11 @@ import { FieldLabel, FieldError, FieldGroup, + SmartFileInput, } from "@edr/ui-common"; +import { api } from "@/services/api"; -type DjiboutiStep = "company" | "representative"; +type DjiboutiStep = "company" | "representative" | "documents" | "confirm"; const djiboutiSchema = z.object({ companyName: z.string().min(1, "Company name is required"), @@ -40,9 +44,23 @@ const djiboutiSchema = z.object({ type FormData = z.infer; -const stepLabels: Record = { - company: "Step 1 of 2 — Company Information", - representative: "Step 2 of 2 — Representative Details", +const stepFields: Record = { + company: [ + "companyName", + "companyEmail", + "companyPhone", + "companyPhoneCountryCode", + "companyLocation", + "companyAddress", + ], + representative: [ + "repName", + "repEmail", + "repPhone", + "repPhoneCountryCode", + ], + documents: [], + confirm: [], }; function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { @@ -64,22 +82,35 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { } export default function DjiboutiAgentForm({ + documentSettingCode, user, onSubmit, isPending, onBack, }: { + documentSettingCode: string; user: AuthUser; onSubmit: (data: CreateCompanyPayload) => void; isPending: boolean; onBack: () => void; }) { const [step, setStep] = useState("company"); + const [documentFiles, setDocumentFiles] = useState< + Record + >({}); + + const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( + api.fileUploadSettings.getByCode.queryOptions({ + input: { code: documentSettingCode }, + refetchOnMount: false, + }), + ); const { register, handleSubmit, trigger, + watch, formState: { errors }, } = useForm({ resolver: zodResolver(djiboutiSchema), @@ -97,32 +128,42 @@ export default function DjiboutiAgentForm({ }, }); + const formValues = watch(); + const hasDocuments = Boolean(uploadSetting?.fields?.length); + const totalSteps = 4; + const nextStep = async () => { if (step === "representative") { + setStep("documents"); + return; + } + if (step === "documents") { + setStep("confirm"); + return; + } + if (step === "confirm") { handleSubmit((data) => onSubmit(buildPayload(data, user)))(); return; } - const fields: (keyof FormData)[] = - step === "company" - ? [ - "companyName", - "companyEmail", - "companyPhone", - "companyPhoneCountryCode", - "companyLocation", - "companyAddress", - ] - : ["repName", "repEmail", "repPhone", "repPhoneCountryCode"]; + const fields = stepFields[step]; const isValid = await trigger(fields); if (!isValid) return; setStep("representative"); }; + const skipDocuments = () => { + setStep("confirm"); + }; + const prevStep = () => { if (step === "company") { onBack(); - } else { + } else if (step === "representative") { setStep("company"); + } else if (step === "documents") { + setStep("representative"); + } else { + setStep("documents"); } }; @@ -143,21 +184,34 @@ export default function DjiboutiAgentForm({ } active={step === "company"} - completed={step === "representative"} + completed={step !== "company"} /> } active={step === "representative"} + completed={step === "documents" || step === "confirm"} + /> + } + active={step === "documents"} + completed={step === "confirm"} + /> + } + active={step === "confirm"} completed={false} />

- {stepLabels[step]} + {step === "company" && `Step 1 of ${totalSteps} — Company Information`} + {step === "representative" && `Step 2 of ${totalSteps} — Representative Details`} + {step === "documents" && `Step 3 of ${totalSteps} — Upload Documents (Optional)`} + {step === "confirm" && `Step 4 of ${totalSteps} — Review & Confirm`}

onSubmit(buildPayload(data, user)))} + onSubmit={(e) => e.preventDefault()} className="flex flex-col gap-4" > @@ -262,35 +316,120 @@ export default function DjiboutiAgentForm({ )} + + {step === "documents" && ( + <> + {loadingDocuments ? ( +
+ +
+ ) : !uploadSetting ? ( +

+ No document requirements found for your account type. +

+ ) : ( +
+ +
+ )} + + )} + + {step === "confirm" && ( +
+
+

+ Review your registration +

+

+ Confirm the company details below before saving. +

+
+ +
+ + + + + + + + +
+
+ )}
- )} - + + +
); } +function ReviewRow({ label, value }: { label: string; value?: string | null }) { + return ( +
+
+ {label} +
+
+ {value?.trim() ? value : "Not provided"} +
+
+ ); +} + function StepIcon({ icon, active, diff --git a/apps/edr-freight-web/portal/src/pages/customers/on_boarding/CustomerOnboardingPage.tsx b/apps/edr-freight-web/portal/src/pages/accounts/ForwarderForm.tsx similarity index 58% rename from apps/edr-freight-web/portal/src/pages/customers/on_boarding/CustomerOnboardingPage.tsx rename to apps/edr-freight-web/portal/src/pages/accounts/ForwarderForm.tsx index 994dd751e..b933c4707 100644 --- a/apps/edr-freight-web/portal/src/pages/customers/on_boarding/CustomerOnboardingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/ForwarderForm.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useForm } from "react-hook-form"; +import { useQuery } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { @@ -11,11 +11,11 @@ import { FileText, CheckCircle2, Loader2, + ChevronLeft, + UploadCloud, } from "lucide-react"; -import useAuth from "@/hooks/useAuth"; -import { api } from "@/services/api"; -import type { CreateCustomerDto } from "@/types/customers"; -import AuthLayout from "@/components/auth/AuthLayout"; +import type { AuthUser } from "@/types/auth"; +import type { CreateCompanyPayload } from "@/services/companies.service"; import PhoneInput from "@/components/auth/PhoneInput"; import { Button, @@ -24,14 +24,13 @@ import { FieldLabel, FieldError, FieldGroup, + SmartFileInput, } from "@edr/ui-common"; -import TransporterOnboarding from "./TransportrOnBoarding"; -import DjiboutiForwardingAgentForm from "./DjiboutiFreightForwardingAgent"; -import ImportExportOnBoarding from "./ImportExportOnBoarding"; +import { api } from "@/services/api"; -type OnboardingStep = "company" | "personnel" | "poa"; +type ForwarderStep = "company" | "personnel" | "poa" | "documents" | "confirm"; -const onboardingSchema = z.object({ +const forwarderSchema = z.object({ companyName: z.string().min(1, "Company name is required"), companyEmail: z.string().email("Invalid email address"), companyPhone: z.string().min(1, "Company phone is required"), @@ -59,9 +58,9 @@ const onboardingSchema = z.object({ poaLocation: z.string().optional(), }); -type FormData = z.infer; +type FormData = z.infer; -const stepFields: Record = { +const stepFields: Record = { company: [ "companyName", "companyEmail", @@ -83,20 +82,71 @@ const stepFields: Record = { "generalManagerPhoneCountryCode", ], poa: [], + documents: [], + confirm: [], }; -export default function CustomerOnboardingPage() { - const queryClient = useQueryClient(); - const { user } = useAuth(); - const [step, setStep] = useState("company"); +function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { + return { + companyName: data.companyName, + companyEmail: data.companyEmail, + companyPhone: `${data.companyPhoneCountryCode}${data.companyPhone}`, + companyLocation: data.companyLocation, + companyAddress: data.companyAddress, + tin: data.tinNumber, + vatNumber: data.vatNumber, + fanNumber: data.fanNumber, + attributes: { + contactPersonName: data.contactPersonName, + contactPersonPhone: `${data.contactPersonPhoneCountryCode}${data.contactPersonPhone}`, + generalManagerName: data.generalManagerName, + generalManagerEmail: data.generalManagerEmail, + generalManagerPhone: `${data.generalManagerPhoneCountryCode}${data.generalManagerPhone}`, + poaName: data.poaName || undefined, + poaPhone: + data.poaPhone && data.poaPhoneCountryCode + ? `${data.poaPhoneCountryCode}${data.poaPhone}` + : undefined, + poaAddress: data.poaAddress || undefined, + poaEmail: data.poaEmail || undefined, + poaLocation: data.poaLocation || undefined, + }, + }; +} + +export default function ForwarderForm({ + documentSettingCode, + user, + onSubmit, + isPending, + onBack, +}: { + documentSettingCode: string; + user: AuthUser; + onSubmit: (data: CreateCompanyPayload) => void; + isPending: boolean; + onBack: () => void; +}) { + const [step, setStep] = useState("company"); + const [documentFiles, setDocumentFiles] = useState< + Record + >({}); + + const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( + api.fileUploadSettings.getByCode.queryOptions({ + input: { code: documentSettingCode }, + refetchOnMount: false, + }), + ); const { register, handleSubmit, trigger, + watch, formState: { errors }, } = useForm({ - resolver: zodResolver(onboardingSchema), + resolver: zodResolver(forwarderSchema), defaultValues: { companyName: "", companyEmail: "", @@ -123,20 +173,21 @@ export default function CustomerOnboardingPage() { }, }); - const createCustomerMutation = useMutation({ - mutationFn: (payload: CreateCustomerDto) => - api.customers.create.call(payload), - onSuccess: () => { - if (user) - queryClient.invalidateQueries({ - queryKey: api.customers.getByUserId.queryKey({ id: user.id }), - }); - }, - }); + const formValues = watch(); + const hasDocuments = Boolean(uploadSetting?.fields?.length); + const totalSteps = 5; const nextStep = async () => { if (step === "poa") { - handleSubmit(onSubmit)(); + setStep("documents"); + return; + } + if (step === "documents") { + setStep("confirm"); + return; + } + if (step === "confirm") { + handleSubmit((data) => onSubmit(buildPayload(data, user)))(); return; } const fields = stepFields[step]; @@ -145,69 +196,36 @@ export default function CustomerOnboardingPage() { setStep(step === "company" ? "personnel" : "poa"); }; - const prevStep = () => { - if (step === "personnel") setStep("company"); - else if (step === "poa") setStep("personnel"); + const skipDocuments = () => { + setStep("confirm"); }; - const onSubmit = async (data: FormData) => { - const nameParts = (user?.name?.en ?? "").split(" "); - const payload: CreateCustomerDto = { - userId: user!.id, - firstName: nameParts[0] || "", - lastName: nameParts.slice(-1)[0] || "", - email: user!.email, - phone: user!.phoneNumber, - companyName: data.companyName, - companyEmail: data.companyEmail, - companyPhone: `${data.companyPhoneCountryCode}${data.companyPhone}`, - companyLocation: data.companyLocation, - companyAddress: data.companyAddress, - contactPersonName: data.contactPersonName, - contactPersonPhone: `${data.contactPersonPhoneCountryCode}${data.contactPersonPhone}`, - tinNumber: data.tinNumber, - vatNumber: data.vatNumber, - fanNumber: data.fanNumber, - generalManagerName: data.generalManagerName, - generalManagerEmail: data.generalManagerEmail, - generalManagerPhone: `${data.generalManagerPhoneCountryCode}${data.generalManagerPhone}`, - poaName: data.poaName || undefined, - poaPhone: - data.poaPhone && data.poaPhoneCountryCode - ? `${data.poaPhoneCountryCode}${data.poaPhone}` - : undefined, - poaAddress: data.poaAddress || undefined, - poaEmail: data.poaEmail || undefined, - poaLocation: data.poaLocation || undefined, - }; - createCustomerMutation.mutate(payload); + const prevStep = () => { + if (step === "company") { + onBack(); + } else if (step === "personnel") { + setStep("company"); + } else if (step === "poa") { + setStep("personnel"); + } else if (step === "documents") { + setStep("poa"); + } else { + setStep("documents"); + } }; return ( - + <> +
+ - - {/* */} - {/* */} - {/*
} active={step === "personnel"} - completed={step === "poa"} + completed={step === "poa" || step === "documents" || step === "confirm"} /> } active={step === "poa"} + completed={step === "documents" || step === "confirm"} + /> + } + active={step === "documents"} + completed={step === "confirm"} + /> + } + active={step === "confirm"} completed={false} />

- {step === "company" && "Step 1 of 3 — Company Information"} - {step === "personnel" && "Step 2 of 3 — Personnel Details"} - {step === "poa" && "Step 3 of 3 — Power of Attorney (Optional)"} + {step === "company" && + `Step 1 of ${totalSteps} — Company Information`} + {step === "personnel" && + `Step 2 of ${totalSteps} — Personnel Details`} + {step === "poa" && + `Step 3 of ${totalSteps} — Power of Attorney (Optional)`} + {step === "documents" && + `Step 4 of ${totalSteps} — Upload Documents (Optional)`} + {step === "confirm" && `Step 5 of ${totalSteps} — Review & Confirm`}

-
*/} +
- {/*
+ e.preventDefault()} + className="flex flex-col gap-4" + > {step === "company" && ( <> @@ -332,11 +369,6 @@ export default function CustomerOnboardingPage() { {step === "personnel" && ( <> -

- Personal details are pulled from your account. Contact and - management info is collected below. -

-

Contact Person @@ -418,88 +450,212 @@ export default function CustomerOnboardingPage() { {step === "poa" && ( <>

- Power of Attorney details are optional. Skip if not applicable. + Power of Attorney details are optional. Fill them in if you have + them, or skip to continue.

- + PoA Name +
- + PoA Email +
- + PoA Location + - + PoA Address +
)} + + {step === "documents" && ( + <> + {loadingDocuments ? ( +
+ +
+ ) : !uploadSetting ? ( +

+ No document requirements found for your account type. +

+ ) : ( +
+ +
+ )} + + )} + + {step === "confirm" && ( +
+
+

+ Review your registration +

+

+ Confirm the company details below before saving. +

+
+ +
+ + + + + + + + + + + + + + + + + +
+
+ )}
- - )} - + + +

- */} - + + + ); +} + +function ReviewRow({ label, value }: { label: string; value?: string | null }) { + return ( +
+
+ {label} +
+
+ {value?.trim() ? value : "Not provided"} +
+
); } diff --git a/apps/edr-freight-web/portal/src/pages/accounts/OnboardingPage.tsx b/apps/edr-freight-web/portal/src/pages/accounts/OnboardingPage.tsx index a53b37ee5..8e8952ef7 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/OnboardingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/OnboardingPage.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { ArrowDownToLine, ArrowUpFromLine, @@ -13,6 +13,7 @@ import { api } from "@/services/api"; import type { CreateCompanyPayload } from "@/services/companies.service"; import AuthLayout from "@/components/auth/AuthLayout"; import CompanyProfileForm from "./CompanyProfileForm"; +import ForwarderForm from "./ForwarderForm"; import DjiboutiAgentForm from "./DjiboutiAgentForm"; import TransporterForm from "./TransporterForm"; import type { OnboardingUserType } from "./types"; @@ -113,18 +114,19 @@ const PREFLIGHT_LEFT = { }, }; +const DOCUMENT_SETTING_CODE_MAP: Record = { + importer: "company_onboarding_documents_customer", + exporter: "company_onboarding_documents_customer", + "freight-forwarder-et": "company_onboarding_documents_forwarder", + "freight-forwarder-dj": "company_onboarding_documents_forwarder_dj", + transporter: "company_onboarding_documents_transporter", +}; + export default function OnboardingPage() { const queryClient = useQueryClient(); const { user } = useAuth(); const [userType, setUserType] = useState(null); - useQuery( - api.fileUploadSettings.getByEntity.queryOptions({ - input: { entity: "customer" }, - refetchOnMount: false, - }), - ); - const COMPANY_TYPE_MAP: Record = { importer: "customer", exporter: "customer", @@ -237,6 +239,7 @@ export default function OnboardingPage() { {userType === "transporter" ? ( ) : userType === "freight-forwarder-dj" ? ( + ) : userType === "freight-forwarder-et" ? ( + ) : ( { - if (data.truckType === "Casoni" && (!data.plateNumber2 || data.plateNumber2.trim().length === 0)) { + if ( + data.truckType === "Casoni" && + (!data.plateNumber2 || data.plateNumber2.trim().length === 0) + ) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["plateNumber2"], @@ -77,19 +89,34 @@ function buildPayload(data: FormData, user: AuthUser): CreateCompanyPayload { } export default function TransporterForm({ + documentSettingCode, user, onSubmit, isPending, onBack, }: { + documentSettingCode: string; user: AuthUser; onSubmit: (data: CreateCompanyPayload) => void; isPending: boolean; onBack: () => void; }) { + const [step, setStep] = useState("vehicle"); + const [documentFiles, setDocumentFiles] = useState< + Record + >({}); + + const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( + api.fileUploadSettings.getByCode.queryOptions({ + input: { code: documentSettingCode }, + refetchOnMount: false, + }), + ); + const { register, handleSubmit, + trigger, watch, control, formState: { errors }, @@ -108,187 +135,341 @@ export default function TransporterForm({ const truckType = watch("truckType"); const isCasoni = truckType === "Casoni"; + const formValues = watch(); + const hasDocuments = Boolean(uploadSetting?.fields?.length); + const totalSteps = 3; + + const nextStep = async () => { + if (step === "documents") { + setStep("confirm"); + return; + } + if (step === "confirm") { + handleSubmit((data) => onSubmit(buildPayload(data, user)))(); + return; + } + const fields: (keyof FormData)[] = [ + "tinNumber", + "fanNumber", + "truckType", + "plateNumber", + "vehicleModel", + "yearOfManufacturing", + ]; + const isValid = await trigger(fields); + if (!isValid) return; + setStep("documents"); + }; + + const skipDocuments = () => { + setStep("confirm"); + }; + + const prevStep = () => { + if (step === "vehicle") { + onBack(); + } else if (step === "documents") { + setStep("vehicle"); + } else { + setStep("documents"); + } + }; return ( <>
-
-
- -
+
+
+ } + active={step === "vehicle"} + completed={step !== "vehicle"} + /> + } + active={step === "documents"} + completed={step === "confirm"} + /> + } + active={step === "confirm"} + completed={false} + />

- Transporter Registration + {step === "vehicle" && `Step 1 of ${totalSteps} — Vehicle Information`} + {step === "documents" && `Step 2 of ${totalSteps} — Upload Documents (Optional)`} + {step === "confirm" && `Step 3 of ${totalSteps} — Review & Confirm`}

onSubmit(buildPayload(data, user)))} + onSubmit={(e) => e.preventDefault()} className="flex flex-col gap-4" > - {/* Personal Info (read-only) */} -
-
- - Account Holder + {step === "vehicle" && ( + <> +
+ + TIN Number (10 digits) + + + + + + FAN Number (16 digits) + + + +
+ +
+ +

+ Vehicle / Truck Information +

+ + ( + + Truck Type + + + + )} + /> + +
+ + Plate Number{isCasoni ? " (Front)" : ""} + + + + + {isCasoni && ( + + Plate Number (Trailer) + + + + )} + + {!isCasoni && ( + + Vehicle Model + + + + )} +
+ +
+ {isCasoni && ( + + Vehicle Model + + + + )} + + + Year of Manufacturing + + + +
+ + )} + + {step === "documents" && ( + <> + {loadingDocuments ? ( +
+ +
+ ) : !uploadSetting ? ( +

+ No document requirements found for your account type. +

+ ) : ( +
+ +
+ )} + + )} + + {step === "confirm" && ( +
+
+

+ Review your registration +

+

+ Confirm the details below before saving. +

+
+ +
+ + + + + {formValues.plateNumber2 && ( + + )} + + +
-

- {user.name?.en} — {user.email} — {user.phoneNumber} -

-
- -
- - TIN Number (10 digits) - - - - - - FAN Number (16 digits) - - - -
- -
- -

- Vehicle / Truck Information -

- - ( - - Truck Type - - - - )} - /> - -
- - - Plate Number{isCasoni ? " (Front)" : ""} - - - - - - {isCasoni && ( - - Plate Number (Trailer) - - - - )} - - {!isCasoni && ( - - Vehicle Model - - - - )} -
- -
- {isCasoni && ( - - Vehicle Model - - - - )} - - - Year of Manufacturing - - - -
+ )}
- - )} - + + +
); } + +function ReviewRow({ label, value }: { label: string; value?: string | null }) { + return ( +
+
+ {label} +
+
+ {value?.trim() ? value : "Not provided"} +
+
+ ); +} + +function StepIcon({ + icon, + active, + completed, +}: { + icon: React.ReactNode; + active: boolean; + completed: boolean; +}) { + return ( +
+ {completed ? : icon} +
+ ); +}