diff --git a/public/index.html b/public/index.html index 50e8cb60..506e68cc 100644 --- a/public/index.html +++ b/public/index.html @@ -5,7 +5,7 @@ - E-Commerce Ninjas FrontEnd + E-Commerce Ninjas
diff --git a/src/App.scss b/src/App.scss index 10855ae9..c0ab4765 100644 --- a/src/App.scss +++ b/src/App.scss @@ -44,5 +44,5 @@ @import "./assets//styles/UserProfile.scss"; @import "./assets/styles/SellerSideProduct.scss"; @import "./assets/styles/SellerDeleteItem.scss"; -@import "./assets/styles/ServicesPage.scss" - +@import "./assets/styles/ServicesPage.scss"; +@import "./assets/styles/Settings.scss"; \ No newline at end of file diff --git a/src/assets/styles/Settings.scss b/src/assets/styles/Settings.scss new file mode 100644 index 00000000..a34b2589 --- /dev/null +++ b/src/assets/styles/Settings.scss @@ -0,0 +1,99 @@ +.settings { + border: 1px solid $border-color; + width: 100%; + height: 100%; + background-color: $white-color; + + h2 { + margin: 0; + padding: 2%; + font-size: 2.5rem; + font-weight: bold; + text-align: left; + } + + .menu-bar { + display: flex; + margin: 0 2.5rem; + border-bottom: 1px solid #ddd; + + + .menu-item { + cursor: pointer; + font-size: 1.3rem; + p{ + margin-right: 1rem; + padding: 1rem; + &.active { + background-color: $primary-color-light; + color: $primary-color; + padding: 1rem; + border-bottom: 1px solid $primary-color; + transition: padding .2s ease-in-out; + } + } + + } + } + + + .section_container { + flex-grow: 1; + padding: 0 2.5rem; + .loading__spinner{ + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 50rem; + + } + + .section-content { + background-color: white; + padding: 20px; + + .password_exp{ + background-color: $border-color; + padding: 2rem 5rem; + border-radius: .5rem; + + .form-group{ + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + label{ + font-size: 2.5rem; + font-weight: bold; + } + input{ + padding: 10px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 1.4rem; + width: 30%; + } + } + .btn{ + padding: 10px 20px; + background-color: $primary-color; + color: $white-color; + border: none; + border-radius: 5px; + font-size: 1.6rem; + width: 8rem; + cursor: pointer; + transition: width 0.5s linear; + + &:hover{ + background-color: $primary-color-dark; + width: 10rem; + transition: width 0.5s linear; + } + } + } + } + } + +} \ No newline at end of file diff --git a/src/assets/styles/adminDashboard.scss b/src/assets/styles/adminDashboard.scss index 78639605..e240b457 100644 --- a/src/assets/styles/adminDashboard.scss +++ b/src/assets/styles/adminDashboard.scss @@ -19,9 +19,7 @@ flex-direction: column; height: 100%; width: 100%; - .dashboard, - .users, - .logout { + .menu__item,.logout { display: flex; align-items: center; padding-left: 1rem; @@ -214,5 +212,17 @@ .dropdown-item:hover { background-color: #f1f1f1; } -@media only screen and (min-width: 76.8rem) { + +.password-expiration-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.password-expiration-input { + padding: 8px; + font-size: 1rem; + border: 1px solid #ccc; + border-radius: 4px; } diff --git a/src/components/settings/AccountSettings.tsx b/src/components/settings/AccountSettings.tsx new file mode 100644 index 00000000..e691d519 --- /dev/null +++ b/src/components/settings/AccountSettings.tsx @@ -0,0 +1,10 @@ +/* eslint-disable */ +import React from 'react' + +export const AccountSettings = () => { + return ( +
+

Account Settings

+
+ ) +} diff --git a/src/components/settings/GeneralSettings.tsx b/src/components/settings/GeneralSettings.tsx new file mode 100644 index 00000000..4f2ab620 --- /dev/null +++ b/src/components/settings/GeneralSettings.tsx @@ -0,0 +1,86 @@ +/* eslint-disable */ +import React, { useEffect, useState } from "react"; +import { useAppDispatch, useAppSelector } from "../../store/store"; +import { + fetchPasswordExpiration, + updateUserPasswordExpiration, +} from "../../store/features/admin/adminSlice"; +import CircularProgress from "@mui/material/CircularProgress"; +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; +import { Bars } from "react-loader-spinner"; +import { LinearProgress } from "@mui/material"; + +export const GeneralSettings = () => { + const dispatch = useAppDispatch(); + const { users, isLoading, passwordExpiration } = useAppSelector( + (state) => state?.admin + ); + const [minutes, setMinutes] = useState(passwordExpiration); + const [isDisabled, setIsDisabled] = useState(true); + const [isEdit, setIsEdit] = useState(true); + + useEffect(() => { + dispatch(fetchPasswordExpiration()); + setMinutes(passwordExpiration); + }, [dispatch, passwordExpiration]); + + const handleMinutesChange = (e: React.ChangeEvent) => { + setMinutes(Number(e.target.value)); + }; + + const handleSavePasswordExpiration = () => { + if (minutes) { + dispatch(updateUserPasswordExpiration({ minutes })); + } + setIsDisabled(true); + setIsEdit(true); + }; + + return ( + <> +
+ {isLoading && ( +
+ + + +
+ )} +
+
+ + setIsEdit(false)} + placeholder="Enter password expiration period in minutes" + /> +
+ {isEdit ? ( + + ) : ( + + )} +
+
+ + ); +}; diff --git a/src/components/settings/Menu.tsx b/src/components/settings/Menu.tsx new file mode 100644 index 00000000..72082bbb --- /dev/null +++ b/src/components/settings/Menu.tsx @@ -0,0 +1,30 @@ +/* eslint-disable */ +import React from 'react'; + +interface Props { + selectedSection: string; + onSelectSection: (section: string) => void; +} + +const Menu: React.FC = ({ selectedSection, onSelectSection }) => { + const menuItems = [ + 'General Settings', + 'Account Settings', + ]; + + return ( +
+ {menuItems.map(item => ( +
onSelectSection(item)} + > +

{item}

+
+ ))} +
+ ); +}; + +export default Menu; diff --git a/src/components/vertical stepper/VerticalStepper.tsx b/src/components/vertical stepper/VerticalStepper.tsx new file mode 100644 index 00000000..56ad8135 --- /dev/null +++ b/src/components/vertical stepper/VerticalStepper.tsx @@ -0,0 +1,85 @@ +/* eslint-disable */ +import React, { useState } from 'react'; +import { Box, Stepper, Step, StepLabel, StepContent, Button, Paper, Typography } from '@mui/material'; + +const steps = [ + { + label: 'Eligibility Requirements', + description: 'Ensure you meet all the eligibility criteria to become a seller on our platform.', + }, + { + label: 'Account Setup', + description: 'Create your seller account by providing your personal and business details.', + }, + { + label: 'Seller Fees', + description: 'Understand the fees associated with selling on our platform, including listing and transaction fees.', + }, + { + label: 'Product Listing', + description: 'Learn how to list your products, including setting prices, uploading images, and writing descriptions.', + }, + { + label: 'Terms & Conditions', + description: 'Review and agree to the terms and conditions to start selling on our platform.', + }, +]; + +const VerticalStepper = () => { + const [activeStep, setActiveStep] = useState(0); + + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const handleReset = () => { + setActiveStep(0); + }; + + return ( + + + {steps.map((step, index) => ( + + {step.label} + + {step.description} + +
+ + +
+
+
+
+ ))} +
+ {activeStep === steps.length && ( + + All steps completed - you're now a seller! + + + )} +
+ ); +}; + +export default VerticalStepper; \ No newline at end of file diff --git a/src/pages/SellerOnboarding.tsx b/src/pages/SellerOnboarding.tsx new file mode 100644 index 00000000..62017738 --- /dev/null +++ b/src/pages/SellerOnboarding.tsx @@ -0,0 +1,12 @@ +/* eslint-disable */ +import React from "react"; +import VerticalStepper from "../components/vertical stepper/VerticalStepper"; + +export const SellerOnboarding = () => { + return ( +
+

Become a seller

+ +
+ ) +} diff --git a/src/pages/admin/Dashboard.tsx b/src/pages/admin/Dashboard.tsx index 92a8f307..beb87337 100644 --- a/src/pages/admin/Dashboard.tsx +++ b/src/pages/admin/Dashboard.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import { AiFillDashboard } from "react-icons/ai"; import { FaUsers } from "react-icons/fa"; -import { IoLogOutSharp } from "react-icons/io5"; +import { IoLogOutSharp, IoSettingsSharp } from "react-icons/io5"; import Header from "../../components/layout/AdminHeader"; import { Link, Outlet, useNavigate } from "react-router-dom"; import { useAppDispatch, useAppSelector } from "../../store/store"; @@ -13,14 +13,13 @@ import { Meta } from "../../components/Meta"; import useAdminAuthCheck from "../../hooks/useAdminAuthCheck"; import { disconnect } from "../../utils/socket/socket"; - 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; }); @@ -49,7 +48,28 @@ export const AdminDashboard = () => { return; } }, [isError, message, navigate]); - + + const menuItems = [ + { + icon: , + title: "Dashboard", + path: "/admin/dashboard", + index: 1, + }, + { + icon: , + title: "Users", + path: "/admin/dashboard/users", + index: 2, + }, + { + icon: , + title: "Settings", + path: "/admin/dashboard/settings", + index: 3, + }, + ]; + return ( <> @@ -57,42 +77,22 @@ export const AdminDashboard = () => {
-
-
- handleClick(1, "/admin/dashboard")} - /> -
-
- handleClick(1, "/admin/dashboard")} - > - Dashboard - + {menuItems.map((item) => ( +
+
handleClick(item.index, item.path)}> + {item.icon} +
+
+ handleClick(item.index, item.path)} + > + {item.title} + +
-
-
-
- handleClick(2, "/admin/users")} - /> -
-
- handleClick(2, "/admin/dashboard/users")} - > - Users - -
-
+ ))}
@@ -104,20 +104,19 @@ export const AdminDashboard = () => {

Logout

-
+
- - +
diff --git a/src/pages/admin/Settings.tsx b/src/pages/admin/Settings.tsx new file mode 100644 index 00000000..14cdf9fb --- /dev/null +++ b/src/pages/admin/Settings.tsx @@ -0,0 +1,33 @@ +/* eslint-disable */ +import React, { useState } from 'react' +import { Meta } from '../../components/Meta' +import Menu from '../../components/settings/Menu' +import { GeneralSettings } from '../../components/settings/GeneralSettings'; +import { AccountSettings } from '../../components/settings/AccountSettings'; + +export const Settings = () => { + const [selectedSection, setSelectedSection] = useState('General Settings'); + + const renderSection = () => { + switch (selectedSection) { + case 'General Settings': + return ; + case 'Account Settings': + return ; + default: + return ; + } + }; + return ( + <> + +
+

Settings

+ +
+ {renderSection()} +
+
+ + ) +} diff --git a/src/pages/admin/Users.tsx b/src/pages/admin/Users.tsx index 0326bc31..e9a5f22f 100644 --- a/src/pages/admin/Users.tsx +++ b/src/pages/admin/Users.tsx @@ -4,7 +4,9 @@ import Switch from "@mui/material/Switch"; import Table from "../../components/table/Table"; import { useAppDispatch, useAppSelector } from "../../store/store"; import { + fetchPasswordExpiration, getAllUsers, + updateUserPasswordExpiration, updateUserRole, updateUserStatus, } from "../../store/features/admin/adminSlice"; @@ -30,6 +32,7 @@ export default function Users() { const [localUserState, setLocalUserState] = useState([]); const [open, setOpen] = useState(false); const [pendingRoleChange, setPendingRoleChange] = useState(null); + const headers = [ "N0", "Profile", diff --git a/src/router.tsx b/src/router.tsx index 0ab0d6ab..2d93bdd8 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -29,6 +29,7 @@ import ProductsPage from './pages/Products'; import TrackOrder from './pages/trackOrder'; import UserVIewOrders from './pages/UserViewOrders'; import ServicesPage from './pages/ServicesPage'; +import { Settings } from './pages/admin/Settings'; const AppRouter: React.FC = () => { return (
@@ -98,6 +99,7 @@ const AppRouter: React.FC = () => { > } /> } /> + } /> diff --git a/src/store/features/admin/adminService.tsx b/src/store/features/admin/adminService.tsx index 5bf1eba0..32564065 100644 --- a/src/store/features/admin/adminService.tsx +++ b/src/store/features/admin/adminService.tsx @@ -30,8 +30,15 @@ const getAllShops = async () => { return response.data; } +const updatePasswordExpiration = async (minutes: number) => { + const response = await axiosInstance.put('/api/user/admin-update-password-expiration', { minutes }); + return response.data; +}; - +const getPasswordExpiration = async () => { + const response = await axiosInstance.get('/api/user/admin-get-password-expiration'); + return response.data; +}; const adminService = { getAllUsers, @@ -39,7 +46,9 @@ const adminService = { updateUserRole, updateUserStatus, getOrderHistory, - getAllShops + getAllShops, + updatePasswordExpiration, + getPasswordExpiration, } export default adminService; \ No newline at end of file diff --git a/src/store/features/admin/adminSlice.tsx b/src/store/features/admin/adminSlice.tsx index 8f106688..746f112e 100644 --- a/src/store/features/admin/adminSlice.tsx +++ b/src/store/features/admin/adminSlice.tsx @@ -11,6 +11,7 @@ const initialState: IAdminInitialResponse = { isError: false, isLoading: false, isSuccess: false, + passwordExpiration:null }; export const getAllUsers = createAsyncThunk('admin/getAllUsers', async (_, thunkApi) => { @@ -65,6 +66,27 @@ export const getAllShops = createAsyncThunk('admin/admin-get-shops', async (_, t } }) +export const updateUserPasswordExpiration = createAsyncThunk( + 'admin/updateUserPasswordExpiration', + async ({ minutes }: { minutes: number }, thunkApi) => { + try { + const response = await adminService.updatePasswordExpiration(minutes); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } + } +); + +export const fetchPasswordExpiration = createAsyncThunk('admin/fetchPasswordExpiration', async (_, thunkApi) => { + try { + const response = await adminService.getPasswordExpiration(); + return response.minutes; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } +}); + const adminSlice = createSlice({ name: 'admin', initialState, @@ -163,6 +185,36 @@ const adminSlice = createSlice({ state.isError = true; state.message = action.payload || action.payload.message; }) + .addCase(updateUserPasswordExpiration.pending, (state) => { + state.isLoading = true; + }) + .addCase(updateUserPasswordExpiration.fulfilled, (state, action: PayloadAction) => { + state.isLoading = false; + state.isSuccess = true; + state.isError = false; + state.message = action.payload.message; + toast.success(state.message); + }) + .addCase(updateUserPasswordExpiration.rejected, (state, action: PayloadAction) => { + state.isLoading = false; + state.isError = true; + state.message = action.payload || action.payload.message; + toast.error(state.message); + }) + .addCase(fetchPasswordExpiration.pending, (state) => { + state.isLoading = true; + }) + .addCase(fetchPasswordExpiration.fulfilled, (state, action: PayloadAction) => { + state.isLoading = false; + state.passwordExpiration = action.payload; + }) + + .addCase(fetchPasswordExpiration.rejected, (state, action: PayloadAction) => { + state.isLoading = false; + state.isError = true; + state.message = action.payload; + toast.error(state.message); + }) }, }); diff --git a/src/utils/types/store.d.ts b/src/utils/types/store.d.ts index c003b4f0..005f7b16 100644 --- a/src/utils/types/store.d.ts +++ b/src/utils/types/store.d.ts @@ -187,6 +187,7 @@ export interface IAdminInitialResponse { isSuccess: boolean; isLoading: boolean; message: string | null; + passwordExpiration:null; } export interface ISingleProductResponse {