phosphat-report-app/app/routes/signup.tsx
2025-08-01 05:00:14 +03:00

215 lines
8.2 KiB
TypeScript

import type { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, Link, useActionData } from "@remix-run/react";
import { createUser, createUserSession, getUserId } from "~/utils/auth.server";
import { prisma } from "~/utils/db.server";
export const meta: MetaFunction = () => [{ title: "Sign Up - Phosphat Report" }];
export const loader = async ({ request }: LoaderFunctionArgs) => {
const userId = await getUserId(request);
if (userId) return redirect("/dashboard");
// For first time signup only
// //////////////////////////
const users = await prisma.employee.findMany({
where: { authLevel: 2 },
select: { name: true, username: true, email: true, authLevel: true },
});
if (users.length > 0) {
return redirect("/signin");
}
// //////////////////////////
return json({});
};
export const action = async ({ request }: ActionFunctionArgs) => {
const formData = await request.formData();
const name = formData.get("name");
const username = formData.get("username");
const email = formData.get("email");
const password = formData.get("password");
const confirmPassword = formData.get("confirmPassword");
if (
typeof name !== "string" ||
typeof username !== "string" ||
typeof email !== "string" ||
typeof password !== "string" ||
typeof confirmPassword !== "string"
) {
return json({ errors: { form: "Form not submitted correctly." } }, { status: 400 });
}
if (name.length === 0) {
return json({ errors: { name: "Name is required" } }, { status: 400 });
}
if (username.length === 0) {
return json({ errors: { username: "Username is required" } }, { status: 400 });
}
if (email.length === 0) {
return json({ errors: { email: "Email is required" } }, { status: 400 });
}
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return json({ errors: { email: "Please enter a valid email address" } }, { status: 400 });
}
if (password.length < 6) {
return json({ errors: { password: "Password must be at least 6 characters" } }, { status: 400 });
}
if (password !== confirmPassword) {
return json({ errors: { confirmPassword: "Passwords do not match" } }, { status: 400 });
}
try {
const user = await createUser(name, username, email, password, 2); // Default auth level 2 (for admin)
return createUserSession(user.id, "/dashboard");
} catch (error) {
return json({ errors: { form: "Username or email already exists" } }, { status: 400 });
}
};
export default function SignUp() {
const actionData = useActionData<typeof action>();
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center py-8 px-4 sm:py-12 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-6 sm:space-y-8">
<div className="text-center">
<img
className="mx-auto h-20 sm:h-24 w-auto"
src="/clogo-sm.png"
alt="Phosphat Report"
/>
<h2 className="mt-4 sm:mt-6 text-2xl sm:text-3xl font-extrabold text-gray-900">
Create your account
</h2>
<p className="mt-2 text-sm text-gray-600">
Or{" "}
<Link
to="/signin"
className="font-medium text-indigo-600 hover:text-indigo-500"
>
sign in to existing account
</Link>
</p>
</div>
<Form method="post" className="mt-8 space-y-6">
<div className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
Full Name
</label>
<input
id="name"
name="name"
type="text"
autoComplete="name"
required
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder="Enter your full name"
/>
{actionData?.errors?.name && (
<div className="text-red-500 text-sm mt-1">{actionData.errors.name}</div>
)}
</div>
<div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
Username
</label>
<input
id="username"
name="username"
type="text"
autoComplete="username"
required
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder="Choose your username"
/>
{actionData?.errors?.username && (
<div className="text-red-500 text-sm mt-1">{actionData.errors.username}</div>
)}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email Address
</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder="Enter your email address"
/>
{actionData?.errors?.email && (
<div className="text-red-500 text-sm mt-1">{actionData.errors.email}</div>
)}
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Password
</label>
<input
id="password"
name="password"
type="password"
autoComplete="new-password"
required
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder="Create a password"
/>
{actionData?.errors?.password && (
<div className="text-red-500 text-sm mt-1">{actionData.errors.password}</div>
)}
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700">
Confirm Password
</label>
<input
id="confirmPassword"
name="confirmPassword"
type="password"
autoComplete="new-password"
required
className="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
placeholder="Confirm your password"
/>
{actionData?.errors?.confirmPassword && (
<div className="text-red-500 text-sm mt-1">{actionData.errors.confirmPassword}</div>
)}
</div>
</div>
{actionData?.errors?.form && (
<div className="text-red-500 text-sm text-center">{actionData.errors.form}</div>
)}
<div>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Create Account
</button>
</div>
</Form>
</div>
</div>
);
}