diff --git a/README.md b/README.md index accdfc8..e69905f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # Daisy UI Admin Dashboard Template - DashWind [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) -This is a free admin dashboard template that uses **Daisy UI** and React js. It has **fully customizable and themable CSS** CSS and is powered by Tailwind CSS utility classes. Additionally, it comes with **redux toolkit** and other libraries already set up. +This is a free admin dashboard template that uses **Daisy UI** and React js. It has **fully customizable and themable CSS** CSS and is powered by Tailwind CSS utility classes. Additionally, it comes with **zustand** and other libraries already set up. ## Preview @@ -21,7 +21,7 @@ This is a free admin dashboard template that uses **Daisy UI** and React js. It - **Light/dark** mode toggle - Token based user **authentication** - **Submenu support** in sidebar -- Store management using **redux toolkit** +- Store management using **zustand** - **Daisy UI** components and **Tailwind** support - **Right and left sidebar**, Universal loader, notifications - **Calendar**, global modal, **chart js 2** and other components @@ -47,7 +47,7 @@ Go to project directory and run (make sure you have node installed first) - [Tailwind CSS v3.3.6](https://tailwindcss.com/) - [Daisy UI v4.4.19](https://daisyui.com/) - [HeroIcons](https://heroicons.com/) -- [Redux toolkit v1.9](https://redux-toolkit.js.org/) +- [Zustand v5.0.3](https://zustand-demo.pmnd.rs/) - [React ChartJS 2 v5](https://react-chartjs-2.js.org/) ## Documentation diff --git a/package-lock.json b/package-lock.json index 53bd284..8e9f754 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "ISC", "dependencies": { "@heroicons/react": "^2.0.13", - "@reduxjs/toolkit": "^1.9.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -23,12 +22,12 @@ "react-chartjs-2": "^5.0.1", "react-dom": "^18.2.0", "react-notifications": "^1.7.4", - "react-redux": "^8.0.5", "react-router-dom": "^6.4.3", "react-scripts": "5.0.1", "react-tailwindcss-datepicker": "^1.6.0", "theme-change": "^2.2.0", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^5.0.3" }, "devDependencies": { "@tailwindcss/typography": "^0.5.8", @@ -2900,28 +2899,6 @@ } } }, - "node_modules/@reduxjs/toolkit": { - "version": "1.9.0", - "license": "MIT", - "dependencies": { - "immer": "^9.0.16", - "redux": "^4.2.0", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.7" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, "node_modules/@remix-run/router": { "version": "1.0.3", "license": "MIT", @@ -3558,14 +3535,6 @@ "@types/node": "*" } }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "license": "MIT", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "license": "MIT" @@ -3918,10 +3887,6 @@ "version": "2.0.2", "license": "MIT" }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "license": "MIT" - }, "node_modules/@types/ws": { "version": "8.5.3", "license": "MIT", @@ -7982,17 +7947,6 @@ "he": "bin/he" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" - }, "node_modules/hoopy": { "version": "0.1.4", "license": "MIT", @@ -13225,47 +13179,6 @@ "node": ">=0.4.0" } }, - "node_modules/react-redux": { - "version": "8.0.5", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "node_modules/react-redux/node_modules/react-is": { - "version": "18.2.0", - "license": "MIT" - }, "node_modules/react-refresh": { "version": "0.11.0", "license": "MIT", @@ -13446,20 +13359,6 @@ "node": ">=8" } }, - "node_modules/redux": { - "version": "4.2.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-thunk": { - "version": "2.4.2", - "license": "MIT", - "peerDependencies": { - "redux": "^4" - } - }, "node_modules/regenerate": { "version": "1.4.2", "license": "MIT" @@ -13585,10 +13484,6 @@ "version": "1.0.0", "license": "MIT" }, - "node_modules/reselect": { - "version": "4.1.7", - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -15086,6 +14981,8 @@ "node_modules/use-sync-external-store": { "version": "1.2.0", "license": "MIT", + "optional": true, + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -16007,6 +15904,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } }, "dependencies": { @@ -17626,15 +17552,6 @@ "source-map": "^0.7.3" } }, - "@reduxjs/toolkit": { - "version": "1.9.0", - "requires": { - "immer": "^9.0.16", - "redux": "^4.2.0", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.7" - } - }, "@remix-run/router": { "version": "1.0.3" }, @@ -18017,13 +17934,6 @@ "@types/node": "*" } }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "@types/html-minifier-terser": { "version": "6.1.0" }, @@ -18276,9 +18186,6 @@ "@types/trusted-types": { "version": "2.0.2" }, - "@types/use-sync-external-store": { - "version": "0.0.3" - }, "@types/ws": { "version": "8.5.3", "requires": { @@ -20742,17 +20649,6 @@ "he": { "version": "1.2.0" }, - "hoist-non-react-statics": { - "version": "3.3.2", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1" - } - } - }, "hoopy": { "version": "0.1.4" }, @@ -23741,22 +23637,6 @@ } } }, - "react-redux": { - "version": "8.0.5", - "requires": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "dependencies": { - "react-is": { - "version": "18.2.0" - } - } - }, "react-refresh": { "version": "0.11.0" }, @@ -23876,16 +23756,6 @@ "strip-indent": "^3.0.0" } }, - "redux": { - "version": "4.2.0", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "redux-thunk": { - "version": "2.4.2", - "requires": {} - }, "regenerate": { "version": "1.4.2" }, @@ -23965,9 +23835,6 @@ "requires-port": { "version": "1.0.0" }, - "reselect": { - "version": "4.1.7" - }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -24877,6 +24744,8 @@ }, "use-sync-external-store": { "version": "1.2.0", + "optional": true, + "peer": true, "requires": {} }, "util-deprecate": { @@ -25487,6 +25356,12 @@ }, "yocto-queue": { "version": "0.1.0" + }, + "zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "requires": {} } } } diff --git a/package.json b/package.json index 4caceb2..312150e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "admin-dashboard-template-dashwind", "version": "1.0.0", - "description": "Admin Dashboard template built with create-react-app, tailwind css and daisy UI. Template uses rich tailwind css utility classes and have components of daisy UI, also have redux toolkit implemented for store management.", + "description": "Admin Dashboard template built with create-react-app, tailwind css and daisy UI. Template uses rich tailwind css utility classes and have components of daisy UI, also have Zustand implemented for store management.", "scripts": { "start": "react-scripts start", "build": "react-scripts build", @@ -10,7 +10,6 @@ }, "dependencies": { "@heroicons/react": "^2.0.13", - "@reduxjs/toolkit": "^1.9.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -23,12 +22,12 @@ "react-chartjs-2": "^5.0.1", "react-dom": "^18.2.0", "react-notifications": "^1.7.4", - "react-redux": "^8.0.5", "react-router-dom": "^6.4.3", "react-scripts": "5.0.1", "react-tailwindcss-datepicker": "^1.6.0", "theme-change": "^2.2.0", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^5.0.3" }, "repository": { "type": "git", @@ -39,7 +38,7 @@ "tailwind-css", "starter-kit", "saas-starter-kit", - "reduxt-toolkit-dashboard-template", + "zustand-dashboard-template", "daisyui-template", "dashboard-template", "react-router", diff --git a/public/index.html b/public/index.html index e2bd832..0c50ee2 100644 --- a/public/index.html +++ b/public/index.html @@ -25,7 +25,7 @@ Learn how to configure a non-root public URL by running `npm run build`. --> Daisy UI Admin Dashboard Template - DashWind - + diff --git a/src/app/store.js b/src/app/store.js deleted file mode 100644 index 1decc15..0000000 --- a/src/app/store.js +++ /dev/null @@ -1,16 +0,0 @@ -import { configureStore } from '@reduxjs/toolkit' -import headerSlice from '../features/common/headerSlice' -import modalSlice from '../features/common/modalSlice' -import rightDrawerSlice from '../features/common/rightDrawerSlice' -import leadsSlice from '../features/leads/leadSlice' - -const combinedReducer = { - header : headerSlice, - rightDrawer : rightDrawerSlice, - modal : modalSlice, - lead : leadsSlice -} - -export default configureStore({ - reducer: combinedReducer -}) \ No newline at end of file diff --git a/src/app/store_zustand.js b/src/app/store_zustand.js new file mode 100644 index 0000000..46aef80 --- /dev/null +++ b/src/app/store_zustand.js @@ -0,0 +1,117 @@ +import { create } from "zustand"; +import axios from "axios"; + +const useStore = create((set, get) => ({ + // Initial state combined from slices + header: { + pageTitle: "Home", + noOfNotifications: 15, + newNotificationMessage: "", + newNotificationStatus: 1, + }, + modal: { + title: "", + isOpen: false, + bodyType: "", + size: "md", // Default size + extraObject: {}, + }, + rightDrawer: { + header: "", + isOpen: false, + bodyType: "", + extraObject: {}, + }, + lead: { + isLoading: false, + leads: [], + }, + + // Header actions + setPageTitle: (title) => + set((state) => ({ header: { ...state.header, pageTitle: title } })), + removeNotificationMessage: () => + set((state) => ({ + header: { ...state.header, newNotificationMessage: "" }, + })), + showNotification: ({ message, status }) => + set((state) => ({ + header: { + ...state.header, + newNotificationMessage: message, + newNotificationStatus: status, + }, + })), + + // Modal actions + openModal: ({ title, bodyType, extraObject, size }) => + set((state) => ({ + modal: { + ...state.modal, + isOpen: true, + title, + bodyType, + size: size || "md", + extraObject, + }, + })), + closeModal: () => + set((state) => ({ + modal: { + ...state.modal, + isOpen: false, + bodyType: "", + title: "", + extraObject: {}, + }, + })), + + // Right Drawer actions + openRightDrawer: ({ header, bodyType, extraObject }) => + set((state) => ({ + rightDrawer: { + ...state.rightDrawer, + isOpen: true, + header, + bodyType, + extraObject, + }, + })), + closeRightDrawer: () => + set((state) => ({ + rightDrawer: { + ...state.rightDrawer, + isOpen: false, + bodyType: "", + header: "", + extraObject: {}, + }, + })), + + // Lead actions + addNewLead: (newLeadObj) => + set((state) => ({ + lead: { ...state.lead, leads: [...state.lead.leads, newLeadObj] }, + })), + deleteLead: (index) => + set((state) => { + const newLeads = [...state.lead.leads]; + newLeads.splice(index, 1); + return { lead: { ...state.lead, leads: newLeads } }; + }), + getLeadsContent: async () => { + set((state) => ({ lead: { ...state.lead, isLoading: true } })); + try { + const response = await axios.get("/api/users?page=2"); // Assuming the API endpoint remains the same + set((state) => ({ + lead: { ...state.lead, leads: response.data.data, isLoading: false }, + })); + } catch (error) { + console.error("Failed to fetch leads:", error); + set((state) => ({ lead: { ...state.lead, isLoading: false } })); + // Optionally, handle the error state in the store + } + }, +})); + +export default useStore; diff --git a/src/containers/Header.js b/src/containers/Header.js index 0988c4c..cd22e07 100644 --- a/src/containers/Header.js +++ b/src/containers/Header.js @@ -1,68 +1,67 @@ -import { themeChange } from 'theme-change' -import React, { useEffect, useState } from 'react' -import { useSelector, useDispatch } from 'react-redux' -import BellIcon from '@heroicons/react/24/outline/BellIcon' -import Bars3Icon from '@heroicons/react/24/outline/Bars3Icon' -import MoonIcon from '@heroicons/react/24/outline/MoonIcon' -import SunIcon from '@heroicons/react/24/outline/SunIcon' -import { openRightDrawer } from '../features/common/rightDrawerSlice'; -import { RIGHT_DRAWER_TYPES } from '../utils/globalConstantUtil' - -import { NavLink, Routes, Link , useLocation} from 'react-router-dom' - - -function Header(){ - - const dispatch = useDispatch() - const {noOfNotifications, pageTitle} = useSelector(state => state.header) - const [currentTheme, setCurrentTheme] = useState(localStorage.getItem("theme")) - - useEffect(() => { - themeChange(false) - if(currentTheme === null){ - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ) { - setCurrentTheme("dark") - }else{ - setCurrentTheme("light") - } - } - // 👆 false parameter is required for react project - }, []) - - - // Opening right sidebar for notification - const openNotification = () => { - dispatch(openRightDrawer({header : "Notifications", bodyType : RIGHT_DRAWER_TYPES.NOTIFICATION})) +import { themeChange } from "theme-change"; +import React, { useEffect, useState } from "react"; +import useStore from "../app/store_zustand"; +import BellIcon from "@heroicons/react/24/outline/BellIcon"; +import Bars3Icon from "@heroicons/react/24/outline/Bars3Icon"; +import MoonIcon from "@heroicons/react/24/outline/MoonIcon"; +import SunIcon from "@heroicons/react/24/outline/SunIcon"; +import { RIGHT_DRAWER_TYPES } from "../utils/globalConstantUtil"; + +import { Link } from "react-router-dom"; + +function Header() { + const { noOfNotifications, pageTitle } = useStore((state) => state.header); + const openRightDrawer = useStore((state) => state.openRightDrawer); + + const [currentTheme, setCurrentTheme] = useState( + localStorage.getItem("theme") + ); + + useEffect(() => { + themeChange(false); + if (currentTheme === null) { + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + setCurrentTheme("dark"); + } else { + setCurrentTheme("light"); + } } - - - function logoutUser(){ - localStorage.clear(); - window.location.href = '/' - } - - return( - // navbar fixed flex-none justify-between bg-base-300 z-10 shadow-md - - <> -
- - - {/* Menu toogle for mobile view or small screen */} -
- -

{pageTitle}

-
- - - -
- - {/* Multiple theme selection, uncomment this if you want to enable multiple themes selection, + }, []); + + const openNotification = () => { + openRightDrawer({ + header: "Notifications", + bodyType: RIGHT_DRAWER_TYPES.NOTIFICATION, + }); + }; + + function logoutUser() { + localStorage.clear(); + window.location.href = "/"; + } + + return ( + <> +
+ {/* Menu toogle for mobile view or small screen */} +
+ +

{pageTitle}

+
+ +
+ {/* Multiple theme selection, uncomment this if you want to enable multiple themes selection, also includes corporate and retro themes in tailwind.config file */} - - {/* @@ -70,48 +69,72 @@ function Header(){ */} - - {/* Light and dark theme selection toogle **/} - - - - {/* Notification icon */} - - - - {/* Profile icon, opening menu on click */} -
- -
    -
  • - - Profile Settings - New - -
  • -
  • Bill History
  • -
    -
  • Logout
  • -
-
+ {/* Light and dark theme selection toogle **/} + + + {/* Notification icon */} +
- - - ) + + + {/* Profile icon, opening menu on click */} +
+ +
    +
  • + + Profile Settings + New + +
  • +
  • + Bill History +
  • +
    +
  • + Logout +
  • +
+
+
+
+ + ); } -export default Header \ No newline at end of file +export default Header; diff --git a/src/containers/Layout.js b/src/containers/Layout.js index e8cd62d..2481b2d 100644 --- a/src/containers/Layout.js +++ b/src/containers/Layout.js @@ -1,47 +1,56 @@ -import PageContent from "./PageContent" -import LeftSidebar from "./LeftSidebar" -import { useSelector, useDispatch } from 'react-redux' -import RightSidebar from './RightSidebar' -import { useEffect } from "react" -import { removeNotificationMessage } from "../features/common/headerSlice" -import {NotificationContainer, NotificationManager} from 'react-notifications'; -import 'react-notifications/lib/notifications.css'; -import ModalLayout from "./ModalLayout" - -function Layout(){ - const dispatch = useDispatch() - const {newNotificationMessage, newNotificationStatus} = useSelector(state => state.header) - +import PageContent from "./PageContent"; +import LeftSidebar from "./LeftSidebar"; +import useStore from "../app/store_zustand"; +import RightSidebar from "./RightSidebar"; +import { useEffect } from "react"; +import { + NotificationContainer, + NotificationManager, +} from "react-notifications"; +import "react-notifications/lib/notifications.css"; +import ModalLayout from "./ModalLayout"; + +function Layout() { + const { newNotificationMessage, newNotificationStatus } = useStore( + (state) => state.header + ); + const removeNotificationMessage = useStore( + (state) => state.removeNotificationMessage + ); useEffect(() => { - if(newNotificationMessage !== ""){ - if(newNotificationStatus === 1)NotificationManager.success(newNotificationMessage, 'Success') - if(newNotificationStatus === 0)NotificationManager.error( newNotificationMessage, 'Error') - dispatch(removeNotificationMessage()) - } - }, [newNotificationMessage]) - - return( - <> - { /* Left drawer - containing page content and side bar (always open) */ } -
- - - -
- - { /* Right drawer - containing secondary content like notifications list etc.. */ } - - - - {/** Notification layout container */} - + if (newNotificationMessage !== "") { + if (newNotificationStatus === 1) + NotificationManager.success(newNotificationMessage, "Success"); + if (newNotificationStatus === 0) + NotificationManager.error(newNotificationMessage, "Error"); + removeNotificationMessage(); + } + }, [newNotificationMessage, removeNotificationMessage]); + + return ( + <> + {/* Left drawer - containing page content and side bar (always open) */} +
+ + + +
+ + {/* Right drawer - containing secondary content like notifications list etc.. */} + + + {/** Notification layout container */} + {/* Modal layout container */} - - - - ) + + + ); } -export default Layout \ No newline at end of file +export default Layout; diff --git a/src/containers/LeftSidebar.js b/src/containers/LeftSidebar.js index b06fcb2..31399fb 100644 --- a/src/containers/LeftSidebar.js +++ b/src/containers/LeftSidebar.js @@ -1,57 +1,66 @@ -import routes from '../routes/sidebar' -import { NavLink, Routes, Link , useLocation} from 'react-router-dom' -import SidebarSubmenu from './SidebarSubmenu'; -import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon' -import { useDispatch } from 'react-redux'; +import routes from "../routes/sidebar"; +import { NavLink, Link, useLocation } from "react-router-dom"; +import SidebarSubmenu from "./SidebarSubmenu"; +import XMarkIcon from "@heroicons/react/24/outline/XMarkIcon"; -function LeftSidebar(){ - const location = useLocation(); +function LeftSidebar() { + const location = useLocation(); - const dispatch = useDispatch() + const close = (e) => { + document.getElementById("left-sidebar-drawer").click(); + }; + return ( +
+ +
    + - const close = (e) => { - document.getElementById('left-sidebar-drawer').click() - } - - return( -
    - -
      - - -
    • - - DashWind LogoDashWind
    • - { - routes.map((route, k) => { - return( -
    • - { - route.submenu ? - : - ( `${isActive ? 'font-semibold bg-base-200 ' : 'font-normal'}`} > - {route.icon} {route.name} - { - location.pathname === route.path ? () : null - } - ) - } - -
    • - ) - }) - } - -
    -
    - ) +
  • + + DashWind Logo + DashWind + {" "} +
  • + {routes.map((route, k) => { + return ( +
  • + {route.submenu ? ( + + ) : ( + + `${ + isActive ? "font-semibold bg-base-200 " : "font-normal" + }` + } + > + {route.icon} {route.name} + {location.pathname === route.path ? ( + + ) : null} + + )} +
  • + ); + })} +
+
+ ); } -export default LeftSidebar \ No newline at end of file +export default LeftSidebar; diff --git a/src/containers/ModalLayout.js b/src/containers/ModalLayout.js index 37a87f9..6376f44 100644 --- a/src/containers/ModalLayout.js +++ b/src/containers/ModalLayout.js @@ -1,46 +1,55 @@ -import { useEffect } from 'react' -import { MODAL_BODY_TYPES } from '../utils/globalConstantUtil' -import { useSelector, useDispatch } from 'react-redux' -import { closeModal } from '../features/common/modalSlice' -import AddLeadModalBody from '../features/leads/components/AddLeadModalBody' -import ConfirmationModalBody from '../features/common/components/ConfirmationModalBody' - - -function ModalLayout(){ - - - const {isOpen, bodyType, size, extraObject, title} = useSelector(state => state.modal) - const dispatch = useDispatch() - - const close = (e) => { - dispatch(closeModal(e)) - } - - - - return( - <> - {/* The button to open modal */} - - {/* Put this part before tag */} -
-
- -

{title}

- - - {/* Loading modal body according to different modal type */} - { - { - [MODAL_BODY_TYPES.LEAD_ADD_NEW] : , - [MODAL_BODY_TYPES.CONFIRMATION] : , - [MODAL_BODY_TYPES.DEFAULT] :
- }[bodyType] - } -
-
- - ) +import { MODAL_BODY_TYPES } from "../utils/globalConstantUtil"; +import useStore from "../app/store_zustand"; +import AddLeadModalBody from "../features/leads/components/AddLeadModalBody"; +import ConfirmationModalBody from "../features/common/components/ConfirmationModalBody"; + +function ModalLayout() { + const { isOpen, bodyType, size, extraObject, title } = useStore( + (state) => state.modal + ); + const closeModal = useStore((state) => state.closeModal); + + const close = (e) => { + closeModal(); + }; + + return ( + <> + {/* The button to open modal */} + + {/* Put this part before tag */} +
+
+ +

{title}

+ + {/* Loading modal body according to different modal type */} + { + { + [MODAL_BODY_TYPES.LEAD_ADD_NEW]: ( + + ), + [MODAL_BODY_TYPES.CONFIRMATION]: ( + + ), + [MODAL_BODY_TYPES.DEFAULT]:
, + }[bodyType] + } +
+
+ + ); } -export default ModalLayout \ No newline at end of file +export default ModalLayout; diff --git a/src/containers/PageContent.js b/src/containers/PageContent.js index 710307f..c525184 100644 --- a/src/containers/PageContent.js +++ b/src/containers/PageContent.js @@ -1,55 +1,53 @@ -import Header from "./Header" -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' -import routes from '../routes' -import { Suspense, lazy } from 'react' -import SuspenseContent from "./SuspenseContent" -import { useSelector } from 'react-redux' -import { useEffect, useRef } from "react" - -const Page404 = lazy(() => import('../pages/protected/404')) - - -function PageContent(){ - const mainContentRef = useRef(null); - const {pageTitle} = useSelector(state => state.header) - - - // Scroll back to top on new page load - useEffect(() => { - mainContentRef.current.scroll({ - top: 0, - behavior: "smooth" - }); - }, [pageTitle]) - - return( -
-
-
- }> - - { - routes.map((route, key) => { - return( - } - /> - ) - }) - } - - {/* Redirecting unknown url to 404 page */} - } /> - - -
-
-
- ) +import Header from "./Header"; +import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import routes from "../routes"; +import { Suspense, lazy } from "react"; +import SuspenseContent from "./SuspenseContent"; +import useStore from "../app/store_zustand"; +import { useEffect, useRef } from "react"; + +const Page404 = lazy(() => import("../pages/protected/404")); + +function PageContent() { + const mainContentRef = useRef(null); + const pageTitle = useStore((state) => state.header.pageTitle); + + // Scroll back to top on new page load + useEffect(() => { + mainContentRef.current.scroll({ + top: 0, + behavior: "smooth", + }); + }, [pageTitle]); + + return ( +
+
+
+ }> + + {routes.map((route, key) => { + return ( + } + /> + ); + })} + + {/* Redirecting unknown url to 404 page */} + } /> + + +
+
+
+ ); } - -export default PageContent +export default PageContent; diff --git a/src/containers/RightSidebar.js b/src/containers/RightSidebar.js index 31516fb..87463d6 100644 --- a/src/containers/RightSidebar.js +++ b/src/containers/RightSidebar.js @@ -1,60 +1,79 @@ -import XMarkIcon from '@heroicons/react/24/solid/XMarkIcon' -import { useDispatch, useSelector } from 'react-redux' -import NotificationBodyRightDrawer from '../features/common/components/NotificationBodyRightDrawer' -import { closeRightDrawer } from '../features/common/rightDrawerSlice' -import { RIGHT_DRAWER_TYPES } from '../utils/globalConstantUtil' -import CalendarEventsBodyRightDrawer from '../features/calendar/CalendarEventsBodyRightDrawer' +import XMarkIcon from "@heroicons/react/24/solid/XMarkIcon"; +import useStore from "../app/store_zustand"; +import NotificationBodyRightDrawer from "../features/common/components/NotificationBodyRightDrawer"; +import { RIGHT_DRAWER_TYPES } from "../utils/globalConstantUtil"; +import CalendarEventsBodyRightDrawer from "../features/calendar/CalendarEventsBodyRightDrawer"; +function RightSidebar() { + const { isOpen, bodyType, extraObject, header } = useStore( + (state) => state.rightDrawer + ); + const closeRightDrawer = useStore((state) => state.closeRightDrawer); -function RightSidebar(){ + const close = (e) => { + closeRightDrawer(); + }; - const {isOpen, bodyType, extraObject, header} = useSelector(state => state.rightDrawer) - const dispatch = useDispatch() + return ( +
+
+
+ {/* Header */} +
+ + {header} +
- const close = (e) => { - dispatch(closeRightDrawer(e)) - } - - - - return( -
- -
- -
- - {/* Header */} -
- - {header} -
- - - {/* ------------------ Content Start ------------------ */} -
-
- {/* Loading drawer body according to different drawer type */} - { - { - [RIGHT_DRAWER_TYPES.NOTIFICATION] : , - [RIGHT_DRAWER_TYPES.CALENDAR_EVENTS] : , - [RIGHT_DRAWER_TYPES.DEFAULT] :
- }[bodyType] - } - -
-
- {/* ------------------ Content End ------------------ */} -
- -
- -
close()} >
+ {/* ------------------ Content Start ------------------ */} +
+
+ {/* Loading drawer body according to different drawer type */} + { + { + [RIGHT_DRAWER_TYPES.NOTIFICATION]: ( + + ), + [RIGHT_DRAWER_TYPES.CALENDAR_EVENTS]: ( + + ), + [RIGHT_DRAWER_TYPES.DEFAULT]:
, + }[bodyType] + } +
+
+ {/* ------------------ Content End ------------------ */}
- ) +
+ +
close()} + >
+
+ ); } -export default RightSidebar \ No newline at end of file +export default RightSidebar; diff --git a/src/features/calendar/index.js b/src/features/calendar/index.js index 4ef7a2e..4515cbd 100644 --- a/src/features/calendar/index.js +++ b/src/features/calendar/index.js @@ -1,45 +1,50 @@ -import { useState } from 'react' -import CalendarView from '../../components/CalendarView' -import moment from 'moment' -import { CALENDAR_INITIAL_EVENTS } from '../../utils/dummyData' -import { useDispatch } from 'react-redux' -import { openRightDrawer } from '../common/rightDrawerSlice' -import { RIGHT_DRAWER_TYPES } from '../../utils/globalConstantUtil' -import { showNotification } from '../common/headerSlice' - - - -const INITIAL_EVENTS = CALENDAR_INITIAL_EVENTS - -function Calendar(){ - - const dispatch = useDispatch() - - const [events, setEvents] = useState(INITIAL_EVENTS) - - // Add your own Add Event handler, like opening modal or random event addition - // Format - {title :"", theme: "", startTime : "", endTime : ""}, typescript version comming soon :) - const addNewEvent = (date) => { - let randomEvent = INITIAL_EVENTS[Math.floor(Math.random() * 10)] - let newEventObj = {title : randomEvent.title, theme : randomEvent.theme, startTime : moment(date).startOf('day'), endTime : moment(date).endOf('day')} - setEvents([...events, newEventObj]) - dispatch(showNotification({message : "New Event Added!", status : 1})) - } - - // Open all events of current day in sidebar - const openDayDetail = ({filteredEvents, title}) => { - dispatch(openRightDrawer({header : title, bodyType : RIGHT_DRAWER_TYPES.CALENDAR_EVENTS, extraObject : {filteredEvents}})) - } - - return( - <> - - - ) +import { useState } from "react"; +import CalendarView from "../../components/CalendarView"; +import moment from "moment"; +import { CALENDAR_INITIAL_EVENTS } from "../../utils/dummyData"; +import useStore from "../../app/store_zustand"; +import { RIGHT_DRAWER_TYPES } from "../../utils/globalConstantUtil"; + +const INITIAL_EVENTS = CALENDAR_INITIAL_EVENTS; + +function Calendar() { + const showNotification = useStore((state) => state.showNotification); + const openRightDrawer = useStore((state) => state.openRightDrawer); + + const [events, setEvents] = useState(INITIAL_EVENTS); + + // Add your own Add Event handler, like opening modal or random event addition + // Format - {title :"", theme: "", startTime : "", endTime : ""}, typescript version comming soon :) + const addNewEvent = (date) => { + let randomEvent = INITIAL_EVENTS[Math.floor(Math.random() * 10)]; + let newEventObj = { + title: randomEvent.title, + theme: randomEvent.theme, + startTime: moment(date).startOf("day"), + endTime: moment(date).endOf("day"), + }; + setEvents([...events, newEventObj]); + showNotification({ message: "New Event Added!", status: 1 }); + }; + + // Open all events of current day in sidebar + const openDayDetail = ({ filteredEvents, title }) => { + openRightDrawer({ + header: title, + bodyType: RIGHT_DRAWER_TYPES.CALENDAR_EVENTS, + extraObject: { filteredEvents }, + }); + }; + + return ( + <> + + + ); } -export default Calendar \ No newline at end of file +export default Calendar; diff --git a/src/features/charts/index.js b/src/features/charts/index.js index 338e956..0ffb3e2 100644 --- a/src/features/charts/index.js +++ b/src/features/charts/index.js @@ -1,58 +1,59 @@ -import LineChart from './components/LineChart' -import BarChart from './components/BarChart' -import DoughnutChart from './components/DoughnutChart' -import PieChart from './components/PieChart' -import ScatterChart from './components/ScatterChart' -import StackBarChart from './components/StackBarChart' -import Datepicker from "react-tailwindcss-datepicker"; -import { useState } from 'react' - - - - -function Charts(){ - - const [dateValue, setDateValue] = useState({ - startDate: new Date(), - endDate: new Date() - }); - - const handleDatePickerValueChange = (newValue) => { - console.log("newValue:", newValue); - setDateValue(newValue); - } - - return( - <> - - {/** ---------------------- Different charts ------------------------- */} -
- - -
- - -
- - -
- -
- - -
- - ) +import LineChart from "./components/LineChart"; +import BarChart from "./components/BarChart"; +import DoughnutChart from "./components/DoughnutChart"; +import PieChart from "./components/PieChart"; +import ScatterChart from "./components/ScatterChart"; +import StackBarChart from "./components/StackBarChart"; +import Datepicker from "react-tailwindcss-datepicker"; +import { useState } from "react"; +import useStore from "../../app/store_zustand"; + +function Charts() { + const showNotification = useStore((state) => state.showNotification); + + const [dateValue, setDateValue] = useState({ + startDate: new Date(), + endDate: new Date(), + }); + + const handleDatePickerValueChange = (newValue) => { + setDateValue(newValue); + showNotification({ + message: `Date range updated to ${newValue.startDate} - ${newValue.endDate}`, + status: 1, + }); + }; + + return ( + <> + + {/** ---------------------- Different charts ------------------------- */} +
+ + +
+ +
+ + +
+ +
+ + +
+ + ); } -export default Charts \ No newline at end of file +export default Charts; diff --git a/src/features/common/components/ConfirmationModalBody.js b/src/features/common/components/ConfirmationModalBody.js index ba7ddf0..59d66bd 100644 --- a/src/features/common/components/ConfirmationModalBody.js +++ b/src/features/common/components/ConfirmationModalBody.js @@ -1,40 +1,38 @@ -import {useDispatch, useSelector} from 'react-redux' -import axios from 'axios' -import { CONFIRMATION_MODAL_CLOSE_TYPES, MODAL_CLOSE_TYPES } from '../../../utils/globalConstantUtil' -import { deleteLead } from '../../leads/leadSlice' -import { showNotification } from '../headerSlice' +import useStore from "../../../app/store_zustand"; +import { CONFIRMATION_MODAL_CLOSE_TYPES } from "../../../utils/globalConstantUtil"; -function ConfirmationModalBody({ extraObject, closeModal}){ +function ConfirmationModalBody({ extraObject, closeModal }) { + const deleteLead = useStore((state) => state.deleteLead); + const showNotification = useStore((state) => state.showNotification); - const dispatch = useDispatch() + const { message, type, _id, index } = extraObject; - const { message, type, _id, index} = extraObject - - - const proceedWithYes = async() => { - if(type === CONFIRMATION_MODAL_CLOSE_TYPES.LEAD_DELETE){ - // positive response, call api or dispatch redux function - dispatch(deleteLead({index})) - dispatch(showNotification({message : "Lead Deleted!", status : 1})) - } - closeModal() + const proceedWithYes = async () => { + if (type === CONFIRMATION_MODAL_CLOSE_TYPES.LEAD_DELETE) { + deleteLead(index); + showNotification({ message: "Lead Deleted!", status: 1 }); } - - return( - <> -

- {message} -

- -
- - - - - -
- - ) + closeModal(); + }; + + return ( + <> +

{message}

+ +
+ + + +
+ + ); } -export default ConfirmationModalBody \ No newline at end of file +export default ConfirmationModalBody; diff --git a/src/features/common/headerSlice.js b/src/features/common/headerSlice.js deleted file mode 100644 index d91e198..0000000 --- a/src/features/common/headerSlice.js +++ /dev/null @@ -1,30 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit' - -export const headerSlice = createSlice({ - name: 'header', - initialState: { - pageTitle: "Home", // current page title state management - noOfNotifications : 15, // no of unread notifications - newNotificationMessage : "", // message of notification to be shown - newNotificationStatus : 1, // to check the notification type - success/ error/ info - }, - reducers: { - setPageTitle: (state, action) => { - state.pageTitle = action.payload.title - }, - - - removeNotificationMessage: (state, action) => { - state.newNotificationMessage = "" - }, - - showNotification: (state, action) => { - state.newNotificationMessage = action.payload.message - state.newNotificationStatus = action.payload.status - }, - } -}) - -export const { setPageTitle, removeNotificationMessage, showNotification } = headerSlice.actions - -export default headerSlice.reducer \ No newline at end of file diff --git a/src/features/common/modalSlice.js b/src/features/common/modalSlice.js deleted file mode 100644 index 874a5d0..0000000 --- a/src/features/common/modalSlice.js +++ /dev/null @@ -1,35 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit' - -export const modalSlice = createSlice({ - name: 'modal', - initialState: { - title: "", // current title state management - isOpen : false, // modal state management for opening closing - bodyType : "", // modal content management - size : "", // modal content management - extraObject : {}, - }, - reducers: { - - openModal: (state, action) => { - const {title, bodyType, extraObject, size} = action.payload - state.isOpen = true - state.bodyType = bodyType - state.title = title - state.size = size || 'md' - state.extraObject = extraObject - }, - - closeModal: (state, action) => { - state.isOpen = false - state.bodyType = "" - state.title = "" - state.extraObject = {} - }, - - } -}) - -export const { openModal, closeModal } = modalSlice.actions - -export default modalSlice.reducer \ No newline at end of file diff --git a/src/features/common/rightDrawerSlice.js b/src/features/common/rightDrawerSlice.js deleted file mode 100644 index e47acbe..0000000 --- a/src/features/common/rightDrawerSlice.js +++ /dev/null @@ -1,33 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit' - -export const rightDrawerSlice = createSlice({ - name: 'rightDrawer', - initialState: { - header: "", // current title state management - isOpen : false, // right drawer state management for opening closing - bodyType : "", // right drawer content management - extraObject : {}, - }, - reducers: { - - openRightDrawer: (state, action) => { - const {header, bodyType, extraObject} = action.payload - state.isOpen = true - state.bodyType = bodyType - state.header = header - state.extraObject = extraObject - }, - - closeRightDrawer: (state, action) => { - state.isOpen = false - state.bodyType = "" - state.header = "" - state.extraObject = {} - }, - - } -}) - -export const { openRightDrawer, closeRightDrawer } = rightDrawerSlice.actions - -export default rightDrawerSlice.reducer \ No newline at end of file diff --git a/src/features/dashboard/index.js b/src/features/dashboard/index.js index e56733d..faad588 100644 --- a/src/features/dashboard/index.js +++ b/src/features/dashboard/index.js @@ -1,78 +1,90 @@ -import DashboardStats from './components/DashboardStats' -import AmountStats from './components/AmountStats' -import PageStats from './components/PageStats' +import DashboardStats from "./components/DashboardStats"; +import AmountStats from "./components/AmountStats"; +import PageStats from "./components/PageStats"; -import UserGroupIcon from '@heroicons/react/24/outline/UserGroupIcon' -import UsersIcon from '@heroicons/react/24/outline/UsersIcon' -import CircleStackIcon from '@heroicons/react/24/outline/CircleStackIcon' -import CreditCardIcon from '@heroicons/react/24/outline/CreditCardIcon' -import UserChannels from './components/UserChannels' -import LineChart from './components/LineChart' -import BarChart from './components/BarChart' -import DashboardTopBar from './components/DashboardTopBar' -import { useDispatch } from 'react-redux' -import {showNotification} from '../common/headerSlice' -import DoughnutChart from './components/DoughnutChart' -import { useState } from 'react' +import UserGroupIcon from "@heroicons/react/24/outline/UserGroupIcon"; +import UsersIcon from "@heroicons/react/24/outline/UsersIcon"; +import CircleStackIcon from "@heroicons/react/24/outline/CircleStackIcon"; +import CreditCardIcon from "@heroicons/react/24/outline/CreditCardIcon"; +import UserChannels from "./components/UserChannels"; +import LineChart from "./components/LineChart"; +import BarChart from "./components/BarChart"; +import DashboardTopBar from "./components/DashboardTopBar"; +import useStore from "../../app/store_zustand"; +import DoughnutChart from "./components/DoughnutChart"; +import { useState } from "react"; const statsData = [ - {title : "New Users", value : "34.7k", icon : , description : "↗︎ 2300 (22%)"}, - {title : "Total Sales", value : "$34,545", icon : , description : "Current month"}, - {title : "Pending Leads", value : "450", icon : , description : "50 in hot leads"}, - {title : "Active Users", value : "5.6k", icon : , description : "↙ 300 (18%)"}, -] + { + title: "New Users", + value: "34.7k", + icon: , + description: "↗︎ 2300 (22%)", + }, + { + title: "Total Sales", + value: "$34,545", + icon: , + description: "Current month", + }, + { + title: "Pending Leads", + value: "450", + icon: , + description: "50 in hot leads", + }, + { + title: "Active Users", + value: "5.6k", + icon: , + description: "↙ 300 (18%)", + }, +]; +function Dashboard() { + const showNotification = useStore((state) => state.showNotification); + const updateDashboardPeriod = (newRange) => { + // Dashboard range changed, write code to refresh your values + showNotification({ + message: `Period updated to ${newRange.startDate} to ${newRange.endDate}`, + status: 1, + }); + }; -function Dashboard(){ + return ( + <> + {/** ---------------------- Select Period Content ------------------------- */} + - const dispatch = useDispatch() - + {/** ---------------------- Different stats content 1 ------------------------- */} +
+ {statsData.map((d, k) => { + return ; + })} +
- const updateDashboardPeriod = (newRange) => { - // Dashboard range changed, write code to refresh your values - dispatch(showNotification({message : `Period updated to ${newRange.startDate} to ${newRange.endDate}`, status : 1})) - } + {/** ---------------------- Different charts ------------------------- */} +
+ + +
- return( - <> - {/** ---------------------- Select Period Content ------------------------- */} - - - {/** ---------------------- Different stats content 1 ------------------------- */} -
- { - statsData.map((d, k) => { - return ( - - ) - }) - } -
+ {/** ---------------------- Different stats content 2 ------------------------- */} +
+ + +
+ {/** ---------------------- User source channels table ------------------------- */} - {/** ---------------------- Different charts ------------------------- */} -
- - -
- - {/** ---------------------- Different stats content 2 ------------------------- */} - -
- - -
- - {/** ---------------------- User source channels table ------------------------- */} - -
- - -
- - ) +
+ + +
+ + ); } -export default Dashboard \ No newline at end of file +export default Dashboard; diff --git a/src/features/documentation/DocComponents.js b/src/features/documentation/DocComponents.js index 23852ba..a6da4b0 100644 --- a/src/features/documentation/DocComponents.js +++ b/src/features/documentation/DocComponents.js @@ -1,39 +1,31 @@ -import { useEffect, useState } from "react" -import { useDispatch } from "react-redux" -import TitleCard from "../../components/Cards/TitleCard" -import { setPageTitle, showNotification } from "../common/headerSlice" -import DocComponentsNav from "./components/DocComponentsNav" -import ReadMe from "./components/GettingStartedContent" -import DocComponentsContent from "./components/DocComponentsContent" -import FeaturesNav from "./components/FeaturesNav" -import FeaturesContent from "./components/FeaturesContent" - - - -function DocComponents(){ - - const dispatch = useDispatch() - - useEffect(() => { - dispatch(setPageTitle({ title : "Documentation"})) - }, []) - - - return( - <> -
-
- -
- -
- -
- -
- - - ) +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import DocComponentsNav from "./components/DocComponentsNav"; +import DocComponentsContent from "./components/DocComponentsContent"; + +function DocComponents() { + const setPageTitle = useStore((state) => state.setPageTitle); + + useEffect(() => { + setPageTitle("Documentation"); + }, [setPageTitle]); + + return ( + <> +
+
+ +
+ +
+ +
+
+ + ); } -export default DocComponents \ No newline at end of file +export default DocComponents; diff --git a/src/features/documentation/DocFeatures.js b/src/features/documentation/DocFeatures.js index 54c6417..2812d0b 100644 --- a/src/features/documentation/DocFeatures.js +++ b/src/features/documentation/DocFeatures.js @@ -1,39 +1,31 @@ -import { useEffect, useState } from "react" -import { useDispatch } from "react-redux" -import TitleCard from "../../components/Cards/TitleCard" -import { setPageTitle, showNotification } from "../common/headerSlice" -import GettingStartedNav from "./components/GettingStartedNav" -import ReadMe from "./components/GettingStartedContent" -import GettingStartedContent from "./components/GettingStartedContent" -import FeaturesNav from "./components/FeaturesNav" -import FeaturesContent from "./components/FeaturesContent" - - - -function Features(){ - - const dispatch = useDispatch() - - useEffect(() => { - dispatch(setPageTitle({ title : "Documentation"})) - }, []) - - - return( - <> -
-
- -
- -
- -
- -
- - - ) +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import FeaturesNav from "./components/FeaturesNav"; +import FeaturesContent from "./components/FeaturesContent"; + +function Features() { + const setPageTitle = useStore((state) => state.setPageTitle); + + useEffect(() => { + setPageTitle("Documentation"); + }, [setPageTitle]); + + return ( + <> +
+
+ +
+ +
+ +
+
+ + ); } -export default Features \ No newline at end of file +export default Features; diff --git a/src/features/documentation/DocGettingStarted.js b/src/features/documentation/DocGettingStarted.js index 636785d..5f01578 100644 --- a/src/features/documentation/DocGettingStarted.js +++ b/src/features/documentation/DocGettingStarted.js @@ -1,37 +1,31 @@ -import { useEffect, useState } from "react" -import { useDispatch } from "react-redux" -import TitleCard from "../../components/Cards/TitleCard" -import { setPageTitle, showNotification } from "../common/headerSlice" -import GettingStartedNav from "./components/GettingStartedNav" -import ReadMe from "./components/GettingStartedContent" -import GettingStartedContent from "./components/GettingStartedContent" - - - -function GettingStarted(){ - - const dispatch = useDispatch() - - useEffect(() => { - dispatch(setPageTitle({ title : "Documentation"})) - }, []) - - - return( - <> -
-
- -
- -
- -
- -
- - - ) +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import GettingStartedNav from "./components/GettingStartedNav"; +import GettingStartedContent from "./components/GettingStartedContent"; + +function GettingStarted() { + const setPageTitle = useStore((state) => state.setPageTitle); + + useEffect(() => { + setPageTitle("Documentation"); + }, [setPageTitle]); + + return ( + <> +
+
+ +
+ +
+ +
+
+ + ); } -export default GettingStarted \ No newline at end of file +export default GettingStarted; diff --git a/src/features/documentation/components/DocComponentsContent.js b/src/features/documentation/components/DocComponentsContent.js index f7c4e41..ec7dcc0 100644 --- a/src/features/documentation/components/DocComponentsContent.js +++ b/src/features/documentation/components/DocComponentsContent.js @@ -1,99 +1,151 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import InputText from '../../../components/Input/InputText' -import Title from '../../../components/Typography/Title' -import Subtitle from '../../../components/Typography/Subtitle' -import ErrorText from '../../../components/Typography/ErrorText' -import HelperText from '../../../components/Typography/HelperText' - -import { setPageTitle, showNotification } from '../../common/headerSlice' -import TitleCard from '../../../components/Cards/TitleCard' - -function DocComponentsContent(){ - - const dispatch = useDispatch() - - const updateFormValue = () => { - // Dummy function for input text component - } - - return( - <> -
-

Components

- - We have added some global components that are used commonly inside the project. - - {/* Typography*/} -

Typography

-
- These components are present under /components/Typography folder. It accepts styleClass as props which can be used to pass additional className for style. It has following components which you can import and use it - -
-
{'import  Title from "../components/Typography/Title"\n  Your Title here'}
-
-
    -
  • Title - Use this component to show title - Title Example -
  • -
  • Subtitle - Component that shows text smaller than title - Subtitle Example -
  • -
  • ErrorText - Used for showing error messages - Error Text Example -
  • -
  • HelperText - Used for showing secondary message - Helper Text Example
  • -
-
- - - {/* Form Input*/} -

Form Input

-

- Many times we have to use form input like text, select one or toogle and in every file we have to handle its state management, here we have added global form component that can be used in any file and state variables can be managed by passing props to it. It is present in /components/Input folder. -

- Ex- -
-
{'const INITIAL_LEAD_OBJ = {\n   first_name : "", \n   last_name : "", \n   email : "" \n  } \n   const [leadObj, setLeadObj] = useState(INITIAL_LEAD_OBJ) \n   const updateFormValue = ({updateType, value}) => {\n    setErrorMessage("") \n    setLeadObj({...leadObj, [updateType] : value})\n   }\n\n'}
-
- - - -

This example is from add new lead modal, here we are importing component for creating text input and passing some props to handle its content and state variable. Description of props are as follows -

-
    -
  • type - Input type value like number, date, time etc..
  • -
  • updateType - This is used to update state variable in parent component
  • -
  • containerStyle - Style class for container of input, which include label as well
  • -
  • labelTitle - Title of the label
  • -
  • updateFormValue - Function of parent component to update state variable
  • -
- - - - - {/* Cards */} -

Cards

-

- Daisy UI already have many cards layout, on top of that we have added one card component that accept title props and shows children inside its body. Also there is a divider between title and body of card. On more provision has been added to add buttons on top left side of card using TopSideButtons props (check leads page). - -

- Ex - -
-
{' 

Card Body

'}
-
-
-

Card Body

-
- - - - -
- - -
- - ) +import InputText from "../../../components/Input/InputText"; +import Title from "../../../components/Typography/Title"; +import Subtitle from "../../../components/Typography/Subtitle"; +import ErrorText from "../../../components/Typography/ErrorText"; +import HelperText from "../../../components/Typography/HelperText"; + +import TitleCard from "../../../components/Cards/TitleCard"; + +function DocComponentsContent() { + const updateFormValue = () => { + // Dummy function for input text component + }; + + return ( + <> +
+

Components

+ We have added some global components that are used commonly inside the + project. + {/* Typography*/} +

Typography

+
+ These components are present under{" "} + + /components/Typography + {" "} + folder. It accepts styleClass as props which can be used to pass + additional className for style. It has following components which you + can import and use it - +
+
+              
+                {
+                  'import  Title from "../components/Typography/Title"\n  Your Title here'
+                }
+              
+            
+
+
    +
  • + Title - Use this component to + show title + Title Example +
  • +
  • + Subtitle - Component that shows + text smaller than title + Subtitle Example +
  • +
  • + ErrorText - Used for showing + error messages + Error Text Example +
  • +
  • + HelperText - Used for showing + secondary message + Helper Text Example +
  • +
+
+ {/* Form Input*/} +

Form Input

+

+ Many times we have to use form input like text, select one or toogle + and in every file we have to handle its state management, here we have + added global form component that can be used in any file and state + variables can be managed by passing props to it. It is present in{" "} + /components/Input{" "} + folder. +

+ Ex- +
+
+            
+              {
+                'const INITIAL_LEAD_OBJ = {\n   first_name : "", \n   last_name : "", \n   email : "" \n  } \n   const [leadObj, setLeadObj] = useState(INITIAL_LEAD_OBJ) \n   const updateFormValue = ({updateType, value}) => {\n    setErrorMessage("") \n    setLeadObj({...leadObj, [updateType] : value})\n   }\n\n'
+              }
+            
+          
+
+ +

+ {" "} + This example is from add new lead modal, here we are importing + component for creating text input and passing some props to handle its + content and state variable. Description of props are as follows -{" "} +

+
    +
  • + type - Input type value like + number, date, time etc..{" "} +
  • +
  • + updateType - This is used to + update state variable in parent component +
  • +
  • + containerStyle - Style class for + container of input, which include label as well +
  • +
  • + labelTitle - Title of the label +
  • +
  • + updateFormValue - Function of + parent component to update state variable +
  • +
+ {/* Cards */} +

Cards

+

+ + Daisy UI + {" "} + already have many cards layout, on top of that we have added one card + component that accept title props and shows children inside its body. + Also there is a divider between title and body of card. On more + provision has been added to add buttons on top left side of card using + TopSideButtons props (check leads page). +

+ Ex - +
+
+            
+              {
+                ' 

Card Body

' + } +
+
+
+
+ + {" "} +

Card Body

+
+
+
+
+ + ); } -export default DocComponentsContent \ No newline at end of file +export default DocComponentsContent; diff --git a/src/features/documentation/components/FeaturesContent.js b/src/features/documentation/components/FeaturesContent.js index 9401f4e..ae1c3d3 100644 --- a/src/features/documentation/components/FeaturesContent.js +++ b/src/features/documentation/components/FeaturesContent.js @@ -1,147 +1,289 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import Subtitle from '../../../components/Typography/Subtitle' -import { setPageTitle, showNotification } from '../../common/headerSlice' - -function FeaturesContent(){ - - const dispatch = useDispatch() - - return( - <> -
-

Features

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

Authentication

-

- JWT based Authentication logic is present in /app/auth.js. In the file you can see we are adding bearer token in header for every request. Every routes under /routes/ folder will need authentication. For public routes like login, register you will have to add routes in App.js file and also include the path in PUBLIC_ROUTES variable under /app/auth.js file so that auto redirect to login page is not triggered. - -

- - - - - {/* Left Sidebar*/} -

Left Sidebar

-

- This is main internal navigation (for pages that will come after login only), all sidebar menu items with their icons are present in /routes/sidebar.js file, while path and page components mapping are respectively present in /routes/index.js file. -

- - - - {/* Add New Page*/} -

Add New Page

-

All public routes are present in App.js file. Steps to add new public page - -

- -
    -
  • Create Page inside /pages folder
  • -
  • Go to App.js and import the component and add its path
  • -
  • Add your new route path in /app/auth.js file under PUBLIC_ROUTES variable, this will allow the page to open without login.
  • -
- -

All protected routes are present in /routes/sidebar.js file

- -
    -
  • Create your page inside /pages/protected folder
  • -
  • Add your new routes in /routes/sidebar.js, this will show your new page in sidebar
  • -
  • Import your new routes component and map its path in /routes/index.js
  • -
- - - - {/* Right Sidebar*/} -

Right Sidebar

-
- This is used for showing long list contents like notifications, settings etc.. We are using redux to show and hide and it is single component and can be called from any file with dispatch method. - To add new content follow following steps: -
    -
  • Create new component file containing main body of your content
  • -
  • Create new variable in /utils/globalConstantUtils.js file under RIGHT_DRAWER_TYPES variable
  • -
  • Now include the file mapped with the new variable in /containers/RightSidebar.js file using switch.
    - For ex- If you new component name is TestRightSideBar.js and variable name is TEST_RIGHT_SIDEBAR, then add following code inside switch code block -
    -
    -
    {`[RIGHT_DRAWER_TYPES.TEST_RIGHT_SIDEBAR] : \n`}
    -
    - Here extraObject have variables that is passed from parent component while calling openRightDrawer method -
  • -
  • Now the last step, call dispatch method as follows -
    -
    {'import { useDispatch } from "react-redux"\n  const dispatch = useDispatch()\n  dispatch(openRightDrawer({header : "Test Right Drawer", \n  bodyType : RIGHT_DRAWER_TYPES.TEST_RIGHT_SIDEBAR}))'}
    -
    -
  • -
-
- - - {/* Themes*/} -

Themes

-

- By default we have added light and dark theme and Daisy UI comes with a number of themes, which you can use with no extra effort, you just have to include it in tailwind.config.js file, you can add themes like cupcake, corporate, reto etc... Also we can configure themes colors in config file, for more documentation on themes checkout Daisy UI documentation. -

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

Modal

-
- With global modal functionality you dont have to create seperate modal for each page. We are using redux to show and hide and it is a single component and can be called from any file with dispatch method. - Code for showing modal is present in modalSlice and layout container component. To show modal just call openModal() function of modalSlice using dispatch. -
- To add new modal in any page follow following steps: -
    -
  • Create new component file containing main body of your modal content
  • -
  • Create new variable in /utils/globalConstantUtils.js file under MODAL_BODY_TYPES variable
  • -
  • Now include the file mapped with the new variable in /containers/ModalLayout.js file using switch.
    - For ex- If you new component name is TestModal.js and variable name is TEST_MODAL, then add following code inside switch code block -
    -
    -
    {`[RIGHT_DRAWER_TYPES.TEST_MODAL] : \n`}
    -
    - Here extraObject have variables that is passed from parent component while calling openModal method -
  • -
  • Now the last step, call dispatch method as follows -
    -
    {'import { useDispatch } from "react-redux"\n  const dispatch = useDispatch()\n   dispatch(openModal({title : "Test Modal Title", \n   bodyType : MODAL_BODY_TYPES.TEST_MODAL}))'}
    -
    -
  • -
-
- - - - - - - - {/* Notification*/} -

Notification

-

Many times we have to show notification to user be it on successfull form submission or any api success. And requirement can come to show notification from any page, so global notification handling is needed.

- -

Code for showing notification is present in headerSlice and layout container component. To show notification just call showNotification() function of headerSlice using dispatch. To show success message notification pass status as 1 and for showing error message pass status as 0.

- -
-
{'import { useDispatch } from "react-redux"\n  const dispatch = useDispatch()\n  dispatch(showNotification({message : "Message here", status : 1}))'}
-
- -

Click on this button to check

- - - - - - -
- - -
- - ) +import useStore from "../../../app/store_zustand"; + +function FeaturesContent() { + const showNotification = useStore((state) => state.showNotification); + + return ( + <> +
+

Features

+ + {/* Authentication*/} +

Authentication

+

+ JWT based Authentication logic is present in{" "} + /app/auth.js. In + the file you can see we are adding bearer token in header for every + request. Every routes under{" "} + /routes/ folder + will need authentication. For public routes like login, register you + will have to add routes in{" "} + App.js file and + also include the path in PUBLIC_ROUTES variable under{" "} + /app/auth.js file + so that auto redirect to login page is not triggered. +

+ + {/* Left Sidebar*/} +

Left Sidebar

+

+ This is main internal navigation (for pages that will come after login + only), all sidebar menu items with their icons are present in{" "} + + /routes/sidebar.js + {" "} + file, while path and page components mapping are respectively present + in{" "} + /routes/index.js{" "} + file. +

+ + {/* Add New Page*/} +

Add New Page

+

+ All public routes are present + in App.js file. + Steps to add new public page - +

+ +
    +
  • + Create Page inside{" "} + /pages folder +
  • +
  • + Go to App.js{" "} + and import the component and add its path +
  • +
  • + Add your new route path in{" "} + /app/auth.js{" "} + file under PUBLIC_ROUTES variable, this will allow the page to open + without login. +
  • +
+ +

+ All protected routes are + present in{" "} + + /routes/sidebar.js + {" "} + file +

+ +
    +
  • + Create your page inside{" "} + + /pages/protected + {" "} + folder +
  • +
  • + Add your new routes in{" "} + + /routes/sidebar.js + + , this will show your new page in sidebar +
  • +
  • + Import your new routes component and map its path in{" "} + + /routes/index.js + +
  • +
+ + {/* Right Sidebar*/} +

Right Sidebar

+
+ This is used for showing long list contents like notifications, + settings etc.. We are using{" "} + Zustand now to show and + hide and it is single component and can be called from any file. To + add new content follow following steps: +
    +
  • + Create new component file containing main body of your content +
  • +
  • + Create new variable in{" "} + + /utils/globalConstantUtils.js + {" "} + file under RIGHT_DRAWER_TYPES variable +
  • +
  • + Now include the file mapped with the new variable in{" "} + + /containers/RightSidebar.js + {" "} + file using switch.
    + For ex- If you new component name is{" "} + + TestRightSideBar.js + {" "} + and variable name is TEST_RIGHT_SIDEBAR, then add following code + inside switch code block +
    +
    +
    +                  {`[RIGHT_DRAWER_TYPES.TEST_RIGHT_SIDEBAR] : \n`}
    +                
    +
    + + Here extraObject have variables that is passed from parent + component while calling openRightDrawer method + +
  • +
  • + Now the last step, call the action from the Zustand store +
    +
    +                  
    +                    {
    +                      "import useStore from '../app/store_zustand';\n// Inside your component\nconst openRightDrawer = useStore((state) => state.openRightDrawer);\n\n// To open the drawer\nopenRightDrawer({header : \"Test Right Drawer\", bodyType : RIGHT_DRAWER_TYPES.TEST_RIGHT_SIDEBAR});"
    +                    }
    +                  
    +                
    +
    +
  • +
+
+ + {/* Themes*/} +

Themes

+

+ By default we have added light and dark theme and Daisy UI comes with + a number of themes, which you can use with no extra effort, you just + have to include it in{" "} + + tailwind.config.js + {" "} + file, you can add themes like cupcake, corporate, reto etc... Also we + can configure themes colors in config file, for more documentation on + themes checkout{" "} + + Daisy UI documentation. + +

+ + {/* Modal*/} +

Modal

+
+ With global modal functionality you dont have to create seperate modal + for each page. We are using{" "} + Zustand now to show and + hide and it is a single component and can be called from any file. + Code for showing modal is present in the Zustand store and layout + container component. To show modal just call the openModal action from + the store. +
+ To add new modal in any page follow following steps: +
    +
  • + Create new component file containing main body of your modal + content +
  • +
  • + Create new variable in{" "} + + /utils/globalConstantUtils.js + {" "} + file under MODAL_BODY_TYPES variable +
  • +
  • + Now include the file mapped with the new variable in{" "} + + /containers/ModalLayout.js + {" "} + file using switch.
    + For ex- If you new component name is{" "} + + TestModal.js + {" "} + and variable name is TEST_MODAL, then add following code inside + switch code block +
    +
    +
    +                  {`[MODAL_BODY_TYPES.TEST_MODAL] : \n`}
    +                
    +
    + + Here extraObject have variables that is passed from parent + component while calling openModal method + +
  • +
  • + Now the last step, call the action from the Zustand store +
    +
    +                  
    +                    {
    +                      "import useStore from '../app/store_zustand';\n// Inside your component\nconst openModal = useStore((state) => state.openModal);\n\n// To open the modal\nopenModal({title : \"Test Modal Title\", bodyType : MODAL_BODY_TYPES.TEST_MODAL});"
    +                    }
    +                  
    +                
    +
    +
  • +
+
+ + {/* Notification*/} +

Notification

+

+ Many times we have to show notification to user be it on successfull + form submission or any api success. And requirement can come to show + notification from any page, so global notification handling is needed. +

+ +

+ Code for showing notification is present in the Zustand store and + layout container component. To show notification just call the{" "} + showNotification action + from the store. To show success message notification pass status as 1 + and for showing error message pass status as 0. +

+ +
+
+            
+              {
+                "import useStore from '../app/store_zustand';\n// Inside your component\nconst showNotification = useStore((state) => state.showNotification);\n\n// To show notification\nshowNotification({message : \"Message here\", status : 1});"
+              }
+            
+          
+
+ +

Click on this button to check

+ + + + + +
+
+ + ); } -export default FeaturesContent \ No newline at end of file +export default FeaturesContent; diff --git a/src/features/documentation/components/GettingStartedContent.js b/src/features/documentation/components/GettingStartedContent.js index 467632e..25189dc 100644 --- a/src/features/documentation/components/GettingStartedContent.js +++ b/src/features/documentation/components/GettingStartedContent.js @@ -1,173 +1,338 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import Subtitle from '../../../components/Typography/Subtitle' -import { setPageTitle } from '../../common/headerSlice' - -function GettingStartedContent(){ - - const dispatch = useDispatch() - - - - return( - <> -
-

Getting Started

- - - {/* Introduction */} -

Introduction

-

A free dashboard template using Daisy UI and react js. With the help of Dasisy UI, it comes with fully customizable and themable CSS and power of Tailwind CSS utility classes. We have also added redux toolkit and configured it for API calls and state management.

-

User authentication has been implemented using JWT token method (ofcourse you need backend API for generating and verifying token). This template can be used to start your next SaaS project or build new internal tools in your company.

-

Core libraries used -

- -

Major features -

-

Almost all major UI components are available in Daisy UI library. Apart from this logic has been added for following -

-
    -
  • Light/dark mode toggle
  • -
  • Token based user authentication
  • -
  • Submenu support in sidebar
  • -
  • Store management using redux toolkit
  • -
  • Daisy UI components
  • -
  • Right and left sidebar, Universal loader, notifications and other components
  • -
  • React chart js 2 examples
  • -
- - - - {/* How to Use */} -

How to use?

-

- Just clone the repo from github and then run following command (Make sure you have node js installed )
- Repo Link -
- npm install
- npm start -

- - - {/* Tailwind CSS*/} -

Tailwind CSS

-

- Tailwind CSS is a utility-first CSS framework with predefined classes that you can use to build and design the UI directly in the JSX. We have also included Daisy UI Component, that is based on tailwind CSS. -

- - {/* Daisy UI */} -

Daisy UI

- -

Daisy UI, a popular free and opensource tailwind component library has been used for this template. It has a rich collection of components, layouts and is fully customizable and themeable.

- -

Apart from this it also helps in making HTML code more cleaner as we don't have to include all utility classes of tailwind to make the UI. Check components documentation here. For Ex-

- -
-

Creating a button

-
-
- -
-

using only utility classes of tailwind

-
-
{'Button'}
-
- -
- -
- -
-

using daisyUI component classes

-
-
{'\nButton'}
-
- -
-
- - - - {/* Chart JS */} -

Chart JS

-

- Chart JS library has rich components of different charts available. It is based on Chart.js library, the most popular charting library. We have added this library and added couple of examples in seperate page. -

- - - - {/* Redux Toolkit */} -

Redux Toolkit

-

- The Redux Toolkit package helps in writing redux logic easily. It was originally created to help address three common concerns about Redux: -

  • Configuring a Redux store is too complicated
  • -
  • I have to add a lot of packages to get Redux to do anything useful
  • -
  • Redux requires too much boilerplate code"
  • - This library has been configured and used for showing notifications, modals and loading data from API in leads page. -

    - - - {/* Hero Icons */} -

    Hero Icons

    -

    HeroIcons library has been used for all the icons in this templates. It has a rich collection of SVG icons, and is made by the makers of Tailwind CSS.

    - -

    Each icon can be imported individually as a React component, check documentation

    - -
    {"import BeakerIcon from '@heroicons/react/24/solid/BeakerIcon'"}
    -

    Use as follows in your component

    -
    {""}
    - -
    - -
    -
    Note: Importing all icons in single line will increase your build time
    -
    - -

    Don't import like this (will load all icons and increase build time)

    -
    {"import {BeakerIcon, BellIcon } from '@heroicons/react/24/solid'"}
    - -

    Instead import as follows

    -
    {"import BeakerIcon from '@heroicons/react/24/solid/BeakerIcon'"}
    - {"import BellIcon from '@heroicons/react/24/solid/BellIcon'"}
    - -
    This is better way for importing icons
    - - - - {/* Project Structure */} -

    Project Structure

    -

    Folders -

    -
      -
    • app - store management, auth and libraries settings are present
    • -
    • components - this include all common components to be used in project
    • -
    • containers - components related to layout like sidebar, page layout, header etc..
    • -
    • features - main folder where all page logic resides, there will be folder for each page and additional folder inside that to group different functionalities like components, modals etc... Redux slice file will also present inside page specific folder.
    • -
    • pages - this contain one single file related to one page, if you want to divide page into different components file, use features folder and create seperate folder related to that page
    • -
    • routes - all settings related to routes
    • -
    - -

    Files -

    -
      -
    • App.js - Main file containing different routes and components
    • -
    • index.css - Additional global css if required
    • -
    • index.js - Entry point of project
    • -
    • package.json - All dependencies and npm scripts
    • -
    • tailwind.config.js - Tailwind CSS configuration file, add theme customization and new themes in this file
    • -
    - - -
    - -
    - - ) +function GettingStartedContent() { + return ( + <> +
    +

    Getting Started

    + + {/* Introduction */} +

    + Introduction +

    +

    + A free dashboard template using{" "} + Daisy UI and react js. With the + help of Dasisy UI, it comes with{" "} + fully customizable and themable CSS{" "} + and power of Tailwind CSS utility classes. We have also added{" "} + Zustand and configured it for API + calls and state management. +

    +

    + User authentication has been implemented using JWT token method + (ofcourse you need backend API for generating and verifying token). + This template can be used to start your next SaaS project or build new + internal tools in your company. +

    +

    Core libraries used -

    + +

    Major features -

    +

    + Almost all major UI components are available in Daisy UI library. + Apart from this logic has been added for following -{" "} +

    +
      +
    • + {" "} + Light/dark mode toggle +
    • +
    • Token based user authentication
    • +
    • + {" "} + Submenu support in sidebar +
    • +
    • + {" "} + Store management using Zustand +
    • +
    • + {" "} + Daisy UI components +
    • +
    • + {" "} + Right and left sidebar, Universal + loader, notifications and other components +
    • +
    • + {" "} + React chart js 2 examples +
    • +
    + + {/* How to Use */} +

    How to use?

    +

    + Just clone the repo from github and then run following command (Make + sure you have node js installed )
    + + Repo Link + +
    + npm install +
    + npm start +

    + + {/* Tailwind CSS*/} +

    Tailwind CSS

    +

    + Tailwind CSS is a utility-first CSS framework with predefined classes + that you can use to build and design the UI directly in the JSX. We + have also included Daisy UI Component, that is based on tailwind CSS. +

    + + {/* Daisy UI */} +

    Daisy UI

    + +

    + + Daisy UI + + , a popular free and opensource tailwind component library has been + used for this template. It has a rich collection of components, + layouts and is fully customizable and themeable. +

    + +

    + Apart from this it also helps in making HTML code more cleaner as we + don't have to include all utility classes of tailwind to make the UI. + Check components{" "} + + documentation here + + . For Ex-{" "} +

    + +
    +

    Creating a button

    +
    +
    +
    +

    + {" "} + using only utility classes of tailwind +

    +
    +
    +                
    +                  {
    +                    'Button'
    +                  }
    +                
    +              
    +
    + +
    + +
    + +
    +

    + using daisyUI component classes +

    +
    +
    +                {'\nButton'}
    +              
    +
    + +
    +
    + + {/* Chart JS */} +

    Chart JS

    +

    + Chart JS library has rich components of different charts available. It + is based on{" "} + + {" "} + Chart.js + {" "} + library, the most popular charting library. We have added this library + and added couple of examples in seperate page. +

    + + {/* Zustand */} +

    Zustand

    +

    + Zustand is a small, fast and scalable bearbones state-management + solution using simplified flux principles. It has a comfy api based on + hooks, isn't boilerplatey or opinionated. +

    +

    + This library has been configured and used for showing notifications, + modals and loading data from API in leads page. +

    + + {/* Hero Icons */} +

    Hero Icons

    +

    + + HeroIcons + {" "} + library has been used for all the icons in this templates. It has a + rich collection of SVG icons, and is made by the makers of Tailwind + CSS. +

    + +

    + Each icon can be imported individually as a React component, check{" "} + + documentation + +

    + +
    +          
    +            {"import BeakerIcon from '@heroicons/react/24/solid/BeakerIcon'"}
    +          
    +        
    +

    Use as follows in your component

    +
    +          {""}
    +        
    + +
    + +
    +
    + + Note: Importing all icons in single line will increase your build + time + +
    +
    + +

    + Don't import like this (will load all icons and increase build time) +

    +
    +          
    +            {"import {BeakerIcon, BellIcon } from '@heroicons/react/24/solid'"}
    +          
    +        
    + +

    Instead import as follows

    +
    +          
    +            {"import BeakerIcon from '@heroicons/react/24/solid/BeakerIcon'"}
    +            
    + {"import BellIcon from '@heroicons/react/24/solid/BellIcon'"} +
    +
    + +
    + This is better way for importing icons +
    + + {/* Project Structure */} +

    Project Structure

    +

    Folders -

    +
      +
    • + app - store management ( + Zustand store), auth and + libraries settings are present +
    • +
    • + components - this include all common components to be used in + project +
    • +
    • + containers - components related to layout like sidebar, page layout, + header etc.. +
    • +
    • + features - main folder where all page logic resides, there will be + folder for each page and additional folder inside that to group + different functionalities like components, modals etc... +
    • +
    • + pages - this contain one single file related to one page, if you + want to divide page into different components file, use features + folder and create seperate folder related to that page +
    • +
    • routes - all settings related to routes
    • +
    + +

    Files -

    +
      +
    • + App.js - Main file containing different routes and components{" "} +
    • +
    • index.css - Additional global css if required
    • +
    • index.js - Entry point of project
    • +
    • package.json - All dependencies and npm scripts
    • +
    • + tailwind.config.js - Tailwind CSS configuration file, add theme + customization and new themes in this file +
    • +
    + +
    +
    + + ); } -export default GettingStartedContent \ No newline at end of file +export default GettingStartedContent; diff --git a/src/features/documentation/components/GettingStartedNav.js b/src/features/documentation/components/GettingStartedNav.js index f1029bd..483c8b8 100644 --- a/src/features/documentation/components/GettingStartedNav.js +++ b/src/features/documentation/components/GettingStartedNav.js @@ -8,7 +8,7 @@ function GettingStartedNav({activeIndex}){ {name : "Tailwind CSS", isActive : false}, {name : "Daisy UI", isActive : false}, {name : "Chart JS", isActive : false}, - {name : "Redux Toolkit", isActive : false}, + {name : "Zustand", isActive : false}, {name : "Hero Icons", isActive : false}, {name : "Project Structure", isActive : false}, ] diff --git a/src/features/integration/index.js b/src/features/integration/index.js index 22c8e87..3405648 100644 --- a/src/features/integration/index.js +++ b/src/features/integration/index.js @@ -1,60 +1,110 @@ -import { useState } from "react" -import { useDispatch } from "react-redux" -import TitleCard from "../../components/Cards/TitleCard" -import { showNotification } from "../common/headerSlice" - +import { useState } from "react"; +import useStore from "../../app/store_zustand"; +import TitleCard from "../../components/Cards/TitleCard"; const INITIAL_INTEGRATION_LIST = [ - {name : "Slack", icon : "https://cdn-icons-png.flaticon.com/512/2111/2111615.png", isActive : true, description : "Slack is an instant messaging program designed by Slack Technologies and owned by Salesforce."}, - {name : "Facebook", icon : "https://cdn-icons-png.flaticon.com/512/124/124010.png", isActive : false, description : "Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook."}, - {name : "Linkedin", icon : "https://cdn-icons-png.flaticon.com/512/174/174857.png", isActive : true, description : "LinkedIn is a business and employment-focused social media platform that works through websites and mobile apps."}, - {name : "Google Ads", icon : "https://cdn-icons-png.flaticon.com/512/2301/2301145.png", isActive : false, description : "Google Ads is an online advertising platform developed by Google, where advertisers bid to display brief advertisements, service offerings"}, - {name : "Gmail", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968534.png", isActive : false, description : "Gmail is a free email service provided by Google. As of 2019, it had 1.5 billion active users worldwide."}, - {name : "Salesforce", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968880.png", isActive : false, description : "It provides customer relationship management software and applications focused on sales, customer service, marketing automation."}, - {name : "Hubspot", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968872.png", isActive : false, description : "American developer and marketer of software products for inbound marketing, sales, and customer service."}, -] - -function Integration(){ - - const dispatch = useDispatch() - - const [integrationList, setIntegrationList] = useState(INITIAL_INTEGRATION_LIST) + { + name: "Slack", + icon: "https://cdn-icons-png.flaticon.com/512/2111/2111615.png", + isActive: true, + description: + "Slack is an instant messaging program designed by Slack Technologies and owned by Salesforce.", + }, + { + name: "Facebook", + icon: "https://cdn-icons-png.flaticon.com/512/124/124010.png", + isActive: false, + description: + "Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook.", + }, + { + name: "Linkedin", + icon: "https://cdn-icons-png.flaticon.com/512/174/174857.png", + isActive: true, + description: + "LinkedIn is a business and employment-focused social media platform that works through websites and mobile apps.", + }, + { + name: "Google Ads", + icon: "https://cdn-icons-png.flaticon.com/512/2301/2301145.png", + isActive: false, + description: + "Google Ads is an online advertising platform developed by Google, where advertisers bid to display brief advertisements, service offerings", + }, + { + name: "Gmail", + icon: "https://cdn-icons-png.flaticon.com/512/5968/5968534.png", + isActive: false, + description: + "Gmail is a free email service provided by Google. As of 2019, it had 1.5 billion active users worldwide.", + }, + { + name: "Salesforce", + icon: "https://cdn-icons-png.flaticon.com/512/5968/5968880.png", + isActive: false, + description: + "It provides customer relationship management software and applications focused on sales, customer service, marketing automation.", + }, + { + name: "Hubspot", + icon: "https://cdn-icons-png.flaticon.com/512/5968/5968872.png", + isActive: false, + description: + "American developer and marketer of software products for inbound marketing, sales, and customer service.", + }, +]; +function Integration() { + const showNotification = useStore((state) => state.showNotification); - const updateIntegrationStatus = (index) => { - let integration = integrationList[index] - setIntegrationList(integrationList.map((i, k) => { - if(k===index)return {...i, isActive : !i.isActive} - return i - })) - dispatch(showNotification({message : `${integration.name} ${integration.isActive ? "disabled" : "enabled"}` , status : 1})) - } + const [integrationList, setIntegrationList] = useState( + INITIAL_INTEGRATION_LIST + ); + const updateIntegrationStatus = (index) => { + let integration = integrationList[index]; + setIntegrationList( + integrationList.map((i, k) => { + if (k === index) return { ...i, isActive: !i.isActive }; + return i; + }) + ); + showNotification({ + message: `${integration.name} ${ + integration.isActive ? "disabled" : "enabled" + }`, + status: 1, + }); + }; - return( - <> -
    - { - integrationList.map((i, k) => { - return( - - -

    - icon - {i.description} -

    -
    - updateIntegrationStatus(k)}/> -
    - -
    - ) - - }) - } -
    - - ) + return ( + <> +
    + {integrationList.map((i, k) => { + return ( + +

    + icon + {i.description} +

    +
    + updateIntegrationStatus(k)} + /> +
    +
    + ); + })} +
    + + ); } -export default Integration \ No newline at end of file +export default Integration; diff --git a/src/features/leads/components/AddLeadModalBody.js b/src/features/leads/components/AddLeadModalBody.js index cf292ad..1c6b8bb 100644 --- a/src/features/leads/components/AddLeadModalBody.js +++ b/src/features/leads/components/AddLeadModalBody.js @@ -1,62 +1,84 @@ -import { useState } from "react" -import { useDispatch } from "react-redux" -import InputText from '../../../components/Input/InputText' -import ErrorText from '../../../components/Typography/ErrorText' -import { showNotification } from "../../common/headerSlice" -import { addNewLead } from "../leadSlice" +import { useState } from "react"; +import useStore from "../../../app/store_zustand"; +import InputText from "../../../components/Input/InputText"; +import ErrorText from "../../../components/Typography/ErrorText"; const INITIAL_LEAD_OBJ = { - first_name : "", - last_name : "", - email : "" -} + first_name: "", + last_name: "", + email: "", +}; -function AddLeadModalBody({closeModal}){ - const dispatch = useDispatch() - const [loading, setLoading] = useState(false) - const [errorMessage, setErrorMessage] = useState("") - const [leadObj, setLeadObj] = useState(INITIAL_LEAD_OBJ) - - - const saveNewLead = () => { - if(leadObj.first_name.trim() === "")return setErrorMessage("First Name is required!") - else if(leadObj.email.trim() === "")return setErrorMessage("Email id is required!") - else{ - let newLeadObj = { - "id": 7, - "email": leadObj.email, - "first_name": leadObj.first_name, - "last_name": leadObj.last_name, - "avatar": "https://reqres.in/img/faces/1-image.jpg" - } - dispatch(addNewLead({newLeadObj})) - dispatch(showNotification({message : "New Lead Added!", status : 1})) - closeModal() - } - } +function AddLeadModalBody({ closeModal }) { + const addNewLead = useStore((state) => state.addNewLead); + const showNotification = useStore((state) => state.showNotification); + const [errorMessage, setErrorMessage] = useState(""); + const [leadObj, setLeadObj] = useState(INITIAL_LEAD_OBJ); - const updateFormValue = ({updateType, value}) => { - setErrorMessage("") - setLeadObj({...leadObj, [updateType] : value}) + const saveNewLead = () => { + if (leadObj.first_name.trim() === "") + return setErrorMessage("First Name is required!"); + else if (leadObj.email.trim() === "") + return setErrorMessage("Email id is required!"); + else { + let newLeadObj = { + id: 7, // Note: This ID generation might need adjustment depending on your backend/data strategy + email: leadObj.email, + first_name: leadObj.first_name, + last_name: leadObj.last_name, + avatar: "https://reqres.in/img/faces/1-image.jpg", // Placeholder avatar + }; + addNewLead(newLeadObj); + showNotification({ message: "New Lead Added!", status: 1 }); + closeModal(); } + }; - return( - <> - - + const updateFormValue = ({ updateType, value }) => { + setErrorMessage(""); + setLeadObj({ ...leadObj, [updateType]: value }); + }; - + return ( + <> + - + + - {errorMessage} -
    - - -
    - - ) + {errorMessage} +
    + + +
    + + ); } -export default AddLeadModalBody \ No newline at end of file +export default AddLeadModalBody; diff --git a/src/features/leads/index.js b/src/features/leads/index.js index 876b034..3c1af97 100644 --- a/src/features/leads/index.js +++ b/src/features/leads/index.js @@ -1,104 +1,131 @@ -import moment from "moment" -import { useEffect } from "react" -import { useDispatch, useSelector } from "react-redux" -import TitleCard from "../../components/Cards/TitleCard" -import { openModal } from "../common/modalSlice" -import { deleteLead, getLeadsContent } from "./leadSlice" -import { CONFIRMATION_MODAL_CLOSE_TYPES, MODAL_BODY_TYPES } from '../../utils/globalConstantUtil' -import TrashIcon from '@heroicons/react/24/outline/TrashIcon' -import { showNotification } from '../common/headerSlice' +import moment from "moment"; +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import TitleCard from "../../components/Cards/TitleCard"; +import { + CONFIRMATION_MODAL_CLOSE_TYPES, + MODAL_BODY_TYPES, +} from "../../utils/globalConstantUtil"; +import TrashIcon from "@heroicons/react/24/outline/TrashIcon"; const TopSideButtons = () => { + const openModal = useStore((state) => state.openModal); - const dispatch = useDispatch() + const openAddNewLeadModal = () => { + openModal({ + title: "Add New Lead", + bodyType: MODAL_BODY_TYPES.LEAD_ADD_NEW, + }); + }; - const openAddNewLeadModal = () => { - dispatch(openModal({title : "Add New Lead", bodyType : MODAL_BODY_TYPES.LEAD_ADD_NEW})) - } + return ( +
    + +
    + ); +}; - return( -
    - -
    - ) -} - -function Leads(){ - - const {leads } = useSelector(state => state.lead) - const dispatch = useDispatch() +function Leads() { + const leads = useStore((state) => state.lead.leads); + const getLeadsContent = useStore((state) => state.getLeadsContent); + const openModal = useStore((state) => state.openModal); - useEffect(() => { - dispatch(getLeadsContent()) - }, []) + useEffect(() => { + getLeadsContent(); + }, [getLeadsContent]); - + const getDummyStatus = (index) => { + if (index % 5 === 0) return
    Not Interested
    ; + else if (index % 5 === 1) + return
    In Progress
    ; + else if (index % 5 === 2) + return
    Sold
    ; + else if (index % 5 === 3) + return
    Need Followup
    ; + else return
    Open
    ; + }; - const getDummyStatus = (index) => { - if(index % 5 === 0)return
    Not Interested
    - else if(index % 5 === 1)return
    In Progress
    - else if(index % 5 === 2)return
    Sold
    - else if(index % 5 === 3)return
    Need Followup
    - else return
    Open
    - } + const deleteCurrentLead = (index) => { + openModal({ + title: "Confirmation", + bodyType: MODAL_BODY_TYPES.CONFIRMATION, + extraObject: { + message: `Are you sure you want to delete this lead?`, + type: CONFIRMATION_MODAL_CLOSE_TYPES.LEAD_DELETE, + index, + }, + }); + }; - const deleteCurrentLead = (index) => { - dispatch(openModal({title : "Confirmation", bodyType : MODAL_BODY_TYPES.CONFIRMATION, - extraObject : { message : `Are you sure you want to delete this lead?`, type : CONFIRMATION_MODAL_CLOSE_TYPES.LEAD_DELETE, index}})) - } - - return( - <> - - }> - - {/* Leads List in table format loaded from slice after api call */} -
    - - - - - - - - - - - - - { - leads.map((l, k) => { - return( - - - - - - - - - ) - }) - } - -
    NameEmail IdCreated AtStatusAssigned To
    -
    -
    -
    - Avatar -
    -
    -
    -
    {l.first_name}
    -
    {l.last_name}
    -
    -
    -
    {l.email}{moment(new Date()).add(-5*(k+2), 'days').format("DD MMM YY")}{getDummyStatus(k)}{l.last_name}
    -
    -
    - - ) + return ( + <> + } + > + {/* Leads List in table format loaded from slice after api call */} +
    + + + + + + + + + + + + + {leads.map((l, k) => { + return ( + + + + + + + + + ); + })} + +
    NameEmail IdCreated AtStatusAssigned To
    +
    +
    +
    + Avatar +
    +
    +
    +
    {l.first_name}
    +
    + {l.last_name} +
    +
    +
    +
    {l.email} + {moment(new Date()) + .add(-5 * (k + 2), "days") + .format("DD MMM YY")} + {getDummyStatus(k)}{l.last_name} + +
    +
    +
    + + ); } - -export default Leads \ No newline at end of file +export default Leads; diff --git a/src/features/leads/leadSlice.js b/src/features/leads/leadSlice.js deleted file mode 100644 index eb92937..0000000 --- a/src/features/leads/leadSlice.js +++ /dev/null @@ -1,47 +0,0 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' -import axios from 'axios' - - - -export const getLeadsContent = createAsyncThunk('/leads/content', async () => { - const response = await axios.get('/api/users?page=2', {}) - return response.data; -}) - -export const leadsSlice = createSlice({ - name: 'leads', - initialState: { - isLoading: false, - leads : [] - }, - reducers: { - - - addNewLead: (state, action) => { - let {newLeadObj} = action.payload - state.leads = [...state.leads, newLeadObj] - }, - - deleteLead: (state, action) => { - let {index} = action.payload - state.leads.splice(index, 1) - } - }, - - extraReducers: { - [getLeadsContent.pending]: state => { - state.isLoading = true - }, - [getLeadsContent.fulfilled]: (state, action) => { - state.leads = action.payload.data - state.isLoading = false - }, - [getLeadsContent.rejected]: state => { - state.isLoading = false - }, - } -}) - -export const { addNewLead, deleteLead } = leadsSlice.actions - -export default leadsSlice.reducer \ No newline at end of file diff --git a/src/features/settings/billing/index.js b/src/features/settings/billing/index.js index 92d450e..5eafd45 100644 --- a/src/features/settings/billing/index.js +++ b/src/features/settings/billing/index.js @@ -1,82 +1,155 @@ -import moment from "moment" -import { useEffect, useState } from "react" -import { useDispatch, useSelector } from "react-redux" -import TitleCard from "../../../components/Cards/TitleCard" -import { showNotification } from '../../common/headerSlice' - - +import moment from "moment"; +import { useEffect, useState } from "react"; +import TitleCard from "../../../components/Cards/TitleCard"; const BILLS = [ - {invoiceNo : "#4567", amount : "23,989", description : "Product usages", status : "Pending", generatedOn : moment(new Date()).add(-30*1, 'days').format("DD MMM YYYY"), paidOn : "-"}, - - {invoiceNo : "#4523", amount : "34,989", description : "Product usages", status : "Pending", generatedOn : moment(new Date()).add(-30*2, 'days').format("DD MMM YYYY"), paidOn : "-"}, - - {invoiceNo : "#4453", amount : "39,989", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*3, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*2, 'days').format("DD MMM YYYY")}, - - {invoiceNo : "#4359", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*4, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*3, 'days').format("DD MMM YYYY")}, - - {invoiceNo : "#3359", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*5, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*4, 'days').format("DD MMM YYYY")}, - - {invoiceNo : "#3367", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*6, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*5, 'days').format("DD MMM YYYY")}, - - {invoiceNo : "#3359", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*7, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*6, 'days').format("DD MMM YYYY")}, - - {invoiceNo : "#2359", amount : "28,927", description : "Product usages", status : "Paid", generatedOn : moment(new Date()).add(-30*8, 'days').format("DD MMM YYYY"), paidOn : moment(new Date()).add(-24*7, 'days').format("DD MMM YYYY")}, - - -] - -function Billing(){ - - - const [bills, setBills] = useState(BILLS) - - const getPaymentStatus = (status) => { - if(status === "Paid")return
    {status}
    - if(status === "Pending")return
    {status}
    - else return
    {status}
    - } - - return( - <> - - - - {/* Invoice list in table format loaded constant */} -
    - - - - - - - - - - - - - { - bills.map((l, k) => { - return( - - - - - - - - - ) - }) - } - -
    Invoice NoInvoice Generated OnDescriptionAmountStatusInvoice Paid On
    {l.invoiceNo}{l.generatedOn}{l.description}${l.amount}{getPaymentStatus(l.status)}{l.paidOn}
    -
    -
    - - ) + { + invoiceNo: "#4567", + amount: "23,989", + description: "Product usages", + status: "Pending", + generatedOn: moment(new Date()) + .add(-30 * 1, "days") + .format("DD MMM YYYY"), + paidOn: "-", + }, + + { + invoiceNo: "#4523", + amount: "34,989", + description: "Product usages", + status: "Pending", + generatedOn: moment(new Date()) + .add(-30 * 2, "days") + .format("DD MMM YYYY"), + paidOn: "-", + }, + + { + invoiceNo: "#4453", + amount: "39,989", + description: "Product usages", + status: "Paid", + generatedOn: moment(new Date()) + .add(-30 * 3, "days") + .format("DD MMM YYYY"), + paidOn: moment(new Date()) + .add(-24 * 2, "days") + .format("DD MMM YYYY"), + }, + + { + invoiceNo: "#4359", + amount: "28,927", + description: "Product usages", + status: "Paid", + generatedOn: moment(new Date()) + .add(-30 * 4, "days") + .format("DD MMM YYYY"), + paidOn: moment(new Date()) + .add(-24 * 3, "days") + .format("DD MMM YYYY"), + }, + + { + invoiceNo: "#3359", + amount: "28,927", + description: "Product usages", + status: "Paid", + generatedOn: moment(new Date()) + .add(-30 * 5, "days") + .format("DD MMM YYYY"), + paidOn: moment(new Date()) + .add(-24 * 4, "days") + .format("DD MMM YYYY"), + }, + + { + invoiceNo: "#3367", + amount: "28,927", + description: "Product usages", + status: "Paid", + generatedOn: moment(new Date()) + .add(-30 * 6, "days") + .format("DD MMM YYYY"), + paidOn: moment(new Date()) + .add(-24 * 5, "days") + .format("DD MMM YYYY"), + }, + + { + invoiceNo: "#3359", + amount: "28,927", + description: "Product usages", + status: "Paid", + generatedOn: moment(new Date()) + .add(-30 * 7, "days") + .format("DD MMM YYYY"), + paidOn: moment(new Date()) + .add(-24 * 6, "days") + .format("DD MMM YYYY"), + }, + + { + invoiceNo: "#2359", + amount: "28,927", + description: "Product usages", + status: "Paid", + generatedOn: moment(new Date()) + .add(-30 * 8, "days") + .format("DD MMM YYYY"), + paidOn: moment(new Date()) + .add(-24 * 7, "days") + .format("DD MMM YYYY"), + }, +]; + +function Billing() { + const [bills, setBills] = useState(BILLS); + + const getPaymentStatus = (status) => { + if (status === "Paid") + return
    {status}
    ; + if (status === "Pending") + return
    {status}
    ; + else return
    {status}
    ; + }; + + return ( + <> + + {/* Invoice list in table format loaded constant */} +
    + + + + + + + + + + + + + {bills.map((l, k) => { + return ( + + + + + + + + + ); + })} + +
    Invoice NoInvoice Generated OnDescriptionAmountStatusInvoice Paid On
    {l.invoiceNo}{l.generatedOn}{l.description}${l.amount}{getPaymentStatus(l.status)}{l.paidOn}
    +
    +
    + + ); } - -export default Billing \ No newline at end of file +export default Billing; diff --git a/src/features/settings/profilesettings/index.js b/src/features/settings/profilesettings/index.js index 2386337..5d29db2 100644 --- a/src/features/settings/profilesettings/index.js +++ b/src/features/settings/profilesettings/index.js @@ -1,51 +1,83 @@ -import moment from "moment" -import { useEffect, useState } from "react" -import { useDispatch, useSelector } from "react-redux" -import TitleCard from "../../../components/Cards/TitleCard" -import { showNotification } from '../../common/headerSlice' -import InputText from '../../../components/Input/InputText' -import TextAreaInput from '../../../components/Input/TextAreaInput' -import ToogleInput from '../../../components/Input/ToogleInput' +import useStore from "../../../app/store_zustand"; +import TitleCard from "../../../components/Cards/TitleCard"; +import InputText from "../../../components/Input/InputText"; +import TextAreaInput from "../../../components/Input/TextAreaInput"; +import ToogleInput from "../../../components/Input/ToogleInput"; -function ProfileSettings(){ +function ProfileSettings() { + const showNotification = useStore((state) => state.showNotification); + // Call API to update profile settings changes + const updateProfile = () => { + showNotification({ message: "Profile Updated", status: 1 }); + }; - const dispatch = useDispatch() + const updateFormValue = ({ updateType, value }) => { + console.log(updateType); + }; - // Call API to update profile settings changes - const updateProfile = () => { - dispatch(showNotification({message : "Profile Updated", status : 1})) - } + return ( + <> + +
    + + + + + +
    +
    - const updateFormValue = ({updateType, value}) => { - console.log(updateType) - } +
    + + + +
    - return( - <> - - - -
    - - - - - -
    -
    - -
    - - - -
    - -
    -
    - - ) +
    + +
    +
    + + ); } - -export default ProfileSettings \ No newline at end of file +export default ProfileSettings; diff --git a/src/features/settings/team/index.js b/src/features/settings/team/index.js index 07e4547..7a62ba0 100644 --- a/src/features/settings/team/index.js +++ b/src/features/settings/team/index.js @@ -1,97 +1,152 @@ -import moment from "moment" -import { useEffect, useState } from "react" -import { useDispatch, useSelector } from "react-redux" -import TitleCard from "../../../components/Cards/TitleCard" -import { showNotification } from '../../common/headerSlice' +import moment from "moment"; +import { useState } from "react"; +import useStore from "../../../app/store_zustand"; +import TitleCard from "../../../components/Cards/TitleCard"; const TopSideButtons = () => { + const showNotification = useStore((state) => state.showNotification); - const dispatch = useDispatch() - - const addNewTeamMember = () => { - dispatch(showNotification({message : "Add New Member clicked", status : 1})) - } - - return( -
    - -
    - ) -} + const addNewTeamMember = () => { + showNotification({ message: "Add New Member clicked", status: 1 }); + }; + return ( +
    + +
    + ); +}; const TEAM_MEMBERS = [ - {name : "Alex", avatar : "https://reqres.in/img/faces/1-image.jpg", email : "alex@dashwind.com", role : "Owner", joinedOn : moment(new Date()).add(-5*1, 'days').format("DD MMM YYYY"), lastActive : "5 hr ago"}, - {name : "Ereena", avatar : "https://reqres.in/img/faces/2-image.jpg", email : "ereena@dashwind.com", role : "Admin", joinedOn : moment(new Date()).add(-5*2, 'days').format("DD MMM YYYY"), lastActive : "15 min ago"}, - {name : "John", avatar : "https://reqres.in/img/faces/3-image.jpg", email : "jhon@dashwind.com", role : "Admin", joinedOn : moment(new Date()).add(-5*3, 'days').format("DD MMM YYYY"), lastActive : "20 hr ago"}, - {name : "Matrix", avatar : "https://reqres.in/img/faces/4-image.jpg", email : "matrix@dashwind.com", role : "Manager", joinedOn : moment(new Date()).add(-5*4, 'days').format("DD MMM YYYY"), lastActive : "1 hr ago"}, - {name : "Virat", avatar : "https://reqres.in/img/faces/5-image.jpg", email : "virat@dashwind.com", role : "Support", joinedOn : moment(new Date()).add(-5*5, 'days').format("DD MMM YYYY"), lastActive : "40 min ago"}, - {name : "Miya", avatar : "https://reqres.in/img/faces/6-image.jpg", email : "miya@dashwind.com", role : "Support", joinedOn : moment(new Date()).add(-5*7, 'days').format("DD MMM YYYY"), lastActive : "5 hr ago"}, - -] + { + name: "Alex", + avatar: "https://reqres.in/img/faces/1-image.jpg", + email: "alex@dashwind.com", + role: "Owner", + joinedOn: moment(new Date()) + .add(-5 * 1, "days") + .format("DD MMM YYYY"), + lastActive: "5 hr ago", + }, + { + name: "Ereena", + avatar: "https://reqres.in/img/faces/2-image.jpg", + email: "ereena@dashwind.com", + role: "Admin", + joinedOn: moment(new Date()) + .add(-5 * 2, "days") + .format("DD MMM YYYY"), + lastActive: "15 min ago", + }, + { + name: "John", + avatar: "https://reqres.in/img/faces/3-image.jpg", + email: "jhon@dashwind.com", + role: "Admin", + joinedOn: moment(new Date()) + .add(-5 * 3, "days") + .format("DD MMM YYYY"), + lastActive: "20 hr ago", + }, + { + name: "Matrix", + avatar: "https://reqres.in/img/faces/4-image.jpg", + email: "matrix@dashwind.com", + role: "Manager", + joinedOn: moment(new Date()) + .add(-5 * 4, "days") + .format("DD MMM YYYY"), + lastActive: "1 hr ago", + }, + { + name: "Virat", + avatar: "https://reqres.in/img/faces/5-image.jpg", + email: "virat@dashwind.com", + role: "Support", + joinedOn: moment(new Date()) + .add(-5 * 5, "days") + .format("DD MMM YYYY"), + lastActive: "40 min ago", + }, + { + name: "Miya", + avatar: "https://reqres.in/img/faces/6-image.jpg", + email: "miya@dashwind.com", + role: "Support", + joinedOn: moment(new Date()) + .add(-5 * 7, "days") + .format("DD MMM YYYY"), + lastActive: "5 hr ago", + }, +]; -function Team(){ +function Team() { + const [members, setMembers] = useState(TEAM_MEMBERS); + const getRoleComponent = (role) => { + if (role === "Admin") + return
    {role}
    ; + if (role === "Manager") return
    {role}
    ; + if (role === "Owner") + return
    {role}
    ; + if (role === "Support") + return
    {role}
    ; + else return
    {role}
    ; + }; - const [members, setMembers] = useState(TEAM_MEMBERS) - - const getRoleComponent = (role) => { - if(role === "Admin")return
    {role}
    - if(role === "Manager")return
    {role}
    - if(role === "Owner")return
    {role}
    - if(role === "Support")return
    {role}
    - else return
    {role}
    - } - - return( - <> - - }> - - {/* Team Member list in table format loaded constant */} -
    - - - - - - - - - - - - { - members.map((l, k) => { - return( - - - - - - - - ) - }) - } - -
    NameEmail IdJoined OnRoleLast Active
    -
    -
    -
    - Avatar -
    -
    -
    -
    {l.name}
    -
    -
    -
    {l.email}{l.joinedOn}{getRoleComponent(l.role)}{l.lastActive}
    -
    -
    - - ) + return ( + <> + } + > + {/* Team Member list in table format loaded constant */} +
    + + + + + + + + + + + + {members.map((l, k) => { + return ( + + + + + + + + ); + })} + +
    NameEmail IdJoined OnRoleLast Active
    +
    +
    +
    + Avatar +
    +
    +
    +
    {l.name}
    +
    +
    +
    {l.email}{l.joinedOn}{getRoleComponent(l.role)}{l.lastActive}
    +
    +
    + + ); } - -export default Team \ No newline at end of file +export default Team; diff --git a/src/features/transactions/index.js b/src/features/transactions/index.js index 071649b..0e16d00 100644 --- a/src/features/transactions/index.js +++ b/src/features/transactions/index.js @@ -1,128 +1,156 @@ -import moment from "moment" -import { useEffect, useState } from "react" -import { useDispatch, useSelector } from "react-redux" -import { showNotification } from "../common/headerSlice" -import TitleCard from "../../components/Cards/TitleCard" -import { RECENT_TRANSACTIONS } from "../../utils/dummyData" -import FunnelIcon from '@heroicons/react/24/outline/FunnelIcon' -import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon' -import SearchBar from "../../components/Input/SearchBar" - -const TopSideButtons = ({removeFilter, applyFilter, applySearch}) => { - - const [filterParam, setFilterParam] = useState("") - const [searchText, setSearchText] = useState("") - const locationFilters = ["Paris", "London", "Canada", "Peru", "Tokyo"] - - const showFiltersAndApply = (params) => { - applyFilter(params) - setFilterParam(params) +import moment from "moment"; +import { useEffect, useState } from "react"; +import TitleCard from "../../components/Cards/TitleCard"; +import { RECENT_TRANSACTIONS } from "../../utils/dummyData"; +import FunnelIcon from "@heroicons/react/24/outline/FunnelIcon"; +import XMarkIcon from "@heroicons/react/24/outline/XMarkIcon"; +import SearchBar from "../../components/Input/SearchBar"; + +const TopSideButtons = ({ removeFilter, applyFilter, applySearch }) => { + const [filterParam, setFilterParam] = useState(""); + const [searchText, setSearchText] = useState(""); + const locationFilters = ["Paris", "London", "Canada", "Peru", "Tokyo"]; + + const showFiltersAndApply = (params) => { + applyFilter(params); + setFilterParam(params); + }; + + const removeAppliedFilter = () => { + removeFilter(); + setFilterParam(""); + setSearchText(""); + }; + + useEffect(() => { + if (searchText == "") { + removeAppliedFilter(); + } else { + applySearch(searchText); } - - const removeAppliedFilter = () => { - removeFilter() - setFilterParam("") - setSearchText("") - } - - useEffect(() => { - if(searchText == ""){ - removeAppliedFilter() - }else{ - applySearch(searchText) + }, [searchText]); + + return ( +
    + + {filterParam != "" && ( + + )} +
    + + +
    +
    + ); +}; + +function Transactions() { + const [trans, setTrans] = useState(RECENT_TRANSACTIONS); + + const removeFilter = () => { + setTrans(RECENT_TRANSACTIONS); + }; + + const applyFilter = (params) => { + let filteredTransactions = RECENT_TRANSACTIONS.filter((t) => { + return t.location == params; + }); + setTrans(filteredTransactions); + }; + + // Search according to name + const applySearch = (value) => { + let filteredTransactions = RECENT_TRANSACTIONS.filter((t) => { + return ( + t.email.toLowerCase().includes(value.toLowerCase()) || + t.email.toLowerCase().includes(value.toLowerCase()) + ); + }); + setTrans(filteredTransactions); + }; + + return ( + <> + } - }, [searchText]) - - return( -
    - - {filterParam != "" && } -
    - - -
    + > + {/* Team Member list in table format loaded constant */} +
    + + + + + + + + + + + + {trans.map((l, k) => { + return ( + + + + + + + + ); + })} + +
    NameEmail IdLocationAmountTransaction Date
    +
    +
    +
    + Avatar +
    +
    +
    +
    {l.name}
    +
    +
    +
    {l.email}{l.location}${l.amount}{moment(l.date).format("D MMM")}
    - ) + + + ); } - -function Transactions(){ - - - const [trans, setTrans] = useState(RECENT_TRANSACTIONS) - - const removeFilter = () => { - setTrans(RECENT_TRANSACTIONS) - } - - const applyFilter = (params) => { - let filteredTransactions = RECENT_TRANSACTIONS.filter((t) => {return t.location == params}) - setTrans(filteredTransactions) - } - - // Search according to name - const applySearch = (value) => { - let filteredTransactions = RECENT_TRANSACTIONS.filter((t) => {return t.email.toLowerCase().includes(value.toLowerCase()) || t.email.toLowerCase().includes(value.toLowerCase())}) - setTrans(filteredTransactions) - } - - return( - <> - - }> - - {/* Team Member list in table format loaded constant */} -
    - - - - - - - - - - - - { - trans.map((l, k) => { - return( - - - - - - - - ) - }) - } - -
    NameEmail IdLocationAmountTransaction Date
    -
    -
    -
    - Avatar -
    -
    -
    -
    {l.name}
    -
    -
    -
    {l.email}{l.location}${l.amount}{moment(l.date).format("D MMM")}
    -
    -
    - - ) -} - - -export default Transactions \ No newline at end of file +export default Transactions; diff --git a/src/features/user/components/TemplatePointers.js b/src/features/user/components/TemplatePointers.js index 208afd9..ff15f75 100644 --- a/src/features/user/components/TemplatePointers.js +++ b/src/features/user/components/TemplatePointers.js @@ -3,7 +3,7 @@ function TemplatePointers(){ <>

    Admin Dashboard Starter Kit

    ✓ Light/dark mode toggle

    -

    ✓ Redux toolkit and other utility libraries configured

    +

    ✓ Zustand toolkit and other utility libraries configured

    ✓ Calendar, Modal, Sidebar components

    ✓ User-friendly documentation

    ✓ Daisy UI components, Tailwind CSS support

    diff --git a/src/index.js b/src/index.js index c6c53d9..d20b617 100644 --- a/src/index.js +++ b/src/index.js @@ -1,20 +1,16 @@ -import React, { Suspense } from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; -import store from './app/store' -import { Provider } from 'react-redux' -import SuspenseContent from './containers/SuspenseContent'; +import React, { Suspense } from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; +import SuspenseContent from "./containers/SuspenseContent"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( // - }> - - - - + }> + + // ); diff --git a/src/pages/protected/404.js b/src/pages/protected/404.js index 0df7b8e..25c0c18 100644 --- a/src/pages/protected/404.js +++ b/src/pages/protected/404.js @@ -1,26 +1,24 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import FaceFrownIcon from '@heroicons/react/24/solid/FaceFrownIcon' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import FaceFrownIcon from "@heroicons/react/24/solid/FaceFrownIcon"; -function InternalPage(){ +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - const dispatch = useDispatch() + useEffect(() => { + setPageTitle(""); + }, [setPageTitle]); - useEffect(() => { - dispatch(setPageTitle({ title : ""})) - }, []) - - return( -
    -
    -
    - -

    404 - Not Found

    -
    -
    + return ( +
    +
    +
    + +

    404 - Not Found

    - ) +
    +
    + ); } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Bills.js b/src/pages/protected/Bills.js index ea9cf68..87c9abe 100644 --- a/src/pages/protected/Bills.js +++ b/src/pages/protected/Bills.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import Billing from '../../features/settings/billing' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import Billing from "../../features/settings/billing"; -function InternalPage(){ - const dispatch = useDispatch() +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - useEffect(() => { - dispatch(setPageTitle({ title : "Bills"})) - }, []) + useEffect(() => { + setPageTitle("Bills"); + }, [setPageTitle]); - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Blank.js b/src/pages/protected/Blank.js index 0aba97b..cadcd7e 100644 --- a/src/pages/protected/Blank.js +++ b/src/pages/protected/Blank.js @@ -1,27 +1,27 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import DocComponentsNav from "../../features/documentation/components/DocComponentsNav"; +import GettingStartedNav from "../../features/documentation/components/GettingStartedNav"; -import DocumentIcon from '@heroicons/react/24/solid/DocumentIcon' +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); -function InternalPage(){ + useEffect(() => { + setPageTitle("Documentation"); + }, [setPageTitle]); - const dispatch = useDispatch() - - useEffect(() => { - dispatch(setPageTitle({ title : "Page Title"})) - }, []) - - return( -
    -
    -
    - -

    Blank Page

    -
    -
    + return ( +
    +
    +
    +
    + +
    +
    - ) +
    +
    + ); } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Calendar.js b/src/pages/protected/Calendar.js index 5a4a708..2015f2a 100644 --- a/src/pages/protected/Calendar.js +++ b/src/pages/protected/Calendar.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import Calendar from '../../features/calendar' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import Calendar from "../../features/calendar"; -function InternalPage(){ - const dispatch = useDispatch() +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - useEffect(() => { - dispatch(setPageTitle({ title : "Calendar"})) - }, []) + useEffect(() => { + setPageTitle("Calendar"); + }, [setPageTitle]); - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Charts.js b/src/pages/protected/Charts.js index 9db4cfd..248f2f8 100644 --- a/src/pages/protected/Charts.js +++ b/src/pages/protected/Charts.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import Charts from '../../features/charts' -import { setPageTitle } from '../../features/common/headerSlice' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import Charts from "../../features/charts"; -function InternalPage(){ - const dispatch = useDispatch() +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - useEffect(() => { - dispatch(setPageTitle({ title : "Analytics"})) - }, []) + useEffect(() => { + setPageTitle("Analytics"); + }, [setPageTitle]); - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Dashboard.js b/src/pages/protected/Dashboard.js index 74e6c8f..34ca576 100644 --- a/src/pages/protected/Dashboard.js +++ b/src/pages/protected/Dashboard.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import Dashboard from '../../features/dashboard/index' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import Dashboard from "../../features/dashboard/index"; -function InternalPage(){ - const dispatch = useDispatch() +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - useEffect(() => { - dispatch(setPageTitle({ title : "Dashboard"})) - }, []) + useEffect(() => { + setPageTitle("Dashboard"); + }, [setPageTitle]); - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Integration.js b/src/pages/protected/Integration.js index 9ee5ae7..c8f2f25 100644 --- a/src/pages/protected/Integration.js +++ b/src/pages/protected/Integration.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import Integration from '../../features/integration' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import Integration from "../../features/integration"; -function InternalPage(){ +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - const dispatch = useDispatch() + useEffect(() => { + setPageTitle("Integrations"); + }, [setPageTitle]); - useEffect(() => { - dispatch(setPageTitle({ title : "Integrations"})) - }, []) - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Leads.js b/src/pages/protected/Leads.js index b63c818..8463e4b 100644 --- a/src/pages/protected/Leads.js +++ b/src/pages/protected/Leads.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import Leads from '../../features/leads' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import Leads from "../../features/leads"; -function InternalPage(){ - const dispatch = useDispatch() +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - useEffect(() => { - dispatch(setPageTitle({ title : "Leads"})) - }, []) + useEffect(() => { + setPageTitle("Leads"); + }, [setPageTitle]); - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/ProfileSettings.js b/src/pages/protected/ProfileSettings.js index f6b23c9..996ad80 100644 --- a/src/pages/protected/ProfileSettings.js +++ b/src/pages/protected/ProfileSettings.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import ProfileSettings from '../../features/settings/profilesettings' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import ProfileSettings from "../../features/settings/profilesettings"; -function InternalPage(){ - const dispatch = useDispatch() +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - useEffect(() => { - dispatch(setPageTitle({ title : "Settings"})) - }, []) + useEffect(() => { + setPageTitle("Settings"); + }, [setPageTitle]); - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Team.js b/src/pages/protected/Team.js index 1d16b9c..587f68d 100644 --- a/src/pages/protected/Team.js +++ b/src/pages/protected/Team.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import Team from '../../features/settings/team' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import Team from "../../features/settings/team"; -function InternalPage(){ - const dispatch = useDispatch() +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - useEffect(() => { - dispatch(setPageTitle({ title : "Team Members"})) - }, []) + useEffect(() => { + setPageTitle("Team Members"); + }, [setPageTitle]); - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Transactions.js b/src/pages/protected/Transactions.js index 609cb0e..eea5ef2 100644 --- a/src/pages/protected/Transactions.js +++ b/src/pages/protected/Transactions.js @@ -1,19 +1,15 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import Transactions from '../../features/transactions' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import Transactions from "../../features/transactions"; -function InternalPage(){ - const dispatch = useDispatch() +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - useEffect(() => { - dispatch(setPageTitle({ title : "Transactions"})) - }, []) + useEffect(() => { + setPageTitle("Transactions"); + }, [setPageTitle]); - - return( - - ) + return ; } -export default InternalPage \ No newline at end of file +export default InternalPage; diff --git a/src/pages/protected/Welcome.js b/src/pages/protected/Welcome.js index 8a94caa..fc19bab 100644 --- a/src/pages/protected/Welcome.js +++ b/src/pages/protected/Welcome.js @@ -1,27 +1,27 @@ -import { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { setPageTitle } from '../../features/common/headerSlice' -import {Link} from 'react-router-dom' -import TemplatePointers from '../../features/user/components/TemplatePointers' +import { useEffect } from "react"; +import useStore from "../../app/store_zustand"; +import { Link } from "react-router-dom"; +import TemplatePointers from "../../features/user/components/TemplatePointers"; -function InternalPage(){ +function InternalPage() { + const setPageTitle = useStore((state) => state.setPageTitle); - const dispatch = useDispatch() + useEffect(() => { + setPageTitle(""); + }, [setPageTitle]); - useEffect(() => { - dispatch(setPageTitle({ title : ""})) - }, []) - - return( -
    + return ( +
    - - + + + +
    - ) + ); } -export default InternalPage \ No newline at end of file +export default InternalPage;