diff --git a/package-lock.json b/package-lock.json index e605161..00afeec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "npx": "^10.2.2", "react": "^18", "react-dom": "^18", + "react-google-charts": "^4.0.1", "react-hook-form": "^7.51.5", "react-icons": "^5.2.1", "react-query": "^3.39.3", @@ -27770,6 +27771,15 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, + "node_modules/react-google-charts": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-google-charts/-/react-google-charts-4.0.1.tgz", + "integrity": "sha512-V/hcMcNuBgD5w49BYTUDye+bUKaPmsU5vy/9W/Nj2xEeGn+6/AuH9IvBkbDcNBsY00cV9OeexdmgfI5RFHgsXQ==", + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, "node_modules/react-hook-form": { "version": "7.52.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.0.tgz", diff --git a/package.json b/package.json index 4b66244..277cd45 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev ", "build": "next build", "start": "next start", "lint": "next lint", @@ -29,6 +29,7 @@ "npx": "^10.2.2", "react": "^18", "react-dom": "^18", + "react-google-charts": "^4.0.1", "react-hook-form": "^7.51.5", "react-icons": "^5.2.1", "react-query": "^3.39.3", @@ -36,8 +37,8 @@ "react-rating-stars-component": "^2.2.0", "react-redux": "^9.1.2", "react-router-dom": "^6.23.1", - "react-toastify": "^10.0.5", "react-slick": "^0.30.2", + "react-toastify": "^10.0.5", "redux-mock-store": "^1.5.4", "redux-thunk": "^3.1.0", "slick-carousel": "^1.8.1", @@ -97,8 +98,8 @@ "jest-dom": "^4.0.0", "jest-environment-jsdom": "^29.7.0", "lint-staged": "^15.2.4", - "msw": "^2.3.1", "match-media-mock": "^0.1.1", + "msw": "^2.3.1", "postcss": "^8", "prettier": "^3.2.5", "react-test-renderer": "^18.3.1", @@ -109,4 +110,4 @@ "ts-node": "^10.9.2", "typescript": "^5.4.5" } -} \ No newline at end of file +} diff --git a/public/dash1.png b/public/dash1.png new file mode 100644 index 0000000..f5aee4f Binary files /dev/null and b/public/dash1.png differ diff --git a/public/dash2.png b/public/dash2.png new file mode 100644 index 0000000..f56b528 Binary files /dev/null and b/public/dash2.png differ diff --git a/public/orderdash.png b/public/orderdash.png new file mode 100644 index 0000000..924c691 Binary files /dev/null and b/public/orderdash.png differ diff --git a/public/orderdash1.png b/public/orderdash1.png new file mode 100644 index 0000000..7f8b32e Binary files /dev/null and b/public/orderdash1.png differ diff --git a/public/wishdash.png b/public/wishdash.png new file mode 100644 index 0000000..486c066 Binary files /dev/null and b/public/wishdash.png differ diff --git a/src/app/admin/categories/page.tsx b/src/app/admin/categories/page.tsx new file mode 100644 index 0000000..f162f0a --- /dev/null +++ b/src/app/admin/categories/page.tsx @@ -0,0 +1,40 @@ +import React, { useEffect, useState } from 'react'; +import DashNavbar from '@/components/DashNavbar'; +import HeaderDash from '@/components/headerDash'; +import request from '@/utils/axios'; + +function page() { + const [user, setUser] = useState(null); + useEffect(() => { + const categ = async () => { + const responsecat: any = await request.get('/categories'); + const user = localStorage.getItem('profile') || 'no'; + const finalUser = JSON.parse(user); + + setUser(finalUser.User); + }; + categ(); + }, []); + + if (!user) { + return ( +
+
+
+ ); + } + return ( +
+
+ +
+
+
+ +
+
+
+ ); +} + +export default page; diff --git a/src/app/admin/home/page.tsx b/src/app/admin/home/page.tsx new file mode 100644 index 0000000..088ad07 --- /dev/null +++ b/src/app/admin/home/page.tsx @@ -0,0 +1,89 @@ +'use client'; +import React, { useEffect, useState } from 'react'; +import DashNavbar from '@/components/DashNavbar'; +import HeaderDash from '@/components/headerDash'; +import { unstable_noStore as noStore } from 'next/cache'; +import SellerSummary from '@/components/SellerSummary'; +import Chartssection from '@/components/chartssection'; +import request from '@/utils/axios'; +import { useQuery } from '@tanstack/react-query'; + +function page() { + const [categories, setcategoris] = useState([]); + const [user, setUser] = useState(); + const [users, setUsers] = useState(); + + const { data, isLoading, error } = useQuery({ + queryKey: ['data'], + queryFn: async () => { + noStore(); + let data; + const date = new Date(); + const dataDateNow = date + .toLocaleDateString() + .replaceAll('/', '-') + .split('-'); + + const response: any = await request.get( + `http://localhost:5500/api/stats?start=2023-02-06&end=${dataDateNow[2]}-${dataDateNow[0].length > 1 ? dataDateNow[0] : '0' + dataDateNow[0]}-${dataDateNow[1]}`, + ); + data = response.data; + + return data; + }, + }); + useEffect(() => { + const categ = async () => { + const responsecat: any = await request.get('/categories'); + const user = localStorage.getItem('profile') || 'no'; + const finalUser = JSON.parse(user); + + setUser(finalUser.User); + if (finalUser.User.Role.name === 'admin') { + const responseUsers: any = await request.get('/users'); + setUsers(responseUsers.users); + } + setcategoris(responsecat.categories); + }; + categ(); + }, []); + if (isLoading) { + return ( +
+
+
+ ); + } + return ( +
+
+ +
+
+
+ +
+ {/* body section */} +
+ {/* Statistic section */} +
+ + +
+
+
+
+ ); +} + +export default page; diff --git a/src/app/admin/product/page.tsx b/src/app/admin/product/page.tsx new file mode 100644 index 0000000..c4ca2a3 --- /dev/null +++ b/src/app/admin/product/page.tsx @@ -0,0 +1,45 @@ +'use client'; +import React, { useEffect, useState } from 'react'; +import DashNavbar from '@/components/DashNavbar'; +import HeaderDash from '@/components/headerDash'; +import request from '@/utils/axios'; +import ProductsTable from '@/components/Table'; + +function page() { + const [user, setUser] = useState(null); + useEffect(() => { + const categ = async () => { + const responsecat: any = await request.get('/categories'); + const user = localStorage.getItem('profile') || 'no'; + const finalUser = JSON.parse(user); + + setUser(finalUser.User); + }; + categ(); + }, []); + + if (!user) { + return ( +
+
+
+ ); + } + return ( +
+
+ +
+
+
+ +
+
+ +
+
+
+ ); +} + +export default page; diff --git a/src/app/admin/productcreate/page.tsx b/src/app/admin/productcreate/page.tsx new file mode 100644 index 0000000..6e9ef80 --- /dev/null +++ b/src/app/admin/productcreate/page.tsx @@ -0,0 +1,57 @@ +'use client'; +import React, { useEffect, useState } from 'react'; +import DashNavbar from '@/components/DashNavbar'; +import HeaderDash from '@/components/headerDash'; +import request from '@/utils/axios'; +import ProductPopup from '@/components/AddProducts'; +import { useRouter } from 'next/navigation'; + +function page() { + const [user, setUser] = useState(); + const router = useRouter(); + useEffect(() => { + const categ = async () => { + const responsecat: any = await request.get('/categories'); + const user = localStorage.getItem('profile') || 'no'; + const finalUser = JSON.parse(user); + + setUser(finalUser.User); + if (finalUser?.User.Role.name !== 'seller') { + router.push('/'); + } + }; + categ(); + }, []); + + if (!user) { + return ( +
+
+
+ ); + } + return ( +
+
+ +
+
+
+ +
+ {/* body section */} +
+ {/* Statistic section */} +
+ console.log('nothing')} + /> +
+
+
+
+ ); +} + +export default page; diff --git a/src/app/admin/profile/page.tsx b/src/app/admin/profile/page.tsx new file mode 100644 index 0000000..1dd0b09 --- /dev/null +++ b/src/app/admin/profile/page.tsx @@ -0,0 +1,40 @@ +'use client'; +import React, { useEffect, useState } from 'react'; +import DashNavbar from '@/components/DashNavbar'; +import HeaderDash from '@/components/headerDash'; +import request from '@/utils/axios'; + +function page() { + const [user, setUser] = useState(); + useEffect(() => { + const categ = async () => { + const responsecat: any = await request.get('/categories'); + const user = localStorage.getItem('profile') || 'no'; + const finalUser = await JSON.parse(user); + + setUser(finalUser.User); + }; + categ(); + }, []); + if (!user) { + return ( +
+
+
+ ); + } + return ( +
+
+ +
+
+
+ +
+
+
+ ); +} + +export default page; diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx new file mode 100644 index 0000000..ba7593c --- /dev/null +++ b/src/app/admin/users/page.tsx @@ -0,0 +1,32 @@ +'use client'; +import React, { use, useEffect, useState } from 'react'; +import DashNavbar from '@/components/DashNavbar'; +import HeaderDash from '@/components/headerDash'; +import request from '@/utils/axios'; + +function page() { + const [user, setUser] = useState(); + useEffect(() => { + const categ = async () => { + const responsecat: any = await request.get('/categories'); + const user = localStorage.getItem('profile') || 'no'; + const finalUser = JSON.parse(user); + + setUser(finalUser.User); + }; + }, []); + return ( +
+
+ +
+
+
+ +
+
+
+ ); +} + +export default page; diff --git a/src/components/2faVerification.tsx b/src/components/2faVerification.tsx index 974d755..ab2b330 100644 --- a/src/components/2faVerification.tsx +++ b/src/components/2faVerification.tsx @@ -13,7 +13,6 @@ import { resendOTPCode, } from '@/redux/slices/2faAuthenticationSlice'; import GlobarPopUp from './UsablePopUp'; -import request from '@/utils/axios'; interface OtpVerifyInterface { isOpen: boolean; @@ -32,7 +31,7 @@ const OtpVerify: React.FC = ({ isOpen }) => { useEffect(() => { if (isAuthenticated) { - router.push('/seller/dashboard/products'); + router.push('/admin/home'); } }, [isAuthenticated]); @@ -45,12 +44,7 @@ const OtpVerify: React.FC = ({ isOpen }) => { const VerifyOtp = async () => { var otp = input.join(''); const result = otpValidation.safeParse({ otp }); - const result1 = await dispatch(handleOTPVerification(otp)); - if (result1) { - const profile = await request.get(`${URL}/users/profile`); - const userData = JSON.stringify(profile); - localStorage.setItem('profile', userData); - } + await dispatch(handleOTPVerification(otp)); }; const HandleInput = ( index: number, diff --git a/src/components/AddProducts.tsx b/src/components/AddProducts.tsx index f4fadbf..343c12c 100644 --- a/src/components/AddProducts.tsx +++ b/src/components/AddProducts.tsx @@ -1,4 +1,4 @@ -"use client"; +'use client'; import React, { useState, useEffect } from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; @@ -7,7 +7,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { createProduct, fetchCategories } from '../redux/slices/productSlice'; import { unwrapResult } from '@reduxjs/toolkit'; import type { AppDispatch, RootState } from '../redux/store'; -import { productSchema } from "../validations/productValidation"; +import { productSchema } from '../validations/productValidation'; import { showToast } from '@/helpers/toast'; import InputBox from './InputBox'; @@ -31,22 +31,33 @@ interface ProductPopupProps { onClose: () => void; } -const ProductPopup: React.FC = ({ isOpen, onClose }) => { +const ProductPopup: React.FC = ({ + isOpen = true, + onClose, +}) => { const dispatch = useDispatch(); - const { register, handleSubmit, setValue, formState: { errors }, getValues, trigger } = useForm({ + const { + register, + handleSubmit, + setValue, + formState: { errors }, + getValues, + trigger, + } = useForm({ resolver: zodResolver(productSchema), }); const [pictures, setPictures] = useState([]); const [files, setFiles] = useState([]); const [uploadError, setUploadError] = useState(null); - const { categories, status } = useSelector((state: RootState) => state.productsAddReducers); + const { categories, status } = useSelector( + (state: RootState) => state.productsAddReducers, + ); const [loading, setLoading] = useState(false); useEffect(() => { - if (isOpen) { - dispatch(fetchCategories()); - } - }, [dispatch, isOpen]); + const data = dispatch(fetchCategories()); + console.log(data); + }, [dispatch]); useEffect(() => { document.body.style.overflow = 'hidden'; @@ -55,8 +66,8 @@ const ProductPopup: React.FC = ({ isOpen, onClose }) => { }; }, []); - const onSubmit: SubmitHandler = async data => { - console.log("data received", data); + const onSubmit: SubmitHandler = async (data) => { + console.log('data received', data); if (files.length < 4) { setUploadError('You must upload at least 4 pictures.'); return; @@ -73,7 +84,7 @@ const ProductPopup: React.FC = ({ isOpen, onClose }) => { try { const resultAction = await dispatch(createProduct(data as IProduct)); const result = unwrapResult(resultAction); - showToast(result.message, 'success'); + showToast(result.message, 'success'); console.log(result); console.log(result.message); onClose(); @@ -93,8 +104,8 @@ const ProductPopup: React.FC = ({ isOpen, onClose }) => { } else if (error.message) { errorMessage = error.message; } - - showToast(errorMessage, 'error'); + + showToast(errorMessage, 'error'); } finally { setLoading(false); } @@ -104,34 +115,37 @@ const ProductPopup: React.FC = ({ isOpen, onClose }) => { if (e.target.files && e.target.files.length === 1) { const file = e.target.files[0]; const totalFiles = files.length + 1; - + // Check for maximum file limit if (totalFiles > 8) { setUploadError('You can upload a maximum of 8 pictures.'); return; } - + // Validate file type const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png']; if (!allowedTypes.includes(file.type)) { setUploadError('Only jpeg, jpg, and png files are allowed.'); return; } - + // Validate file size (maximum size of 1MB) const maxSizeInBytes = 1 * 1024 * 1024; if (file.size > maxSizeInBytes) { setUploadError('Image size must be less than 1MB.'); return; } - + const filePreview = URL.createObjectURL(file); - setPictures(prevPictures => [...prevPictures, filePreview]); - setFiles(prevFiles => [...prevFiles, file]); - setValue('productPictures', [...getValues('productPictures') || [], file]); + setPictures((prevPictures) => [...prevPictures, filePreview]); + setFiles((prevFiles) => [...prevFiles, file]); + setValue('productPictures', [ + ...(getValues('productPictures') || []), + file, + ]); trigger('productPictures'); setUploadError(null); - + const updatedFiles = getValues('productPictures') || []; if (updatedFiles.length < 4) { setUploadError('You must upload at least 4 pictures.'); @@ -142,21 +156,21 @@ const ProductPopup: React.FC = ({ isOpen, onClose }) => { setUploadError('Please upload one picture at a time.'); } }; - + const handleDeletePicture = (index: number) => { - setPictures(prevPictures => prevPictures.filter((_, i) => i !== index)); + setPictures((prevPictures) => prevPictures.filter((_, i) => i !== index)); const updatedFiles = files.filter((_, i) => i !== index); setFiles(updatedFiles); setValue('productPictures', updatedFiles); trigger('productPictures'); - + if (updatedFiles.length < 4) { setUploadError('You must upload at least 4 pictures.'); } else { setUploadError(null); } }; - + const getCurrentDate = () => { const today = new Date(); const year = today.getFullYear(); @@ -165,19 +179,25 @@ const ProductPopup: React.FC = ({ isOpen, onClose }) => { return `${year}-${month}-${day}`; }; - if (!isOpen) return null; + // if (!isOpen) return null; return ( -
-
e.stopPropagation()}> +
+
e.stopPropagation()} + > -

Add New Product

+

Add New Product

= ({ isOpen, onClose }) => { error={errors.productName?.message} />
-
@@ -253,7 +282,10 @@ const ProductPopup: React.FC = ({ isOpen, onClose }) => { error={errors.stockLevel?.message} />
-
{pictures.map((picture, index) => (
- {`Preview + {`Preview