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 { export default class BaseError extends Error {
public readonly errorCode: string; public readonly errorCode: string;
public readonly statusCode: number; 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 super(message); // Pass message to the Error parent class
this.errorCode = errorCode; this.errorCode = errorCode;
this.statusCode = statusCode; this.statusCode = statusCode;
this.data = data;
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
} }
} }

View File

@ -1,5 +1,6 @@
"use client"; "use client";
import createUser from "@/features/auth/actions/createUser";
import { import {
Paper, Paper,
PasswordInput, PasswordInput,
@ -11,7 +12,7 @@ import {
Button, Button,
} from "@mantine/core"; } from "@mantine/core";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import React, { useEffect } from "react"; import React, { useEffect, useState } from "react";
export interface RegisterFormSchema { export interface RegisterFormSchema {
email: string, email: string,
@ -22,6 +23,8 @@ export interface RegisterFormSchema {
export default function RegisterPage() { export default function RegisterPage() {
const [errorMessage, setErrorMessage] = useState("")
const form = useForm<RegisterFormSchema>({ const form = useForm<RegisterFormSchema>({
initialValues: { initialValues: {
email: "", email: "",
@ -31,19 +34,46 @@ export default function RegisterPage() {
}, },
validate: { validate: {
email: (value: string) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'), 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', passwordConfirmation: (value: string, values: RegisterFormSchema) => value === values.password ? null : 'Passwords should match',
name: (value: string) => (value.length > 0 ? null : 'Name is required'), 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 ( return (
<div className="w-screen h-screen flex items-center justify-center"> <div className="w-screen h-screen flex items-center justify-center">
<Paper radius="md" p="xl" withBorder w={400}> <Paper radius="md" p="xl" withBorder w={400}>
<Text size="lg" fw={500} mb={30}> <Text size="lg" fw={500} mb={30}>
Register Register
</Text> </Text>
<form onSubmit={form.onSubmit(() => {})}> <form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Stack> <Stack>
<TextInput <TextInput
label="Name" label="Name"

View File

View File

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

View File

@ -9,7 +9,7 @@ export enum AuthErrorCode {
} }
export default class AuthError extends BaseError { export default class AuthError extends BaseError {
constructor(errorCode: AuthErrorCode, statusCode = 500, message: string = "Authentication error") { constructor(errorCode: AuthErrorCode, {statusCode = 500, message, data}: Partial<{statusCode: number, message: string, data: object}> = {}) {
super(message, errorCode, statusCode); 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 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 { interface CreateUserSchema {
name: string, name: string;
email: string, email: string;
plainPassword: string, password: string;
} }
const register = async (inputData: CreateUserSchema) => { /**
const existingUser = await prisma.user.findUnique({ * Validation schema for creating a user.
where: { */
email: inputData.email 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
}
}
} }
});
if (existingUser) throw new AuthError(AuthErrorCode.USER_ALREADY_EXISTS, 419, "Email already exists") const existingUser = await prisma.user.findUnique({
where: { email: validatedFields.data.email },
});
const user = await prisma.user.create({ if (existingUser){
data: { return {
name: inputData.name, success: false,
email: inputData.email, error: {
passwordHash: await hashPassword(inputData.plainPassword) message: "",
errors: {
email: ["Email already exists"]
}
}
}
} }
});
return user; const user = await prisma.user.create({
data: {
name: validatedFields.data.name,
email: validatedFields.data.email,
passwordHash: await hashPassword(validatedFields.data.password),
},
});
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 { comparePassword, createJwtToken } from "../authUtils";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import BaseError from "@/BaseError";
/** /**
* Handles the sign-in process for a user. * Handles the sign-in process for a user.
@ -57,7 +58,7 @@ export default async function signIn(prevState: any, rawFormData: FormData) {
redirect("/dashboard"); redirect("/dashboard");
} catch (e: unknown) { } catch (e: unknown) {
// Custom error handling for authentication errors // Custom error handling for authentication errors
if (e instanceof AuthError) { if (e instanceof BaseError) {
// Specific error handling for known authentication errors // Specific error handling for known authentication errors
switch (e.errorCode) { switch (e.errorCode) {
case AuthErrorCode.EMAIL_NOT_FOUND: case AuthErrorCode.EMAIL_NOT_FOUND: