Files
car_mms/app/routes/signin.tsx
2026-01-23 20:35:40 +03:00

282 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, Link, useActionData, useLoaderData, useNavigation } from "@remix-run/react";
import { validateSignIn } from "~/lib/auth-helpers.server";
import { createUserSession, getUserId } from "~/lib/auth.server";
import { AUTH_ERRORS } from "~/lib/auth-constants";
import type { SignInFormData } from "~/types/auth";
export const meta: MetaFunction = () => {
return [
{ title: "تسجيل الدخول - نظام إدارة صيانة السيارات" },
{ name: "description", content: "تسجيل الدخول إلى نظام إدارة صيانة السيارات" },
];
};
export async function loader({ request }: LoaderFunctionArgs) {
// Import the redirect middleware
const { redirectIfAuthenticated } = await import("~/lib/auth-middleware.server");
await redirectIfAuthenticated(request);
const url = new URL(request.url);
const redirectTo = url.searchParams.get("redirectTo") || "/dashboard";
const error = url.searchParams.get("error");
return json({ redirectTo, error });
}
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const usernameOrEmail = formData.get("usernameOrEmail");
const password = formData.get("password");
const redirectTo = formData.get("redirectTo") || "/dashboard";
// Validate form data
if (
typeof usernameOrEmail !== "string" ||
typeof password !== "string" ||
typeof redirectTo !== "string"
) {
return json(
{
errors: [{ message: "بيانات النموذج غير صحيحة" }],
values: { usernameOrEmail: usernameOrEmail || "" }
},
{ status: 400 }
);
}
const signInData: SignInFormData = {
usernameOrEmail: usernameOrEmail.trim(),
password,
redirectTo,
};
// Validate credentials
const result = await validateSignIn(signInData);
if (!result.success) {
return json(
{
errors: result.errors || [{ message: AUTH_ERRORS.INVALID_CREDENTIALS }],
values: { usernameOrEmail: signInData.usernameOrEmail }
},
{ status: 400 }
);
}
if (!result.user) {
return json(
{
errors: [{ message: AUTH_ERRORS.INVALID_CREDENTIALS }],
values: { usernameOrEmail: signInData.usernameOrEmail }
},
{ status: 400 }
);
}
// Create session and redirect
return createUserSession(result.user.id, redirectTo);
}
export default function SignIn() {
const { redirectTo, error } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
const getErrorMessage = (field?: string) => {
if (!actionData?.errors) return null;
const error = actionData.errors.find(e => e.field === field || !e.field);
return error?.message;
};
const getErrorForUrl = (errorParam: string | null) => {
switch (errorParam) {
case "account_inactive":
return AUTH_ERRORS.ACCOUNT_INACTIVE;
case "session_expired":
return AUTH_ERRORS.SESSION_EXPIRED;
default:
return null;
}
};
const urlError = getErrorForUrl(error);
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8" dir="rtl">
<div className="max-w-md w-full space-y-8">
<div>
<div className="mx-auto h-12 w-12 flex items-center justify-center rounded-full bg-blue-100">
<svg
className="h-6 w-6 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
</div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
تسجيل الدخول
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
أو{" "}
<Link
to="/signup"
className="font-medium text-blue-600 hover:text-blue-500"
>
إنشاء حساب جديد
</Link>
</p>
</div>
<Form className="mt-8 space-y-6" method="post">
<input type="hidden" name="redirectTo" value={redirectTo} />
{/* Display URL error */}
{urlError && (
<div className="rounded-md bg-red-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<svg
className="h-5 w-5 text-red-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="mr-3">
<p className="text-sm text-red-800">{urlError}</p>
</div>
</div>
</div>
)}
{/* Display form errors */}
{getErrorMessage() && (
<div className="rounded-md bg-red-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<svg
className="h-5 w-5 text-red-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="mr-3">
<p className="text-sm text-red-800">{getErrorMessage()}</p>
</div>
</div>
</div>
)}
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="usernameOrEmail" className="sr-only">
اسم المستخدم أو البريد الإلكتروني
</label>
<input
id="usernameOrEmail"
name="usernameOrEmail"
type="text"
autoComplete="username"
required
className={`appearance-none rounded-none relative block w-full px-3 py-2 border ${
getErrorMessage("usernameOrEmail")
? "border-red-300 text-red-900 placeholder-red-300 focus:outline-none focus:ring-red-500 focus:border-red-500"
: "border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-blue-500 focus:border-blue-500"
} rounded-t-md focus:z-10 sm:text-sm`}
placeholder="اسم المستخدم أو البريد الإلكتروني"
defaultValue={actionData?.values?.usernameOrEmail}
dir="ltr"
/>
</div>
<div>
<label htmlFor="password" className="sr-only">
كلمة المرور
</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className={`appearance-none rounded-none relative block w-full px-3 py-2 border ${
getErrorMessage("password")
? "border-red-300 text-red-900 placeholder-red-300 focus:outline-none focus:ring-red-500 focus:border-red-500"
: "border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-blue-500 focus:border-blue-500"
} rounded-b-md focus:z-10 sm:text-sm`}
placeholder="كلمة المرور"
dir="ltr"
/>
</div>
</div>
<div>
<button
type="submit"
disabled={isSubmitting}
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
<span className="absolute right-0 inset-y-0 flex items-center pr-3">
{isSubmitting ? (
<svg
className="animate-spin h-5 w-5 text-blue-300"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
) : (
<svg
className="h-5 w-5 text-blue-500 group-hover:text-blue-400"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clipRule="evenodd"
/>
</svg>
)}
</span>
{isSubmitting ? "جاري تسجيل الدخول..." : "تسجيل الدخول"}
</button>
</div>
</Form>
</div>
</div>
);
}