diff --git a/prisma/migrations/20240122063550_add_user_photo_profile/migration.sql b/prisma/migrations/20240122063550_add_user_photo_profile/migration.sql
new file mode 100644
index 0000000..7c605ea
--- /dev/null
+++ b/prisma/migrations/20240122063550_add_user_photo_profile/migration.sql
@@ -0,0 +1,11 @@
+-- CreateTable
+CREATE TABLE `UserPhotoProfiles` (
+ `id` VARCHAR(191) NOT NULL,
+ `userId` VARCHAR(191) NOT NULL,
+
+ UNIQUE INDEX `UserPhotoProfiles_userId_key`(`userId`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `UserPhotoProfiles` ADD CONSTRAINT `UserPhotoProfiles_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/migrations/20240122063854_move_user_profile_image_from_user_to_user_photo_table/migration.sql b/prisma/migrations/20240122063854_move_user_profile_image_from_user_to_user_photo_table/migration.sql
new file mode 100644
index 0000000..6524c4f
--- /dev/null
+++ b/prisma/migrations/20240122063854_move_user_profile_image_from_user_to_user_photo_table/migration.sql
@@ -0,0 +1,12 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `image` on the `User` table. All the data in the column will be lost.
+ - Added the required column `path` to the `UserPhotoProfiles` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- AlterTable
+ALTER TABLE `User` DROP COLUMN `image`;
+
+-- AlterTable
+ALTER TABLE `UserPhotoProfiles` ADD COLUMN `path` VARCHAR(191) NOT NULL;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 9b4ba05..ffc26e3 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -42,10 +42,17 @@ model User {
name String?
email String? @unique
emailVerified DateTime?
- image String?
passwordHash String?
accounts Account[]
sessions Session[]
+ photoProfile UserPhotoProfiles?
+}
+
+model UserPhotoProfiles {
+ id String @id @default(cuid())
+ userId String @unique
+ path String
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model VerificationToken {
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index b16587e..e84ee87 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,33 +1,34 @@
-import type { Metadata } from 'next'
-import { Inter } from 'next/font/google'
+import type { Metadata } from "next";
+import { Inter } from "next/font/google";
-import "./globals.css"
-import '@mantine/core/styles.css';
+import "./globals.css";
+import "@mantine/core/styles.css";
-import { ColorSchemeScript, MantineProvider } from '@mantine/core';
+import { ColorSchemeScript, MantineProvider } from "@mantine/core";
+import { AuthContextProvider } from "@/features/auth/contexts/AuthContext";
-const inter = Inter({ subsets: ['latin'] })
+const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
- title: 'Create Next App',
- description: 'Generated by create next app',
-}
+ title: "Create Next App",
+ description: "Generated by create next app",
+};
export default function RootLayout({
- children,
+ children,
}: {
- children: React.ReactNode
+ children: React.ReactNode;
}) {
- return (
-
-
-
-
-
-
- {children}
-
-
-
- )
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+ );
}
diff --git a/src/components/AppHeader/Header.tsx b/src/components/AppHeader/Header.tsx
index 8027a62..41b8d35 100644
--- a/src/components/AppHeader/Header.tsx
+++ b/src/components/AppHeader/Header.tsx
@@ -17,21 +17,24 @@ import classNames from "./styles.module.css";
import { TbChevronDown, TbLogout, TbSettings } from "react-icons/tb";
import userMenuItems from "./_data/userMenuItems";
import UserMenuItem from "./_components/UserMenuItem/UserMenuItem";
+import { useAuth } from "@/features/auth/contexts/AuthContext";
interface Props {
openNavbar: boolean;
toggle: () => void;
}
-const mockUserData = {
- name: "Fulan bin Fulanah",
- email: "janspoon@fighter.dev",
- image: "https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-5.png",
-};
+// const mockUserData = {
+// name: "Fulan bin Fulanah",
+// email: "janspoon@fighter.dev",
+// image: "https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-5.png",
+// };
export default function AppHeader(props: Props) {
const [userMenuOpened, setUserMenuOpened] = useState(false);
+ const { user } = useAuth()
+
const userMenus = userMenuItems.map((item, i) => (
));
@@ -62,13 +65,13 @@ export default function AppHeader(props: Props) {
>
- {mockUserData.name}
+ {user?.name}
{
+ fetchUserData()
+ }, [fetchUserData])
+
return (
{
- return bcrypt.hash(password, authConfig.saltRounds);
+ return bcrypt.hash(password, authConfig.saltRounds);
}
/**
* Compares a plain text password with a hashed password.
- *
+ *
* @param password - The plain text password to compare.
* @param hash - The hashed password to compare against.
* @returns True if the passwords match, false otherwise.
*/
-export async function comparePassword(password: string, hash: string): Promise {
- return bcrypt.compare(password, hash);
+export async function comparePassword(
+ password: string,
+ hash: string
+): Promise {
+ return bcrypt.compare(password, hash);
}
/**
* Creates a JWT token based on user claims.
- *
+ *
* @param userClaims - The user claims to encode in the JWT.
* @param options - Optional signing options.
* @returns The generated JWT token.
*/
-export function createJwtToken(userClaims: UserClaims, options?: SignOptions): string {
- const secret = process.env.JWT_SECRET;
- if (!secret) throw new AuthError(AuthErrorCode.JWT_SECRET_EMPTY);
- return jwt.sign(userClaims, secret, options);
+export function createJwtToken(
+ userClaims: UserClaims,
+ options?: SignOptions
+): string {
+ const secret = process.env.JWT_SECRET;
+ if (!secret) throw new AuthError(AuthErrorCode.JWT_SECRET_EMPTY);
+ return jwt.sign(userClaims, secret, options);
}
/**
* Decodes a JWT token and retrieves the payload.
- *
+ *
* @param token - The JWT token to decode.
* @returns The decoded payload.
*/
export function decodeJwtToken(token: string): JwtPayload | string {
- const secret = process.env.JWT_SECRET;
- if (!secret) throw new AuthError(AuthErrorCode.JWT_SECRET_EMPTY);
+ const secret = process.env.JWT_SECRET;
+ if (!secret) throw new AuthError(AuthErrorCode.JWT_SECRET_EMPTY);
- try {
- return jwt.verify(token, secret) as JwtPayload;
- } catch (error) {
- throw new AuthError(AuthErrorCode.INVALID_JWT_TOKEN);
- }
+ try {
+ return jwt.verify(token, secret) as JwtPayload;
+ } catch (error) {
+ throw new AuthError(AuthErrorCode.INVALID_JWT_TOKEN);
+ }
+}
+
+export async function getUserFromToken(token: string) {
+ const decodedToken = decodeJwtToken(token) as {
+ id: string;
+ iat: number;
+ };
+
+ const user = await prisma.user.findFirst({
+ include:{
+ photoProfile: true,
+ },
+ where: {
+ id: decodedToken.id,
+ },
+ });
+
+ return user;
}
diff --git a/src/features/auth/contexts/AuthContext.tsx b/src/features/auth/contexts/AuthContext.tsx
new file mode 100644
index 0000000..11a3415
--- /dev/null
+++ b/src/features/auth/contexts/AuthContext.tsx
@@ -0,0 +1,66 @@
+"use client";
+import React, {
+ ReactElement,
+ ReactNode,
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from "react";
+import getUser from "../actions/getUser";
+
+interface UserData {
+ name: string;
+ email: string
+ photoUrl: string | null;
+ // Add additional user fields as needed
+}
+
+interface AuthContextState {
+ user: UserData | null;
+ fetchUserData: () => void;
+ logout: () => void;
+}
+
+interface Props {
+ children: ReactNode;
+}
+
+const AuthContext = createContext(undefined);
+
+export const AuthContextProvider = ({ children }: Props) => {
+ const [user, setUser] = useState(null);
+
+ const fetchUserData = useCallback(() => {
+ const getUserData = async () => {
+ const user = await getUser();
+ setUser(user)
+ }
+
+ getUserData().then()
+ }, [])
+
+ useEffect(() => {
+ fetchUserData()
+ }, [fetchUserData]);
+
+ const logout = () => {
+ setUser(null);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error("useAuth must be used within an AuthContextProvider");
+ }
+ return context;
+};