215 lines
8.1 KiB
TypeScript
215 lines
8.1 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-12 px-4 sm:px-6 lg:px-8">
|
|
<div className="max-w-md w-full space-y-8">
|
|
<div className="text-center">
|
|
<img
|
|
className="mx-auto h-24 w-auto"
|
|
src="/clogo-sm.png"
|
|
alt="Phosphat Report"
|
|
/>
|
|
<h2 className="mt-6 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>
|
|
);
|
|
} |