Added user data context
This commit is contained in:
parent
047e1f6fa9
commit
02a2356da1
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
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: React.ReactNode
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
|
|
@ -25,9 +26,9 @@ export default function RootLayout({
|
|||
</head>
|
||||
<body className={inter.className}>
|
||||
<MantineProvider>
|
||||
{children}
|
||||
<AuthContextProvider>{children}</AuthContextProvider>
|
||||
</MantineProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
<UserMenuItem item={item} key={i} />
|
||||
));
|
||||
|
|
@ -62,13 +65,13 @@ export default function AppHeader(props: Props) {
|
|||
>
|
||||
<Group gap={7}>
|
||||
<Avatar
|
||||
src={mockUserData.image}
|
||||
alt={mockUserData.name}
|
||||
src={user?.photoUrl}
|
||||
alt={user?.name}
|
||||
radius="xl"
|
||||
size={20}
|
||||
/>
|
||||
<Text fw={500} size="sm" lh={1} mr={3}>
|
||||
{mockUserData.name}
|
||||
{user?.name}
|
||||
</Text>
|
||||
<TbChevronDown
|
||||
style={{ width: rem(12), height: rem(12) }}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { AppShell } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
|
||||
import AppHeader from "../AppHeader";
|
||||
import AppNavbar from "../AppNavbar";
|
||||
import { useAuth } from "@/features/auth/contexts/AuthContext";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
|
|
@ -22,6 +23,12 @@ export default function DashboardLayout(props: Props) {
|
|||
// State and toggle function for handling the disclosure of the navigation bar
|
||||
const [openNavbar, { toggle }] = useDisclosure(false);
|
||||
|
||||
const {fetchUserData} = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserData()
|
||||
}, [fetchUserData])
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
padding="md"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { cookies } from "next/headers"
|
||||
import "server-only"
|
||||
import { decodeJwtToken } from "../authUtils";
|
||||
import { decodeJwtToken, getUserFromToken } from "../authUtils";
|
||||
import prisma from "@/db";
|
||||
import AuthError, { AuthErrorCode } from "../AuthError";
|
||||
import logout from "./logout";
|
||||
|
|
@ -13,16 +13,15 @@ export default async function getUser(){
|
|||
|
||||
if (!token) return null;
|
||||
|
||||
const decodedToken = decodeJwtToken(token.value) as {id: string, iat: number};
|
||||
console.log('token', decodedToken)
|
||||
const user = await getUserFromToken(token.value);
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: decodedToken.id
|
||||
if (!user) return null;
|
||||
|
||||
return {
|
||||
name: user.name ?? "",
|
||||
email: user.email ?? "",
|
||||
photoUrl: user.photoProfile?.path ?? null
|
||||
}
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (e: unknown){
|
||||
if (e instanceof AuthError && e.errorCode === AuthErrorCode.INVALID_JWT_TOKEN){
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { comparePassword, createJwtToken } from "../authUtils";
|
|||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import BaseError from "@/BaseError";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
/**
|
||||
* Handles the sign-in process for a user.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ export async function hashPassword(password: string): Promise<string> {
|
|||
* @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<boolean> {
|
||||
export async function comparePassword(
|
||||
password: string,
|
||||
hash: string
|
||||
): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +37,10 @@ export async function comparePassword(password: string, hash: string): Promise<b
|
|||
* @param options - Optional signing options.
|
||||
* @returns The generated JWT token.
|
||||
*/
|
||||
export function createJwtToken(userClaims: UserClaims, options?: SignOptions): string {
|
||||
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);
|
||||
|
|
@ -56,3 +62,21 @@ export function decodeJwtToken(token: string): JwtPayload | string {
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
66
src/features/auth/contexts/AuthContext.tsx
Normal file
66
src/features/auth/contexts/AuthContext.tsx
Normal file
|
|
@ -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<AuthContextState | undefined>(undefined);
|
||||
|
||||
export const AuthContextProvider = ({ children }: Props) => {
|
||||
const [user, setUser] = useState<UserData | null>(null);
|
||||
|
||||
const fetchUserData = useCallback(() => {
|
||||
const getUserData = async () => {
|
||||
const user = await getUser();
|
||||
setUser(user)
|
||||
}
|
||||
|
||||
getUserData().then()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserData()
|
||||
}, [fetchUserData]);
|
||||
|
||||
const logout = () => {
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, fetchUserData, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error("useAuth must be used within an AuthContextProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user