"use server";
import { hash } from "@node-rs/argon2";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { z } from "zod";
import {
ActionState,
fromErrorToActionState,
toActionState,
} from "@/components/form/utils/to-action-state";
import { Prisma } from "@/generated/prisma/client";
import { lucia } from "@/lib/lucia";
import prisma from "@/lib/prisma";
import { ticketsPath } from "@/paths";
// Zod validation
const signUpSchema = z
.object({
username: z
.string()
.min(1)
.max(191)
.refine(
(value) => !value.includes(" "),
"Username cannot contain spaces",
),
email: z.string().min(1, { message: "Is required" }).max(191).email(),
password: z.string().min(6).max(191),
confirmPassword: z.string().min(6).max(191),
})
// Additional Custom Validation
.superRefine(({ password, confirmPassword }, ctx) => {
if (password !== confirmPassword) {
ctx.addIssue({
code: "custom",
message: "Passwords do not match",
path: ["confirmPassword"],
});
}
});
export const signUp = async (_actionState: ActionState, formData: FormData) => {
// Try to parse zod schema with formData
try {
const { username, email, password } = signUpSchema.parse(
Object.fromEntries(formData),
);
const passwordHash = await hash(password);
// Create user with passwordHash
const user = await prisma.user.create({
data: {
username,
email,
passwordHash,
},
});
// Create session and cookie
const session = await lucia.createSession(user.id, {});
const sessionCookie = lucia.createSessionCookie(session.id);
// Set them
(await cookies()).set(
sessionCookie.name,
sessionCookie.value,
sessionCookie.attributes,
);
} catch (error) {
if (
error instanceof Prisma.PrismaClientKnownRequestError &&
error.code === "P2002"
) {
return toActionState(
"ERROR",
"Either email or username is already in use",
formData,
);
}
return fromErrorToActionState(error, formData);
}
redirect(ticketsPath());
};