diff --git a/.env b/.env index dffaf48..0e6fae4 100644 --- a/.env +++ b/.env @@ -1,7 +1,10 @@ SKIP_PREFLIGHT_CHECK=true REACT_APP_NODE_ENV="development" REACT_APP_SOCKET_URL="https://dev-socket.mermsemr.com" -REACT_APP_MAIN_API="https://dev-api.mermsemr.com" +REACT_APP_MAIN_API="https://devapi.mermsemr.com" REACT_APP_MEDIA_SERVER="https://dev-media.mermsemr.com" +REACT_APP_MAIN_SOCKET="https://dev-socket.mermsemr.com" +# Inactivity timeout/logout AT 10MINS +REACT_APP_TIMEOUT=600000 diff --git a/.env.development b/.env.development index ad289a2..76f8ff5 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,9 @@ SKIP_PREFLIGHT_CHECK=true REACT_APP_NODE_ENV="development" REACT_APP_SOCKET_URL="https://dev-socket.mermsemr.com" -REACT_APP_MAIN_API="https://dev-api.mermsemr.com" +REACT_APP_MAIN_API="https://devapi.mermsemr.com" REACT_APP_MEDIA_SERVER="https://dev-media.mermsemr.com" +REACT_APP_MAIN_SOCKET="https://dev-socket.mermsemr.com" +# Inactivity timeout/logout AT 10MINS +REACT_APP_TIMEOUT=600000 diff --git a/.env.production b/.env.production index c9b51b9..38699d4 100644 --- a/.env.production +++ b/.env.production @@ -3,3 +3,6 @@ REACT_APP_NODE_ENV="production" REACT_APP_SOCKET_URL="https://socket.mermsemr.com" REACT_APP_MAIN_API="https://api.mermsemr.com" REACT_APP_MEDIA_SERVER="https://media.mermsemr.com" + +# Inactivity timeout/logout AT 10MINS +REACT_APP_TIMEOUT=600000 \ No newline at end of file diff --git a/package.json b/package.json index fd40f6b..64c8218 100644 --- a/package.json +++ b/package.json @@ -4,19 +4,29 @@ "private": true, "dependencies": { "@popperjs/core": "^2.11.8", + "@reduxjs/toolkit": "^2.4.0", + "@tanstack/react-query": "^5.62.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "apexcharts": "^4.1.0", + "axios": "^1.7.9", "bootstrap": "^5.3.3", + "dayjs": "^1.11.13", + "formik": "^2.4.6", "react": "^18.3.1", "react-apexcharts": "^1.7.0", + "react-big-calendar": "^1.17.0", "react-dom": "^18.3.1", "react-icons": "^5.4.0", + "react-redux": "^9.1.2", "react-router-dom": "^7.0.2", "react-scripts": "5.0.1", + "redux": "^5.0.1", "sass": "^1.82.0", - "web-vitals": "^2.1.4" + "socket.io-client": "^4.8.1", + "web-vitals": "^2.1.4", + "yup": "^1.6.0" }, "scripts": { "start": "react-scripts start -e .env.development", diff --git a/public/logo-light.png b/public/logo-light.png index 7881c26..52d8f3b 100644 Binary files a/public/logo-light.png and b/public/logo-light.png differ diff --git a/public/logo.png b/public/logo.png index 46eb892..52d8f3b 100644 Binary files a/public/logo.png and b/public/logo.png differ diff --git a/src/App.css b/src/App.css index 74b5e05..79280ae 100644 --- a/src/App.css +++ b/src/App.css @@ -1,38 +1,11 @@ -.App { - text-align: center; +.custom-bg { + background-image: url('./assets/bg/bg_1.jpg') !important; + background-size: cover; + background-repeat: no-repeat; } -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} +.signup-bg { + background-image: url('./assets/bg/signup_bg.jpg') !important; + background-size: 100%; + background-repeat: no-repeat; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 56f3498..5f0b727 100644 --- a/src/App.js +++ b/src/App.js @@ -1,47 +1,24 @@ -import { Routes, Route } from 'react-router-dom'; -// import './App.css'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query' +import AppRouters from './AppRouters'; -import UserExist from './component/authorization/UserExist'; -import AuthLayout from './component/auth/AuthLayout'; - -import LoginPage from './views/LoginPage'; -import siteLinks from './links/siteLinks'; -import SignupPage from './views/SignupPage'; -import ForgetpwdPage from './views/ForgetpwdPage'; -import HomePage from './views/HomePage'; -import ReportsPage from './views/ReportsPage' -import CommentsPage from './views/CommentsPage' -import ContactsPage from './views/ContactsPage' -import UserPage from './views/UserPage' -import CalendarPage from './views/CalendarPage' -import SettingsPage from './views/SettingsPage' +import './App.css'; function App() { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: 3, + // refetchOnMount: false, + staleTime: 3000 + }, + }, + }) return ( -
- - {/* auth routes wrapper */} - }> - } /> - } /> - } /> - } /> - } /> - - - {/* protected routes */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - -
+ + + ); } diff --git a/src/AppRouters.jsx b/src/AppRouters.jsx new file mode 100644 index 0000000..268a456 --- /dev/null +++ b/src/AppRouters.jsx @@ -0,0 +1,53 @@ +import { Routes, Route } from 'react-router-dom'; + +import UserExist from './component/authorization/UserExist'; +import AuthLayout from './component/auth/AuthLayout'; +import siteLinks from './links/siteLinks'; + +import LoginPage from './views/LoginPage'; +import SignupPage from './views/SignupPage'; +import ForgetpwdPage from './views/ForgetpwdPage'; +import HomePage from './views/HomePage'; +import ReportsPage from './views/ReportsPage' +import CommentsPage from './views/CommentsPage' +import ContactsPage from './views/ContactsPage' +import UserPage from './views/UserPage' +import CalendarPage from './views/CalendarPage' +import SettingsPage from './views/SettingsPage' +import ProductPage from './views/ProductPage' +import SocketIOContextProvider from './component/context/SocketIOContext'; +import CSignupPage from './views/CSignupPage'; + +function AppRouters() { + return ( +
+ + {/* auth routes wrapper */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* protected routes */} + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + +
+ ); +} + +export default AppRouters; diff --git a/src/assets/bg/bg_1.jpg b/src/assets/bg/bg_1.jpg new file mode 100644 index 0000000..8525b77 Binary files /dev/null and b/src/assets/bg/bg_1.jpg differ diff --git a/src/assets/bg/signup_bg.jpg b/src/assets/bg/signup_bg.jpg new file mode 100644 index 0000000..e48db05 Binary files /dev/null and b/src/assets/bg/signup_bg.jpg differ diff --git a/src/assets/img/_logo-light.png b/src/assets/img/_logo-light.png new file mode 100644 index 0000000..7881c26 Binary files /dev/null and b/src/assets/img/_logo-light.png differ diff --git a/src/assets/img/download/andriod.jpg b/src/assets/img/download/andriod.jpg new file mode 100644 index 0000000..0c05410 Binary files /dev/null and b/src/assets/img/download/andriod.jpg differ diff --git a/src/assets/img/download/apple.jpg b/src/assets/img/download/apple.jpg new file mode 100644 index 0000000..b0e1771 Binary files /dev/null and b/src/assets/img/download/apple.jpg differ diff --git a/src/assets/img/logo-light.png b/src/assets/img/logo-light.png index 7881c26..52d8f3b 100644 Binary files a/src/assets/img/logo-light.png and b/src/assets/img/logo-light.png differ diff --git a/src/assets/img/product/01.jpg b/src/assets/img/product/01.jpg new file mode 100644 index 0000000..fbdd78b Binary files /dev/null and b/src/assets/img/product/01.jpg differ diff --git a/src/assets/img/product/p1.jpg b/src/assets/img/product/p1.jpg new file mode 100644 index 0000000..f5ce47f Binary files /dev/null and b/src/assets/img/product/p1.jpg differ diff --git a/src/assets/img/product/p2.jpg b/src/assets/img/product/p2.jpg new file mode 100644 index 0000000..f6783e5 Binary files /dev/null and b/src/assets/img/product/p2.jpg differ diff --git a/src/assets/img/product/p3.jpg b/src/assets/img/product/p3.jpg new file mode 100644 index 0000000..0631a28 Binary files /dev/null and b/src/assets/img/product/p3.jpg differ diff --git a/src/assets/img/product/p4.jpg b/src/assets/img/product/p4.jpg new file mode 100644 index 0000000..30a9391 Binary files /dev/null and b/src/assets/img/product/p4.jpg differ diff --git a/src/assets/img/product/p5.jpg b/src/assets/img/product/p5.jpg new file mode 100644 index 0000000..af5c66d Binary files /dev/null and b/src/assets/img/product/p5.jpg differ diff --git a/src/component/auth/CSignup.jsx b/src/component/auth/CSignup.jsx new file mode 100644 index 0000000..c38db83 --- /dev/null +++ b/src/component/auth/CSignup.jsx @@ -0,0 +1,174 @@ +import React, { useEffect, useState } from 'react' +import { Form, Formik } from "formik"; +import * as Yup from "yup"; + +// import LoginImg from '../../assets/bg/login.svg' + +import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom' +import siteLinks from '../../links/siteLinks' +import { useMutation } from '@tanstack/react-query'; +import { signUpUser } from '../../services/services'; + +const validationSchema = Yup.object().shape({ + email: Yup.string() + .email("Wrong email format") + // .matches( + // /^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/, + // "Invalid email format" + // ) + .min(3, "Minimum 3 characters") + .max(50, "Maximum 50 characters") + .required("Email is required"), + password: Yup.string().required("Password is required"), + confirmpassword: Yup.string().required("Confirm Password is required").oneOf([Yup.ref('password')], 'Passwords must match') + // lastname: Yup.string().required("Lastname is required"), + // isChecked: Yup.bool().oneOf([true], "Please accept the terms & policy"), // use bool instead of boolean + }) + + const initialValues = { + email: '', + password: '', + confirmpassword: '', + // lastname: '', + // isChecked: false, + }; + +export default function CSignup() { + + const {jwt} = useParams() + + const navigate = useNavigate() + + const mutation = useMutation({ + mutationFn: (fields) => { + return signUpUser(fields) + }, + onSuccess: (res) => { + console.log('res', res) + } + }) + + const CSignUp = (values) => { + // helpers.resetForm() + // console.log('values', values, helpers) + // mutation.mutate(values) + console.log('values', values) + } + + useEffect(()=>{ + if(!jwt){ + return navigate(siteLinks.login, {replace: true}) + } + }) + + return ( +
+
+
+
+
+
+
+
+
+

MERMS Panel

+

Welcome, Enter your password.

+ + {(props) => { + return ( +
+
+ {!mutation.isSuccess ? + <> + {/*
+
+ + +
+
*/} +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + {/*
+
+ + +
+ {props.errors.isChecked} +
*/} + + {mutation.error && + <> +
+

{mutation.error.message}

+
+ + } + +
+ +
+ + : +
+
+

Check your email to continue.

+ Home +
+
+ } + +
+ Need help with logging in or signing up? +
+ +
+ {/*

Already have an account ? Sign In

*/} +

Ready our Privacy Statement

+
+
+
+ ); + }} +
+
+
+
+ {/*
+
+
+ +
+
+
*/} +
+
+
+
+ +
+
+ ) +} diff --git a/src/component/auth/Forgetpwd2.jsx b/src/component/auth/Forgetpwd2.jsx index f506561..ccdb727 100644 --- a/src/component/auth/Forgetpwd2.jsx +++ b/src/component/auth/Forgetpwd2.jsx @@ -1,23 +1,43 @@ import React, { useEffect, useState } from 'react' -import LoginImg from '../../assets/bg/login.svg' +import { Form, Formik } from "formik"; +import * as Yup from "yup"; +// import LoginImg from '../../assets/bg/login.svg' -import MainLoaderBS from '../loaders/MainLoaderBS' -import { Link, useNavigate } from 'react-router-dom' +import { Link } from 'react-router-dom' import siteLinks from '../../links/siteLinks' +import { useMutation } from '@tanstack/react-query' +import { recoverPWD } from '../../services/services'; + +const validationSchema = Yup.object().shape({ + username: Yup.string() + .email("Wrong email format") + // .matches( + // /^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/, + // "Invalid email format" + // ) + .min(3, "Minimum 3 characters") + .max(50, "Maximum 50 characters") + .required("Email is required"), + }) + + const initialValues = { + username: '' + }; export default function Forgetpwd2() { - const [loading, setLoading] = useState(true) + const mutation = useMutation({ + mutationFn: (fields) => { + return recoverPWD(fields) + }, + // onSuccess: (res) => { + // console.log('res', res) + // } + }) - const navigate = useNavigate() - - useEffect(()=>{ - const timer = setTimeout(()=>{ - setLoading(false) - },1000) - - return () => clearTimeout(timer) - },[]) + const recoverPWDSubmit = (values) => { + mutation.mutate(values) + } return (
@@ -31,29 +51,57 @@ export default function Forgetpwd2() {

Recover Password

Please enter your email.

-
-
-
-
- - + + {(props) => { + return ( + +
+ {!mutation.isSuccess ? + <> +
+
+ + +
+
+ {mutation.error && + <> +
+

{mutation.error.message}

+
+ + } +
+ +
+ + : +
+
+

Check your email to continue password reset.

+ Home +
-
-
- -
-
-

Go Back

-
-
- + } +
+

Go Back

+
+
+ + ); + }} +
-
+
- + {/* */}
diff --git a/src/component/auth/Login2.jsx b/src/component/auth/Login2.jsx index d48b822..1517044 100644 --- a/src/component/auth/Login2.jsx +++ b/src/component/auth/Login2.jsx @@ -1,23 +1,66 @@ -import React, { useEffect, useState } from 'react' -import LoginImg from '../../assets/bg/login.svg' +import React, { useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { useDispatch } from 'react-redux' + +// import LoginImg from '../../assets/bg/login.svg' -import MainLoaderBS from '../loaders/MainLoaderBS' import { Link, useNavigate } from 'react-router-dom' import siteLinks from '../../links/siteLinks' +import { loginUser } from '../../services/services' +import { updateUserDetails } from '../../store/UserDetails' + +import GoogleDownload from '../../assets/img/download/andriod.jpg' +import IOSDownload from '../../assets/img/download/apple.jpg' export default function Login() { - const [loading, setLoading] = useState(true) + const dispatch = useDispatch() const navigate = useNavigate() - useEffect(()=>{ - const timer = setTimeout(()=>{ - setLoading(false) - },1000) + const [fields, setFields] = useState({ + username: localStorage.getItem('username') || '', + password: '', + remember: localStorage.getItem('username') ? true : false + }) - return () => clearTimeout(timer) - },[]) + const handleChange = ({target:{name, value}}) => { + if(name == 'remember'){ + return setFields(prev => ({...prev, remember:!prev.remember})) + } + setFields(prev => ({...prev, [name]:value})) + } + + const login = useMutation({ + mutationFn: (fields) => { + if(!fields.username || !fields.password){ + throw new Error('Please provide all fields marked *') + } + rememberMe(fields.remember) // FUNCTION TO SAVE USERNAME OF THE USER TO LOCAL STORAGE + return loginUser(fields) + }, + onError: (error) => { + console.log(error) + }, + onSuccess: (res) => { + const {token, room} = res?.data?.data + if(token){ + localStorage.setItem('token', token) + localStorage.setItem('room', room) + // const data = {token} + // dispatch(updateUserDetails({ ...data })); + navigate('/dash') // later add redux to dispatch state + } + } + }) + + const rememberMe = (checked) => { + if(checked){ + localStorage.setItem('username', fields.username) + }else{ + localStorage.removeItem('username') + } + } return (
@@ -35,42 +78,72 @@ export default function Login() {
- - + +
- - + +
- -
Forgot Password ?
-
- + {login.error && + <> +
+

{login.error.message}

+
+ + } +
+

Don't have an account ? Sign Up

+
+
+
+ + IOS Download + +
+
+ +
+
+ + IOS Download + +
+
+
-
+
- + {/* */}
diff --git a/src/component/auth/Signup2.jsx b/src/component/auth/Signup2.jsx index 3b66c55..6aa786b 100644 --- a/src/component/auth/Signup2.jsx +++ b/src/component/auth/Signup2.jsx @@ -1,23 +1,56 @@ -import React, { useEffect, useState } from 'react' -import LoginImg from '../../assets/bg/login.svg' +import React, { useState } from 'react' +import { Form, Formik } from "formik"; +import * as Yup from "yup"; -import MainLoaderBS from '../loaders/MainLoaderBS' -import { Link, useNavigate } from 'react-router-dom' +// import LoginImg from '../../assets/bg/login.svg' + +import { Link } from 'react-router-dom' import siteLinks from '../../links/siteLinks' +import { useMutation } from '@tanstack/react-query'; +import { signUpUser } from '../../services/services'; + +const validationSchema = Yup.object().shape({ + email: Yup.string() + .email("Wrong email format") + // .matches( + // /^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/, + // "Invalid email format" + // ) + .min(3, "Minimum 3 characters") + .max(50, "Maximum 50 characters") + .required("Email is required"), + firstname: Yup.string().required("Firstname is required"), + lastname: Yup.string().required("Lastname is required"), + isChecked: Yup.bool().oneOf([true], "Please accept the terms & policy"), // use bool instead of boolean + // username: Yup.string().min(3, "Minimum 3 characters").max(50, "Maximum 50 characters").required("Email is required"), + // password: Yup.string().min(3, "Minimum 3 characters").max(50, "Maximum 50 characters").required("Email is required"), + }) + + const initialValues = { + email: '', + firstname: '', + lastname: '', + isChecked: false, + // username: '', + // password: '' + }; export default function Signup2() { - const [loading, setLoading] = useState(true) + const mutation = useMutation({ + mutationFn: (fields) => { + return signUpUser(fields) + }, + onSuccess: (res) => { + console.log('res', res) + } + }) - const navigate = useNavigate() - - useEffect(()=>{ - const timer = setTimeout(()=>{ - setLoading(false) - },1000) - - return () => clearTimeout(timer) - },[]) + const signUp = (values) => { + // helpers.resetForm() + // console.log('values', values, helpers) + mutation.mutate(values) + } return (
@@ -26,66 +59,98 @@ export default function Signup2() {
-
-
-
-

MERMS Panel

+
+
+
+

MERMS Panel

Welcome, Please create your account.

-
-
-
-
- - + + {(props) => { + return ( + +
+ {!mutation.isSuccess ? + <> +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ {/*
+
+ + +
+
+
+
+ + +
+
*/} +
+
+ + +
+ {props.errors.isChecked} +
+ + {mutation.error && + <> +
+

{mutation.error.message}

+
+ + } + +
+ +
+ + : +
+
+

Check your email to continue.

+ Home +
+
+ } + +
+

Already have an account ? Sign In

+
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- -
-
-

Already have an account ? Sign In

-
-
- + + ); + }} +
-
+
- + {/* */}
diff --git a/src/component/authorization/UserExist.jsx b/src/component/authorization/UserExist.jsx index c742ac3..b4f4cb5 100644 --- a/src/component/authorization/UserExist.jsx +++ b/src/component/authorization/UserExist.jsx @@ -1,19 +1,103 @@ import React, { useEffect, useState } from 'react' -import { Outlet } from 'react-router-dom' +import { useDispatch, useSelector } from "react-redux"; +import { Outlet, useNavigate } from 'react-router-dom' +import { updateUserDetails } from "../../store/UserDetails"; import MainLoaderBS from '../loaders/MainLoaderBS' import Layout from '../layout/Layout' +import siteLinks from '../../links/siteLinks' + +import debounceFunction from '../../utils/debounceFunction' +import { accountDashboard } from '../../services/services'; +import { SocketContextValues } from '../context/SocketIOContext'; export default function UserExist() { - const [loading, setLoading] = useState(true) + const {joinRoom} = SocketContextValues() // Destructures values from socket context + + const navigate = useNavigate() + const [loading, setLoading] = useState(true) + + const dispatch = useDispatch() + + const [lastActivityTime, setLastActivityTime] = useState(Date.now()); // HOLDS THE INITIAL TIME USER LOGS IN + + const { userDetails: { lastname }} = useSelector((state) => state?.userDetails); // CHECKS IF USER Details are avaliable, to determine if user is active + + let loggedIn = lastname ? true : false; // variable to determine if user is logged in + // console.log('loggedIn', loggedIn) + + // Function to log the user out + const logoutUser = () => { + localStorage.clear() + navigate(siteLinks.login) + window.location.reload() + }; + + // Function to reset the activity time + const resetTimer = () => { + debounceFunction(setLastActivityTime(Date.now()), 1000) + }; + useEffect(()=>{ const timer = setTimeout(()=>{ - setLoading(false) - },1000) + if(Date.now() - Number(lastActivityTime) >= Number(process.env.REACT_APP_TIMEOUT)){ + logoutUser() + } + }, Number(process.env.REACT_APP_TIMEOUT)) + + // Listen for activity events + const events = ['mousemove', 'keydown', 'click', 'scroll', 'touchstart']; + + // Adding event listeners + events.forEach(event => { + window.addEventListener(event, resetTimer); + }); + + return () => { + clearTimeout(timer) + events.forEach(event => { + window.removeEventListener(event, resetTimer); + }) + } + },[lastActivityTime]) + + useEffect(()=>{ + accountDashboard().then(res => { + const {dash_data} = res?.data + setLoading(false) + dispatch(updateUserDetails({ ...dash_data })); + }).catch(err => { + navigate(siteLinks.login) + setLoading(false) + }) + },[]) - return () => clearTimeout(timer) + + // useEffect(()=>{ + // let token = localStorage.getItem('token') + // const timer = setTimeout(()=>{ + // if(token && loggedIn){ + // setLoading(false) + // }else if(token && !loggedIn){ + // const data = {token} + // dispatch(updateUserDetails({ ...data })); + // setLoading(false) + // // dispatch(updateUserDetails({ ...res.data })); + // }else{ + // navigate('auth/login') + // } + // },1000) + + // return () => clearTimeout(timer) + // },[]) + + useEffect(()=>{ + if(localStorage.getItem('room')){ + joinRoom(localStorage.getItem('room')); + joinRoom("merms_global_events"); // global room for all + } },[]) return ( diff --git a/src/component/calendar/Calendar.jsx b/src/component/calendar/Calendar.jsx index 350dd35..9aa4175 100644 --- a/src/component/calendar/Calendar.jsx +++ b/src/component/calendar/Calendar.jsx @@ -1,14 +1,112 @@ -import React from "react"; +import React, { useCallback, useState } from "react"; import BreadcrumbComBS from "../breadcrumb/BreadcrumbComBS"; +import EventCalendar from "./EventCalendar"; export default function Calendar(){ + const [draggedEvent, setDraggedEvent] = useState('undroppable') + const handleDragStart = useCallback((event) => setDraggedEvent(event), []) + + // const dummyEvents = [ + // {id: '1', title: 'Family Vacation', color: 'fc-event-primary', start: new Date('2024-12-18'), end: new Date('2024-12-18'), isAllDay: false, resource: ''}, + // {id: '2', title: 'Meeting In Office', color: 'fc-event-warning', start: new Date('2024-12-19'), end: new Date('2024-12-19'), isAllDay: false, resource: ''}, + // {id: '3', title: 'Client Call', color: 'fc-event-danger', start: new Date('2024-12-20'), end: new Date('2024-12-20'), isAllDay: false, resource: ''}, + // {id: '4', title: 'Interview', color: 'fc-event-success', start: new Date('2024-12-21'), end: new Date('2024-12-21'), isAllDay: false, resource: ''} + // ] + const dummyEvents = [ + {id: '1', title: 'Family Vacation', color: 'fc-event-primary', isAllDay: false}, + {id: '2', title: 'Meeting In Office', color: 'fc-event-warning', isAllDay: false}, + {id: '3', title: 'Client Call', color: 'fc-event-danger', isAllDay: false}, + {id: '4', title: 'Interview', color: 'fc-event-success', isAllDay: false} + ] + return( <>
-
Coming Soon
+
+
+
+
+

Event Calendar

+
+
+
+
+
+
+ +

+ Drag and drop your event or click in the calendar. +

+ {dummyEvents.map((item, index) => ( +
+ handleDragStart({...item}) + } + > + {item.title} +
+ ))} +
+ + +
+
+
+
+
+ +
+
+
+ +
+
+
+
+ + {/* Event Modal */} + ) diff --git a/src/component/calendar/EventCalendar.jsx b/src/component/calendar/EventCalendar.jsx new file mode 100644 index 0000000..5d03708 --- /dev/null +++ b/src/component/calendar/EventCalendar.jsx @@ -0,0 +1,130 @@ +import React, { useCallback, useState } from 'react' +import { Calendar, dayjsLocalizer } from 'react-big-calendar' +import dayjs from 'dayjs' + +import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop' + + + +const localizer = dayjsLocalizer(dayjs) + +const DnDCalendar = withDragAndDrop(Calendar) + +export default function EventCalendar({draggedEvent, setDraggedEvent}) { + const myEventsList = [] + const [myEvents, setMyEvents] = useState(myEventsList) + + const moveEvent = useCallback( + ({ event, start, end, isAllDay: droppedOnAllDaySlot = false }) => { + // const { isAllDay } = event + // if (!allDay && droppedOnAllDaySlot) { + // event.allDay = true + // } + // if (allDay && !droppedOnAllDaySlot) { + // event.allDay = false; + // } + + setMyEvents((prev) => { + const existing = prev.find((ev) => ev.id === event.id) ?? {} + const filtered = prev.filter((ev) => ev.id !== event.id) + return [...filtered, { ...existing, start, end, allDay: event.allDay }] + }) + }, + [setMyEvents] + ) + const [displayDragItemInCell, setDisplayDragItemInCell] = useState(true) + + + const dragFromOutsideItem = useCallback(() => draggedEvent === 'undroppable' ? null : draggedEvent, [draggedEvent]) + + const customOnDragOverFromOutside = useCallback( + (dragEvent) => { + // check for undroppable is specific to this example + // and not part of API. This just demonstrates that + // onDragOver can optionally be passed to conditionally + // allow draggable items to be dropped on cal, based on + // whether event.preventDefault is called + if (draggedEvent !== 'undroppable') { + console.log('preventDefault') + dragEvent.preventDefault() + } + }, + [draggedEvent] + ) + + + const eventPropGetter = useCallback( + (event) => ({ + ...(event.isDraggable + ? { className: 'isDraggable' } + : { className: 'nonDraggable' }), + }), + [] + ) + + const newEvent = useCallback( + (event) => { + setMyEvents((prev) => { + const idList = prev.map((item) => item.id) + const newId = Math.max(...idList) + 1 + // return [...prev, { ...event, id: newId }] + return [...prev, { ...event}] + }) + }, + [setMyEvents] + ) + + const onDropFromOutside = useCallback( + ({ start, end, allDay: isAllDay }) => { + if (draggedEvent === 'undroppable') { + setDraggedEvent(null) + return + } + + const { title, id } = draggedEvent + const event = { + title: title, + start, + end, + isAllDay, + id + } + setDraggedEvent(null) + newEvent(event) + }, + [draggedEvent, setDraggedEvent, newEvent] + ) + + const resizeEvent = useCallback( + ({ event, start, end }) => { + setMyEvents((prev) => { + const existing = prev.find((ev) => ev.id === event.id) ?? {} + const filtered = prev.filter((ev) => ev.id !== event.id) + return [...filtered, { ...existing, start, end }] + }) + }, + [setMyEvents] + ) + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/src/component/comments/Comments.js b/src/component/comments/Comments.js index aa9c8ab..2486032 100644 --- a/src/component/comments/Comments.js +++ b/src/component/comments/Comments.js @@ -1,6 +1,6 @@ import React from "react"; import BreadcrumbComBS from "../breadcrumb/BreadcrumbComBS"; - +import getImage from "../../utils/getImage"; export default function Comments(){ @@ -8,7 +8,364 @@ export default function Comments(){ <>
-
Coming Soon
+ {/*
Coming Soon
*/} +
+
+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+ + + + + + + + + +
+
+
+
+
+
+
+ user +
+
+

Dutca Patrick

+

30 Min ago

+
+
+
+
+

Landing page Designer...

+
+
+ {/**/} + {/**/} +
+
+
+

hey adminjon...

+

I truly believe Augustine’s words are true and if you look at history you know it is true. There are many people in the world with amazing talents who realize only a small percentage of their potential. We all know people who live this truth.

+

We also know those epic stories, those modern-day legends surrounding the early failures of such supremely successful folks as Michael Jordan and Bill Gates. We can look a bit further back in time to Albert Einstein or even further back to Abraham Lincoln. What made each of these people so successful? Motivation.

+

We know this in our gut, but what can we do about it? How can we motivate ourselves? One of the most difficult aspects of achieving success is staying motivated over the long haul.

+
+

Have lovely Day,

+

adminjon

+
+
+
+ {/*
*/} + {/*
*/} + {/*
*/} + {/* */} + {/*

Wireframe

*/} + {/*

(220.MB)

*/} + {/*
*/} + {/*
*/} + {/* */} + {/*
*/} +
+
+

Click here to ReplyorForward

+ +
+
+
+ +
+
+
+
+ +
+
+
+
+
    +
  • +
  • +
  • +
+
+
+ Send +
+
+
+
+
+
+
+
) diff --git a/src/component/contacts/Contacts.js b/src/component/contacts/Contacts.js index 93cadba..734d134 100644 --- a/src/component/contacts/Contacts.js +++ b/src/component/contacts/Contacts.js @@ -1,6 +1,6 @@ import React from "react"; import BreadcrumbComBS from "../breadcrumb/BreadcrumbComBS"; - +import getImage from "../../utils/getImage"; export default function Contacts(){ @@ -8,7 +8,355 @@ export default function Contacts(){ <>
-
Coming Soon
+ {/*
Coming Soon
*/} +
+
+
+
+ +
+
+
+
+
+ {/*
*/} + {/* */} + {/* */} + {/*
*/} +
+
+
+
+ + + + + + + + + +
+
+
+
+
+
+
+ user +
+
+

Dutca Patrick

+

30 Min ago

+
+
+
+
+

Landing page Designer...

+
+
+ {/**/} + {/**/} +
+
+
+

hey adminjon...

+

I truly believe Augustine’s words are true and if you look at history you know it is true. There are many people in the world with amazing talents who realize only a small percentage of their potential. We all know people who live this truth.

+

We also know those epic stories, those modern-day legends surrounding the early failures of such supremely successful folks as Michael Jordan and Bill Gates. We can look a bit further back in time to Albert Einstein or even further back to Abraham Lincoln. What made each of these people so successful? Motivation.

+

We know this in our gut, but what can we do about it? How can we motivate ourselves? One of the most difficult aspects of achieving success is staying motivated over the long haul.

+ {/*
*/} + {/*

Have lovely Day,

*/} + {/*

adminjon

*/} + {/*
*/} +
+
+ +
+
+

Click here to ReplyorForward

+ +
+
+
+ +
+
+
+
+ +
+
+
+
+ {/*
    */} + {/*
  • */} + {/*
  • */} + {/*
  • */} + {/*
*/} +
+
+ {/*Send */} +
+
+
+
+
+
+
+
) diff --git a/src/component/context/SocketIOContext.jsx b/src/component/context/SocketIOContext.jsx new file mode 100644 index 0000000..1643d74 --- /dev/null +++ b/src/component/context/SocketIOContext.jsx @@ -0,0 +1,80 @@ +import { useQueryClient } from "@tanstack/react-query"; +import React, { createContext, useContext, useEffect, useState } from "react"; +import { Outlet } from "react-router-dom"; +import { io } from "socket.io-client"; +import queryKeys from "../../services/queryKeys"; +import { socketEmitEvents, socketOnEvents } from "./socketEvents"; +// import io from "socket.io-client"; + +// import { tableReload } from "../../store/TableReloads"; +// import { useDispatch, useSelector } from "react-redux"; + + +let SocketIOContext = createContext({}) + +export default function SocketIOContextProvider({children}) { + const queryClient = useQueryClient() + // const {userDetails} = useSelector((state) => state?.userDetails); // CHECKS IF USER UID, to determine if user is active + + // const dispatch = useDispatch() + + const socket = io.connect(process.env.REACT_APP_SOCKET_URL); + + // // Messages States + // const [message, setMessage] = useState(""); + const [socketMsgReceived, setSocketMsgReceived] = useState(""); + + const joinRoom = (room) => { + if (room !== "") { + return socket.emit("join_room", room); + } + return + }; + + const sendMessage = (eventType, message, room) => { + if(message && room){ + socket.emit(eventType, { message, room }); + } + }; + + useEffect(() => { + socket.on(socketOnEvents.receive_message, (data) => { + // setSocketMsgReceived(data.message); + // dispatch(tableReload({type:'CHATMESSAGELIST'})) // dispatches to update chat message sending from owner to worker and vice versa + console.log('DATA', data) + queryClient.refetchQueries({ + queryKey: [...queryKeys.recentAction], + // type: 'active', + // exact: true, + }) + }); + + // client-side + socket.on("connect", () => { + console.log(socket.id); + }); + + socket.on("disconnect", () => { + console.log(socket.id); + }); + }, [socket]); + + let values = { + socket, + sendMessage, + socketMsgReceived, + setSocketMsgReceived, + joinRoom, + } + + return ( + + + + ) +} + + +export const SocketContextValues = () => { + return useContext(SocketIOContext) +} diff --git a/src/component/context/socketEvents.js b/src/component/context/socketEvents.js new file mode 100644 index 0000000..d3b198c --- /dev/null +++ b/src/component/context/socketEvents.js @@ -0,0 +1,9 @@ +export const socketEmitEvents = { + send_message: 'send_message' +} + + + +export const socketOnEvents = { + receive_message: 'receive_message' +} \ No newline at end of file diff --git a/src/component/home/HomeSections.jsx b/src/component/home/HomeSections.jsx index 584aaea..c525ec9 100644 --- a/src/component/home/HomeSections.jsx +++ b/src/component/home/HomeSections.jsx @@ -1,9 +1,19 @@ -import React from "react"; +import React, { useEffect } from "react"; import BreadcrumbComBS from "../breadcrumb/BreadcrumbComBS"; - +import RecentActions from "./RecentActions"; +import Products from "./Products"; +import TopBar from "./TopBar"; +import ProductsURL from "./ProductsURL"; +import { SocketContextValues } from "../context/SocketIOContext"; export default function HomeSections(){ + const {sendMessage} = SocketContextValues() // Destructures values from socket context + + // useEffect(()=>{ + // sendMessage('Hello socket', '2020aklksod') + // },[]) + return <> {/*
@@ -29,424 +39,50 @@ export default function HomeSections(){
*/}
-
-
-
-
-
-
-
-

$65,456

- Last 6 months -
-
-
Revenue
- N/A -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

52%

- Past 12 hours -
-
-
Conversion Rate
- 5.35% -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

605

- Last 90 days -
-
-
Transactions
- 4.65% -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

5,687

- Last 3 months -
-
-
Purchases
- 9.89% -
-
-
-
-
-
-
-
-
-
+
-
-
-

My Products

-
-
-
-
-
-
- -
-
-

Annual Sales

-

15,236

-
-
-
-
-
-
- -
-
-

Annual Revenue

-

$40,516

-
-
-
-
-
-
-
-
-
+
-
-
-
-

Recent Actions

-
-
- {/**/} -
-
-
-
-
-

Last Update

-

10-10-2021 10 AM

-
-
- {/**/} -
-
-
-
-
-
-
-
-
-
-

Overdue

-

$1596

-
-
-

Outstanding

-

$2586

-
-
-

Open

-

$5678

-
-
-

Paid

-

$2458

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
No.NameDateTotalStatus
1Smith Drake27/3/2014$1,00,000 - -
2Martha Doe28/3/2015$70,000 - -
3Fenish Paul24/3/2015$60,000 - -
4Albom Mitch29/3/2016$60,000 - -
-
-
-
+
-
-
-
-
-

Top selling products

-
-
- {/*Export */} -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - -
#NamePriceIn stockStatusAction
1Cold Shoulder Bling Dress$65.342 -
-
-
-
Active
2PlayStation 4 Pro 1TB Console$47.655 -
-
-
-
Active
3Extra Fine Wool Jumpers$56.479 -
-
-
-
Canceled
4Long Sleeve Bow Top$04.786 -
-
-
-
Active
5Shine Stripe Long Sleeve Ruffle$23.456 -
-
-
-
Active
6Long Sleeve Micro Thermal Shirt$65.598 -
-
-
-
info
#NamePriceIn stockStatusAction
-
-
-
-
-
+
-

Lifetime sales

-
-
- - +

Payments

+ {/*
*/} + {/* */} + {/*
*/} + {/*
Action
*/} + {/* View*/} + {/* reports*/} + {/* Edit reports*/} + {/* Statistics*/} + {/*
Export
*/} + {/* Export*/} + {/* to PDF*/} + {/* Export*/} + {/* to CSV*/} + {/*
*/} + {/*
*/}
-
We only started collecting data from February 2019
-

Questions about the Net Earnings number? Click here

+ {/*
We only started collecting data from February 2019
*/} + {/*

Questions about the Net Earnings number? Click here

*/}
-
-
-
-
-
-
-
-

680

-

Total sales

-
-
-

800

-

Open campaign

-
-
-

500

-

Daily sales

-
-
+ .
diff --git a/src/component/home/Products.jsx b/src/component/home/Products.jsx new file mode 100644 index 0000000..67d3893 --- /dev/null +++ b/src/component/home/Products.jsx @@ -0,0 +1,75 @@ +import { useQuery } from '@tanstack/react-query' +import React from 'react' +import { productData } from '../../services/services' +import queryKeys from '../../services/queryKeys' +import productPath from "../../utils/productpath"; +import { Link } from 'react-router-dom'; +export default function Products() { + + const {data, isFetching, isError, error} = useQuery({ + queryKey: queryKeys.product, + queryFn: () => productData() + }) + + const products = data?.data?.products_data?.products + + return ( + <> +
+
+

My Products

+
+
+ {isFetching ? + <> +
+
+

Loading...

+
+
+ + : isError ? +
+
+

{error.message}

+
+
+ : +
+ {products && products.map((product, index) => ( +
+ +
+
+ +
+
+

{product?.status}

+

{product?.description}

+
+
+ + +
+ ))} + {/*
*/} + {/*
*/} + {/*
*/} + {/* */} + {/*
*/} + {/*
*/} + {/*

Annual Revenue

*/} + {/*

$40,516

*/} + {/*
*/} + {/*
*/} + {/*
*/} +
+ } +
+
+
+
+
+ + ) +} diff --git a/src/component/home/ProductsURL.jsx b/src/component/home/ProductsURL.jsx new file mode 100644 index 0000000..baafc4e --- /dev/null +++ b/src/component/home/ProductsURL.jsx @@ -0,0 +1,86 @@ +import React from 'react' +import { productsURL } from '../../services/services' +import { useQuery } from '@tanstack/react-query' +import queryKeys from '../../services/queryKeys' + +export default function ProductsURL() { + + const {data:data, isFetching, isError, error} = useQuery({ + queryKey: queryKeys.product_url, + queryFn: () => productsURL() + }) + + const urlData = data?.data?.url_data?.url + + return ( + <> +
+
+
+
+

My Product URLs

+
+
+ {/*Export */} +
+
+
+
+ {isFetching ? + <> +
+
+

Loading...

+
+
+ + : isError ? +
+
+

{error.message}

+
+
+ : + + + + + + + + + + + + {urlData && urlData.map((item, index) => { + let statusColor = item?.status == 'Active' ? 'badge-success-inverse' : item?.status == 'Updating' ? 'badge-success-inverse' : item?.status == 'Refreshing' ? 'badge-danger-inverse' : 'badge-info-inverse' + return ( + + + + + + + + ) + })} + + + + + + + + + + + +
#DescriptionStatusAction
{Number(item?.no) + Number(index)}{item?.description} - {item?.url}{item?.status}
#NamePriceIn stockStatusAction
+ } +
+
+
+
+ + ) +} diff --git a/src/component/home/RecentActions.jsx b/src/component/home/RecentActions.jsx new file mode 100644 index 0000000..5ec76a2 --- /dev/null +++ b/src/component/home/RecentActions.jsx @@ -0,0 +1,113 @@ +import React from 'react' +import { useQuery } from "@tanstack/react-query"; +import { recentActions } from "../../services/services"; +import queryKeys from "../../services/queryKeys"; + +export default function RecentActions() { + + const {data:dataAction, isFetching, isError, error} = useQuery({ + queryKey: queryKeys.recentAction, + queryFn: () => recentActions() + }) + + const actionData = dataAction?.data?.action_data + + return ( + <> +
+
+
+

Recent Actions

+
+ {/*
+ +
*/} +
+
+ {isFetching ? + <> +
+
+

Loading...

+
+
+ + : isError ? +
+
+

{error.message}

+
+
+ : + <> +
+
+

Last Update

+

{dataAction?.data?.action_data?.last_update}

+
+
+ {/**/} +
+
+
+
+
+
+
+
+
+
+

Initial

+

{actionData?.initial}

+
+
+

Processing

+

{actionData?.processing}

+
+
+

Verifying

+

{actionData?.verifying}

+
+
+

Completed

+

{actionData?.completed}

+
+
+
+ + + + + + + + + + + {actionData && actionData?.actions.map((action, index) => { + let bgColor = action?.status == 'completed' ? 'badge-success-inverse' : action?.status == 'verifying' ? 'badge-info-inverse' : action?.status == 'processing' ? 'badge-warning-inverse' : 'badge-primary-inverse' + return ( + + + + + + + ) + })} + +
#.DescriptionDateStatus
{action?.no}{action?.description}{action?.date} + +
+
+ + } +
+
+ + ) +} diff --git a/src/component/home/TopBar.jsx b/src/component/home/TopBar.jsx new file mode 100644 index 0000000..f2f1fd9 --- /dev/null +++ b/src/component/home/TopBar.jsx @@ -0,0 +1,67 @@ +import { useQuery } from '@tanstack/react-query' +import React from 'react' +import { topBar } from '../../services/services' +import queryKeys from '../../services/queryKeys' + +export default function TopBar() { + + const {data, isFetching, isError, error} = useQuery({ + queryKey: queryKeys.topBar, + queryFn: () => topBar() + }) + + const topData = data?.data?.bar_data?.top_bar + console.log('TOP', topData) + + return ( + <> + {isFetching ? + <> +
+
+

Loading...

+
+
+ + : isError ? +
+
+

{error.message}

+
+
+ : + <> + {topData && topData?.map((item, index)=>{ + let textColor = item?.description == 'Contacts' ? 'text-danger' : item?.description == 'Site Traffic' ? 'text-primary' : item?.description == 'Appointments' ? 'text-orange' : 'text-success' + return ( +
+
+
+
+
+
+
+

{item?.value || 0}

+ {item?.data_span} +
+
+
{item?.description}
+ N/A +
+
+
+
+
+
+
+
+
+
+ ) + })} + + } + + ) +} diff --git a/src/component/layout/layoutcom/UserHeader.jsx b/src/component/layout/layoutcom/UserHeader.jsx index 56d9f20..bf0aff0 100644 --- a/src/component/layout/layoutcom/UserHeader.jsx +++ b/src/component/layout/layoutcom/UserHeader.jsx @@ -1,46 +1,74 @@ import React from "react"; import getImage from "../../../utils/getImage"; +import { useNavigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import siteLinks from "../../../links/siteLinks"; export default function UserHeader(){ + + const { userDetails } = useSelector((state) => state?.userDetails); // CHECKS IF USER Details are avaliable, to determine if user is active + + const toggleSidebar = (e) => { + e.preventDefault() + document.body.classList.toggle('sidebar-toggled') + } + + const removeSidebar = (e) => { + e.preventDefault() + document.body.classList.remove('sidebar-toggled') + } + + // const toggleSidebarMini = (e) => { + // e.preventDefault() + // } + + const navigate = useNavigate() + + const logout = () => { + localStorage.clear() + navigate(siteLinks.login, {replace: true}) + window.location.reload() + } + return (