first commit
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 },
|
||||
],
|
||||
},
|
||||
}
|
||||
28
.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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?
|
||||
|
||||
# Other files to ignore
|
||||
yarn.lock
|
||||
.env
|
||||
24
index.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tanami Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script>
|
||||
|
||||
const craftedMsg = "Crafted with ❤️ by WDI Team for a better web.";
|
||||
const websiteMsg = "Website: www.wdipl.com";
|
||||
|
||||
const craftedStyles = 'font-size: 16px; font-family: monospace; background: #000; color: #E5195E; padding: 12px 19px; border: 1.8px dashed; border-right: 0px #000 solid';
|
||||
const websiteStyles = 'font-size: 16px; font-family: monospace; background: #E5195E; color: #000; padding: 12px 19px; border: 1.8px dashed #000; border-left: 0px #000 solid';
|
||||
console.log('%c' + craftedMsg + ' %c' + websiteMsg, craftedStyles, websiteStyles);
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
6191
package-lock.json
generated
Normal file
46
package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "tanami-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",
|
||||
"xlsx": "^0.18.5",
|
||||
"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 |
301
src/App.css
Normal file
@@ -0,0 +1,301 @@
|
||||
@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");
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* font-family: "League Spartan", sans-serif !important; */
|
||||
font-family: "Poppins", 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: #ced8e6a2;
|
||||
/* background-color: #e2e8f01c; */
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
background-color: #ced8e6a2 !important;
|
||||
/* background-color: #e2e8f01c !important; */
|
||||
}
|
||||
|
||||
.active:hover {
|
||||
background-color: #ced8e6a2 !important;
|
||||
/* background-color: #e2e8f01c !important; */
|
||||
}
|
||||
|
||||
.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: #000000;
|
||||
}
|
||||
|
||||
.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: #004118 !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
|
||||
);
|
||||
}
|
||||
|
||||
.text-animate {
|
||||
animation-name: text;
|
||||
animation-duration: 5s;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
|
||||
/* Total scrollbar width */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* The track (background) of the scrollbar */
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border-radius: 10px;
|
||||
|
||||
}
|
||||
|
||||
/* The draggable scrollbar handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #004118;
|
||||
border-radius: 10px;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* On hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #e97d88;
|
||||
}
|
||||
|
||||
@keyframes text {
|
||||
0% {
|
||||
color: #DE858E;
|
||||
/* margin-bottom: -40px; */
|
||||
}
|
||||
30% {
|
||||
letter-spacing: 10px;
|
||||
/* margin-bottom: -40px; */
|
||||
}
|
||||
85% {
|
||||
letter-spacing: 8px;
|
||||
/* margin-bottom: -40px; */
|
||||
}
|
||||
100% {
|
||||
/* margin-bottom: 20px; */
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
424
src/Components/Banner/AddBanner.jsx
Normal file
@@ -0,0 +1,424 @@
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Input,
|
||||
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 { useNavigate } from "react-router-dom";
|
||||
import Loader01 from "../../Components/Loaders/Loader01";
|
||||
import Header from "../Header";
|
||||
import ToastBox from "../ToastBox";
|
||||
import BannerMainCard from "./BannerMainCard";
|
||||
|
||||
const AddBanner = ({ createApi, navigateLink, title, center }) => {
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState(fallbackImage);
|
||||
const [largeImageData, setLargeImageData] = useState(null);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(addCommunityBannerSchema),
|
||||
});
|
||||
|
||||
const formData = watch();
|
||||
|
||||
const onSubmit = async (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 (selectedImage[0]) {
|
||||
formData.append("banner_image", data.banner_image[0]);
|
||||
}
|
||||
// Trigger the mutation
|
||||
createApi(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({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
reset();
|
||||
navigate(navigateLink);
|
||||
} else if (response?.data?.statusCode === 500) {
|
||||
setIsLoading(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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];
|
||||
setLargeImageData(file);
|
||||
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={title} />
|
||||
|
||||
<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">
|
||||
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"
|
||||
/> */}
|
||||
|
||||
<BannerMainCard
|
||||
imgLink={selectedImage}
|
||||
heading={formData?.heading}
|
||||
subHeading={formData?.sub_heading}
|
||||
buttonTitle={formData?.CTO_button_title}
|
||||
center={center}
|
||||
/>
|
||||
|
||||
{selectedImage === fallbackImage || 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={() => 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 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"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.heading?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={formData?.heading?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If heading crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered {formData?.heading?.length}{" "}
|
||||
characters
|
||||
</FormHelperText>
|
||||
{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>
|
||||
<Textarea
|
||||
{...register("sub_heading")}
|
||||
placeholder="Sub heading"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.sub_heading?.length > 230}
|
||||
/>
|
||||
|
||||
<FormHelperText
|
||||
color={formData?.sub_heading?.length > 230 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If sub heading crosses 230 characters it will cause problem in
|
||||
alignment on website.you have entered{" "}
|
||||
{formData?.sub_heading?.length} characters
|
||||
</FormHelperText>
|
||||
{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"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.CTO_button_title?.length > 30}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={
|
||||
formData?.CTO_button_title?.length > 30 ? "red" : "gray.500"
|
||||
}
|
||||
className="web-text-small"
|
||||
>
|
||||
If Button title crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered{" "}
|
||||
{formData?.CTO_button_title?.length} 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 10MB.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end mb-0">
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
rounded={"sm"}
|
||||
size="sm"
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddBanner;
|
||||
496
src/Components/Banner/BannerEdit.jsx
Normal file
@@ -0,0 +1,496 @@
|
||||
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,
|
||||
Text,
|
||||
} 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";
|
||||
import CommunityBannerCard from "../../Pages/Community/CommunityBannerCard";
|
||||
import BannerMainCard from "./BannerMainCard";
|
||||
import ToastBox from "../ToastBox";
|
||||
import { IMAGE_URI } from "../../Constants/Paginations";
|
||||
|
||||
const BannerEdit = ({isLoading, data, updateBanner, navigateTo, refetch, center}) => {
|
||||
const { id } = useParams();
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [isLoadingEdit, setIsLoadingEdit] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState();
|
||||
const [largeImageData, setLargeImageData] = useState(null);
|
||||
const [title, setTitle] = useState("");
|
||||
|
||||
const handleTitleChange = (e) => {
|
||||
setTitle(e.target.value);
|
||||
};
|
||||
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
watch
|
||||
} = useForm({
|
||||
resolver: yupResolver(editCommunityBannerSchema),
|
||||
defaultValues: {
|
||||
heading: data?.data?.Heading,
|
||||
sub_heading: data?.data?.sub_heading,
|
||||
CTO_button_title: data?.data?.CTO_button_title,
|
||||
CTO_button_link: data?.data?.CTO_button_link,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// Watch form values to update preview
|
||||
const formData = watch();
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data) {
|
||||
setSelectedImage(
|
||||
`${IMAGE_URI}/${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);
|
||||
watch()
|
||||
}
|
||||
}, [data, setValue]);
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// const subscription = watch((value) => {setFormData(value)});
|
||||
// return () => subscription.unsubscribe();
|
||||
// }, [watch]);
|
||||
|
||||
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) {
|
||||
form.delete("banner_image");
|
||||
}
|
||||
// Log the FormData entries
|
||||
// for (const [key, value] of form.entries()) {
|
||||
// // console.log(`${key}: ${value}`);
|
||||
// }
|
||||
const mutationResult = await updateBanner({ 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({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
refetch()
|
||||
navigate(navigateTo);
|
||||
// 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"}
|
||||
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"
|
||||
/> */}
|
||||
|
||||
|
||||
<BannerMainCard
|
||||
imgLink={selectedImage}
|
||||
heading={formData?.heading}
|
||||
subHeading={formData?.sub_heading}
|
||||
buttonTitle={formData?.CTO_button_title}
|
||||
center={center}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
{selectedImage === fallbackImage || 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={() => 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"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.heading?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={
|
||||
formData?.heading?.length > 50
|
||||
? "red"
|
||||
: "gray.500"
|
||||
}
|
||||
className="web-text-small"
|
||||
>
|
||||
If heading crosses 50 characters it will cause problem in alignment on website.you have entered {formData?.heading?.length} characters
|
||||
</FormHelperText>
|
||||
{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>
|
||||
<Textarea
|
||||
{...register("sub_heading")}
|
||||
placeholder="Sub heading"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
id="sub_heading"
|
||||
name="sub_heading"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.sub_heading?.length > 230}
|
||||
/>
|
||||
|
||||
<FormHelperText
|
||||
color={
|
||||
formData?.sub_heading?.length > 230
|
||||
? "red"
|
||||
: "gray.500"
|
||||
}
|
||||
className="web-text-small"
|
||||
>
|
||||
If sub heading crosses 230 characters it will cause problem in alignment on website.you have entered {formData?.sub_heading?.length} characters
|
||||
</FormHelperText>
|
||||
{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="Button title"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
maxLength={90}
|
||||
id="CTO_button_title"
|
||||
name="CTO_button_title"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.CTO_button_title?.length > 30}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={
|
||||
formData?.CTO_button_title?.length > 30
|
||||
? "red"
|
||||
: "gray.500"
|
||||
}
|
||||
className="web-text-small"
|
||||
>
|
||||
If sub heading crosses 30 characters it will cause problem in alignment on website.you have entered {formData?.CTO_button_title?.length} 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 10MB.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end">
|
||||
<Button
|
||||
isLoading={isLoadingEdit}
|
||||
spinner={<Loader01 />}
|
||||
color={'whitesmoke'}
|
||||
backgroundColor={'purple.900'}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
size="sm"
|
||||
rounded={'sm'}
|
||||
>
|
||||
Update banner
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerEdit;
|
||||
63
src/Components/Banner/BannerMainCard.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Box, Button, Text } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
|
||||
const BannerMainCard = ({heading, subHeading, buttonTitle, imgLink, center}) => {
|
||||
|
||||
|
||||
const words = heading?.split(' ');
|
||||
const firstThreeWords = words.slice(0, 3).join(' ');
|
||||
const remainingWords = words.slice(3).join(' ');
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
shadow={"md"}
|
||||
rounded={8}
|
||||
w={469}
|
||||
h={250}
|
||||
borderRadius={'sm'}
|
||||
bgImage={imgLink}
|
||||
bgSize="cover"
|
||||
bgPosition="center"
|
||||
ps={8}
|
||||
pt={14}
|
||||
>
|
||||
<Box w={center ? "92%" : "100%"} display={'flex'} flexDirection={'column'} alignItems={center ? 'center' : "start"}>
|
||||
<Text textAlign={center ? "center":'start'} w={center ? "80%" : "74%"}fontSize={"19px"} className="fw-bolder" color={"#FFFFFF"} display={'block'} as={"span"}>
|
||||
<span style={{ color: '#DE858E' }}>{firstThreeWords}</span>
|
||||
{' '}
|
||||
<span>{remainingWords}</span>
|
||||
</Text>
|
||||
<Text textAlign={center ? "center":'start'} w={center ? "92%" : "74%"} fontSize={"10px"} mt={1} className=" fw-normal" color={"#ffffff"} display={'block'} as={"span"}>
|
||||
{subHeading}
|
||||
</Text>
|
||||
<Button
|
||||
fontWeight={"normal"}
|
||||
fontSize={"9px"}
|
||||
ps={4}
|
||||
pe={4}
|
||||
pt={1}
|
||||
pb={1}
|
||||
mt={2}
|
||||
color={"#ffffff"}
|
||||
_hover={{
|
||||
bgGradient:"linear(to-r, #1E114B, purple)"
|
||||
}}
|
||||
// bg={'#1E114B'}
|
||||
// bgGradient="linear(to-r, #1E114B, purple)"
|
||||
variant={"outline"}
|
||||
// colorScheme="purple"
|
||||
rounded={4}
|
||||
size={"xs"}
|
||||
// border={'1px soild #fff'}
|
||||
|
||||
>
|
||||
{buttonTitle}
|
||||
</Button></Box>
|
||||
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default BannerMainCard
|
||||
369
src/Components/Banner/BannerTable.jsx
Normal file
@@ -0,0 +1,369 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Text,
|
||||
Tooltip,
|
||||
HStack,
|
||||
Input,
|
||||
Select,
|
||||
Image,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
Switch,
|
||||
Portal,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { HiDotsVertical } from "react-icons/hi";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
import CustomAlertDialog from "../../Components/CustomAlertDialog";
|
||||
import DataTable from "../DataTable/DataTable";
|
||||
import Header from "../Header";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import { IMAGE_URI, TABLE_PAGINATION } from "../../Constants/Paginations";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
WarningIcon,
|
||||
} from "@chakra-ui/icons";
|
||||
import ToastBox from "../ToastBox";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const BannerCommunity = ({
|
||||
dataArray,
|
||||
deleteApi,
|
||||
statusUpdateApi,
|
||||
title,
|
||||
addLink,
|
||||
viewLink,
|
||||
editLink,
|
||||
}) => {
|
||||
// ====================================================[Hooks]===================================================================
|
||||
const toast = useToast();
|
||||
const [deleteAlert, setDeleteAlert] = useState(false);
|
||||
const [actionId, setActionId] = useState(null);
|
||||
const [actionStatus, setActionStatus] = 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,
|
||||
// });
|
||||
|
||||
// ====================================================[Functions]===================================================================
|
||||
const handleDelete = async (bannerId, status) => {
|
||||
if (status) {
|
||||
return toast({
|
||||
render: () => (
|
||||
<ToastBox status={"warn"} message="You cant delete active banner" />
|
||||
),
|
||||
});
|
||||
}
|
||||
try {
|
||||
// Trigger the mutation
|
||||
setDeleteIsLoading(true);
|
||||
await deleteApi(bannerId)
|
||||
.then((response) => {
|
||||
// Handle the response here
|
||||
|
||||
if (response?.data?.statusCode === 200) {
|
||||
setDeleteIsLoading(false);
|
||||
setDeleteAlert(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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, status) => {
|
||||
if (status) {
|
||||
return toast({
|
||||
render: () => (
|
||||
<ToastBox status={"warn"} message={"Please toggle another banner."} />
|
||||
),
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
// Trigger the mutation
|
||||
await statusUpdateApi({ id })
|
||||
.then((response) => {
|
||||
if (response?.data?.statusCode === 201) {
|
||||
toast({
|
||||
render: () => <ToastBox message={response?.data?.message} />,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// // console.log(error);
|
||||
});
|
||||
} catch (error) {
|
||||
// Handle errors
|
||||
// // console.error("Error updating community status:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ====================================================[Table Filter]================================================================
|
||||
const filteredData = dataArray?.data?.data?.rows?.filter((item) => {
|
||||
// Filter by name (case insensitive)
|
||||
const name = item.Heading;
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const nameMatches = name.toLowerCase().includes(searchLower);
|
||||
|
||||
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={`${IMAGE_URI}/${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":
|
||||
<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?.CTO_button_title}</Text></Box></Tooltip>,
|
||||
Active: (
|
||||
<Switch
|
||||
size={"sm"}
|
||||
colorScheme="purple"
|
||||
onChange={() => handleUpdateStatus(item.id, item?.status)}
|
||||
isChecked={item.status}
|
||||
// disabled={item.status}
|
||||
/>
|
||||
),
|
||||
"Created At": (
|
||||
<span className="d-flex justify-content-between align-items-center">
|
||||
<Text as={"span"} color={"gray.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={`${editLink}${item.id}`}>
|
||||
<MenuItem className="web-text-medium">Edit</MenuItem>
|
||||
</RouterLink>
|
||||
<RouterLink to={`${viewLink}${item.id}`}>
|
||||
<MenuItem className="web-text-medium">View</MenuItem>
|
||||
</RouterLink>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setActionId(item.id);
|
||||
setDeleteAlert(true);
|
||||
setActionStatus(item.status);
|
||||
}}
|
||||
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={title} btnTitle={`Create banner `} link={addLink} />
|
||||
{/* ====================================================[ Top bar ]================================================================ */}
|
||||
|
||||
<Box pt={4}>
|
||||
<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{" "}
|
||||
{dataArray?.data?.data?.totalItems}
|
||||
</Text>
|
||||
<ChevronRightIcon
|
||||
onClick={paginationNext}
|
||||
className=" link rounded-3 pointer"
|
||||
/>
|
||||
</HStack> */}
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* ====================================================[ Table ]================================================================ */}
|
||||
<DataTable
|
||||
emptyMessage={"We don't have any banner of this heading"}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={extractedArray}
|
||||
isLoading={dataArray?.isLoading}
|
||||
/>
|
||||
{/* ====================================================[ Alert ]================================================================ */}
|
||||
<CustomAlertDialog
|
||||
onClose={() => setDeleteAlert(false)}
|
||||
isOpen={deleteAlert}
|
||||
alertHandler={() => handleDelete(actionId, actionStatus)}
|
||||
message={"Are you sure you want to delete member?"}
|
||||
isLoading={deleteIsLoading}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerCommunity;
|
||||
165
src/Components/Banner/BannerView.jsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetBuildBannerByIdQuery } from "../../Services/api.service";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
import Header from "../../Components/Header";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Image,
|
||||
StackDivider,
|
||||
Tag,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import BannerMainCard from "./BannerMainCard";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const BannerView = ({data, isLoading, editLink, center}) => {
|
||||
const banner = data?.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={`${editLink}/${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={`${API_URL}/${banner?.banner_image}`}
|
||||
alt="Selected Image"
|
||||
/> */}
|
||||
|
||||
<BannerMainCard
|
||||
imgLink={`${API_URL}/${banner?.banner_image}`}
|
||||
heading={banner?.Heading}
|
||||
subHeading={banner?.sub_heading}
|
||||
buttonTitle={banner?.CTO_button_title}
|
||||
center={center}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="col-7 pt-4 overflow-auto p-4">
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark mb-1">
|
||||
Status
|
||||
</Box>
|
||||
{data?.data?.status ? (
|
||||
<Tag size={"sm"} variant="solid" 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">
|
||||
Heading
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{banner?.Heading}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box 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 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 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 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 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 BannerView;
|
||||
84
src/Components/BannerStack.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
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={"purple.900"} className="web-text-large fw-bold">
|
||||
{stackTitle}
|
||||
</Text>
|
||||
|
||||
<Link to={viewAllLink}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
rightIcon={<ArrowForwardIcon />}
|
||||
colorScheme={"purple"}
|
||||
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}
|
||||
ps={1}
|
||||
>
|
||||
{bannerIsLoading
|
||||
? Array.from({ length: 3 }).map((_, index) => (
|
||||
<Box key={index} className="col-4 p-2 ps-0">
|
||||
<Skeleton w={"100%"} rounded={"md"} height={180} />
|
||||
</Box>
|
||||
))
|
||||
: bannerArray?.map(
|
||||
({
|
||||
id,
|
||||
CTO_button_title,
|
||||
banner_image,
|
||||
Heading,
|
||||
createdAt,
|
||||
sub_heading,
|
||||
status
|
||||
}) => (
|
||||
<Link className="col-4 h-100 p-3 ps-0" key={id} to={`${viewBannerLink}/${id}`}>
|
||||
<CommunityBannerCard
|
||||
status={status}
|
||||
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;
|
||||
}
|
||||
|
||||
68
src/Components/ChipSelector/ChipSelector.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, FormHelperText, Input, Tag, TagCloseButton, TagLabel } from "@chakra-ui/react";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
|
||||
const ChipSelector = ({chips, setChips, type}) => {
|
||||
const [text, setText] = useState("");
|
||||
const [validationError, setValidationError] = useState("");
|
||||
|
||||
function removeChip(chipToRemove) {
|
||||
const updatedChips = chips.filter((chip, index) => index !== 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={2}
|
||||
className="">
|
||||
<Input
|
||||
type={type? 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>}
|
||||
|
||||
<FormHelperText className="web-text-small mt-0">
|
||||
{type ? "Please select and press enter to add date." : "Please type and press enter to add tags."}
|
||||
|
||||
</FormHelperText>
|
||||
<Box
|
||||
display={'flex'}
|
||||
justifyContent={'start'}
|
||||
flexWrap={'wrap'}
|
||||
gap={2}
|
||||
>
|
||||
{chips?.map((chip, i) => (
|
||||
<Tag key={i} size="sm" ps={3} pe={3} pt={0.5} pb={0.5} rounded="full" variant='solid' backgroundColor={'#565263'}>
|
||||
<TagLabel className="text-uppercase">{chip}</TagLabel>
|
||||
<TagCloseButton onClick={() => removeChip(i)} />
|
||||
</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 overflowX={"hidden"} className="h-auto mb-3 w-100">
|
||||
{data?.length === 0 ? (
|
||||
<EmptySearchList message={emptyMessage} />
|
||||
) : (
|
||||
<Table size="sm">
|
||||
<TableCaption>Rubix v1.0.0</TableCaption>
|
||||
<Thead backgroundColor="purple.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 color={"gray.600"} 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
|
||||
13
src/Components/Functions/DebounceFunction.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
|
||||
const DebounceFunction = (func, delay) => {
|
||||
let timeoutId;
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
|
||||
export default DebounceFunction;
|
||||
8
src/Components/Functions/FileNameAlter.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
const extractFilename = (filePath) => {
|
||||
// Use the split method to break the path into parts based on '/'
|
||||
const parts = filePath.split('/');
|
||||
// Return the last part, which is the filename
|
||||
return parts[parts.length - 1];
|
||||
};
|
||||
|
||||
export default extractFilename ;
|
||||
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;
|
||||
}
|
||||
83
src/Components/Header.jsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { AddIcon } from "@chakra-ui/icons";
|
||||
import { Box, Button, Text } from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { IoMdDownload } from "react-icons/io";
|
||||
import * as XLSX from "xlsx";
|
||||
import { useGetNewsLetterEmailQuery } from "../Services/api.service";
|
||||
|
||||
const Header = ({ link, btnTitle, title }) => {
|
||||
const { data, error, isLoading } = useGetNewsLetterEmailQuery();
|
||||
|
||||
const handleDownload = () => {
|
||||
if (Array.isArray(data?.data?.rows)) {
|
||||
const worksheet = XLSX.utils.json_to_sheet(data?.data?.rows);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
|
||||
XLSX.writeFile(workbook, "newsletter_emails.xlsx");
|
||||
} else {
|
||||
// // console.error(
|
||||
// "Expected data to be an array but received:",
|
||||
// data?.data?.rows
|
||||
// );
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
backgroundColor={"#fff"}
|
||||
// bg="white.900"
|
||||
// backdropFilter="blur(10px) hue-rotate(90deg)"
|
||||
position={"sticky"}
|
||||
top={0}
|
||||
me={0.5}
|
||||
// zIndex={999}
|
||||
className={`${
|
||||
link && btnTitle ? "" : " pt-3 pb-3 "
|
||||
} p-2 pe-2 ps-0 fw-400 border-bottom d-flex justify-content-between align-items-center`}
|
||||
>
|
||||
{/* <span className="fs-5">Community</span> */}
|
||||
|
||||
<Text
|
||||
as={"span"}
|
||||
fontWeight={"bold"}
|
||||
color={"forestGreen.500"}
|
||||
className="fs-6 "
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
{/* <Text fontWeight='bold' bgGradient='linear(to-l, #A5626D, #331E8C)' bgClip='text' as={"span"} className="fs-6 ">
|
||||
{title}
|
||||
</Text> */}
|
||||
|
||||
{btnTitle ? btnTitle != "Export email" ? (
|
||||
<Link to={link}>
|
||||
<Button
|
||||
leftIcon={<AddIcon />}
|
||||
colorScheme={"forestGreen"}
|
||||
size="sm"
|
||||
rounded={"lg"}
|
||||
>
|
||||
{btnTitle}
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<Button
|
||||
leftIcon={<IoMdDownload />}
|
||||
backgroundColor={"green.900"}
|
||||
_hover={{
|
||||
backgroundColor: "green.800",
|
||||
}}
|
||||
color={"whitesmoke"}
|
||||
size="sm"
|
||||
rounded={"sm"}
|
||||
onClick={handleDownload}
|
||||
>
|
||||
{btnTitle}
|
||||
</Button>
|
||||
):""}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
110
src/Components/HeaderMain.jsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { AddIcon } from "@chakra-ui/icons";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverFooter,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { IoMdDownload } from "react-icons/io";
|
||||
import * as XLSX from "xlsx";
|
||||
import { useGetNewsLetterEmailQuery } from "../Services/api.service";
|
||||
|
||||
const HeaderMain = ({ link, btnTitle, title, icon, logOutHandler }) => {
|
||||
const { data, error, isLoading } = useGetNewsLetterEmailQuery();
|
||||
|
||||
const handleDownload = () => {
|
||||
if (Array.isArray(data?.data?.rows)) {
|
||||
const worksheet = XLSX.utils.json_to_sheet(data?.data?.rows);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
|
||||
XLSX.writeFile(workbook, "newsletter_emails.xlsx");
|
||||
} else {
|
||||
// // console.error(
|
||||
// "Expected data to be an array but received:",
|
||||
// data?.data?.rows
|
||||
// );
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
backgroundColor={"#fff"}
|
||||
// bg="white.900"
|
||||
// backdropFilter="blur(10px) hue-rotate(90deg)"
|
||||
// h={12}
|
||||
className={` pt-2 pb-2 fw-400 border-bottom d-flex justify-content-between align-items-center`}
|
||||
>
|
||||
{/* <span className="fs-5">Community</span> */}
|
||||
|
||||
<Text
|
||||
as={"span"}
|
||||
fontWeight={"bold"}
|
||||
color={"forestGreen.500"}
|
||||
className="fs-6 "
|
||||
>
|
||||
{/* <icon /> */}
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
<Box me={4} className="d-flex justify-content-center ">
|
||||
<Popover placement="bottom">
|
||||
<Portal>
|
||||
<PopoverContent maxW="200px" className="">
|
||||
<PopoverArrow />
|
||||
<PopoverBody className="web-text-medium pointer link">
|
||||
Profile
|
||||
</PopoverBody>
|
||||
<Link to={"/help-and-support"}>
|
||||
<PopoverBody className="web-text-medium pointer ">
|
||||
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="sm"
|
||||
name="Dan Abrahmov"
|
||||
src="https://bit.ly/dan-abramov"
|
||||
/>
|
||||
<Box
|
||||
color={"gray.800"}
|
||||
style={{
|
||||
display: "flex",
|
||||
}}
|
||||
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>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderMain;
|
||||
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 color='teal.700' />
|
||||
</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;
|
||||
136
src/Components/TabularView/TabularView.jsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Box, HStack, Input, Select, Text } from "@chakra-ui/react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import { TABLE_PAGINATION } from "../../Constants/Paginations";
|
||||
import { useGetVideosQuery } from "../../Services/api.service";
|
||||
import { useState } from "react";
|
||||
import Header from "../../Components/Header";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@chakra-ui/icons";
|
||||
import DataTable from "../../Components/DataTable/DataTable";
|
||||
|
||||
const TabularView = ({
|
||||
apiData,
|
||||
tableHeadRow,
|
||||
title,
|
||||
btnTitle,
|
||||
link,
|
||||
extractedArray,
|
||||
searchTerm,
|
||||
setSearchTerm,
|
||||
statusFilter,
|
||||
setStatusFilter,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
totalPages,
|
||||
noDataTitle,
|
||||
totalItems,
|
||||
}) => {
|
||||
const [displayRange, setDisplayRange] = useState({
|
||||
start: TABLE_PAGINATION?.page,
|
||||
end: pageSize,
|
||||
});
|
||||
// ====================================================[Pagination Setup]================================================================
|
||||
const paginationPrev = () => {
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
updateDisplayRange(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const paginationNext = () => {
|
||||
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, totalItems);
|
||||
setDisplayRange({ start, end });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
overflowY={"scroll"}
|
||||
paddingBottom={50}
|
||||
height={"100vh"}
|
||||
{...OPACITY_ON_LOAD}
|
||||
>
|
||||
<Header title={title} btnTitle={btnTitle} link={link} />
|
||||
|
||||
<Box bg="white.500">
|
||||
<HStack
|
||||
display={"flex"}
|
||||
justifyContent={"space-between"}
|
||||
ps={1}
|
||||
pe={1}
|
||||
pb={4}
|
||||
pt={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={5}>{5}</option>
|
||||
<option value={10}>{10}</option>
|
||||
<option value={15}>{15}</option>
|
||||
</Select>
|
||||
|
||||
<HStack>
|
||||
<ChevronLeftIcon
|
||||
onClick={paginationPrev}
|
||||
className=" link rounded-3 pointer"
|
||||
/>
|
||||
<Text className="web-text-medium" as={"span"}>
|
||||
{displayRange.start} - {displayRange.end} of {totalItems}
|
||||
</Text>
|
||||
<ChevronRightIcon
|
||||
onClick={paginationNext}
|
||||
className=" link rounded-3 pointer"
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* ====================================================[ Table ]================================================================ */}
|
||||
<DataTable
|
||||
emptyMessage={`We don't have any ${noDataTitle} `}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={extractedArray}
|
||||
isLoading={apiData?.isLoading}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabularView;
|
||||
21
src/Components/ToastBox.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { CheckCircleIcon, WarningIcon } from "@chakra-ui/icons";
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
|
||||
const ToastBox = ({ message, status }) => {
|
||||
return (
|
||||
<Box
|
||||
color="white"
|
||||
rounded={"md"}
|
||||
className="web-text-large d-flex gap-2 align-items-center"
|
||||
p={3}
|
||||
bg={status === "error" ? "red.500" : status === "warn" ? "blue.500" : "green.500"}
|
||||
>
|
||||
|
||||
{status === "error" || status === "warn" ? <WarningIcon/> : <CheckCircleIcon /> }
|
||||
<Text as={"span"}>{message}</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToastBox;
|
||||
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
|
||||
2
src/Constants/Paginations.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const TABLE_PAGINATION = { page: 1, size: 10 }
|
||||
export const IMAGE_URI = import.meta.env.VITE_API_IMAGE_URL
|
||||
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 |
BIN
src/Images/welcomeBanner.gif
Normal file
|
After Width: | Height: | Size: 7.7 MiB |
342
src/Layout/DefaultLayout.jsx
Normal file
@@ -0,0 +1,342 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import logo from "../assets/logo2.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
|
||||
import Header from "../Components/Header";
|
||||
import HeaderMain from "../Components/HeaderMain";
|
||||
|
||||
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);
|
||||
|
||||
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, Admin";
|
||||
case "/investment":
|
||||
return "Investment";
|
||||
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 "Tanami";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
|
||||
// backgroundColor:"#101015"
|
||||
// backgroundColor:"#000000"
|
||||
}}
|
||||
className="d-flex"
|
||||
pe={0.5}
|
||||
|
||||
>
|
||||
<aside
|
||||
className="h-100 position-relative sideBar pe-1"
|
||||
// onMouseOver={() => setIsDrawerOpen(true)}
|
||||
// onMouseLeave={() => setIsDrawerOpen(false)}
|
||||
style={{
|
||||
width: isDrawerOpen || openDrawerClick ? 225 : 70,
|
||||
transition: "width 0.3s ease-in-out", // Smooth transition for width change
|
||||
overflow: "hidden", // Hide overflow to prevent content overflow during transition
|
||||
backgroundColor:"#0041180A",
|
||||
// backgroundColor: "#002F0F",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`d-flex ${isDrawerOpen || openDrawerClick ? "justify-content-start" : "justify-content-center"} p-3 pt-3 pb-4 position-relative `}
|
||||
height={"10%"}
|
||||
>
|
||||
{isDrawerOpen || openDrawerClick ? (
|
||||
<img
|
||||
style={{
|
||||
width: 120,
|
||||
}}
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
style={{
|
||||
width: 30,
|
||||
}}
|
||||
src={logoMini}
|
||||
alt="Logo"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="ps-2 scroll-bar "
|
||||
style={{ height: "80%", overflowY: "scroll", overflowX:"hidden" }}
|
||||
>
|
||||
{nav.map(({ title, path, Icon }, index) => (
|
||||
<Box
|
||||
// color={"whitesmoke"}
|
||||
color={"gray.600"}
|
||||
key={index}
|
||||
className=" mb-1 w-100 d-flex "
|
||||
>
|
||||
{path ? (
|
||||
<NavLink
|
||||
style={{
|
||||
height: "auto",
|
||||
}}
|
||||
className={`${
|
||||
isDrawerOpen || openDrawerClick
|
||||
? "p-1 web-text-medium ps-3"
|
||||
: "p-2 ps-1 web-text-xlarge justify-content-center"
|
||||
} rounded-1 link d-flex align-items-center gap-2 w-100 `}
|
||||
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 className="web-text-xxsmall fw-600 mt-1 text-secondary fw-bold">
|
||||
{title}
|
||||
</span>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</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 ">
|
||||
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
|
||||
color={"whitesmoke"}
|
||||
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 ${path === "/" ? "ps-0" : "ps-3" } `}
|
||||
style={{
|
||||
width: `calc(100% - ${isDrawerOpen || openDrawerClick ? 225 : 70}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={"forestGreen"}
|
||||
rounded={"lg"}
|
||||
// onMouseOver={() => setIsDrawerOpen(true)}
|
||||
// onMouseLeave={() => setIsDrawerOpen(false)}
|
||||
onClick={openDrawerOnClick}
|
||||
style={{
|
||||
width: 18,
|
||||
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> */}
|
||||
|
||||
<HeaderMain logOutHandler={logOutHandler} icon title={getTitle()}/>
|
||||
|
||||
<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" }
|
||||
};
|
||||
|
||||
81
src/Pages/Banners/Banner.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Box, Divider } from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import {
|
||||
useGetBuildBannerQuery,
|
||||
useGetCommunityBannerQuery,
|
||||
useGetEcoBannerQuery,
|
||||
useGetHomeBannerQuery,
|
||||
useGetLearnBannerQuery,
|
||||
useGetNewsBannerQuery,
|
||||
} from "../../Services/api.service";
|
||||
import Header from "../../Components/Header";
|
||||
import BannerStack from "../../Components/BannerStack";
|
||||
|
||||
const Banner = () => {
|
||||
const communityBanner = useGetCommunityBannerQuery();
|
||||
const learnBanner = useGetLearnBannerQuery();
|
||||
const buildBanner = useGetBuildBannerQuery();
|
||||
const newsBanner = useGetNewsBannerQuery();
|
||||
const homeBanner = useGetHomeBannerQuery();
|
||||
const ecoBanner = useGetEcoBannerQuery();
|
||||
|
||||
return (
|
||||
<Box {...OPACITY_ON_LOAD} overflowY={"scroll"} height={"100vh"}>
|
||||
<Header title={"Banners"} />
|
||||
|
||||
|
||||
<BannerStack
|
||||
stackTitle={"Home banner"}
|
||||
viewAllLink={"/banner/home"}
|
||||
bannerIsLoading={homeBanner?.isLoading}
|
||||
bannerArray={homeBanner?.data?.data?.rows?.slice(0, 3)}
|
||||
viewBannerLink={"/banner/home/view"}
|
||||
/>
|
||||
|
||||
<Divider/>
|
||||
<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/news"}
|
||||
bannerIsLoading={newsBanner?.isLoading}
|
||||
bannerArray={newsBanner?.data?.data?.rows?.slice(0, 3)}
|
||||
viewBannerLink={"/banner/news/view"}
|
||||
/> */}
|
||||
<Divider />
|
||||
<BannerStack
|
||||
stackTitle={"Ecosystem banner"}
|
||||
viewAllLink={"/banner/eco"}
|
||||
bannerIsLoading={ecoBanner?.isLoading}
|
||||
bannerArray={ecoBanner?.data?.data?.rows?.slice(0, 3)}
|
||||
viewBannerLink={"/banner/eco/view"}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Banner;
|
||||
27
src/Pages/Banners/BannerBuild/BannerBuild.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import {
|
||||
useDeleteBuildBannerMutation,
|
||||
useGetBuildBannerQuery,
|
||||
useUpdateBuildBannerStatusMutation,
|
||||
} from "../../../Services/api.service";
|
||||
import BannerTable from "../../../Components/Banner/BannerTable";
|
||||
|
||||
const BannerBuild = () => {
|
||||
const buildBanner = useGetBuildBannerQuery();
|
||||
const [deleteBuildBanner] = useDeleteBuildBannerMutation();
|
||||
const [updateLearnBuildStatus] = useUpdateBuildBannerStatusMutation();
|
||||
|
||||
return (
|
||||
<BannerTable
|
||||
addLink={"/banner/build/add-banner"}
|
||||
title={"Build Banner"}
|
||||
viewLink={"/banner/build/view/"}
|
||||
editLink={"/banner/build/edit/"}
|
||||
deleteApi={deleteBuildBanner}
|
||||
dataArray={buildBanner}
|
||||
statusUpdateApi={updateLearnBuildStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerBuild;
|
||||
18
src/Pages/Banners/BannerBuild/BannerBuildAdd.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import AddBanner from "../../../Components/Banner/AddBanner";
|
||||
import {
|
||||
useCreateBuildBannerMutation,
|
||||
useCreateLearnBannerMutation,
|
||||
} from "../../../Services/api.service";
|
||||
|
||||
const BannerBuildAdd = () => {
|
||||
const [createBuildBannerData] = useCreateBuildBannerMutation();
|
||||
return (
|
||||
<AddBanner
|
||||
title={"Build banner"}
|
||||
navigateLink={"/banner/build"}
|
||||
createApi={createBuildBannerData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerBuildAdd;
|
||||
15
src/Pages/Banners/BannerBuild/BannerBuildEdit.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import BannerEdit from '../../../Components/Banner/BannerEdit'
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useGetBuildBannerByIdQuery, useUpdateBuildBannerMutation } from '../../../Services/api.service';
|
||||
|
||||
const BannerBuildEdit = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading, refetch } = useGetBuildBannerByIdQuery(id);
|
||||
const [updateBuildBanner] = useUpdateBuildBannerMutation();
|
||||
|
||||
return (<BannerEdit refetch={refetch} navigateTo={'/banner/build'} isLoading={isLoading} data={data} updateBanner={updateBuildBanner} />
|
||||
)
|
||||
}
|
||||
|
||||
export default BannerBuildEdit
|
||||
15
src/Pages/Banners/BannerBuild/BannerBuildView.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetBuildBannerByIdQuery } from "../../../Services/api.service";
|
||||
import BannerView from "../../../Components/Banner/BannerView";
|
||||
|
||||
const BannerBuildView = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useGetBuildBannerByIdQuery(id);
|
||||
|
||||
return (
|
||||
<BannerView editLink={'/banner/build/edit'} isLoading={isLoading} data={data} />
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerBuildView;
|
||||
30
src/Pages/Banners/BannerCommunity/BannerCommunity.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import {
|
||||
useDeleteCommunityBannerMutation,
|
||||
useGetCommunityBannerQuery,
|
||||
useUpdateCommunityBannerStatusMutation,
|
||||
} from "../../../Services/api.service";
|
||||
import BannerTable from "../../../Components/Banner/BannerTable";
|
||||
|
||||
const BannerCommunity = () => {
|
||||
const communityBanner = useGetCommunityBannerQuery();
|
||||
const [deleteCommunityBanner] = useDeleteCommunityBannerMutation();
|
||||
const [updateCommunityBannerStatus] =
|
||||
useUpdateCommunityBannerStatusMutation();
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<BannerTable
|
||||
title={'Community banner'}
|
||||
addLink={"/banner/banner-community/add-banner"}
|
||||
viewLink={"/banner/banner-community/view/"}
|
||||
editLink={"/banner/banner-community/edit/"}
|
||||
deleteApi={deleteCommunityBanner}
|
||||
dataArray={communityBanner}
|
||||
statusUpdateApi={updateCommunityBannerStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerCommunity;
|
||||
16
src/Pages/Banners/BannerCommunity/BannerCommunityAdd.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useCreateCommunityBannerMutation } from "../../../Services/api.service";
|
||||
import AddBanner from "../../../Components/Banner/AddBanner";
|
||||
|
||||
const BannerCommunityAdd = () => {
|
||||
const [createCommunityBannerData] = useCreateCommunityBannerMutation();
|
||||
|
||||
return (
|
||||
<AddBanner
|
||||
title={"Learn banner"}
|
||||
navigateLink={"/banner/banner-community"}
|
||||
createApi={createCommunityBannerData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerCommunityAdd;
|
||||
17
src/Pages/Banners/BannerCommunity/BannerCommunityEdit.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
useGetCommunityBannerByIdQuery,
|
||||
useUpdateCommunityBannerMutation,
|
||||
} from "../../../Services/api.service";
|
||||
import BannerEdit from "../../../Components/Banner/BannerEdit";
|
||||
|
||||
const BannerComunityEditPage = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading, refetch } = useGetCommunityBannerByIdQuery(id);
|
||||
const [updateCommunityBanner] = useUpdateCommunityBannerMutation();
|
||||
|
||||
|
||||
return <BannerEdit refetch={refetch} navigateTo={'/banner/banner-community'} isLoading={isLoading} data={data} updateBanner={updateCommunityBanner} />
|
||||
};
|
||||
|
||||
export default BannerComunityEditPage;
|
||||
27
src/Pages/Banners/BannerCommunity/BannerCommunityView.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetCommunityBannerByIdQuery } 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";
|
||||
import BannerView from "../../../Components/Banner/BannerView";
|
||||
|
||||
const BannerComunityViewPage = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useGetCommunityBannerByIdQuery(id);
|
||||
|
||||
return <BannerView editLink={'/banner/banner-community/edit'} isLoading={isLoading} data={data} />;
|
||||
|
||||
};
|
||||
|
||||
export default BannerComunityViewPage;
|
||||
27
src/Pages/Banners/BannerLearn/BannerLearn.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import {
|
||||
useDeleteLearnBannerMutation,
|
||||
useGetLearnBannerQuery,
|
||||
useUpdateLearnBannerStatusMutation,
|
||||
} from "../../../Services/api.service";
|
||||
import BannerTable from "../../../Components/Banner/BannerTable";
|
||||
|
||||
const BannerLearn = () => {
|
||||
const learnBanner = useGetLearnBannerQuery();
|
||||
const [deleteLearnBanner] = useDeleteLearnBannerMutation();
|
||||
const [updateLearnBannerStatus] = useUpdateLearnBannerStatusMutation();
|
||||
|
||||
return (
|
||||
<BannerTable
|
||||
addLink={"/banner/learn/add-banner"}
|
||||
title={"Learn Banner"}
|
||||
viewLink={"/banner/learn/view/"}
|
||||
editLink={"/banner/learn/edit/"}
|
||||
deleteApi={deleteLearnBanner}
|
||||
dataArray={learnBanner}
|
||||
statusUpdateApi={updateLearnBannerStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerLearn;
|
||||
15
src/Pages/Banners/BannerLearn/BannerLearnAdd.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import AddBanner from "../../../Components/Banner/AddBanner";
|
||||
import { useCreateLearnBannerMutation } from "../../../Services/api.service";
|
||||
|
||||
const BannerLearnAdd = () => {
|
||||
const [createLearnBannerData] = useCreateLearnBannerMutation();
|
||||
return (
|
||||
<AddBanner
|
||||
title={"Learn banner"}
|
||||
navigateLink={"/banner/learn"}
|
||||
createApi={createLearnBannerData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerLearnAdd;
|
||||
15
src/Pages/Banners/BannerLearn/BannerLearnEdit.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import BannerEdit from '../../../Components/Banner/BannerEdit'
|
||||
import { useGetCommunityBannerByIdQuery, useGetLearnBannerByIdQuery, useUpdateCommunityBannerMutation, useUpdateLearnBannerMutation } from '../../../Services/api.service';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const BannerLearnEdit = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading, refetch} = useGetLearnBannerByIdQuery(id);
|
||||
const [updateLearnBanner] = useUpdateLearnBannerMutation();
|
||||
return (
|
||||
<BannerEdit refetch={refetch} navigateTo="/banner/learn" isLoading={isLoading} data={data} updateBanner={updateLearnBanner} />
|
||||
)
|
||||
}
|
||||
|
||||
export default BannerLearnEdit
|
||||
14
src/Pages/Banners/BannerLearn/BannerLearnView.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetLearnBannerByIdQuery } from "../../../Services/api.service";
|
||||
import BannerView from "../../../Components/Banner/BannerView";
|
||||
|
||||
const BannerLearnView = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useGetLearnBannerByIdQuery(id);
|
||||
|
||||
|
||||
return <BannerView editLink={'/banner/learn/edit'} isLoading={isLoading} data={data} />;
|
||||
};
|
||||
|
||||
export default BannerLearnView;
|
||||
33
src/Pages/Banners/BannerNews/BannerNews.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import {
|
||||
useDeleteBuildBannerMutation,
|
||||
useDeleteLearnBannerMutation,
|
||||
useDeleteNewsBannerMutation,
|
||||
useGetBuildBannerQuery,
|
||||
useGetLearnBannerQuery,
|
||||
useGetNewsBannerQuery,
|
||||
useUpdateBuildBannerStatusMutation,
|
||||
useUpdateLearnBannerStatusMutation,
|
||||
useUpdateNewsBannerStatusMutation,
|
||||
} from "../../../Services/api.service";
|
||||
import BannerTable from "../../../Components/Banner/BannerTable";
|
||||
|
||||
const BannerNews = () => {
|
||||
const newsBanner = useGetNewsBannerQuery();
|
||||
const [deleteNewsBanner] = useDeleteNewsBannerMutation();
|
||||
const [updateNewsBuildStatus] = useUpdateNewsBannerStatusMutation();
|
||||
const { data, error, isLoading } = useUpdateNewsBannerStatusMutation();
|
||||
return (
|
||||
<BannerTable
|
||||
addLink={"/banner/news/add-banner"}
|
||||
title={"News Banner"}
|
||||
viewLink={"/banner/news/view/"}
|
||||
editLink={"/banner/news/edit/"}
|
||||
deleteApi={deleteNewsBanner}
|
||||
dataArray={newsBanner}
|
||||
statusUpdateApi={updateNewsBuildStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerNews;
|
||||
15
src/Pages/Banners/BannerNews/BannerNewsAdd.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import AddBanner from "../../../Components/Banner/AddBanner";
|
||||
import { useCreateLearnBannerMutation, useCreateNewsBannerMutation } from "../../../Services/api.service";
|
||||
|
||||
const BannerNewsAdd = () => {
|
||||
const [createNewsBannerData] = useCreateNewsBannerMutation();
|
||||
return (
|
||||
<AddBanner
|
||||
title={"News banner"}
|
||||
navigateLink={"/banner/news"}
|
||||
createApi={createNewsBannerData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerNewsAdd;
|
||||
15
src/Pages/Banners/BannerNews/BannerNewsEdit.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import BannerEdit from '../../../Components/Banner/BannerEdit'
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useGetNewsBannerByIdQuery, useUpdateNewsBannerMutation } from '../../../Services/api.service';
|
||||
|
||||
const BannerNewsEdit = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useGetNewsBannerByIdQuery(id);
|
||||
const [updateNewsBanner] = useUpdateNewsBannerMutation();
|
||||
return (
|
||||
<BannerEdit navigateTo="/banner/build" isLoading={isLoading} data={data} updateBanner={updateNewsBanner} />
|
||||
)
|
||||
}
|
||||
|
||||
export default BannerNewsEdit
|
||||
14
src/Pages/Banners/BannerNews/BannerNewsView.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetLearnBannerByIdQuery, useGetNewsBannerByIdQuery } from "../../../Services/api.service";
|
||||
import BannerView from "../../../Components/Banner/BannerView";
|
||||
|
||||
const BannerNewsView = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useGetNewsBannerByIdQuery(id);
|
||||
|
||||
|
||||
return <BannerView editLink={'/banner/news/edit'} isLoading={isLoading} data={data} />;
|
||||
};
|
||||
|
||||
export default BannerNewsView;
|
||||
36
src/Pages/Banners/EcoBanner/EcoBanner.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import {
|
||||
useDeleteBuildBannerMutation,
|
||||
useDeleteEcoBannerMutation,
|
||||
useDeleteLearnBannerMutation,
|
||||
useDeleteNewsBannerMutation,
|
||||
useGetBuildBannerQuery,
|
||||
useGetEcoBannerQuery,
|
||||
useGetLearnBannerQuery,
|
||||
useGetNewsBannerQuery,
|
||||
useUpdateBuildBannerStatusMutation,
|
||||
useUpdateEcoBannerStatusMutation,
|
||||
useUpdateLearnBannerStatusMutation,
|
||||
useUpdateNewsBannerStatusMutation,
|
||||
} from "../../../Services/api.service";
|
||||
import BannerTable from "../../../Components/Banner/BannerTable";
|
||||
|
||||
const EcoBanner = () => {
|
||||
const ecoBanner = useGetEcoBannerQuery();
|
||||
const [deleteEcoBanner] = useDeleteEcoBannerMutation();
|
||||
const [updateEcoBuildStatus] = useUpdateEcoBannerStatusMutation();
|
||||
const { data, error, isLoading } = useUpdateNewsBannerStatusMutation();
|
||||
return (
|
||||
<BannerTable
|
||||
addLink={"/banner/eco/add-banner"}
|
||||
title={"Ecosystem Banner"}
|
||||
viewLink={"/banner/eco/view/"}
|
||||
editLink={"/banner/eco/edit/"}
|
||||
deleteApi={deleteEcoBanner}
|
||||
dataArray={ecoBanner}
|
||||
statusUpdateApi={updateEcoBuildStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EcoBanner;
|
||||
15
src/Pages/Banners/EcoBanner/EcoBannerAdd.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import AddBanner from "../../../Components/Banner/AddBanner";
|
||||
import { useCreateEcoBannerMutation, useCreateLearnBannerMutation, useCreateNewsBannerMutation } from "../../../Services/api.service";
|
||||
|
||||
const EcoBannerAdd = () => {
|
||||
const [createEcoBannerData] = useCreateEcoBannerMutation();
|
||||
return (
|
||||
<AddBanner
|
||||
title={"Ecosystem banner"}
|
||||
navigateLink={"/banner/eco"}
|
||||
createApi={createEcoBannerData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EcoBannerAdd;
|
||||
15
src/Pages/Banners/EcoBanner/EcoBannerEdit.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import BannerEdit from '../../../Components/Banner/BannerEdit'
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useGetEcoBannerByIdQuery, useGetNewsBannerByIdQuery, useUpdateEcoBannerMutation, useUpdateNewsBannerMutation } from '../../../Services/api.service';
|
||||
|
||||
const EcoBannerEdit = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading, refetch } = useGetEcoBannerByIdQuery(id);
|
||||
const [updateNewsBanner] = useUpdateEcoBannerMutation();
|
||||
return (
|
||||
<BannerEdit refetch={refetch} navigateTo="/banner/eco" isLoading={isLoading} data={data} updateBanner={updateNewsBanner} />
|
||||
)
|
||||
}
|
||||
|
||||
export default EcoBannerEdit
|
||||
14
src/Pages/Banners/EcoBanner/EcoBannerView.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetEcoBannerByIdQuery, useGetLearnBannerByIdQuery, useGetNewsBannerByIdQuery } from "../../../Services/api.service";
|
||||
import BannerView from "../../../Components/Banner/BannerView";
|
||||
|
||||
const EcoBannerView = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useGetEcoBannerByIdQuery(id);
|
||||
|
||||
|
||||
return <BannerView editLink={'/banner/eco/edit'} isLoading={isLoading} data={data} />;
|
||||
};
|
||||
|
||||
export default EcoBannerView;
|
||||
39
src/Pages/Banners/HomeBanner/HomeBanner.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import {
|
||||
useDeleteBuildBannerMutation,
|
||||
useDeleteHomeBannerMutation,
|
||||
useDeleteLearnBannerMutation,
|
||||
useDeleteNewsBannerMutation,
|
||||
useGetBuildBannerQuery,
|
||||
useGetHomeBannerQuery,
|
||||
useGetLearnBannerQuery,
|
||||
useGetNewsBannerQuery,
|
||||
useUpdateBuildBannerStatusMutation,
|
||||
useUpdateHomeBannerStatusMutation,
|
||||
useUpdateLearnBannerStatusMutation,
|
||||
useUpdateNewsBannerStatusMutation,
|
||||
} from "../../../Services/api.service";
|
||||
import BannerTable from "../../../Components/Banner/BannerTable";
|
||||
|
||||
const HomeBanner = () => {
|
||||
const homeBanner = useGetHomeBannerQuery();
|
||||
|
||||
|
||||
const [deleteHomeBanner] = useDeleteHomeBannerMutation();
|
||||
|
||||
const [updateHomeBuildStatus] = useUpdateHomeBannerStatusMutation();
|
||||
const { data, error, isLoading } = useUpdateNewsBannerStatusMutation();
|
||||
return (
|
||||
<BannerTable
|
||||
addLink={"/banner/home/add-banner"}
|
||||
title={"Home Banner"}
|
||||
viewLink={"/banner/home/view/"}
|
||||
editLink={"/banner/home/edit/"}
|
||||
deleteApi={deleteHomeBanner}
|
||||
dataArray={homeBanner}
|
||||
statusUpdateApi={updateHomeBuildStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeBanner;
|
||||
16
src/Pages/Banners/HomeBanner/HomeBannerAdd.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import AddBanner from "../../../Components/Banner/AddBanner";
|
||||
import { useCreateHomeBannerMutation } from "../../../Services/api.service";
|
||||
|
||||
const HomeBannerAdd = () => {
|
||||
const [createLearnBannerData] = useCreateHomeBannerMutation();
|
||||
return (
|
||||
<AddBanner
|
||||
center={true}
|
||||
title={"Home banner"}
|
||||
navigateLink={"/banner/home"}
|
||||
createApi={createLearnBannerData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeBannerAdd;
|
||||
18
src/Pages/Banners/HomeBanner/HomeBannerEdit.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import BannerEdit from '../../../Components/Banner/BannerEdit'
|
||||
import { useGetCommunityBannerByIdQuery, useGetHomeBannerByIdQuery, useGetLearnBannerByIdQuery, useUpdateCommunityBannerMutation, useUpdateHomeBannerMutation, useUpdateLearnBannerMutation } from '../../../Services/api.service';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const BannerHomeEdit = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading, refetch } = useGetHomeBannerByIdQuery(id);
|
||||
const [updateLearnBanner] = useUpdateHomeBannerMutation();
|
||||
// useEffect(() => {
|
||||
// refetch();
|
||||
// }, [id, refetch]);
|
||||
return (
|
||||
<BannerEdit center={true} refetch={refetch} navigateTo="/banner/home" isLoading={isLoading} data={data} updateBanner={updateLearnBanner} />
|
||||
)
|
||||
}
|
||||
|
||||
export default BannerHomeEdit
|
||||
14
src/Pages/Banners/HomeBanner/HomeBannerView.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetHomeBannerByIdQuery, useGetNewsBannerByIdQuery } from "../../../Services/api.service";
|
||||
import BannerView from "../../../Components/Banner/BannerView";
|
||||
|
||||
const HomeBannerView = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useGetHomeBannerByIdQuery(id);
|
||||
|
||||
|
||||
return <BannerView center={true} editLink={'/banner/home/edit'} isLoading={isLoading} data={data} />;
|
||||
};
|
||||
|
||||
export default HomeBannerView;
|
||||
772
src/Pages/BlogsAndArticles/AddBlogsAndArticles.jsx
Normal file
@@ -0,0 +1,772 @@
|
||||
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";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
|
||||
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 [metaDescription, setMetaDescription] = useState("");
|
||||
|
||||
const handleDescriptionChange = (e) => {
|
||||
setMetaDescription(e.target.value);
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setValue: setYupFormValue,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(addBlogSchema),
|
||||
});
|
||||
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
setYupFormValue('content', value)
|
||||
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({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
reset();
|
||||
navigate("/blogs-articles");
|
||||
}else if(response?.error?.status === 500){
|
||||
setIsLoading(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.error?.status?.error?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
}
|
||||
})
|
||||
.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);
|
||||
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 10MB.
|
||||
</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 summary
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
rows={2}
|
||||
{...register("summary")}
|
||||
placeholder="Summary"
|
||||
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 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"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={metaDescription.length > 160}
|
||||
onChange={handleDescriptionChange}
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
<FormHelperText
|
||||
color={
|
||||
metaDescription.length > 160
|
||||
? "red"
|
||||
: "green.400"
|
||||
}
|
||||
|
||||
|
||||
fontWeight={""
|
||||
}
|
||||
className="web-text-small"
|
||||
>
|
||||
If description crosses 160 characters it will cause problem in SEO
|
||||
optimisation.you have entered {metaDescription.length} characters
|
||||
</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 enter your content here. You can format your text using the toolbar above.
|
||||
</FormHelperText>
|
||||
</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={50}
|
||||
/>
|
||||
<FormHelperText className="web-text-small">
|
||||
Maximum characters must be 50 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 10MB.
|
||||
</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 10MB.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end">
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
size="sm"
|
||||
rounded={"sm"}
|
||||
>
|
||||
Create blog
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddBlogsAndArticles;
|
||||
299
src/Pages/BlogsAndArticles/BlogsAndArticles.jsx
Normal file
@@ -0,0 +1,299 @@
|
||||
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,
|
||||
Badge,
|
||||
} 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";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
import TabularView from "../../Components/TabularView/TabularView";
|
||||
import { TABLE_PAGINATION } from "../../Constants/Paginations";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const BlogsAndArticles = () => {
|
||||
// ====================================================[Hooks]===================================================================
|
||||
const toast = useToast();
|
||||
const [pageSize, setPageSize] = useState(TABLE_PAGINATION?.size);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
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({ page: currentPage, size: pageSize });
|
||||
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) => {
|
||||
if (response?.data?.statusCode === 201) {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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.author_name;
|
||||
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={`${API_URL}/${item.profile_image}`}
|
||||
/>
|
||||
<span className="d-flex flex-column">
|
||||
<Text
|
||||
as={"span"}
|
||||
color={"gray.600"}
|
||||
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={400}>
|
||||
<Text as={"span"} isTruncated={true}>
|
||||
{item?.summary}
|
||||
</Text>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
),
|
||||
Tags: (
|
||||
<Box
|
||||
display={"flex"}
|
||||
flexWrap={"wrap"}
|
||||
gap={1}
|
||||
alignItems={"center"}
|
||||
w={200}
|
||||
>
|
||||
{item?.tags?.map(({ id, tag }) => (
|
||||
<Badge rounded={'full'} key={id} variant="solid" fontWeight={'normal'} size={"sm"} ps={3} pe={3} pt={0.5} pb={0.5} backgroundColor={'#565263'}>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</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={"gray.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 (
|
||||
<>
|
||||
<TabularView
|
||||
title={"Blogs and articles"}
|
||||
btnTitle={"Create Blog"}
|
||||
link={"/blogs-articles/add-blog"}
|
||||
apiData={blog}
|
||||
tableHeadRow={tableHeadRow}
|
||||
extractedArray={extractedArray}
|
||||
searchTerm={searchTerm}
|
||||
setSearchTerm={setSearchTerm}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
totalItems={blog?.data?.data?.totalItems}
|
||||
totalPages={blog?.data?.data?.totalPages}
|
||||
noDataTitle={'blog'}
|
||||
/>
|
||||
<CustomAlertDialog
|
||||
onClose={() => setDeleteAlert(false)}
|
||||
isOpen={deleteAlert}
|
||||
alertHandler={() => handleDelete(actionId)}
|
||||
message={"Are you sure you want to delete blogs?"}
|
||||
isLoading={deleteIsLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogsAndArticles;
|
||||
703
src/Pages/BlogsAndArticles/EditBlogsAndArticles.jsx
Normal file
@@ -0,0 +1,703 @@
|
||||
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, editBlogSchema } 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";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const EditBlogsAndArticles = () => {
|
||||
const { id } = useParams();
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [updateBlog] = useUpdateBlogMutation();
|
||||
const { data, error, isLoading, refetch } = 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(null);
|
||||
const [smallImageData, setSmallImageData] = useState(null);
|
||||
const [chips, setChips] = useState();
|
||||
|
||||
|
||||
const [valueQuill, setValueQuill] = useState(blog?.content);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
} = useForm({
|
||||
resolver: yupResolver(editBlogSchema),
|
||||
defaultValues: {
|
||||
author_name: "",
|
||||
author_designation: "",
|
||||
meta_description: "",
|
||||
title: "",
|
||||
category: "",
|
||||
summary: "",
|
||||
content: "",
|
||||
profile_image: "",
|
||||
content_image_large: "",
|
||||
tags: "",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data) {
|
||||
setSelectedImage(`${API_URL}/${blog?.profile_image}`);
|
||||
setSelectedImageLarge(
|
||||
`${API_URL}/${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);
|
||||
setValueQuill(blog?.content);
|
||||
setChips(blog?.tags?.map((tagObject) => tagObject.tag));
|
||||
}
|
||||
}, [data, blog, setValue]);
|
||||
|
||||
const onSubmit = async (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);
|
||||
if (response?.data?.statusCode === 201) {
|
||||
setIsLoading01(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
navigate("/blogs-articles");
|
||||
} else if (response?.error?.status === 500) {
|
||||
setIsLoading01(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.error?.status?.error?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setIsLoading01(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"error"} message={"File size too large"} />
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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"
|
||||
>
|
||||
|
||||
<Image
|
||||
shadow={"md"}
|
||||
rounded={8}
|
||||
w={500}
|
||||
h={240}
|
||||
src={selectedImageLarge}
|
||||
alt="Selected Image"
|
||||
/>
|
||||
{selectedImageLarge === fallbackImageLarge ? (
|
||||
""
|
||||
) : (
|
||||
largeImageData && <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"
|
||||
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" isRequired >
|
||||
<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>
|
||||
<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 10MB.
|
||||
</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 && <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 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 10MB.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end">
|
||||
<Button
|
||||
isLoading={isLoading01}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
size="sm"
|
||||
rounded={"sm"}
|
||||
>
|
||||
Save edit
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditBlogsAndArticles;
|
||||
228
src/Pages/BlogsAndArticles/ViewBlogsAndArticles.jsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import {
|
||||
Badge,
|
||||
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 API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
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 />;
|
||||
}
|
||||
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={`${API_URL}/${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 className="d-flex flex-column align-items-start gap-1 mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Status
|
||||
</Box>
|
||||
{blog.active_blog ? (
|
||||
<Badge size={"sm"} colorScheme="teal">
|
||||
Active
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
size={"sm"}
|
||||
variant="solid"
|
||||
colorScheme="red"
|
||||
>
|
||||
Inactive
|
||||
</Badge>
|
||||
)}
|
||||
</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 }) => (
|
||||
<Badge rounded={'full'} key={id} variant="solid" size={"sm"} ps={3} pe={3} pt={0.5} pb={0.5} backgroundColor={'#565263'}>
|
||||
{tag}
|
||||
</Badge>
|
||||
|
||||
))}
|
||||
</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"} mb={6}>
|
||||
<Box className="col-5 d-flex flex-column gap-2 pt-">
|
||||
<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
|
||||
className="d-flex w-100 justify-content-center flex-column align-items-center "
|
||||
>
|
||||
<Image
|
||||
shadow={"md"}
|
||||
rounded={8}
|
||||
w={214}
|
||||
h={240}
|
||||
src={`${API_URL}/${blog?.profile_image}`}
|
||||
alt="Selected Image"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="col-7 pt-0 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;
|
||||
384
src/Pages/Community/AddComunity.jsx
Normal file
@@ -0,0 +1,384 @@
|
||||
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";
|
||||
import { CloseIcon } from "@chakra-ui/icons";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
|
||||
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 [imageData, setImageData] = useState(null);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
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({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
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];
|
||||
setImageData(file);
|
||||
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 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"
|
||||
/>
|
||||
{selectedImage === fallbackImage || imageData === null ? (
|
||||
""
|
||||
) : (
|
||||
<Box w={"100%"} display={"flex"} justifyContent={'space-between'} >
|
||||
<Box display={"flex"} flexDirection={"column"}>
|
||||
<span className="web-text-small">{imageData?.name}</span>
|
||||
<span className="web-text-small text-secondary fst-italic">
|
||||
{(imageData?.size / (1024 * 1024)).toFixed(2)} mb
|
||||
</span>
|
||||
</Box>
|
||||
<Box onClick={()=> setSelectedImage(fallbackImage)} className=" web-text-large link rounded-2 pointer p-1" as="span" >
|
||||
<CloseIcon className="web-text-small" /></Box>
|
||||
</Box>
|
||||
)}
|
||||
</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"
|
||||
minLength={4}
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.member_name?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.member_name?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If name crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered {watch()?.member_name?.length}{" "}
|
||||
characters
|
||||
</FormHelperText>
|
||||
{errors.member_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}
|
||||
minLength={4}
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.description?.length > 230}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.description?.length > 230 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If title crosses 230 characters it will cause problem in
|
||||
alignment on website.you have entered {watch()?.description?.length}{" "}
|
||||
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 10MB.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end ">
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
size="sm"
|
||||
rounded={"sm"}
|
||||
>
|
||||
Create member
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddComunity;
|
||||
86
src/Pages/Community/CommCard.jsx
Normal file
@@ -0,0 +1,86 @@
|
||||
/* 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 API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
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={`${API_URL}/${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;
|
||||
324
src/Pages/Community/Community.jsx
Normal file
@@ -0,0 +1,324 @@
|
||||
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";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
import TabularView from "../../Components/TabularView/TabularView";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
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) => {
|
||||
if (response?.data?.statusCode === 201) {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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",
|
||||
"Description",
|
||||
"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={`${API_URL}/${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>
|
||||
),
|
||||
Description: (
|
||||
<Box w={350} isTruncated={true}>
|
||||
<Text as={"span"} color={"teal.900"}>
|
||||
{item?.description}
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
"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={"gray.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 (
|
||||
<>
|
||||
<TabularView
|
||||
title={"Community"}
|
||||
btnTitle={"Create member"}
|
||||
link={"/community/add-community"}
|
||||
apiData={community}
|
||||
tableHeadRow={tableHeadRow}
|
||||
extractedArray={extractedArray}
|
||||
searchTerm={searchTerm}
|
||||
setSearchTerm={setSearchTerm}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
totalItems={community?.data?.data?.totalItems}
|
||||
totalPages={community?.data?.data?.totalPages}
|
||||
noDataTitle={"community's"}
|
||||
/>
|
||||
|
||||
{/* ====================================================[ Alert ]================================================================ */}
|
||||
<CustomAlertDialog
|
||||
onClose={() => setDeleteAlert(false)}
|
||||
isOpen={deleteAlert}
|
||||
alertHandler={() => handleDelete(actionId)}
|
||||
message={"Are you sure you want to delete community?"}
|
||||
isLoading={deleteIsLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Community;
|
||||
188
src/Pages/Community/CommunityBannerCard.jsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Heading,
|
||||
Image,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import WebButton from "../../Components/WebButton";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const CommunityBannerCard = ({
|
||||
bgImage,
|
||||
subHeading,
|
||||
heading,
|
||||
ctoBtnTitle,
|
||||
createdAt,
|
||||
status,
|
||||
}) => {
|
||||
return (
|
||||
<Card
|
||||
color={"teal.900"}
|
||||
w={"100%"}
|
||||
h={"100%"}
|
||||
size={"md"}
|
||||
// boxShadow="md"
|
||||
// overflow={"hidden"}
|
||||
position={"relative"}
|
||||
// rounded={"lg"}
|
||||
boxShadow={
|
||||
" rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px"
|
||||
}
|
||||
|
||||
borderRadius={8.5}
|
||||
>
|
||||
<CardBody
|
||||
position={"absolute"}
|
||||
h={"100%"}
|
||||
w={"100%"}
|
||||
bottom={0}
|
||||
// border={"1px solid #9B4651"}
|
||||
borderRadius={'sm'}
|
||||
overflow={"hidden"}
|
||||
bgImage={`${API_URL}/${bgImage}`}
|
||||
bgSize="cover"
|
||||
bgPosition="center"
|
||||
// backgroundColor={status ? "#ffe5ea": '#ffffff'}
|
||||
backdropFilter="blur(1px)"
|
||||
>
|
||||
{status ? (
|
||||
<Badge
|
||||
position={"absolute"}
|
||||
top={2}
|
||||
right={-2}
|
||||
pe={3}
|
||||
colorScheme="green"
|
||||
variant='solid'
|
||||
>
|
||||
Active
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
position={"absolute"}
|
||||
top={2}
|
||||
right={-2}
|
||||
pe={3}
|
||||
colorScheme="red"
|
||||
variant='solid'
|
||||
>
|
||||
Inactive
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<Box
|
||||
mb={0}
|
||||
display={"flex"}
|
||||
justifyContent={"space-between"}
|
||||
h={"100%"}
|
||||
w={"100%"}
|
||||
>
|
||||
<Box
|
||||
w={40}
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
alignItems={"start"}
|
||||
>
|
||||
<Tooltip
|
||||
className="rounded-2 web-text-xsmall"
|
||||
width={"fit-content"}
|
||||
placement="top"
|
||||
hasArrow
|
||||
label={heading}
|
||||
bg="blue.200"
|
||||
>
|
||||
<Box
|
||||
display={"flex"}
|
||||
className="web-text-large fw-bold"
|
||||
alignItems={"center"}
|
||||
w={"100%"}
|
||||
>
|
||||
<Text color={"#DE858E"} as={"span"} isTruncated={true}>
|
||||
{heading}
|
||||
</Text>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
<Box
|
||||
display={"flex"}
|
||||
className="web-text-large fw-bold"
|
||||
alignItems={"center"}
|
||||
w={180}
|
||||
>
|
||||
<Tooltip
|
||||
className="rounded-2 web-text-xsmall"
|
||||
width={"fit-content"}
|
||||
placement="top"
|
||||
hasArrow
|
||||
label={subHeading}
|
||||
bg="blue.200"
|
||||
>
|
||||
<Text
|
||||
className="web-text-medium"
|
||||
as={"span"}
|
||||
isTruncated={true}
|
||||
color={'whitesmoke'}
|
||||
>
|
||||
{subHeading}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Button
|
||||
fontWeight={"normal"}
|
||||
className="web-text-xsmall"
|
||||
ps={3}
|
||||
pe={3}
|
||||
pt={1}
|
||||
pb={1}
|
||||
mt={2}
|
||||
color={"#ffffff"}
|
||||
_hover={{
|
||||
bgGradient:"linear(to-r, #1E114B, purple)"
|
||||
}}
|
||||
// bg={'#1E114B'}
|
||||
// bgGradient="linear(to-r, #1E114B, purple)"
|
||||
variant={"outline"}
|
||||
// colorScheme="purple"
|
||||
rounded={"sm"}
|
||||
size={"xs"}
|
||||
// border={'1px soild #fff'}
|
||||
|
||||
>
|
||||
{ctoBtnTitle}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* <Box display={"flex"} alignItems={"center"}>
|
||||
<Image
|
||||
boxShadow={"inner"}
|
||||
rounded={"md"}
|
||||
w={130}
|
||||
h={"75px"}
|
||||
src={`${API_URL}/${bgImage}`}
|
||||
/>
|
||||
</Box> */}
|
||||
</Box>
|
||||
</CardBody>
|
||||
<span
|
||||
className="web-text-xsmall text-secondary fw-bold"
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 4,
|
||||
right: 10,
|
||||
opacity: 1,
|
||||
}}
|
||||
>
|
||||
{formatDate(createdAt)}
|
||||
</span>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommunityBannerCard;
|
||||
71
src/Pages/Community/CommunityCardDisplay.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
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();
|
||||
const communityData = community.data?.data?.rows
|
||||
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
|
||||
455
src/Pages/Community/ComunityEditPage.jsx
Normal file
@@ -0,0 +1,455 @@
|
||||
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";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
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,
|
||||
watch,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
} = useForm({
|
||||
resolver: yupResolver(addCommunitySchema),
|
||||
defaultValues: {
|
||||
member_name: "",
|
||||
designation: "",
|
||||
description: "",
|
||||
linkedin: "",
|
||||
profile_image: null,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data) {
|
||||
setSelectedImage(
|
||||
`${API_URL}/${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) {
|
||||
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({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
navigate("/community");
|
||||
// setDeleteAlert(false);
|
||||
} else {
|
||||
setIsLoading01(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"error"} message={"File size too large"} />
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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"
|
||||
minLength={4}
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.member_name?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.member_name?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If name crosses 50 characters it will cause problem in alignment
|
||||
on website.you have entered {watch()?.member_name?.length}{" "}
|
||||
characters
|
||||
</FormHelperText>
|
||||
{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}
|
||||
minLength={4}
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.description?.length > 230}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.description?.length > 230 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If Description crosses 230 characters it will cause problem in
|
||||
alignment on website.you have entered {watch()?.description?.length}{" "}
|
||||
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 10MB.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end ">
|
||||
<Button
|
||||
isLoading={isLoadingEdit}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
size="sm"
|
||||
rounded={"sm"}
|
||||
>
|
||||
Save edit
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComunityEditPage;
|
||||
158
src/Pages/Community/ComunityViewPage.jsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import {
|
||||
useGetCommunityByIdQuery,
|
||||
} from "../../Services/api.service";
|
||||
import BannerView from "../../Components/Banner/BannerView";
|
||||
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
|
||||
import { Box, Divider, Image, Tag } from "@chakra-ui/react";
|
||||
import Header from "../../Components/Header";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const ComunityViewPage = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useGetCommunityByIdQuery(id);
|
||||
|
||||
|
||||
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={`/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">
|
||||
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={214}
|
||||
h={240}
|
||||
src={`${API_URL}/${data?.data?.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">
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark mb-1">
|
||||
Status
|
||||
</Box>
|
||||
{data?.data?.status ? (
|
||||
<Tag size={"sm"} variant="solid" 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">
|
||||
Author name
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{data?.data?.member_name}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Designation
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{data?.data?.designation}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Description
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{data?.data?.description}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Linked In
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{data?.data?.linkedin}
|
||||
</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(data?.data?.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(data?.data?.updatedAt)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComunityViewPage;
|
||||
582
src/Pages/Events/AddEvents.jsx
Normal file
@@ -0,0 +1,582 @@
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import Header from "../../Components/Header";
|
||||
import {
|
||||
AspectRatio,
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Heading,
|
||||
Image,
|
||||
Input,
|
||||
Stack,
|
||||
Textarea,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import fallbackImage from "../../assets/ultp-fallback-img.webp";
|
||||
import { useState } from "react";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { addEvents } from "../../Validations/Validations";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
import Loader01 from "../../Components/Loaders/Loader01";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { motion } from "framer-motion";
|
||||
import { useCreateEventsMutation } from "../../Services/api.service";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
|
||||
const AddEvents = () => {
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [createEvents] = useCreateEventsMutation(); // Invoke the hook to get the mutation function
|
||||
const [selectedImage, setSelectedImage] = useState(fallbackImage);
|
||||
const [largeImageData, setLargeImageData] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [eventDatesInput, setEventsDatesInputs] = useState([]);
|
||||
|
||||
|
||||
// Function to handle adding new date inputs
|
||||
const addDateInput = () => {
|
||||
setEventsDatesInputs([...eventDatesInput, ""]);
|
||||
};
|
||||
|
||||
// Function to handle the change in date inputs
|
||||
const handleDateChange = (index, event) => {
|
||||
const newDates = [...eventDatesInput];
|
||||
newDates[index] = event.target.value;
|
||||
setEventsDatesInputs(newDates);
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(addEvents),
|
||||
});
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = (data) => {
|
||||
setIsLoading(true);
|
||||
const formData = new FormData();
|
||||
formData.append("title", data.title);
|
||||
formData.append("content", data.content);
|
||||
formData.append("location", data.location);
|
||||
formData.append("organizer_name", data.organizer_name);
|
||||
formData.append("organizer_mobile_number", data.organizer_mobile_number);
|
||||
formData.append("organizer_email", data.organizer_email);
|
||||
|
||||
if (eventDatesInput.length === 0 || eventDatesInput[0]==="") {
|
||||
setIsLoading(false);
|
||||
return toast({
|
||||
render: () => (
|
||||
<ToastBox status={"warn"} message={"Please add events date"} />
|
||||
),
|
||||
});
|
||||
} else {
|
||||
eventDatesInput.forEach((date, index) => {
|
||||
formData.append(`dates[${index}]`, date);
|
||||
});
|
||||
}
|
||||
|
||||
if (data.banner_image[0]) {
|
||||
formData.append("banner_image", data.banner_image[0]);
|
||||
}
|
||||
|
||||
// for (const [key, value] of formData.entries()) {
|
||||
// // console.log(`${key}: ${value}`);
|
||||
// }
|
||||
|
||||
// Trigger the mutationconst
|
||||
const res = createEvents(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("/events");
|
||||
} else if (response?.data?.statusCode === 500) {
|
||||
setIsLoading(false);
|
||||
toast({
|
||||
title: response?.data?.message,
|
||||
status: "error", // Change status to error
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// Handle errors
|
||||
// // console.error("Error creating community:", error?.message);
|
||||
setIsLoading(false);
|
||||
// Handle error notification if needed
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...OPACITY_ON_LOAD}
|
||||
w={"100%"}
|
||||
h={"100vh"}
|
||||
|
||||
className="overflow-auto "
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
>
|
||||
<Header title={"Events"} />
|
||||
|
||||
|
||||
{/* <Box width="100%" maxWidth="1200px" mx="auto" my={4}>
|
||||
<AspectRatio ratio={16 / 6}>
|
||||
<iframe
|
||||
title="naruto"
|
||||
src="https://rubix.betadelivery.com/events"
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 'none' }}
|
||||
allowFullScreen
|
||||
/>
|
||||
</AspectRatio>
|
||||
</Box> */}
|
||||
|
||||
<Box className="d-flex">
|
||||
<form className="w-100" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Box display={"flex"}>
|
||||
<Box className="col-5 d-flex flex-column gap-2 pt-4">
|
||||
<Box h={340} gap={4} display={'flex'} flexDirection={'column'}>
|
||||
<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>
|
||||
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
<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"
|
||||
/>
|
||||
{selectedImage === fallbackImage || 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={() => 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 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"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.title?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.title?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If title crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered {watch()?.title?.length}{" "}
|
||||
characters
|
||||
</FormHelperText>
|
||||
{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">
|
||||
Location
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("location")}
|
||||
placeholder="Location"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
/>
|
||||
{errors.location && (
|
||||
<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.location.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Content
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
{...register("content")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="content"
|
||||
type="text"
|
||||
id="content"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.content?.length > 230}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.content?.length > 230 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If content crosses 230 characters it will cause problem in
|
||||
alignment on website.you have entered{" "}
|
||||
{watch()?.content?.length} 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">
|
||||
Organisation email
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="email"
|
||||
{...register("organizer_email")}
|
||||
placeholder="Organisation email"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
/>
|
||||
{errors.organizer_email && (
|
||||
<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.organizer_email.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Event dates
|
||||
</FormLabel>
|
||||
{eventDatesInput.map((date, index) => (
|
||||
<Input
|
||||
key={index}
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(event) => handleDateChange(index, event)}
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
/>
|
||||
))}
|
||||
<Box display={"flex"} justifyContent={"flex-end"} mt={2}>
|
||||
<Button
|
||||
size={"xs"}
|
||||
rounded={"sm"}
|
||||
type="button"
|
||||
onClick={addDateInput}
|
||||
>
|
||||
Add Date
|
||||
</Button>
|
||||
</Box>
|
||||
{/* <FormHelperText className="web-text-small">
|
||||
{data?.data?.eventDates?.map(({ date }, index) => (
|
||||
<span className="web-text-small me-2">
|
||||
{formatDate(date)}
|
||||
</span>
|
||||
))}
|
||||
</FormHelperText> */}
|
||||
{errors.eventDates && (
|
||||
<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.eventDates.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 10MB.
|
||||
</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">
|
||||
Orgainsation info
|
||||
</span>
|
||||
<span className="web-text-medium text-secondary mb-0">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Tenetur
|
||||
dicta exercitationem laboriosam fugit vel ipsam hic, consectetur
|
||||
eum nesciunt adipisci?
|
||||
</span>
|
||||
</Box>
|
||||
|
||||
<Box className="col-7 pt-4 p-4">
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation name
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("organizer_name")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="organizer_name"
|
||||
type="text"
|
||||
id="organizer_name"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.organizer_name?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={
|
||||
watch()?.organizer_name?.length > 50 ? "red" : "gray.500"
|
||||
}
|
||||
className="web-text-small"
|
||||
>
|
||||
If Organisation name crosses 50 characters it will cause
|
||||
problem in alignment on website.you have entered{" "}
|
||||
{watch()?.organizer_name?.length} characters
|
||||
</FormHelperText>
|
||||
{errors.organizer_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.organizer_name.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation number
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("organizer_mobile_number")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="organizer_mobile_number"
|
||||
type="text"
|
||||
id="organizer_mobile_number"
|
||||
/>
|
||||
{errors.organizer_mobile_number && (
|
||||
<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.organizer_mobile_number.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation email
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("organizer_email")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="organizer_email"
|
||||
type="text"
|
||||
id="organizer_email"
|
||||
/>
|
||||
<FormHelperText className="web-text-small">
|
||||
Please enter valid email
|
||||
</FormHelperText>
|
||||
{errors.organizer_email && (
|
||||
<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.organizer_email.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end mb-5">
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.700"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
rounded={"sm"}
|
||||
type="submit"
|
||||
size="sm"
|
||||
>
|
||||
Create event
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddEvents;
|
||||
601
src/Pages/Events/EditEvents.jsx
Normal file
@@ -0,0 +1,601 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
useGetEventsByIdQuery,
|
||||
useUpdateEventsMutation,
|
||||
} from "../../Services/api.service";
|
||||
import fallbackImage from "../../assets/ultp-fallback-img.webp";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Heading,
|
||||
Image,
|
||||
Input,
|
||||
Stack,
|
||||
Tag,
|
||||
Textarea,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { addEvents } from "../../Validations/Validations";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import Header from "../../Components/Header";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
import { motion } from "framer-motion";
|
||||
import Loader01 from "../../Components/Loaders/Loader01";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
import ChipSelector from "../../Components/ChipSelector/ChipSelector";
|
||||
|
||||
const convertToDateArray = (dateArray) => {
|
||||
return dateArray?.map((dateString) => {
|
||||
const date = new Date(dateString);
|
||||
const year = date.getUTCFullYear();
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, "0"); // getUTCMonth() is zero-based
|
||||
const day = String(date.getUTCDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
});
|
||||
};
|
||||
|
||||
const EditEvents = () => {
|
||||
const { id } = useParams();
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { data, error, isLoading } = useGetEventsByIdQuery(id);
|
||||
const [isLoadingEdit, setIsLoadingEdit] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState(fallbackImage);
|
||||
const [largeImageData, setLargeImageData] = useState(null);
|
||||
const [updateEvents] = useUpdateEventsMutation();
|
||||
const [eventsDate, setEventsDate] = useState(
|
||||
convertToDateArray(data?.data?.eventDates.map((event) => event.date))
|
||||
);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
} = useForm({
|
||||
resolver: yupResolver(addEvents),
|
||||
defaultValues: {
|
||||
title: "",
|
||||
content: "",
|
||||
location: "",
|
||||
organizer_name: "",
|
||||
organizer_mobile_number: "",
|
||||
// eventDates: eventsDate,
|
||||
organizer_email: "",
|
||||
banner_image: null,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data) {
|
||||
setSelectedImage(
|
||||
`${API_URL}/${data?.data?.banner_image}`
|
||||
);
|
||||
setValue("title", data?.data?.title);
|
||||
setValue("content", data?.data?.content);
|
||||
setValue("location", data?.data?.location);
|
||||
setValue("organizer_name", data?.data?.organizer_name);
|
||||
setValue("organizer_mobile_number", data?.data?.organizer_mobile_number);
|
||||
setValue("organizer_email", data?.data?.organizer_email);
|
||||
setValue("content", data?.data?.content);
|
||||
// setValue("eventDates", data?.data?.eventDates);
|
||||
setValue("banner_image", data?.data?.banner_image);
|
||||
|
||||
// setValue("eventDates", eventsDate);
|
||||
}
|
||||
}, [data, setValue]);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const onSubmit = (data) => {
|
||||
setIsLoadingEdit(true);
|
||||
const formData = new FormData();
|
||||
formData.append("title", data.title);
|
||||
formData.append("content", data.content);
|
||||
formData.append("location", data.location);
|
||||
formData.append("organizer_name", data.organizer_name);
|
||||
formData.append("organizer_mobile_number", data.organizer_mobile_number);
|
||||
formData.append("organizer_email", data.organizer_email);
|
||||
|
||||
|
||||
if (eventsDate.length === 0) {
|
||||
setIsLoadingEdit(false);
|
||||
return toast({
|
||||
render: () => (
|
||||
<ToastBox status={"warn"} message={"Please add events date"} />
|
||||
),
|
||||
})
|
||||
}else{
|
||||
eventsDate.forEach((date, index) => {
|
||||
formData.append(`dates[${index}]`, date);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (data.banner_image[0]) {
|
||||
formData.append("banner_image", data.banner_image[0]);
|
||||
}
|
||||
|
||||
// for (const [key, value] of formData.entries()) {
|
||||
// // console.log(`${key}: ${value}`);
|
||||
// }
|
||||
|
||||
// Trigger the mutationconst
|
||||
const res = updateEvents({ id: id, data: 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) {
|
||||
setIsLoadingEdit(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
reset();
|
||||
navigate("/events");
|
||||
} else if (response?.data?.statusCode === 500) {
|
||||
setIsLoadingEdit(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"error"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// Handle errors
|
||||
// // console.error("Error creating community:", error?.message);
|
||||
setIsLoadingEdit(false);
|
||||
// Handle error notification if needed
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return isLoading ? (
|
||||
<FullscreenLoaders />
|
||||
) : (
|
||||
<Box
|
||||
{...OPACITY_ON_LOAD}
|
||||
overflowY={"scroll"}
|
||||
paddingBottom={50}
|
||||
height={"100vh"}
|
||||
>
|
||||
<Header title={"Events"} />
|
||||
|
||||
<Box display={"flex"}>
|
||||
<form className="w-100" onSubmit={handleSubmit(onSubmit)}>
|
||||
<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">
|
||||
Events 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 image
|
||||
</span>
|
||||
<span className="web-text-medium text-secondary mb-4">
|
||||
Below is the profile that will be displayed on the events 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>
|
||||
|
||||
<Box
|
||||
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"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.title?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.title?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If title crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered {watch()?.title?.length}{" "}
|
||||
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">
|
||||
Location
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("location")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="location"
|
||||
type="text"
|
||||
id="location"
|
||||
/>
|
||||
{errors.location && (
|
||||
<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.location.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Content
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
{...register("content")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="content"
|
||||
type="text"
|
||||
id="content"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.content?.length > 230}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.content?.length > 230 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If content crosses 230 characters it will cause problem in
|
||||
alignment on website.you have entered{" "}
|
||||
{watch()?.content?.length} 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 className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Event dates
|
||||
</FormLabel>
|
||||
<ChipSelector
|
||||
type={"date"}
|
||||
chips={eventsDate}
|
||||
setChips={setEventsDate}
|
||||
/>
|
||||
{errors.eventDates && (
|
||||
<span className="text-warning web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
|
||||
<TiWarning className="fw-bold fs-5 " />{" "}
|
||||
{errors.eventDates.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl 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 should be 1mb to protect website from
|
||||
slow loading.
|
||||
</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">
|
||||
Orgainsation info
|
||||
</span>
|
||||
<span className="web-text-medium text-secondary mb-0">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Tenetur
|
||||
dicta exercitationem laboriosam fugit vel ipsam hic, consectetur
|
||||
eum nesciunt adipisci?
|
||||
</span>
|
||||
</Box>
|
||||
|
||||
<Box className="col-7 pt-4 p-4">
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation name
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("organizer_name")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="organizer_name"
|
||||
type="text"
|
||||
id="organizer_name"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.organizer_name?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={
|
||||
watch()?.organizer_name?.length > 50 ? "red" : "gray.500"
|
||||
}
|
||||
className="web-text-small"
|
||||
>
|
||||
If Organisation name crosses 50 characters it will cause
|
||||
problem in alignment on website.you have entered{" "}
|
||||
{watch()?.organizer_name?.length} characters
|
||||
</FormHelperText>
|
||||
{errors.organizer_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.organizer_name.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation number
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("organizer_mobile_number")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="organizer_mobile_number"
|
||||
type="text"
|
||||
id="organizer_mobile_number"
|
||||
/>
|
||||
{errors.organizer_mobile_number && (
|
||||
<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.organizer_mobile_number.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation email
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("organizer_email")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="organizer_email"
|
||||
type="text"
|
||||
id="organizer_email"
|
||||
/>
|
||||
<FormHelperText className="web-text-small">
|
||||
Please enter valid email
|
||||
</FormHelperText>
|
||||
{errors.organizer_email && (
|
||||
<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.organizer_email.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end">
|
||||
<Button
|
||||
isLoading={isLoadingEdit}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
size="sm"
|
||||
rounded={"sm"}
|
||||
>
|
||||
Save edit
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditEvents;
|
||||
246
src/Pages/Events/Events.jsx
Normal file
@@ -0,0 +1,246 @@
|
||||
import {
|
||||
Box,
|
||||
HStack,
|
||||
Input,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Portal,
|
||||
Select,
|
||||
Switch,
|
||||
Text,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import {
|
||||
useDeleteEventsMutation,
|
||||
useGetEventsQuery,
|
||||
useUpdateEventsStatusMutation,
|
||||
} from "../../Services/api.service";
|
||||
import { useState } from "react";
|
||||
import { TABLE_PAGINATION } from "../../Constants/Paginations";
|
||||
import Header from "../../Components/Header";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@chakra-ui/icons";
|
||||
import DataTable from "../../Components/DataTable/DataTable";
|
||||
import { HiDotsVertical } from "react-icons/hi";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
import CustomAlertDialog from "../../Components/CustomAlertDialog";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
import TabularView from "../../Components/TabularView/TabularView";
|
||||
|
||||
const Events = () => {
|
||||
// ====================================================[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 events = useGetEventsQuery({ page: currentPage, size: pageSize });
|
||||
const [updateEventsStatus] = useUpdateEventsStatusMutation();
|
||||
const [deleteEvents] = useDeleteEventsMutation();
|
||||
|
||||
// ====================================================[Functions]===================================================================
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
// Trigger the mutation
|
||||
setDeleteIsLoading(true);
|
||||
await deleteEvents(id)
|
||||
.then((response) => {
|
||||
if (response?.data?.statusCode === 201) {
|
||||
setDeleteIsLoading(false);
|
||||
setDeleteAlert(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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 updateEventsStatus({ id })
|
||||
.then((response) => {
|
||||
// console.log(response?.data);
|
||||
if (response?.data?.statusCode === 201) {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// console.log(error);
|
||||
});
|
||||
} catch (error) {
|
||||
// Handle errors
|
||||
// console.error("Error updating community status:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// ====================================================[Pagination Setup]================================================================
|
||||
const paginationPrev = () => {
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
updateDisplayRange(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const paginationNext = () => {
|
||||
const totalPages = Math.ceil(events?.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, events?.data?.data?.totalItems);
|
||||
setDisplayRange({ start, end });
|
||||
};
|
||||
|
||||
// ====================================================[Table Filter]================================================================
|
||||
const filteredData = events?.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",
|
||||
"Organisation name",
|
||||
"Contact no",
|
||||
"Email",
|
||||
"Location",
|
||||
"Status",
|
||||
"Created At",
|
||||
];
|
||||
|
||||
const extractedArray = filteredData?.map((item, index) => {
|
||||
return {
|
||||
Title: <RouterLink to={`view/${item.id}`}>{item?.title}</RouterLink>,
|
||||
"Organisation name": item?.organizer_name,
|
||||
"Contact no": item?.organizer_mobile_number,
|
||||
Email: item?.organizer_email,
|
||||
Location: item?.location,
|
||||
|
||||
Status: (
|
||||
<Switch
|
||||
size={"sm"}
|
||||
colorScheme="pink.500"
|
||||
onChange={() => handleUpdateStatus(item?.id)}
|
||||
isChecked={item?.status}
|
||||
/>
|
||||
),
|
||||
"Created At": (
|
||||
<span className="d-flex justify-content-between align-items-center">
|
||||
<Text as={"span"} color={"gray.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 (
|
||||
<>
|
||||
|
||||
<TabularView
|
||||
title={"Events"}
|
||||
btnTitle={"Create event"}
|
||||
link={"/events/add-events"}
|
||||
apiData={events}
|
||||
tableHeadRow={tableHeadRow}
|
||||
extractedArray={extractedArray}
|
||||
searchTerm={searchTerm}
|
||||
setSearchTerm={setSearchTerm}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
totalItems={events?.data?.data?.totalItems}
|
||||
totalPages={events?.data?.data?.totalPages}
|
||||
|
||||
noDataTitle={"event's"}
|
||||
/>
|
||||
|
||||
{/* ====================================================[ Alert ]================================================================ */}
|
||||
<CustomAlertDialog
|
||||
onClose={() => setDeleteAlert(false)}
|
||||
isOpen={deleteAlert}
|
||||
alertHandler={() => handleDelete(actionId)}
|
||||
message={"Are you sure you want to delete event?"}
|
||||
isLoading={deleteIsLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Events;
|
||||
193
src/Pages/Events/ViewEvents.jsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import React from 'react'
|
||||
import { useGetEventsByIdQuery } from '../../Services/api.service';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Box, Divider, Image, Tag, useToast } from '@chakra-ui/react';
|
||||
import FullscreenLoaders from '../../Components/Loaders/FullscreenLoaders';
|
||||
import { OPACITY_ON_LOAD } from '../../Layout/animations';
|
||||
import Header from '../../Components/Header';
|
||||
import { formatDate } from '../../Components/Functions/UTCConvertor';
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const ViewEvents = () => {
|
||||
const { id } = useParams();
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { data, error, isLoading } = useGetEventsByIdQuery(id);
|
||||
const events = data?.data;
|
||||
if (isLoading) {
|
||||
return <FullscreenLoaders />;
|
||||
}
|
||||
return(
|
||||
<Box
|
||||
{...OPACITY_ON_LOAD}
|
||||
w={"100%"}
|
||||
h={"100vh"}
|
||||
className="overflow-auto "
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
>
|
||||
<Header
|
||||
title={"Events"}
|
||||
btnTitle={'Edit events'}
|
||||
link={`/events/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">
|
||||
Events 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">
|
||||
Events 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}
|
||||
objectFit='cover'
|
||||
w={500}
|
||||
h={240}
|
||||
src={`${API_URL}/${events?.banner_image}`}
|
||||
alt="Selected Image"
|
||||
/>
|
||||
</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>
|
||||
{events?.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">{events?.title}</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">
|
||||
{events?.content}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Location
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{events?.location}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation name
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{events?.organizer_name}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation number
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{events?.organizer_mobile_number}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Organisation email
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{events?.organizer_email}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Event Dates
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
|
||||
{events?.eventDates?.map(({ date }, index) => (
|
||||
<span key={index} className="web-text-small me-2">
|
||||
{formatDate(date)}
|
||||
</span>
|
||||
))}
|
||||
</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(events?.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(events?.updatedAt)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Box>
|
||||
</Box>)
|
||||
}
|
||||
|
||||
export default ViewEvents
|
||||
344
src/Pages/Faq/AddFaq.jsx
Normal file
@@ -0,0 +1,344 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Heading,
|
||||
Image,
|
||||
Input,
|
||||
Stack,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import React, { useState } from "react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import Header from "../../Components/Header";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import fallbackImage from "../../assets/ultp-fallback-img.webp";
|
||||
import { addFaq } from "../../Validations/Validations";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { motion } from "framer-motion";
|
||||
import Loader01 from "../../Components/Loaders/Loader01";
|
||||
import { useCreateFaqMutation } from "../../Services/api.service";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
import ReactQuill from "react-quill";
|
||||
|
||||
const AddFaq = () => {
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState(fallbackImage);
|
||||
const [imageData, setImageData] = useState(null);
|
||||
const [value, setValue] = useState(null);
|
||||
|
||||
const [createFaq] = useCreateFaqMutation();
|
||||
|
||||
// const termContent = data?.data;
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
reset,
|
||||
setValue: setYupFormValue,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(addFaq),
|
||||
defaultValues: {
|
||||
question: "",
|
||||
answer: "",
|
||||
},
|
||||
});
|
||||
|
||||
// console.log(errors);
|
||||
// const handleImageChange = (e) => {
|
||||
// const file = e.target.files[0];
|
||||
// setImageData(file);
|
||||
// // setYupFormValue("banner_image", file);
|
||||
// if (file) {
|
||||
// const reader = new FileReader();
|
||||
// reader.onloadend = () => {
|
||||
// setSelectedImage(reader.result);
|
||||
// };
|
||||
// reader.readAsDataURL(file);
|
||||
// }
|
||||
// };
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
if (value === null) {
|
||||
return toast({
|
||||
render: () => (
|
||||
<ToastBox status={"warn"} message={"Answer can not be empty."} />
|
||||
),
|
||||
});
|
||||
}
|
||||
setYupFormValue("answer", value);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
// Trigger the mutation
|
||||
createFaq(data)
|
||||
.then((response) => {
|
||||
// Handle the response here
|
||||
|
||||
if (response?.data?.statusCode === 201) {
|
||||
setIsLoading(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
reset();
|
||||
navigate("/faq");
|
||||
} else if (response?.data?.statusCode === 500) {
|
||||
setIsLoading(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"Some thing went wrong"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...OPACITY_ON_LOAD}
|
||||
w={"100%"}
|
||||
h={"100vh"}
|
||||
className="overflow-auto "
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
>
|
||||
<Header title={"Add FAQ"} />
|
||||
|
||||
<Box className="d-flex">
|
||||
{/* <Box className="col-5 d-flex flex-column gap-2 pt-4">
|
||||
<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"
|
||||
/>
|
||||
{selectedImage === fallbackImage || imageData === null ? (
|
||||
""
|
||||
) : (
|
||||
<Box display={"flex"} flexDirection={"column"} w={"100%"}>
|
||||
<span className="web-text-small">{imageData?.name}</span>
|
||||
<span className="web-text-small text-secondary fst-italic">
|
||||
{(imageData?.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
|
||||
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">
|
||||
Question
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...register("question")}
|
||||
placeholder="Question"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
minLength={4}
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.question?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.question?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If question crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered {watch()?.question?.length}{" "}
|
||||
characters
|
||||
</FormHelperText>
|
||||
{errors.question && (
|
||||
<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.question.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Answer
|
||||
</FormLabel>
|
||||
<ReactQuill
|
||||
className="rounded-3"
|
||||
theme="snow"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
{errors.answer && (
|
||||
<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.answer.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
{/* <FormControl isRequired className="mb-3">
|
||||
<FormLabel className="web-text-large fw-bold rubix-text-dark">
|
||||
Banner image
|
||||
</FormLabel>
|
||||
|
||||
<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("image")}
|
||||
type="file"
|
||||
height="100%"
|
||||
width="100%"
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
opacity="0"
|
||||
aria-hidden="true"
|
||||
accept="image/*"
|
||||
onChange={handleImageChange}
|
||||
onDrop={handleImageChange}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{errors.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.image.message}
|
||||
</span>
|
||||
)}
|
||||
<FormHelperText className="web-text-small">
|
||||
Maximum limit of image is 10MB.
|
||||
</FormHelperText>
|
||||
</FormControl> */}
|
||||
|
||||
<Box className=" d-flex justify-content-end mb-5">
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
rounded={"sm"}
|
||||
size="sm"
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddFaq;
|
||||
182
src/Pages/Faq/EditFaq.jsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Header from "../../Components/Header";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Input,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
useGetFaqByIdQuery,
|
||||
useUpdateFaqMutation,
|
||||
} from "../../Services/api.service";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
import { addFaq } from "../../Validations/Validations";
|
||||
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
import ReactQuill from "react-quill";
|
||||
|
||||
const EditFaq = () => {
|
||||
const { id } = useParams();
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { data, error, isLoading } = useGetFaqByIdQuery(id);
|
||||
const [isLoadingEdit, setIsLoadingEdit] = useState(false);
|
||||
const [updateFaq] = useUpdateFaqMutation();
|
||||
const [valueQuill, setValueQuill] = useState(data?.data?.answer);
|
||||
|
||||
// console.log(data);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
reset,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
} = useForm({
|
||||
resolver: yupResolver(addFaq),
|
||||
defaultValues: {
|
||||
question: "",
|
||||
answer: "",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data) {
|
||||
setValue("question", data?.data?.question);
|
||||
setValueQuill(data?.data?.answer || ""); // Set initial value for Quill
|
||||
}
|
||||
}, [data, setValue]);
|
||||
|
||||
const onSubmit = async (formData) => {
|
||||
if (!valueQuill || valueQuill.trim() === "") {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"error"} message={"Answer cannot be empty"} />
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoadingEdit(true);
|
||||
const updatedData = {
|
||||
...formData,
|
||||
answer: valueQuill, // Include the value from ReactQuill
|
||||
};
|
||||
|
||||
await updateFaq({ id, data: updatedData })
|
||||
.then((response) => {
|
||||
if (response?.data?.statusCode === 200) {
|
||||
setIsLoadingEdit(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
|
||||
reset();
|
||||
navigate("/faq");
|
||||
} else {
|
||||
setIsLoadingEdit(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"error"} message={"Something went wrong"} />
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// console.error("Error updating FAQ:", error);
|
||||
setIsLoadingEdit(false);
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <FullscreenLoaders />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...OPACITY_ON_LOAD}
|
||||
overflowY={"scroll"}
|
||||
paddingBottom={50}
|
||||
height={"100vh"}
|
||||
>
|
||||
<Header title={"FAQ"} />
|
||||
|
||||
<Box display={"flex"}>
|
||||
<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("question")}
|
||||
placeholder="Question"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
minLength={4}
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={watch()?.question?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={watch()?.question?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If question crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered {watch()?.question?.length}{" "}
|
||||
characters
|
||||
</FormHelperText>
|
||||
{errors.question && (
|
||||
<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.question.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired className="mb-3">
|
||||
<ReactQuill
|
||||
className="rounded-3"
|
||||
theme="snow"
|
||||
value={valueQuill}
|
||||
onChange={setValueQuill}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<Box className="d-flex justify-content-end">
|
||||
<Button
|
||||
isLoading={isLoadingEdit}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
size="sm"
|
||||
rounded={"sm"}
|
||||
>
|
||||
Save edit
|
||||
</Button>
|
||||
</Box>
|
||||
<Divider />
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditFaq;
|
||||
254
src/Pages/Faq/Faq.jsx
Normal file
@@ -0,0 +1,254 @@
|
||||
import {
|
||||
Box,
|
||||
Image,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Portal,
|
||||
Switch,
|
||||
Text,
|
||||
Tooltip,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import { TABLE_PAGINATION } from "../../Constants/Paginations";
|
||||
import {
|
||||
useCreateFaqMutation,
|
||||
useDeleteFaqMutation,
|
||||
useGetFaqQuery,
|
||||
useUpdateFaqStatusMutation,
|
||||
} from "../../Services/api.service";
|
||||
import { useState } from "react";
|
||||
import TabularView from "../../Components/TabularView/TabularView";
|
||||
import CustomAlertDialog from "../../Components/CustomAlertDialog";
|
||||
import { HiDotsVertical } from "react-icons/hi";
|
||||
import { Link } from "react-router-dom";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
import extractFilename from "../../Components/Functions/FileNameAlter";
|
||||
|
||||
const Faq = () => {
|
||||
const toast = useToast();
|
||||
const [pageSize, setPageSize] = useState(TABLE_PAGINATION?.size);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
|
||||
const [deleteAlert, setDeleteAlert] = useState(false);
|
||||
const [actionId, setActionId] = useState(null);
|
||||
const [actionStatus, setActionStatus] = useState(null);
|
||||
const [deleteIsLoading, setDeleteIsLoading] = useState(false);
|
||||
|
||||
const faqPage = useCreateFaqMutation();
|
||||
|
||||
const faq = useGetFaqQuery({ page: currentPage, size: pageSize });
|
||||
|
||||
|
||||
const [deleteFaq] = useDeleteFaqMutation();
|
||||
const [updateFaqStatus] = useUpdateFaqStatusMutation();
|
||||
|
||||
const filteredData = faq?.data?.data?.rows?.filter((item) => {
|
||||
// Filter by name (case insensitive)
|
||||
const name = item.question;
|
||||
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",
|
||||
// "Content",
|
||||
"Active",
|
||||
"Created At",
|
||||
];
|
||||
|
||||
const extractedArray = filteredData?.map((item, index) => {
|
||||
return {
|
||||
Title: (
|
||||
<Link to={`/faq/view/${item.id}`}>
|
||||
<Tooltip
|
||||
className="rounded-2 web-text-xsmall"
|
||||
width={"fit-content"}
|
||||
placement="top"
|
||||
hasArrow
|
||||
label={item?.question}
|
||||
bg="blue.200"
|
||||
>
|
||||
<Box display={"flex"} alignItems={"center"} w={200}>
|
||||
<Text as={"span"} isTruncated={true}>
|
||||
{item?.question}
|
||||
</Text>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
),
|
||||
Active: (
|
||||
<Switch
|
||||
size={"sm"}
|
||||
colorScheme="teal"
|
||||
onChange={() => handleUpdateStatus(item.id, item?.status)}
|
||||
isChecked={item.status}
|
||||
// disabled={item.status}
|
||||
/>
|
||||
),
|
||||
"Created At": (
|
||||
<span className="d-flex justify-content-between align-items-center">
|
||||
<Text as={"span"} color={"gray.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">
|
||||
<Link to={`/faq/edit/${item.id}`}>
|
||||
<MenuItem className="web-text-medium">Edit</MenuItem>
|
||||
</Link>
|
||||
<Link to={`/faq/view/${item.id}`}>
|
||||
<MenuItem className="web-text-medium">View</MenuItem>
|
||||
</Link>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setActionId(item.id);
|
||||
setDeleteAlert(true);
|
||||
setActionStatus(item.status);
|
||||
}}
|
||||
className="web-text-medium"
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Portal>
|
||||
</Menu>
|
||||
</span>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
// ====================================================[Functions]===================================================================
|
||||
const handleDelete = async (communityId, status) => {
|
||||
if (status) {
|
||||
return toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"warn"}
|
||||
message={"Can't delete Partner. Status is true."}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the mutation
|
||||
setDeleteIsLoading(true);
|
||||
const response = await deleteFaq(communityId);
|
||||
|
||||
// Handle the response here
|
||||
// console.log("Mutation response:", response?.data?.statusCode);
|
||||
// console.log("Mutation response:", response?.data?.message);
|
||||
|
||||
if (response?.data?.statusCode === 200) {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"error"}
|
||||
message={"Failed to delete partner. Please try again."}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("Error deleting community:", error);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"error"}
|
||||
message={"Error deleting partner. Please try again."}
|
||||
/>
|
||||
),
|
||||
});
|
||||
} finally {
|
||||
// Ensure the loading state is reset and alert is closed
|
||||
setDeleteIsLoading(false);
|
||||
setDeleteAlert(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateStatus = async (id) => {
|
||||
try {
|
||||
await updateFaqStatus({ id })
|
||||
.then((response) => {
|
||||
// console.log(response?.data);
|
||||
if (response?.data?.statusCode === 201) {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={"Please toggle another banner."}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
// whitePaper?.refetch()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// console.log(error);
|
||||
});
|
||||
} catch (error) {
|
||||
// Handle errors
|
||||
// console.error("Error updating community status:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TabularView
|
||||
title={"FAQ"}
|
||||
btnTitle={"Create Faq"}
|
||||
link={"/faq/add-faq"}
|
||||
apiData={faq}
|
||||
tableHeadRow={tableHeadRow}
|
||||
extractedArray={extractedArray}
|
||||
searchTerm={searchTerm}
|
||||
setSearchTerm={setSearchTerm}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
totalPages={faq?.data?.data?.totalPages}
|
||||
totalItems={faq?.data?.data?.totalItems}
|
||||
noDataTitle={"FAQ's"}
|
||||
/>
|
||||
<CustomAlertDialog
|
||||
onClose={() => setDeleteAlert(false)}
|
||||
isOpen={deleteAlert}
|
||||
alertHandler={() => handleDelete(actionId)}
|
||||
message={"Are you sure you want to delete Faq?"}
|
||||
isLoading={deleteIsLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Faq;
|
||||
97
src/Pages/Faq/ViewFaq.jsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React from "react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import { Box, Divider, Image, Tag, Text, useToast } from "@chakra-ui/react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import Header from "../../Components/Header";
|
||||
import {
|
||||
useGetFaqByIdQuery,
|
||||
useGetPolicyByIdQuery,
|
||||
} from "../../Services/api.service";
|
||||
import { AttachmentIcon } from "@chakra-ui/icons";
|
||||
import extractFilename from "../../Components/Functions/FileNameAlter";
|
||||
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
|
||||
import pdf from "../../assets/pdfscreen.png";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const ViewFaq = () => {
|
||||
const { id } = useParams();
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { data, error, isLoading } = useGetFaqByIdQuery(id);
|
||||
const viewFaq = data?.data;
|
||||
|
||||
// // console.log(viewPolicy?.banner_image);
|
||||
// // console.log(`https://rubix.betadelivery.com/${viewPolicy?.banner_image}`);
|
||||
|
||||
if (isLoading) {
|
||||
return <FullscreenLoaders />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...OPACITY_ON_LOAD}
|
||||
w={"100%"}
|
||||
h={"100vh"}
|
||||
className="overflow-auto "
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
>
|
||||
<Header title={"FAQ"} btnTitle={"Edit FAQ"} link={`/faq/edit/${id}`} />
|
||||
|
||||
<Box display={"flex"}>
|
||||
{/* <Box className="col-5 d-flex flex-column gap-2 pt-4">
|
||||
<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}
|
||||
objectFit="cover"
|
||||
w={500}
|
||||
h={240}
|
||||
src={`${API_URL}/${viewPolicy?.banner_image}`}
|
||||
alt="Selected Image"
|
||||
/>
|
||||
</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>
|
||||
{viewFaq?.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">
|
||||
{viewFaq?.question}
|
||||
</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">
|
||||
{viewFaq?.answer}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewFaq;
|
||||
11
src/Pages/Investment/Investment.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Box } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import { OPACITY_ON_LOAD } from '../../Layout/animations'
|
||||
|
||||
const Investment = () => {
|
||||
return (
|
||||
<Box {...OPACITY_ON_LOAD} bg={"green.100"} overflowY={"scroll"} height={"100vh"}></Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Investment
|
||||
273
src/Pages/Login.jsx
Normal file
@@ -0,0 +1,273 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Input01 from "../Components/Inputs/Input01";
|
||||
import logo from "../assets/logo2.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 Asset1 from "../assets/asset1.png";
|
||||
import Asset2 from "../assets/asset2.png";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import GlobalStateContext from "../Contexts/GlobalStateContext";
|
||||
import Cookies from "js-cookie";
|
||||
import { validationSchema } from "../Validations/Validations";
|
||||
import ToastBox from "../Components/ToastBox";
|
||||
|
||||
const Login = () => {
|
||||
const [show, setShow] = useState(false);
|
||||
const handleClick = () => setShow(!show);
|
||||
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({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={"Login Successfully"} />
|
||||
),
|
||||
});
|
||||
|
||||
Cookies.set("isAuthenticated", true, { expires: 7 });
|
||||
navigate("/");
|
||||
}, 2000); // 3-second delay
|
||||
} else {
|
||||
return setTimeout(() => {
|
||||
// dispatch(loginUser(true));
|
||||
setIsAuthenticate(false);
|
||||
setIsLoading(false);
|
||||
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"error"} message={"Invalid credentials"} />
|
||||
),
|
||||
});
|
||||
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",
|
||||
backgroundColor:"#0041180A"
|
||||
}}
|
||||
className="rubix-primary"
|
||||
>
|
||||
<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: "130px",
|
||||
}}
|
||||
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 tanami.
|
||||
</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="green.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>
|
||||
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
{...register("password")}
|
||||
className="web-text-medium"
|
||||
focusBorderColor="green.500"
|
||||
variant="filled"
|
||||
pr="4.5rem"
|
||||
type={show ? "text" : "password"}
|
||||
placeholder="Enter password"
|
||||
/>
|
||||
<InputRightElement width="4.5rem">
|
||||
<Button
|
||||
h="1.75rem"
|
||||
size="sm"
|
||||
fontSize={"xs"}
|
||||
color={"green.800"}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{show ? "Hide" : "Show"}
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
{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="green.500"
|
||||
size="lg"
|
||||
>
|
||||
Log In
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: "0%",
|
||||
fontSize: "13px",
|
||||
color: "#919191",
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
Tanami v1.0.0
|
||||
</div>
|
||||
|
||||
{/* <img
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
// width:100
|
||||
}}
|
||||
src={Asset1}
|
||||
alt="bg-img"
|
||||
/>
|
||||
|
||||
<img
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
// width:400
|
||||
}}
|
||||
src={Asset2}
|
||||
alt="bg-img"
|
||||
/> */}
|
||||
|
||||
<img
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: 150,
|
||||
}}
|
||||
src={Asset1}
|
||||
alt="bg-img"
|
||||
/>
|
||||
|
||||
{/* <img
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
src={Asset1}
|
||||
alt="bg-img"
|
||||
/> */}
|
||||
<img
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
src={Asset2}
|
||||
alt="bg-img"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
232
src/Pages/NewLetter/NewsLetter.jsx
Normal file
@@ -0,0 +1,232 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Image,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Portal,
|
||||
Switch,
|
||||
Text,
|
||||
Tooltip,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogCloseButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogFooter,
|
||||
AlertDialogOverlay,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import { TABLE_PAGINATION } from "../../Constants/Paginations";
|
||||
import {
|
||||
useDeleteEmailMutation,
|
||||
useGetNewsLetterEmailQuery,
|
||||
useGetNewsLetterQuery,
|
||||
} from "../../Services/api.service";
|
||||
import { useState } from "react";
|
||||
import TabularView from "../../Components/TabularView/TabularView";
|
||||
import CustomAlertDialog from "../../Components/CustomAlertDialog";
|
||||
import { HiDotsVertical } from "react-icons/hi";
|
||||
import { Link } from "react-router-dom";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
import extractFilename from "../../Components/Functions/FileNameAlter";
|
||||
import { DeleteIcon } from "@chakra-ui/icons";
|
||||
|
||||
const NewsLetter = () => {
|
||||
const toast = useToast();
|
||||
const [pageSize, setPageSize] = useState(TABLE_PAGINATION?.size);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
|
||||
const [deleteAlert, setDeleteAlert] = useState(false);
|
||||
const [actionId, setActionId] = useState(null);
|
||||
const [actionStatus, setActionStatus] = useState(null);
|
||||
const [deleteIsLoading, setDeleteIsLoading] = useState(false);
|
||||
|
||||
const { data } = useGetNewsLetterQuery({ page: currentPage, size: pageSize });
|
||||
// // console.log(useGetNewsletterQuery);
|
||||
const email = data?.data?.rows;
|
||||
// console.log(email);
|
||||
|
||||
const [deleteEmail] = useDeleteEmailMutation();
|
||||
// const [updateFaqStatus] = useUpdateFaqStatusMutation();
|
||||
|
||||
const handleDelete = async (communityId, status) => {
|
||||
if (status) {
|
||||
return toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"warn"}
|
||||
message={"Can't delete Email. Status is true."}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger the mutation
|
||||
setDeleteIsLoading(true);
|
||||
const response = await deleteEmail(communityId);
|
||||
|
||||
// Handle the response here
|
||||
// console.log("Mutation response:", response?.data?.statusCode);
|
||||
// console.log("Mutation response:", response?.data?.message);
|
||||
|
||||
if (response?.data?.statusCode === 200) {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"error"}
|
||||
message={"Failed to delete partner. Please try again."}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("Error deleting community:", error);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"error"}
|
||||
message={"Error deleting partner. Please try again."}
|
||||
/>
|
||||
),
|
||||
});
|
||||
} finally {
|
||||
// Ensure the loading state is reset and alert is closed
|
||||
setDeleteIsLoading(false);
|
||||
setDeleteAlert(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ====================================================[Table Setup]================================================================
|
||||
const tableHeadRow = [
|
||||
"Title",
|
||||
// "Content",
|
||||
"Created At",
|
||||
"Actions",
|
||||
];
|
||||
|
||||
const extractedArray = email?.map((item, index) => {
|
||||
return {
|
||||
Title: (
|
||||
<Tooltip
|
||||
className="rounded-2 web-text-xsmall"
|
||||
width={"fit-content"}
|
||||
placement="top"
|
||||
hasArrow
|
||||
label={item?.email}
|
||||
bg="blue.200"
|
||||
>
|
||||
<Box display={"flex"} alignItems={"center"} w={200}>
|
||||
<Text as={"span"} isTruncated={true}>
|
||||
{item?.email}
|
||||
</Text>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
),
|
||||
"Created At": (
|
||||
<Box
|
||||
w={200}
|
||||
className="d-flex justify-content-between align-items-center"
|
||||
>
|
||||
<Text as={"span"} color={"gray.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">
|
||||
<Link to={`/faq/edit/${item.id}`}>
|
||||
<MenuItem className="web-text-medium">Edit</MenuItem>
|
||||
</Link>
|
||||
<Link to={`/faq/view/${item.id}`}>
|
||||
<MenuItem className="web-text-medium">View</MenuItem>
|
||||
</Link>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setActionId(item.id);
|
||||
setDeleteAlert(true);
|
||||
setActionStatus(item.status);
|
||||
}}
|
||||
className="web-text-medium"
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Portal>
|
||||
</Menu> */}
|
||||
</Box>
|
||||
),
|
||||
Actions: (
|
||||
<Box w={200}>
|
||||
<Button
|
||||
size={"xs"}
|
||||
colorScheme="red"
|
||||
variant="ghost"
|
||||
// onClick={() => {
|
||||
// setDeleteAlert(true);
|
||||
// }}
|
||||
onClick={() => {
|
||||
setActionId(item.id);
|
||||
setDeleteAlert(true);
|
||||
setActionStatus(item.status);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon me={2} /> Delete
|
||||
</Button>
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
// ====================================================[Functions]===================================================================
|
||||
|
||||
return (
|
||||
<>
|
||||
<TabularView
|
||||
title={"News Letter"}
|
||||
btnTitle={"Export email"}
|
||||
link={"/faq/add-faq"}
|
||||
apiData={email}
|
||||
tableHeadRow={tableHeadRow}
|
||||
extractedArray={extractedArray}
|
||||
searchTerm={searchTerm}
|
||||
setSearchTerm={setSearchTerm}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
totalPages={email?.data?.data?.totalPages}
|
||||
totalItems={email?.data?.data?.totalItems}
|
||||
noDataTitle={"NewsLetter"}
|
||||
/>
|
||||
<CustomAlertDialog
|
||||
onClose={() => setDeleteAlert(false)}
|
||||
isOpen={deleteAlert}
|
||||
alertHandler={() => handleDelete(actionId)}
|
||||
message={"Are you sure you want to delete Email?"}
|
||||
isLoading={deleteIsLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsLetter;
|
||||
424
src/Pages/News/AddNews.jsx
Normal file
@@ -0,0 +1,424 @@
|
||||
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";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
|
||||
const AddNews = () => {
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [createNews] = useCreateNewsMutation(); // Invoke the hook to get the mutation function
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState(fallbackImage);
|
||||
const [imageData, setImageData] = useState(null);
|
||||
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(addNews),
|
||||
});
|
||||
|
||||
const formData = watch()
|
||||
|
||||
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({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
reset();
|
||||
navigate("/news");
|
||||
} else if (response?.data?.statusCode === 500) {
|
||||
setIsLoading(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox
|
||||
status={"success"}
|
||||
message={response?.data?.message}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.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];
|
||||
setImageData(file);
|
||||
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"
|
||||
/>
|
||||
{selectedImage === fallbackImage || imageData === null ? (
|
||||
""
|
||||
) : (
|
||||
<Box display={"flex"} flexDirection={"column"} w={"100%"}>
|
||||
<span className="web-text-small">{imageData?.name}</span>
|
||||
<span className="web-text-small text-secondary fst-italic">
|
||||
{(imageData?.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
|
||||
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"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.title?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={formData?.title?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If description crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered {formData?.title?.length}{" "}
|
||||
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">
|
||||
Meta description
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
{...register("meta_description")}
|
||||
placeholder="Description"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.meta_description?.length > 160}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={formData?.meta_description?.length > 160 ? "red" : "green.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If description crosses 160 characters it will cause problem in
|
||||
alignment on website.you have entered {formData?.meta_description?.length}{" "}
|
||||
characters
|
||||
</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">
|
||||
Content
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
{...register("content")}
|
||||
placeholder="Content"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.content?.length > 230}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={formData?.content?.length > 230 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If content crosses 230 characters it will cause problem in
|
||||
alignment on website.you have entered {formData?.content?.length}{" "}
|
||||
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"
|
||||
min={today} // Disable past dates
|
||||
/>
|
||||
<FormHelperText className="web-text-small">
|
||||
Please share proper release date 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 10MB.
|
||||
</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"
|
||||
rounded={"sm"}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddNews;
|
||||
458
src/Pages/News/EditNews.jsx
Normal file
@@ -0,0 +1,458 @@
|
||||
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";
|
||||
import ToastBox from "../../Components/ToastBox";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const EditNews = () => {
|
||||
const { id } = useParams();
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const { data, error, isLoading, refetch } = 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 },
|
||||
watch,
|
||||
setValue,
|
||||
} = useForm({
|
||||
resolver: yupResolver(editNews),
|
||||
defaultValues: {
|
||||
title: "",
|
||||
release_date: "",
|
||||
meta_description: "",
|
||||
content: "",
|
||||
banner_image: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const formData = watch()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data) {
|
||||
setSelectedImage(
|
||||
`${API_URL}/${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);
|
||||
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) => {
|
||||
if (response?.data?.statusCode === 200) {
|
||||
setIsLoadingEdit(false);
|
||||
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={response?.data?.message} />
|
||||
),
|
||||
});
|
||||
refetch();
|
||||
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"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.title?.length > 50}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={formData?.title?.length > 50 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If title crosses 50 characters it will cause problem in
|
||||
alignment on website.you have entered {formData?.title?.length}{" "}
|
||||
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">
|
||||
Meta description
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
{...register("meta_description")}
|
||||
placeholder="Name"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
name="meta_description"
|
||||
type="text"
|
||||
id="meta_description"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.meta_description?.length > 160}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={formData?.meta_description?.length > 160 ? "red" : "green.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If meta_description crosses 160 characters it will cause problem in
|
||||
alignment on website.you have entered {formData?.meta_description?.length}{" "}
|
||||
characters
|
||||
</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">
|
||||
Content
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
{...register("content")}
|
||||
placeholder="Content"
|
||||
className="web-text-medium"
|
||||
size="sm"
|
||||
errorBorderColor="crimson"
|
||||
isInvalid={formData?.content?.length > 230}
|
||||
// maxLength={51}
|
||||
/>
|
||||
<FormHelperText
|
||||
color={formData?.content?.length > 230 ? "red" : "gray.500"}
|
||||
className="web-text-small"
|
||||
>
|
||||
If content crosses 230 characters it will cause problem in
|
||||
alignment on website.you have entered {formData?.content?.length}{" "}
|
||||
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.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 should be 1mb to protect website from slow
|
||||
loading.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Box className=" d-flex justify-content-end ">
|
||||
<Button
|
||||
isLoading={isLoadingEdit}
|
||||
spinner={<Loader01 />}
|
||||
color={"whitesmoke"}
|
||||
backgroundColor={"purple.900"}
|
||||
_hover={{
|
||||
backgroundColor: "purple.800",
|
||||
}}
|
||||
type="submit"
|
||||
size="sm"
|
||||
rounded={"sm"}
|
||||
>
|
||||
Save edit
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditNews;
|
||||