Added register

This commit is contained in:
Sianida26 2024-01-21 16:21:08 +07:00
parent 3e1fea085c
commit 839339ecbf
7 changed files with 133 additions and 28 deletions

View File

@ -1,11 +1,17 @@
export enum BaseErrorCodes {
INVALID_FORM_DATA = "INVALID_FORM_DATA"
}
export default class BaseError extends Error {
public readonly errorCode: string;
public readonly statusCode: number;
public readonly data: object;
constructor(message: string = "An unexpected error occurred", errorCode: string = "GENERIC_ERROR", statusCode: number = 500) {
constructor(message: string = "An unexpected error occurred", errorCode: string = "GENERIC_ERROR", statusCode: number = 500, data: object = {}) {
super(message); // Pass message to the Error parent class
this.errorCode = errorCode;
this.statusCode = statusCode;
this.data = data;
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
}
}

View File

@ -1,5 +1,6 @@
"use client";
import createUser from "@/features/auth/actions/createUser";
import {
Paper,
PasswordInput,
@ -11,7 +12,7 @@ import {
Button,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
export interface RegisterFormSchema {
email: string,
@ -22,6 +23,8 @@ export interface RegisterFormSchema {
export default function RegisterPage() {
const [errorMessage, setErrorMessage] = useState("")
const form = useForm<RegisterFormSchema>({
initialValues: {
email: "",
@ -31,19 +34,46 @@ export default function RegisterPage() {
},
validate: {
email: (value: string) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
password: (value: string) => (value.length > 6 ? null : 'Password should be at least 6 characters'),
password: (value: string) => (value.length >= 6 ? null : 'Password should be at least 6 characters'),
passwordConfirmation: (value: string, values: RegisterFormSchema) => value === values.password ? null : 'Passwords should match',
name: (value: string) => (value.length > 0 ? null : 'Name is required'),
}
});
const handleSubmit = async (values: RegisterFormSchema) => {
const formData = new FormData();
Object.entries(values)
.forEach(([key, value]) => {
formData.append(key, value)
});
const response = await createUser(formData);
if (!response.success){
setErrorMessage(response.error.message);
if (response.error.errors){
const errors = Object.entries(response.error.errors)
.reduce((prev, [k,v]) => {
prev[k] = v[0]
return prev;
}, {} as {[k: string]: string})
form.setErrors(errors)
console.log(form.errors)
} else {
form.clearErrors()
}
}
}
return (
<div className="w-screen h-screen flex items-center justify-center">
<Paper radius="md" p="xl" withBorder w={400}>
<Text size="lg" fw={500} mb={30}>
Register
</Text>
<form onSubmit={form.onSubmit(() => {})}>
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Stack>
<TextInput
label="Name"

View File

View File

@ -1,4 +1,3 @@
import { auth } from '@/features/auth'
import React from 'react'
export default async function Dashboard() {

View File

@ -9,7 +9,7 @@ export enum AuthErrorCode {
}
export default class AuthError extends BaseError {
constructor(errorCode: AuthErrorCode, statusCode = 500, message: string = "Authentication error") {
super(message, errorCode, statusCode);
constructor(errorCode: AuthErrorCode, {statusCode = 500, message, data}: Partial<{statusCode: number, message: string, data: object}> = {}) {
super(message, errorCode, statusCode, data);
}
}

View File

@ -1,31 +1,100 @@
import prisma from "@/db"
"use server"
import { z } from "zod";
import prisma from "@/db";
import AuthError, { AuthErrorCode } from "../AuthError";
import { hashPassword } from "../authUtils";
import BaseError, { BaseErrorCodes } from "@/BaseError";
import { createJwtToken, hashPassword } from "../authUtils";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
/**
* Interface for the schema of a new user.
*/
interface CreateUserSchema {
name: string,
email: string,
plainPassword: string,
name: string;
email: string;
password: string;
}
const register = async (inputData: CreateUserSchema) => {
const existingUser = await prisma.user.findUnique({
where: {
email: inputData.email
/**
* Validation schema for creating a user.
*/
const createUserSchema = z.object({
name: z.string(),
email: z.string().email(),
password: z.string().min(6),
passwordConfirmation: z.string().optional(),
}).refine(
(data) => data.password === data.passwordConfirmation,
{
message: "Password confirmation must match the password",
path: ["passwordConfirmation"],
}
);
/**
* Creates a new user in the system.
*
* @param formData - The form data containing user details.
* @returns An object indicating the result of the operation.
*/
export default async function createUser(formData: FormData){
//TODO: Add Throttling
//TODO: Add validation check if the user is already logged in
try {
const parsedData = {
email: formData.get("email")?.toString() ?? '',
name: formData.get("name")?.toString() ?? '',
password: formData.get("password")?.toString() ?? '',
passwordConfirmation: formData.get("passwordConfirmation")?.toString()
};
const validatedFields = createUserSchema.safeParse(parsedData);
if (!validatedFields.success) {
return {
success: false,
error: {
message: "",
errors: validatedFields.error.flatten().fieldErrors
}
}
}
const existingUser = await prisma.user.findUnique({
where: { email: validatedFields.data.email },
});
if (existingUser) throw new AuthError(AuthErrorCode.USER_ALREADY_EXISTS, 419, "Email already exists")
if (existingUser){
return {
success: false,
error: {
message: "",
errors: {
email: ["Email already exists"]
}
}
}
}
const user = await prisma.user.create({
data: {
name: inputData.name,
email: inputData.email,
passwordHash: await hashPassword(inputData.plainPassword)
}
name: validatedFields.data.name,
email: validatedFields.data.email,
passwordHash: await hashPassword(validatedFields.data.password),
},
});
return user;
const token = createJwtToken({ id: user.id });
cookies().set("token", token);
redirect("/dashboard");
} catch (e: unknown) {
// Handle unexpected errors
return {
success: false,
error: {
message: "An unexpected error occurred on the server. Please try again or contact the administrator.",
},
};
}
}
export default register;

View File

@ -5,6 +5,7 @@ import AuthError, { AuthErrorCode } from "../AuthError";
import { comparePassword, createJwtToken } from "../authUtils";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import BaseError from "@/BaseError";
/**
* Handles the sign-in process for a user.
@ -57,7 +58,7 @@ export default async function signIn(prevState: any, rawFormData: FormData) {
redirect("/dashboard");
} catch (e: unknown) {
// Custom error handling for authentication errors
if (e instanceof AuthError) {
if (e instanceof BaseError) {
// Specific error handling for known authentication errors
switch (e.errorCode) {
case AuthErrorCode.EMAIL_NOT_FOUND: