diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 776f5df..a22f240 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,7 @@ import Header from "./components/header/header.component"; import { Toaster } from "react-hot-toast"; -import "./index.css"; import { Routes } from "./routes/routes"; +import "./index.css"; function App() { return (
diff --git a/frontend/src/assets/images/404.png b/frontend/src/assets/images/404.png new file mode 100644 index 0000000..19f47b5 Binary files /dev/null and b/frontend/src/assets/images/404.png differ diff --git a/frontend/src/assets/images/bannerBike.jpg b/frontend/src/assets/images/bannerBike.jpg new file mode 100644 index 0000000..3f06d61 Binary files /dev/null and b/frontend/src/assets/images/bannerBike.jpg differ diff --git a/frontend/src/components/auth/loginForm/loginForm.component.tsx b/frontend/src/components/auth/loginForm/loginForm.component.tsx index ff4361d..3766c27 100644 --- a/frontend/src/components/auth/loginForm/loginForm.component.tsx +++ b/frontend/src/components/auth/loginForm/loginForm.component.tsx @@ -1,7 +1,6 @@ -import React, { useState, useContext, useEffect } from "react"; +import React, { useState } from "react"; import { Link } from "react-router-dom"; import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; -import GlobalContext from "../../../context/globalContext"; import axios from "../../../apis/axios"; import { useNavigate } from "react-router-dom"; import toast from "react-hot-toast"; @@ -18,6 +17,7 @@ import { InputRightElement, InputGroup, } from "@chakra-ui/react"; +import { useAuth } from "../../../hooks/useAuth"; interface LoginCredentials { email: string; @@ -27,7 +27,7 @@ interface LoginCredentials { const LoginForm: React.FC = () => { const navigate = useNavigate(); - const { setAuth, auth } = useContext(GlobalContext); + const { login } = useAuth(); const [data, setData] = useState({ email: "", password: "", @@ -37,12 +37,6 @@ const LoginForm: React.FC = () => { const [errMsg, setErrMsg] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [showPassword, setShowPassword] = useState(false); - useEffect(() => { - if (auth?.accessToken) { - navigate("/"); - } - }); - console.log(auth); /** * Handles the change event for the email input field. @@ -84,14 +78,20 @@ const LoginForm: React.FC = () => { try { // Make a POST request to your login endpoint const response = await axios.post("/auth/login", JSON.stringify(data), { - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + }, withCredentials: true, }); - console.log(JSON.stringify(response?.data)); const accessToken = response?.data?.accessToken; - const roles = response?.data?.roles; - setAuth({ ...data, roles, accessToken }); + const userRes = response?.data?.user; + login({ + id: userRes.id, + name: userRes?.name, + email: userRes?.email, + accessToken: accessToken, + }); setData({ email: "", password: "" }); setErrMsg(""); setIsSubmitting(false); @@ -110,14 +110,13 @@ const LoginForm: React.FC = () => { } console.log(data); + console.log(error); console.log(errEmail, errPassword); - console.log(auth); console.log(errMsg); } finally { setIsSubmitting(false); } }; - return (
diff --git a/frontend/src/components/auth/registerForm/registerForm.component.tsx b/frontend/src/components/auth/registerForm/registerForm.component.tsx index 91b0d5a..c812aa6 100644 --- a/frontend/src/components/auth/registerForm/registerForm.component.tsx +++ b/frontend/src/components/auth/registerForm/registerForm.component.tsx @@ -1,6 +1,5 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; -import GlobalContext from "../../../context/globalContext"; import toast from "react-hot-toast"; import { FormControl, @@ -18,7 +17,7 @@ import { } from "@chakra-ui/react"; import { PhoneIcon, ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; import axios from "../../../apis/axios"; - +import { useAuth } from "../../../hooks/useAuth"; interface RegisterCredentials { name: string; email: string; @@ -26,10 +25,8 @@ interface RegisterCredentials { } const LoginForm: React.FC = () => { - const { auth } = useContext(GlobalContext); const navigate = useNavigate(); - - // const [email, setEmail] = useState(""); + const { user } = useAuth(); const [confirmPassword, setConfirmPassword] = useState(""); const [data, setData] = useState({ @@ -45,72 +42,6 @@ const LoginForm: React.FC = () => { const [isSubmitting, setIsSubmitting] = useState(false); const [showPassword, setShowPassword] = useState(false); - useEffect(() => { - if (auth?.accessToken) { - navigate("/"); - } - }); - - /** - * Handles the change event for the email input field. - * - * @param event - The change event object. - */ - const handleEmailChange = (event: React.ChangeEvent) => { - setData({ ...data, email: event.target.value }); - }; - - /** - * Handles the change event for the name input field. - * - * @param event - The change event object. - */ - const handleNameChange = (event: React.ChangeEvent) => { - setData({ ...data, name: event.target.value }); - }; - - // /** - // * Handles the change event for the Date Of Birth input field. - // * - // * @param event - The change event object. - // */ - // const handleDateOfBirthChange = ( - // event: React.ChangeEvent - // ) => { - // setData({ ...data, dateOfBirth: event.target.value }); - // }; - - // /** - // * Handles the change event for the Phone Number input field. - // * - // * @param event - The change event object. - // */ - // const handlePhoneNumberChange = ( - // event: React.ChangeEvent - // ) => { - // setData({ ...data, phoneNumber: event.target.value }); - // }; - - /** - * Handles the change event for the password input field. - * - * @param event - The change event object. - */ - const handlePasswordChange = (event: React.ChangeEvent) => { - setData({ ...data, password: event.target.value }); - }; - - /** - * Handles the change event for the confirm password input field. - * - * @param event - The change event object. - */ - const handleConfirmPasswordChange = ( - event: React.ChangeEvent - ) => { - setConfirmPassword(event.target.value); - }; - /** * Handles validation input field. * @@ -170,7 +101,7 @@ const LoginForm: React.FC = () => { toast.success("Successfully created!"); } catch (error: any) { console.log(error); - toast.error(error.response.data?.message[0]); + toast.error(error?.response?.data?.message[0]); if (!error?.response) { setErrMsg("Something went wrong. Please try again later."); @@ -184,7 +115,7 @@ const LoginForm: React.FC = () => { } console.log(data); console.log(errName, errEmail, errPassword); - console.log(auth); + console.log(user); console.log(errMsg); setIsSubmitting(false); }; @@ -204,7 +135,9 @@ const LoginForm: React.FC = () => { { + setData({ ...data, name: e.target.value }); + }} placeholder="Full Name" /> Name is messing @@ -214,7 +147,9 @@ const LoginForm: React.FC = () => { { + setData({ ...data, email: e.target.value }); + }} placeholder="Email" /> email should not be empty @@ -226,7 +161,9 @@ const LoginForm: React.FC = () => { { + // setData({ ...data, dateOfBirth: e.target.value }); + // }} placeholder="DD/MM/YYYY" /> @@ -240,7 +177,9 @@ const LoginForm: React.FC = () => { { + // setData({ ...data, phoneNumber: e.target.value }); + // }} placeholder="Phone Number" /> @@ -254,7 +193,9 @@ const LoginForm: React.FC = () => { { + setData({ ...data, password: e.target.value }); + }} placeholder="Password" /> @@ -274,7 +215,7 @@ const LoginForm: React.FC = () => { setConfirmPassword(e.target.value)} placeholder="Confirm Password" /> diff --git a/frontend/src/components/header/header.component.tsx b/frontend/src/components/header/header.component.tsx index d3d12ae..0b0477f 100644 --- a/frontend/src/components/header/header.component.tsx +++ b/frontend/src/components/header/header.component.tsx @@ -1,8 +1,8 @@ -import React, { useContext } from "react"; +import React from "react"; import { Link } from "react-router-dom"; import { Button } from "@chakra-ui/react"; -import GlobalContext from "../../context/globalContext"; import LogoutButton from "../logoutButton.component"; +import { useAuth } from "../../hooks/useAuth"; /** * Header: A functional component representing a header in React with Tailwind CSS. @@ -10,23 +10,21 @@ import LogoutButton from "../logoutButton.component"; * @returns {JSX.Element} - The JSX element representing the header. */ const Header: React.FC = () => { - const { auth } = useContext(GlobalContext); - console.log(auth); - + const { user } = useAuth(); return (

My Header

- {auth?.accessToken ? ( + {user?.accessToken ? ( <> - + - + ) : ( <> diff --git a/frontend/src/components/logoutButton.component.tsx b/frontend/src/components/logoutButton.component.tsx index 92babd7..5b2b931 100644 --- a/frontend/src/components/logoutButton.component.tsx +++ b/frontend/src/components/logoutButton.component.tsx @@ -1,32 +1,26 @@ import { Button } from "@chakra-ui/react"; -import React, { useContext } from "react"; +import React from "react"; import axios from "../apis/axios"; -import GlobalContext from "../context/globalContext"; import { useNavigate } from "react-router-dom"; +import { useAuth } from "../hooks/useAuth"; -interface AuthData { - accessToken: string; -} - -const LogoutButton: React.FC<{ - auth: AuthData | null; -}> = ({ auth }) => { - const { setAuth } = useContext(GlobalContext); +const LogoutButton: React.FC = () => { + const { logout, user } = useAuth(); const navigate = useNavigate(); const handleLogout = async () => { try { - if (auth?.accessToken) { + if (user?.accessToken) { await axios.post("/auth/logout", null, { headers: { - Authorization: `Bearer ${auth.accessToken}`, + Authorization: `Bearer ${user.accessToken}`, }, withCredentials: true, }); } navigate("/login"); console.log("Logout successful"); - setAuth({}); + logout(); } catch (error) { console.error("Logout failed:", error); } diff --git a/frontend/src/components/route/privateRoutes.component.tsx b/frontend/src/components/route/privateRoutes.component.tsx new file mode 100644 index 0000000..174e77d --- /dev/null +++ b/frontend/src/components/route/privateRoutes.component.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { Navigate, Outlet } from "react-router-dom"; +import { useAuth } from "../../hooks/useAuth"; + +const PrivateRoutes: React.FC = () => { + const { user } = useAuth(); + return <>{user?.accessToken ? : }; +}; + +export default PrivateRoutes; diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx new file mode 100644 index 0000000..3fee36b --- /dev/null +++ b/frontend/src/context/AuthContext.tsx @@ -0,0 +1,22 @@ +import { useState } from "react"; +import { User } from "../hooks/useUser"; +// import { useAuth } from "../hooks/useAuth"; + +import { createContext, ReactNode } from "react"; + +interface AuthContext { + user: User | null; + setUser: (user: User | null) => void; +} + +export const AuthContext = createContext({} as AuthContext); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(null); + + return ( + + {children} + + ); +} diff --git a/frontend/src/context/globalContext.tsx b/frontend/src/context/globalContext.tsx deleted file mode 100644 index 682e749..0000000 --- a/frontend/src/context/globalContext.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { useState } from "react"; - -const BASE_URL = "http://34.175.134.108:3300/api/v1/"; - -const GlobalContext = React.createContext({}); - -export const GlobalProvider = ({ children }: { children: React.ReactNode }) => { - const [auth, setAuth] = useState({}); - - return ( - - {children} - - ); -}; - -export default GlobalContext; diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts new file mode 100644 index 0000000..722c9d4 --- /dev/null +++ b/frontend/src/hooks/useAuth.ts @@ -0,0 +1,25 @@ +import { useEffect } from "react"; +import { useUser, User } from "./useUser"; +import { useLocalStorage } from "./useLocalStorage"; + +export const useAuth = () => { + const { user, setUser, addUser, removeUser } = useUser(); + const { getItem } = useLocalStorage(); + + useEffect(() => { + const userLocal = getItem("user"); + if (userLocal) { + addUser(JSON.parse(userLocal)); + } + }, []); + + const login = (user: User) => { + addUser(user); + }; + + const logout = () => { + removeUser(); + }; + + return { user, setUser, login, logout }; +}; diff --git a/frontend/src/hooks/useLocalStorage.ts b/frontend/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..1847d20 --- /dev/null +++ b/frontend/src/hooks/useLocalStorage.ts @@ -0,0 +1,23 @@ +import { useState } from "react"; + +export const useLocalStorage = () => { + const [value, setValue] = useState(null); + + const setItem = (key: string, value: string) => { + localStorage.setItem(key, value); + setValue(value); + }; + + const getItem = (key: string) => { + const value = localStorage.getItem(key); + setValue(value); + return value; + }; + + const removeItem = (key: string) => { + localStorage.removeItem(key); + setValue(null); + }; + + return { value, setItem, getItem, removeItem }; +}; diff --git a/frontend/src/hooks/useUser.ts b/frontend/src/hooks/useUser.ts new file mode 100644 index 0000000..8d61e11 --- /dev/null +++ b/frontend/src/hooks/useUser.ts @@ -0,0 +1,27 @@ +import { useContext } from "react"; +import { AuthContext } from "../context/AuthContext"; +import { useLocalStorage } from "./useLocalStorage"; + +export interface User { + id?: string; + name?: string; + email: string; + accessToken?: string; +} + +export const useUser = () => { + const { user, setUser } = useContext(AuthContext); + const { setItem } = useLocalStorage(); + + const addUser = (user: User) => { + setUser(user); + setItem("user", JSON.stringify(user)); + }; + + const removeUser = () => { + setUser(null); + setItem("user", ""); + }; + + return { user, setUser, addUser, removeUser }; +}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index c8b23cc..e5ece0c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -4,7 +4,7 @@ import { ChakraProvider, extendTheme } from "@chakra-ui/react"; import App from "./App.tsx"; import { BrowserRouter } from "react-router-dom"; -import { GlobalProvider } from "./context/globalContext.tsx"; +import { AuthProvider } from "./context/AuthContext.tsx"; const breakpoints = { base: "0px", sm: "320px", @@ -20,11 +20,11 @@ const theme = extendTheme({ breakpoints }); ReactDOM.createRoot(document.getElementById("root")!).render( - - + + - - + + ); diff --git a/frontend/src/pages/login.page.tsx b/frontend/src/pages/auth/login.page.tsx similarity index 88% rename from frontend/src/pages/login.page.tsx rename to frontend/src/pages/auth/login.page.tsx index 7df4e23..d1480db 100644 --- a/frontend/src/pages/login.page.tsx +++ b/frontend/src/pages/auth/login.page.tsx @@ -1,8 +1,17 @@ -import React from "react"; -import LoginForm from "../components/auth/loginForm/loginForm.component"; -import loginImage from "../assets/images/loginImage.png"; +import React, { useEffect } from "react"; +import LoginForm from "../../components/auth/loginForm/loginForm.component"; +import loginImage from "../../assets/images/loginImage.png"; +import { useAuth } from "../../hooks/useAuth"; +import { useNavigate } from "react-router-dom"; const LoginPage: React.FC = () => { + const { user } = useAuth(); + const navigate = useNavigate(); + useEffect(() => { + if (user?.accessToken) { + navigate("/"); + } + }); return (
diff --git a/frontend/src/pages/signup.page.tsx b/frontend/src/pages/auth/signup.page.tsx similarity index 67% rename from frontend/src/pages/signup.page.tsx rename to frontend/src/pages/auth/signup.page.tsx index f5d0807..a0301ce 100644 --- a/frontend/src/pages/signup.page.tsx +++ b/frontend/src/pages/auth/signup.page.tsx @@ -1,8 +1,17 @@ -import React from "react"; -import RegisterForm from "../components/auth/registerForm/registerForm.component"; -import signupImage from "../assets/images/signupImage.png"; +import React, { useEffect } from "react"; +import RegisterForm from "../../components/auth/registerForm/registerForm.component"; +import signupImage from "../../assets/images/signupImage.png"; +import { useNavigate } from "react-router-dom"; +import { useAuth } from "../../hooks/useAuth"; const SignupPage: React.FC = () => { + const { user } = useAuth(); + const navigate = useNavigate(); + useEffect(() => { + if (user?.accessToken) { + navigate("/"); + } + }); return (
diff --git a/frontend/src/pages/error/notFound.page.tsx b/frontend/src/pages/error/notFound.page.tsx new file mode 100644 index 0000000..f45ad4b --- /dev/null +++ b/frontend/src/pages/error/notFound.page.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { Box, Button, Flex, Text } from "@chakra-ui/react"; +import notFoundImage from "../../assets/images/404.png"; +import { Link } from "react-router-dom"; + +/** + * NotFoundPage: A React component representing the 404 error page. + */ +const NotFoundPage: React.FC = () => { + return ( + + + + Error 404: Page Not Found + + + We're sorry, but the page you are looking for does not exist. + + + + + + + ); +}; + +export default NotFoundPage; diff --git a/frontend/src/pages/home.page.tsx b/frontend/src/pages/home.page.tsx index 21c575b..15aa4f1 100644 --- a/frontend/src/pages/home.page.tsx +++ b/frontend/src/pages/home.page.tsx @@ -1,22 +1,29 @@ -import React, { useContext } from "react"; -import GlobalContext from "../context/globalContext.tsx"; -import { Box, Flex, Text } from "@chakra-ui/react"; +import React from "react"; +import { Box, Button, Input } from "@chakra-ui/react"; +import bannerImage from "../assets/images/bannerBike.jpg"; const HomePage: React.FC = () => { - const { auth } = useContext(GlobalContext); return ( - - Home page - - {auth?.email} + + + - + ); }; diff --git a/frontend/src/pages/profile/profle.page.tsx b/frontend/src/pages/profile/profle.page.tsx new file mode 100644 index 0000000..09c448a --- /dev/null +++ b/frontend/src/pages/profile/profle.page.tsx @@ -0,0 +1,8 @@ +import { Center } from "@chakra-ui/react"; +import React from "react"; + +const PofilePage: React.FC = () => { + return
profle.page
; +}; + +export default PofilePage; diff --git a/frontend/src/routes/routes.tsx b/frontend/src/routes/routes.tsx index 467c3c3..9689dc8 100644 --- a/frontend/src/routes/routes.tsx +++ b/frontend/src/routes/routes.tsx @@ -1,15 +1,22 @@ import React from "react"; import { Routes as ReactRouterRoutes, Route } from "react-router-dom"; -import LoginPage from "../pages/login.page"; -import SignupPage from "../pages/signup.page"; +import LoginPage from "../pages/auth/login.page"; +import SignupPage from "../pages/auth/signup.page"; import HomePage from "../pages/home.page"; +import Profile from "../pages/profile/profle.page"; +import NotFoundPage from "../pages/error/notFound.page"; +import PrivateRoutes from "../components/route/privateRoutes.component"; const Routes: React.FC = () => { return ( - } /> + }> + } /> + } /> + } /> } /> + } /> ); };