diff --git a/.env b/.env new file mode 100644 index 0000000..333327d --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +REACT_APP_BASE_URL="https://rubix.betadelivery.com/api" +REACT_APP_IMAGE_URL="https://rubix.betadelivery.com/" +REACT_APP_ENVIRONMENT="development" \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..3e212e1 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,21 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + settings: { react: { version: '18.2' } }, + plugins: ['react-refresh'], + rules: { + 'react/jsx-no-target-blank': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/index.html b/index.html new file mode 100644 index 0000000..d1163d1 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Rubix + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..6618cd0 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "rubix-admin-dashboard", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@chakra-ui/icons": "^2.1.1", + "@chakra-ui/react": "^2.8.2", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@hookform/resolvers": "^3.3.4", + "@reduxjs/toolkit": "^2.2.3", + "bootstrap": "5.3.3", + "dotenv": "^16.4.5", + "framer-motion": "^11.1.5", + "js-cookie": "^3.0.5", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.51.3", + "react-icons": "^5.1.0", + "react-quill": "^2.0.0", + "react-redux": "^9.1.1", + "react-router-dom": "^6.22.3", + "redux-persist": "^6.0.0", + "redux-persist-transform-encrypt": "^5.1.1", + "uuid": "^9.0.1", + "yup": "^1.4.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "vite": "^5.2.0" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..428484b --- /dev/null +++ b/src/App.css @@ -0,0 +1,274 @@ +@import url("https://fonts.googleapis.com/css2?family=League+Spartan:wght@100..900&display=swap"); +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap'); + +* { + box-sizing: border-box; + margin: 0; + padding: 0; + /* font-family: "League Spartan", sans-serif !important; */ + font-family: "Noto Sans", sans-serif !important; +} + +.pointer { + cursor: pointer !important; +} + +.activee{ + text-decoration: none; /* Remove underline */ + font-weight: bold; /* Optionally change font weight for active link */ + background: linear-gradient(to right, #7A45FB, #DE41B5); /* Gradient background */ + -webkit-background-clip: text; /* Clip text to the background area */ + -webkit-text-fill-color: transparent; /* Fill text with the background color */ + transition: all 0.3s ease-in-out; +} + +.active{ + background-color: #e2e8f086; +} + +.link { + text-decoration: none ; + transition: all 0.2s ease-in-out; + font-weight: 400; +} + +.link:hover { + background-color: #e2e8f065 !important; +} + +.active:hover { + background-color: #e2e8f065; +} + +.web-text-small{ + font-size: 12px !important; +} +.web-text-xxsmall{ + font-size: 9px !important; +} +.web-text-xsmall{ + font-size: 11px !important; +} + +.web-text-medium{ + font-size: 13px !important; +} + +.web-text-large{ + font-size: 15px !important; +} + +.rubix-text-dark{ + color:#3b3e3f +} + + + +.arrow-button{ + transition: all 0.5s; +} + + +.arrow-button:active{ + background: #fff; +} + +.arrow-button:focus{ + background: #fff; +} + +.arrow-button:hover{ + background: linear-gradient(90deg, #DE41B5 0%, #7A45FB 100%); +} + +.greeting{ + text-decoration: none; /* Remove underline */ + font-weight: bold; /* Optionally change font weight for active link */ + background: linear-gradient(to right, #7A45FB, #DE41B5); /* Gradient background */ + -webkit-background-clip: text; /* Clip text to the background area */ + -webkit-text-fill-color: transparent; /* Fill text with the background color */ + transition: all 0.3s ease-in-out; +} + +.primary-btn{ + background-color: #38187C !important; +} + +.team-slider .swiper-button-next:after { + position: absolute; + top: 185px; + width: 38px; + height: 38px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50px; + font-size: 18px; + color: #000; + font-weight: 700; + right: 110px; + background-image: radial-gradient( + circle, + #ffffff, + #eee2f2, + #e7c3dc, + #e5a3ba, + #de858e + ); +} +.team-slider .swiper-button-prev:after { + position: absolute; + top: 185px; + width: 38px; + height: 38px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50px; + font-size: 18px; + color: #000; + font-weight: 700; + left: 110px; + background-image: radial-gradient( + circle, + #ffffff, + #eee2f2, + #e7c3dc, + #e5a3ba, + #de858e + ); +} + + + +.rotate{ + + animation: animName 10s linear infinite; +} + +@keyframes animName { + 0%{ + transform: rotate(0deg); + } + 100%{ + transform: rotate(360deg); + } + } + + + +.lds-ellipsis { + display: inline-block; + position: relative; + width: 64px; + height: 10px; +} +.lds-ellipsis div { + position: absolute; + width: 10px; + height: 10px; + border-radius: 50%; + background: #fff; + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} +.lds-ellipsis div:nth-child(1) { + left: 6px; + animation: lds-ellipsis1 0.6s infinite; +} +.lds-ellipsis div:nth-child(2) { + left: 6px; + animation: lds-ellipsis2 0.6s infinite; +} +.lds-ellipsis div:nth-child(3) { + left: 26px; + animation: lds-ellipsis2 0.6s infinite; +} +.lds-ellipsis div:nth-child(4) { + left: 45px; + animation: lds-ellipsis3 0.6s infinite; +} +@keyframes lds-ellipsis1 { + 0% { + transform: scale(0); + } + 100% { + transform: scale(1); + } +} +@keyframes lds-ellipsis3 { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0); + } +} +@keyframes lds-ellipsis2 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(19px, 0); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + +/* Extra small devices (phones, 600px and down) */ +@media only screen and (max-width: 600px) { + body { + font-size: 14px !important; + } + + +} + +/* Small devices (portrait tablets and large phones, 600px and up) */ +@media only screen and (min-width: 600px) { + body { + font-size: 16px !important; + } +} + +/* Medium devices (landscape tablets, 768px and up) */ +@media only screen and (min-width: 768px) { + body { + font-size: 18px !important; + } +} + +/* Large devices (laptops/desktops, 992px and up) */ +@media only screen and (min-width: 992px) { + body { + font-size: 20px !important; + } +} + +/* Extra large devices (large laptops and desktops, 1200px and up) */ +@media only screen and (min-width: 1200px) { + body { + font-size: 22px !important; + } +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..0c2871d --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,72 @@ +import React, { useContext, useEffect, useState } from "react"; +import "bootstrap/dist/css/bootstrap.min.css"; +import "bootstrap/dist/js/bootstrap.bundle.min.js"; +import { + BrowserRouter as Router, + Routes, + Route, + Navigate, +} from "react-router-dom"; +import "./App.css"; // Import CSS file +import DefaultLayout from "./Layout/DefaultLayout"; +import NotFound from "./Pages/NotFound"; +import Login from "./Pages/Login"; +import GlobalStateContext from "./Contexts/GlobalStateContext"; +import Cookies from "js-cookie"; +import NoInternetScreen from "./Pages/NoInternetScreen"; + +const App = () => { + // const { isAuthenticate } = useSelector((state) => state?.auth); + + const { isAuthenticate } = useContext(GlobalStateContext); + const isAuthenticatedInCookie = Cookies.get("isAuthenticated"); + + const [isOnline, setIsOnline] = useState(navigator.onLine); + + useEffect(() => { + const handleOnlineStatusChange = () => { + setIsOnline(navigator.onLine); + }; + + window.addEventListener("online", handleOnlineStatusChange); + window.addEventListener("offline", handleOnlineStatusChange); + + return () => { + window.removeEventListener("online", handleOnlineStatusChange); + window.removeEventListener("offline", handleOnlineStatusChange); + }; + }, []); + + const PrivateRoute = ({ children }) => { + if (!isAuthenticate && isAuthenticatedInCookie !== "true") { + return ; + } + return children; + }; + + return ( + + + } /> + + ) : ( + + ) + ) : ( + + ) + } + /> + + } /> + + + ); +}; + +export default App; diff --git a/src/Components/BannerStack.jsx b/src/Components/BannerStack.jsx new file mode 100644 index 0000000..a799996 --- /dev/null +++ b/src/Components/BannerStack.jsx @@ -0,0 +1,79 @@ +import { ArrowForwardIcon } from "@chakra-ui/icons"; +import { Box, Button, HStack, Skeleton, Text } from "@chakra-ui/react"; +import React from "react"; +import { Link } from "react-router-dom"; +import CommunityBannerCard from "../Pages/Community/CommunityBannerCard"; + +const BannerStack = ({ + stackTitle, + viewAllLink, + bannerIsLoading, + bannerArray, + viewBannerLink, +}) => { + return ( + + + + {stackTitle} + + + + + + + + + {bannerIsLoading + ? Array.from({ length: 4 }).map((_, index) => ( + + + + )) + : bannerArray?.map( + ({ + id, + CTO_button_title, + banner_image, + Heading, + createdAt, + sub_heading, + }) => ( + + + + ) + )} + + + ); +}; + +export default BannerStack; diff --git a/src/Components/Buttons/Button01.jsx b/src/Components/Buttons/Button01.jsx new file mode 100644 index 0000000..1417714 --- /dev/null +++ b/src/Components/Buttons/Button01.jsx @@ -0,0 +1,25 @@ +import Loader01 from "../Loaders/Loader01"; + +const Button01 = ({ title, onClick, type, backgroundColor, hover, isLoading }) => { + return ( + + ); +}; + +export default Button01; diff --git a/src/Components/Buttons/Button02.jsx b/src/Components/Buttons/Button02.jsx new file mode 100644 index 0000000..cc5d079 --- /dev/null +++ b/src/Components/Buttons/Button02.jsx @@ -0,0 +1,47 @@ + +import { RiLogoutCircleLine } from "react-icons/ri"; + +const Button02 = ({ title, onClick, type, gradientColors, height, width, open }) => { + const [startColor, endColor] = gradientColors; + return ( + + ); +}; + +export default Button02; diff --git a/src/Components/ChipSelector/ChipSelector.css b/src/Components/ChipSelector/ChipSelector.css new file mode 100644 index 0000000..4b2247d --- /dev/null +++ b/src/Components/ChipSelector/ChipSelector.css @@ -0,0 +1,60 @@ + +label { + display: inline-block; + margin-bottom: 0.5rem; + font-weight: bold; + } + + .input-container { + display: flex; + flex-wrap: wrap; + row-gap: 1rem; + align-items: center; + border: 1px solid rgb(212, 206, 206); + border-radius: 0.2rem; + } + + + + .chips { + list-style: none; + display: flex; + } + + .chip { + background-color: #cfe1ff; + display: flex; + align-items: center; + padding: 0.2rem; + border-radius: 0.5rem; + margin-right: 1rem; + } + + .chip span { + color: #013380; + } + + .chip svg { + font-size: 2.5rem; + fill: #3270d1; + cursor: pointer; + transition: fill 0.2s; + } + + .chip svg:hover { + fill: #2857a3; + transition: all 0.2s; + } + + input { + font-size: 1.6rem; + background: transparent; + border: none; + outline: none; + } + + .error-message { + margin-top: 1rem; + color: red; + } + \ No newline at end of file diff --git a/src/Components/ChipSelector/ChipSelector.jsx b/src/Components/ChipSelector/ChipSelector.jsx new file mode 100644 index 0000000..173bd48 --- /dev/null +++ b/src/Components/ChipSelector/ChipSelector.jsx @@ -0,0 +1,62 @@ +import React, { useState } from "react"; +import { Box, Input, Tag, TagCloseButton, TagLabel } from "@chakra-ui/react"; +import { TiWarning } from "react-icons/ti"; + +const ChipSelector = ({chips, setChips}) => { + const [text, setText] = useState(""); + const [validationError, setValidationError] = useState(""); + console.log(chips); + + function removeChip(chipToRemove) { + const updatedChips = chips.filter((chip) => chip !== chipToRemove); + setChips(updatedChips); + } + + function handlePressEnter(e) { + if (e.key === "Enter") e.preventDefault(); + if (e.key !== "Enter" || !text) return; + if (chips.includes(text)) { + return setValidationError("Cannot add the same input more than once"); + } + setChips((prevState) => [...prevState, text]); + setText(""); + setValidationError(""); + } + + return ( +
+ + setText(e.target.value)} + onKeyDown={handlePressEnter} + /> + {validationError && {validationError}} + + {chips?.map((chip, i) => ( + + {chip} + removeChip(chip)} /> + + ))} + + +
+ ); +}; + +export default ChipSelector; diff --git a/src/Components/CommunityBanner.jsx b/src/Components/CommunityBanner.jsx new file mode 100644 index 0000000..f1d044e --- /dev/null +++ b/src/Components/CommunityBanner.jsx @@ -0,0 +1,153 @@ +import { Box, Button, Container, Text } from "@chakra-ui/react"; +import banner from "../assets/communityBanner.webp"; + +const BannerContent = [ + { + heading1: `Welcome To The`, + heading2: `Rubix Community`, + }, + { + subheading: `This is a space for enterprises, dApp developers, stakeholders + and blockchain advocates to aggregate resources and ideas to make a difference + through our green blockchain technology.`, + }, + { + btn: `Explore our community`, + }, +]; + +const CommunityBanner = () => { + return ( + + + + + + {BannerContent[0].heading1} + {" "} +
+ {BannerContent[0].heading2} +
+ + + {BannerContent[1].subheading} + + + +
+
+
+ ); +}; + +export default CommunityBanner; diff --git a/src/Components/CustomAlertDialog.jsx b/src/Components/CustomAlertDialog.jsx new file mode 100644 index 0000000..af5a6a1 --- /dev/null +++ b/src/Components/CustomAlertDialog.jsx @@ -0,0 +1,46 @@ +import { AlertDialog, AlertDialogBody, AlertDialogCloseButton, AlertDialogContent, AlertDialogFooter, AlertDialogOverlay, Button, useDisclosure } from "@chakra-ui/react"; +import React, { useRef } from "react"; + +const CustomAlertDialog = ({ isOpen, onOpen, onClose, alertHandler, isLoading, message }) => { + // const cancelRef = useRef(); + + return ( + + + + + + + {message} + + + + + + + + ); +}; + +export default CustomAlertDialog; diff --git a/src/Components/DataTable/DataTable.jsx b/src/Components/DataTable/DataTable.jsx new file mode 100644 index 0000000..feb299e --- /dev/null +++ b/src/Components/DataTable/DataTable.jsx @@ -0,0 +1,51 @@ +import React from "react"; +import { Table, TableContainer, Tbody, Td, Th, Thead, Tr, Skeleton, TableCaption, Tfoot } from "@chakra-ui/react"; +import EmptySearchList from "../EmptySearchList"; + +const DataTable = ({ data, isLoading, tableHeadRow, emptyMessage }) => { + const columnWidth = data && data[0] ? `${(100 / Object.keys(data[0]).length).toFixed(2)}%` : "auto"; + return ( + + {data?.length === 0 ? ( + + ) : ( + + Rubix v1.0.0 + + + {tableHeadRow.map((heading, index) => ( + + ))} + + + + {isLoading + ? Array.from({ length: 12 }).map((_, index) => ( + + {tableHeadRow.map((_, i) => ( + + ))} + + )) + : data?.map((item, index) => ( + + {tableHeadRow.map((heading, i) => ( + + ))} + + ))} + +
+ {isLoading ? : heading} + {/* {heading} */} +
+ +
+ {item[heading]} +
+ )} +
+ ); +}; + +export default DataTable; diff --git a/src/Components/EmptySearchList.jsx b/src/Components/EmptySearchList.jsx new file mode 100644 index 0000000..d707736 --- /dev/null +++ b/src/Components/EmptySearchList.jsx @@ -0,0 +1,21 @@ +import { Box, Image, Text } from "@chakra-ui/react" +import EmptySearchListImage from "../assets/EmptySearchList.svg" + +const EmptySearchList = ({message}) => { + return ( + + Dan Abramov + {message} + Posts of rubix will appear here. + + + ) +} + +export default EmptySearchList \ No newline at end of file diff --git a/src/Components/Functions/TimeCalculator.jsx b/src/Components/Functions/TimeCalculator.jsx new file mode 100644 index 0000000..46bce24 --- /dev/null +++ b/src/Components/Functions/TimeCalculator.jsx @@ -0,0 +1,35 @@ +import { useState, useEffect } from "react"; + +const TimeCalculator = ({ JoiningDate }) => { + const [elapsedTime, setElapsedTime] = useState(0); + + useEffect(() => { + const calculateTimeElapsed = () => { + const startDate = new Date(JoiningDate); + const currentDate = new Date(); + const difference = currentDate - startDate; + setElapsedTime(Math.floor(difference / 1000)); // Convert milliseconds to seconds + }; + + // Calculate time elapsed initially + calculateTimeElapsed(); + + // Update time elapsed every second + const intervalId = setInterval(calculateTimeElapsed, 1000); + + // Clear interval on component unmount + return () => clearInterval(intervalId); + }, []); + + // Convert seconds to years, months, days, hours, minutes, and remaining seconds + const years = Math.floor(elapsedTime / (365 * 24 * 60 * 60)); + const months = Math.floor((elapsedTime % (365 * 24 * 60 * 60)) / (30 * 24 * 60 * 60)); // Approximating a month to 30 days + const days = Math.floor((elapsedTime % (30 * 24 * 60 * 60)) / (24 * 60 * 60)); + const hours = Math.floor((elapsedTime % (24 * 60 * 60)) / (60 * 60)); + const minutes = Math.floor((elapsedTime % (60 * 60)) / 60); + const seconds = elapsedTime % 60; + + return `${years}Y ${months}M ${hours}H ${minutes}M ${seconds}S`; +}; + +export default TimeCalculator; diff --git a/src/Components/Functions/Toaster.jsx b/src/Components/Functions/Toaster.jsx new file mode 100644 index 0000000..1b25e43 --- /dev/null +++ b/src/Components/Functions/Toaster.jsx @@ -0,0 +1,11 @@ +import { useToast } from "@chakra-ui/react"; + +export const toaster = () => { + const toast = useToast(); + + return toast({ + title: "Loged In", + status: "success", + isClosable: true, + }); +} \ No newline at end of file diff --git a/src/Components/Functions/UTCConvertor.jsx b/src/Components/Functions/UTCConvertor.jsx new file mode 100644 index 0000000..beab87f --- /dev/null +++ b/src/Components/Functions/UTCConvertor.jsx @@ -0,0 +1,16 @@ +export const formatDate = (dateString) => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + + // Create a new Date object from the provided string + const date = new Date(dateString); + + // Extract day, month, and year components + const day = date.getDate(); + const month = months[date.getMonth()]; + const year = date.getFullYear(); + + // Format the date in "DD-Mon-YYYY" format + const formattedDate = `${day}-${month}-${year}`; + + return formattedDate; +} \ No newline at end of file diff --git a/src/Components/Header.jsx b/src/Components/Header.jsx new file mode 100644 index 0000000..5fe7522 --- /dev/null +++ b/src/Components/Header.jsx @@ -0,0 +1,45 @@ +import { AddIcon } from "@chakra-ui/icons"; +import { Box, Button, Text } from "@chakra-ui/react"; +import React from "react"; +import { Link } from "react-router-dom"; + +const Header = ({ link, btnTitle, title }) => { + return ( + + {/* Community */} + + + {title} + + + {btnTitle && link && ( + + + + )} + + ); +}; + +export default Header; diff --git a/src/Components/ImageDropBox.jsx b/src/Components/ImageDropBox.jsx new file mode 100644 index 0000000..d68ac5a --- /dev/null +++ b/src/Components/ImageDropBox.jsx @@ -0,0 +1,77 @@ +import { + AspectRatio, + Box, + Container, + Heading, + Input, + Stack, + Text, +} from "@chakra-ui/react"; +import React from "react"; + +import { motion, useAnimation } from "framer-motion"; + +const ImageDropBox = () => { + return ( + + + + + + + Drop images here + + or click to upload + + + + + + + ); +}; + +export default ImageDropBox; diff --git a/src/Components/Inputs/Input01.jsx b/src/Components/Inputs/Input01.jsx new file mode 100644 index 0000000..61d7766 --- /dev/null +++ b/src/Components/Inputs/Input01.jsx @@ -0,0 +1,44 @@ +import React, { useState } from "react"; + +const input01Style = { + width: "100%", + border: "1px solid #ADB3BD", + outline: "none", + height: 50, + fontSize: "16px", +}; + +const focusedInputStyle = { + border: "1px solid #38187C", // Change border color for focused state +}; + +const Input01 = ({ type, placeholder, onChange, name, register }) => { + const [isFocused, setIsFocused] = useState(false); + + const handleFocus = () => { + setIsFocused(true); + }; + + const handleBlur = () => { + setIsFocused(false); + }; + + return ( + + ); +}; + +export default Input01; diff --git a/src/Components/Loaders/FullscreenLoaders.jsx b/src/Components/Loaders/FullscreenLoaders.jsx new file mode 100644 index 0000000..0705dba --- /dev/null +++ b/src/Components/Loaders/FullscreenLoaders.jsx @@ -0,0 +1,18 @@ +import { Box, Spinner } from "@chakra-ui/react"; +import React from "react"; + +const FullscreenLoaders = () => { + return ( + + + + ); +}; + +export default FullscreenLoaders; diff --git a/src/Components/Loaders/Loader01.jsx b/src/Components/Loaders/Loader01.jsx new file mode 100644 index 0000000..89f04bc --- /dev/null +++ b/src/Components/Loaders/Loader01.jsx @@ -0,0 +1,14 @@ +import React from "react"; + +const Loader01 = () => { + return ( +
+
+
+
+
+
+ ); +}; + +export default Loader01; diff --git a/src/Components/WebButton.jsx b/src/Components/WebButton.jsx new file mode 100644 index 0000000..6d425fc --- /dev/null +++ b/src/Components/WebButton.jsx @@ -0,0 +1,68 @@ + +import { Button } from '@chakra-ui/react' +import React from 'react' + +const WebButton = ({title}) => { + return ( + ) +} + +export default WebButton \ No newline at end of file diff --git a/src/Constants/Paginations.js b/src/Constants/Paginations.js new file mode 100644 index 0000000..c31b729 --- /dev/null +++ b/src/Constants/Paginations.js @@ -0,0 +1 @@ +export const TABLE_PAGINATION = { page: 1, size: 15 } \ No newline at end of file diff --git a/src/Contexts/GlobalStateContext.jsx b/src/Contexts/GlobalStateContext.jsx new file mode 100644 index 0000000..48a2ae0 --- /dev/null +++ b/src/Contexts/GlobalStateContext.jsx @@ -0,0 +1,6 @@ +// GlobalStateContext.js +import { createContext } from 'react'; + +const GlobalStateContext = createContext(); + +export default GlobalStateContext; diff --git a/src/Contexts/GlobalStateProvider.jsx b/src/Contexts/GlobalStateProvider.jsx new file mode 100644 index 0000000..40c7366 --- /dev/null +++ b/src/Contexts/GlobalStateProvider.jsx @@ -0,0 +1,30 @@ +// GlobalStateContext.js +import React, { useState } from "react"; +import GlobalStateContext from "./GlobalStateContext"; + +function generateUID() { + // Generates a random 8-character alphanumeric string + return Math.random().toString(36).substring(2, 10); +} + +const GlobalStateProvider = ({ children }) => { + const [isAuthenticate, setIsAuthenticate] = useState(false); + const [memberIfo, setMemberInfo] = useState(); + const [communityMembers, setCommityMembers] = useState(); + + return ( + + {children} + + ); +}; +export default GlobalStateProvider; diff --git a/src/Images/dark-bg.png b/src/Images/dark-bg.png new file mode 100644 index 0000000..690e6b5 Binary files /dev/null and b/src/Images/dark-bg.png differ diff --git a/src/Images/light-bg.png b/src/Images/light-bg.png new file mode 100644 index 0000000..3b23568 Binary files /dev/null and b/src/Images/light-bg.png differ diff --git a/src/Images/logo.jpg b/src/Images/logo.jpg new file mode 100644 index 0000000..38dac87 Binary files /dev/null and b/src/Images/logo.jpg differ diff --git a/src/Images/logoDark.png b/src/Images/logoDark.png new file mode 100644 index 0000000..cf272a3 Binary files /dev/null and b/src/Images/logoDark.png differ diff --git a/src/Images/logoDarkMini.png b/src/Images/logoDarkMini.png new file mode 100644 index 0000000..28e09e7 Binary files /dev/null and b/src/Images/logoDarkMini.png differ diff --git a/src/Images/logoLight.png b/src/Images/logoLight.png new file mode 100644 index 0000000..9f0ea48 Binary files /dev/null and b/src/Images/logoLight.png differ diff --git a/src/Images/miniLogo.jpg b/src/Images/miniLogo.jpg new file mode 100644 index 0000000..4c2d702 Binary files /dev/null and b/src/Images/miniLogo.jpg differ diff --git a/src/Images/reactLogo.png b/src/Images/reactLogo.png new file mode 100644 index 0000000..80d81cf Binary files /dev/null and b/src/Images/reactLogo.png differ diff --git a/src/Layout/DefaultLayout.jsx b/src/Layout/DefaultLayout.jsx new file mode 100644 index 0000000..514cbbb --- /dev/null +++ b/src/Layout/DefaultLayout.jsx @@ -0,0 +1,312 @@ +import React, { useContext, useState } from "react"; +import logo from "../assets/logo.png"; +import logoMini from "../assets/logo-min.png"; +import { useDispatch } from "react-redux"; +import { loginUser } from "../Redux/Slice/auth"; +import Button02 from "../Components/Buttons/Button02"; +import { TbArrowBadgeLeftFilled } from "react-icons/tb"; +import { TbArrowBadgeRightFilled } from "react-icons/tb"; +import { ArrowBackIcon, ArrowLeftIcon, ArrowRightIcon } from "@chakra-ui/icons"; +import { + Link, + NavLink, + Route, + Routes, + useLocation, + useNavigate, +} from "react-router-dom"; +import { RouteLink } from "../Routes/Routes"; +import NotFound from "../Pages/NotFound"; +import { nav } from "../Routes/Nav"; +import { + Avatar, + Box, + Button, + PopoverArrow, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverFooter, + PopoverHeader, + PopoverTrigger, + Portal, + Text, + WrapItem, + Popover, + Tag, +} from "@chakra-ui/react"; +import GlobalStateContext from "../Contexts/GlobalStateContext"; +import Cookies from "js-cookie"; // Import the Cookies library + +const DashboardLayout = () => { + const navigate = useNavigate(); + const dispach = useDispatch(); + const location = useLocation(); + const path = location.pathname; + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [openDrawerClick, setOpenDrawerClick] = useState(true); + const { setIsAuthenticate } = useContext(GlobalStateContext); + + console.log(isDrawerOpen, openDrawerClick); + + const openDrawerOnClick = () => { + setOpenDrawerClick(!openDrawerClick); + }; + + const logOutHandler = () => { + // dispach(loginUser(false)); + setIsAuthenticate(false); + Cookies.remove("isAuthenticated"); + navigate("/login"); + }; + + // Function to get the title based on the route + const getTitle = () => { + switch (path) { + case "/": + return "👋🏻 Hi, developer admin"; + case "/blogs-articles": + return "Blogs and Articles"; + case "/videos": + return "Videos"; + case "/news": + return "News"; + case "/events": + return "Events"; + case "/whitepaper": + return "Whitepaper"; + case "/community/": + return "Community"; + case "/community": + return "Community"; + case "/community/view/": + return "Community"; + case "/community/add-comunity": + return + + + + Community + ; + default: + if (path.startsWith("/community/view/")) { + return ( + + + + + Community + + ); + } else if (path.startsWith("/community/edit/")) { + return ( + + + + + Community + + ); + } + return "Rubix"; + } + }; + + return ( + + + +
+ + + {/*
+ {getTitle()} +
*/} + + +
+
+ ); +}; + +export default DashboardLayout; + +const AppContent = () => { + return ( + + {RouteLink.map(({ path, Component }, index) => ( + } /> + ))} + } /> + + ); +}; diff --git a/src/Layout/animations.jsx b/src/Layout/animations.jsx new file mode 100644 index 0000000..7ac044d --- /dev/null +++ b/src/Layout/animations.jsx @@ -0,0 +1,23 @@ +import { motion } from "framer-motion"; + +export const OPACITY_ON_LOAD = { + as: motion.div, + initial: { opacity: 0 }, + animate: { opacity: 1 } +} + +export const SLIDE_IN_BOTTOM = { + as: motion.div, + initial: { opacity: 0, y: 50 }, + animate: { opacity: 1, y: 0 }, + transition: { duration: 0.5, ease: "easeInOut" } + }; + + + export const FADE_IN_SCALE_UP = { + as: motion.div, + initial: { opacity: 0, scale: 0.9 }, + animate: { opacity: 1, scale: 1 }, + transition: { duration: 0.5, ease: "easeInOut" } + }; + \ No newline at end of file diff --git a/src/Pages/Banners/Banner.jsx b/src/Pages/Banners/Banner.jsx new file mode 100644 index 0000000..5e43227 --- /dev/null +++ b/src/Pages/Banners/Banner.jsx @@ -0,0 +1,69 @@ +import { Box, Button, Divider, HStack, Skeleton, Text } from "@chakra-ui/react"; +import React from "react"; +import { OPACITY_ON_LOAD } from "../../Layout/animations"; +import { Link } from "react-router-dom"; +import { AddIcon, ArrowForwardIcon } from "@chakra-ui/icons"; +import { useGetBuildBannerQuery, useGetCommunityBannerQuery, useGetLearnBannerQuery, useGetNewsBannerQuery } from "../../Services/api.service"; +import CommunityBannerCard from "../Community/CommunityBannerCard"; +import Header from "../../Components/Header"; +import BannerStack from "../../Components/BannerStack"; + +const Banner = () => { + const communityBanner = useGetCommunityBannerQuery(); + const learnBanner = useGetLearnBannerQuery(); + const buildBanner = useGetBuildBannerQuery() + const newsBanner = useGetNewsBannerQuery() + + return ( + +
+ item?.status === true)} + viewBannerLink={'/banner/banner-community/view'} + /> + + + + + + + + ); +}; + +export default Banner; + + + + + + + + diff --git a/src/Pages/Banners/BannerCommunity/AddBanner.jsx b/src/Pages/Banners/BannerCommunity/AddBanner.jsx new file mode 100644 index 0000000..e21f13c --- /dev/null +++ b/src/Pages/Banners/BannerCommunity/AddBanner.jsx @@ -0,0 +1,359 @@ +import { + Box, + FormControl, + FormHelperText, + FormLabel, + Input, + Text, + Stack, + Textarea, + Heading, + Button, + useToast, + Divider, + Image, + } from "@chakra-ui/react"; + import React, { useState } from "react"; + import fallbackImage from "../../../assets/ultp-fallback-img.webp"; + import { TiWarning } from "react-icons/ti"; + + import { motion } from "framer-motion"; + import { OPACITY_ON_LOAD } from "../../../Layout/animations"; + + import { yupResolver } from "@hookform/resolvers/yup"; + import { useForm } from "react-hook-form"; + import { addCommunityBannerSchema, addCommunitySchema } from "../../../Validations/Validations"; + import { + useCreateCommunityBannerMutation, + useCreateCommunityMutation, + useGetCommunityQuery, + } from "../../../Services/api.service"; + import { useNavigate } from "react-router-dom"; + import Loader01 from "../../../Components/Loaders/Loader01"; + + const AddBanner = () => { + const toast = useToast(); + const navigate = useNavigate(); + const getCommunityQuery = useGetCommunityQuery(); + const [createCommunityBannerData] = useCreateCommunityBannerMutation(); // Invoke the hook to get the mutation function + const [isLoading, setIsLoading] = useState(false); + const [selectedImage, setSelectedImage] = useState(fallbackImage); + + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + resolver: yupResolver(addCommunityBannerSchema), + }); + + const onSubmit = async (data) => { + console.log(data); + try { + setIsLoading(true); + const formData = new FormData(); + formData.append("heading", data.heading); + formData.append("sub_heading", data.sub_heading); + formData.append("CTO_button_link", data.CTO_button_link); + formData.append("CTO_button_title", data.CTO_button_title); + if (data.banner_image[0]) { + formData.append("banner_image", data.banner_image[0]); + } + // Trigger the mutation + createCommunityBannerData(formData) + .then((response) => { + // Handle the response here + console.log("Mutation response:", response?.data?.statusCode); + console.log("Mutation response:", response?.data?.message); + + if (response?.data?.statusCode === 200) { + setIsLoading(false); + toast({ + title: response?.data?.message, + status: "success", + isClosable: true, + }); + reset(); + navigate("/banner/banner-community"); + }else if(response?.data?.statusCode === 500){ + setIsLoading(false); + toast({ + title: response?.data?.message, + status: "success", + isClosable: true, + }); + + } + }) + .catch((error) => { + // Handle errors + console.error("Error creating community:", error); + setIsLoading(false); + // Handle error notification if needed + }); + } catch (error) { + // Handle errors + console.error("Error creating community:", error); + setIsLoading(false); + } + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setSelectedImage(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + return ( + + + + Banner Info + + + Select the platform for which you need to create this campaign. + + + + + + Banner Image + + + Below is the profile that will be displayed on the community page. + + + + Selected Image + + + + +
+ + + Heading + + + {errors.name && ( + + {" "} + {errors.heading.message} + + )} + + + + + Sub Heading + + + {errors.sub_heading && ( + + {" "} + {errors.sub_heading.message} + + )} + + + + + Button title + + + + Maximum characters must be 100 characters. + + + {errors.CTO_button_title && ( + + {" "} + {errors.CTO_button_title.message} + + )} + + + + + Button link + + + + Please share proper linked in link here. + + {errors.CTO_button_link && ( + + {errors.CTO_button_link.message} + + )} + + + + + Banner image + + {/* */} + + + + + + + + Drop images here + + + or click to upload + + + + + + + + + {errors.banner_image && ( + + {" "} + {errors.banner_image.message} + + )} + + Maximum limit of image is 5mb. + + + + + + +
+
+ ); + }; + + export default AddBanner; + \ No newline at end of file diff --git a/src/Pages/Banners/BannerCommunity/BannerCommunity.jsx b/src/Pages/Banners/BannerCommunity/BannerCommunity.jsx new file mode 100644 index 0000000..3e967e6 --- /dev/null +++ b/src/Pages/Banners/BannerCommunity/BannerCommunity.jsx @@ -0,0 +1,318 @@ +import React, { useRef, useState } from "react"; +import { + Avatar, + Box, + Link, + Tag, + Text, + WrapItem, + Tooltip, + Divider, + Stack, + HStack, + Input, + Button, + Select, + Image, + Menu, + MenuButton, + MenuList, + MenuItem, + Switch, + Portal, + useDisclosure, + AlertDialog, + AlertDialogOverlay, + AlertDialogContent, + AlertDialogHeader, + AlertDialogCloseButton, + AlertDialogBody, + AlertDialogFooter, + useToast, + Skeleton, + VStack, +} from "@chakra-ui/react"; +import { GrAdd } from "react-icons/gr"; +import { AddIcon, ChevronDownIcon, HamburgerIcon } from "@chakra-ui/icons"; +import DataTable from "../../../Components/DataTable/DataTable"; +import CommunityBanner from "../../../Components/CommunityBanner"; +import { OPACITY_ON_LOAD } from "../../../Layout/animations"; +import { v4 as uuidv4 } from "uuid"; +import { Link as RouterLink } from "react-router-dom"; +import { + useDeleteCommunityBannerMutation, + useDeleteCommunityMutation, + useGetCommunityBannerQuery, + useGetCommunityByIdQuery, + useGetCommunityQuery, + useUpdateCommunityBannerStatusMutation, + useUpdateCommunityStatusMutation, +} from "../../../Services/api.service"; +import { HiDotsVertical } from "react-icons/hi"; +import TimeCalculator from "../../../Components/Functions/TimeCalculator"; +import { formatDate } from "../../../Components/Functions/UTCConvertor"; +import CustomAlertDialog from "../../../Components/CustomAlertDialog"; +import WebButton from "../../../Components/WebButton"; +import CommunityCardDisplay from "../../Community/CommunityCardDisplay"; +import CommunityBannerCard from "../../Community/CommunityBannerCard"; +import Header from "../../../Components/Header"; + +const BannerCommunity = () => { + // ====================================================[Hooks]=================================================================== + const toast = useToast(); + const [deleteAlert, setDeleteAlert] = useState(false); + const [actionId, setActionId] = useState(null); + const [deleteIsLoading, setDeleteIsLoading] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + const community = useGetCommunityQuery(); + const communityBanner = useGetCommunityBannerQuery(); + const [deleteCommunityBanner] = useDeleteCommunityBannerMutation(); + const [updateCommunityBannerStatus] = + useUpdateCommunityBannerStatusMutation(); + + // ====================================================[Functions]=================================================================== + const handleDelete = async (bannerId) => { + try { + // Trigger the mutation + setDeleteIsLoading(true); + await deleteCommunityBanner(bannerId) + .then((response) => { + // Handle the response here + console.log("Mutation response:", response?.data?.statusCode); + console.log("Mutation response:", response?.data?.message); + + if (response?.data?.statusCode === 200) { + setDeleteIsLoading(false); + setDeleteAlert(false); + toast({ + title: response?.data?.message, + status: "success", + duration: 1000, + isClosable: true, + }); + } + }) + .catch((error) => { + console.error("Error creating community:", error); + setDeleteIsLoading(false); + setDeleteAlert(false); + }); + } catch (error) { + // Handle errors + console.error("Error deleting community:", error); + } + }; + + const handleUpdateStatus = async (id) => { + console.log(id); + + try { + // Trigger the mutation + await updateCommunityBannerStatus({ id }) + .then((response) => { + console.log(response?.data); + if (response?.data?.statusCode === 200) { + console.log("toasted"); + toast({ + title: response?.data?.message, + status: "success", + isClosable: true, + }); + } + }) + .catch((error) => { + console.log(error); + }); + } catch (error) { + // Handle errors + console.error("Error updating community status:", error); + } + }; + + // ====================================================[Table Filter]================================================================ + const filteredData = communityBanner?.data?.data?.rows?.filter((item) => { + // Filter by name (case insensitive) + const name = item.Heading; + const searchLower = searchTerm.toLowerCase(); + const nameMatches = name.toLowerCase().includes(searchLower); + + return nameMatches; + }); + + // ====================================================[Table Setup]================================================================ + const tableHeadRow = [ + "Banner image", + "Heading", + "Sub heading", + "Button title", + "Active", + "Created At", + ]; + + const extractedArray = filteredData?.map((item, index) => { + return { + "Banner image": ( + Dan Abramov + ), + Heading: ( + + + + {item?.Heading} + + + + ), + "Sub heading": ( + + + + {item?.sub_heading} + + + + ), + "Button title": item?.CTO_button_title, + Active: ( + handleUpdateStatus(item.id)} + isChecked={item.status} + /> + ), + "Created At": ( + + + {formatDate(item?.createdAt)} + + + + + + + + + Edit + + + View + + { + setActionId(item.id); + setDeleteAlert(true); + }} + className="web-text-medium" + > + Delete + + + + + + ), + }; + }); + + return ( + + + + +
+ {/* ====================================================[ Top bar ]================================================================ */} + + {/* */} + + + {/* + + Community Banners + + */} + + setSearchTerm(e.target.value)} + /> + + + + + + + {/* ====================================================[ Table ]================================================================ */} + + {/* ====================================================[ Alert ]================================================================ */} + setDeleteAlert(false)} + isOpen={deleteAlert} + alertHandler={() => handleDelete(actionId)} + message={"Are you sure you want to delete member?"} + isLoading={deleteIsLoading} + /> + + ); +}; + +export default BannerCommunity; diff --git a/src/Pages/Banners/BannerCommunity/BannerCommunityEdit.jsx b/src/Pages/Banners/BannerCommunity/BannerCommunityEdit.jsx new file mode 100644 index 0000000..a0506b9 --- /dev/null +++ b/src/Pages/Banners/BannerCommunity/BannerCommunityEdit.jsx @@ -0,0 +1,415 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { + useGetCommunityBannerByIdQuery, + useGetCommunityByIdQuery, + useUpdateCommunityBannerMutation, + useUpdateCommunityMutation, +} from "../../../Services/api.service"; +import { editCommunityBannerSchema, schemaEdit } from "../../../Validations/Validations"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { + Box, + Divider, + FormControl, + FormHelperText, + FormLabel, + Heading, + Image, + Input, + Stack, + Textarea, + Button, + Skeleton, + useToast, + Switch, + Tag, +} from "@chakra-ui/react"; +import { TiWarning } from "react-icons/ti"; +import { OPACITY_ON_LOAD } from "../../../Layout/animations"; +import { motion } from "framer-motion"; +import Loader01 from "../../../Components/Loaders/Loader01"; +import FullscreenLoaders from "../../../Components/Loaders/FullscreenLoaders"; +import fallbackImage from "../../../assets/ultp-fallback-img.webp"; +import Header from "../../../Components/Header"; + +const BannerComunityEditPage = () => { + const { id } = useParams(); + const toast = useToast(); + const navigate = useNavigate(); + const { data, error, isLoading } = useGetCommunityBannerByIdQuery(id); + const [isLoadingEdit, setIsLoadingEdit] = useState(false); + const [selectedImage, setSelectedImage] = useState(); + const [updateCommunityBanner] = useUpdateCommunityBannerMutation(); + console.log(data); + + const { + register, + handleSubmit, + reset, + formState: { errors }, + setValue, + } = useForm({ + resolver: yupResolver(editCommunityBannerSchema), + defaultValues: { + Heading: "", + sub_heading: "", + CTO_button_title: "", + CTO_button_link: "", + }, + }); + + useEffect(() => { + if (data?.data) { + setSelectedImage( + `https://rubix.betadelivery.com/${data?.data?.banner_image}` + ); + setValue("Heading", data?.data?.Heading); + setValue("sub_heading", data?.data?.sub_heading); + setValue("CTO_button_title", data?.data?.CTO_button_title); + setValue("CTO_button_link", data?.data?.CTO_button_link); + setValue("banner_image", data?.data?.banner_image); + } + }, [data, setValue]); + + const onSubmit = async (formData) => { + setIsLoadingEdit(true); + const form = new FormData(); + form.append("Heading", formData.Heading); + form.append("sub_heading", formData.sub_heading); + form.append("CTO_button_title", formData.CTO_button_title); + form.append("CTO_button_link", formData.CTO_button_link); + if (formData.banner_image[0]) { + form.append("banner_image", formData.banner_image[0]); + } + if (formData?.banner_image === data?.data?.banner_image) { + console.log("hit"); + form.delete("banner_image"); + } + const mutationResult = await updateCommunityBanner({ id: id, data: form }) + .then((response) => { + // Handle the response here + console.log("Mutation response:", response?.data?.statusCode); + console.log("Mutation response:", response?.data?.message); + + if (response?.data?.statusCode === 200) { + setIsLoadingEdit(false); + toast({ + title: response?.data?.message, + status: "success", + duration: 1000, + isClosable: true, + }); + navigate("/banner/banner-community"); + // setDeleteAlert(false); + } + }) + .catch((error) => { + console.error("Error creating community:", error); + setIsLoadingEdit(false); + // setDeleteIsLoading(false); + // setDeleteAlert(false); + }); + + // Log the FormData entries + // for (const [key, value] of form.entries()) { + // console.log(`${key}: ${value}`); + // } + + reset(); + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setSelectedImage(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + return isLoading ? ( + + ) : ( + + +
+ + + + Display Info + + + Select the platform for which you need to create this campaign. + + + + + + Display banner + + + Below is the profile that will be displayed on the community page. + + + + + Selected Image + + + + +
+ {/* handleUpdateStatus(item.id)} + isChecked={data?.data?.status} + /> */} + + Status + + {data?.data?.status ? + Active + : + Inactive + } + + + + Heading + + + {errors.name && ( + + {" "} + {errors.Heading.message} + + )} + + + + + Sub heading + + + {errors.sub_heading && ( + + {" "} + {errors.sub_heading.message} + + )} + + + + + CTO Button title + + + + Maximum characters must be 100 characters. + + + {errors.CTO_button_title && ( + + {" "} + {errors.CTO_button_title.message} + + )} + + + + + CTO Button link + + + {errors.CTO_button_link && ( + + {errors.CTO_button_link.message} + + )} + + Please share proper linked in link here. + + + + + + Display banner + + {/* */} + + + + + + + + Drop images here + + + or click to upload + + + + + + + + + {errors.banner_image && ( + + {" "} + {errors.banner_image.message} + + )} + + Maximum limit of image is 5mb. + + + + + + + +
+ + ); +}; + +export default BannerComunityEditPage; diff --git a/src/Pages/Banners/BannerCommunity/BannerCommunityView.jsx b/src/Pages/Banners/BannerCommunity/BannerCommunityView.jsx new file mode 100644 index 0000000..084077e --- /dev/null +++ b/src/Pages/Banners/BannerCommunity/BannerCommunityView.jsx @@ -0,0 +1,155 @@ +import React, { useEffect, useState } from "react"; +import { Link, useParams } from "react-router-dom"; +import { + useGetCommunityBannerByIdQuery, + useGetCommunityByIdQuery, + useGetCommunityQuery, +} from "../../../Services/api.service"; +import { + Box, + Button, + Divider, + Image, + StackDivider, + Tag, + VStack, +} from "@chakra-ui/react"; +import { OPACITY_ON_LOAD } from "../../../Layout/animations"; +import FullscreenLoaders from "../../../Components/Loaders/FullscreenLoaders"; +import { formatDate } from "../../../Components/Functions/UTCConvertor"; +import Header from "../../../Components/Header"; + +const BannerComunityViewPage = () => { + const { id } = useParams(); + const { data, error, isLoading } = useGetCommunityBannerByIdQuery(id); + const banner = data?.data; + useEffect(() => { + }, [data]) + + return isLoading ? ( + + ) : ( + + + +
+ + + + Banners Info + + + Select the platform for which you need to create this campaign. + + + + + + Display banner + + + Below is the profile that will be displayed on the community page. + + + + + Selected Image + {/* */} + + + + + {data?.data?.status ? + Active + : + Inactive + } + + Heading + + {banner?.Heading} + + + + + + Sub heading + + + {banner?.sub_heading} + + + + + + Button title + + + {banner?.CTO_button_title} + + + + + Button link + + {banner?.CTO_button_link} + + + + + + Created At + + + {formatDate(banner?.createdAt)} + + + + + + Updated At + + + {formatDate(banner?.updatedAt)} + + + + + + + + ); +}; + +export default BannerComunityViewPage; diff --git a/src/Pages/Banners/BannerLearn/AddLearnBanner.jsx b/src/Pages/Banners/BannerLearn/AddLearnBanner.jsx new file mode 100644 index 0000000..dd35882 --- /dev/null +++ b/src/Pages/Banners/BannerLearn/AddLearnBanner.jsx @@ -0,0 +1,358 @@ +import { + Box, + FormControl, + FormHelperText, + FormLabel, + Input, + Text, + Stack, + Textarea, + Heading, + Button, + useToast, + Divider, + Image, +} from "@chakra-ui/react"; +import React, { useState } from "react"; +import fallbackImage from "../../../assets/ultp-fallback-img.webp"; +import { TiWarning } from "react-icons/ti"; + +import { motion } from "framer-motion"; +import { OPACITY_ON_LOAD } from "../../../Layout/animations"; + +import { yupResolver } from "@hookform/resolvers/yup"; +import { useForm } from "react-hook-form"; +import { addCommunityBannerSchema, addCommunitySchema } from "../../../Validations/Validations"; +import { + useCreateCommunityBannerMutation, + useCreateCommunityMutation, + useGetCommunityQuery, +} from "../../../Services/api.service"; +import { useNavigate } from "react-router-dom"; +import Loader01 from "../../../Components/Loaders/Loader01"; + +const AddLearnBanner = () => { + const toast = useToast(); + const navigate = useNavigate(); + const getCommunityQuery = useGetCommunityQuery(); + const [createCommunityBannerData] = useCreateCommunityBannerMutation(); // Invoke the hook to get the mutation function + const [isLoading, setIsLoading] = useState(false); + const [selectedImage, setSelectedImage] = useState(fallbackImage); + + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + resolver: yupResolver(addCommunityBannerSchema), + }); + + const onSubmit = async (data) => { + console.log(data); + try { + setIsLoading(true); + const formData = new FormData(); + formData.append("heading", data.heading); + formData.append("sub_heading", data.sub_heading); + formData.append("CTO_button_link", data.CTO_button_link); + formData.append("CTO_button_title", data.CTO_button_title); + if (data.banner_image[0]) { + formData.append("banner_image", data.banner_image[0]); + } + // Trigger the mutation + createCommunityBannerData(formData) + .then((response) => { + // Handle the response here + console.log("Mutation response:", response?.data?.statusCode); + console.log("Mutation response:", response?.data?.message); + + if (response?.data?.statusCode === 200) { + setIsLoading(false); + toast({ + title: response?.data?.message, + status: "success", + isClosable: true, + }); + reset(); + navigate("/banner/banner-community"); + }else if(response?.data?.statusCode === 500){ + setIsLoading(false); + toast({ + title: response?.data?.message, + status: "success", + isClosable: true, + }); + + } + }) + .catch((error) => { + // Handle errors + console.error("Error creating community:", error); + setIsLoading(false); + // Handle error notification if needed + }); + } catch (error) { + // Handle errors + console.error("Error creating community:", error); + setIsLoading(false); + } + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setSelectedImage(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + return ( + + + + Banner Info + + + Select the platform for which you need to create this campaign. + + + + + + Banner Image + + + Below is the profile that will be displayed on the community page. + + + + Selected Image + + + + +
+ + + Heading + + + {errors.name && ( + + {" "} + {errors.heading.message} + + )} + + + + + Sub Heading + + + {errors.sub_heading && ( + + {" "} + {errors.sub_heading.message} + + )} + + + + + Button title + + + + Maximum characters must be 100 characters. + + + {errors.CTO_button_title && ( + + {" "} + {errors.CTO_button_title.message} + + )} + + + + + Button link + + + + Please share proper linked in link here. + + {errors.CTO_button_link && ( + + {errors.CTO_button_link.message} + + )} + + + + + Banner image + + {/* */} + + + + + + + + Drop images here + + + or click to upload + + + + + + + + + {errors.banner_image && ( + + {" "} + {errors.banner_image.message} + + )} + + Maximum limit of image is 5mb. + + + + + + +
+
+ ); +}; + +export default AddLearnBanner; diff --git a/src/Pages/Banners/BannerLearn/BannerLearn.jsx b/src/Pages/Banners/BannerLearn/BannerLearn.jsx new file mode 100644 index 0000000..ec60882 --- /dev/null +++ b/src/Pages/Banners/BannerLearn/BannerLearn.jsx @@ -0,0 +1,328 @@ +import React, { useRef, useState } from "react"; +import { + Avatar, + Box, + Link, + Tag, + Text, + WrapItem, + Tooltip, + Divider, + Stack, + HStack, + Input, + Button, + Select, + Image, + Menu, + MenuButton, + MenuList, + MenuItem, + Switch, + Portal, + useDisclosure, + AlertDialog, + AlertDialogOverlay, + AlertDialogContent, + AlertDialogHeader, + AlertDialogCloseButton, + AlertDialogBody, + AlertDialogFooter, + useToast, + Skeleton, + VStack, +} from "@chakra-ui/react"; +import { GrAdd } from "react-icons/gr"; +import { AddIcon, ChevronDownIcon, HamburgerIcon } from "@chakra-ui/icons"; +import DataTable from "../../../Components/DataTable/DataTable"; +import CommunityBanner from "../../../Components/CommunityBanner"; +import { OPACITY_ON_LOAD } from "../../../Layout/animations"; +import { v4 as uuidv4 } from "uuid"; +import { Link as RouterLink } from "react-router-dom"; +import { + useDeleteCommunityBannerMutation, + useDeleteCommunityMutation, + useDeleteLearnBannerMutation, + useGetCommunityBannerQuery, + useGetCommunityByIdQuery, + useGetCommunityQuery, + useGetLearnBannerByIdQuery, + useGetLearnBannerQuery, + useUpdateCommunityBannerStatusMutation, + useUpdateCommunityStatusMutation, + useUpdateLearnBannerStatusMutation, +} from "../../../Services/api.service"; +import { HiDotsVertical } from "react-icons/hi"; +import TimeCalculator from "../../../Components/Functions/TimeCalculator"; +import { formatDate } from "../../../Components/Functions/UTCConvertor"; +import CustomAlertDialog from "../../../Components/CustomAlertDialog"; +import WebButton from "../../../Components/WebButton"; +import CommunityCardDisplay from "../../Community/CommunityCardDisplay"; +import CommunityBannerCard from "../../Community/CommunityBannerCard"; +import Header from "../../../Components/Header"; + +const BannerLearn = () => { + // ====================================================[Hooks]=================================================================== + const toast = useToast(); + const [deleteAlert, setDeleteAlert] = useState(false); + const [actionId, setActionId] = useState(null); + const [deleteIsLoading, setDeleteIsLoading] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + const Learn = useGetLearnBannerQuery(); + const learnBanner = useGetLearnBannerQuery(); + const [deleteLearnBanner] = useDeleteLearnBannerMutation(); + const [updateLearnBannerStatus] = useUpdateLearnBannerStatusMutation(); + + // ====================================================[Functions]=================================================================== + const handleDelete = async (bannerId) => { + try { + // Trigger the mutation + setDeleteIsLoading(true); + await deleteLearnBanner(bannerId) + .then((response) => { + // Handle the response here + console.log("Mutation response:", response?.data?.statusCode); + console.log("Mutation response:", response?.data?.message); + + if (response?.data?.statusCode === 200) { + setDeleteIsLoading(false); + setDeleteAlert(false); + toast({ + title: response?.data?.message, + status: "success", + duration: 1000, + isClosable: true, + }); + } + }) + .catch((error) => { + console.error("Error creating Learn:", error); + setDeleteIsLoading(false); + setDeleteAlert(false); + }); + } catch (error) { + // Handle errors + console.error("Error deleting Learn:", error); + } + }; + + const handleUpdateStatus = async (id) => { + console.log(id); + + try { + // Trigger the mutation + await updateLearnBannerStatus({ id }) + .then((response) => { + console.log(response?.data); + if (response?.data?.statusCode === 201) { + console.log("toasted"); + toast({ + title: response?.data?.message, + status: "success", + isClosable: true, + }); + } + }) + .catch((error) => { + console.log(error); + }); + } catch (error) { + // Handle errors + console.error("Error updating community status:", error); + } + }; + + // ====================================================[Table Filter]================================================================ + const filteredData = learnBanner?.data?.data?.rows?.filter((item) => { + // Filter by name (case insensitive) + const name = item.Heading; + const searchLower = searchTerm.toLowerCase(); + const nameMatches = name.toLowerCase().includes(searchLower); + + + + // Filter by status + const status = item.status; + + const statusMatches = + statusFilter === "all" || + (statusFilter === "active" && status === true) || + (statusFilter === "inactive" && status === false); + + return nameMatches && statusMatches; + }); + + // ====================================================[Table Setup]================================================================ + const tableHeadRow = [ + "Banner image", + "Heading", + "Sub heading", + "Button title", + "Active", + "Created At", + ]; + + const extractedArray = filteredData?.map((item, index) => { + return { + "Banner image": ( + Dan Abramov + ), + Heading: ( + + + + {item?.Heading} + + + + ), + "Sub heading": ( + + + + {item?.sub_heading} + + + + ), + "Button title": item?.CTO_button_title, + Active: ( + handleUpdateStatus(item.id)} + isChecked={item.status} + /> + ), + "Created At": ( + + + {formatDate(item?.createdAt)} + + + + + + + + + Edit + + + View + + { + setActionId(item.id); + setDeleteAlert(true); + }} + className="web-text-medium" + > + Delete + + + + + + ), + }; + }); + + return ( + +
+ {/* ====================================================[ Top bar ]================================================================ */} + + {/* */} + + + {/* + + Community Banners + + */} + + setSearchTerm(e.target.value)} + /> + + + + + + + {/* ====================================================[ Table ]================================================================ */} + + {/* ====================================================[ Alert ]================================================================ */} + setDeleteAlert(false)} + isOpen={deleteAlert} + alertHandler={() => handleDelete(actionId)} + message={"Are you sure you want to delete member?"} + isLoading={deleteIsLoading} + /> + + ); +}; + +export default BannerLearn; diff --git a/src/Pages/BlogsAndArticles/AddBlogsAndArticles.jsx b/src/Pages/BlogsAndArticles/AddBlogsAndArticles.jsx new file mode 100644 index 0000000..4ec8ad0 --- /dev/null +++ b/src/Pages/BlogsAndArticles/AddBlogsAndArticles.jsx @@ -0,0 +1,746 @@ +import { + Box, + FormControl, + FormHelperText, + FormLabel, + Input, + Text, + Stack, + Textarea, + Heading, + Button, + useToast, + Divider, + Image, +} from "@chakra-ui/react"; +import React, { useState } from "react"; +import fallbackImage from "../../assets/fallBackImage.png"; +import fallbackImageLarge from "../../assets/ultp-fallback-img.webp"; +import { TiWarning } from "react-icons/ti"; + +import { motion } from "framer-motion"; +import { OPACITY_ON_LOAD } from "../../Layout/animations"; + +import { yupResolver } from "@hookform/resolvers/yup"; +import { useForm } from "react-hook-form"; +import { addBlogSchema } from "../../Validations/Validations"; +import { useCreateBlogMutation } from "../../Services/api.service"; +import { useNavigate } from "react-router-dom"; +import Loader01 from "../../Components/Loaders/Loader01"; +import ReactQuill from "react-quill"; +import "react-quill/dist/quill.snow.css"; +import ChipSelector from "../../Components/ChipSelector/ChipSelector"; +import Header from "../../Components/Header"; + +const AddBlogsAndArticles = () => { + const toast = useToast(); + const navigate = useNavigate(); + const [createBlog] = useCreateBlogMutation(); // Invoke the hook to get the mutation function + const [isLoading, setIsLoading] = useState(false); + const [selectedImage, setSelectedImage] = useState(fallbackImage); + const [selectedImageLarge, setSelectedImageLarge] = + useState(fallbackImageLarge); + const [largeImageData, setLargeImageData] = useState(null); + const [smallImageData, setSmallImageData] = useState(null); + const [chips, setChips] = useState([]); + const [value, setValue] = useState(""); + + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + resolver: yupResolver(addBlogSchema), + }); + + const onSubmit = async (data) => { + console.log(data); + const formData = new FormData(); + formData.append("author_name", data.author_name); + formData.append("author_designation", data.author_designation); + formData.append("meta_description", data.meta_description); + formData.append("title", data.title); + formData.append("category", data.category); + formData.append("summary", data.summary); + formData.append("content", value); // Add the content to formData + if (data.profile_image[0]) { + formData.append("profile_image", data.profile_image[0]); + } + if (data.content_image_large[0]) { + formData.append("content_image_large", data.content_image_large[0]); + } + if (chips.length === 0) { + return toast({ + title: "Please add tags", + status: "error", + isClosable: true, + }); + } else { + // formData.append("tags", chips); + chips.forEach((tag, i) => { + formData.append(`tags[${i}]`, tag); // Append each tag as an array element + }); + } + + try { + setIsLoading(true); + createBlog(formData) + .then((response) => { + // Handle the response here + console.log("Mutation response:", response?.data?.statusCode); + console.log("Mutation response:", response?.data?.message); + + if (response?.data?.statusCode === 201) { + setIsLoading(false); + toast({ + title: response?.data?.message, + status: "success", + isClosable: true, + }); + reset(); + navigate("/blogs-articles"); + } + }) + .catch((error) => { + // Handle errors + console.error("Error creating community:", error); + setIsLoading(false); + // Handle error notification if needed + }); + } catch (error) { + // Handle errors + console.error("Error creating community:", error); + } + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + setSmallImageData(file); + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setSelectedImage(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + const handleImageChangeLarge = (e) => { + const file = e.target.files[0]; + setLargeImageData(file); + console.log(largeImageData); + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setSelectedImageLarge(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + return ( + + +
+
+ + + + + + Blog Info + + + + Select the platform for which you need to create this campaign. + + + + + + + + + Blog's banner image + + + Below is the profile that will be displayed on the community page. + + + + {false ? ( + + {/* + Display profile + */} + {/* */} + + + + + + + + Drop images here + + + or click to upload + + + + + + + + + {errors.content_image_large && ( + + {" "} + {errors.content_image_large.message} + + )} + + Maximum limit of image is 5mb. + + + ) : ( + <> + Selected Image + {selectedImageLarge === fallbackImageLarge || largeImageData === null ? ( + "" + ) : ( + + + {largeImageData?.name} + + + {(largeImageData?.size / (1024 * 1024)).toFixed(2)} mb + + + )} + + )} + + + + + + + + + + + + + + + + Blog title + + + + Maximum characters must be 100 characters. + + + {errors.title && ( + + {errors.title.message} + + )} + + + + + Blog description + + +