second commit

This commit is contained in:
2024-05-07 12:47:06 +05:30
parent f9b8ba66a2
commit f70245efd5
93 changed files with 13356 additions and 0 deletions

3
.env Normal file
View File

@@ -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"

21
.eslintrc.cjs Normal file
View File

@@ -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 },
],
},
}

24
.gitignore vendored Normal file
View File

@@ -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?

13
index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rubix</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

45
package.json Normal file
View File

@@ -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"
}
}

1
public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

274
src/App.css Normal file
View File

@@ -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;
}
}

72
src/App.jsx Normal file
View File

@@ -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 <Navigate to="/login" replace />;
}
return children;
};
return (
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/*"
element={
isOnline ? (
isAuthenticate || isAuthenticatedInCookie === "true" ? (
<DefaultLayout />
) : (
<Login />
)
) : (
<NoInternetScreen />
)
}
/>
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
);
};
export default App;

View File

@@ -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 (
<Box>
<HStack
display={"flex"}
justifyContent={"space-between"}
alignItems={"center"}
pe={1}
>
<Text as={"span"} color={"teal.800"} className="web-text-large fw-bold">
{stackTitle}
</Text>
<Link to={viewAllLink}>
<Button
variant="ghost"
rightIcon={<ArrowForwardIcon />}
colorScheme={"teal"}
size="sm"
>
View all
</Button>
</Link>
</HStack>
<Box
display={"flex"}
// bg={"red.500"}
alignItems={"start"}
flexWrap={"wrap"}
justifyContent={"start"}
w={"100%"}
// h={"auto"}
h={200}
>
{bannerIsLoading
? Array.from({ length: 4 }).map((_, index) => (
<Box className="col-4 p-2 ps-0">
<Skeleton w={"100%"} key={index} rounded={"md"} height={150} />
</Box>
))
: bannerArray?.map(
({
id,
CTO_button_title,
banner_image,
Heading,
createdAt,
sub_heading,
}) => (
<Link className="col-4 h-100 p-3 ps-0" key={id} to={`${viewBannerLink}/${id}`}>
<CommunityBannerCard
bgImage={banner_image}
subHeading={sub_heading}
heading={Heading}
createdAt={createdAt}
ctoBtnTitle={CTO_button_title}
/>
</Link>
)
)}
</Box>
</Box>
);
};
export default BannerStack;

View File

@@ -0,0 +1,25 @@
import Loader01 from "../Loaders/Loader01";
const Button01 = ({ title, onClick, type, backgroundColor, hover, isLoading }) => {
return (
<button
style={{
backgroundColor: backgroundColor,
outline: "none",
border: "none",
height: 50,
transition: "opacity 0.3s", // Add transition for smooth hover effect
}}
type={type}
onClick={onClick ? onClick : null}
className=" rounded-3 p-2 w-100 text-white p-3 fs-6 fw-bold border-none"
onMouseEnter={(e) => (e.target.style.backgroundColor = hover)}
onMouseLeave={(e) => (e.target.style.backgroundColor = backgroundColor)}
>
{isLoading ? <Loader01/> : title}
</button>
);
};
export default Button01;

View File

@@ -0,0 +1,47 @@
import { RiLogoutCircleLine } from "react-icons/ri";
const Button02 = ({ title, onClick, type, gradientColors, height, width, open }) => {
const [startColor, endColor] = gradientColors;
return (
<button
style={{
background: `linear-gradient(to right, ${startColor}, ${endColor})`, // Apply linear gradient background
outline: "none",
border: "none",
height: height,
width: width,
opacity:0.9,
boxShadow:"rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset"
}}
type={type}
onClick={onClick ? onClick : null}
className=" rounded-4 text-white fs-6 fw-bold border-none text-center overflow-hidden d-flex align-items-center gap-2 justify-content-center"
// Add hover style
onMouseEnter={(e) =>
(e.target.style.opacity = 1)
}
onMouseLeave={(e) =>
(e.target.style.opacity = 0.9)
}
>
<RiLogoutCircleLine style={{
opacity: open ? 0 : 1,
transform: "translateX(14px)",
display: open ? 'none' : '',
transition: 'All 1s ease-in-out'
}} className="fs-4" />
<span
style={{
transform: open ? 'translateX(0px)' : 'translateX(-100px)',
fontSize: open ? '' : '6px',
// display: open ? '' : 'none',
transition: 'All 0.5s ease-in-out'
}}
>{title}</span>
</button>
);
};
export default Button02;

View File

@@ -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;
}

View File

@@ -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 (
<div>
<Box
display={'flex'}
flexDirection={'column'}
gap={3}
className="">
<Input
type="text"
id="tags"
size="sm"
className="web-text-medium "
placeholder="Press Enter to add tag"
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={handlePressEnter}
/>
{validationError && <span className="error-message web-text-small text-danger d-flex align-items-center gap-1"><TiWarning className="fw-bold fs-5 " />{validationError}</span>}
<Box
display={'flex'}
justifyContent={'start'}
flexWrap={'wrap'}
gap={2}
>
{chips?.map((chip, i) => (
<Tag key={i} size="md" variant='solid' colorScheme='teal'>
<TagLabel>{chip}</TagLabel>
<TagCloseButton onClick={() => removeChip(chip)} />
</Tag>
))}
</Box>
</Box>
</div>
);
};
export default ChipSelector;

View File

@@ -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 (
<Box
height={"75%"}
backgroundImage={`url(${banner})`}
backgroundRepeat={"no-repeat"}
backgroundSize={"cover"}
display={"grid"}
placeContent={"center"}
>
<Container
alignItems={"center"}
display={"flex"}
height={"100%"}
alignContent={"center"}
maxW="container.xl"
textAlign={"left"}
marginTop={"2rem"}
paddingLeft={"3.5rem"}
>
<Box
width={"75%"}
>
<Text
fontWeight={700}
fontSize={"30px"}
// textTransform={"upperCase"}
color={"#DE858E"}
lineHeight={"42px"}
letterSpacing={"1px"}
sx={{
"@media (max-width: 996px)": {
fontSize: "46px",
},
"@media (max-width: 600px)": {
fontSize: "40px",
marginBottom: "0rem",
lineHeight: "54px",
},
}}
>
<span
style={{
color: "#fff",
}}
>
{BannerContent[0].heading1}
</span>{" "}
<br />
{BannerContent[0].heading2}
</Text>
<Box
marginTop={"0.5rem"}
width={"80%"}
sx={{
"@media (max-width: 500px)": {
width: "100%",
},
}}
>
<Text
color={"#fff"}
fontSize={"15px"}
fontWeight={"400"}
lineHeight={"27.5px"}
fontFamily={"Poppins"}
textTransform={"capitalize"}
>
{BannerContent[1].subheading}
</Text>
</Box>
<Button
position={"relative"}
backgroundColor={"transparent"}
cursor={"pointer"}
transition="0.3s ease-in-out"
color={"#fff"}
width={"180px"}
height={"44px"}
fontFamily={"Poppins"}
fontWeight={"400"}
border={"1px solid white"}
borderRadius={"10px"}
fontSize={"12px"}
zIndex={"1"}
overflow={"hidden"}
marginTop={"0.5rem"}
sx={{
"::before": {
content: '""',
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "65px",
height: "65px",
borderRadius: "50%",
transition: "0.35s linear",
zIndex: -1,
bgGradient:
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
opacity: "0",
},
"&:hover::before": {
width: "100%",
height: "120%",
borderRadius: "0px",
opacity: "1",
},
"@media (max-width: 500px)": {
fontSize: "14px",
width: "230px",
height: "44px",
marginTop: "2rem",
bgGradient:
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
color: "#000",
fontWeight: "600",
},
}}
_hover={{
color: "#000",
border: "1px solid white",
zIndex: 1,
}}
>
{BannerContent[2].btn}
</Button>
</Box>
</Container>
</Box>
);
};
export default CommunityBanner;

View File

@@ -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 (
<AlertDialog
motionPreset="slideInBottom"
// leastDestructiveRef={cancelRef}
onClose={onClose}
isOpen={isOpen}
isCentered
>
<AlertDialogOverlay />
<AlertDialogContent w={400}>
<AlertDialogCloseButton className="web-text-xsmall link" />
<AlertDialogBody className="text-center web-text-large fw-bold" pt={8}>
{message}
</AlertDialogBody>
<AlertDialogFooter display={"flex"} justifyContent={"center"}>
<Button
size={"sm"}
// ref={cancelRef}
onClick={onClose}
>
No
</Button>
<Button
backgroundColor={"#ff6b6b"}
isLoading={isLoading}
onClick={alertHandler}
size={"sm"}
colorScheme="red"
ml={3}
>
Yes
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
export default CustomAlertDialog;

View File

@@ -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 (
<TableContainer className="h-auto mb-3 w-100">
{data?.length === 0 ? (
<EmptySearchList message={emptyMessage} />
) : (
<Table size="sm">
<TableCaption>Rubix v1.0.0</TableCaption>
<Thead backgroundColor="gray.50">
<Tr>
{tableHeadRow.map((heading, index) => (
<Th key={index} p={3} w={columnWidth}>
{isLoading ? <Skeleton height="20px" /> : heading}
{/* {heading} */}
</Th>
))}
</Tr>
</Thead>
<Tbody className="web-text-small">
{isLoading
? Array.from({ length: 12 }).map((_, index) => (
<Tr key={index}>
{tableHeadRow.map((_, i) => (
<Td key={i} style={{ whiteSpace: "nowrap", textOverflow: "ellipsis" }} className="web-text-small" w={columnWidth}>
<Skeleton height="20px" mb={1} mt={1} />
</Td>
))}
</Tr>
))
: data?.map((item, index) => (
<Tr key={index}>
{tableHeadRow.map((heading, i) => (
<Td key={i} style={{ whiteSpace: "nowrap", textOverflow: "ellipsis" }} className="web-text-small" >
{item[heading]}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
)}
</TableContainer>
);
};
export default DataTable;

View File

@@ -0,0 +1,21 @@
import { Box, Image, Text } from "@chakra-ui/react"
import EmptySearchListImage from "../assets/EmptySearchList.svg"
const EmptySearchList = ({message}) => {
return (
<Box
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
flexDirection={'column'}
w={"100%"} h={"80vh"}
>
<Image w={200} mb={8} h={200} src={EmptySearchListImage} alt='Dan Abramov' />
<Text className=" fw-bold fs-5" >{message}</Text>
<Text as={'p'} className="web-text-medium">Posts of rubix will appear here.</Text>
</Box>
)
}
export default EmptySearchList

View File

@@ -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;

View File

@@ -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,
});
}

View File

@@ -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;
}

45
src/Components/Header.jsx Normal file
View File

@@ -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 (
<Box
backgroundColor={'#fff'}
// bg="white.900"
// backdropFilter="blur(10px) hue-rotate(90deg)"
position={"sticky"}
top={0}
zIndex={999}
className={`${
link && btnTitle ? "" : " pt-3 pb-3"
} p-2 pe-2 fw-400 border-bottom d-flex justify-content-between align-items-center`}
>
{/* <span className="fs-5">Community</span> */}
<Text as={"span"} color={"teal.800"} className="fs-6 ">
{title}
</Text>
{btnTitle && link && (
<Link to={link}>
<Button
leftIcon={<AddIcon />}
backgroundColor={"purple.700"}
_hover={{
backgroundColor: "purple.800",
}}
color={"whitesmoke"}
size="sm"
rounded={"sm"}
>
{btnTitle}
</Button>
</Link>
)}
</Box>
);
};
export default Header;

View File

@@ -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 (
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={"105px"}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span className="d-flex flex-column align-items-center" spacing="1">
<Heading fontSize="lg" color="gray.700" fontWeight="bold">
Drop images here
</Heading>
<span fontWeight="light" className="web-text-large text-secondary text-center">or click to upload</span>
</span>
</Stack>
</Box>
<Input
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
);
};
export default ImageDropBox;

View File

@@ -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 (
<input
style={
isFocused ? { ...input01Style, ...focusedInputStyle } : input01Style
}
type={type}
name={name}
className="rounded-3 ps-3 d-flex align-items-center"
onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
{...register}
/>
);
};
export default Input01;

View File

@@ -0,0 +1,18 @@
import { Box, Spinner } from "@chakra-ui/react";
import React from "react";
const FullscreenLoaders = () => {
return (
<Box
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
w={"100%"}
h={"90%"}
>
<Spinner />
</Box>
);
};
export default FullscreenLoaders;

View File

@@ -0,0 +1,14 @@
import React from "react";
const Loader01 = () => {
return (
<div className="lds-ellipsis ">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
);
};
export default Loader01;

View File

@@ -0,0 +1,68 @@
import { Button } from '@chakra-ui/react'
import React from 'react'
const WebButton = ({title}) => {
return ( <Button
position={"relative"}
backgroundColor={"transparent"}
cursor={"pointer"}
transition="0.3s ease-in-out"
color={"#fff"}
width={"auto"}
height={"auto"}
p={6}
pt={2}
pb={2}
fontFamily={"Poppins"}
fontWeight={"600"}
border={"1px solid white"}
borderRadius={"4px"}
fontSize={"14px"}
zIndex={"1"}
overflow={"hidden"}
sx={{
"::before": {
content: '""',
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "65px",
height: "65px",
borderRadius: "50%",
transition: "0.35s linear",
zIndex: -1,
bgGradient:
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
opacity: "0",
},
"&:hover::before": {
width: "100%",
height: "120%",
borderRadius: "0px",
opacity: "1",
},
"@media (max-width: 500px)": {
fontSize: "14px",
width: "230px",
height: "44px",
marginTop: "2rem",
bgGradient:
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
color: "#000",
fontWeight: "600",
},
}}
_hover={{
color: "#000",
border: "1px solid white",
zIndex: 1,
}}
>
{title}
</Button>
)
}
export default WebButton

View File

@@ -0,0 +1 @@
export const TABLE_PAGINATION = { page: 1, size: 15 }

View File

@@ -0,0 +1,6 @@
// GlobalStateContext.js
import { createContext } from 'react';
const GlobalStateContext = createContext();
export default GlobalStateContext;

View File

@@ -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 (
<GlobalStateContext.Provider
value={{
isAuthenticate,
setIsAuthenticate,
memberIfo,
setMemberInfo,
communityMembers,
setCommityMembers,
}}
>
{children}
</GlobalStateContext.Provider>
);
};
export default GlobalStateProvider;

BIN
src/Images/dark-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

BIN
src/Images/light-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
src/Images/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
src/Images/logoDark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/Images/logoDarkMini.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/Images/logoLight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/Images/miniLogo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
src/Images/reactLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -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 <Text color={'teal.800'} className="d-flex align-items-center">
<Link to={"/community/"}>
<ArrowBackIcon className="me-2 fs-3 link p-1 rounded-1" />
</Link>
Community
</Text>;
default:
if (path.startsWith("/community/view/")) {
return (
<span className="d-flex align-items-center">
<Link to={"/community/"}>
<ArrowBackIcon className="me-2 fs-3 link p-1 rounded-1" />
</Link>
Community
</span>
);
} else if (path.startsWith("/community/edit/")) {
return (
<span className="d-flex align-items-center">
<Link to={"/community/"}>
<ArrowBackIcon className="me-2 fs-3 link p-1 rounded-1" />
</Link>
Community
</span>
);
}
return "Rubix";
}
};
return (
<Box
style={{
height: "100vh",
width: "100vw",
position: "relative",
overflow: "hidden",
// backgroundColor:"#000000"
}}
className="d-flex"
>
<aside
className="h-100 position-relative sideBar"
// onMouseOver={() => setIsDrawerOpen(true)}
// onMouseLeave={() => setIsDrawerOpen(false)}
style={{
width: isDrawerOpen || openDrawerClick ? 225 : 66,
transition: "width 0.3s ease-in-out", // Smooth transition for width change
overflow: "hidden", // Hide overflow to prevent content overflow during transition
backgroundColor:"#FAFBFC"
}}
>
<div className="d-flex justify-content-start p-3 pt-3 pb-4 position-relative">
{isDrawerOpen || openDrawerClick ? (
<img
style={{
width: 100,
}}
src={logo}
alt="Logo"
/>
) : (
<img
style={{
width: 25,
}}
src={logoMini}
alt="Logo"
/>
)}
</div>
<div className="p-2 ">
{nav.map(({ title, path, Icon }, index) => (
<div key={index} className=" mb-1 rubix-text-dark w-100 d-flex ">
{path ? (
<NavLink
style={{
height: "auto",
}}
className={`${isDrawerOpen || openDrawerClick ? "p-1 web-text-medium" : "p-2 web-text-large"} rounded-2 link d-flex align-items-center gap-2 w-100 ps-3 `}
to={path}
>
<span
style={{
display:
isDrawerOpen || openDrawerClick ? "flex" : "flex",
alignItems: "center",
paddingBottom: 0,
}}
>
<Icon className="web-text-large" />
</span>
<span
style={{
display:
isDrawerOpen || openDrawerClick ? "flex" : "none",
alignItems: "center",
padding: 3,
overflow:"hidden"
}}
>
{title}
</span>
</NavLink>
) : (
<span
style={{
display: "none",}}
className="web-text-xxsmall fw-600 mt-1 text-secondary">
{title}
</span>
)}
</div>
))}
</div>
<section
className="d-flex justify-content-center border-top p-2 "
style={{
position: "absolute",
left: 0,
bottom: 0,
width: "100%",
}}
>
<Popover placement="top">
<Portal>
<PopoverContent maxW="220px" className="ms-2">
<PopoverArrow />
<PopoverBody className="web-text-medium pointer link">
Profile
</PopoverBody>
<Link to={'/help-and-support'}>
<PopoverBody className="web-text-medium pointer link">
Help & Support
</PopoverBody>
</Link>
<PopoverFooter
onClick={logOutHandler}
className="web-text-medium pointer link"
>
Log Out
</PopoverFooter>
</PopoverContent>
</Portal>
<PopoverTrigger>
<Box
// onClick={logOutHandler}
className="d-flex pointer align-items-center"
>
<Avatar
size="xs"
name="Dan Abrahmov"
src="https://bit.ly/dan-abramov"
/>
<Box style={{
opacity:isDrawerOpen || openDrawerClick ? 1 : 0,
display:isDrawerOpen || openDrawerClick ? "flex" : "none",
transition: "opacity 0.3s ease-in-out",
}} className=" overflow-hidden ms-3 flex-column ">
<Text as={"span"} className="web-text-small">
Hello, developer admin
</Text>
<Text as={"span"} className="web-text-xsmall">
siddhesh@rubix.com
</Text>
</Box>
</Box>
</PopoverTrigger>
</Popover>
</section>
</aside>
<main
className={`h-100 ${isDrawerOpen || openDrawerClick ? "ps-3" : "ps-3"}`}
style={{
width: `calc(100% - ${isDrawerOpen || openDrawerClick ? 225 : 66}px)`,
transition: "width 0.3s ease-in-out",
position: "relative",
// boxShadow:
// "rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset",
}}
>
<Button
colorScheme="teal"
rounded={'sm'}
// onMouseOver={() => setIsDrawerOpen(true)}
// onMouseLeave={() => setIsDrawerOpen(false)}
onClick={openDrawerOnClick}
style={{
width: 20,
height: 26,
position: "absolute",
left: -28,
bottom: 80,
zIndex: 6,
}}
>
{isDrawerOpen || openDrawerClick ? (
<ArrowLeftIcon className="web-text-small" />
) : (
<ArrowRightIcon className="web-text-small " />
)}
</Button>
{/* <header className="p-2 ps-0 pt-3 fw-400 border-bottom">
<span className="fs-5">{getTitle()}</span>
</header> */}
<AppContent />
</main>
</Box>
);
};
export default DashboardLayout;
const AppContent = () => {
return (
<Routes>
{RouteLink.map(({ path, Component }, index) => (
<Route key={index} path={path} element={<Component />} />
))}
<Route path="*" element={<NotFound />} />
</Routes>
);
};

23
src/Layout/animations.jsx Normal file
View File

@@ -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" }
};

View File

@@ -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 (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
paddingBottom={50}
height={"100vh"}
>
<Header title={"👋🏻 Hi, developer admin"} />
<BannerStack
stackTitle={"Community banner"}
viewAllLink={"/banner/banner-community"}
bannerIsLoading={communityBanner?.isLoading}
bannerArray={communityBanner?.data?.data?.rows?.slice(0, 3)}
// bannerArray={communityBanner?.data?.data?.rows?.filter(item => item?.status === true)}
viewBannerLink={'/banner/banner-community/view'}
/>
<Divider/>
<BannerStack
stackTitle={"Learn banner"}
viewAllLink={"/banner/learn"}
bannerIsLoading={learnBanner?.isLoading}
bannerArray={learnBanner?.data?.data?.rows?.slice(0, 3)}
viewBannerLink={'/banner/learn/view'}
/>
<Divider/>
<BannerStack
stackTitle={"Build banner"}
viewAllLink={"/banner/build"}
bannerIsLoading={buildBanner?.isLoading}
bannerArray={buildBanner?.data?.data?.rows?.slice(0, 3)}
viewBannerLink={'/banner/build/view'}
/>
<Divider/>
<BannerStack
stackTitle={"News banner"}
viewAllLink={"/banner/build"}
bannerIsLoading={newsBanner?.isLoading}
bannerArray={newsBanner?.data?.data?.rows?.slice(0, 3)}
viewBannerLink={'/banner/build/view'}
/>
</Box>
);
};
export default Banner;

View File

@@ -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 (
<Box
{...OPACITY_ON_LOAD}
w={"100%"}
h={"100vh"}
className="overflow-auto "
display={"flex"}
>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Banner Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Banner Image
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box boxSize="sm"
className="d-flex w-100 justify-content-center flex-column align-items-center gap-3">
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={selectedImage}
alt="Selected Image"
/>
<Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
<form
onSubmit={handleSubmit(onSubmit)}
className="col-7 pt-4 mb-3 overflow-auto p-4"
>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Heading
</FormLabel>
<Input
{...register("heading")}
placeholder="Heading"
className="web-text-medium"
size="sm"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.heading.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Sub Heading
</FormLabel>
<Input
{...register("sub_heading")}
placeholder="Sub Heading"
className="web-text-medium"
size="sm"
/>
{errors.sub_heading && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.sub_heading.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Button title
</FormLabel>
<Input
{...register("CTO_button_title")}
placeholder="Button title"
className="web-text-medium"
size="sm"
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.CTO_button_title && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.CTO_button_title.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Button link
</FormLabel>
<Input
{...register("CTO_button_link")}
placeholder="Button link"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
{errors.CTO_button_link && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.CTO_button_link.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Banner image
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={"105px"}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("banner_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.banner_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.banner_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end mb-5">
<Button
isLoading={isLoading}
spinner={<Loader01 />}
color={'whitesmoke'}
backgroundColor={'purple.700'}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
>
Create
</Button>
</Box>
</form>
</Box>
);
};
export default AddBanner;

View File

@@ -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": (
<Image
w={150}
h={14}
rounded={4}
objectFit="cover"
src={`https://rubix.betadelivery.com/${item.banner_image}`}
alt="Dan Abramov"
/>
),
Heading: (
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.Heading}
bg="blue.200"
>
<Box display={"flex"} alignItems={"center"} w={180}>
<Text as={"span"} isTruncated={true}>
{item?.Heading}
</Text>
</Box>
</Tooltip>
),
"Sub heading": (
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.sub_heading}
bg="blue.200"
>
<Box display={"flex"} alignItems={"center"} w={180}>
<Text as={"span"} isTruncated={true}>
{item?.sub_heading}
</Text>
</Box>
</Tooltip>
),
"Button title": item?.CTO_button_title,
Active: (
<Switch
size={"sm"}
colorScheme="teal"
onChange={() => handleUpdateStatus(item.id)}
isChecked={item.status}
/>
),
"Created At": (
<span className="d-flex justify-content-between align-items-center">
<Text as={"span"} color={"teal.600"} className=" fw-bold">
{formatDate(item?.createdAt)}
</Text>
<Menu>
<MenuButton className="link p-1 rounded-1">
<HiDotsVertical className="rubix-text-dark fs-6" />
</MenuButton>
<Portal>
<MenuList minWidth="80px">
<RouterLink to={`/banner/banner-community/edit/${item.id}`}>
<MenuItem className="web-text-medium">Edit</MenuItem>
</RouterLink>
<RouterLink to={`/banner/banner-community/view/${item.id}`}>
<MenuItem className="web-text-medium">View</MenuItem>
</RouterLink>
<MenuItem
onClick={() => {
setActionId(item.id);
setDeleteAlert(true);
}}
className="web-text-medium"
>
Delete
</MenuItem>
</MenuList>
</Portal>
</Menu>
</span>
),
};
});
return (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
paddingBottom={50}
height={"100vh"}
>
<Header
title={"Community banner"}
btnTitle={'Create banner'}
link={'/banner/banner-community/add-banner'}
/>
{/* ====================================================[ Top bar ]================================================================ */}
{/* <Divider /> */}
<Box pt={4}>
{/* <HStack>
<Text color={"teal.800"} className="web-text-large fw-bold">
Community Banners
</Text>
</HStack> */}
<HStack
display={"flex"}
justifyContent={"space-between"}
ps={1}
pe={1}
pb={4}
spacing="24px"
>
<Input
type="search"
width={300}
rounded="md"
placeholder="Search..."
size="sm"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<HStack>
<Select
className="pointer"
width={300}
rounded="md"
size="sm"
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
>
<option value="all">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</Select>
</HStack>
</HStack>
</Box>
{/* ====================================================[ Table ]================================================================ */}
<DataTable
emptyMessage={"We don't have any banner of this heading"}
tableHeadRow={tableHeadRow}
data={extractedArray}
isLoading={community?.isLoading}
/>
{/* ====================================================[ Alert ]================================================================ */}
<CustomAlertDialog
onClose={() => setDeleteAlert(false)}
isOpen={deleteAlert}
alertHandler={() => handleDelete(actionId)}
message={"Are you sure you want to delete member?"}
isLoading={deleteIsLoading}
/>
</Box>
);
};
export default BannerCommunity;

View File

@@ -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 ? (
<FullscreenLoaders />
) : (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
w={"100%"}
h={"100vh"}
className="overflow-auto "
display={"flex"}
flexDirection={'column'}
>
<Header
title={"Banner's"}
/>
<Box display={'flex'}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Display Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Display banner
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box boxSize="sm"
className="d-flex w-100 justify-content-center flex-column align-items-center gap-3">
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={selectedImage}
alt="Selected Image"
/>
<Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
<form
className="col-7 pt-4 overflow-auto p-4"
onSubmit={handleSubmit(onSubmit)}
>
{/* <Switch
size={"sm"}
colorScheme="teal"
onChange={() => handleUpdateStatus(item.id)}
isChecked={data?.data?.status}
/> */}
<Box className="web-text-large fw-bold mb-2 rubix-text-dark">
Status
</Box>
{data?.data?.status ? <Tag position={'sticky'} right={10} size={"sm"} variant="solid" colorScheme="teal">
Active
</Tag> : <Tag position={'sticky'} right={10} size={"sm"} variant="solid" colorScheme="red">
Inactive
</Tag>}
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Heading
</FormLabel>
<Input
{...register("Heading")}
placeholder="Heading"
className="web-text-medium"
size="sm"
name="Heading"
type="text"
id="Heading"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.Heading.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Sub heading
</FormLabel>
<Input
{...register("sub_heading")}
placeholder="Sub heading"
className="web-text-medium"
size="sm"
id="sub_heading"
name="sub_heading"
/>
{errors.sub_heading && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.sub_heading.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
CTO Button title
</FormLabel>
<Input
{...register("CTO_button_title")}
placeholder="CTO_button_title"
className="web-text-medium"
size="sm"
maxLength={90}
id="CTO_button_title"
name="CTO_button_title"
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.CTO_button_title && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.CTO_button_title.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
CTO Button link
</FormLabel>
<Input
{...register("CTO_button_link")}
placeholder="CTO_button_link link"
className="web-text-medium"
size="sm"
id="CTO_button_link"
name="CTO_button_link"
/>
{errors.CTO_button_link && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.CTO_button_link.message}
</span>
)}
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
</FormControl>
<FormControl className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Display banner
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={"105px"}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("banner_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.banner_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.banner_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end">
<Button
isLoading={isLoadingEdit}
spinner={<Loader01 />}
color={'whitesmoke'}
backgroundColor={'purple.700'}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
rounded={'sm'}
>
Create banner
</Button>
</Box>
</form>
</Box>
</Box>
);
};
export default BannerComunityEditPage;

View File

@@ -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 ? (
<FullscreenLoaders />
) : (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
w={"100%"}
h={"100vh"}
className="overflow-auto "
display={"flex"}
flexDirection={'column'}
>
<Header
title={"Banner's"}
btnTitle={'Edit banner'}
link={`/banner/banner-community/edit/${banner.id}`}
/>
<Box display={'flex'}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Banners Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Display banner
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box boxSize="sm"
className="d-flex w-100 justify-content-center flex-column align-items-center gap-3">
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={`https://rubix.betadelivery.com/${banner?.banner_image}`}
alt="Selected Image"
/>
{/* <Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button> */}
</Box>
</Box>
<Box className="col-7 pt-4 overflow-auto p-4">
{data?.data?.status ? <Tag position={'absolute'} right={10} size={"sm"} variant="solid" colorScheme="teal">
Active
</Tag> : <Tag position={'absolute'} right={10} size={"sm"} variant="solid" colorScheme="red">
Inactive
</Tag>}
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">Heading</Box>
<Box className="web-text-medium text-secondary">
{banner?.Heading}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Sub heading
</Box>
<Box className="web-text-medium text-secondary">
{banner?.sub_heading}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Button title
</Box>
<Box className="web-text-medium text-secondary">
{banner?.CTO_button_title}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">Button link</Box>
<Box className="web-text-medium text-secondary">
{banner?.CTO_button_link}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Created At
</Box>
<Box className="web-text-medium text-secondary">
{formatDate(banner?.createdAt)}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Updated At
</Box>
<Box className="web-text-medium text-secondary">
{formatDate(banner?.updatedAt)}
</Box>
</Box>
<Divider/>
</Box>
</Box>
</Box>
);
};
export default BannerComunityViewPage;

View File

@@ -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 (
<Box
{...OPACITY_ON_LOAD}
w={"100%"}
h={"100vh"}
className="overflow-auto "
display={"flex"}
>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Banner Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Banner Image
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box boxSize="sm"
className="d-flex w-100 justify-content-center flex-column align-items-center gap-3">
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={selectedImage}
alt="Selected Image"
/>
<Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
<form
onSubmit={handleSubmit(onSubmit)}
className="col-7 pt-4 mb-3 overflow-auto p-4"
>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Heading
</FormLabel>
<Input
{...register("heading")}
placeholder="Heading"
className="web-text-medium"
size="sm"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.heading.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Sub Heading
</FormLabel>
<Input
{...register("sub_heading")}
placeholder="Sub Heading"
className="web-text-medium"
size="sm"
/>
{errors.sub_heading && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.sub_heading.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Button title
</FormLabel>
<Input
{...register("CTO_button_title")}
placeholder="Button title"
className="web-text-medium"
size="sm"
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.CTO_button_title && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.CTO_button_title.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Button link
</FormLabel>
<Input
{...register("CTO_button_link")}
placeholder="Button link"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
{errors.CTO_button_link && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.CTO_button_link.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Banner image
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={"105px"}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("banner_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.banner_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.banner_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end mb-5">
<Button
isLoading={isLoading}
spinner={<Loader01 />}
color={'whitesmoke'}
backgroundColor={'purple.700'}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
>
Create
</Button>
</Box>
</form>
</Box>
);
};
export default AddLearnBanner;

View File

@@ -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": (
<Image
w={150}
h={14}
rounded={4}
objectFit="cover"
src={`https://rubix.betadelivery.com/${item.banner_image}`}
alt="Dan Abramov"
/>
),
Heading: (
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.Heading}
bg="blue.200"
>
<Box display={"flex"} alignItems={"center"} w={180}>
<Text as={"span"} isTruncated={true}>
{item?.Heading}
</Text>
</Box>
</Tooltip>
),
"Sub heading": (
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.sub_heading}
bg="blue.200"
>
<Box display={"flex"} alignItems={"center"} w={180}>
<Text as={"span"} isTruncated={true}>
{item?.sub_heading}
</Text>
</Box>
</Tooltip>
),
"Button title": item?.CTO_button_title,
Active: (
<Switch
size={"sm"}
colorScheme="teal"
onChange={() => handleUpdateStatus(item.id)}
isChecked={item.status}
/>
),
"Created At": (
<span className="d-flex justify-content-between align-items-center">
<Text as={"span"} color={"teal.600"} className=" fw-bold">
{formatDate(item?.createdAt)}
</Text>
<Menu>
<MenuButton className="link p-1 rounded-1">
<HiDotsVertical className="rubix-text-dark fs-6" />
</MenuButton>
<Portal>
<MenuList minWidth="80px">
<RouterLink to={`/banner/banner-community/edit/${item.id}`}>
<MenuItem className="web-text-medium">Edit</MenuItem>
</RouterLink>
<RouterLink to={`/banner/banner-community/view/${item.id}`}>
<MenuItem className="web-text-medium">View</MenuItem>
</RouterLink>
<MenuItem
onClick={() => {
setActionId(item.id);
setDeleteAlert(true);
}}
className="web-text-medium"
>
Delete
</MenuItem>
</MenuList>
</Portal>
</Menu>
</span>
),
};
});
return (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
paddingBottom={50}
height={"100vh"}
>
<Header
title={"Community banner"}
btnTitle={"Create banner"}
link={"/banner/learn/add-banner"}
/>
{/* ====================================================[ Top bar ]================================================================ */}
{/* <Divider /> */}
<Box pt={4}>
{/* <HStack>
<Text color={"teal.800"} className="web-text-large fw-bold">
Community Banners
</Text>
</HStack> */}
<HStack
display={"flex"}
justifyContent={"space-between"}
ps={1}
pe={1}
pb={4}
spacing="24px"
>
<Input
type="search"
width={300}
rounded="md"
placeholder="Search..."
size="sm"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<HStack>
<Select
className="pointer"
width={300}
rounded="md"
size="sm"
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
>
<option value="all">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</Select>
</HStack>
</HStack>
</Box>
{/* ====================================================[ Table ]================================================================ */}
<DataTable
emptyMessage={"We don't have any banner of this heading"}
tableHeadRow={tableHeadRow}
data={extractedArray}
isLoading={learnBanner?.isLoading}
/>
{/* ====================================================[ Alert ]================================================================ */}
<CustomAlertDialog
onClose={() => setDeleteAlert(false)}
isOpen={deleteAlert}
alertHandler={() => handleDelete(actionId)}
message={"Are you sure you want to delete member?"}
isLoading={deleteIsLoading}
/>
</Box>
);
};
export default BannerLearn;

View File

@@ -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 (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
height={"100vh"}
display={'flex'}
flexDirection={'column'}
>
<Header title={"Blog"} />
<form onSubmit={handleSubmit(onSubmit)}>
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column justify-content-between gap-2 pt-4">
<Box
flexDirection={'column'}
display={'flex'}>
<span
className="web-text-large fw-bold rubix-text-dark">
Blog Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
</Box>
<Box
flexDirection={'column'}
display={'flex'}>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Blog's banner image
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex w-100 p-2 justify-content-center flex-column align-items-center gap-3"
>
{false ? (
<FormControl isRequired className="mb-3">
{/* <FormLabel className="web-text-large fw-bold rubix-text-dark">
Display profile
</FormLabel> */}
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
h={220}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("content_image_large")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChangeLarge}
onDrop={handleImageChangeLarge}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.content_image_large && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.content_image_large.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
) : (
<>
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={selectedImageLarge}
alt="Selected Image"
/>
{selectedImageLarge === fallbackImageLarge || largeImageData === null ? (
""
) : (
<Box display={"flex"} flexDirection={"column"} w={"100%"}>
<span className="web-text-small">
{largeImageData?.name}
</span>
<span className="web-text-small text-secondary fst-italic">
{(largeImageData?.size / (1024 * 1024)).toFixed(2)} mb
</span>
</Box>
)}
</>
)}
<Button
onClick={() => setSelectedImageLarge(fallbackImageLarge)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
</Box>
<Box className="col-7 pt-4 p-4">
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog title
</FormLabel>
<Input
{...register("title")}
placeholder="Blog title"
className="web-text-medium"
size="sm"
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.title && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.title.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog description
</FormLabel>
<Textarea
rows={4}
{...register("meta_description")}
placeholder="Blog description"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
{errors.meta_description && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.meta_description.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog content
</FormLabel>
{/* <Textarea
rows={4}
{...register("content")}
placeholder="content link"
className="web-text-medium"
size="sm"
/> */}
<ReactQuill
className="rounded-3"
theme="snow"
value={value}
onChange={setValue}
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog summary
</FormLabel>
<Textarea
rows={2}
{...register("summary")}
placeholder="summary link"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
{errors.summary && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.summary.message}
</span>
)}
</FormControl>
<FormControl className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Tags
</FormLabel>
<ChipSelector chips={chips} setChips={setChips} />
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Category
</FormLabel>
<Input
{...register("category")}
placeholder="Category"
className="web-text-medium"
size="sm"
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.category && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.category.message}
</span>
)}
</FormControl>
<FormControl isRequired>
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog banner
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
h={105}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("content_image_large")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChangeLarge}
onDrop={handleImageChangeLarge}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.content_image_large && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.content_image_large.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
</Box>
</Box>
<Divider />
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
{/* <span className="web-text-large fw-bold rubix-text-dark">
Author's Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider /> */}
<span className="web-text-large fw-bold rubix-text-dark">
Author's display profile
</span>
<span className="web-text-medium text-secondary mb-0">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex flex-column align-items-center gap-3 justify-content-center"
>
<Image
shadow={"md"}
rounded={8}
w={214}
h={240}
src={selectedImage}
alt="Selected Image"
/>
{selectedImage === fallbackImage || smallImageData === null ? (
""
) : (
<Box display={"flex"} flexDirection={"column"} w={"100%"}>
<span className="web-text-small">{smallImageData?.name}</span>
<span className="web-text-small text-secondary fst-italic">
{(smallImageData?.size / (1024 * 1024)).toFixed(2)} mb
</span>
</Box>
)}
<Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
<Box className="col-7 pt-4 p-4">
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Author name
</FormLabel>
<Input
{...register("author_name")}
placeholder="Name"
className="web-text-medium"
size="sm"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.author_name.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Author designation
</FormLabel>
<Input
{...register("author_designation")}
placeholder="Author designation"
className="web-text-medium"
size="sm"
/>
{errors.author_designation && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.author_designation.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Display profile
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={105}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("profile_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.profile_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.profile_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end">
<Button
isLoading={isLoading}
spinner={<Loader01 />}
color={"whitesmoke"}
backgroundColor={"purple.700"}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
rounded={'sm'}
>
Create blog
</Button>
</Box>
</Box>
</Box>
</form>
</Box>
);
};
export default AddBlogsAndArticles;

View File

@@ -0,0 +1,335 @@
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 { AddIcon } from "@chakra-ui/icons";
import DataTable from "../../Components/DataTable/DataTable";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import { Link as RouterLink } from "react-router-dom";
import {
useDeleteBlogMutation,
useDeleteCommunityMutation,
useGetBlogQuery,
useGetCommunityBannerQuery,
useGetCommunityByIdQuery,
useGetCommunityQuery,
useUpdateBlogStatusMutation,
useUpdateCommunityStatusMutation,
} from "../../Services/api.service";
import { HiDotsVertical } from "react-icons/hi";
import { formatDate } from "../../Components/Functions/UTCConvertor";
import CustomAlertDialog from "../../Components/CustomAlertDialog";
import Header from "../../Components/Header";
const BlogsAndArticles = () => {
// ====================================================[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 blog = useGetBlogQuery();
const [deleteBlog] = useDeleteBlogMutation();
const [updateBlogStatus] = useUpdateBlogStatusMutation();
// ====================================================[Functions]===================================================================
const handleDelete = async (communityId) => {
try {
// Trigger the mutation
setDeleteIsLoading(true);
await deleteBlog(communityId)
.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) {
setDeleteIsLoading(false);
setDeleteAlert(false);
}
})
.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) => {
try {
// Trigger the mutation
await updateBlogStatus({ 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 = blog?.data?.data?.rows?.filter((item) => {
// Filter by name (case insensitive)
const name = item.title;
const searchLower = searchTerm.toLowerCase();
const nameMatches = name.toLowerCase().includes(searchLower);
// Filter by status
const status = item.active_blog;
const statusMatches =
statusFilter === "all" ||
(statusFilter === "active" && status === true) ||
(statusFilter === "inactive" && status === false);
return nameMatches && statusMatches;
});
// ====================================================[Table Setup]================================================================
const tableHeadRow = [
"Auther Name",
"Discription",
"Summary",
"Tags",
"Active",
"Created At",
];
const extractedArray = filteredData?.map((item, index) => {
return {
"Auther Name": (
<RouterLink
to={`view/${item.id}`}
className="d-flex align-items-center gap-2 pointer"
>
<Avatar
size="sm"
name="KC Reddy"
src={`https://rubix.betadelivery.com/${item.profile_image}`}
/>
<span className="d-flex flex-column">
<Text
as={"span"}
color={"teal.700"}
className="d-flex fw-bold align-items-center web-text-small"
>
{item?.author_name}
</Text>
<span className="d-flex align-items-center web-text-xsmall text-secondary">
{item?.author_designation}
</span>
</span>
</RouterLink>
),
Discription: (
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.meta_description}
bg="blue.200"
>
<Box display={"flex"} alignItems={"center"} w={200}>
<Text as={"span"} isTruncated={true}>
{item?.meta_description}
</Text>
</Box>
</Tooltip>
),
Summary: (
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.summary}
bg="blue.200"
>
<Box display={"flex"} alignItems={"center"} w={200}>
<Text as={"span"} isTruncated={true}>
{item?.summary}
</Text>
</Box>
</Tooltip>
),
Tags: (
<Box
display={"flex"}
flexWrap={"wrap"}
gap={2}
alignItems={"center"}
w={220}
>
{item?.tags?.map(({ id, tag }) => (
<Tag key={id} colorScheme="gray" size={"sm"}>
{tag}
</Tag>
))}
</Box>
),
Active: (
<Switch
size={"sm"}
colorScheme="teal"
onChange={() => handleUpdateStatus(item.id)}
isChecked={item.active_blog}
/>
),
"Created At": (
<span className="d-flex justify-content-between align-items-center">
<Text as={"span"} color={"teal.600"} className=" fw-bold">
{formatDate(item?.createdAt)}
</Text>
<Menu>
<MenuButton className="link p-1 rounded-1">
<HiDotsVertical className="rubix-text-dark fs-6" />
</MenuButton>
<Portal>
<MenuList minWidth="80px">
<RouterLink to={`edit/${item.id}`}>
<MenuItem className="web-text-medium">Edit</MenuItem>
</RouterLink>
<RouterLink to={`view/${item.id}`}>
<MenuItem className="web-text-medium">View</MenuItem>
</RouterLink>
<MenuItem
onClick={() => {
setActionId(item.id);
setDeleteAlert(true);
}}
className="web-text-medium"
>
Delete
</MenuItem>
</MenuList>
</Portal>
</Menu>
</span>
),
};
});
return (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
paddingBottom={50}
height={"100vh"}
>
{/* ====================================================[ Top bar ]================================================================ */}
{/* <Divider /> */}
<Header
title={"Blog"}
btnTitle={"Create blog"}
link={"/blogs-articles/add-blog"}
/>
<Box pt={4} bg="white.500">
{/* <HStack>
<Text color={"teal.800"} className="web-text-large fw-bold">
Blogs
</Text>
</HStack> */}
<HStack
display={"flex"}
justifyContent={"space-between"}
ps={1}
pe={1}
pb={4}
spacing="24px"
>
<Input
type="search"
width={300}
rounded="sm"
placeholder="Search..."
size="sm"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<HStack>
<Select
className="pointer"
width={300}
rounded="sm"
size="sm"
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
>
<option value="all">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</Select>
</HStack>
</HStack>
</Box>
{/* ====================================================[ Table ]================================================================ */}
<DataTable
emptyMessage={"We don't have any blog for this author"}
tableHeadRow={tableHeadRow}
data={extractedArray}
isLoading={blog?.isLoading}
/>
{/* ====================================================[ Alert ]================================================================ */}
<CustomAlertDialog
onClose={() => setDeleteAlert(false)}
isOpen={deleteAlert}
alertHandler={() => handleDelete(actionId)}
message={"Are you sure you want to delete member?"}
isLoading={deleteIsLoading}
/>
</Box>
);
};
export default BlogsAndArticles;

View File

@@ -0,0 +1,789 @@
import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
Box,
Button,
Divider,
FormControl,
Textarea,
FormHelperText,
FormLabel,
Heading,
Image,
Input,
Stack,
useToast,
} from "@chakra-ui/react";
import {
useGetBlogByIdQuery,
useUpdateBlogMutation,
} from "../../Services/api.service";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
import { addBlogSchema } from "../../Validations/Validations";
import fallbackImage from "../../assets/fallBackImage.png";
import fallbackImageLarge from "../../assets/ultp-fallback-img.webp";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import { TiWarning } from "react-icons/ti";
import ReactQuill from "react-quill";
import ChipSelector from "../../Components/ChipSelector/ChipSelector";
import { motion } from "framer-motion";
import Loader01 from "../../Components/Loaders/Loader01";
import Header from "../../Components/Header";
const EditBlogsAndArticles = () => {
const { id } = useParams();
const toast = useToast();
const navigate = useNavigate();
const [updateBlog] = useUpdateBlogMutation();
const { data, error, isLoading } = useGetBlogByIdQuery(id);
const blog = data?.data;
const [isLoading01, setIsLoading01] = useState(false);
const [selectedImage, setSelectedImage] = useState(fallbackImage);
const [selectedImageLarge, setSelectedImageLarge] =
useState(fallbackImageLarge);
const [largeImageData, setLargeImageData] = useState({});
const [smallImageData, setSmallImageData] = useState({});
const [chips, setChips] = useState(
blog?.tags?.map((tagObject) => tagObject.tag)
);
console.log(blog?.tags?.map((tagObject) => tagObject.tag));
const [valueQuill, setValueQuill] = useState(blog?.content);
console.log(blog);
const {
register,
handleSubmit,
reset,
formState: { errors },
setValue,
} = useForm({
resolver: yupResolver(addBlogSchema),
defaultValues: {
author_name: "",
author_designation: "",
meta_description: "",
title: "",
category: "",
summary: "",
content: "",
profile_image: "",
content_image_large: "",
tags: "",
},
});
useEffect(() => {
if (data?.data) {
setSelectedImage(`https://rubix.betadelivery.com/${blog?.profile_image}`);
setSelectedImageLarge(
`https://rubix.betadelivery.com/${blog?.content_image_large}`
);
setValue("author_name", blog?.author_name);
setValue("author_designation", blog?.author_designation);
setValue("meta_description", blog?.meta_description);
setValue("title", blog?.title);
setValue("category", blog?.category?.blog_category);
setValue("summary", blog?.summary);
setValue("content", blog?.content);
setValue("tags", blog?.tags);
}
}, [data, blog, setValue]);
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", valueQuill); // 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 {
setIsLoading01(true);
updateBlog({ id: id, data: formData })
.then((response) => {
// Handle the response here
console.log("Mutation response:", response?.data?.statusCode);
console.log("Mutation response:", response?.data?.message);
console.log(response);
if (response?.data?.statusCode === 201) {
setIsLoading01(false);
toast({
title: response?.data?.message,
status: "success",
isClosable: true,
});
reset();
navigate("/blogs-articles");
}
})
.catch((error) => {
// Handle errors
console.error("Error creating community:", error);
setIsLoading01(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);
}
};
if (isLoading) {
return <FullscreenLoaders />;
}
return (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
w={"100%"}
h={"100vh"}
className="overflow-auto "
display={"flex"}
flexDirection={"column"}
>
<Header title={"Blog"} />
<form className="w-100" onSubmit={handleSubmit(onSubmit)}>
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column justify-content-between gap-2 pt-4">
<Box
flexDirection={'column'}
display={'flex'}>
<span
className="web-text-large fw-bold rubix-text-dark">
Blog Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
</Box>
<Box
flexDirection={'column'}
display={'flex'}>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Blog's banner image
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex w-100 p-2 justify-content-center flex-column align-items-center gap-3"
>
{false ? (
<FormControl isRequired className="mb-3">
{/* <FormLabel className="web-text-large fw-bold rubix-text-dark">
Display profile
</FormLabel> */}
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
h={220}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("content_image_large")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChangeLarge}
onDrop={handleImageChangeLarge}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.content_image_large && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.content_image_large.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
) : (
<>
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={selectedImageLarge}
alt="Selected Image"
/>
{selectedImageLarge === fallbackImageLarge ? (
""
) : (
<Box display={"flex"} flexDirection={"column"} w={"100%"}>
<span className="web-text-small">
{largeImageData?.name}
</span>
<span className="web-text-small text-secondary fst-italic">
{(largeImageData?.size / (1024 * 1024)).toFixed(2)} mb
</span>
</Box>
)}
</>
)}
<Button
onClick={() => setSelectedImageLarge(fallbackImageLarge)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
</Box>
<Box className="col-7 pt-4 p-4">
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog title
</FormLabel>
<Input
{...register("title")}
placeholder="Blog title"
className="web-text-medium"
size="sm"
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.title && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.title.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog description
</FormLabel>
<Textarea
rows={4}
{...register("meta_description")}
placeholder="Blog description"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
{errors.meta_description && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.meta_description.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog content
</FormLabel>
{/* <Textarea
rows={4}
{...register("content")}
placeholder="content link"
className="web-text-medium"
size="sm"
/> */}
<ReactQuill
className="rounded-3"
theme="snow"
value={valueQuill}
onChange={setValueQuill}
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog summary
</FormLabel>
<Textarea
rows={2}
{...register("summary")}
placeholder="summary link"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
{errors.summary && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.summary.message}
</span>
)}
</FormControl>
<FormControl className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Tags
</FormLabel>
<ChipSelector chips={chips} setChips={setChips} />
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Category
</FormLabel>
<Input
{...register("category")}
placeholder="Category"
className="web-text-medium"
size="sm"
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.category && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.category.message}
</span>
)}
</FormControl>
<FormControl isRequired>
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Blog banner
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
h={105}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("content_image_large")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChangeLarge}
onDrop={handleImageChangeLarge}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.content_image_large && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.content_image_large.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
</Box>
</Box>
<Divider />
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
{/* <span className="web-text-large fw-bold rubix-text-dark">
Author's Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider /> */}
<span className="web-text-large fw-bold rubix-text-dark">
Author's display profile
</span>
<span className="web-text-medium text-secondary mb-0">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex flex-column align-items-center gap-3 justify-content-center"
>
<Image
shadow={"md"}
rounded={8}
w={214}
h={240}
src={selectedImage}
alt="Selected Image"
/>
{selectedImage === fallbackImage ? (
""
) : (
<Box display={"flex"} flexDirection={"column"} w={"100%"}>
<span className="web-text-small">{smallImageData?.name}</span>
<span className="web-text-small text-secondary fst-italic">
{(smallImageData?.size / (1024 * 1024)).toFixed(2)} mb
</span>
</Box>
)}
<Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
<Box className="col-7 pt-4 p-4">
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Author name
</FormLabel>
<Input
{...register("author_name")}
placeholder="Name"
className="web-text-medium"
size="sm"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.author_name.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Author designation
</FormLabel>
<Input
{...register("author_designation")}
placeholder="Author designation"
className="web-text-medium"
size="sm"
/>
{errors.author_designation && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.author_designation.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Display profile
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={105}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("profile_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.profile_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.profile_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end">
<Button
isLoading={isLoading01}
spinner={<Loader01 />}
color={"whitesmoke"}
backgroundColor={"purple.700"}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
rounded={"sm"}
>
Create blog
</Button>
</Box>
</Box>
</Box>
</form>
</Box>
);
};
export default EditBlogsAndArticles;

View File

@@ -0,0 +1,228 @@
import {
Box,
Button,
Divider,
Image,
Tag,
Text,
useToast,
} from "@chakra-ui/react";
import React from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useGetBlogByIdQuery } from "../../Services/api.service";
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import Header from "../../Components/Header";
const ViewBlogsAndArticles = () => {
const { id } = useParams();
const toast = useToast();
const navigate = useNavigate();
const { data, error, isLoading } = useGetBlogByIdQuery(id);
const blog = data?.data;
if (isLoading) {
return <FullscreenLoaders />;
}
console.log(blog);
return (
<Box
{...OPACITY_ON_LOAD}
h={"100vh"}
overflowY={"scroll"}
display={"flex"}
flexDirection={"column"}
>
<Header
title={'Blog'}
btnTitle={'Edit blog'}
link={`/blogs-articles/edit/${id}`}
/>
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Blog Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Blog's banner image
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex h-auto w-100 justify-content-start flex-column align-items-center gap-3"
>
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={`https://rubix.betadelivery.com/${blog?.content_image_large}`}
alt="Selected Image"
/>
{/* <Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button> */}
</Box>
</Box>
<Box className="col-7 pt-4 p-4">
<Box>
<Box className="web-text-large fw-bold mb-1 rubix-text-dark">
Status
</Box>
{blog.active_blog ? (
<Tag size={"sm"} borderRadius="full" colorScheme="teal">
Active
</Tag>
) : (
<Tag
size={"sm"}
variant="solid"
colorScheme="red"
>
Inactive
</Tag>
)}
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">Title</Box>
<Box className="web-text-medium text-secondary">{blog?.title}</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Blog description
</Box>
<Box className="web-text-medium text-secondary">
{blog?.meta_description}
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Content
</Box>
<Box className="web-text-medium">
<Text
pb={2}
className="text-dark"
dangerouslySetInnerHTML={{ __html: blog?.content }}
/>
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Blog summary
</Box>
<Box className="web-text-medium text-secondary">
{blog?.summary}
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark mb-2">
Tags
</Box>
<Box
display={"flex"}
flexWrap={"wrap"}
gap={2}
alignItems={"center"}
w={"100%"}
>
{blog?.tags?.map(({ id, tag }) => (
<Tag
key={id}
borderRadius="full"
colorScheme="teal"
size={"sm"}
>
{tag}
</Tag>
))}
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Category
</Box>
<Box className="web-text-medium text-secondary">
{blog?.category?.blog_category}
</Box>
</Box>
</Box>
</Box>
<Divider />
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Author's display profile
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex w-100 justify-content-start flex-column align-items-center gap-3"
>
<Image
shadow={"md"}
rounded={8}
w={214}
h={240}
src={`https://rubix.betadelivery.com/${blog?.profile_image}`}
alt="Selected Image"
/>
</Box>
</Box>
<Box className="col-7 pt-4 p-4">
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Author name
</Box>
<Box className="web-text-medium text-secondary">
{blog?.author_name}
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Author designation
</Box>
<Box className="web-text-medium text-secondary">
{blog?.author_designation}
</Box>
</Box>
</Box>
</Box>
</Box>
);
};
export default ViewBlogsAndArticles;

View File

@@ -0,0 +1,343 @@
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 { 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 { addCommunitySchema } from "../../Validations/Validations";
import {
useCreateCommunityMutation,
useGetCommunityQuery,
} from "../../Services/api.service";
import { useNavigate } from "react-router-dom";
import Loader01 from "../../Components/Loaders/Loader01";
import Header from "../../Components/Header";
const AddComunity = () => {
const toast = useToast();
const navigate = useNavigate();
const getCommunityQuery = useGetCommunityQuery();
const [createCommunityData] = useCreateCommunityMutation(); // 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(addCommunitySchema),
});
const onSubmit = async (data) => {
try {
setIsLoading(true);
const formData = new FormData();
formData.append("member_name", data.member_name);
formData.append("designation", data.designation);
formData.append("description", data.description);
formData.append("linkedin", data.linkedin);
if (data.profile_image[0]) {
formData.append("profile_image", data.profile_image[0]);
}
// Trigger the mutation
createCommunityData(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("/community");
}
})
.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];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setSelectedImage(reader.result);
};
reader.readAsDataURL(file);
}
};
return (
<Box
{...OPACITY_ON_LOAD}
w={"100%"}
h={"100vh"}
overflowY={"scroll"}
display={"flex"}
flexDirection={"column"}
>
<Header title={"Community"} />
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Members Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Display profile
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box boxSize="sm" className="d-flex justify-content-center">
<Image
shadow={"md"}
rounded={8}
w={214}
h={240}
src={selectedImage}
alt="Selected Image"
/>
</Box>
</Box>
<form
onSubmit={handleSubmit(onSubmit)}
className="col-7 pt-4 overflow-auto p-4"
>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Name
</FormLabel>
<Input
{...register("member_name")}
placeholder="Name"
className="web-text-medium"
size="sm"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.member_name.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Designation
</FormLabel>
<Input
{...register("designation")}
placeholder="Designation"
className="web-text-medium"
size="sm"
/>
{errors.designation && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.designation.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Description
</FormLabel>
<Textarea
{...register("description")}
placeholder="Description"
className="web-text-medium"
size="sm"
rows={2}
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.description && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.description.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Linked In
</FormLabel>
<Input
{...register("linkedin")}
placeholder="Linkedin link"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
{errors.linkedin && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.linkedin.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Display profile
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={"105px"}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("profile_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.profile_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.profile_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end ">
<Button
isLoading={isLoading}
spinner={<Loader01 />}
color={"whitesmoke"}
backgroundColor={"purple.700"}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
rounded={'sm'}
>
Create member
</Button>
</Box>
</form>
</Box>
</Box>
);
};
export default AddComunity;

View File

@@ -0,0 +1,85 @@
/* eslint-disable react/prop-types */
/* eslint-disable no-unused-vars */
import { Text, SimpleGrid, Card, Image, Box, Flex } from "@chakra-ui/react";
// import map from "../../assets/images/map-pin.png";
import linkedin from "../../assets/linkedin.png";
import { Link } from "react-router-dom";
const CommCard = ({ id, imageUrl, name, jobTitle, description, linkdin }) => {
return (
<Box
// height={"100vh"}
background={"#101015"}
backgroundSize={"cover"}
backgroundRepeat={"no-repeat"}
>
<Text display={"flex"}>
<Text
position="relative"
overflow="hidden"
_hover={{
"&::before": {
content: '""',
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
background:
"linear-gradient(to bottom, #f8697a8c 0%, #8d54f86e 86%)",
borderRadius: "5px",
},
}}
>
<Image src={`https://rubix.betadelivery.com/${imageUrl}`} />
</Text>
<Text
position={"relative"}
marginLeft={"10px"}
_before={{
content: "''",
position: "absolute",
bottom: 0,
left: "15px",
height: "84%",
width: "100%",
borderLeft: "1px solid #ffffff70",
}}
>
<Link to={linkdin}>
<img
src={linkedin}
style={{ minWidth: "34px", height: "34px", marginBottom: "10px" }}
/>
</Link>
{/* <img src={games} style={{ minWidth: "34px", height: "34px" }} /> */}
</Text>
</Text>
{/* <Text
color={"#fff"}
fontSize={"16px"}
marginTop={"12px"}
maxWidth={"460px"}
display={"flex"}
>
<img src={map} style={{ marginRight: "10px" }} /> {location}
</Text> */}
<Text
color={"#fff"}
fontSize={"16px"}
maxWidth={"460px"}
>
{name}
</Text>
<Text fontSize={"12px"} color={"#DEDEDE"} margin={"2px 0px"}>
{jobTitle}
</Text>
<Text fontSize={"11px"} color={"#DEDEDE"} margin={"3px 0px"}>
{description}
</Text>
</Box>
);
};
export default CommCard;

View File

@@ -0,0 +1,510 @@
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, ChevronLeftIcon, ChevronRightIcon, 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 {
useDeleteCommunityMutation,
useGetCommunityBannerQuery,
useGetCommunityByIdQuery,
useGetCommunityQuery,
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 "./CommunityCardDisplay";
import CommunityBannerCard from "./CommunityBannerCard";
import Header from "../../Components/Header";
import { TABLE_PAGINATION } from "../../Constants/Paginations";
const Community = () => {
// ====================================================[Hooks]===================================================================
const cancelRef = useRef();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
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 [pageSize, setPageSize] = useState(TABLE_PAGINATION?.size);
const [currentPage, setCurrentPage] = useState(1);
const [displayRange, setDisplayRange] = useState({ start: TABLE_PAGINATION?.page, end: pageSize });
const community = useGetCommunityQuery({ page: currentPage, size: pageSize });
const [deleteCommunity] = useDeleteCommunityMutation();
const [updateCommunityStatus] = useUpdateCommunityStatusMutation();
// ====================================================[Functions]===================================================================
const handleDelete = async (communityId) => {
try {
// Trigger the mutation
setDeleteIsLoading(true);
await deleteCommunity(communityId)
.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) {
setDeleteIsLoading(false);
setDeleteAlert(false);
}
})
.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) => {
try {
// Trigger the mutation
await updateCommunityStatus({ 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 = community?.data?.data?.rows?.filter((item) => {
// Filter by name (case insensitive)
const name = item.member_name;
const searchLower = searchTerm.toLowerCase();
const nameMatches = name.toLowerCase().includes(searchLower);
// Filter by status
const status = item.status;
const statusLower = status ? "active" : "inactive";
const statusMatches =
statusFilter === "all" ||
(statusFilter === "active" && status === true) ||
(statusFilter === "inactive" && status === false);
return nameMatches && statusMatches;
});
// ====================================================[Table Setup]================================================================
const tableHeadRow = [
"Name",
"Discription",
"Linked In",
"Active",
"Created At",
];
const extractedArray = filteredData?.map((item, index) => {
return {
Name: (
<RouterLink
to={`view/${item.id}`}
className="d-flex align-items-center gap-2 pointer"
>
<Avatar
size="sm"
name="KC Reddy"
src={`https://rubix.betadelivery.com/${item.profile_image}`}
/>
<span className="d-flex flex-column">
<Text
as={"span"}
color={"teal.700"}
className="d-flex fw-bold align-items-center web-text-small"
>
{item?.member_name}
</Text>
<span className="d-flex align-items-center web-text-xsmall text-secondary">
{item?.designation}
</span>
</span>
</RouterLink>
),
Discription: (
<Text as={"span"} color={"teal.900"}>
{item?.description}
</Text>
),
"Linked In": (
<Link href={item?.linkedin} isExternal>
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.linkedin}
bg="blue.200"
>
<Tag
variant="solid"
size={"sm"}
borderRadius={2}
colorScheme="linkedin"
>
Linked In
</Tag>
</Tooltip>
</Link>
),
Active: (
<Switch
size={"sm"}
colorScheme="teal"
onChange={() => handleUpdateStatus(item.id)}
isChecked={item.status}
/>
),
"Created At": (
<span className="d-flex justify-content-between align-items-center">
<Text as={"span"} color={"teal.600"} className=" fw-bold">
{formatDate(item?.createdAt)}
</Text>
<Menu>
<MenuButton className="link p-1 rounded-1">
<HiDotsVertical className="rubix-text-dark fs-6" />
</MenuButton>
<Portal>
<MenuList minWidth="80px">
<RouterLink to={`edit/${item.id}`}>
<MenuItem className="web-text-medium">Edit</MenuItem>
</RouterLink>
<RouterLink to={`view/${item.id}`}>
<MenuItem className="web-text-medium">View</MenuItem>
</RouterLink>
<MenuItem
onClick={() => {
setActionId(item.id);
setDeleteAlert(true);
}}
className="web-text-medium"
>
Delete
</MenuItem>
</MenuList>
</Portal>
</Menu>
</span>
),
};
});
// ====================================================[Pagination Setup]================================================================
const paginationPrev = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
updateDisplayRange(currentPage - 1);
}
};
const paginationNext = () => {
const totalPages = Math.ceil(community?.data?.data?.totalItems / pageSize);
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
updateDisplayRange(currentPage + 1);
}
};
const updateDisplayRange = (page) => {
const start = (page - 1) * pageSize + 1;
const end = Math.min(start + pageSize - 1, community?.data?.data?.totalItems);
setDisplayRange({ start, end });
};
return (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
paddingBottom={50}
height={"100vh"}
>
<Header
title={"Community"}
btnTitle={"Create member"}
link={"/community/add-comunity"}
/>
<Box pt={4}>
{/* <HStack
display={"flex"}
justifyContent={"space-between"}
alignItems={"center"}
pe={1}
> */}
{/* <Text
as={"span"}
color={"teal.800"}
className="web-text-large fw-bold"
>
Community banner
</Text>
<RouterLink to="/community/add-banner">
<Button
leftIcon={<AddIcon />}
backgroundColor={"purple.700"}
_hover={{
backgroundColor: "purple.800",
}}
color={"whitesmoke"}
size="sm"
>
Create banner
</Button>
</RouterLink>
</HStack> */}
{/* <Box
display={"flex"}
// bg={"red.500"}
alignItems={"start"}
flexWrap={"wrap"}
justifyContent={"start"}
gap={3}
w={"100%"}
h={"auto"}
spacing="24px"
p={3}
> */}
{/* {communityBanner?.isLoading ? <Skeleton
h={"100%"}
w={"100%"}
borderRadius={6}/> :
<Box
bgImage={`https://rubix.betadelivery.com/${banner?.banner_image}`}
bgSize="cover"
bgPosition="center"
cursor={'pointer'}
h={"100%"}
w={"100%"}
borderRadius={6}
display={"flex"}
p={10}
justifyContent={"center"}
alignItems={"start"}
flexDirection={"column"}
position={'relative'}
>
<Text
mt={10}
w={400}
color={"white"}
className="fw-bolder"
as={"h1"}
>
{banner?.Heading}
</Text>
<Text
w={600}
color={"white"}
as={"p"}
className="fw-bolder web-text-large"
>
{banner?.sub_heading}
</Text>
<WebButton title={banner?.CTO_button_title} />
<Text opacity={0.3} position={'absolute'} bottom={0} right={4} className="web-text-small text-white">Last update: {formatDate(banner?.createdAt)}</Text>
</Box>} */}
{/* {communityBanner?.isLoading
? Array.from({ length: 3 }).map((_, index) => (
<Skeleton rounded={"md"} w={352} height={150} />
))
: banner?.map(
({
id,
CTO_button_title,
banner_image,
Heading,
createdAt,
sub_heading,
}) => (
<CommunityBannerCard
key={id}
bgImage={banner_image}
subHeading={sub_heading}
heading={Heading}
createdAt={createdAt}
ctoBtnTitle={CTO_button_title}
/>
)
)}
</Box>*/}
</Box>
{/* ====================================================[ Top bar ]================================================================ */}
{/* <Divider /> */}
<Box
bg="white.500"
>
{/* <HStack>
<Text color={"teal.800"} className="web-text-large fw-bold">
Community cards
</Text>
</HStack> */}
<HStack
display={"flex"}
justifyContent={"space-between"}
ps={1}
pe={1}
pb={4}
spacing="24px"
>
<Input
type="search"
width={300}
placeholder="Search..."
size="sm"
rounded="sm"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<HStack>
<Select
className="pointer web-text-small"
width={"90px"}
rounded="sm"
size="sm"
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
>
<option value="all">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</Select>
<Select
className="pointer web-text-small"
width={"90px"}
rounded="sm"
size="sm"
value={pageSize}
onChange={(e) => setPageSize(e.target.value)}
>
<option value={pageSize}>{pageSize}</option>
{/* <option value={20}>20 rows</option>
<option value={30}>30 rows</option> */}
</Select>
<HStack>
<ChevronLeftIcon
onClick={paginationPrev}
className=" link rounded-3 pointer"
/>
<Text className="web-text-medium" as={"span"}>
{displayRange.start} - {displayRange.end} of{" "}
{community?.data?.data?.totalItems}
</Text>
<ChevronRightIcon
onClick={paginationNext}
className=" link rounded-3 pointer"
/>
</HStack>
</HStack>
</HStack>
</Box>
{/* ====================================================[ Table ]================================================================ */}
<DataTable
emptyMessage={"We don't have any blog for this author"}
tableHeadRow={tableHeadRow}
data={extractedArray}
isLoading={community?.isLoading}
/>
{/* <Divider /> */}
{/* <HStack>
<Text color={"teal.800"} className="web-text-large fw-bold">
Community cards
</Text>
</HStack>
<RouterLink to="/community-table-view">
<CommunityCardDisplay />
</RouterLink> */}
{/* ====================================================[ Alert ]================================================================ */}
<CustomAlertDialog
onClose={() => setDeleteAlert(false)}
isOpen={deleteAlert}
alertHandler={() => handleDelete(actionId)}
message={"Are you sure you want to delete member?"}
isLoading={deleteIsLoading}
/>
</Box>
);
};
export default Community;

View File

@@ -0,0 +1,53 @@
import { Box, Button, Card, CardBody, CardHeader, Heading, Text } from "@chakra-ui/react";
import React from "react";
import WebButton from "../../Components/WebButton";
import { formatDate } from "../../Components/Functions/UTCConvertor";
const CommunityBannerCard = ({ bgImage, subHeading, heading, ctoBtnTitle, createdAt }) => {
return (
<Card
bgImage={`https://rubix.betadelivery.com/${bgImage}`}
color={'teal.900'}
bgSize="cover"
bgPosition="center"
w={"100%"}
h={"100%"}
size={"md"}
shadow={'md'}
overflow={'hidden'}
position={'relative'}
rounded={"lg"}
>
<CardBody
position={'absolute'}
h={"60%"}
w={'100%'}
bottom={0}
backgroundColor={'#ffffffb4'}
backdropFilter='blur(1px)'
>
<Heading className="web-text-large " mb={0} size="md">{heading}</Heading>
<Box mb={0} display={"flex"} alignItems={"center"} w={250}>
<Text className="web-text-medium" as={"span"} isTruncated={true}>
{subHeading}
</Text>
</Box>
<Button colorScheme="teal" rounded={'sm'} size={'xs'}>{ctoBtnTitle}</Button>
</CardBody>
<span
className="web-text-xsmall fw-bold"
style={{
position:"absolute",
bottom:4,
right:10,
opacity:0.5
}}>{formatDate(createdAt)}</span>
</Card>
);
};
export default CommunityBannerCard;

View File

@@ -0,0 +1,73 @@
import React from 'react'
import { useGetCommunityQuery } from '../../Services/api.service';
import CommCard from './CommCard';
import { Box, Container, SimpleGrid, Text } from '@chakra-ui/react';
const CommunityCardDisplay = () => {
const community = useGetCommunityQuery();
console.log(community.data?.data?.rows);
const communityData = community.data?.data?.rows
console.log(communityData);
return (
<Box rounded={4} backgroundColor={"#101015"}>
<Container
cursor={'pointer'}
maxW={"1200px"}
padding={"0rem"}
paddingBottom={"2rem"}
display={'flex'}
alignItems={'center'}
flexDirection={'column'}
sx={{
"@media (max-width: 1024px)": {
padding: "3rem",
},
"@media (max-width: 435px)": {},
}}
>
<Text
as={"h2"}
paddingTop={"1rem"}
paddingBottom={"1rem"}
fontWeight={700}
fontSize={"30px"}
textAlign={"left"}
textTransform={"capitalize"}
color={"#fff"}
sx={{
"@media (max-width: 435px)": {
fontSize: "35px",
},
"@media (max-width: 375px)": {
fontSize: "28px",
textAlign: "center",
},
}}
>
Rubix Community
</Text>
<SimpleGrid
w={'100%'}
p={4}
spacing={"20px"}
templateColumns="repeat(auto-fill, minmax(200px, 1fr))"
>
{communityData?.map((item) => (
<CommCard
key={item.id}
location={item.member_name}
name={item.member_name}
jobTitle={item.designation}
description={item.description}
linkdInd={item.linkedin}
imageUrl={item.profile_image}
/>
))}
</SimpleGrid>
</Container>
</Box>
)
}
export default CommunityCardDisplay

View File

@@ -0,0 +1,9 @@
import React from 'react'
const CommunityCardsTableView = () => {
return (
<div>CommunityCardsTableView</div>
)
}
export default CommunityCardsTableView

View File

@@ -0,0 +1,413 @@
import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
useGetCommunityByIdQuery,
useUpdateCommunityMutation,
} from "../../Services/api.service";
import { addCommunitySchema, 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/fallBackImage.png";
import Header from "../../Components/Header";
const ComunityEditPage = () => {
const { id } = useParams();
const toast = useToast();
const navigate = useNavigate();
const { data, error, isLoading } = useGetCommunityByIdQuery(id);
const [isLoadingEdit, setIsLoadingEdit] = useState(false);
const [selectedImage, setSelectedImage] = useState();
const [updateCommunity] = useUpdateCommunityMutation();
const {
register,
handleSubmit,
reset,
formState: { errors },
setValue,
} = useForm({
resolver: yupResolver(addCommunitySchema),
defaultValues: {
member_name: "",
designation: "",
description: "",
linkedin: "",
profile_image:null
},
});
useEffect(() => {
if (data?.data) {
setSelectedImage(
`https://rubix.betadelivery.com/${data?.data?.profile_image}`
);
setValue("member_name", data?.data?.member_name);
setValue("designation", data?.data?.designation);
setValue("description", data?.data?.description);
setValue("linkedin", data?.data?.linkedin);
setValue("profile_image", data?.data?.profile_image);
}
}, [data, setValue]);
const onSubmit = async (formData) => {
setIsLoadingEdit(true);
const form = new FormData();
form.append("member_name", formData.member_name);
form.append("designation", formData.designation);
form.append("description", formData.description);
form.append("linkedin", formData.linkedin);
if (formData.profile_image[0]) {
form.append("profile_image", formData.profile_image[0]);
}
if (formData?.profile_image === data?.data?.profile_image) {
console.log("hit");
form.delete("profile_image");
}
await updateCommunity({ 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("/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 ? (
<FullscreenLoaders />
) : (
<Box
{...OPACITY_ON_LOAD}
w={"100%"}
h={"100vh"}
overflowY={"scroll"}
display={"flex"}
flexDirection={'column'}
>
<Header
title={"Community"}
/>
<Box display={'flex'}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Members Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Display profile
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box boxSize="sm"
className="d-flex w-100 justify-content-center flex-column align-items-center gap-3">
<Image
shadow={"md"}
rounded={8}
w={214}
h={240}
src={selectedImage}
alt="Selected Image"
/>
<Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
<form
className="col-7 pt-4 overflow-auto p-4"
onSubmit={handleSubmit(onSubmit)}
>
{/* <Switch
size={"sm"}
colorScheme="teal"
onChange={() => handleUpdateStatus(item.id)}
isChecked={data?.data?.status}
/> */}
<Box className="web-text-large fw-bold mb-2 rubix-text-dark">
Status
</Box>
{data?.data?.status ? <Tag position={'sticky'} right={10} size={"sm"} variant="solid" colorScheme="teal">
Active
</Tag> : <Tag position={'sticky'} right={10} size={"sm"} variant="solid" colorScheme="red">
Inactive
</Tag>}
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Name
</FormLabel>
<Input
{...register("member_name")}
placeholder="Name"
className="web-text-medium"
size="sm"
name="member_name"
type="text"
id="member_name"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.member_name.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Designation
</FormLabel>
<Input
{...register("designation")}
placeholder="Designation"
className="web-text-medium"
size="sm"
id="designation"
name="designation"
/>
{errors.designation && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.designation.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Description
</FormLabel>
<Textarea
{...register("description")}
placeholder="Description"
className="web-text-medium"
size="sm"
rows={2}
maxLength={90}
id="description"
name="description"
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.description && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.description.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Linked In
</FormLabel>
<Input
{...register("linkedin")}
placeholder="Linkedin link"
className="web-text-medium"
size="sm"
id="linkedin"
name="linkedin"
/>
{errors.linkedin && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.linkedin.message}
</span>
)}
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
</FormControl>
<FormControl className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Display profile
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={"105px"}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("profile_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.profile_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.profile_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end ">
<Button
isLoading={isLoadingEdit}
spinner={<Loader01 />}
color={'whitesmoke'}
backgroundColor={'purple.700'}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
rounded={'sm'}
>
Save edit
</Button>
</Box>
</form>
</Box>
</Box>
);
};
export default ComunityEditPage;

View File

@@ -0,0 +1,170 @@
import React, { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import {
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 ComunityViewPage = () => {
const { id } = useParams();
const { data, error, isLoading } = useGetCommunityByIdQuery(id);
const member = data?.data;
return isLoading ? (
<FullscreenLoaders />
) : (
<Box
{...OPACITY_ON_LOAD}
w={"100%"}
h={"100vh"}
overflowY={"scroll"}
display={"flex"}
flexDirection={"column"}
>
<Header
title={"Community"}
btnTitle={"Edit community"}
link={`/community/edit/${id}`}
/>
<Box display={'flex'}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Members Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Display profile
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex h-75 w-100 justify-content-start flex-column align-items-center gap-3"
>
<Image
shadow={"md"}
rounded={8}
w={214}
h={240}
src={`https://rubix.betadelivery.com/${member?.profile_image}`}
alt="Selected Image"
/>
{/* <Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button> */}
</Box>
</Box>
<Box className="col-7 pt-4 overflow-auto p-4">
{data?.data?.status ? (
<Tag
position={"absolute"}
right={10}
size={"sm"}
variant="solid"
colorScheme="teal"
>
Active
</Tag>
) : (
<Tag
position={"absolute"}
right={10}
size={"sm"}
variant="solid"
colorScheme="red"
>
Inactive
</Tag>
)}
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">Name</Box>
<Box className="web-text-medium text-secondary">
{member?.member_name}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Designation
</Box>
<Box className="web-text-medium text-secondary">
{member?.designation}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Description
</Box>
<Box className="web-text-medium text-secondary">
{member?.description}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">Linkedin</Box>
<Box className="web-text-medium text-secondary">
{member?.linkedin}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Created At
</Box>
<Box className="web-text-medium text-secondary">
{formatDate(member?.createdAt)}
</Box>
</Box>
<Box isRequired className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Updated At
</Box>
<Box className="web-text-medium text-secondary">
{formatDate(member?.updatedAt)}
</Box>
</Box>
<Divider />
</Box>
</Box>
</Box>
);
};
export default ComunityViewPage;

18
src/Pages/Events.jsx Normal file
View File

@@ -0,0 +1,18 @@
import { Box } from '@chakra-ui/react'
import { OPACITY_ON_LOAD } from '../Layout/animations'
const Events = () => {
return (
<Box
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
height={"100vh"}
backgroundColor={"teal"}
color={"whitesmoke"}
{...OPACITY_ON_LOAD}
>Events</Box>
)
}
export default Events

236
src/Pages/Login.jsx Normal file
View File

@@ -0,0 +1,236 @@
import { useNavigate } from "react-router-dom";
import Input01 from "../Components/Inputs/Input01";
import logo from "../assets/logo.png";
import { useDispatch, useSelector } from "react-redux";
import { loginUser } from "../Redux/Slice/auth";
import { useContext, useEffect, useState } from "react";
import Button01 from "../Components/Buttons/Button01";
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
import { TiWarning } from "react-icons/ti";
import Loader01 from "../Components/Loaders/Loader01";
import Ellipse from "../assets/Ellipse-38.png";
import Ellipse1 from "../assets/Ellipse-37.png";
import Ellipse2 from "../assets/Ellipse-39.png";
import {
Button,
FormControl,
FormLabel,
Input,
useToast,
} from "@chakra-ui/react";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import Cookies from 'js-cookie';
import { validationSchema } from "../Validations/Validations";
const Login = () => {
const { isAuthenticate, setIsAuthenticate } = useContext(GlobalStateContext);
const toast = useToast();
// const { isAuthenticate } = useSelector((state) => state?.auth);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
if (isAuthenticate) {
navigate("/");
}
}, [navigate, isAuthenticate]);
const onSubmit = (value) => {
setIsLoading(true);
if (value.name === "Admin" && value.password === "Admin") {
return setTimeout(() => {
// dispatch(loginUser(true));
setIsAuthenticate(true)
setIsLoading(false);
toast({
title: "Logged In",
status: "success",
isClosable: true,
});
Cookies.set('isAuthenticated', true, { expires: 7 });
navigate("/");
}, 2000); // 3-second delay
} else {
return setTimeout(() => {
// dispatch(loginUser(true));
setIsAuthenticate(false)
setIsLoading(false);
toast({
title: `Invalid Credentials`,
status: "error",
isClosable: true,
})
reset();
navigate("/login");
}, 2000);
}
};
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm({
resolver: yupResolver(validationSchema),
});
return (
<div
style={{
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
overflow: "hidden",
}}
className="rubix-primary-background"
>
<form
onSubmit={handleSubmit(onSubmit)}
style={{
width: "90%",
maxWidth: "450px",
height: "auto",
background: "#fff",
borderRadius: "10px",
padding: "1.5rem",
// boxShadow: "0 24px 64px #26214a1a",
boxShadow:
"rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px",
zIndex: 2,
}}
>
<div className="d-flex flex-column mb-4">
<img
style={{
left: "8px",
top: "8px",
// width: "10%",
maxWidth: "120px",
}}
src={logo}
alt="img"
className="mb-4"
/>
<span className="fw-bold fs-2 rubix-text-dark text-start">
Welcome back.
</span>
<span className="fw-500 web-text-large text-secondary text-start">
Login to manage rubix.
</span>
</div>
<FormControl className=" mb-3">
<FormLabel className="rubix-text-dark ps-1 web-text-medium fw-bold">
Owner name <span className="text-danger">*</span>
</FormLabel>
<Input
{...register("name")}
focusBorderColor="purple.500"
type="text"
name="name"
variant="filled"
placeholder="Owner name"
size="lg"
className="web-text-medium"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.name.message}
</span>
)}
</FormControl>
<FormControl className="mb-4">
<FormLabel className="rubix-text-dark ps-1 web-text-medium fw-bold">
Password <span className="text-danger">*</span>
</FormLabel>
<Input
{...register("password")}
focusBorderColor="purple.500"
type="password"
name="password"
variant="filled"
placeholder="Password"
size="lg"
className="web-text-medium"
/>
{errors.password && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.password.message}
</span>
)}
</FormControl>
<Button
isLoading={isLoading}
spinner={<Loader01 />}
type="submit"
className="w-100 primary-btn"
color={"whitesmoke"}
colorScheme="purple.500"
size="lg"
>
Log In
</Button>
</form>
<div
style={{
position: "absolute",
bottom: "0%",
fontSize: "13px",
color: "#919191",
textAlign: "center",
width: "100%",
zIndex: 2,
}}
>
Rubix v1.0.0
</div>
<img
style={{
position: "absolute",
top: 0,
right: 0,
// width:100
}}
src={Ellipse}
alt="bg-img"
/>
<img
style={{
position: "absolute",
top: 0,
right: 0,
// width:400
}}
src={Ellipse1}
alt="bg-img"
/>
<img
style={{
position: "absolute",
bottom: 0,
left: 0,
}}
src={Ellipse2}
alt="bg-img"
/>
</div>
);
};
export default Login;

370
src/Pages/News/AddNews.jsx Normal file
View File

@@ -0,0 +1,370 @@
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,
addNews,
} from "../../Validations/Validations";
import {
useCreateCommunityBannerMutation,
useCreateCommunityMutation,
useCreateNewsMutation,
useGetCommunityQuery,
} from "../../Services/api.service";
import { useNavigate } from "react-router-dom";
import Loader01 from "../../Components/Loaders/Loader01";
import Header from "../../Components/Header";
const AddNews = () => {
const toast = useToast();
const navigate = useNavigate();
const getCommunityQuery = useGetCommunityQuery();
const [createNews] = useCreateNewsMutation(); // 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(addNews),
});
const onSubmit = async (data) => {
const date = new Date(data?.release_date).toUTCString();
try {
setIsLoading(true);
const formData = new FormData();
formData.append("title", data.title);
formData.append("meta_description", data.meta_description);
formData.append("content", data.content);
formData.append("release_date", date);
if (data.banner_image[0]) {
formData.append("banner_image", data.banner_image[0]);
}
// Trigger the mutation
createNews(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("/news");
} 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 (
<Box
{...OPACITY_ON_LOAD}
w={"100%"}
h={"100vh"}
className="overflow-auto "
display={"flex"}
flexDirection={"column"}
>
<Header title={"News"} />
<Box className="d-flex">
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Banner Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Banner Image
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex w-100 justify-content-center flex-column align-items-center gap-3"
>
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={selectedImage}
alt="Selected Image"
/>
<Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
<form
onSubmit={handleSubmit(onSubmit)}
className="col-7 pt-4 mb-3 overflow-auto p-4"
>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Title
</FormLabel>
<Input
{...register("title")}
placeholder="Title"
className="web-text-medium"
size="sm"
/>
{errors.name && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.title.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Description
</FormLabel>
<Input
{...register("meta_description")}
placeholder="Sub Heading"
className="web-text-medium"
size="sm"
/>
{errors.meta_description && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.meta_description.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Content
</FormLabel>
<Textarea
{...register("content")}
placeholder="Content"
className="web-text-medium"
size="sm"
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.content && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.content.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Release date
</FormLabel>
<Input
type="date"
{...register("release_date")}
placeholder="Button link"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
Please share proper linked in link here.
</FormHelperText>
{errors.release_date && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.release_date.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Banner image
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={"105px"}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("banner_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.banner_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.banner_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end mb-5">
<Button
isLoading={isLoading}
spinner={<Loader01 />}
color={"whitesmoke"}
backgroundColor={"purple.700"}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
>
Create
</Button>
</Box>
</form>
</Box>
</Box>
);
};
export default AddNews;

426
src/Pages/News/EditNews.jsx Normal file
View File

@@ -0,0 +1,426 @@
import {
Box,
Button,
Divider,
FormControl,
FormHelperText,
FormLabel,
Heading,
Image,
Input,
Stack,
Tag,
Textarea,
useToast,
} from "@chakra-ui/react";
import React, { useEffect, useState } from "react";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import Header from "../../Components/Header";
import { useNavigate, useParams } from "react-router-dom";
import {
useGetNewsByIdQuery,
useUpdateNewsMutation,
} from "../../Services/api.service";
import { addNews, editNews } from "../../Validations/Validations";
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
import fallbackImage from "../../assets/ultp-fallback-img.webp";
import { motion } from "framer-motion";
import Loader01 from "../../Components/Loaders/Loader01";
import { formatDate } from "../../Components/Functions/UTCConvertor";
import { TiWarning } from "react-icons/ti";
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
const EditNews = () => {
const { id } = useParams();
const toast = useToast();
const navigate = useNavigate();
const { data, error, isLoading } = useGetNewsByIdQuery(id);
const [isLoadingEdit, setIsLoadingEdit] = useState(false);
const [selectedImage, setSelectedImage] = useState(fallbackImage);
const [largeImageData, setLargeImageData] = useState(null);
const [updateNews] = useUpdateNewsMutation();
const {
register,
handleSubmit,
reset,
formState: { errors },
setValue,
} = useForm({
resolver: yupResolver(editNews),
defaultValues: {
title: "",
release_date: "",
meta_description: "",
content: "",
banner_image: null,
},
});
useEffect(() => {
if (data?.data) {
setSelectedImage(
`https://rubix.betadelivery.com/${data?.data?.banner_image}`
);
setValue("title", data?.data?.title);
setValue("meta_description", data?.data?.meta_description);
setValue("release_date", data?.data?.release_date);
setValue("content", data?.data?.content);
setValue("banner_image", data?.data?.banner_image);
}
}, [data, setValue]);
console.log(errors);
const onSubmit = async (data) => {
setIsLoadingEdit(true);
console.log(largeImageData);
const form = new FormData();
form.append("title", data?.title);
form.append("meta_description", data?.meta_description);
form.append("content", data?.content);
form.append("release_date", data?.release_date);
if (largeImageData !== null) {
form.append("banner_image", largeImageData);
}
await updateNews({ 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("/news");
// setDeleteAlert(false);
}
})
.catch((error) => {
console.error("Error creating community:", error);
setIsLoadingEdit(false);
// setDeleteIsLoading(false);
// setDeleteAlert(false);
});
reset()
};
const handleImageChange = (e) => {
const file = e.target.files[0];
setLargeImageData(file);
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setSelectedImage(reader.result);
};
reader.readAsDataURL(file);
}
};
return isLoading ? (
<FullscreenLoaders />
) : (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
paddingBottom={50}
height={"100vh"}
>
<Header title={"News"} />
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
Members Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
Display profile
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex w-100 justify-content-center flex-column align-items-center gap-3"
>
<Image
shadow={"md"}
rounded={8}
w={"100%"}
h={240}
src={selectedImage}
alt="Selected Image"
/>
{selectedImage === fallbackImage || largeImageData === null ? (
""
) : (
<Box display={"flex"} flexDirection={"column"} w={"100%"}>
<span className="web-text-small">
{largeImageData && largeImageData?.name}
</span>
<span className="web-text-small text-secondary fst-italic">
{largeImageData &&
(largeImageData?.size / (1024 * 1024)).toFixed(2)}{" "}
mb
</span>
</Box>
)}
<Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button>
</Box>
</Box>
<form
className="col-7 pt-4 overflow-auto p-4"
onSubmit={handleSubmit(onSubmit)}
>
<Box className="web-text-large fw-bold mb-2 rubix-text-dark">
Status
</Box>
{data?.data?.status ? (
<Tag
position={"sticky"}
right={10}
size={"sm"}
variant="solid"
colorScheme="teal"
>
Active
</Tag>
) : (
<Tag
position={"sticky"}
right={10}
size={"sm"}
variant="solid"
colorScheme="red"
>
Inactive
</Tag>
)}
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Title
</FormLabel>
<Input
{...register("title")}
placeholder="Name"
className="web-text-medium"
size="sm"
name="title"
type="text"
id="title"
/>
{errors.title && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.title.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Description
</FormLabel>
<Input
{...register("meta_description")}
placeholder="Name"
className="web-text-medium"
size="sm"
name="meta_description"
type="text"
id="meta_description"
/>
{errors.meta_description && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.meta_description.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Content
</FormLabel>
<Textarea
{...register("content")}
placeholder="Content"
className="web-text-medium"
size="sm"
maxLength={90}
/>
<FormHelperText className="web-text-small">
Maximum characters must be 100 characters.
</FormHelperText>
{errors.content && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " /> {errors.content.message}
</span>
)}
</FormControl>
<FormControl isRequired className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Release date
</FormLabel>
<Input
type="date"
{...register("release_date")}
placeholder="Button link"
className="web-text-medium"
size="sm"
/>
<FormHelperText className="web-text-small">
{formatDate(data?.data?.release_date)}
</FormHelperText>
{errors.release_date && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.release_date.message}
</span>
)}
</FormControl>
<FormControl className="mb-3">
<FormLabel className="web-text-large fw-bold rubix-text-dark">
Display profile
</FormLabel>
{/* <ImageDropBox /> */}
<Box
borderColor="gray.300"
borderStyle="dashed"
borderWidth="2px"
rounded="md"
shadow="sm"
role="group"
transition="all 150ms ease-in-out"
_hover={{
shadow: "md",
}}
as={motion.div}
initial="rest"
animate="rest"
whileHover="hover"
height={"105px"}
className="pointer"
>
<Box position="relative" height="100%" width="100%">
<Box
position="absolute"
top="0"
left="0"
height="100%"
width="100%"
display="flex"
flexDirection="column"
>
<Stack
height="100%"
width="100%"
display="flex"
alignItems="center"
justify="center"
>
<span
className="d-flex flex-column align-items-center pointer"
spacing="1"
>
<Heading
fontSize="lg"
color="gray.700"
fontWeight="bold"
cursor={"pointer"}
>
Drop images here
</Heading>
<span
fontWeight="light"
className="web-text-large text-secondary text-center pointer"
>
or click to upload
</span>
</span>
</Stack>
</Box>
<Input
{...register("profile_image")}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={handleImageChange}
onDrop={handleImageChange}
// onDragEnter={startAnimation}
// onDragLeave={stopAnimation}
/>
</Box>
</Box>
{errors.profile_image && (
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
<TiWarning className="fw-bold fs-5 " />{" "}
{errors.profile_image.message}
</span>
)}
<FormHelperText className="web-text-small">
Maximum limit of image is 5mb.
</FormHelperText>
</FormControl>
<Box className=" d-flex justify-content-end ">
<Button
isLoading={isLoadingEdit}
spinner={<Loader01 />}
color={"whitesmoke"}
backgroundColor={"purple.700"}
_hover={{
backgroundColor: "purple.800",
}}
type="submit"
size="sm"
rounded={"sm"}
>
Save edit
</Button>
</Box>
</form>
</Box>
</Box>
);
};
export default EditNews;

View File

@@ -0,0 +1,19 @@
import React from "react";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import { Box } from "@chakra-ui/react";
import Header from "../../Components/Header";
const HelpAndSupport = () => {
return (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
paddingBottom={50}
height={"100vh"}
>
<Header title={"Help And Support"} />
</Box>
);
};
export default HelpAndSupport;

319
src/Pages/News/News.jsx Normal file
View File

@@ -0,0 +1,319 @@
import React, { useState } from "react";
import {
Box,
Text,
Tooltip,
HStack,
Input,
Select,
Menu,
MenuButton,
MenuList,
MenuItem,
Switch,
Portal,
useToast,
} from "@chakra-ui/react";
import { ChevronLeftIcon, ChevronRightIcon } from "@chakra-ui/icons";
import DataTable from "../../Components/DataTable/DataTable";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import { Link as RouterLink } from "react-router-dom";
import {
useDeleteNewsMutation,
useGetNewsQuery,
useUpdateNewsStatusMutation,
} from "../../Services/api.service";
import { HiDotsVertical } from "react-icons/hi";
import { formatDate } from "../../Components/Functions/UTCConvertor";
import CustomAlertDialog from "../../Components/CustomAlertDialog";
import Header from "../../Components/Header";
import { TABLE_PAGINATION } from "../../Constants/Paginations";
const News = () => {
// ====================================================[Hooks]===================================================================
const toast = useToast();
const [deleteAlert, setDeleteAlert] = useState(false);
const [actionId, setActionId] = useState(null);
const [deleteIsLoading, setDeleteIsLoading] = useState(false);
// ====================================================[Constants]===================================================================
const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState("all");
const [pageSize, setPageSize] = useState(TABLE_PAGINATION?.size);
const [currentPage, setCurrentPage] = useState(TABLE_PAGINATION?.page);
const [displayRange, setDisplayRange] = useState({ start: TABLE_PAGINATION?.page, end: pageSize });
// ====================================================[RTK Hooks]===================================================================
const news = useGetNewsQuery({ page: currentPage, size: pageSize });
const [deleteNews] = useDeleteNewsMutation();
const [updateNewsStatus] = useUpdateNewsStatusMutation();
// ====================================================[Functions]===================================================================
const handleDelete = async (communityId) => {
try {
// Trigger the mutation
setDeleteIsLoading(true);
await deleteNews(communityId)
.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);
}
})
.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) => {
try {
// Trigger the mutation
await updateNewsStatus({ 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 = news?.data?.data?.rows?.filter((item) => {
// Filter by name (case insensitive)
const name = item.title;
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 = [
"Title",
"Discription",
"Content",
"Release Data",
"Active",
"Created At",
];
const extractedArray = filteredData?.map((item, index) => {
return {
Title: item?.title,
Discription: (
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.meta_description}
bg="blue.200"
>
<Box display={"flex"} alignItems={"center"} w={180}>
<Text as={"span"} isTruncated={true}>
{item?.meta_description}
</Text>
</Box>
</Tooltip>
),
Content: (
<Tooltip
className="rounded-2 web-text-xsmall"
width={"fit-content"}
placement="top"
hasArrow
label={item?.content}
bg="blue.200"
>
<Box display={"flex"} alignItems={"center"} w={350}>
<Text as={"span"} isTruncated={true}>
{item?.content}
</Text>
</Box>
</Tooltip>
),
"Release Data": (
<Text as={"span"} color={"teal.600"} className=" fw-bold">
{formatDate(item?.release_date)}
</Text>
),
Active: (
<Switch
size={"sm"}
colorScheme="teal"
onChange={() => handleUpdateStatus(item.id)}
isChecked={item.status}
/>
),
"Created At": (
<span className="d-flex justify-content-between align-items-center">
<Text as={"span"} color={"teal.600"} className=" fw-bold">
{formatDate(item?.createdAt)}
</Text>
<Menu>
<MenuButton className="link p-1 rounded-1">
<HiDotsVertical className="rubix-text-dark fs-6" />
</MenuButton>
<Portal>
<MenuList minWidth="80px">
<RouterLink to={`edit/${item.id}`}>
<MenuItem className="web-text-medium">Edit</MenuItem>
</RouterLink>
<RouterLink to={`view/${item.id}`}>
<MenuItem className="web-text-medium">View</MenuItem>
</RouterLink>
<MenuItem
onClick={() => {
setActionId(item.id);
setDeleteAlert(true);
}}
className="web-text-medium"
>
Delete
</MenuItem>
</MenuList>
</Portal>
</Menu>
</span>
),
};
});
// ====================================================[Pagination Setup]================================================================
const paginationPrev = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
updateDisplayRange(currentPage - 1);
}
};
const paginationNext = () => {
const totalPages = Math.ceil(news?.data?.data?.totalItems / pageSize);
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
updateDisplayRange(currentPage + 1);
}
};
const updateDisplayRange = (page) => {
const start = (page - 1) * pageSize + 1;
const end = Math.min(start + pageSize - 1, news?.data?.data?.totalItems);
setDisplayRange({ start, end });
};
return (
<Box
{...OPACITY_ON_LOAD}
overflowY={"scroll"}
overflowX={"hidden"}
paddingBottom={50}
height={"100vh"}
>
{/* ====================================================[ Top bar ]================================================================ */}
<Header title={"News"} btnTitle={"Create news"} link={"/news/add-news"} />
<Box pt={4}>
<HStack
display={"flex"}
justifyContent={"space-between"}
ps={1}
pe={1}
pb={4}
spacing="24px"
>
<Input
type="search"
width={300}
rounded="sm"
placeholder="Search..."
size="sm"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<HStack>
<Select
className="pointer web-text-small"
width={"90px"}
rounded="sm"
size="sm"
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
>
<option value="all">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</Select>
<Select
className="pointer web-text-small"
width={"90px"}
rounded="sm"
size="sm"
value={pageSize}
onChange={(e) => setPageSize(e.target.value)}
>
<option value={pageSize}>{pageSize} rows</option>
{/* <option value={20}>20 rows</option>
<option value={30}>30 rows</option> */}
</Select>
<HStack>
<ChevronLeftIcon
onClick={paginationPrev}
className=" link rounded-3 pointer"
/>
<Text className="web-text-medium" as={"span"}>
{displayRange.start} - {displayRange.end} of{" "}
{news?.data?.data?.totalItems}
</Text>
<ChevronRightIcon
onClick={paginationNext}
className=" link rounded-3 pointer"
/>
</HStack>
</HStack>
</HStack>
</Box>
{/* ====================================================[ Table ]================================================================ */}
<DataTable
emptyMessage={"We don't have any blog for this author"}
tableHeadRow={tableHeadRow}
data={extractedArray}
isLoading={news?.isLoading}
/>
{/* ====================================================[ Alert ]================================================================ */}
<CustomAlertDialog
onClose={() => setDeleteAlert(false)}
isOpen={deleteAlert}
alertHandler={() => handleDelete(actionId)}
message={"Are you sure you want to delete member?"}
isLoading={deleteIsLoading}
/>
</Box>
);
};
export default News;

156
src/Pages/News/ViewNews.jsx Normal file
View File

@@ -0,0 +1,156 @@
import { Box, Divider, Image, Tag, useToast } from "@chakra-ui/react";
import React from "react";
import Header from "../../Components/Header";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import {
useGetNewsByIdQuery,
useGetNewsQuery,
} from "../../Services/api.service";
import { useNavigate, useParams } from "react-router-dom";
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
import { formatDate } from "../../Components/Functions/UTCConvertor";
const ViewNews = () => {
const { id } = useParams();
const toast = useToast();
const navigate = useNavigate();
const { data, error, isLoading } = useGetNewsByIdQuery(id);
console.log(isLoading);
// const { data, error, isLoading } = useGetNewsQuery();
const news = data?.data;
console.log(data);
if (isLoading) {
return <FullscreenLoaders />;
}
return (
<Box
{...OPACITY_ON_LOAD}
w={"100%"}
h={"100vh"}
className="overflow-auto "
display={"flex"}
flexDirection={"column"}
>
<Header
title={"News"}
btnTitle={'Edit news'}
link={`/news/edit/${id}`}
/>
<Box display={"flex"}>
<Box className="col-5 d-flex flex-column gap-2 pt-4">
<span className="web-text-large fw-bold rubix-text-dark">
News Info
</span>
<span className="web-text-medium text-secondary">
Select the platform for which you need to create this campaign.
</span>
<Divider />
<span className="web-text-large fw-bold rubix-text-dark">
News banner image
</span>
<span className="web-text-medium text-secondary mb-4">
Below is the profile that will be displayed on the community page.
</span>
<Box
boxSize="sm"
className="d-flex h-auto w-100 justify-content-start flex-column align-items-center gap-3"
>
<Image
shadow={"md"}
rounded={8}
w={500}
h={240}
src={`https://rubix.betadelivery.com/${news?.banner_image}`}
alt="Selected Image"
/>
{/* <Button
onClick={() => setSelectedImage(fallbackImage)}
backgroundColor="red.400"
color={"whitesmoke"}
transition={"0.5s"}
_hover={{
backgroundColor: "red.500",
}}
size="xs"
>
Remove
</Button> */}
</Box>
</Box>
<Box className="col-7 pt-4 p-4">
<Box>
<Box className="web-text-large fw-bold mb-1 rubix-text-dark">
Status
</Box>
{news?.status ? (
<Tag size={"sm"} borderRadius="full" colorScheme="teal">
Active
</Tag>
) : (
<Tag size={"sm"} borderRadius="full" colorScheme="red">
Inactive
</Tag>
)}
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">Title</Box>
<Box className="web-text-medium text-secondary">{news?.title}</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
news description
</Box>
<Box className="web-text-medium text-secondary">
{news?.meta_description}
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Content
</Box>
<Box className="web-text-medium text-secondary">
{news?.content}
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Created at
</Box>
<Box className="web-text-medium text-secondary">
{formatDate(news?.createdAt)}
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Updated at
</Box>
<Box className="web-text-medium text-secondary">
{formatDate(news?.updatedAt)}
</Box>
</Box>
<Box className="mb-3">
<Box className="web-text-large fw-bold rubix-text-dark">
Release date
</Box>
<Box className="web-text-medium text-secondary">
{formatDate(news?.release_date)}
</Box>
</Box>
</Box>
</Box>
</Box>
);
};
export default ViewNews;

View File

@@ -0,0 +1,22 @@
import { Box, Image, Text } from '@chakra-ui/react'
import noInternet from "../assets/Error.svg"
const NoInternetScreen = () => {
return (
<Box
w={'100vw'}
h={'100vh'}
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
flexDirection={'column'}
gap={5}
>
<Image src={noInternet} w={300} />
<Text color={'blue.800'} as={'span'} className='fw-bold'>No Internet !</Text>
</Box>
)
}
export default NoInternetScreen

8
src/Pages/NotFound.jsx Normal file
View File

@@ -0,0 +1,8 @@
const NotFound = () => {
return (
<div>NotFound</div>
)
}
export default NotFound

19
src/Pages/Videos.jsx Normal file
View File

@@ -0,0 +1,19 @@
import { Box } from '@chakra-ui/react'
import { OPACITY_ON_LOAD } from '../Layout/animations'
const Videos = () => {
return (
<Box
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
height={"100vh"}
backgroundColor={"teal"}
color={"whitesmoke"}
{...OPACITY_ON_LOAD}
>Videos</Box>
)
}
export default Videos

18
src/Pages/Whitepapers.jsx Normal file
View File

@@ -0,0 +1,18 @@
import { Box } from '@chakra-ui/react'
import { OPACITY_ON_LOAD } from '../Layout/animations'
const Whitepapers = () => {
return (
<Box
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
height={"100vh"}
backgroundColor={"teal"}
color={"whitesmoke"}
{...OPACITY_ON_LOAD}
>Whitepapers</Box>
)
}
export default Whitepapers

80
src/Redux/Slice/auth.js Normal file
View File

@@ -0,0 +1,80 @@
import { createSlice } from "@reduxjs/toolkit";
// export const AuthMe = createAsyncThunk(
// "auth/Me",
// async (args, { rejectWithValue, dispatch }) => {
// try {
// const res = await authMeService();
// if (res?.status === 200) return res.data;
// return rejectWithValue(res);
// } catch (err) {
// return rejectWithValue(err.response);
// }
// }
// );
const initialState = {
isLoading: true,
isError: false,
error: null,
message: null,
isAuthenticate: false,
};
const authMeSlice = createSlice({
name: "auth",
initialState: initialState,
reducers: {
logoutUser: (state, action) => {
state = initialState;
},
loginUser: (state, action) => {
state.isAuthenticate = action.payload;
},
},
// extraReducers: (builder) => {
// builder
// .addCase(AuthMe.pending, (state) => {
// state.isLoading = true;
// })
// .addCase(AuthMe.fulfilled, (state, action) => {
// state.isLoading = false;
// state.profileData = action.payload?.data;
// })
// .addCase(AuthMe.rejected, (state, action) => {
// state.isLoading = false;
// })
// .addCase(AuthReGenrateAccessToken.pending, (state) => {
// state.isLoading = true;
// })
// .addCase(AuthReGenrateAccessToken.fulfilled, (state, action) => {
// state.isLoading = false;
// state.accessToken = action.payload?.accessToken || null;
// })
// .addCase(AuthReGenrateAccessToken.rejected, (state) => {
// state.isAuthenticate = false;
// state.isLoading = false;
// })
// .addCase(AuthLogin.pending, (state) => {
// state.isLoading = true;
// })
// .addCase(AuthLogin.fulfilled, (state, action) => {
// state.isLoading = false;
// state.isAuthenticate = true;
// state.role = action.payload.role[0].title;
// state.group = action.payload.group[0].title;
// state.actions = action.payload.role[0].actions;
// state.resources = action.payload.resources;
// state.accessToken = action.payload.accessToken;
// state.refreshToken = action.payload.refreshToken;
// })
// .addCase(AuthLogin.rejected, (state, action) => {
// state.isLoading = false;
// state.isError = true;
// state.error = action.payload;
// });
// },
});
export const { logoutUser, loginUser } = authMeSlice.actions;
export default authMeSlice.reducer;

43
src/Redux/Store.js Normal file
View File

@@ -0,0 +1,43 @@
import { configureStore } from "@reduxjs/toolkit";
import { combineReducers } from "redux";
import { persistReducer, persistStore } from "redux-persist";
import storage from "redux-persist/lib/storage"; // defaults to localStorage for web
import { encryptTransform } from "redux-persist-transform-encrypt";
import auth from "./Slice/auth";
// Import your reducers and combine them
const rootReducer = combineReducers({
// Add your reducers here
auth: auth,
});
// Define encryption for persisted state
const encryptor = encryptTransform({
secretKey: "webStore",
onError: function (error) {
console.log(error);
},
});
// Configuration for persisting the Redux store
const persistConfig = {
key: "root",
storage,
transforms: [encryptor],
};
// Create a persisted reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
// Create the Redux store
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}), // This line already includes thunk middleware
});
// Export store state and persistor
export const RootState = store.getState();
export const persistor = persistStore(store);

51
src/Routes/Nav.js Normal file
View File

@@ -0,0 +1,51 @@
import { FaHome, FaTable } from "react-icons/fa";
import { FiHome } from "react-icons/fi";
import { RiBloggerLine } from "react-icons/ri";
import { LuVideo } from "react-icons/lu";
import { HiOutlineNewspaper } from "react-icons/hi";
import { IoMdPaper } from "react-icons/io";
import { MdOutlineEvent } from "react-icons/md";
import { CgCommunity } from "react-icons/cg";
import { AiOutlineIdcard } from "react-icons/ai";
export const nav = [
{
title: "Banners",
path: "/",
Icon: AiOutlineIdcard,
},
{
title: "CONTENT MANAGEMENT",
path: null,
},
{
title: "Blogs",
path: "/blogs-articles",
Icon: RiBloggerLine,
},
{
title: "Videos",
path: "/videos",
Icon: LuVideo,
},
{
title: "News",
path: "/news",
Icon: HiOutlineNewspaper,
},
{
title: "Events",
path: "/events",
Icon: MdOutlineEvent,
},
{
title: "Whitepaper",
path: "/whitepaper",
Icon: IoMdPaper,
},
{
title: "Community",
path: "/community",
Icon: CgCommunity,
},
];

View File

@@ -0,0 +1,18 @@
import React, { useContext } from 'react';
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
import GlobalStateContext from '../Contexts/GlobalStateContext';
const PrivateRoute = ({ children }) => {
const navigate = useNavigate()
const { isAuthenticate } = useContext(GlobalStateContext);
const isAuthenticatedInCookie = Cookies.get("isAuthenticated");
if (!isAuthenticate && isAuthenticatedInCookie !== "true") {
return navigate('/login');
}
return children;
};
export default PrivateRoute;

64
src/Routes/Routes.js Normal file
View File

@@ -0,0 +1,64 @@
import BlogsAndArticles from "../Pages/BlogsAndArticles/BlogsAndArticles";
import AddComunity from "../Pages/Community/AddComunity";
import Community from "../Pages/Community/Community";
import CommunityCardsTableView from "../Pages/Community/CommunityCardsTableView";
import ComunityEditPage from "../Pages/Community/ComunityEditPage";
import ComunityViewPage from "../Pages/Community/ComunityViewPage";
import Events from "../Pages/Events";
import Home from "../Pages/Banners/Banner";
import Videos from "../Pages/Videos";
import Whitepapers from "../Pages/Whitepapers";
import BannerCommunity from "../Pages/Banners/BannerCommunity/BannerCommunity";
import BannerComunityEditPage from "../Pages/Banners/BannerCommunity/BannerCommunityEdit";
import BannerComunityViewPage from "../Pages/Banners/BannerCommunity/BannerCommunityView";
import AddBlogsAndArticles from "../Pages/BlogsAndArticles/AddBlogsAndArticles";
import News from "../Pages/News/News";
import AddNews from "../Pages/News/AddNews";
import EditNews from "../Pages/News/EditNews";
import ViewNews from "../Pages/News/ViewNews";
import ViewBlogsAndArticles from "../Pages/BlogsAndArticles/ViewBlogsAndArticles";
import EditBlogsAndArticles from "../Pages/BlogsAndArticles/EditBlogsAndArticles";
import BannerLearn from "../Pages/Banners/BannerLearn/BannerLearn";
import AddBanner from "../Pages/Banners/BannerCommunity/AddBanner";
import AddLearnBanner from "../Pages/Banners/BannerLearn/AddLearnBanner";
import HelpAndSupport from "../Pages/News/HelpAndSupport";
export const RouteLink = [
{ path: "/", Component: Home },
{ path: "/videos", Component: Videos },
{ path: "/news", Component: News },
{ path: "/events", Component: Events },
{ path: "/whitepaper", Component: Whitepapers },
{ path: "/community", Component: Community },
{ path: "/help-and-support", Component: HelpAndSupport },
{ path: "community/view/:id", Component: ComunityViewPage },
{ path: "community/edit/:id", Component: ComunityEditPage },
{ path: "community/add-comunity", Component: AddComunity },
{ path: "community-table-view", Component: CommunityCardsTableView },
{ path: "banner/banner-community", Component: BannerCommunity },
{ path: "banner/banner-community/add-banner", Component: AddBanner },
{ path: "banner/banner-community/edit/:id", Component: BannerComunityEditPage },
{ path: "banner/banner-community/view/:id", Component: BannerComunityViewPage },
{ path: "banner/learn/add-banner", Component: AddLearnBanner },
{ path: "banner/learn", Component: BannerLearn },
// =============[ blog ]================
{ path: "/blogs-articles", Component: BlogsAndArticles },
{ path: "blogs-articles/add-blog", Component: AddBlogsAndArticles },
{ path: "blogs-articles/view/:id", Component: ViewBlogsAndArticles },
{ path: "blogs-articles/edit/:id", Component: EditBlogsAndArticles },
// =============[ news ]================
{ path: "/news/view/:id", Component: ViewNews },
{ path: "/news/add-news", Component: AddNews },
{ path: "/news/edit/:id", Component: EditNews },
];

297
src/Services/api.service.js Normal file
View File

@@ -0,0 +1,297 @@
// Need to use the React-specific entry point to import createApi
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const baseUrl = "https://rubix.betadelivery.com/api";
// Define a service using a base URL and expected endpoints
export const rubixApi = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl }),
tagTypes: [
"getCommunity",
"getCommunityById",
"getCommunityBanner",
"getCommunityBannerById",
],
endpoints: (builder) => ({
// ===============[ Community cards endpoints ]=======================
getCommunity: builder.query({
query: ({ page, size }) => `/admin/community?page=${page}&size=${size}`,
providesTags: ["getCommunity"],
}),
getCommunityById: builder.query({
query: (id) => `/admin/community/${id}`,
providesTags: ["getCommunityById"],
}),
createCommunity: builder.mutation({
query: (newCommunity) => ({
url: "/admin/community",
method: "POST",
body: newCommunity,
}),
invalidatesTags: ["getCommunity"],
}),
deleteCommunity: builder.mutation({
query: (communityId) => ({
url: `/admin/community/${communityId}`,
method: "DELETE",
}),
invalidatesTags: ["getCommunity"],
}),
updateCommunityStatus: builder.mutation({
query: ({ id }) => ({
url: `/admin/community/change-visibility/${id}`,
method: "POST",
}),
invalidatesTags: ["getCommunity"],
}),
updateCommunity: builder.mutation({
query: ({ id, data }) => ({
url: `/admin/community/${id}`,
method: "PUT",
body: data, // Include the data you want to send in the request body
}),
invalidatesTags: ["getCommunity"],
}),
// ===============[ Community Banners endpoints ]=======================
getCommunityBanner: builder.query({
query: () => "/admin/main-community",
providesTags: ["getCommunityBanner"],
}),
getCommunityBannerById: builder.query({
query: (id) => `/admin/main-community/${id}`,
providesTags: ["getCommunityBannerById"],
}),
createCommunityBanner: builder.mutation({
query: (newBanner) => ({
url: "/admin/main-community",
method: "POST",
body: newBanner,
}),
invalidatesTags: ["getCommunityBanner"],
}),
deleteCommunityBanner: builder.mutation({
query: (communityBannerId) => ({
url: `/admin/main-community/${communityBannerId}`,
method: "DELETE",
}),
invalidatesTags: ["getCommunityBanner"],
}),
updateCommunityBanner: builder.mutation({
query: ({ id, data }) => ({
url: `/admin/main-community/${id}`,
method: "PUT",
body: data,
}),
invalidatesTags: ["getCommunityBanner"],
}),
updateCommunityBannerStatus: builder.mutation({
query: ({ id }) => ({
url: `/admin/main/change-visibility/${id}`,
method: "POST",
}),
invalidatesTags: ["getCommunityBanner"],
}),
// ===============[ Learn Banners endpoints ]=======================
getLearnBanner: builder.query({
query: () => "/admin/learn",
providesTags: ["getLearnBanner"],
}),
getLearnBannerById: builder.query({
query: (id) => `/admin/learn/${id}`,
providesTags: ["getLearnBannerById"],
}),
createLearnBanner: builder.mutation({
query: (newBanner) => ({
url: "/admin/learn",
method: "POST",
body: newBanner,
}),
invalidatesTags: ["getLearnBanner"],
}),
deleteLearnBanner: builder.mutation({
query: (id) => ({
url: `/admin/learn/${id}`,
method: "DELETE",
}),
invalidatesTags: ["getLearnBanner"],
}),
updateLearnBanner: builder.mutation({
query: ({ id, data }) => ({
url: `/admin/learn/${id}`,
method: "PUT",
body: data,
}),
invalidatesTags: ["getLearnBanner"],
}),
updateLearnBannerStatus: builder.mutation({
query: ({ id }) => ({
url: `/admin/learn/change-visibility/${id}`,
method: "POST",
}),
invalidatesTags: ["getLearnBanner"],
}),
// ===============[ Learn Banners endpoints ]=======================
getBuildBanner: builder.query({
query: () => "/admin/build",
providesTags: ["getBuildBanner"],
}),
// ===============[ news Banners endpoints ]=======================
getNewsBanner: builder.query({
query: () => "/admin/main-news",
providesTags: ["getNewsBanner"],
}),
// ================[ blog endpoints ]====================
getBlog: builder.query({
query: () => "/admin/blog",
providesTags: ["getBlog"],
}),
getBlogById: builder.query({
query: (id) => `/admin/blog/${id}`,
providesTags: ["getBlogById"],
}),
createBlog: builder.mutation({
query: (newBlog) => ({
url: "/admin/blog",
method: "POST",
body: newBlog,
}),
invalidatesTags: ["getBlog"],
}),
deleteBlog: builder.mutation({
query: (blogId) => ({
url: `/admin/blog/${blogId}`,
method: "DELETE",
}),
invalidatesTags: ["getBlog"],
}),
updateBlog: builder.mutation({
query: ({ id, data }) => ({
url: `/admin/blog/${id}`,
method: "PUT",
body: data, // Include the data you want to send in the request body
}),
invalidatesTags: ["getBlog"],
}),
updateBlogStatus: builder.mutation({
query: ({ id }) => ({
url: `/admin/blog/change-visibility/${id}`,
method: "POST",
}),
invalidatesTags: ["getBlog"],
}),
// ================[ news ]====================
getNews: builder.query({
query: ({ page, size }) => `/admin/news?page=${page}&size=${size}`,
providesTags: ["getNews"],
}),
getNewsById: builder.query({
query: (id) => `/admin/news/${id}`,
providesTags: ["getNews"],
}),
createNews: builder.mutation({
query: (news) => ({
url: "/admin/news",
method: "POST",
body: news,
}),
invalidatesTags: ["getNews"],
}),
updateNewsStatus: builder.mutation({
query: ({ id }) => ({
url: `/admin/news/change-visibility/${id}`,
method: "POST",
}),
invalidatesTags: ["getNews"],
}),
updateNews: builder.mutation({
query: ({ id, data }) => ({
url: `/admin/news/${id}`,
method: "PUT",
body: data, // Include the data you want to send in the request body
}),
invalidatesTags: ["getNews"],
}),
deleteNews: builder.mutation({
query: (blogId) => ({
url: `/admin/news/${blogId}`,
method: "DELETE",
}),
invalidatesTags: ["getNews"],
}),
}),
});
export const {
useGetCommunityQuery,
useGetCommunityByIdQuery,
useCreateCommunityMutation,
useDeleteCommunityMutation,
useUpdateCommunityStatusMutation,
useUpdateCommunityMutation,
useGetCommunityBannerQuery,
useCreateCommunityBannerMutation,
useDeleteCommunityBannerMutation,
useUpdateCommunityBannerMutation,
useGetCommunityBannerByIdQuery,
useCreateLearnBannerMutation,
useDeleteLearnBannerMutation,
useGetLearnBannerByIdQuery,
useGetLearnBannerQuery,
useUpdateLearnBannerMutation,
useUpdateLearnBannerStatusMutation,
useGetBuildBannerQuery,
useGetNewsBannerQuery,
useGetBlogQuery,
useGetBlogByIdQuery,
useCreateBlogMutation,
useDeleteBlogMutation,
useUpdateBlogMutation,
useUpdateCommunityBannerStatusMutation,
useUpdateBlogStatusMutation,
useGetNewsQuery,
useUpdateNewsStatusMutation,
useDeleteNewsMutation,
useCreateNewsMutation,
useGetNewsByIdQuery,
useUpdateNewsMutation,
} = rubixApi;

17
src/Store/Store.js Normal file
View File

@@ -0,0 +1,17 @@
// Store.js
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { rubixApi } from '../Services/api.service';
export const store = configureStore({
reducer: {
[rubixApi.reducerPath]: rubixApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(rubixApi.middleware),
});
setupListeners(store.dispatch);
export default store; // Make sure to export the store variable

View File

@@ -0,0 +1,93 @@
import * as Yup from "yup";
export const validationSchema = Yup.object().shape({
name: Yup.string().required("Owner name is required"),
password: Yup.string().required("Password is required"),
});
export const addCommunitySchema = Yup.object().shape({
member_name: Yup.string().required("Name is required"),
designation: Yup.string().required("Designation is required"),
description: Yup.string().required("Description is required"),
linkedin: Yup.string().required("Linked In link is required"),
profile_image: Yup.mixed().required("Display picture is required"),
});
export const schemaEdit = Yup.object().shape({
member_name: Yup.string().required("Name is required"),
designation: Yup.string().required("Designation is required"),
description: Yup.string().required("Description is required"),
linkedin: Yup.string()
.url("Invalid LinkedIn URL")
.required("LinkedIn is required"),
});
export const addCommunityBannerSchema = Yup.object().shape({
heading: Yup.string().required("Name is required"),
sub_heading: Yup.string().required("Designation is required"),
CTO_button_title: Yup.string().required("Description is required"),
CTO_button_link: Yup.string().required("Linked In link is required"),
banner_image: Yup.mixed().required("Display picture is required"),
});
export const editCommunityBannerSchema = Yup.object().shape({
Heading: Yup.string().required("Name is required"),
sub_heading: Yup.string().required("Designation is required"),
CTO_button_title: Yup.string().required("Description is required"),
CTO_button_link: Yup.string()
.url("Invalid LinkedIn URL")
.required("LinkedIn is required"),
});
export const addBlogSchema = Yup.object().shape({
author_name: Yup.string().required("Author is required"),
author_designation: Yup.string().required("Author designation is required"),
title: Yup.string().required("Title is required"),
meta_description: Yup.string().required("Description is required"),
// content: Yup.string().required("Content is required"),
summary: Yup.string().required("Summary is required"),
});
export const addNews = Yup.object().shape({
title: Yup.string().required("Author is required"),
release_date: Yup.date().required("Release date is required"),
meta_description: Yup.string().required("Description is required"),
content: Yup.string().required("Content is required"),
banner_image: Yup.mixed()
.test("fileSize", "Image must be at least 2MB", (value) => {
// If no file uploaded, return true (validation passed)
if (!value) return true;
// Calculate file size in bytes
const fileSizeInBytes = value.size;
// Convert bytes to megabytes
const fileSizeInMB = fileSizeInBytes / (1024 * 1024);
// Check if file size is at least 2MB
return fileSizeInMB >= 10;
}).required("Banner image is required")
});
export const editNews = Yup.object().shape({
title: Yup.string(),
release_date: Yup.date(),
meta_description: Yup.string(),
content: Yup.string(),
banner_image: Yup.mixed()
// .test("fileSize", "Image must be at least 2MB", (value) => {
// // If no file uploaded, return true (validation passed)
// if (!value) return true;
// // Calculate file size in bytes
// const fileSizeInBytes = value.size;
// // Convert bytes to megabytes
// const fileSizeInMB = fileSizeInBytes / (1024 * 1024);
// // Check if file size is at least 2MB
// return fileSizeInMB >= 10;
// })
});

BIN
src/assets/Ellipse-37.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
src/assets/Ellipse-38.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/Ellipse-39.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="797.5" height="834.5" viewBox="0 0 797.5 834.5" xmlns:xlink="http://www.w3.org/1999/xlink"><title>void</title><ellipse cx="308.5" cy="780" rx="308.5" ry="54.5" fill="#3f3d56"/><circle cx="496" cy="301.5" r="301.5" fill="#3f3d56"/><circle cx="496" cy="301.5" r="248.89787" opacity="0.05"/><circle cx="496" cy="301.5" r="203.99362" opacity="0.05"/><circle cx="496" cy="301.5" r="146.25957" opacity="0.05"/><path d="M398.42029,361.23224s-23.70394,66.72221-13.16886,90.42615,27.21564,46.52995,27.21564,46.52995S406.3216,365.62186,398.42029,361.23224Z" transform="translate(-201.25 -32.75)" fill="#d0cde1"/><path d="M398.42029,361.23224s-23.70394,66.72221-13.16886,90.42615,27.21564,46.52995,27.21564,46.52995S406.3216,365.62186,398.42029,361.23224Z" transform="translate(-201.25 -32.75)" opacity="0.1"/><path d="M415.10084,515.74682s-1.75585,16.68055-2.63377,17.55847.87792,2.63377,0,5.26754-1.75585,6.14547,0,7.02339-9.65716,78.13521-9.65716,78.13521-28.09356,36.8728-16.68055,94.81576l3.51169,58.82089s27.21564,1.75585,27.21564-7.90132c0,0-1.75585-11.413-1.75585-16.68055s4.38962-5.26754,1.75585-7.90131-2.63377-4.38962-2.63377-4.38962,4.38961-3.51169,3.51169-4.38962,7.90131-63.2105,7.90131-63.2105,9.65716-9.65716,9.65716-14.92471v-5.26754s4.38962-11.413,4.38962-12.29093,23.70394-54.43127,23.70394-54.43127l9.65716,38.62864,10.53509,55.3092s5.26754,50.04165,15.80262,69.356c0,0,18.4364,63.21051,18.4364,61.45466s30.72733-6.14547,29.84941-14.04678-18.4364-118.5197-18.4364-118.5197L533.62054,513.991Z" transform="translate(-201.25 -32.75)" fill="#2f2e41"/><path d="M391.3969,772.97846s-23.70394,46.53-7.90131,48.2858,21.94809,1.75585,28.97148-5.26754c3.83968-3.83968,11.61528-8.99134,17.87566-12.87285a23.117,23.117,0,0,0,10.96893-21.98175c-.463-4.29531-2.06792-7.83444-6.01858-8.16366-10.53508-.87792-22.826-10.53508-22.826-10.53508Z" transform="translate(-201.25 -32.75)" fill="#2f2e41"/><path d="M522.20753,807.21748s-23.70394,46.53-7.90131,48.28581,21.94809,1.75584,28.97148-5.26754c3.83968-3.83969,11.61528-8.99134,17.87566-12.87285a23.117,23.117,0,0,0,10.96893-21.98175c-.463-4.29531-2.06792-7.83444-6.01857-8.16367-10.53509-.87792-22.826-10.53508-22.826-10.53508Z" transform="translate(-201.25 -32.75)" fill="#2f2e41"/><circle cx="295.90488" cy="215.43252" r="36.90462" fill="#ffb8b8"/><path d="M473.43048,260.30832S447.07,308.81154,444.9612,308.81154,492.41,324.62781,492.41,324.62781s13.70743-46.39439,15.81626-50.61206Z" transform="translate(-201.25 -32.75)" fill="#ffb8b8"/><path d="M513.86726,313.3854s-52.67543-28.97148-57.943-28.09356-61.45466,50.04166-60.57673,70.2339,7.90131,53.55335,7.90131,53.55335,2.63377,93.05991,7.90131,93.93783-.87792,16.68055.87793,16.68055,122.90931,0,123.78724-2.63377S513.86726,313.3854,513.86726,313.3854Z" transform="translate(-201.25 -32.75)" fill="#d0cde1"/><path d="M543.2777,521.89228s16.68055,50.91958,2.63377,49.16373-20.19224-43.89619-20.19224-43.89619Z" transform="translate(-201.25 -32.75)" fill="#ffb8b8"/><path d="M498.50359,310.31267s-32.48318,7.02339-27.21563,50.91957,14.9247,87.79237,14.9247,87.79237l32.48318,71.11182,3.51169,13.16886,23.70394-6.14547L528.353,425.32067s-6.14547-108.86253-14.04678-112.37423A33.99966,33.99966,0,0,0,498.50359,310.31267Z" transform="translate(-201.25 -32.75)" fill="#d0cde1"/><polygon points="277.5 414.958 317.885 486.947 283.86 411.09 277.5 414.958" opacity="0.1"/><path d="M533.896,237.31585l.122-2.82012,5.6101,1.39632a6.26971,6.26971,0,0,0-2.5138-4.61513l5.97581-.33413a64.47667,64.47667,0,0,0-43.1245-26.65136c-12.92583-1.87346-27.31837.83756-36.182,10.43045-4.29926,4.653-7.00067,10.57018-8.92232,16.60685-3.53926,11.11821-4.26038,24.3719,3.11964,33.40938,7.5006,9.18513,20.602,10.98439,32.40592,12.12114,4.15328.4,8.50581.77216,12.35457-.83928a29.721,29.721,0,0,0-1.6539-13.03688,8.68665,8.68665,0,0,1-.87879-4.15246c.5247-3.51164,5.20884-4.39635,8.72762-3.9219s7.74984,1.20031,10.062-1.49432c1.59261-1.85609,1.49867-4.559,1.70967-6.99575C521.28248,239.785,533.83587,238.70653,533.896,237.31585Z" transform="translate(-201.25 -32.75)" fill="#2f2e41"/><circle cx="559" cy="744.5" r="43" fill="#35aee3"/><circle cx="54" cy="729.5" r="43" fill="#35aee3"/><circle cx="54" cy="672.5" r="31" fill="#35aee3"/><circle cx="54" cy="624.5" r="22" fill="#35aee3"/></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

1
src/assets/Error.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/assets/linkedin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/assets/logo-min.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

1
src/assets/react.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

22
src/main.jsx Normal file
View File

@@ -0,0 +1,22 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
// import { persistor, store } from "./Redux/Store.js";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { ChakraProvider } from "@chakra-ui/react";
import GlobalStateProvider from "./Contexts/GlobalStateProvider";
import { store } from "./Store/Store.js";
ReactDOM.createRoot(document.getElementById("root")).render(
<ChakraProvider>
<Provider store={store}>
{/* <PersistGate loading={null} persistor={persistor}> */}
<GlobalStateProvider>
<App />
</GlobalStateProvider>
{/* </PersistGate> */}
</Provider>
</ChakraProvider>
);

7
vite.config.js Normal file
View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})

3597
yarn.lock Normal file

File diff suppressed because it is too large Load Diff