second commit
3
.env
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
79
src/Components/BannerStack.jsx
Normal 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;
|
||||
25
src/Components/Buttons/Button01.jsx
Normal 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;
|
||||
47
src/Components/Buttons/Button02.jsx
Normal 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;
|
||||
60
src/Components/ChipSelector/ChipSelector.css
Normal 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;
|
||||
}
|
||||
|
||||
62
src/Components/ChipSelector/ChipSelector.jsx
Normal 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;
|
||||
153
src/Components/CommunityBanner.jsx
Normal 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;
|
||||
46
src/Components/CustomAlertDialog.jsx
Normal 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;
|
||||
51
src/Components/DataTable/DataTable.jsx
Normal 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;
|
||||
21
src/Components/EmptySearchList.jsx
Normal 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
|
||||
35
src/Components/Functions/TimeCalculator.jsx
Normal 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;
|
||||
11
src/Components/Functions/Toaster.jsx
Normal 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,
|
||||
});
|
||||
}
|
||||
16
src/Components/Functions/UTCConvertor.jsx
Normal 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
@@ -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;
|
||||
77
src/Components/ImageDropBox.jsx
Normal 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;
|
||||
44
src/Components/Inputs/Input01.jsx
Normal 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;
|
||||
18
src/Components/Loaders/FullscreenLoaders.jsx
Normal 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;
|
||||
14
src/Components/Loaders/Loader01.jsx
Normal 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;
|
||||
68
src/Components/WebButton.jsx
Normal 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
|
||||
1
src/Constants/Paginations.js
Normal file
@@ -0,0 +1 @@
|
||||
export const TABLE_PAGINATION = { page: 1, size: 15 }
|
||||
6
src/Contexts/GlobalStateContext.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
// GlobalStateContext.js
|
||||
import { createContext } from 'react';
|
||||
|
||||
const GlobalStateContext = createContext();
|
||||
|
||||
export default GlobalStateContext;
|
||||
30
src/Contexts/GlobalStateProvider.jsx
Normal 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
|
After Width: | Height: | Size: 167 KiB |
BIN
src/Images/light-bg.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
src/Images/logo.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/Images/logoDark.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src/Images/logoDarkMini.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/Images/logoLight.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src/Images/miniLogo.jpg
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
src/Images/reactLogo.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
312
src/Layout/DefaultLayout.jsx
Normal 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
@@ -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" }
|
||||
};
|
||||
|
||||
69
src/Pages/Banners/Banner.jsx
Normal 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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
359
src/Pages/Banners/BannerCommunity/AddBanner.jsx
Normal 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;
|
||||
|
||||
318
src/Pages/Banners/BannerCommunity/BannerCommunity.jsx
Normal 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;
|
||||
415
src/Pages/Banners/BannerCommunity/BannerCommunityEdit.jsx
Normal 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;
|
||||
155
src/Pages/Banners/BannerCommunity/BannerCommunityView.jsx
Normal 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;
|
||||
358
src/Pages/Banners/BannerLearn/AddLearnBanner.jsx
Normal 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;
|
||||
328
src/Pages/Banners/BannerLearn/BannerLearn.jsx
Normal 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;
|
||||
746
src/Pages/BlogsAndArticles/AddBlogsAndArticles.jsx
Normal 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;
|
||||
335
src/Pages/BlogsAndArticles/BlogsAndArticles.jsx
Normal 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;
|
||||
789
src/Pages/BlogsAndArticles/EditBlogsAndArticles.jsx
Normal 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;
|
||||
228
src/Pages/BlogsAndArticles/ViewBlogsAndArticles.jsx
Normal 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;
|
||||
343
src/Pages/Community/AddComunity.jsx
Normal 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;
|
||||
85
src/Pages/Community/CommCard.jsx
Normal 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;
|
||||
510
src/Pages/Community/Community.jsx
Normal 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;
|
||||
53
src/Pages/Community/CommunityBannerCard.jsx
Normal 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;
|
||||
73
src/Pages/Community/CommunityCardDisplay.jsx
Normal 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
|
||||
9
src/Pages/Community/CommunityCardsTableView.jsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const CommunityCardsTableView = () => {
|
||||
return (
|
||||
<div>CommunityCardsTableView</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CommunityCardsTableView
|
||||
413
src/Pages/Community/ComunityEditPage.jsx
Normal 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;
|
||||
170
src/Pages/Community/ComunityViewPage.jsx
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
19
src/Pages/News/HelpAndSupport.jsx
Normal 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
@@ -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
@@ -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;
|
||||
22
src/Pages/NoInternetScreen.jsx
Normal 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
@@ -0,0 +1,8 @@
|
||||
|
||||
const NotFound = () => {
|
||||
return (
|
||||
<div>NotFound</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFound
|
||||
19
src/Pages/Videos.jsx
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
},
|
||||
];
|
||||
18
src/Routes/PrivateRoute.jsx
Normal 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
@@ -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
@@ -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
@@ -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
|
||||
93
src/Validations/Validations.js
Normal 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
|
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/Ellipse-38.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/Ellipse-39.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
1
src/assets/EmptySearchList.svg
Normal 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
|
After Width: | Height: | Size: 6.4 KiB |
BIN
src/assets/communityBanner.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/fallBackImage.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/linkedin.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/logo-min.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src/assets/photo-1472313420546-a46e561861d8.avif
Normal file
|
After Width: | Height: | Size: 194 KiB |
1
src/assets/react.svg
Normal 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 |
BIN
src/assets/ultp-fallback-img.webp
Normal file
|
After Width: | Height: | Size: 810 B |
22
src/main.jsx
Normal 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
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||