Added documentation

This commit is contained in:
sianida26 2024-02-27 23:35:56 +07:00
parent e6ffe4112d
commit cb3969ac40
5 changed files with 116 additions and 71 deletions

View File

@ -1,34 +1,31 @@
"use server"; "use server";
import "server-only";
import AuthError from "../error/AuthError";
import getMyDetail from "../services/getMyDetail"; import getMyDetail from "../services/getMyDetail";
import AuthError from "../error/AuthError";
import BaseError from "@/core/error/BaseError";
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction"; import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
import handleCatch from "@/modules/dashboard/utils/handleCatch"; 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. * Asynchronously retrieves the authenticated user's details from a server-side context in a Next.js application.
* This function is designed to be used in a server-side context within a Next.js application. * This function uses a JWT token obtained from cookies to authenticate the user and fetch their details.
* It attempts to parse the user's token, fetch the user's details, and format the response. * If the authentication fails due to an invalid JWT token, or if any other error occurs, the function handles these errors gracefully.
* If the token is invalid or the user cannot be found, it gracefully handles these cases.
* *
* @returns A promise that resolves to the user's details object or null if the user cannot be authenticated or an error occurs. * @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 error if an unexpected error occurs during execution. * @throws an unhandled error if an unexpected error occurs during the function execution.
*/ */
export default async function getMyDetailAction(): Promise< export default async function getMyDetailAction(): Promise<ServerResponseAction<Awaited<ReturnType<typeof getMyDetail>>>> {
ServerResponseAction<Awaited<ReturnType<typeof getMyDetail>>>
> {
try { try {
// Attempt to fetch and return the user's details.
const userDetails = await getMyDetail();
return { return {
success: true, success: true,
data: await getMyDetail(), data: userDetails,
}; };
} catch (e: unknown) { } catch (e: unknown) {
if ( // Check if the error is an instance of AuthError and handle it.
e instanceof AuthError && if (e instanceof AuthError && e.errorCode === "INVALID_JWT_TOKEN") {
["INVALID_JWT_TOKEN"].includes(e.errorCode)
) {
return { return {
success: false, success: false,
error: new BaseError({ error: new BaseError({
@ -37,6 +34,7 @@ export default async function getMyDetailAction(): Promise<
}), }),
}; };
} }
// Handle other types of errors.
return handleCatch(e); return handleCatch(e);
} }
} }

View File

@ -1,12 +1,20 @@
"use server"; "use server";
import { redirect } from "next/navigation"; 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<void> {
const user = await getMyDetail();
// If an authenticated user is detected, redirect them to the dashboard.
if (user) { if (user) {
redirect("dashboard"); redirect("/dashboard");
} }
} }

View File

@ -1,71 +1,90 @@
// Directive to enforce client-side operation in a Next.js application.
"use client"; "use client";
import React, {
ReactElement, // Importing React functionalities and required components.
ReactNode, import React, { ReactElement, ReactNode, createContext, useCallback, useContext, useEffect, useState } from "react";
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";
import { notifications } from "@mantine/notifications"; 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 { interface UserData {
name: string; name: string;
email: string; email: string;
photoUrl: string | null; photoUrl: string | null;
// Add additional user fields as needed // Additional user fields can be added here.
} }
// State structure for the authentication context.
interface AuthContextState { interface AuthContextState {
user: UserData | null; user: UserData | null;
fetchUserData: () => void; fetchUserData: () => void;
logout: () => void; logout: () => void;
} }
// Props type definition for the AuthContextProvider component.
interface Props { interface Props {
children: ReactNode; children: ReactNode;
} }
// Creating the authentication context with an undefined initial value.
const AuthContext = createContext<AuthContextState | undefined>(undefined); const AuthContext = createContext<AuthContextState | undefined>(undefined);
export const AuthContextProvider = ({ children }: Props) => { /**
const [user, setUser] = useState<UserData | null>(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<UserData | null>(null);
const fetchUserData = useCallback(() => { // 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);
});
}, []);
withServerAction(getMyDetailAction) // Fetch user data on component mount.
.then((response) => { useEffect(() => {
setUser(response.data); fetchUserData();
}) }, [fetchUserData]);
.catch((error) => {
console.error("Error while retrieving user data")
})
}, []);
useEffect(() => { // Function to clear user data, effectively logging the user out.
fetchUserData(); const logout = () => {
}, [fetchUserData]); setUser(null);
};
const logout = () => { // Providing authentication state and functions to the context consumers.
setUser(null); return (
}; <AuthContext.Provider value={{ user, fetchUserData, logout }}>
{children}
return ( </AuthContext.Provider>
<AuthContext.Provider value={{ user, fetchUserData, logout }}> );
{children}
</AuthContext.Provider>
);
}; };
export const useAuth = () => { /**
const context = useContext(AuthContext); * Custom hook to consume the authentication context. This hook ensures the context is used within a provider.
if (!context) { *
throw new Error("useAuth must be used within an AuthContextProvider"); * @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.
return context; */
export const useAuth = (): AuthContextState => {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthContextProvider");
}
return context;
}; };

View File

@ -10,6 +10,16 @@ import hashPassword from "../utils/hashPassword";
import { createJwtToken } from "../utils/createJwtToken"; import { createJwtToken } from "../utils/createJwtToken";
import { cookies } from "next/headers"; 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) { export default async function createUser(userData: CreateUserSchema) {
const validatedFields = createUserSchema.safeParse(userData); const validatedFields = createUserSchema.safeParse(userData);

View File

@ -1,15 +1,25 @@
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import getUserFromToken from "../utils/getUserFromToken"; 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() { export default async function getMyDetail() {
const token = cookies().get("token"); const token = cookies().get("token");
// Return null if token is not present
if (!token) return null; if (!token) return null;
const user = await getUserFromToken(token.value); const user = await getUserFromToken(token.value);
// Return null if user is not found
if (!user) return null; if (!user) return null;
// Return user details
return { return {
name: user.name ?? "", name: user.name ?? "",
email: user.email ?? "", email: user.email ?? "",