import { authenticator } from "~/auth.server"; import { ActionFunctionArgs, json, redirect, LoaderFunctionArgs, MetaFunction, } from "@remix-run/node"; import { sessionStorage } from "~/sessions.server"; import { useServiceSendOtp, useServiceVerifyOtp } from "~/utils/RequestHandler"; import { useEffect, useState } from "react"; import verifyBanner from "app/assets/icons/login/verify-banner.svg"; import { Button } from "~/components/ui/button"; import { ArrowLeft, Loader2, Phone, RotateCcw } from "lucide-react"; import { InputOTP, InputOTPGroup, InputOTPSlot, } from "~/components/ui/input-otp"; import AppInput from "~/components/AppInput"; import { useToast } from "~/hooks/use-toast"; import { useSubmit, useLoaderData } from "@remix-run/react"; import { User } from "~/utils/token-manager.server"; // Only allow same-site, path-relative redirects. Rejects absolute URLs // (https://evil.com) and protocol-relative URLs (//evil.com) to prevent // open-redirect / phishing after login. function safeRedirect( to: FormDataEntryValue | string | null, defaultRedirect = "/" ): string { if (!to || typeof to !== "string") return defaultRedirect; if (!to.startsWith("/") || to.startsWith("//") || to.startsWith("/\\")) { return defaultRedirect; } return to; } export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const redirectTo = safeRedirect(url.searchParams.get("redirectTo")); return json({ redirectTo }); } // Helper function to convert Persian numbers to English const convertPersianToEnglish = (str: string): string => { const persianNumbers = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"]; const englishNumbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; let result = str; for (let i = 0; i < 10; i++) { result = result.replace( new RegExp(persianNumbers[i], "g"), englishNumbers[i] ); } return result; }; export default function LoginPage() { const { redirectTo } = useLoaderData(); const { toast } = useToast(); const [phoneNumber, setPhoneNumber] = useState(""); const [otpValue, setOtpValue] = useState(""); const [enableVerifySection, setEnableVerifySection] = useState(false); const sendOtpMutation = useServiceSendOtp( phoneNumber, setEnableVerifySection ); const verifyOtpMutation = useServiceVerifyOtp(phoneNumber, otpValue, toast); const submit = useSubmit(); useEffect(() => { if ( verifyOtpMutation.data && verifyOtpMutation.data.accessToken && verifyOtpMutation.data.refreshToken ) { console.log("redirectTo", redirectTo); submit( { phoneNumber: phoneNumber, access_token: JSON.stringify(verifyOtpMutation.data.accessToken), refresh_token: JSON.stringify(verifyOtpMutation.data.refreshToken), redirectTo: redirectTo, }, { action: "/login", method: "post", encType: "application/x-www-form-urlencoded", } ); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [verifyOtpMutation.data]); const [canResend, setCanResend] = useState(false); useEffect(() => { if (enableVerifySection) setCanResend(false); }, [enableVerifySection]); const handleTimerEnd = () => { setCanResend(true); }; return (
{enableVerifySection ? ( <>

کد تایید شماره موبایل

کد ارسال شده به {phoneNumber} را وارد کنید:

{ const convertedValue = convertPersianToEnglish(finalValue); setOtpValue(convertedValue); }} value={otpValue} onChange={(value) => { const convertedValue = convertPersianToEnglish(value); setOtpValue(convertedValue); }} >
{!canResend ? ( ) : ( )}
) : ( <>

به ویترون خوش آمدید!

برای ادامه اطلاعات زیر را وارد کنید

) => { const value = e.target.value; // Convert Persian numbers to English const convertedValue = convertPersianToEnglish(value); // Only allow English digits if (/^\d*$/.test(convertedValue)) { setPhoneNumber(convertedValue); } }} />
)}
); } const ResendTimer = ({ initialTime = 120, onTimerEnd, }: { initialTime?: number; onTimerEnd?: () => void; }) => { const [timeLeft, setTimeLeft] = useState(initialTime); useEffect(() => { if (timeLeft <= 0) { if (onTimerEnd) onTimerEnd(); return; } const timer = setInterval(() => { setTimeLeft((prev) => prev - 1); }, 1000); return () => clearInterval(timer); // پاک کردن تایمر هنگام خروج از کامپوننت }, [timeLeft, onTimerEnd]); const formatTime = (seconds: number) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds < 10 ? `0${remainingSeconds}` : remainingSeconds}`; }; return (

{formatTime(timeLeft)}  تا ارسال مجدد کد

); }; export const meta: MetaFunction = () => { const title = "ورود و ثبت نام | ویترون"; const description = "ورود یا ثبت نام در ویترون - پلتفرم خرید آنلاین. دسترسی آسان و سریع به حساب کاربری خود برای تجربه خرید بهتر."; const imageUrl = "https://vitrownstatics.s3.ir-thr-at1.arvanstorage.ir/SVG-06.svg?versionId="; return [ { title }, { name: "description", content: description }, { name: "keywords", content: "ورود ویترون، ثبت نام، حساب کاربری، لاگین" }, { name: "robots", content: "noindex, follow" }, // Open Graph { property: "og:type", content: "website" }, { property: "og:title", content: title }, { property: "og:description", content: description }, { property: "og:image", content: imageUrl }, { property: "og:site_name", content: "ویترون" }, { property: "og:locale", content: "fa_IR" }, // Twitter Card { name: "twitter:card", content: "summary" }, { name: "twitter:title", content: title }, { name: "twitter:description", content: description }, ]; }; export async function action({ request }: ActionFunctionArgs) { const clonedRequest = request.clone(); const formData = await clonedRequest.formData(); const redirectTo = safeRedirect(formData.get("redirectTo")); try { const user = (await authenticator.authenticate( "vitrown-login", request )) as User; if (!user.access_token) { return redirect("/login"); } const session = await sessionStorage.getSession( request.headers.get("cookie") ); const currentTime = Date.now(); // Store both tokens with their timestamps session.set("user", { access_token: user.access_token, refresh_token: user.refresh_token, access_token_timestamp: currentTime, refresh_token_timestamp: currentTime, }); return redirect(redirectTo, { headers: { "Set-Cookie": await sessionStorage.commitSession(session, { expires: new Date(new Date().setMonth(new Date().getMonth() + 1)), }), }, }); } catch (error) { console.log("error", error); return json({ error: "Authentication failed" }, { status: 401 }); } }