From 743e8e817c2435420f03d9af8826bb96e9fa8443 Mon Sep 17 00:00:00 2001 From: Saddock Kabandana Date: Fri, 2 Aug 2024 12:44:52 +0200 Subject: [PATCH] ft seller side info (#61) * ft-seller-side-info * requested changes --- src/assets/styles/SellerLayout.scss | 201 ++++++++++++---------- src/assets/styles/SellerProduct.scss | 1 - src/components/layout/SellerHeader.tsx | 14 +- src/hooks/useAdminAuthCheck.ts | 44 +++++ src/hooks/useSellerAuthCheck.ts | 2 +- src/pages/{UserLogin.tsx => Login.tsx} | 20 ++- src/pages/SellerLogin.tsx | 217 ------------------------ src/pages/admin/Dashboard.tsx | 6 +- src/pages/admin/Login.tsx | 176 ------------------- src/pages/seller/SellerDashboard.tsx | 4 +- src/router.tsx | 17 +- src/store/features/auth/authService.tsx | 7 +- src/store/features/auth/authSlice.tsx | 5 +- src/utils/axios/axiosInstance.ts | 2 +- 14 files changed, 206 insertions(+), 510 deletions(-) create mode 100644 src/hooks/useAdminAuthCheck.ts rename src/pages/{UserLogin.tsx => Login.tsx} (91%) delete mode 100644 src/pages/SellerLogin.tsx delete mode 100644 src/pages/admin/Login.tsx diff --git a/src/assets/styles/SellerLayout.scss b/src/assets/styles/SellerLayout.scss index a4af7997..da715076 100644 --- a/src/assets/styles/SellerLayout.scss +++ b/src/assets/styles/SellerLayout.scss @@ -1,110 +1,129 @@ .seller__wrapper { height: 100vh; display: flex; - + .left__side { - width: 26rem; + width: 26rem; + display: flex; + + .icons__side { display: flex; - - .icons__side { + flex-direction: column; + background-color: $primary-color; + height: 100%; + width: 6.5rem; + + .icons { + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + width: 100%; + justify-content: space-between; + + .icons__upper { + margin-top: 10rem; display: flex; flex-direction: column; - background-color: $primary-color; - height: 100%; - width: 6.5rem; - - .icons { - display: flex; - flex-direction: column; - align-items: center; - height: 100%; - width: 100%; - gap: 34rem; - - .icons__upper { - margin-top: 10rem; - display: flex; - flex-direction: column; - gap: 3rem; - - .icon { - color: $white-color; - font-size: 2rem; - } - } - - .icons__bottom { - height: 4rem; - display: flex; - align-items: center; - - .icon { - color: $white-color; - font-size: 3rem; - } - } + gap: 3rem; + + .icon { + color: $white-color; + font-size: 2rem; } + } + + .icons__bottom { + height: 4rem; + display: flex; + align-items: center; + margin-bottom: 3rem; + + .icon { + color: $white-color; + font-size: 3rem; + } + } } - - .dashboard__lower__link h2 { - cursor: pointer; - } - - .dashboard__items { + } + + .dashboard__lower__link h2 { + cursor: pointer; + } + + .dashboard__items { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + margin-left: 1.5rem; + + .dashboard__side { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + justify-content: space-between; + + .dashboard__links { display: flex; flex-direction: column; - height: 100%; - width: 19.5rem; - margin-left: .1rem; - - .dashboard__side { - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - gap: 37.5rem; - - .dashboard__links { - display: flex; - flex-direction: column; - margin-top: 11rem; - height: 5rem; - gap: 3.5rem; - - .text_content { - color: $text-color; - font-size: 1.8rem; - transition: all 0.5s ease-in-out; - text-decoration: none; - } - } - - .dashboard__lower__link { - height: 4rem; - display: flex; - align-items: center; - - .text_content { - color: $text-color; - font-size: 1.8rem; - transition: all 0.5s ease-in-out; - text-decoration: none; - } + margin-top: 10rem; + width: 15rem; + gap: 3rem; + + .text_content { + color: $text-color; + font-size: 1.8rem; + text-decoration: none; + padding: 1rem; + border-radius: .5rem; + transition: background-color 0.3s, color 0.3s; + display: block; + + + &:hover { + background-color: $menu-hover; + color: $white !important; + width: 15rem !important ; } } - - .active { - display: flex; - background-color: $menu-hover; - width: 15rem; - padding: 1rem 1rem; - transition: all 0.5s ease-in-out; - align-items: center; + } + + .dashboard__lower__link { + height: 4rem; + display: flex; + margin-bottom: 3rem; + + .text_content { + color: $text-color; + font-size: 1.9rem; + text-decoration: none; + padding: 1rem; border-radius: .5rem; - } + transition: background-color 0.3s, color 0.3s; + display: block; + + &:hover { + background-color: $menu-hover; + color: $white !important; + width: 15rem; + } + } + } + } + + .active { + display: flex; + background-color: $menu-hover; + color: $white !important; + padding: 1rem; + border-radius: .5rem; + width: 15rem; } + } } - + .main__seller__content__dashboard { display: flex; flex-direction: column; diff --git a/src/assets/styles/SellerProduct.scss b/src/assets/styles/SellerProduct.scss index a80453b3..38002fd0 100644 --- a/src/assets/styles/SellerProduct.scss +++ b/src/assets/styles/SellerProduct.scss @@ -12,7 +12,6 @@ max-width: 100%; max-height: 100%; - .seller-product-header { display: flex; flex-direction: row; diff --git a/src/components/layout/SellerHeader.tsx b/src/components/layout/SellerHeader.tsx index 09d1ae24..60756032 100644 --- a/src/components/layout/SellerHeader.tsx +++ b/src/components/layout/SellerHeader.tsx @@ -7,6 +7,7 @@ import { useAppSelector, useAppDispatch } from "../../store/store"; import Notifications from './notification'; import { fetchNotifications } from '../../store/features/notifications/notificationSlice'; import useSocket from '../../hooks/useSocket'; +import logo from "../../../public/assets/images/logo.png"; function SellerHeader() { const dispatch = useAppDispatch(); @@ -17,7 +18,7 @@ function SellerHeader() { useEffect(() => { if (isAuthenticated) { - dispatch(fetchNotifications()); + dispatch(fetchNotifications()); } }, [dispatch, isAuthenticated]); @@ -30,7 +31,16 @@ function SellerHeader() { return (
-

Seller Dashboard

+
+ Ecommerce logo +

+ e-Commerce Ninjas +

+
{isAuthenticated && (
state.auth); + const [isChecking, setIsChecking] = useState(true); + const [isAuthorized, setIsAuthorized] = useState(false); + + const checkAuth = useCallback(async () => { + if (token) { + await dispatch(getUserDetails(token)); + } + setIsChecking(false); + }, [dispatch, token]); + + useEffect(() => { + checkAuth(); + }, [checkAuth]); + + useEffect(() => { + if (!isChecking) { + if (!isAuthenticated || !user || (user as any).role !== "admin") { + const message = !isAuthenticated ? 'You must login first' : 'You must login as an admin'; + toast.info(message); + navigate(`/login?callbackUrl=${encodeURIComponent(pathname)}`); + setIsAuthorized(false); + } else { + setIsAuthorized(true); + } + } + }, [isChecking, isAuthenticated, user, navigate, pathname]); + + return isAuthorized; +} + +export default useAdminAuthCheck; \ No newline at end of file diff --git a/src/hooks/useSellerAuthCheck.ts b/src/hooks/useSellerAuthCheck.ts index b015a0a2..bd83995a 100644 --- a/src/hooks/useSellerAuthCheck.ts +++ b/src/hooks/useSellerAuthCheck.ts @@ -33,7 +33,7 @@ function useSellerAuthCheck() { if (!isAuthenticated || !token || !user || (user as any).role !== "seller") { const message = !isAuthenticated ? 'You must login first' : 'You must login as a seller'; // toast.info(message); - navigate(`/seller/login?callbackUrl=${callbackUrl}`); + navigate(`/login?callbackUrl=${callbackUrl}`); setIsAuthorized(false); } else { setIsAuthorized(true); diff --git a/src/pages/UserLogin.tsx b/src/pages/Login.tsx similarity index 91% rename from src/pages/UserLogin.tsx rename to src/pages/Login.tsx index b708838a..a502cf18 100644 --- a/src/pages/UserLogin.tsx +++ b/src/pages/Login.tsx @@ -21,7 +21,7 @@ const LoginSchema = Yup.object().shape({ password: Yup.string().required("Password is required"), }); -function UserLogin() { +function Login() { const [isClicked, setIsClicked] = useState(false); const [isFocused, setIsFocused] = useState(false); const [isVisible, setIsVisible] = useState(false); @@ -46,11 +46,17 @@ function UserLogin() { onSubmit: async (values) => { const action = await dispatch(loginUser(values)); if (loginUser.fulfilled.match(action)) { - const pendingWishlistProduct = localStorage.getItem("pendingWishlistProduct"); - if (pendingWishlistProduct) { - await dispatch(addProductToWishlist(pendingWishlistProduct)); - localStorage.removeItem("pendingWishlistProduct"); - } + const token = action.payload.token; + localStorage.setItem("token", token); + toast.success('Login successfully'); + joinRoom(token); + if (action.payload.user.role === "buyer") { + navigate("/home"); + } else if (action.payload.user.role === "seller") { + navigate("/seller/dashboard"); + } else if (action.payload.user.role === "admin") { + navigate("/admin/dashboard"); + } } } }); @@ -186,4 +192,4 @@ function UserLogin() { ); } -export default UserLogin; \ No newline at end of file +export default Login; \ No newline at end of file diff --git a/src/pages/SellerLogin.tsx b/src/pages/SellerLogin.tsx deleted file mode 100644 index a9f455e7..00000000 --- a/src/pages/SellerLogin.tsx +++ /dev/null @@ -1,217 +0,0 @@ -/* eslint-disable */ -import React, { useEffect, useState } from "react"; -import { BiSolidShow, BiSolidHide } from "react-icons/bi"; -import { FcGoogle } from "react-icons/fc"; -import { toast } from "react-toastify"; -import { useAppDispatch, useAppSelector } from "../store/store"; -import { loginUser, getUserDetails, resetAuth } from "../store/features/auth/authSlice"; -import { Link, useNavigate, useLocation } from "react-router-dom"; -import { PulseLoader } from "react-spinners"; -import { useFormik } from "formik"; -import * as Yup from "yup"; -import authService from "../store/features/auth/authService"; -import { joinRoom } from '../utils/socket/socket'; - -const LoginSchema = Yup.object().shape({ - email: Yup.string().email("Email must be valid").required("Email is required"), - password: Yup.string().required("Password is required"), -}); - -function SellerLogin() { - const location = useLocation(); - const queryParams = new URLSearchParams(location.search); - const callbackUrl = queryParams.get('callbackUrl'); - const [isClicked, setIsClicked] = useState(false); - const [isFocused, setIsFocused] = useState(false); - const [isVisible, setIsVisible] = useState(false); - const [badUser, setBadUser] = useState(false); - const [isChecking, setIsChecking] = useState(true); - const navigate = useNavigate(); - - const dispatch = useAppDispatch(); - const { - isLoading, - isError, - isSuccess, - isAuthenticated, - token, - error, - message, - user - } = useAppSelector((state) => state.auth); - - const formik = useFormik({ - initialValues: { - email: "", - password: "", - }, - validationSchema: LoginSchema, - onSubmit: (values) => { - dispatch(loginUser(values)); - }, - }); - - useEffect(() => { - dispatch(resetAuth()); - }, [dispatch]); - - useEffect(() => { - async function checkAuth() { - if (token) { - await dispatch(getUserDetails(token)); - } - setIsChecking(false); - } - - checkAuth(); - }, [dispatch, token]); - - useEffect(() => { - if (!isChecking && isSuccess && isAuthenticated) { - if (user && (user as any).role !== "seller") { - setBadUser(true); - dispatch(resetAuth()); - return; - } - localStorage.setItem("token", token); - {message && toast.success(message)} - navigate(callbackUrl || "/seller/dashboard"); - formik.resetForm(); - joinRoom(token); - } - }, [isChecking, isSuccess, token, isAuthenticated, user, navigate, callbackUrl, message, dispatch]); - - useEffect(() => { - if (!isChecking && isAuthenticated && user && (user as any).role === 'seller') { - navigate(callbackUrl || "/seller/dashboard"); - } - }, [isChecking, isAuthenticated, user, navigate, callbackUrl]); - - function handleIsFocused() { - setIsFocused(true); - setIsClicked(false); - } - - function handleIsVisible() { - setIsVisible((isVisible) => !isVisible); - } - - if (isChecking) { - return ; - } - - return ( -
-
-
-

- Simplify management with our dashboard. -

-

- Simplify your e-commerce management with our user-friendly seller - dashboard. -

- login picture -
-
-
-
- Ecommerce logo -

- e-Commerce Ninjas -

-
-

Welcome back

-

Please login to your account

-
-
-
- setIsClicked(false)} - onBlur={formik.handleBlur} - /> -
-
- - {isFocused ? ( - isVisible ? ( - - ) : ( - - ) - ) : ( - "" - )} -

- - Forgot password - -

-
- {badUser && (

You must login as a seller

)} - {formik.touched.email && formik.errors.email ? ( -

{formik.errors.email}

- ) : formik.touched.password && formik.errors.password ? ( -

{formik.errors.password}

- ) : isError && isClicked ? ( -

{error}

- ) : null} - -
-

or Login with

-
- -

Login with google

-
-

- New to e-commerce Ninjas?{" "} - - Register - -

-
-
-
- ); -} - -export default SellerLogin; \ No newline at end of file diff --git a/src/pages/admin/Dashboard.tsx b/src/pages/admin/Dashboard.tsx index 745e438d..9040ab3c 100644 --- a/src/pages/admin/Dashboard.tsx +++ b/src/pages/admin/Dashboard.tsx @@ -10,13 +10,15 @@ import { logout } from "../../store/features/auth/authSlice"; import { toast } from "react-toastify"; import { Backdrop, Box, CircularProgress, Typography } from "@mui/material"; import { Meta } from "../../components/Meta"; +import useAdminAuthCheck from "../../hooks/useAdminAuthCheck"; export const AdminDashboard = () => { const dispatch = useAppDispatch(); + const isAuthorized = useAdminAuthCheck(); const { isLoading, message, isError } = useAppSelector( (state) => state.admin ); - + const [isActive, setIsActive] = useState(() => { return parseInt(localStorage.getItem("activeTab")) || 1; }); @@ -40,7 +42,7 @@ export const AdminDashboard = () => { useEffect(() => { if (isError && message === "Not authorized") { toast.info("You are not authorized to access this page"); - navigate("/admin"); + navigate("/login"); return; } }, [isError, message, navigate]); diff --git a/src/pages/admin/Login.tsx b/src/pages/admin/Login.tsx deleted file mode 100644 index 2f07886e..00000000 --- a/src/pages/admin/Login.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* eslint-disable */ - -import React, { useEffect, useRef, useState } from "react"; -import { BiSolidShow } from "react-icons/bi"; -import { BiSolidHide } from "react-icons/bi"; - -import { useAppDispatch, useAppSelector } from "../../store/store"; -import { loginUser } from "../../store/features/auth/authSlice"; -import { Link, Navigate, useLocation, useNavigate } from "react-router-dom"; -import { toast } from "react-toastify"; -import { PulseLoader } from "react-spinners"; -import { useFormik } from "formik"; -import * as Yup from "yup"; -import { Meta } from "../../components/Meta"; -import { joinRoom } from "../../utils/socket/socket"; - -const LoginSchema = Yup.object().shape({ - email: Yup.string() - .email("Email must be valid") - .required("Email is required"), - password: Yup.string().required("Password is required"), -}); - -function AdminLogin() { - const [isClicked, setIsClicked] = useState(false); - const [isFocused, setIsFocused] = useState(false); - const [isVisible, setIsVisible] = useState(false); - const navigate = useNavigate(); - const location = useLocation(); - const dispatch = useAppDispatch(); - const { - isLoading, - isError, - isSuccess, - isAuthenticated, - token, - error, - message, - } = useAppSelector((state) => state.auth); - - const formik = useFormik({ - initialValues: { - email: "", - password: "", - }, - validationSchema: LoginSchema, - onSubmit: (values) => { - dispatch(loginUser(values)); - }, - }); - - useEffect(() => { - if (isSuccess && isAuthenticated && token) { - localStorage.setItem("token", token); - navigate("/admin/dashboard"); - formik.resetForm(); - joinRoom(token); - } - }, [token, isAuthenticated, error, isError, isSuccess]); - if (isAuthenticated && localStorage.getItem("token")) { - return ; - } - - function handleIsFocused() { - setIsFocused(true); - setIsClicked(false); - } - - function handleIsVisible() { - setIsVisible((isVisible) => !isVisible); - } - - return ( - <> - -
-
-
-

Admin Dashboard

-

- Access the Admin Dashboard to view insightful statistics and - manage users efficiently. -

- login picture -
-
-
-
- Ecommerce logo -

- e-Commerce Ninjas -

-
-

Welcome back

-

Please login to your account

-
-
-
- setIsClicked(false)} - onBlur={formik.handleBlur} - /> -
-
- - {isFocused ? ( - isVisible ? ( - - ) : ( - - ) - ) : ( - "" - )} -

- - Forgot password - -

-
- {formik.touched.email && formik.errors.email ? ( -

{formik.errors.email}

- ) : formik.touched.password && formik.errors.password ? ( -

{formik.errors.password}

- ) : isError && isClicked ? ( -

{error}

- ) : null} - -
-
-
-
- - ); -} - -export default AdminLogin; diff --git a/src/pages/seller/SellerDashboard.tsx b/src/pages/seller/SellerDashboard.tsx index 20cc2082..cea4c41b 100644 --- a/src/pages/seller/SellerDashboard.tsx +++ b/src/pages/seller/SellerDashboard.tsx @@ -3,7 +3,9 @@ import React from 'react' const SellerDashboard = () => { return ( -
SellerDashboard
+
+ +
) } diff --git a/src/router.tsx b/src/router.tsx index 1f6f4479..91fd54ef 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -13,9 +13,7 @@ import SendResetPasswordLink from './pages/SendResetPasswordLink'; import ResetPassword from './pages/ResetPassword'; import { ProtectedRoute } from "./utils/protectRoute/ProtectedRoute"; import ViewProduct from './pages/ViewProduct'; -import UserLogin from './pages/UserLogin'; -import SellerLogin from './pages/SellerLogin'; -import AdminLogin from './pages/admin/Login'; +import Login from './pages/Login'; import UserViewCart from './pages/UserViewCart'; import Search from "./pages/Search"; import SellerViewProduct from "./pages/seller/SellerViewProduct"; @@ -37,7 +35,7 @@ const AppRouter: React.FC = () => { } /> } /> } /> - } /> + } /> } /> } /> } /> @@ -50,7 +48,13 @@ const AppRouter: React.FC = () => { } /> }/> } /> - } /> + + + }> + } /> + } /> + } /> + } /> }> } /> @@ -59,8 +63,7 @@ const AppRouter: React.FC = () => { } /> - } /> - }> + }> } /> } /> diff --git a/src/store/features/auth/authService.tsx b/src/store/features/auth/authService.tsx index 145092de..8a717f60 100644 --- a/src/store/features/auth/authService.tsx +++ b/src/store/features/auth/authService.tsx @@ -12,8 +12,11 @@ const register = async (userData: IUser) => { }; const login = async (userData: IUser) => { - const response = await axiosInstance.post("/api/auth/login", userData); - return response.data; + const response = await axiosInstance.post<{ data: { token: string } }>("/api/auth/login", userData); + const { token } = response.data.data; + console.log(token); + const userDetails = await fetchUserDetails(token); + return { token, user: userDetails.data.user }; }; const logout = async() => { diff --git a/src/store/features/auth/authSlice.tsx b/src/store/features/auth/authSlice.tsx index 51b78c0c..c6522cc0 100644 --- a/src/store/features/auth/authSlice.tsx +++ b/src/store/features/auth/authSlice.tsx @@ -290,13 +290,14 @@ const userSlice = createSlice({ state.isAuthenticated = true; state.isSuccess = true; state.message = action.payload.message; - state.token = action.payload.data.token; + state.token = action.payload.token; + state.user = action.payload.user; }) .addCase(loginUser.rejected, (state, action: PayloadAction) => { state.isError = true; state.isLoading = false; state.isSuccess = false; - state.error = action.payload + state.error = action.payload; }) .addCase(logout.pending, (state) => { state.isError = false; diff --git a/src/utils/axios/axiosInstance.ts b/src/utils/axios/axiosInstance.ts index 171626ef..3b432f3e 100644 --- a/src/utils/axios/axiosInstance.ts +++ b/src/utils/axios/axiosInstance.ts @@ -31,4 +31,4 @@ const getErrorMessage = (msg: unknown): string => { return "An unknown error occurred"; }; -export { axiosInstance, getErrorMessage }; +export { axiosInstance, getErrorMessage }; \ No newline at end of file