mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 06:28:12 +00:00
chore: rm the verify step in onboarding
This commit is contained in:
@@ -46,7 +46,6 @@ type FormStep =
|
|||||||
| "company"
|
| "company"
|
||||||
| "personnel"
|
| "personnel"
|
||||||
| "contact"
|
| "contact"
|
||||||
| "verify"
|
|
||||||
| "poa"
|
| "poa"
|
||||||
| "documents"
|
| "documents"
|
||||||
| "additional";
|
| "additional";
|
||||||
@@ -54,7 +53,6 @@ const FORM_STEPS: FormStep[] = [
|
|||||||
"company",
|
"company",
|
||||||
"personnel",
|
"personnel",
|
||||||
"contact",
|
"contact",
|
||||||
"verify",
|
|
||||||
"poa",
|
"poa",
|
||||||
"documents",
|
"documents",
|
||||||
"additional",
|
"additional",
|
||||||
@@ -95,11 +93,6 @@ const STEP_META: Record<
|
|||||||
title: "Contact Person",
|
title: "Contact Person",
|
||||||
description: "Who should we reach out to about this account?",
|
description: "Who should we reach out to about this account?",
|
||||||
},
|
},
|
||||||
verify: {
|
|
||||||
icon: <ShieldCheck size={20} />,
|
|
||||||
title: "Verify Contact Person",
|
|
||||||
description: "Confirm the contact phone with a one-time SMS code.",
|
|
||||||
},
|
|
||||||
poa: {
|
poa: {
|
||||||
icon: <FileText size={20} />,
|
icon: <FileText size={20} />,
|
||||||
title: "Power of Attorney",
|
title: "Power of Attorney",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
PinInput,
|
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
@@ -16,9 +15,6 @@ import {
|
|||||||
AlertCircle,
|
AlertCircle,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
CheckCircle2,
|
|
||||||
RotateCw,
|
|
||||||
Smartphone,
|
|
||||||
UserCheck,
|
UserCheck,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
@@ -35,7 +31,6 @@ import RoleLicenseStep, {
|
|||||||
type RoleLicenseProfile,
|
type RoleLicenseProfile,
|
||||||
} from "@/components/onboarding/RoleLicenseStep";
|
} from "@/components/onboarding/RoleLicenseStep";
|
||||||
import ETradeInfo from "@/components/onboarding/ETradeInfo";
|
import ETradeInfo from "@/components/onboarding/ETradeInfo";
|
||||||
import { extractApiError } from "@/utils/result";
|
|
||||||
import {
|
import {
|
||||||
type CompanyStep,
|
type CompanyStep,
|
||||||
type FormData,
|
type FormData,
|
||||||
@@ -44,8 +39,6 @@ import {
|
|||||||
} from "./companyProfileForm/schema";
|
} from "./companyProfileForm/schema";
|
||||||
import {
|
import {
|
||||||
buildPayload,
|
buildPayload,
|
||||||
maskPhone,
|
|
||||||
samePhone,
|
|
||||||
stepPayload,
|
stepPayload,
|
||||||
toFormValues,
|
toFormValues,
|
||||||
} from "./companyProfileForm/helpers";
|
} from "./companyProfileForm/helpers";
|
||||||
@@ -350,85 +343,6 @@ export default function CompanyProfileForm({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 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`.
|
|
||||||
const contactPhoneE164 = toEthiopianE164(watch("contactPersonPhone") ?? "");
|
|
||||||
// Source of truth for "already verified" comes from the onboarding/profile
|
|
||||||
// info (rehydrate) — so a refresh resumes the verify step's "done" state.
|
|
||||||
const [verifiedPhone, setVerifiedPhone] = useState<string | null>(
|
|
||||||
rehydrate?.contactVerifiedPhone ?? null,
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
if (rehydrate?.contactVerifiedPhone) {
|
|
||||||
setVerifiedPhone(rehydrate.contactVerifiedPhone);
|
|
||||||
}
|
|
||||||
}, [rehydrate?.contactVerifiedPhone]);
|
|
||||||
const phoneVerified = samePhone(verifiedPhone, contactPhoneE164);
|
|
||||||
|
|
||||||
const [otpSent, setOtpSent] = useState(false);
|
|
||||||
const [otpCode, setOtpCode] = useState("");
|
|
||||||
const [sendingOtp, setSendingOtp] = useState(false);
|
|
||||||
const [verifyingOtp, setVerifyingOtp] = useState(false);
|
|
||||||
const [otpError, setOtpError] = useState<string | null>(null);
|
|
||||||
const [resendIn, setResendIn] = useState(0);
|
|
||||||
|
|
||||||
// Resend cooldown countdown (no Date.now needed — pure setTimeout ticks).
|
|
||||||
useEffect(() => {
|
|
||||||
if (resendIn <= 0) return;
|
|
||||||
const t = setTimeout(() => setResendIn((s) => s - 1), 1000);
|
|
||||||
return () => clearTimeout(t);
|
|
||||||
}, [resendIn]);
|
|
||||||
|
|
||||||
// A changed contact phone invalidates any in-flight code entry (the previous
|
|
||||||
// code was for a different number). Verified state is handled separately via
|
|
||||||
// the phone comparison, so this only resets the send/enter UI.
|
|
||||||
useEffect(() => {
|
|
||||||
setOtpSent(false);
|
|
||||||
setOtpCode("");
|
|
||||||
setOtpError(null);
|
|
||||||
}, [contactPhoneE164]);
|
|
||||||
|
|
||||||
const sendContactOtp = async () => {
|
|
||||||
setOtpError(null);
|
|
||||||
if (!contactPhoneE164) {
|
|
||||||
setOtpError("Enter a valid contact phone number first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSendingOtp(true);
|
|
||||||
try {
|
|
||||||
await api.auth.sendOTP.call({ phone: contactPhoneE164 });
|
|
||||||
setOtpSent(true);
|
|
||||||
setOtpCode("");
|
|
||||||
setResendIn(60);
|
|
||||||
} catch (err) {
|
|
||||||
setOtpError(extractApiError(err).message);
|
|
||||||
} finally {
|
|
||||||
setSendingOtp(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const verifyContactOtp = async () => {
|
|
||||||
setOtpError(null);
|
|
||||||
if (otpCode.length !== 6) {
|
|
||||||
setOtpError("Enter the 6-digit code we sent you.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setVerifyingOtp(true);
|
|
||||||
try {
|
|
||||||
await api.auth.verifyOTP.call({ phone: contactPhoneE164, otp: otpCode });
|
|
||||||
setVerifiedPhone(contactPhoneE164);
|
|
||||||
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(() => { });
|
|
||||||
} catch (err) {
|
|
||||||
setOtpError(extractApiError(err).message);
|
|
||||||
} finally {
|
|
||||||
setVerifyingOtp(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasDocuments = Boolean(uploadSetting?.fields?.length);
|
const hasDocuments = Boolean(uploadSetting?.fields?.length);
|
||||||
|
|
||||||
// The registration/license details come straight from the eTrade lookup and
|
// The registration/license details come straight from the eTrade lookup and
|
||||||
@@ -451,7 +365,6 @@ export default function CompanyProfileForm({
|
|||||||
"company",
|
"company",
|
||||||
"personnel",
|
"personnel",
|
||||||
"contact",
|
"contact",
|
||||||
"verify",
|
|
||||||
"poa",
|
"poa",
|
||||||
"documents",
|
"documents",
|
||||||
"additional",
|
"additional",
|
||||||
@@ -495,20 +408,6 @@ export default function CompanyProfileForm({
|
|||||||
handleSubmit((data) => onSubmit(buildPayload(data, user)))();
|
handleSubmit((data) => onSubmit(buildPayload(data, user)))();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Contact-phone verification gates advancing past the verify step. The
|
|
||||||
// verified phone is already persisted (on verify success), so there's
|
|
||||||
// nothing extra to save here.
|
|
||||||
if (step === "verify") {
|
|
||||||
if (!phoneVerified) {
|
|
||||||
setSaveError(
|
|
||||||
"Please verify the contact person's phone number to continue.",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSaveError(null);
|
|
||||||
setStep(stepOrder[currentIdx + 1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The documents step auto-uploads whatever the user selected as they
|
// The documents step auto-uploads whatever the user selected as they
|
||||||
// continue (partial uploads are allowed — required-doc completeness is
|
// continue (partial uploads are allowed — required-doc completeness is
|
||||||
// re-checked on resume). A failed upload holds them on the step.
|
// re-checked on resume). A failed upload holds them on the step.
|
||||||
@@ -783,107 +682,6 @@ export default function CompanyProfileForm({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{step === "verify" && (
|
|
||||||
<Stack gap="md">
|
|
||||||
<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.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{!contactPhoneE164 ? (
|
|
||||||
<Alert
|
|
||||||
color="yellow"
|
|
||||||
variant="light"
|
|
||||||
icon={<AlertCircle size={18} />}
|
|
||||||
>
|
|
||||||
Add a valid contact phone number on the previous step first.
|
|
||||||
</Alert>
|
|
||||||
) : phoneVerified ? (
|
|
||||||
<Alert
|
|
||||||
color="edr-green"
|
|
||||||
variant="light"
|
|
||||||
icon={<CheckCircle2 size={18} />}
|
|
||||||
title="Phone verified"
|
|
||||||
>
|
|
||||||
{maskPhone(contactPhoneE164)} has been verified.
|
|
||||||
</Alert>
|
|
||||||
) : (
|
|
||||||
<Stack gap="sm">
|
|
||||||
<Group gap="xs" align="center">
|
|
||||||
<Smartphone
|
|
||||||
size={16}
|
|
||||||
className="text-[var(--mantine-color-edr-muted)]"
|
|
||||||
/>
|
|
||||||
<Text size="sm" c="edr-text">
|
|
||||||
{maskPhone(contactPhoneE164)}
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
{!otpSent ? (
|
|
||||||
<Button
|
|
||||||
color="edr-green"
|
|
||||||
variant="light"
|
|
||||||
onClick={sendContactOtp}
|
|
||||||
loading={sendingOtp}
|
|
||||||
leftSection={<Smartphone size={16} />}
|
|
||||||
style={{ alignSelf: "flex-start" }}
|
|
||||||
>
|
|
||||||
Send code via SMS
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Stack gap="sm">
|
|
||||||
<PinInput
|
|
||||||
length={6}
|
|
||||||
type="number"
|
|
||||||
oneTimeCode
|
|
||||||
value={otpCode}
|
|
||||||
placeholder="0"
|
|
||||||
styles={{
|
|
||||||
input: {
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onChange={setOtpCode}
|
|
||||||
/>
|
|
||||||
<Group gap="sm">
|
|
||||||
<Button
|
|
||||||
color="edr-green"
|
|
||||||
onClick={verifyContactOtp}
|
|
||||||
loading={verifyingOtp}
|
|
||||||
disabled={otpCode.length !== 6}
|
|
||||||
>
|
|
||||||
Verify
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="subtle"
|
|
||||||
color="edr-green"
|
|
||||||
onClick={sendContactOtp}
|
|
||||||
loading={sendingOtp}
|
|
||||||
disabled={resendIn > 0 || sendingOtp}
|
|
||||||
leftSection={<RotateCw size={14} />}
|
|
||||||
>
|
|
||||||
{resendIn > 0
|
|
||||||
? `Resend in ${resendIn}s`
|
|
||||||
: "Resend code"}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{otpError && (
|
|
||||||
<Alert
|
|
||||||
color="red"
|
|
||||||
variant="light"
|
|
||||||
icon={<AlertCircle size={18} />}
|
|
||||||
>
|
|
||||||
{otpError}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{step === "poa" && (
|
{step === "poa" && (
|
||||||
<>
|
<>
|
||||||
<Text size="sm" c="edr-muted">
|
<Text size="sm" c="edr-muted">
|
||||||
@@ -1009,8 +807,7 @@ export default function CompanyProfileForm({
|
|||||||
disabled={
|
disabled={
|
||||||
isPending ||
|
isPending ||
|
||||||
saving ||
|
saving ||
|
||||||
(step === "documents" && !hasDocuments && loadingDocuments) ||
|
(step === "documents" && !hasDocuments && loadingDocuments)
|
||||||
(step === "verify" && !phoneVerified)
|
|
||||||
}
|
}
|
||||||
loading={isPending || saving}
|
loading={isPending || saving}
|
||||||
rightSection={
|
rightSection={
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export type CompanyStep =
|
|||||||
| "company"
|
| "company"
|
||||||
| "personnel"
|
| "personnel"
|
||||||
| "contact"
|
| "contact"
|
||||||
| "verify"
|
|
||||||
| "poa"
|
| "poa"
|
||||||
| "documents"
|
| "documents"
|
||||||
| "additional";
|
| "additional";
|
||||||
@@ -103,7 +102,6 @@ export const stepFields: Record<CompanyStep, (keyof FormData)[]> = {
|
|||||||
"contactPersonEmail",
|
"contactPersonEmail",
|
||||||
"contactPersonPhone",
|
"contactPersonPhone",
|
||||||
],
|
],
|
||||||
verify: [],
|
|
||||||
poa: [],
|
poa: [],
|
||||||
documents: [],
|
documents: [],
|
||||||
additional: [],
|
additional: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user