src/features/auth/actions/sign-up.ts
"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());
};