diff --git a/src/modules/auth/actions/getMyDetailAction.ts b/src/modules/auth/actions/getMyDetailAction.ts index fa7407e..b30b264 100644 --- a/src/modules/auth/actions/getMyDetailAction.ts +++ b/src/modules/auth/actions/getMyDetailAction.ts @@ -1,34 +1,31 @@ "use server"; -import "server-only"; -import AuthError from "../error/AuthError"; import getMyDetail from "../services/getMyDetail"; +import AuthError from "../error/AuthError"; +import BaseError from "@/core/error/BaseError"; import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction"; import handleCatch from "@/modules/dashboard/utils/handleCatch"; -import BaseError from "@/core/error/BaseError"; +import "server-only"; /** - * Retrieves the user details based on the JWT token from cookies. - * This function is designed to be used in a server-side context within a Next.js application. - * It attempts to parse the user's token, fetch the user's details, and format the response. - * If the token is invalid or the user cannot be found, it gracefully handles these cases. + * Asynchronously retrieves the authenticated user's details from a server-side context in a Next.js application. + * This function uses a JWT token obtained from cookies to authenticate the user and fetch their details. + * If the authentication fails due to an invalid JWT token, or if any other error occurs, the function handles these errors gracefully. * - * @returns A promise that resolves to the user's details object or null if the user cannot be authenticated or an error occurs. - * @throws an error if an unexpected error occurs during execution. + * @returns A promise that resolves to a `ServerResponseAction` object. This object includes a `success` flag indicating the operation's outcome, the user's details in the `data` field if successful, or an error object in the `error` field if an error occurs. + * @throws an unhandled error if an unexpected error occurs during the function execution. */ -export default async function getMyDetailAction(): Promise< - ServerResponseAction>> -> { +export default async function getMyDetailAction(): Promise>>> { try { + // Attempt to fetch and return the user's details. + const userDetails = await getMyDetail(); return { success: true, - data: await getMyDetail(), + data: userDetails, }; } catch (e: unknown) { - if ( - e instanceof AuthError && - ["INVALID_JWT_TOKEN"].includes(e.errorCode) - ) { + // Check if the error is an instance of AuthError and handle it. + if (e instanceof AuthError && e.errorCode === "INVALID_JWT_TOKEN") { return { success: false, error: new BaseError({ @@ -37,6 +34,7 @@ export default async function getMyDetailAction(): Promise< }), }; } + // Handle other types of errors. return handleCatch(e); } } diff --git a/src/modules/auth/actions/guestOnly.ts b/src/modules/auth/actions/guestOnly.ts index 473e25f..9336aee 100644 --- a/src/modules/auth/actions/guestOnly.ts +++ b/src/modules/auth/actions/guestOnly.ts @@ -1,12 +1,20 @@ "use server"; import { redirect } from "next/navigation"; -import getUser from "./getMyDetailAction"; +import getMyDetail from "../services/getMyDetail"; -export default async function guestOnly() { - const user = await getUser(); +/** + * Enforces a guest-only access policy by redirecting authenticated users to the dashboard. + * This function asynchronously checks if the user is authenticated by attempting to retrieve user details. + * If the user is authenticated, they are redirected to the dashboard page. + * + * @returns A promise that resolves when the operation completes. The function itself does not return a value. + */ +export default async function guestOnly(): Promise { + const user = await getMyDetail(); + // If an authenticated user is detected, redirect them to the dashboard. if (user) { - redirect("dashboard"); + redirect("/dashboard"); } } diff --git a/src/modules/auth/contexts/AuthContext.tsx b/src/modules/auth/contexts/AuthContext.tsx index 98534d8..ada908b 100644 --- a/src/modules/auth/contexts/AuthContext.tsx +++ b/src/modules/auth/contexts/AuthContext.tsx @@ -1,71 +1,90 @@ +// Directive to enforce client-side operation in a Next.js application. "use client"; -import React, { - ReactElement, - ReactNode, - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from "react"; -import getUser from "../actions/getMyDetailAction"; -import withServerAction from "@/modules/dashboard/utils/withServerAction"; -import getMyDetailAction from "../actions/getMyDetailAction"; + +// Importing React functionalities and required components. +import React, { ReactElement, ReactNode, createContext, useCallback, useContext, useEffect, useState } from "react"; import { notifications } from "@mantine/notifications"; +import getMyDetailAction from "../actions/getMyDetailAction"; +import withServerAction from "@/modules/dashboard/utils/withServerAction"; +// Defining the structure for user data within the authentication context. interface UserData { - name: string; - email: string; - photoUrl: string | null; - // Add additional user fields as needed + name: string; + email: string; + photoUrl: string | null; + // Additional user fields can be added here. } +// State structure for the authentication context. interface AuthContextState { - user: UserData | null; - fetchUserData: () => void; - logout: () => void; + user: UserData | null; + fetchUserData: () => void; + logout: () => void; } +// Props type definition for the AuthContextProvider component. interface Props { - children: ReactNode; + children: ReactNode; } +// Creating the authentication context with an undefined initial value. const AuthContext = createContext(undefined); -export const AuthContextProvider = ({ children }: Props) => { - const [user, setUser] = useState(null); +/** + * Provides an authentication context to wrap around components that require authentication data. + * This component initializes user data state, fetches user data upon component mount, and provides + * a logout function to clear the user data. + * + * @param {Props} props - Component props containing children to be rendered within the provider. + * @returns {ReactElement} A provider component wrapping children with access to authentication context. + */ +export const AuthContextProvider = ({ children }: Props): ReactElement => { + const [user, setUser] = useState(null); - const fetchUserData = useCallback(() => { - - withServerAction(getMyDetailAction) - .then((response) => { - setUser(response.data); - }) - .catch((error) => { - console.error("Error while retrieving user data") - }) - }, []); + // Function to fetch user data and update state accordingly. + const fetchUserData = useCallback(() => { + withServerAction(getMyDetailAction) + .then((response) => { + setUser(response.data); + }) + .catch((error) => { + notifications.show({ + title: 'Error', + message: 'Error while retrieving user data', + color: 'red', + }); + console.error("Error while retrieving user data", error); + }); + }, []); - useEffect(() => { - fetchUserData(); - }, [fetchUserData]); + // Fetch user data on component mount. + useEffect(() => { + fetchUserData(); + }, [fetchUserData]); - const logout = () => { - setUser(null); - }; + // Function to clear user data, effectively logging the user out. + const logout = () => { + setUser(null); + }; - return ( - - {children} - - ); + // Providing authentication state and functions to the context consumers. + return ( + + {children} + + ); }; -export const useAuth = () => { - const context = useContext(AuthContext); - if (!context) { - throw new Error("useAuth must be used within an AuthContextProvider"); - } - return context; +/** + * Custom hook to consume the authentication context. This hook ensures the context is used within a provider. + * + * @returns {AuthContextState} The authentication context state including user data and auth functions. + * @throws {Error} Throws an error if the hook is used outside of an AuthContextProvider. + */ +export const useAuth = (): AuthContextState => { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within an AuthContextProvider"); + } + return context; }; diff --git a/src/modules/auth/services/createUser.ts b/src/modules/auth/services/createUser.ts index 39445f2..fdcab21 100644 --- a/src/modules/auth/services/createUser.ts +++ b/src/modules/auth/services/createUser.ts @@ -10,6 +10,16 @@ import hashPassword from "../utils/hashPassword"; import { createJwtToken } from "../utils/createJwtToken"; import { cookies } from "next/headers"; +/** + * Creates a new user in the database after validating the input data. + * It throws errors if the input data is invalid or if the user already exists. + * On successful creation, it returns a token for the created user. + * + * @param userData - The user data to create a new user. Must conform to CreateUserSchema. + * @returns An object containing the JWT token for the newly created user. + * @throws If the input validation fails. + * @throws If the user already exists in the database. + */ export default async function createUser(userData: CreateUserSchema) { const validatedFields = createUserSchema.safeParse(userData); diff --git a/src/modules/auth/services/getMyDetail.ts b/src/modules/auth/services/getMyDetail.ts index 295989d..b7a9f4c 100644 --- a/src/modules/auth/services/getMyDetail.ts +++ b/src/modules/auth/services/getMyDetail.ts @@ -1,15 +1,25 @@ import { cookies } from "next/headers"; import getUserFromToken from "../utils/getUserFromToken"; +/** + * Retrieves the details of the currently authenticated user based on the JWT token. + * If the token is not present or the user cannot be found, it returns null. + * Otherwise, it returns the user's name, email, and photo URL. + * + * @returns An object containing the user's name, email, and photo URL, or null if the user cannot be authenticated. + */ export default async function getMyDetail() { const token = cookies().get("token"); + // Return null if token is not present if (!token) return null; const user = await getUserFromToken(token.value); + // Return null if user is not found if (!user) return null; + // Return user details return { name: user.name ?? "", email: user.email ?? "",