Added register
This commit is contained in:
parent
3e1fea085c
commit
839339ecbf
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
0
src/app/dashboard/layout.tsx
Normal file
0
src/app/dashboard/layout.tsx
Normal 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() {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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({
|
const user = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
name: inputData.name,
|
name: validatedFields.data.name,
|
||||||
email: inputData.email,
|
email: validatedFields.data.email,
|
||||||
passwordHash: await hashPassword(inputData.plainPassword)
|
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;
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user