259 lines
8.9 KiB
JavaScript
259 lines
8.9 KiB
JavaScript
import { o as __toESM } from "../_runtime.mjs";
|
|
import { h as require_react, m as require_jsx_runtime } from "../_libs/react+tanstack__react-query.mjs";
|
|
import { a as p, i as MyButton, o as pInput, s as MyTouchableOpacity } from "./src-u_N1opJl.mjs";
|
|
import { n as trpc } from "./trpc-client-CQOIB5UU.mjs";
|
|
import { l as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
|
|
import { n as useAuth } from "./auth-context-DzjwonUC.mjs";
|
|
import { n as useForm, t as Controller } from "../_libs/react-hook-form.mjs";
|
|
//#region node_modules/.nitro/vite/services/ssr/assets/login-QH2hpwxi.js
|
|
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
var import_jsx_runtime = require_jsx_runtime();
|
|
function LoginPage() {
|
|
const { loginWithToken } = useAuth();
|
|
const navigate = useNavigate();
|
|
const [step, setStep] = (0, import_react.useState)("mobile");
|
|
const [selectedMobile, setSelectedMobile] = (0, import_react.useState)("");
|
|
const [canResend, setCanResend] = (0, import_react.useState)(false);
|
|
const [resendCountdown, setResendCountdown] = (0, import_react.useState)(0);
|
|
const intervalRef = (0, import_react.useRef)();
|
|
const [otpCells, setOtpCells] = (0, import_react.useState)([
|
|
"",
|
|
"",
|
|
"",
|
|
""
|
|
]);
|
|
const inputRefs = (0, import_react.useRef)([
|
|
null,
|
|
null,
|
|
null,
|
|
null
|
|
]);
|
|
const loginMutation = trpc.user.auth.login.useMutation();
|
|
const sendOtpMutation = trpc.user.auth.sendOtp.useMutation({ onSuccess: (data) => {
|
|
if (data.success) {
|
|
setResendCountdown(120);
|
|
setCanResend(false);
|
|
setStep("otp");
|
|
}
|
|
} });
|
|
const verifyOtpMutation = trpc.user.auth.verifyOtp.useMutation({ onSuccess: (data) => {
|
|
if (data.success && data.token && data.user) {
|
|
loginWithToken(data.token, data.user);
|
|
navigate({ to: "/home" });
|
|
}
|
|
} });
|
|
(0, import_react.useEffect)(() => {
|
|
return () => {
|
|
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
};
|
|
}, []);
|
|
(0, import_react.useEffect)(() => {
|
|
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
if (resendCountdown > 0) intervalRef.current = setInterval(() => {
|
|
setResendCountdown((prev) => {
|
|
if (prev <= 1) {
|
|
setCanResend(true);
|
|
return 0;
|
|
}
|
|
return prev - 1;
|
|
});
|
|
}, 1e3);
|
|
return () => {
|
|
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
};
|
|
}, [resendCountdown]);
|
|
const { control, handleSubmit, setValue, clearErrors, setError } = useForm({ defaultValues: {
|
|
mobile: "",
|
|
otp: "",
|
|
password: ""
|
|
} });
|
|
const validateMobile = (mobile) => {
|
|
const clean = mobile.replace(/\D/g, "");
|
|
return clean.length === 10 && /^[6-9]/.test(clean);
|
|
};
|
|
const handleOtpChange = (index, text) => {
|
|
if (text.length > 1) {
|
|
const digits = text.replace(/\D/g, "").slice(0, 4);
|
|
const newCells = digits.split("").concat([
|
|
"",
|
|
"",
|
|
"",
|
|
""
|
|
]).slice(0, 4);
|
|
setOtpCells(newCells);
|
|
setValue("otp", newCells.join(""));
|
|
const lastIndex = Math.min(digits.length - 1, 3);
|
|
inputRefs.current[lastIndex]?.focus();
|
|
return;
|
|
}
|
|
const newCells = [...otpCells];
|
|
newCells[index] = text;
|
|
setOtpCells(newCells);
|
|
setValue("otp", newCells.join(""));
|
|
if (text && index < 3) inputRefs.current[index + 1]?.focus();
|
|
else if (!text && index > 0) inputRefs.current[index - 1]?.focus();
|
|
};
|
|
const onSubmit = (data) => {
|
|
if (step === "mobile") {
|
|
const mobile = data.mobile.trim();
|
|
if (!validateMobile(mobile)) {
|
|
setError("mobile", { message: "Enter a valid 10-digit mobile number" });
|
|
return;
|
|
}
|
|
setSelectedMobile(mobile.replace(/\D/g, ""));
|
|
sendOtpMutation.mutate({ mobile });
|
|
} else if (step === "otp") {
|
|
if (!data.otp || data.otp.length < 4) {
|
|
setError("otp", { message: "Enter a valid OTP" });
|
|
return;
|
|
}
|
|
verifyOtpMutation.mutate({
|
|
mobile: selectedMobile,
|
|
otp: data.otp
|
|
});
|
|
} else if (step === "password") loginMutation.mutate({
|
|
identifier: selectedMobile,
|
|
password: data.password
|
|
}, { onSuccess: (res) => {
|
|
loginWithToken(res.data.token, res.data.user);
|
|
navigate({ to: "/home" });
|
|
} });
|
|
};
|
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
className: "flex min-h-screen items-center justify-center bg-gradient-to-b from-brand-400 to-brand-700 p-4",
|
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
className: "w-full max-w-md",
|
|
children: [
|
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
|
|
weight: "bold",
|
|
className: "mb-2 text-center text-4xl text-white",
|
|
children: "Welcome"
|
|
}),
|
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
|
|
className: "mb-8 text-center text-lg text-blue-100",
|
|
children: "Sign in to continue your journey"
|
|
}),
|
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
className: "rounded-2xl bg-white p-8 shadow-xl",
|
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
onSubmit: handleSubmit(onSubmit),
|
|
children: [
|
|
step === "mobile" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Controller, {
|
|
control,
|
|
name: "mobile",
|
|
render: ({ field: { onChange, value } }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
|
|
placeholder: "Enter your mobile number",
|
|
value,
|
|
onChange: (e) => {
|
|
const clean = e.target.value.replace(/\D/g, "");
|
|
if (clean.length <= 10) onChange(clean);
|
|
},
|
|
className: "bg-gray-50"
|
|
})
|
|
}),
|
|
step === "otp" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
className: "mb-6",
|
|
children: [
|
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
|
|
weight: "semibold",
|
|
className: "mb-3 text-center text-base text-gray-800",
|
|
children: "Enter 4-digit OTP"
|
|
}),
|
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
className: "flex justify-center gap-2",
|
|
children: [
|
|
0,
|
|
1,
|
|
2,
|
|
3
|
|
].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
ref: (el) => {
|
|
inputRefs.current[i] = el;
|
|
},
|
|
className: "h-14 w-14 rounded-xl border-2 text-center text-2xl font-bold",
|
|
style: {
|
|
borderColor: otpCells[i] ? "#E63946" : "#E5E7EB",
|
|
backgroundColor: otpCells[i] ? "#FFF5F6" : "#F9FAFB"
|
|
},
|
|
type: "text",
|
|
inputMode: "numeric",
|
|
maxLength: 1,
|
|
value: otpCells[i],
|
|
onChange: (e) => handleOtpChange(i, e.target.value)
|
|
}, i))
|
|
}),
|
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
className: "mt-4 flex items-center justify-between border-t border-gray-100 pt-4",
|
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTouchableOpacity, {
|
|
onClick: () => {
|
|
setStep("choice");
|
|
setOtpCells([
|
|
"",
|
|
"",
|
|
"",
|
|
""
|
|
]);
|
|
},
|
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
|
|
weight: "medium",
|
|
className: "text-gray-500",
|
|
children: "Back"
|
|
})
|
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTouchableOpacity, {
|
|
onClick: () => sendOtpMutation.mutate({ mobile: selectedMobile }),
|
|
disabled: !canResend,
|
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
|
|
weight: "semibold",
|
|
className: canResend ? "text-brand-600" : "text-gray-400",
|
|
children: canResend ? "Resend OTP" : `Resend in ${resendCountdown}s`
|
|
})
|
|
})]
|
|
})
|
|
]
|
|
}),
|
|
step === "password" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Controller, {
|
|
control,
|
|
name: "password",
|
|
render: ({ field: { onChange, value } }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(pInput, {
|
|
placeholder: "Enter your password",
|
|
value,
|
|
onChange: (e) => onChange(e.target.value),
|
|
type: "password",
|
|
className: "bg-gray-50"
|
|
})
|
|
}),
|
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
className: "mt-6",
|
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyButton, {
|
|
type: "submit",
|
|
fullWidth: true,
|
|
className: "h-12 rounded-xl bg-brand-600 text-white shadow-lg",
|
|
disabled: sendOtpMutation.isPending || verifyOtpMutation.isPending || loginMutation.isPending,
|
|
textContent: step === "otp" ? "Verify & Login" : step === "password" ? "Login" : "Continue"
|
|
})
|
|
})
|
|
]
|
|
}), step === "otp" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MyTouchableOpacity, {
|
|
onClick: () => {
|
|
setStep("password");
|
|
setOtpCells([
|
|
"",
|
|
"",
|
|
"",
|
|
""
|
|
]);
|
|
},
|
|
className: "mt-4 block text-center",
|
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
|
|
weight: "semibold",
|
|
className: "text-brand-600",
|
|
children: "Or login with Password"
|
|
})
|
|
})]
|
|
})
|
|
]
|
|
})
|
|
});
|
|
}
|
|
//#endregion
|
|
export { LoginPage as component };
|