mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
chore: rm the verify step in onboarding
This commit is contained in:
@@ -46,7 +46,6 @@ type FormStep =
|
||||
| "company"
|
||||
| "personnel"
|
||||
| "contact"
|
||||
| "verify"
|
||||
| "poa"
|
||||
| "documents"
|
||||
| "additional";
|
||||
@@ -54,7 +53,6 @@ const FORM_STEPS: FormStep[] = [
|
||||
"company",
|
||||
"personnel",
|
||||
"contact",
|
||||
"verify",
|
||||
"poa",
|
||||
"documents",
|
||||
"additional",
|
||||
@@ -95,11 +93,6 @@ const STEP_META: Record<
|
||||
title: "Contact Person",
|
||||
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: {
|
||||
icon: <FileText size={20} />,
|
||||
title: "Power of Attorney",
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Divider,
|
||||
Group,
|
||||
Loader,
|
||||
PinInput,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
@@ -16,9 +15,6 @@ import {
|
||||
AlertCircle,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
CheckCircle2,
|
||||
RotateCw,
|
||||
Smartphone,
|
||||
UserCheck,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@@ -35,7 +31,6 @@ import RoleLicenseStep, {
|
||||
type RoleLicenseProfile,
|
||||
} from "@/components/onboarding/RoleLicenseStep";
|
||||
import ETradeInfo from "@/components/onboarding/ETradeInfo";
|
||||
import { extractApiError } from "@/utils/result";
|
||||
import {
|
||||
type CompanyStep,
|
||||
type FormData,
|
||||
@@ -44,8 +39,6 @@ import {
|
||||
} from "./companyProfileForm/schema";
|
||||
import {
|
||||
buildPayload,
|
||||
maskPhone,
|
||||
samePhone,
|
||||
stepPayload,
|
||||
toFormValues,
|
||||
} 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);
|
||||
|
||||
// The registration/license details come straight from the eTrade lookup and
|
||||
@@ -451,7 +365,6 @@ export default function CompanyProfileForm({
|
||||
"company",
|
||||
"personnel",
|
||||
"contact",
|
||||
"verify",
|
||||
"poa",
|
||||
"documents",
|
||||
"additional",
|
||||
@@ -495,20 +408,6 @@ export default function CompanyProfileForm({
|
||||
handleSubmit((data) => onSubmit(buildPayload(data, user)))();
|
||||
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
|
||||
// continue (partial uploads are allowed — required-doc completeness is
|
||||
// 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" && (
|
||||
<>
|
||||
<Text size="sm" c="edr-muted">
|
||||
@@ -1009,8 +807,7 @@ export default function CompanyProfileForm({
|
||||
disabled={
|
||||
isPending ||
|
||||
saving ||
|
||||
(step === "documents" && !hasDocuments && loadingDocuments) ||
|
||||
(step === "verify" && !phoneVerified)
|
||||
(step === "documents" && !hasDocuments && loadingDocuments)
|
||||
}
|
||||
loading={isPending || saving}
|
||||
rightSection={
|
||||
|
||||
@@ -6,7 +6,6 @@ export type CompanyStep =
|
||||
| "company"
|
||||
| "personnel"
|
||||
| "contact"
|
||||
| "verify"
|
||||
| "poa"
|
||||
| "documents"
|
||||
| "additional";
|
||||
@@ -103,7 +102,6 @@ export const stepFields: Record<CompanyStep, (keyof FormData)[]> = {
|
||||
"contactPersonEmail",
|
||||
"contactPersonPhone",
|
||||
],
|
||||
verify: [],
|
||||
poa: [],
|
||||
documents: [],
|
||||
additional: [],
|
||||
|
||||
Reference in New Issue
Block a user