api registration done
This commit is contained in:
1 parent
fd0a327b13
commit
653055debc
38 files changed
+3411
-1385
No files matched your search
@@ -35,7 +35,7 @@ import { useSendOtp, useVerifyOtp } from "../hooks/useAuth";
|
||||
const OTP_LENGTH = 4;
|
||||
const OTP_TIMER_SEC = 120; // 2 minutes
|
||||
|
||||
const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.personalDetails);
|
||||
const nameInputRef = useRef(null);
|
||||
@@ -141,6 +141,52 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
setOtpTimer(OTP_TIMER_SEC);
|
||||
}, []);
|
||||
|
||||
const getApiErrorMessage = useCallback((error, fallback) => {
|
||||
const data = error?.response?.data ?? error?.data ?? error;
|
||||
if (!data) return fallback;
|
||||
if (typeof data === "string") return data;
|
||||
|
||||
const directMessage =
|
||||
data.message || data.error || data.detail || data.msg;
|
||||
if (directMessage) return directMessage;
|
||||
|
||||
if (Array.isArray(data.errors)) {
|
||||
const first = data.errors[0];
|
||||
if (typeof first === "string") return first;
|
||||
if (first && typeof first === "object") {
|
||||
return (
|
||||
first.message ||
|
||||
first.msg ||
|
||||
first.error ||
|
||||
first.detail ||
|
||||
fallback
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.errors && typeof data.errors === "object") {
|
||||
const firstValue = Object.values(data.errors)[0];
|
||||
if (Array.isArray(firstValue)) {
|
||||
const joined = firstValue.filter(Boolean).join(" ");
|
||||
if (joined) return joined;
|
||||
}
|
||||
if (typeof firstValue === "string") return firstValue;
|
||||
if (firstValue && typeof firstValue === "object") {
|
||||
return (
|
||||
firstValue.message ||
|
||||
firstValue.msg ||
|
||||
firstValue.error ||
|
||||
firstValue.detail ||
|
||||
fallback
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.otp) return String(data.otp);
|
||||
if (error?.message) return error.message;
|
||||
return fallback;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (otpTimer <= 0) return;
|
||||
const timerId = setInterval(() => {
|
||||
@@ -207,18 +253,18 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
resetOtp();
|
||||
setMobileOtpVerified(false);
|
||||
} catch (error) {
|
||||
setMobileNumberError("Failed to send OTP. Please try again.");
|
||||
const message =
|
||||
error?.response?.data?.message ||
|
||||
error?.response?.data?.otp ||
|
||||
error?.message ||
|
||||
"Failed to send OTP";
|
||||
const message = getApiErrorMessage(
|
||||
error,
|
||||
"Failed to send OTP. Please try again."
|
||||
);
|
||||
setMobileNumberError(message);
|
||||
toast.error(message, { position: "top-right" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
const updates = { [field]: value };
|
||||
const fieldsToClear = [field];
|
||||
if (field === "mobileNumber") {
|
||||
setMobileNumberError("");
|
||||
setShowOtp(false);
|
||||
@@ -230,21 +276,61 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
if (field === "religion") {
|
||||
updates.caste = "";
|
||||
updates.subCaste = "";
|
||||
fieldsToClear.push("caste", "subCaste");
|
||||
}
|
||||
if (field === "caste") {
|
||||
updates.subCaste = "";
|
||||
fieldsToClear.push("subCaste");
|
||||
}
|
||||
if (field === "state") {
|
||||
updates.city = "";
|
||||
fieldsToClear.push("city");
|
||||
}
|
||||
if (field === "raasi") {
|
||||
updates.star = "";
|
||||
fieldsToClear.push("star");
|
||||
}
|
||||
if (field === "password") {
|
||||
fieldsToClear.push("confirmPassword");
|
||||
}
|
||||
dispatch(updatePersonalDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const isOtpComplete = otp.every((digit) => digit !== "");
|
||||
|
||||
const passwordStrength = useMemo(() => {
|
||||
const value = data.password || "";
|
||||
if (!value) return null;
|
||||
const rules = [
|
||||
{ key: "length", label: "At least 8 characters", ok: value.length >= 8 },
|
||||
{ key: "upper", label: "Uppercase letter", ok: /[A-Z]/.test(value) },
|
||||
{ key: "lower", label: "Lowercase letter", ok: /[a-z]/.test(value) },
|
||||
{ key: "number", label: "Number", ok: /\d/.test(value) },
|
||||
{ key: "symbol", label: "Symbol", ok: /[^A-Za-z0-9]/.test(value) },
|
||||
{ key: "length12", label: "12+ characters (recommended)", ok: value.length >= 12 },
|
||||
];
|
||||
const score = rules.filter((rule) => rule.ok).length;
|
||||
const percent = Math.round((score / rules.length) * 100);
|
||||
|
||||
let label = "Weak";
|
||||
let color = "#d32f2f";
|
||||
if (score >= 5) {
|
||||
label = "Strong";
|
||||
color = "#2e7d32";
|
||||
} else if (score >= 3) {
|
||||
label = "Medium";
|
||||
color = "#ed6c02";
|
||||
}
|
||||
|
||||
return {
|
||||
label,
|
||||
color,
|
||||
percent,
|
||||
rules,
|
||||
};
|
||||
}, [data.password]);
|
||||
|
||||
const handleOtpSubmit = async () => {
|
||||
if (!isOtpComplete) {
|
||||
setOtpError("Complete OTP is required");
|
||||
@@ -265,11 +351,10 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
setMobileNumberError("");
|
||||
setOtpError("");
|
||||
} catch (error) {
|
||||
const message =
|
||||
error?.response?.data?.message ||
|
||||
error?.response?.data?.otp ||
|
||||
error?.message ||
|
||||
"Invalid or expired OTP";
|
||||
const message = getApiErrorMessage(
|
||||
error,
|
||||
"Invalid or expired OTP"
|
||||
);
|
||||
setOtpError(message);
|
||||
toast.error(message, { position: "top-right" });
|
||||
}
|
||||
@@ -576,6 +661,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
}}
|
||||
slotProps={{
|
||||
textField: {
|
||||
name: "dob",
|
||||
fullWidth: true,
|
||||
error: Boolean(errors.dob),
|
||||
helperText: errors.dob,
|
||||
@@ -954,6 +1040,64 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
{passwordStrength && (
|
||||
<div className="rounded-lg border border-gray-200 bg-white/70 p-3">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-600">Password strength</span>
|
||||
<span
|
||||
style={{
|
||||
color: passwordStrength.color,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{passwordStrength.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 h-2 w-full rounded-full bg-gray-200 overflow-hidden">
|
||||
<div
|
||||
style={{
|
||||
width: `${passwordStrength.percent}%`,
|
||||
height: "100%",
|
||||
background: passwordStrength.color,
|
||||
transition: "width 200ms ease",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-gray-500">
|
||||
Recommended: 12+ characters with a mix of upper/lowercase,
|
||||
numbers, and symbols.
|
||||
</div>
|
||||
<div className="mt-2 grid grid-cols-1 sm:grid-cols-2 gap-y-1 gap-x-3 text-xs">
|
||||
{passwordStrength.rules.map((rule) => (
|
||||
<div
|
||||
key={rule.key}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{rule.ok ? (
|
||||
<CheckCircleIcon sx={{ fontSize: 14, color: "#2e7d32" }} />
|
||||
) : (
|
||||
<span
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: "999px",
|
||||
border: "1px solid #d1d5db",
|
||||
display: "inline-block",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
color: rule.ok ? "#111827" : "#6b7280",
|
||||
}}
|
||||
>
|
||||
{rule.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Confirm Password */}
|
||||
|
||||
Reference in new issue
Block a user