mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 21:50:57 +00:00
fix: make the file sync work on onboarding
This commit is contained in:
@@ -164,7 +164,9 @@ export default function OnboardingWizardDialog({
|
||||
(company?.company?.nationality as CompanyNationality | null) ?? null;
|
||||
|
||||
// Resume position from the backend-persisted step.
|
||||
const resumeFormStep: FormStep = FORM_STEPS.includes(onboardingStep as FormStep)
|
||||
const resumeFormStep: FormStep = FORM_STEPS.includes(
|
||||
onboardingStep as FormStep,
|
||||
)
|
||||
? (onboardingStep as FormStep)
|
||||
: "company";
|
||||
|
||||
@@ -275,7 +277,7 @@ export default function OnboardingWizardDialog({
|
||||
const idx = FORM_STEPS.indexOf(step as FormStep);
|
||||
if (idx < 0 || idx <= furthestIdxRef.current) return;
|
||||
furthestIdxRef.current = idx;
|
||||
api.companies.setOnboardingStep.call({ step }).catch(() => {});
|
||||
api.companies.setOnboardingStep.call({ step }).catch(() => { });
|
||||
}, []);
|
||||
|
||||
// Mirror the form's step locally (for the header/pill) and persist it.
|
||||
@@ -321,7 +323,7 @@ export default function OnboardingWizardDialog({
|
||||
|
||||
// Note: no "back to role selection" — once the draft is created the role(s)
|
||||
// are fixed; the form's first-step Back is a no-op so progress never resets.
|
||||
const handleBackToRoles = useCallback(() => {}, []);
|
||||
const handleBackToRoles = useCallback(() => { }, []);
|
||||
|
||||
// Save the current step's fields to the draft (PATCH /profile). Returns the
|
||||
// server error message on failure so the form can show it (e.g. duplicate TIN).
|
||||
@@ -339,6 +341,31 @@ export default function OnboardingWizardDialog({
|
||||
[],
|
||||
);
|
||||
|
||||
// Auto-upload the documents the user just selected as they leave the documents
|
||||
// step. Only the in-memory selections are sent; once uploaded they're cleared
|
||||
// (so the final submit never re-uploads them) and the requirements query is
|
||||
// refreshed so the "Already uploaded" badges light up. Partial uploads are
|
||||
// allowed — the user may continue even with required docs still outstanding.
|
||||
const handleUploadDocuments = useCallback(async (): Promise<
|
||||
{ ok: true } | { ok: false; error: string }
|
||||
> => {
|
||||
const companyId = company?.company?.id;
|
||||
const hasNew = Object.values(documentFiles).some(
|
||||
(f) => f !== null && (Array.isArray(f) ? f.length > 0 : true),
|
||||
);
|
||||
if (!companyId || !hasNew) return { ok: true };
|
||||
try {
|
||||
await companiesService.uploadDocuments(companyId, documentFiles);
|
||||
setDocumentFiles({});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: api.companies.onboardingRequirements.queryKey(),
|
||||
});
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
return { ok: false, error: extractApiError(err).message };
|
||||
}
|
||||
}, [company?.company?.id, documentFiles, queryClient]);
|
||||
|
||||
// Final confirm step → finalize onboarding (no company create; it already
|
||||
// exists as a draft that's been filled in step-by-step).
|
||||
const handleSubmit = useCallback(
|
||||
@@ -383,6 +410,25 @@ export default function OnboardingWizardDialog({
|
||||
requirementsQuery.data?.documentSettingCode ??
|
||||
documentSettingCode(effectiveNationality);
|
||||
|
||||
// Server-confirmed document state, used both to badge already-uploaded fields
|
||||
// and to keep a refreshed resume from over-shooting the documents step.
|
||||
const requirementDocuments = requirementsQuery.data?.documents ?? [];
|
||||
const uploadedDocumentKeys = requirementDocuments
|
||||
.filter((d) => d.uploaded)
|
||||
.map((d) => d.fileKey);
|
||||
// If any REQUIRED document is still missing, the resume must not rest past the
|
||||
// documents step (don't skip to Business License) — clamp it back. This only
|
||||
// changes the target once requirements load; the form follows the correction
|
||||
// as long as the user hasn't navigated yet.
|
||||
const requiredDocsMissing = requirementDocuments.some(
|
||||
(d) => d.isRequired && !d.uploaded,
|
||||
);
|
||||
const effectiveResumeStep: FormStep =
|
||||
requiredDocsMissing &&
|
||||
FORM_STEPS.indexOf(resumeFormStep) > FORM_STEPS.indexOf("documents")
|
||||
? "documents"
|
||||
: resumeFormStep;
|
||||
|
||||
const formProps = {
|
||||
documentSettingCode: resolvedDocumentSettingCode,
|
||||
documentFiles,
|
||||
@@ -392,7 +438,7 @@ export default function OnboardingWizardDialog({
|
||||
isPending: finishMutation.isPending,
|
||||
onBack: handleBackToRoles,
|
||||
hideFirstStepBack: true,
|
||||
initialStep: resumeFormStep,
|
||||
initialStep: effectiveResumeStep,
|
||||
resyncOpen: opened,
|
||||
onStepChange: handleStepChange,
|
||||
onSaveStep: saveStep,
|
||||
@@ -400,6 +446,8 @@ export default function OnboardingWizardDialog({
|
||||
roleProfiles,
|
||||
licenseFiles,
|
||||
onLicenseChange: setLicenseFiles,
|
||||
uploadedDocumentKeys,
|
||||
onUploadDocuments: handleUploadDocuments,
|
||||
// Surface a failed final submit (license/document upload or complete) inside
|
||||
// the form — otherwise the server message (e.g. a 500) would be invisible on
|
||||
// the submit step.
|
||||
@@ -413,7 +461,7 @@ export default function OnboardingWizardDialog({
|
||||
withCloseButton={!completed}
|
||||
closeOnClickOutside={false}
|
||||
closeOnEscape={!completed}
|
||||
size={720}
|
||||
size={1440}
|
||||
radius="lg"
|
||||
padding="xl"
|
||||
centered
|
||||
@@ -422,11 +470,11 @@ export default function OnboardingWizardDialog({
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 4 }}
|
||||
styles={{
|
||||
header: {
|
||||
alignItems:"flex-start"
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
title: {
|
||||
flex: 1
|
||||
}
|
||||
flex: 1,
|
||||
},
|
||||
}}
|
||||
title={
|
||||
completed ? null : (
|
||||
@@ -448,59 +496,64 @@ export default function OnboardingWizardDialog({
|
||||
{completed ? (
|
||||
<OnboardingCompletePanel onClose={handleClose} />
|
||||
) : (
|
||||
<Stack gap="xl">
|
||||
|
||||
{phase === "nationality" ? (
|
||||
<Stack gap="lg">
|
||||
<NationalitySelect
|
||||
value={nationality}
|
||||
onChange={setNationality}
|
||||
embedded
|
||||
/>
|
||||
<Group justify="flex-end" pt="xs">
|
||||
<Button
|
||||
color="edr-green"
|
||||
onClick={handleNationalityContinue}
|
||||
disabled={!nationality}
|
||||
rightSection={<ArrowRight size={16} />}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
) : phase === "role" ? (
|
||||
<Stack gap="lg">
|
||||
<OnboardingRoleSelect value={roles} onChange={setRoles} embedded />
|
||||
{startError && (
|
||||
<Text size="sm" c="red">
|
||||
{startError}
|
||||
</Text>
|
||||
)}
|
||||
<Group justify="space-between" pt="xs">
|
||||
<Button
|
||||
variant="default"
|
||||
leftSection={<ArrowLeft size={16} />}
|
||||
onClick={() => setPhase("nationality")}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
color="edr-green"
|
||||
onClick={handleRolesContinue}
|
||||
disabled={!rolesValid}
|
||||
loading={startMutation.isPending}
|
||||
rightSection={
|
||||
startMutation.isPending ? undefined : <ArrowRight size={16} />
|
||||
}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
) : (
|
||||
<CompanyProfileForm {...formProps} />
|
||||
)}
|
||||
</Stack>
|
||||
<Stack gap="xl">
|
||||
{phase === "nationality" ? (
|
||||
<Stack gap="lg">
|
||||
<NationalitySelect
|
||||
value={nationality}
|
||||
onChange={setNationality}
|
||||
embedded
|
||||
/>
|
||||
<Group justify="flex-end" pt="xs">
|
||||
<Button
|
||||
color="edr-green"
|
||||
onClick={handleNationalityContinue}
|
||||
disabled={!nationality}
|
||||
rightSection={<ArrowRight size={16} />}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
) : phase === "role" ? (
|
||||
<Stack gap="lg">
|
||||
<OnboardingRoleSelect
|
||||
value={roles}
|
||||
onChange={setRoles}
|
||||
embedded
|
||||
/>
|
||||
{startError && (
|
||||
<Text size="sm" c="red">
|
||||
{startError}
|
||||
</Text>
|
||||
)}
|
||||
<Group justify="space-between" pt="xs">
|
||||
<Button
|
||||
variant="default"
|
||||
leftSection={<ArrowLeft size={16} />}
|
||||
onClick={() => setPhase("nationality")}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
color="edr-green"
|
||||
onClick={handleRolesContinue}
|
||||
disabled={!rolesValid}
|
||||
loading={startMutation.isPending}
|
||||
rightSection={
|
||||
startMutation.isPending ? undefined : (
|
||||
<ArrowRight size={16} />
|
||||
)
|
||||
}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
) : (
|
||||
<CompanyProfileForm {...formProps} />
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
@@ -518,7 +571,10 @@ function OnboardingCompletePanel({ onClose }: { onClose: () => void }) {
|
||||
className="flex h-16 w-16 items-center justify-center rounded-full"
|
||||
style={{ background: "var(--mantine-color-edr-green-1)" }}
|
||||
>
|
||||
<PartyPopper size={32} className="text-[var(--mantine-color-edr-green-7)]" />
|
||||
<PartyPopper
|
||||
size={32}
|
||||
className="text-[var(--mantine-color-edr-green-7)]"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
@@ -538,14 +594,20 @@ function OnboardingCompletePanel({ onClose }: { onClose: () => void }) {
|
||||
style={{ background: "var(--mantine-color-edr-green-0)" }}
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap" align="flex-start">
|
||||
<Clock size={18} className="mt-0.5 shrink-0 text-[var(--mantine-color-edr-green-7)]" />
|
||||
<Clock
|
||||
size={18}
|
||||
className="mt-0.5 shrink-0 text-[var(--mantine-color-edr-green-7)]"
|
||||
/>
|
||||
<Text size="sm" ta="left">
|
||||
Each operational profile (importer, exporter, freight forwarder) is
|
||||
reviewed and approved individually.
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="sm" wrap="nowrap" align="flex-start">
|
||||
<ShieldCheck size={18} className="mt-0.5 shrink-0 text-[var(--mantine-color-edr-green-7)]" />
|
||||
<ShieldCheck
|
||||
size={18}
|
||||
className="mt-0.5 shrink-0 text-[var(--mantine-color-edr-green-7)]"
|
||||
/>
|
||||
<Text size="sm" ta="left">
|
||||
You can start creating bookings under a profile as soon as it's
|
||||
approved — we'll let you know the moment that happens.
|
||||
|
||||
@@ -53,7 +53,8 @@ type CompanyStep =
|
||||
| "additional";
|
||||
|
||||
/** Last 9 digits (Ethiopian national significant number) for tolerant compare. */
|
||||
const phoneDigits = (p?: string | null) => (p ?? "").replace(/\D/g, "").slice(-9);
|
||||
const phoneDigits = (p?: string | null) =>
|
||||
(p ?? "").replace(/\D/g, "").slice(-9);
|
||||
const samePhone = (a?: string | null, b?: string | null) => {
|
||||
const da = phoneDigits(a);
|
||||
return da.length === 9 && da === phoneDigits(b);
|
||||
@@ -92,7 +93,6 @@ const onboardingSchema = z.object({
|
||||
woreda: z.string().min(1, "Woreda is required"),
|
||||
kebele: z.string().min(1, "Kebele is required"),
|
||||
houseNo: z.string().min(1, "House number is required"),
|
||||
etradePhone: z.string().optional(),
|
||||
contactPersonName: z.string().min(1, "Contact person name is required"),
|
||||
contactPersonPosition: z.string().optional(),
|
||||
contactPersonEmail: z
|
||||
@@ -143,7 +143,6 @@ const stepFields: Record<CompanyStep, (keyof FormData)[]> = {
|
||||
"woreda",
|
||||
"kebele",
|
||||
"houseNo",
|
||||
"etradePhone",
|
||||
],
|
||||
personnel: [
|
||||
"generalManagerName",
|
||||
@@ -216,7 +215,7 @@ function stepPayload(
|
||||
woreda: d.woreda,
|
||||
kebele: d.kebele,
|
||||
houseNo: d.houseNo,
|
||||
etradePhone: d.etradePhone,
|
||||
etradePhone: d.companyPhone,
|
||||
};
|
||||
case "personnel":
|
||||
return {
|
||||
@@ -268,7 +267,6 @@ function toFormValues(p: ProfileResponse): FormData {
|
||||
woreda: p.woreda ?? "",
|
||||
kebele: p.kebele ?? "",
|
||||
houseNo: p.houseNo ?? "",
|
||||
etradePhone: p.etradePhone ?? "",
|
||||
contactPersonName: p.contactPersonName ?? "",
|
||||
contactPersonPosition: p.contactPersonPosition ?? "",
|
||||
contactPersonEmail: p.contactPersonEmail ?? "",
|
||||
@@ -316,6 +314,8 @@ export default function CompanyProfileForm({
|
||||
licenseFiles,
|
||||
onLicenseChange,
|
||||
submitError,
|
||||
uploadedDocumentKeys,
|
||||
onUploadDocuments,
|
||||
}: {
|
||||
documentSettingCode: string;
|
||||
documentFiles?: Record<string, File | File[] | null>;
|
||||
@@ -345,6 +345,14 @@ export default function CompanyProfileForm({
|
||||
onLicenseChange?: (value: Record<string, File[]>) => void;
|
||||
/** Server error from the final submit (uploads/complete), shown verbatim. */
|
||||
submitError?: string | null;
|
||||
/** fileKeys whose company document is already uploaded server-side (resume). */
|
||||
uploadedDocumentKeys?: string[];
|
||||
/**
|
||||
* Auto-upload the currently-selected company documents (the Documents step's
|
||||
* "Continue" action). Resolves to an error message string on failure so the
|
||||
* step can surface it and hold the user in place.
|
||||
*/
|
||||
onUploadDocuments?: () => Promise<{ ok: true } | { ok: false; error: string }>;
|
||||
}) {
|
||||
const [step, setStep] = useState<CompanyStep>(initialStep ?? "company");
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -356,17 +364,40 @@ export default function CompanyProfileForm({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [step]);
|
||||
|
||||
// Tracks whether the user has manually navigated the form this session. While
|
||||
// false, the form still follows the parent's resume target (initialStep) —
|
||||
// which can shift to an earlier step once server data lands (e.g. a required
|
||||
// document turns out to be un-uploaded, so we must not rest on a later step).
|
||||
const userNavigatedRef = useRef(false);
|
||||
|
||||
// On reopen, jump to the furthest step reached (initialStep) so progress
|
||||
// never appears to reset.
|
||||
// never appears to reset. Re-arm the follow-the-parent behaviour too.
|
||||
const wasOpen = useRef(resyncOpen);
|
||||
useEffect(() => {
|
||||
if (resyncOpen && !wasOpen.current && initialStep) {
|
||||
userNavigatedRef.current = false;
|
||||
setStep(initialStep);
|
||||
setSaveError(null);
|
||||
}
|
||||
wasOpen.current = resyncOpen;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [resyncOpen]);
|
||||
|
||||
// Follow a parent-driven resume correction: if initialStep changes (the wizard
|
||||
// re-clamps it back once onboarding requirements load — e.g. a required
|
||||
// document is still missing, so it must not skip ahead to Business License),
|
||||
// adopt it, but only while the user hasn't started navigating themselves.
|
||||
const lastInitialStep = useRef(initialStep);
|
||||
useEffect(() => {
|
||||
if (initialStep && initialStep !== lastInitialStep.current) {
|
||||
lastInitialStep.current = initialStep;
|
||||
if (!userNavigatedRef.current) {
|
||||
setStep(initialStep);
|
||||
setSaveError(null);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initialStep]);
|
||||
const [internalFiles, setInternalFiles] = useState<
|
||||
Record<string, File | File[] | null>
|
||||
>({});
|
||||
@@ -410,7 +441,6 @@ export default function CompanyProfileForm({
|
||||
woreda: "",
|
||||
kebele: "",
|
||||
houseNo: "",
|
||||
etradePhone: "",
|
||||
contactPersonName: "",
|
||||
contactPersonPosition: "",
|
||||
contactPersonEmail: "",
|
||||
@@ -482,7 +512,7 @@ export default function CompanyProfileForm({
|
||||
setValue("kebele", data.kebele);
|
||||
setValue("houseNo", data.houseNo);
|
||||
setValue(
|
||||
"etradePhone",
|
||||
"companyPhone",
|
||||
toEthiopianE164(data.regularPhone || data.mobilePhone),
|
||||
);
|
||||
// companyAddress is composed reactively from the address fields below, so
|
||||
@@ -530,17 +560,6 @@ export default function CompanyProfileForm({
|
||||
setValue("poaPhone", watch("contactPersonPhone"));
|
||||
};
|
||||
|
||||
/** Populate the Contact Person from the currently logged-in user. */
|
||||
const useLoggedInUserAsContact = () => {
|
||||
setValue("contactPersonName", user?.name?.en ?? "", {
|
||||
shouldValidate: true,
|
||||
});
|
||||
if (user?.email) setValue("contactPersonEmail", user.email);
|
||||
setValue("contactPersonPhone", user?.phoneNumber ?? "", {
|
||||
shouldValidate: true,
|
||||
});
|
||||
};
|
||||
|
||||
// --- Contact-phone SMS OTP verification -----------------------------------
|
||||
// The phone we verify is the contact-person phone, normalised to E.164 so it
|
||||
// matches what the backend persists as `contactVerifiedPhone`.
|
||||
@@ -612,7 +631,7 @@ export default function CompanyProfileForm({
|
||||
setOtpSent(false);
|
||||
// Persist the verified phone so the step resumes as "done" after a refresh
|
||||
// (best-effort — the OTP itself already succeeded server-side).
|
||||
onSaveStep?.({ contactVerifiedPhone: contactPhoneE164 }).catch(() => {});
|
||||
onSaveStep?.({ contactVerifiedPhone: contactPhoneE164 }).catch(() => { });
|
||||
} catch (err) {
|
||||
setOtpError(extractApiError(err).message);
|
||||
} finally {
|
||||
@@ -675,6 +694,7 @@ export default function CompanyProfileForm({
|
||||
);
|
||||
|
||||
const nextStep = async () => {
|
||||
userNavigatedRef.current = true;
|
||||
if (step === "additional") {
|
||||
if (!licenseComplete) {
|
||||
setSaveError(
|
||||
@@ -699,16 +719,34 @@ export default function CompanyProfileForm({
|
||||
setStep(stepOrder[currentIdx + 1]);
|
||||
return;
|
||||
}
|
||||
// The documents step has nothing to persist; field steps validate + save
|
||||
// before advancing.
|
||||
if (step !== "documents") {
|
||||
const ok = await saveCurrentStep();
|
||||
if (!ok) return;
|
||||
// The documents step auto-uploads whatever the user selected as they
|
||||
// continue (partial uploads are allowed — required-doc completeness is
|
||||
// re-checked on resume). A failed upload holds them on the step.
|
||||
if (step === "documents") {
|
||||
if (onUploadDocuments) {
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await onUploadDocuments();
|
||||
if (!res.ok) {
|
||||
setSaveError(res.error);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
setSaveError(null);
|
||||
setStep(stepOrder[currentIdx + 1]);
|
||||
return;
|
||||
}
|
||||
// Field steps validate + save before advancing.
|
||||
const ok = await saveCurrentStep();
|
||||
if (!ok) return;
|
||||
setStep(stepOrder[currentIdx + 1]);
|
||||
};
|
||||
|
||||
const prevStep = () => {
|
||||
userNavigatedRef.current = true;
|
||||
setSaveError(null);
|
||||
if (currentIdx === 0) onBack();
|
||||
else setStep(stepOrder[currentIdx - 1]);
|
||||
@@ -864,11 +902,6 @@ export default function CompanyProfileForm({
|
||||
error={errors.houseNo?.message}
|
||||
{...register("houseNo")}
|
||||
/>
|
||||
<ControlledPhoneField
|
||||
control={control}
|
||||
name="etradePhone"
|
||||
label="Phone"
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
@@ -922,15 +955,6 @@ export default function CompanyProfileForm({
|
||||
Contact Person
|
||||
</Text>
|
||||
<Group gap="xs" wrap="nowrap" style={{ flexShrink: 0 }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
size="xs"
|
||||
leftSection={<UserCheck size={14} />}
|
||||
onClick={useLoggedInUserAsContact}
|
||||
>
|
||||
Use me
|
||||
</Button>
|
||||
{watch("generalManagerName") && (
|
||||
<Button
|
||||
variant="light"
|
||||
@@ -978,12 +1002,6 @@ export default function CompanyProfileForm({
|
||||
|
||||
{step === "verify" && (
|
||||
<Stack gap="md">
|
||||
<Group gap="xs" align="center">
|
||||
<ShieldCheck size={18} className="text-[var(--mantine-color-edr-green-7)]" />
|
||||
<Text fw={600} size="sm" c="edr-text">
|
||||
Verify the contact person
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm" c="edr-muted">
|
||||
We'll text a one-time code to the contact person's phone to
|
||||
confirm it's reachable. This is required before you continue.
|
||||
@@ -1009,7 +1027,10 @@ export default function CompanyProfileForm({
|
||||
) : (
|
||||
<Stack gap="sm">
|
||||
<Group gap="xs" align="center">
|
||||
<Smartphone size={16} className="text-[var(--mantine-color-edr-muted)]" />
|
||||
<Smartphone
|
||||
size={16}
|
||||
className="text-[var(--mantine-color-edr-muted)]"
|
||||
/>
|
||||
<Text size="sm" c="edr-text">
|
||||
{maskPhone(contactPhoneE164)}
|
||||
</Text>
|
||||
@@ -1028,15 +1049,17 @@ export default function CompanyProfileForm({
|
||||
</Button>
|
||||
) : (
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" c="edr-muted">
|
||||
Enter the 6-digit code we sent to{" "}
|
||||
{maskPhone(contactPhoneE164)}.
|
||||
</Text>
|
||||
<PinInput
|
||||
length={6}
|
||||
type="number"
|
||||
oneTimeCode
|
||||
value={otpCode}
|
||||
placeholder="0"
|
||||
styles={{
|
||||
input: {
|
||||
textAlign: "center",
|
||||
},
|
||||
}}
|
||||
onChange={setOtpCode}
|
||||
/>
|
||||
<Group gap="sm">
|
||||
@@ -1056,7 +1079,9 @@ export default function CompanyProfileForm({
|
||||
disabled={resendIn > 0 || sendingOtp}
|
||||
leftSection={<RotateCw size={14} />}
|
||||
>
|
||||
{resendIn > 0 ? `Resend in ${resendIn}s` : "Resend code"}
|
||||
{resendIn > 0
|
||||
? `Resend in ${resendIn}s`
|
||||
: "Resend code"}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -1147,6 +1172,8 @@ export default function CompanyProfileForm({
|
||||
<SmartFileInput
|
||||
file={uploadSetting}
|
||||
value={documentFiles}
|
||||
uploadedKeys={uploadedDocumentKeys}
|
||||
containerClassName="lg:grid grid-cols-2 items-stretch"
|
||||
onChange={setDocumentFiles}
|
||||
/>
|
||||
)}
|
||||
@@ -1210,19 +1237,12 @@ export default function CompanyProfileForm({
|
||||
}
|
||||
loading={isPending || saving}
|
||||
rightSection={
|
||||
!isPending &&
|
||||
!saving &&
|
||||
step !== "additional" &&
|
||||
step !== "documents" ? (
|
||||
!isPending && !saving && step !== "additional" ? (
|
||||
<ArrowRight size={16} />
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
{step === "documents" || step === "verify"
|
||||
? "Continue"
|
||||
: step === "additional"
|
||||
? "Submit for review"
|
||||
: "Save & Continue"}
|
||||
{step === "additional" ? "Submit for review" : "Continue"}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user