diff --git a/backend/src/routes/user.route.ts b/backend/src/routes/user.route.ts index 9c80160d..7cc96764 100644 --- a/backend/src/routes/user.route.ts +++ b/backend/src/routes/user.route.ts @@ -2,6 +2,10 @@ import { Router } from "express"; import { HttpCodes } from "../types/HttpCodes"; import { CustomResponse } from "../types/CustomResponse"; import { newUser } from "../service/user.service"; +import { updateUser } from "../service/user.service"; +import { getUserByEmail } from "../service/user.service"; +import { User } from "@prisma/client"; + export const userRouter = Router(); userRouter.post("/create", async (req, res) => { @@ -29,3 +33,76 @@ userRouter.post("/create", async (req, res) => { return res.status(HttpCodes.CREATED).json(response); }); +userRouter.get("/get", async (req, res) => { + try { + const userEmail = req.session.email as string; + const userDataResponse = await getUserByEmail(userEmail); + + if (userDataResponse.error) { + throw new Error("Failed to fetch user data"); + } + + const userData = userDataResponse.data as User; + + const response: CustomResponse = { + error: false, + message: "User data fetched successfully", + data: { + email: userData.email, + phoneNumber: userData.phoneNumber, + name: userData.name + } + }; + + return res.status(HttpCodes.OK).json(response); + } catch (error) { + console.error("Error fetching user data:", error); + + const response: CustomResponse = { + error: true, + message: "Failed to fetch user data", + data: null + }; + + return res.status(HttpCodes.INTERNAL_SERVER_ERROR).json(response); + } +}); + + +userRouter.post("/update", async (req, res) => { + try { + const data = req.body; + const email = req.session.email as string; + + data["email"] = email; + const updateUserResponse = await updateUser(data); + + if (updateUserResponse.error) { + const response: CustomResponse = { + error: true, + message: updateUserResponse.data as string, + data: null + } + return res.status(HttpCodes.INTERNAL_SERVER_ERROR).json(response); + } + + const response: CustomResponse = { + error: false, + message: "User Updated successfully", + data: updateUserResponse.data + } + + return res.status(HttpCodes.OK).json(response); + } catch (error) { + console.error("Error updating user data:", error); + + const response = { + error: true, + message: "Failed to update user data", + data: null + }; + + return res.status(HttpCodes.INTERNAL_SERVER_ERROR).json(response); + } +}); + diff --git a/backend/src/service/user.service.ts b/backend/src/service/user.service.ts index 74034d5b..d561144c 100644 --- a/backend/src/service/user.service.ts +++ b/backend/src/service/user.service.ts @@ -46,4 +46,76 @@ export const newUser = async (user: { data: null } } -} \ No newline at end of file +} + +export const updateUser = async (user: { + name: string, + email: string, + phoneNumber: string, +}): Promise> => { + if (!user.name || !user.phoneNumber) { + return { + error: true, + data: "Name and Phone Number are required to update the profile." + }; + } + + try { + let userdetail = await prisma.user.findUnique({ + where: { + email: user.email + } + }); + + if (!userdetail) return { + error: true, + data: "User does not exist." + } + + let updatedUser = await prisma.user.update({ + where: { email: user.email }, + data: { + name: user.name, + phoneNumber: user.phoneNumber, + }, + }); + + return { + error: false, + data: updatedUser + }; + } catch (error) { + console.error("Error updating user:", error); + + return { + error: true, + data: null + }; + } +}; + + +export const getUserByEmail = async (email: string): Promise> => { + try { + let user = await prisma.user.findUnique({ + where: { + email: email + } + }); + + if (!user) return { + error: true, + data: "User does not exist." + } + + return { + error: false, + data: user + } + } catch (error) { + return { + error: true, + data: null + } + } +} diff --git a/backend/tests/user.test.ts b/backend/tests/user.test.ts index f03c203e..d7637381 100644 --- a/backend/tests/user.test.ts +++ b/backend/tests/user.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "@jest/globals"; import { prismaMock } from "./_mockdb"; -import { newUser } from "../src/service/user.service"; - +import { getUserByEmail, newUser } from "../src/service/user.service"; +import { updateUser } from "../src/service/user.service"; describe("Create a new user", () => { it("should create a new user", () => { @@ -71,4 +71,113 @@ describe("Create a new user", () => { data: "Phone Number is required to create profile." }); }) -}) + + it("should check if user already exists", () => { + const user = { + id: "1", + name: "name", + email: "email", + phoneNumber: "phoneNumber", + karmaPoints: 0 + + } + + prismaMock.user.create.mockRejectedValue(new Error("User already exists.")); + expect(newUser(user)).resolves.toEqual({ + error: true, + data: null + }); + }) +}); + +describe("Update user profile", () => { + it("should return an error if name is not given", () => { + const user = { + id: "1", + name: "", + email: "email", + phoneNumber: "phoneNumber", + karmaPoints: 0 + }; + + expect(updateUser(user)).resolves.toEqual({ + error: true, + data: "Name and Phone Number are required to update the profile." + }); + }); + + it("should return an error if name is not given", () => { + const user = { + id: "1", + name: "name", + email: "email", + phoneNumber: "", + karmaPoints: 0 + }; + + expect(updateUser(user)).resolves.toEqual({ + error: true, + data: "Name and Phone Number are required to update the profile." + }); + }); + + it("should update user name and phone successfully", () => { + const user = { + id: "1", + name: "NewName", + email: "email", + phoneNumber: "newPhoneNumber", + karmaPoints: 0 + }; + prismaMock.user.findUnique.mockResolvedValue({ + id: "1", + name: "OldName", + email: "email", + phoneNumber: "oldPhoneNumber", + karmaPoints: 0 + }); + + prismaMock.user.update.mockResolvedValue(user); + + expect(updateUser(user)).resolves.toEqual({ + error: false, + data: user + }); + }); +}); + +describe ("Fetch user data", () => { + it("should fetch user data", () => { + const user = { + id: "1", + name: "name", + email: "email", + phoneNumber: "phoneNumber", + karmaPoints: 0 + }; + + prismaMock.user.findUnique.mockResolvedValue(user); + + expect(getUserByEmail(user.email)).resolves.toEqual({ + error: false, + data: user + }); + }); + + it("should return an error if user does not exist", () => { + const user = { + id: "1", + name: "name", + email: "email", + phoneNumber: "phoneNumber", + karmaPoints: 0 + }; + + prismaMock.user.findUnique.mockResolvedValue(null); + + expect(getUserByEmail(user.email)).resolves.toEqual({ + error: true, + data: "User does not exist." + }); + }); +}); \ No newline at end of file diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index a5b962d2..4b1c6d4e 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -71,6 +71,16 @@ export default function Home() { Create a Post +
+ +
{ diff --git a/frontend/app/user/dashboard/page.tsx b/frontend/app/user/dashboard/page.tsx new file mode 100644 index 00000000..e6815ee1 --- /dev/null +++ b/frontend/app/user/dashboard/page.tsx @@ -0,0 +1,117 @@ +"use client" +import { useEffect } from "react"; +import { title } from "@/components/primitives"; +import { Form, SubmitHandler, useForm, UseFormRegister } from "react-hook-form"; +import { Input } from "@nextui-org/input"; +import { useState } from "react"; +import { Button } from "@nextui-org/button"; +import axios from "axios"; +import { siteConfig } from "@/config/site"; +import { User } from "@/types"; + +type UserProfile = { + name: string; + email: string; + phoneNumber: string; +}; + +export default function UserProfile() { + const { register, handleSubmit, formState: { errors }, control, setValue } = useForm(); + + const [message, setMessage] = useState(null); + const [error, setError] = useState(null); + + const fetchUserData = async () => { + try { + const response = await axios.get( + siteConfig.server_url + "/user/get", + { + params:{}, + withCredentials: true, + } + ); + const userData = response.data.data; + + setValue("name", userData.name); + setValue("phoneNumber", userData.phoneNumber); + + setMessage("User data loaded successfully."); + setError(null); + } catch (err) { + console.error("Error fetching user data:", err); + setMessage(null); + setError("Error fetching user data."); + } + }; + + useEffect(() => { + fetchUserData(); + }, []); + + const onSubmit = async (data:User) => { + try { + const res = await axios.post( + siteConfig.server_url + "/user/update", + { + name : data.name, + phoneNumber : data.phoneNumber + }, + { + withCredentials: true, + headers: { + 'Content-Type': 'application/json' + } + } + ); + + console.log("Request Payload:", { + name: data.name, + phoneNumber: data.phoneNumber + }); + + + if (res.status == 201) { + setError(null) + setMessage("User created successfully.") + } else { + setError(res.data.error || "There was an error in updating profile.") + } + + setMessage(res.data.message || "Profile updated successfully."); + setError(null); + } catch (err) { + setMessage(null); + setError("There was an error updating your profile."); + } + }; + + return ( +
+

User Profile

+ +

{message}

+

{error}

+ +
{ + onSubmit(data) + }} + onError={() => { + setMessage(null) + setError("There was an error updating your profile.") + }} + control={control} + > + + + +
+ +
+
+
+ ); +} diff --git a/frontend/types/index.ts b/frontend/types/index.ts index e0c04c61..f187964d 100644 --- a/frontend/types/index.ts +++ b/frontend/types/index.ts @@ -13,8 +13,11 @@ export type Post = { } export type User = { + userId: number; name: string; email: string; + password: string; + karmapoints: number; phoneNumber: string; karmaPoints: number; } \ No newline at end of file