This commit is contained in:
2024-09-24 11:42:14 +05:30
parent 9adefe6acf
commit 91f090186a
164 changed files with 20205 additions and 0 deletions

21
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,21 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

28
.gitignore vendored Normal file
View 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

39
index.html Normal file
View File

@@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/assets/favicons.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>
<!-- <script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({
pageLanguage: 'en',
includedLanguages: `'en,ur,ar,mr,hi'`,
layout: google.translate.TranslateElement.InlineLayout.SIMPLE
}, 'google_translate_element');
}
</script> -->
<!-- <script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script> -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
</body>
</html>

7133
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@@ -0,0 +1,52 @@
{
"name": "optifii-admin",
"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",
"apexcharts": "^3.52.0",
"axios": "^1.7.2",
"bootstrap": "5.3.3",
"chart.js": "^4.4.3",
"dotenv": "^16.4.5",
"framer-motion": "^11.1.5",
"js-cookie": "^3.0.5",
"react": "^18.2.0",
"react-apexcharts": "^1.4.1",
"react-beautiful-dnd": "^13.1.1",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.3",
"react-icons": "^5.1.0",
"react-quill": "^0.0.2",
"react-redux": "^9.1.1",
"react-router-dom": "^6.22.3",
"redux-persist": "^6.0.0",
"redux-persist-transform-encrypt": "^5.1.1",
"uuid": "^10.0.0",
"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
View File

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

After

Width:  |  Height:  |  Size: 1.5 KiB

491
src/App.css Normal file
View File

@@ -0,0 +1,491 @@
@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');
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;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;
/* font-family: "Lato", sans-serif !important; */
}
::selection {
background-color: #6311CB; /* Change this to your desired color */
color: white; /* Optional: Change the text color */
}
.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: #1a04361c;
/* color: #fff; */
/* background-color: #e2e8f01c; */
}
.link {
text-decoration: none;
transition: all 0.2s ease-in-out;
font-weight: 400;
}
.link:hover {
background-color: #1a04361c;
/* color: #fff; */
/* background-color: #e2e8f01c !important; */
}
.active:hover {
background-color: #1a04361c;
/* color: #fff; */
/* 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;
}
.table-scroll::-webkit-scrollbar{
width: 2px !important;
height: 10px !important;
}
/* Total scrollbar width */
::-webkit-scrollbar {
width: 2px;
height: 12px;
}
/* The track (background) of the scrollbar */
::-webkit-scrollbar-track {
background: transparent;
border-radius: 0px;
}
/* The draggable scrollbar handle */
::-webkit-scrollbar-thumb {
background: #0041184f;
border-radius: 0px;
cursor: grabbing;
}
/* On hover */
::-webkit-scrollbar-thumb:hover {
background: #0041189a;
}
#google_translate_element {
/* display: none; Hide the default Google Translate dropdown */
position: fixed;
bottom: 0;
right: 0;
opacity: 0.1;
}
.goog-te-banner-frame {
display: none;
}
.goog-te-banner-frame.skiptranslate {
display: none !important;
}
.goog-logo-link {
display: none !important;
}
.goog-te-gadget {
color: transparent !important;
}
@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;
}
}
/* ========= [ switch BTN ============ */
/* From Uiverse.io by Nawsome */
.switch {
display: block;
background-color: black;
width: 85px;
height: 115px;
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0 1px 2px black, inset 0 2px 2px -2px white, inset 0 0 2px 15px #47434c, inset 0 0 2px 22px black;
border-radius: 5px;
padding: 20px;
perspective: 700px;
}
.switch input {
display: none;
}
.switch input:checked + .button {
transform: translateZ(20px) rotateX(25deg);
box-shadow: 0 -10px 20px #ff1818;
}
.switch input:checked + .button .light {
animation: flicker 0.2s infinite 0.3s;
}
.switch input:checked + .button .shine {
opacity: 1;
}
.switch input:checked + .button .shadow {
opacity: 0;
}
.switch .button {
display: block;
transition: all 0.3s cubic-bezier(1, 0, 1, 1);
transform-origin: center center -20px;
transform: translateZ(20px) rotateX(-25deg);
transform-style: preserve-3d;
background-color: #9b0621;
height: 100%;
position: relative;
cursor: pointer;
background: linear-gradient(#980000 0%, #6f0000 30%, #6f0000 70%, #980000 100%);
background-repeat: no-repeat;
}
.switch .button::before {
content: "";
background: linear-gradient(rgba(255, 255, 255, 0.8) 10%, rgba(255, 255, 255, 0.3) 30%, #650000 75%, #320000) 50% 50%/97% 97%, #b10000;
background-repeat: no-repeat;
width: 100%;
height: 50px;
transform-origin: top;
transform: rotateX(-90deg);
position: absolute;
top: 0;
}
.switch .button::after {
content: "";
background-image: linear-gradient(#650000, #320000);
width: 100%;
height: 58px;
transform-origin: top;
transform: translateY(50px) rotateX(-90deg);
position: absolute;
bottom: 0;
box-shadow: 0 50px 8px 0px black, 0 80px 20px 0px rgba(0, 0, 0, 0.5);
}
.switch .light {
opacity: 0;
animation: light-off 1s;
position: absolute;
width: 100%;
height: 100%;
background-image: radial-gradient(#ffc97e, #ff1818 40%, transparent 70%);
}
.switch .dots {
position: absolute;
width: 100%;
height: 100%;
background-image: radial-gradient(transparent 30%, rgba(101, 0, 0, 0.7) 70%);
background-size: 10px 10px;
}
.switch .characters {
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(white, white) 50% 20%/5% 20%, radial-gradient(circle, transparent 50%, white 52%, white 70%, transparent 72%) 50% 80%/33% 25%;
background-repeat: no-repeat;
}
.switch .shine {
transition: all 0.3s cubic-bezier(1, 0, 1, 1);
opacity: 0.3;
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(white, transparent 3%) 50% 50%/97% 97%, linear-gradient(rgba(255, 255, 255, 0.5), transparent 50%, transparent 80%, rgba(255, 255, 255, 0.5)) 50% 50%/97% 97%;
background-repeat: no-repeat;
}
.switch .shadow {
transition: all 0.3s cubic-bezier(1, 0, 1, 1);
opacity: 1;
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(transparent 70%, rgba(0, 0, 0, 0.8));
background-repeat: no-repeat;
}
@keyframes flicker {
0% {
opacity: 1;
}
80% {
opacity: 0.8;
}
100% {
opacity: 1;
}
}
@keyframes light-off {
0% {
opacity: 1;
}
80% {
opacity: 0;
}
}

82
src/App.jsx Normal file
View File

@@ -0,0 +1,82 @@
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";
import Welcome from "./Pages/PaymentSuccess";
import PaymentFailed from "./Pages/PaymentFailed";
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 token = localStorage.getItem('accessToken')
// console.log(token);
// const PrivateRoute = ({ children }) => {
// if (!isAuthenticate && isAuthenticatedInCookie !== "true") {
// return <Navigate to="/login" replace />;
// }
// return children;
// };
return (
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/payment-success" element={<Welcome />} />
<Route path="/payment-failed" element={<PaymentFailed />} />
<Route
path="/*"
element={
// isOnline ? (
isAuthenticate || isAuthenticatedInCookie === "true" ? (
// localStorage.getItem('accessToken') && localStorage.getItem('refreshToken') ? (
// true ? (
<DefaultLayout isOnline={isOnline} />
) : (
<Login />
)
// ) : (
// <NoInternetScreen />
// )
}
/>
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
);
};
export default App;

View File

@@ -0,0 +1,421 @@
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) => {
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;

View File

@@ -0,0 +1,489 @@
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");
}
const mutationResult = await updateBanner({ id: id, data: form })
.then((response) => {
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;

View 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

View File

@@ -0,0 +1,368 @@
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) => {
});
} 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;

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

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

View File

@@ -0,0 +1,30 @@
import { Button } from '@chakra-ui/button'
import React from 'react'
const PrimaryButton = ({title, onClick, ...props}) => {
return (
<Button
{...props}
_hover={{
// bgGradient: "linear(to-r, #5E0FCD, #3725EA)",
opacity: 0.8,
}}
bgGradient="linear(to-r, #3725EA, #5E0FCD)"
fontSize={"xs"}
px={8}
fontWeight={500}
size={"sm"}
color={"#fff"}
variant="solid"
transition={"0.5s all"}
_active={{
// bgGradient: "linear(to-r, #5E0FCD, #3725EA)",
opacity: 1,
}}
onClick={onClick}
>{title}</Button>
)
}
export default PrimaryButton

View File

@@ -0,0 +1,21 @@
import { Button } from '@chakra-ui/button'
import React from 'react'
const SecondaryButton = ({title, onClick, ...props}) => {
return (
<Button
{...props}
fontSize={"xs"}
px={8}
fontWeight={600}
size={"sm"}
variant="outline"
transition={"0.5s all"}
colorScheme='purple'
onClick={onClick}
>{title}</Button>
)
}
export default SecondaryButton

View File

@@ -0,0 +1,60 @@
label {
display: inline-block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.input-container {
display: flex;
flex-wrap: wrap;
row-gap: 1rem;
align-items: center;
border: 1px solid rgb(212, 206, 206);
border-radius: 0.2rem;
}
.chips {
list-style: none;
display: flex;
}
.chip {
background-color: #cfe1ff;
display: flex;
align-items: center;
padding: 0.2rem;
border-radius: 0.5rem;
margin-right: 1rem;
}
.chip span {
color: #013380;
}
.chip svg {
font-size: 2.5rem;
fill: #3270d1;
cursor: pointer;
transition: fill 0.2s;
}
.chip svg:hover {
fill: #2857a3;
transition: all 0.2s;
}
input {
font-size: 1.6rem;
background: transparent;
border: none;
outline: none;
}
.error-message {
margin-top: 1rem;
color: red;
}

View File

@@ -0,0 +1,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;

View File

@@ -0,0 +1,153 @@
import { Box, Button, Container, Text } from "@chakra-ui/react";
import banner from "../assets/communityBanner.webp";
const BannerContent = [
{
heading1: `Welcome To The`,
heading2: `Rubix Community`,
},
{
subheading: `This is a space for enterprises, dApp developers, stakeholders
and blockchain advocates to aggregate resources and ideas to make a difference
through our green blockchain technology.`,
},
{
btn: `Explore our community`,
},
];
const CommunityBanner = () => {
return (
<Box
height={"75%"}
backgroundImage={`url(${banner})`}
backgroundRepeat={"no-repeat"}
backgroundSize={"cover"}
display={"grid"}
placeContent={"center"}
>
<Container
alignItems={"center"}
display={"flex"}
height={"100%"}
alignContent={"center"}
maxW="container.xl"
textAlign={"left"}
marginTop={"2rem"}
paddingLeft={"3.5rem"}
>
<Box
width={"75%"}
>
<Text
fontWeight={700}
fontSize={"30px"}
// textTransform={"upperCase"}
color={"#DE858E"}
lineHeight={"42px"}
letterSpacing={"1px"}
sx={{
"@media (max-width: 996px)": {
fontSize: "46px",
},
"@media (max-width: 600px)": {
fontSize: "40px",
marginBottom: "0rem",
lineHeight: "54px",
},
}}
>
<span
style={{
color: "#fff",
}}
>
{BannerContent[0].heading1}
</span>{" "}
<br />
{BannerContent[0].heading2}
</Text>
<Box
marginTop={"0.5rem"}
width={"80%"}
sx={{
"@media (max-width: 500px)": {
width: "100%",
},
}}
>
<Text
color={"#fff"}
fontSize={"15px"}
fontWeight={"400"}
lineHeight={"27.5px"}
fontFamily={"Poppins"}
textTransform={"capitalize"}
>
{BannerContent[1].subheading}
</Text>
</Box>
<Button
position={"relative"}
backgroundColor={"transparent"}
cursor={"pointer"}
transition="0.3s ease-in-out"
color={"#fff"}
width={"180px"}
height={"44px"}
fontFamily={"Poppins"}
fontWeight={"400"}
border={"1px solid white"}
borderRadius={"10px"}
fontSize={"12px"}
zIndex={"1"}
overflow={"hidden"}
marginTop={"0.5rem"}
sx={{
"::before": {
content: '""',
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "65px",
height: "65px",
borderRadius: "50%",
transition: "0.35s linear",
zIndex: -1,
bgGradient:
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
opacity: "0",
},
"&:hover::before": {
width: "100%",
height: "120%",
borderRadius: "0px",
opacity: "1",
},
"@media (max-width: 500px)": {
fontSize: "14px",
width: "230px",
height: "44px",
marginTop: "2rem",
bgGradient:
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
color: "#000",
fontWeight: "600",
},
}}
_hover={{
color: "#000",
border: "1px solid white",
zIndex: 1,
}}
>
{BannerContent[2].btn}
</Button>
</Box>
</Container>
</Box>
);
};
export default CommunityBanner;

View File

@@ -0,0 +1,777 @@
import React, { useContext, useEffect, useState } from "react";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
import {
Box,
Divider,
FormControl,
FormLabel,
Heading,
Input,
Select,
Textarea,
Button,
Text,
Image,
Tabs,
TabList,
Tab,
TabPanel,
TabPanels,
Tooltip,
Switch,
useDisclosure,
} from "@chakra-ui/react";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import {
AddIcon,
CloseIcon,
DeleteIcon,
EditIcon,
ViewIcon,
WarningTwoIcon,
} from "@chakra-ui/icons";
import { TiWarning } from "react-icons/ti";
import GlobalStateContext from "../../Contexts/GlobalStateContext";
import { useNavigate } from "react-router-dom";
import FormField from "../../Components/FormField";
import { v4 as uuidv4 } from "uuid";
import AddIOCharges from "./AddIOCharges";
import FormInputMain from "../../Components/FormInputMain";
import DataTable from "../../Components/DataTable/DataTable";
import { debounce } from "../Master/Sponser/AddSponser";
import CustomAlertDialog from "../../Components/CustomAlertDialog";
import { formatDate } from "../../Components/Functions/UTCConvertor";
import InvestmentDocuments from "./InvestmentDocuments";
const schema = yup.object().shape({
ioName: yup.string().required("Arabic name is required"),
ioNameArabic: yup.string().required("Investment Object name is required"),
discription: yup.string().required("Sponser name is required"),
discriptionArabic: yup.string().required("Arabic name is required"),
typeName: yup.string().required("Investment Object name is required"),
typeNameArabic: yup.string().required("Sponser name is required"),
sponserName: yup.string().required("Arabic name is required"),
sponserNameArabic: yup
.string()
.required("Investment Object name is required"),
holdingPeriod: yup.string().required("Sponser name is required"),
ioStartus: yup.string().required("Investment Object name is required"),
ioStartusArabic: yup.string().required("Sponser name is required"),
goalAmount: yup.string().required("Arabic name is required"),
closingDate: yup.string().required("Investment Object name is required"),
minInvestment: yup.string().required("Sponser name is required"),
maxInvestment: yup.string().required("Arabic name is required"),
expectedReturn: yup.string().required("Investment Object name is required"),
originalValue: yup.string().required("Sponser name is required"),
keyname: yup.string().required("Arabic name is required"),
keyNameArabic: yup.string().required("Investment Object name is required"),
keyDescription: yup.string().required("Sponser name is required"),
keyDescriptionArabic: yup.string().required("Sponser name is required"),
docType: yup.string().required("Sponser name is required"),
destributedAmount: yup
.number()
.required("Distributed Amount is required")
.positive("Must be a positive number"),
year: yup.string().required("Year is required"),
tenure: yup
.number()
.required("Tenure is required")
.positive("Must be a positive number"),
annualReturn: yup
.number()
.required("Annual Return is required")
.positive("Must be a positive number"),
miniInvest: yup
.number()
.required("Minimum Invest is required")
.positive("Must be a positive number"),
quaterly: yup.string().required("Quaterly is required"),
targetClose: yup.date().required("Target close date is required"),
annualyield: yup
.number()
.required("Annual Yield is required")
.positive("Must be a positive number"),
iconUpload: yup.mixed().required("Profile image is required"),
bannerImages: yup.mixed().required("Profile image is required"),
otherImage: yup.mixed().required("Profile image is required"),
docAttach: yup.mixed().required("Profile image is required"),
videos: yup.mixed().required("Profile image is required"),
});
const startYear = 2024;
const endYear = 2124;
const years = Array.from(
{ length: endYear - startYear + 1 },
(_, i) => startYear + i
).map((year) => ({ value: year, label: year }));
const CreateIO = () => {
const navigate = useNavigate();
const { create, setCreate, sponser, setSponser, investment, setInvestment } =
useContext(GlobalStateContext);
const [bannerImageData, setBannerImageData] = useState(null);
const [otherImageData, setOtherImageData] = useState(null);
const [selectedBannerImageData, setSelectedBannerImageData] = useState(null);
const [selectedOtherImageData, setSelectedOtherImageData] = useState(null);
const [charges, setCharges] = useState([]);
const [totalCharge, setTotalCharge] = useState(0.0);
const [totalAmount, setTotalAmount] = useState(0.0);
const [searchTerm, setSearchTerm] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [deleteAlert, setDeleteAlert] = useState(false);
const [actionId, setActionId] = useState(false);
const [mouseEntered, setMouseEntered] = useState(false);
const [mouseEnteredId, setMouseEnteredId] = useState("");
const {
control,
handleSubmit,
reset,
watch,
setValue,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
console.log(errors);
useEffect(() => {
// Simulate loading
const timer = setTimeout(() => {
setIsLoading(false);
}, 1500);
// Cleanup the timer on component unmount
return () => clearTimeout(timer);
}, []);
const tableHeadRow = [
"Sponsor name",
"Address",
"Mobile no",
"Created At",
"Action",
];
const handleUpdateStatus = debounce((id) => {
setCreate((prevCreate) =>
prevCreate.map((create) =>
create.id === id ? { ...create, status: !create.status } : create
)
);
toast({
render: () => <ToastBox message={"Status changed succesfully.!"} />,
});
}, 300);
const filteredData = create?.filter((item) => {
// Filter by name (case insensitive)
const name = item.sponserName;
const searchLower = searchTerm.toLowerCase();
const nameMatches = name.toLowerCase().includes(searchLower);
return nameMatches;
});
const handleDelete = () => {
const updatedCreate = create.filter((create) => create.id !== actionId);
setTimeout(() => {
setSponser(updatedCreate);
setDeleteAlert(false);
setIsLoading(false);
}, 100);
setIsLoading(true);
};
const extractedArray = filteredData?.map((item) => ({
id: item?.id,
"Sponsor name": (
<Text
justifyContent={"left"}
as={"span"}
color={"teal.900"}
fontWeight={"500"}
className="d-flex align-items-center web-text-small"
>
"{item.sponserName}"
</Text>
),
Address: (
<Box w={350} isTruncated={true}>
<Text as={"span"} color={"teal.900"} fontWeight={"500"}>
" {item.sponserAddress}"
</Text>
</Box>
),
"Mobile no": (
<Box w={"auto"} isTruncated={true}>
<Text as={"span"} color={"teal.900"} fontWeight={"500"}>
"{item.mobileNo}"
</Text>
</Box>
),
"Created At": (
<span className="d-flex justify-content-between align-items-center">
<Text as={"span"} color={"gray.600"} fontWeight={"500"}>
{formatDate(item.createdAt)}
</Text>
</span>
),
Action: <Box display={"flex"} justifyContent={"space-between"}></Box>,
}));
const destributedAmount = Number(watch().destributedAmount) || 0;
useEffect(() => {
const calculateTotalCharge = () => {
const totalChargeValue = charges.reduce(
(acc, { value }) => acc + Number(value),
0
);
setTotalCharge(totalChargeValue);
};
const calculateTotalAmount = () => {
const totalChargeValue = charges.reduce(
(acc, { value }) => acc + Number(value),
0
);
setTotalAmount(destributedAmount + totalChargeValue);
};
calculateTotalCharge();
calculateTotalAmount();
}, [charges, destributedAmount]);
const onSubmit = (data) => {
// setValue("banner_image", selectedBannerImageData);
data.banner_image = selectedBannerImageData;
const updatedData = { ...data, status: "Available" };
setInvestment([...investment, updatedData]);
navigate("/view-io");
reset();
};
// Extract options for the select input
const createOptions = create.map((item) => ({
value: item.sponserName,
label: item.sponserName,
}));
const handleBannerImageChange = (e) => {
const file = e.target.files[0];
setBannerImageData(file);
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setSelectedBannerImageData(reader.result);
};
reader.readAsDataURL(file);
}
};
const { isOpen, onOpen, onClose } = useDisclosure();
const firstField = React.useRef();
// Handler for file input
const handleOtherImageChange = (e) => {
const files = Array.from(e.target.files);
const newImageData = [...(otherImageData || []), ...files]; // Ensure otherImageData is an array
setOtherImageData(newImageData);
const readers = files.map((file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
});
Promise.all(readers)
.then((results) => {
setSelectedOtherImageData([
...(selectedOtherImageData || []),
...results,
]); // Ensure selectedOtherImageData is an array
})
.catch((error) => {
console.error("Error reading files:", error);
});
};
// Function to remove a specific image
const removeOtherImage = (index) => {
const newImageData = otherImageData.filter((_, i) => i !== index);
const newSelectedImageData = selectedOtherImageData.filter(
(_, i) => i !== index
);
setOtherImageData(newImageData);
setSelectedOtherImageData(newSelectedImageData);
};
const formFields = [
{
label: "IO Name (English)",
placeHolder: " ",
name: "ioName",
type: "text",
isRequired: true,
section: " ",
width: "49%",
},
{
label: "IO Name (Arabic)",
placeHolder: " ",
name: "ioNameArabic",
type: "text",
isRequired: true,
section: " ",
width: "49%",
},
{
label: "Description (English)",
placeHolder: " ",
name: "discription",
type: "textarea",
isRequired: true,
section: " ",
width: "49%",
},
{
label: "Description (Arabic)",
placeHolder: " ",
name: "discriptionArabic",
type: "textarea",
isRequired: true,
section: " ",
width: "49%",
},
{
label: "Investment Type (English)",
placeHolder: " ",
name: "typeName",
type: "select",
isRequired: true,
section: " ",
width: "49%",
options: [
{
label: "option 1",
value: "option 1",
},
{
label: "option 2",
value: "option 2",
},
{
label: "option 3",
value: "option 3",
},
{
label: "option 4",
value: "option 4",
},
],
},
{
label: "Investment Type (Arabic)",
placeHolder: " ",
name: "typeNameArabic",
type: "select",
isRequired: true,
section: " ",
width: "49%",
options: [
{
label: "option 1",
value: "option 1",
},
{
label: "option 2",
value: "option 2",
},
{
label: "option 3",
value: "option 3",
},
{
label: "option 4",
value: "option 4",
},
],
},
{
label: "Sponsor Name (English)",
placeHolder: " ",
name: "sponserName",
type: "text",
isRequired: true,
section: " ",
width: "49%",
},
{
label: "Goal Amount (English)",
placeHolder: " ",
name: "goalAmount",
type: "Number",
isRequired: true,
section: " ",
width: "49%",
},
{
label: "Minimum Investment Amount (English)",
placeHolder: " ",
name: "minInvestment",
type: "number",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Maximum Investment Amount (English)",
placeHolder: " ",
name: "maxInvestment",
type: "number",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Holding Period (English)",
placeHolder: " ",
name: "holdingPeriod",
type: "number",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Expected Return Estimated (English)",
placeHolder: " ",
name: "expectedReturn",
type: "number",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Closing Date (English)",
placeHolder: " ",
name: "closingDate",
type: "date",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "IO Status (English)",
placeHolder: " ",
name: "minInvestment",
type: "text",
isRequired: true,
section: " ",
width: "32.3%",
},
];
const keyMerits = [
{
label: "Name (English)",
placeHolder: " ",
name: "keyname",
type: "text",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Name (Arabic)",
placeHolder: " ",
name: "keyNameArabic",
type: "text",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Icon",
placeHolder: " ",
name: "iconUpload",
type: "fileNormal",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Description (English)",
placeHolder: " ",
name: "keyDescription",
type: "textarea",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Description (Arabic)",
placeHolder: " ",
name: "keyDescriptionArabic",
type: "textarea",
isRequired: true,
section: " ",
width: "32.3%",
},
];
const images = [
{
label: "Banner Images ",
placeHolder: " ",
name: "bannerImages",
type: "fileNormal",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Other Images",
placeHolder: " ",
name: "otherImage",
type: "fileNormal",
isRequired: true,
section: " ",
width: "32.3%",
},
];
const documents = [
{
label: "Type",
placeHolder: " ",
name: "docType",
type: "text",
isRequired: true,
section: " ",
width: "32.3%",
},
{
label: "Attachment",
placeHolder: " ",
name: "type",
type: "docAttach",
isRequired: true,
section: " ",
width: "32.3%",
},
];
const Videos = [
{
label: "Videos",
placeHolder: " ",
name: "videos",
type: "fileNormal",
isRequired: true,
section: " ",
width: "32.3%",
},
];
const groupedFields = formFields.reduce((groups, field) => {
const { section } = field;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(field);
return groups;
}, {});
const groupedFieldsTwo = keyMerits.reduce((groups, field) => {
const { section } = field;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(field);
return groups;
}, {});
const groupedFieldsThree = images.reduce((groups, field) => {
const { section } = field;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(field);
return groups;
}, {});
const groupedFieldsFour = documents.reduce((groups, field) => {
const { section } = field;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(field);
return groups;
}, {});
const groupedFieldsFive = Videos.reduce((groups, field) => {
const { section } = field;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(field);
return groups;
}, {});
return (
<Box {...OPACITY_ON_LOAD} overflowY={"scroll"} height={"100vh"} pb={14}>
<Tabs mt={4}>
<TabList>
<Tab
fontSize={"sm"}
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
>
IO Details
</Tab>
<Tab
fontSize={"sm"}
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
>
Investment Documents
</Tab>
<Tab
fontSize={"sm"}
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
>
Key Merits
</Tab>
<Tab
fontSize={"sm"}
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
>
IO artifacts
</Tab>
<Tab
fontSize={"sm"}
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
>
Investors
</Tab>
<Tab
fontSize={"sm"}
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
>
IO Cash detail
</Tab>
<Tab
fontSize={"sm"}
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
>
IO NAV detail
</Tab>
<Tab
fontSize={"sm"}
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
>
Distribution
</Tab>
</TabList>
<TabPanels>
<TabPanel>
<FormInputMain
width={"23.8%"}
groupedFields={groupedFields}
control={control}
errors={errors}
></FormInputMain>
</TabPanel>
<TabPanel>
<Box display={'flex'} justifyContent={'space-between'} mb={4}>
<Input
type="search"
width={300}
placeholder="Search..."
size="sm"
rounded="sm"
focusBorderColor="green.500"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Button leftIcon={<AddIcon />} onClick={onOpen}
size={"sm"}
// width={"44.5%"}
fontSize={'xs'}
rounded={"sm"}
colorScheme='green'
>
Add
</Button>
<InvestmentDocuments isOpen={isOpen} onClose={onClose} firstField={firstField} />
</Box>
<DataTable
emptyMessage={`We don't have any Sponers `}
tableHeadRow={tableHeadRow}
data={extractedArray}
isLoading={isLoading}
viewActionId={actionId}
setViewActionId={setActionId}
// totalPages={10}
setMouseEnteredId={setMouseEnteredId}
setMouseEntered={setMouseEntered}
/>
<CustomAlertDialog
onClose={() => setDeleteAlert(false)}
isOpen={deleteAlert}
message={"Are you sure you want to delete sponers?"}
alertHandler={handleDelete}
isLoading={isLoading}
/>
</TabPanel>
<TabPanel>
<FormInputMain
width={"23.8%"}
groupedFields={groupedFieldsThree}
control={control}
errors={errors}
onSubmit={handleSubmit(onSubmit)}
></FormInputMain>
</TabPanel>
<TabPanel>
<FormInputMain
width={"23.8%"}
groupedFields={groupedFieldsFour}
control={control}
errors={errors}
onSubmit={handleSubmit(onSubmit)}
></FormInputMain>
</TabPanel>
<TabPanel>
<FormInputMain
width={"23.8%"}
groupedFields={groupedFieldsFive}
control={control}
errors={errors}
onSubmit={handleSubmit(onSubmit)}
></FormInputMain>
</TabPanel>
<TabPanel></TabPanel>
</TabPanels>
</Tabs>
</Box>
);
};
export default CreateIO;

View File

@@ -0,0 +1,90 @@
import React, { forwardRef } from 'react';
import { Input } from "@chakra-ui/react";
export const formatCurrency = (value) => {
if (value === undefined || value === null) return '';
const [integer, decimal] = String(value).split('.');
const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return decimal !== undefined ? `${formattedInteger}.${decimal}` : formattedInteger;
};
const CurrencyInput = forwardRef(({ value, onChange, ...props }, ref) => {
const handleChange = (event) => {
let { value } = event?.target;
// Remove non-numeric characters except decimal point
value = value.replace(/[^0-9.]/g, '');
// Ensure only one decimal point
const parts = value.split('.');
if (parts.length > 2) {
value = parts[0] + '.' + parts.slice(1).join('');
}
// Restrict to two decimal places
if (parts[1]?.length > 2) {
value = parts[0] + '.' + parts[1].slice(0, 2);
}
onChange(value); // Pass the raw value to parent
};
return (
<Input
{...props}
ref={ref} // Forward ref here
type="text"
value={formatCurrency(value)}
onChange={handleChange}
/>
);
});
export default CurrencyInput;
// import React, { forwardRef } from 'react';
// import { Input } from "@chakra-ui/react";
// export const formatCurrency = (value) => {
// if (value === undefined || value === null) return ''; // Handle undefined or null values
// const [integer, decimal] = String(value).split('.'); // Convert value to string before splitting
// const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// return decimal ? `${formattedInteger}.${decimal}` : formattedInteger;
// };
// const CurrencyInput = forwardRef(({ value, onChange, ...props }, ref) => {
// const handleChange = (event) => {
// let { value } = event?.target;
// // Remove non-numeric characters except decimal point
// value = value?.replace(/[^0-9.]/g, '');
// // Ensure only one decimal point and restrict to two decimal places
// const parts = value?.split('.');
// if (parts.length > 2) {
// value = parts[0] + '.' + parts?.slice(1)?.join('');
// }
// if (parts[1]?.length > 2) {
// value = parts[0] + '.' + parts[1]?.slice(0, 2);
// }
// onChange(value); // Pass the raw value to parent or use it directly
// };
// return (
// <Input
// {...props}
// ref={ref} // Forward ref here
// type="text"
// value={formatCurrency(value)}
// onChange={handleChange}
// />
// );
// });
// export default CurrencyInput;

View File

@@ -0,0 +1,47 @@
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={10}>
{message}
</AlertDialogBody>
<AlertDialogFooter display={"flex"} justifyContent={"center"}>
<Button
size={"sm"}
// ref={cancelRef}
onClick={onClose}
rounded={'sm'}
>
No
</Button>
<Button
isLoading={isLoading}
onClick={alertHandler}
size={"sm"}
rounded={'sm'}
colorScheme="forestGreen"
ml={3}
>
Yes
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
export default CustomAlertDialog;

View File

@@ -0,0 +1,106 @@
import React from "react";
import {
Box,
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
Text,
Button,
} from "@chakra-ui/react";
import { Link, useLocation } from "react-router-dom";
import { nav } from "../Routes/Nav";
import { MinusIcon } from "@chakra-ui/icons";
const CustomBreadcrumb = () => {
const { pathname } = useLocation();
// Remove leading slash and split path into parts
const pathParts = pathname.replace(/^\//, "").split("/");
// Find the current menu item based on the provided path
const findMenuItem = (path) => {
let menuItem = null;
nav.forEach((menu) => {
if (menu.submenu) {
menu.submenu.forEach((item) => {
if (item.path === path) {
menuItem = item;
}
});
}
});
return menuItem;
};
// Generate breadcrumb items based on the current path
const generateBreadcrumbs = (pathParts) => {
const breadcrumbs = [];
let currentPath = "";
pathParts.forEach((part, index) => {
currentPath += `/${part}`;
const menuItem = findMenuItem(currentPath);
if (menuItem) {
breadcrumbs.push({ path: currentPath, title: menuItem.title });
} else {
// For nested paths without direct match, create a custom breadcrumb title
const customTitle = part.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
breadcrumbs.push({ path: currentPath, title: customTitle });
}
});
return breadcrumbs;
};
const breadcrumbs = generateBreadcrumbs(pathParts);
return (
<Box
fontWeight="medium"
fontSize="xs"
color={"blue.700"}
display={"flex"}
alignItems={"center"}
p={1}
mt={1}
borderBottom={"1px dashed #DEE2E6"}
>
<Button
cursor={"pointer"}
variant="ghost"
pt={1.5}
pb={1.5}
ps={2}
pe={2}
rounded={"full"}
size={"xs"}
as={"span"}
>
Master
</Button>{" "}
{breadcrumbs.map((item, index) => (
<React.Fragment key={index}>
<Text size={"md"} as={"span"}>
{/* <MinusIcon fontStyle={4} color={"#1A202C"}/> */}
-
</Text>
<Link to={item.path}>
<Button
cursor={"pointer"}
variant="ghost"
pt={0.5}
pb={0.5}
ps={2}
pe={2}
rounded={"full"}
size={"xs"}
as={"span"}
>
{item.title}
</Button></Link>
</React.Fragment>
))}
</Box>
);
};
export default CustomBreadcrumb;

View File

@@ -0,0 +1,157 @@
import React, { useContext, useState } from "react";
import {
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
Skeleton,
TableCaption,
Box,
} from "@chakra-ui/react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import EmptySearchList from "../EmptySearchList";
import { useNavigate } from "react-router-dom";
import GlobalStateContext from "../../Contexts/GlobalStateContext";
const DataTable = ({
data,
setData,
isLoading,
tableHeadRow,
tableHeadRowTwo,
emptyMessage,
centered,
setMouseEntered,
setMouseEnteredId,
caption,
isDraggable
}) => {
const navigate = useNavigate();
const { slideFromRight } = useContext(GlobalStateContext);
if (isLoading) {
return (
<Box>
{Array.from({ length: 10 }).map((_, index) => (
<Skeleton height="32px" my="10px" key={index} />
))}
</Box>
);
}
const handleDragEnd = (result) => {
if (!result.destination) return;
const reorderedItems = Array.from(data);
const [removed] = reorderedItems.splice(result.source.index, 1);
reorderedItems.splice(result.destination.index, 0, removed);
setData(reorderedItems)
// console.log("New Order:", reorderedItems.map((item, index) => ({ index, item })));
};
return (
<TableContainer pb={8} overflowX={"scroll"} className="h-auto w-100 table-scroll">
{data?.length === 0 ? (
<EmptySearchList message={emptyMessage} />
) : (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="table">
{(provided) => (
<>
{/* <Box mb={2}>{caption}</Box> */}
<Table size="sm" {...provided.droppableProps} ref={provided.innerRef}>
<TableCaption p={0}>{caption}</TableCaption>
<Thead backgroundColor="forestGreen.100">
<Tr>
{tableHeadRow.map((heading, index) => (
<Th
textAlign={tableHeadRow.length - 1 === index || centered ? "center" : "left"}
key={index}
p={3}
width="100px" // Adjust width as needed
color={"#004118"}
whiteSpace="normal" // Allow text to wrap
wordBreak="normal" // Ensure long words break properly
overflowWrap="normal" // Break long words if necessary
>
{heading}
</Th>
))}
</Tr>
</Thead>
<Tbody className="web-text-small">
{data?.map((item, index) => (
item.id && isDraggable ? (
<Draggable key={item.id.toString()} draggableId={item.id.toString()} index={index}>
{(provided, snapshot) => (
<Tr
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
transition={"0s all"}
_hover={{
bg: "blue.50",
cursor: "grab",
}}
bg={snapshot.isDragging ? "blue.100" : index % 2 ? "white" : "forestGreen.50"}
boxShadow={snapshot.isDragging ? "0 0 1em rgba(0, 0, 0, 0.2)" : "none"}
>
{tableHeadRow.map((heading, i) => (
<Td
textAlign={tableHeadRow.length - 1 === i || centered ? "center" : "left"}
color={"gray.600"}
key={i}
style={{
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
className="web-text-small"
>
<Skeleton fadeDuration={index / 12} isLoaded={!isLoading}>
{item[heading]}
</Skeleton>
</Td>
))}
</Tr>
)}
</Draggable>
) : (
<Tr bg={index % 2 ? "forestGreen.50" : "white"} key={index}>
{tableHeadRow.map((heading, i) => (
<Td
textAlign={tableHeadRow.length - 1 === i || centered ? "center" : "left"}
color={"gray.600"}
key={i}
style={{
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
className="web-text-small"
w={400}
>
<Skeleton display={'flex'} alignItems={'center'} justifyContent={tableHeadRow.length - 1 === i || centered ? "center" : "start"} h={5} fadeDuration={index / 12} isLoaded={!isLoading}>
{item[heading]}
</Skeleton>
</Td>
))}
</Tr>
)
))}
{provided.placeholder}
</Tbody>
</Table>
</>
)}
</Droppable>
</DragDropContext>
)}
</TableContainer>
);
};
export default DataTable;

View File

@@ -0,0 +1,115 @@
import React from "react";
import {
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
Skeleton,
TableCaption,
Tfoot,
} from "@chakra-ui/react";
import EmptySearchList from "../EmptySearchList";
import { TABLE_PAGINATION } from "../../Constants/Paginations";
const DataTable = ({
data,
isLoading,
tableHeadRow,
emptyMessage,
centered,
total,
}) => {
console.log(data);
const columnWidth =
data && data[0]
? `${(100 / Object.keys(data[0]).length).toFixed(2)}%`
: "auto";
return (
<TableContainer overflowX={"auto"} className="h-auto mb-3 w-100 table-scroll">
{data?.length === 0 ? (
<EmptySearchList message={emptyMessage} />
) : (
<Table size="sm">
<TableCaption p={total ? 0 : null}>
{total ? total : "Tanami v1.0.0"}
</TableCaption>
<Thead backgroundColor="forestGreen.100">
<Tr>
{tableHeadRow.map((heading, index) => (
<Th
textAlign={
tableHeadRow.length - 1 === index || centered
? "center"
: "left"
}
key={index}
p={3}
width="20px" // Adjust width as needed
color={"#004118"}
whiteSpace="normal" // Allow text to wrap
wordBreak="normal" // Ensure long words break properly
overflowWrap="normal" // Break long words if necessary
textTransform={"none"}
>
{isLoading ? <Skeleton height="20px" /> : heading}
{/* {heading} */}
</Th>
))}
</Tr>
</Thead>
<Tbody className="web-text-small">
{isLoading
? Array.from({ length: TABLE_PAGINATION?.size }).map(
(_, index) => (
<Tr bg={index % 2 === 0 ? "" : "forestGreen.50"} key={index}>
{tableHeadRow.map((_, i) => (
<Td
width={"fit-content"}
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 bg={index % 2 === 0 ? "" : "forestGreen.50"} key={index}>
{tableHeadRow.map((heading, i) => (
<Td
textAlign={
tableHeadRow?.length - 1 === i || centered
? "center"
: "left"
}
color={"gray.600"}
key={i}
style={{
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
className="web-text-small"
>
{item[heading]}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
)}
</TableContainer>
);
};
export default DataTable;

View File

@@ -0,0 +1,55 @@
import React, { useState } from 'react';
import ApexCharts from 'react-apexcharts';
const ApexChart = ({ data }) => {
// Customize colors and series titles here
const [options] = useState({
chart: {
width: 600,
type: 'donut',
},
plotOptions: {
pie: {
startAngle: -90,
endAngle: 270,
donut: {
size: '45%' // Adjust the donut size here (percentage of chart size)
}
}
},
labels:data?.labels,
dataLabels: {
enabled: false
},
fill: {
type: 'gradient',
},
colors: data?.backgroundColor,
legend: {
show: false,
position: 'right',
labels: {
colors: ['#000'], // Customize the color of the legend labels
useSeriesColors: true
}
},
responsive: [{
breakpoint: 480,
options: {
chart: {
width: 500
},
legend: {
position: 'center'
}
}
}]
});
return (
<ApexCharts options={options} series={data?.values} type="donut" width={300} />
);
};
export default ApexChart;

View File

@@ -0,0 +1,66 @@
import React, { useState } from 'react';
import ReactApexChart from 'react-apexcharts';
function ApexLine() {
const [chartOptions, setChartOptions] = useState({
series: [{
name: 'Rate',
data: [45, 23, 70, 65, 5, 34, 32],
gradientToColors: ['#004017'],
}],
options: {
chart: {
height: 350,
type: 'line',
toolbar: {
show: false // Hide the action icons
}
},
stroke: {
width: 5,
curve: 'smooth',
colors: ['#598369'], // Customize the line color here
},
markers: {
size: 6, // Size of markers
colors: ['#004118'], // Marker (dot) color
strokeColor: '#fff', // Stroke color of the marker
strokeWidth: 2
},
xaxis: {
type: 'category', // Change from 'datetime' to 'category'
categories: ['BH', 'KW', 'OM', 'QA', 'SA', 'UAE', 'IND'],
tickAmount: 7
},
title: {
text: 'Exchange Rate Currency', // Adjust the title if needed
align: 'left',
style: {
fontSize: '15px',
color: '#000',
fontWeight: 400
}
},
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
gradientToColors: ['#004017'],
shadeIntensity: 4,
type: 'horizontal',
opacityFrom: 1,
opacityTo: 1,
stops: [0, 100] // Gradient stops
},
}
}
});
return (
<div>
<ReactApexChart options={chartOptions.options} series={chartOptions.series} type="line" height={"100%"} width={"600"} />
</div>
);
}
export default ApexLine;

View File

@@ -0,0 +1,39 @@
// DonutChart.jsx
import React from 'react';
import { Doughnut } from 'react-chartjs-2';
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement } from 'chart.js';
ChartJS.register(Title, Tooltip, Legend, ArcElement);
const DonutChart = ({ data, width = 300, height = 250 }) => {
const chartData = {
labels: data.labels,
datasets: [
{
label: 'My Dataset',
data: data.values,
backgroundColor: [ '#3182ce', '#004118', '#D69E2E', '#E53E3E' ],
borderColor: ['#FFF'],
borderWidth: 2,
},
],
};
const options = {
responsive: true,
plugins: {
legend: {
display: false, // Hide the legend
},
tooltip: {
callbacks: {
label: (tooltipItem) => `${tooltipItem.label}: ${tooltipItem.raw}`,
},
},
},
};
return <Doughnut data={chartData} options={options} width={'100%'} />;
};
export default DonutChart;

View File

@@ -0,0 +1,80 @@
// LineChart.jsx
import React from 'react';
import { Line } from 'react-chartjs-2';
import { Chart as ChartJS, Title, Tooltip, Legend, LineElement, PointElement, LinearScale, CategoryScale } from 'chart.js';
// Register the necessary components
ChartJS.register( Title, Tooltip, Legend, LineElement, PointElement, LinearScale, CategoryScale );
// Sample options for the chart
// Sample options for the chart
const options = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
tooltip: {
callbacks: {
label: function (context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += `${context.parsed.y}`;
}
return label;
}
}
}
},
animation: {
tension: {
duration: 2000,
easing: 'linear',
from: 1,
to: 0,
loop: true
}
}
};
const Utils = {
numbers: ({ count, min, max }) => Array.from({ length: count }, () => Math.floor(Math.random() * (max - min + 1)) + min),
CHART_COLORS: {
red: 'rgba(255, 99, 132, 1)',
darkGreen: 'rgba(0, 65, 24, 1)' // Added color related to #004118
},
transparentize: (color, opacity) => {
// Use regex to replace the alpha value
return color.replace(/(rgba\(\d+, \d+, \d+, )\d+(\))/, `$1${opacity}$2`);
}
};
const LineChart = ({ width = 300, height = 250 }) => {
const data = {
labels: ['Bahrain', 'Kuwait', 'Oman', 'Qatar', 'Saudi Arabia', 'UAE', 'India'],
datasets: [
{
label: 'Exchange rate',
data: [45.9087, 23.8798, 99.9809, 65.8987, 65.8987, 34.9898, 32.8987],
borderColor: Utils.CHART_COLORS.darkGreen,
backgroundColor: Utils.transparentize(Utils.CHART_COLORS.darkGreen, 0.5),
pointStyle: 'rectRounded',
pointRadius: 10,
pointHoverRadius: 15
}
]
};
return (
<Line data={data} options={options} />
);
};
export default LineChart;

View File

@@ -0,0 +1,41 @@
import { Box, Input } from "@chakra-ui/react";
import React, { useRef, useState } from "react";
import audioClick from "../assets/click-151673.mp3";
const DummyComponent = () => {
// Define the state for the checkbox
const [isSwitchOn, setIsSwitchOn] = useState(false);
const audio = useRef();
// Function to toggle the switch
const handleToggle = () => {
setIsSwitchOn(!isSwitchOn);
if(audio.current){
audio.current.play();
}
};
return (
<Box display={"flex"} justifyContent={"right"} p={"2rem"}>
<label className="switch">
<Input
type="checkbox"
checked={isSwitchOn}
onChange={handleToggle} // Toggle the switch on change
/>
<Box className="button">
<div className="light"></div>
<div className="dots"></div>
<div className="characters"></div>
<div className="shine"></div>
<div className="shadow"></div>
</Box>
</label>
<audio ref={audio} src={audioClick} />
</Box>
);
};
export default DummyComponent;

View File

@@ -0,0 +1,23 @@
import { Box, Image, Text } from "@chakra-ui/react"
// import EmptySearchListImage from "../assets/empty_state_empty_folder.svg"
import EmptySearchListImage from "../assets/EmptySearchList.png"
const EmptySearchList = ({message}) => {
return (
<Box
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
flexDirection={'column'}
w={"100%"} h={"40vh"}
>
<Image w={200} mb={8} src={EmptySearchListImage} alt='empty list' />
{/* <Text className=" fw-bold fs-5" >{message}</Text> */}
<Text className=" fw-bold fs-5" >We do not have any records</Text>
{/* <Text as={'p'} className="web-text-medium">Posts of tanami will appear here.</Text> */}
</Box>
)
}
export default EmptySearchList

View File

@@ -0,0 +1,549 @@
import {
FormControl,
FormLabel,
Input,
Textarea,
Select,
Checkbox,
RadioGroup,
Radio,
Stack,
Box,
Heading,
FormHelperText,
Kbd,
Image,
Text,
Table,
Thead,
Tr,
Th,
Tbody,
Td,
InputGroup,
InputRightAddon,
} from "@chakra-ui/react";
import { Controller } from "react-hook-form";
import { TiWarning } from "react-icons/ti";
import { motion } from "framer-motion";
import { AddIcon, CloseIcon } from "@chakra-ui/icons";
import CurrencyInput from "./CurrencyInput";
const today = new Date().toISOString().split("T")[0];
export const formatDatee = (dateString) => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are zero-based
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const defaultDate = "8/2/2024";
// Format the default date as YYYY-MM-DD
const formattedDate = formatDatee(defaultDate);
const FormField = ({
label,
control,
name,
id,
type = "text",
options = [],
errors,
isRequired,
rules,
arabic,
placeHolder,
helperText,
multiple,
handleImageChange,
selectedImageData,
setSelectedImageData,
removeImage,
imageData,
width,
value,
handleInputChange,
align,
maxLength,
dateValue,
...props
}) => (
<FormControl
w={width ? width : "49%"}
isInvalid={errors[name]}
isRequired={type === "date" ? true: isRequired}
mb={2}
>
<FormLabel textAlign={"left"} fontSize={"xs"} color={"gray.600"}>
{label}
</FormLabel>
<Controller
control={control}
name={name}
defaultValue={value}
rules={rules}
render={({ field }) => {
if (type === "select") {
return (
<Select
bg={"#F5F8F6"}
focusBorderColor="forestGreen.300"
size={"sm"}
{...field}
{...props}
cursor={"pointer"}
fontSize={"sm"}
placeholder={placeHolder ? placeHolder : label}
textAlign={arabic ? "right" : "left"}
_placeholder={{ fontSize: "sm" }}
rounded={"sm"}
>
{options.map((option, index) => (
<option key={index} value={option.value}>
{option.label}
</option>
))}
</Select>
);
} else if (type === "textarea") {
return (
<Textarea
focusBorderColor="forestGreen.400"
size={"sm"}
{...field}
{...props}
placeholder={placeHolder ? placeHolder : label}
textAlign={arabic ? "right" : "left"}
_placeholder={{ fontSize: "sm" }}
rounded={"sm"}
resize={"none"}
rows={2}
bg={"#F5F8F6"}
maxLength={maxLength} // Set the maximum character limit
/>
);
} else if (type === "checkbox") {
return (
<Checkbox
size={"sm"}
{...field}
{...props}
textAlign={arabic ? "right" : "left"}
>
{label}
</Checkbox>
);
} else if (type === "radio") {
return (
<RadioGroup bg={"#F5F8F6"} {...field} {...props}>
<Stack direction="row">
{options.map((option, index) => (
<Radio key={index} value={option.value}>
{option.label}
</Radio>
))}
</Stack>
</RadioGroup>
);
} else if (type === "file") {
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 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
{...field}
{...props}
type="file"
height="100%"
width="100%"
position="absolute"
top="0"
left="0"
opacity="0"
aria-hidden="true"
accept="image/*"
onChange={(e) => {
field.onChange(e);
handleImageChange(e);
}}
onDrop={(e) => {
field.onChange(e);
handleImageChange(e);
}}
/>
</Box>
</Box>
</>
);
} else if (type === "fileNormal") {
return (
<>
<Input
id={id}
{...field}
{...props}
multiple={multiple} // Support for multiple file uploads
accept="image/*"
type="file"
className="web-text-medium form-control rounded-1"
size="sm"
bg={"#F5F8F6"}
onChange={(e) => {
field.onChange(e);
handleImageChange(e);
}}
/>
{multiple && (
<FormHelperText className="web-text-small">
You can select multiple images using{" "}
<span className="text-dark">
<Kbd size={"sm"} className="text-dark">
ctrl
</Kbd>{" "}
+ <Kbd className="text-dark">select</Kbd>
</span>
</FormHelperText>
)}
{selectedImageData &&
(multiple ? (
selectedImageData?.length > 0 && (
<Box
mt={4}
width={"100%"}
display="flex"
flexWrap="wrap"
gap={4}
>
{selectedImageData?.map((imageDataLink, index) => (
<Box key={index} width={"100px"}>
<Image
rounded={"md"}
objectFit={"cover"}
src={imageDataLink}
alt={`profile-${index}`}
width={100}
height={100}
/>
<Box
display={"flex"}
flexDirection={"column"}
position={"relative"}
>
<CloseIcon
onClick={() => removeImage(index)}
bg={"#fff"}
className="link pointer"
p={1}
fontSize={"lg"}
color={"red"}
fontWeight={"500"}
rounded={"md"}
position={"absolute"}
bottom={0}
right={0}
/>
<Text
as={"span"}
fontSize={"sm"}
fontWeight={"500"}
mt={1}
isTruncated={true}
>
{imageData[index]?.name}
</Text>
<Text
as={"span"}
fontSize={"sm"}
fontStyle={"italic"}
>
{(imageData[index]?.size / (1024 * 1024)).toFixed(
2
)}{" "}
mb
</Text>
</Box>
</Box>
))}
<AddIcon
onClick={() => document.getElementById(id).click()}
rounded={"md"}
width={50}
height={50}
mt={26}
p={4}
cursor={"pointer"}
className="link"
/>
</Box>
)
) : (
<Box mt={5} width={"49%"}>
<Image
rounded={"md"}
objectFit={"cover"}
src={selectedImageData}
alt="profile"
width={100}
height={100}
/>
<Box
w={"30%"}
display={"flex"}
flexDirection={"column"}
position={"relative"}
>
<CloseIcon
onClick={() => setSelectedImageData(null)}
className="link pointer"
p={1}
fontSize={"lg"}
color={"red"}
fontWeight={"500"}
rounded={"md"}
position={"absolute"}
top={1}
right={0}
/>
<Text
as={"span"}
fontSize={"xs"}
w={"70%"}
fontWeight={"500"}
mt={1}
isTruncated={true}
>
{imageData?.name}
</Text>
<Text as={"span"} fontSize={"xs"} fontStyle={"italic"}>
{(imageData?.size / (1024 * 1024)).toFixed(2)} mb
</Text>
</Box>
</Box>
))}
</>
);
} else if (type === "table") {
return (
<Table w={"100%"} variant="simple">
<Thead>
<Tr>
{value?.map((item, index) => (
<Th
border={"none"}
p={2}
textTransform={"none"}
key={index}
>
<Box
as="span"
display={"flex"}
alignItems={"center"}
gap={2}
>
<Image
objectFit={"cover"}
opacity={0.9}
rounded={"full"}
w={6}
h={6}
src={
import.meta.env.VITE_IMAGE_URL +
item?.logo
}
/>
{item.country === "United Arab Emirates"
? "UAE"
: item.country}
</Box>
</Th>
))}
</Tr>
</Thead>
<Tbody>
<Tr>
{value?.map((item, index) => (
<Td
p={2}
color={"gray.600"}
style={{
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
className="web-text-small"
key={index}
border={"none"}
>
<InputGroup size="sm">
<Input
isRequired={true}
bg={"#F5F8F6"}
focusBorderColor="forestGreen.300"
// border="1px solid #000"
size={"sm"}
fontSize={"sm"}
rounded={"sm"}
type="number"
value={item.value}
textAlign={"right"}
placeholder={"00.00"}
onChange={(e) =>
handleInputChange(index, e.target.value)
}
border={"1px solid #e2e8f0"}
/>
<InputRightAddon
fontSize={"xs"}
fontWeight={600}
color={"forestGreen.500"}
>
{item?.curr}
</InputRightAddon>
</InputGroup>
</Td>
))}
</Tr>
</Tbody>
</Table>
);
} else if(type === 'date'){
return (
<Input
position={'relative'}
bg={"#F5F8F6"}
focusBorderColor="forestGreen.300"
size={"sm"}
fontSize={"sm"}
rounded={"sm"}
type={"date"}
{...field}
{...props}
placeholder={placeHolder ? placeHolder : label}
textAlign={arabic ? "right" : align ? align : "left"}
_placeholder={{ fontSize: "sm" }}
// min={type === "date" ? today : undefined}
// maxLength={maxLength}
// defaultValue={type === "date" && "2023-07-26" : undefined}
// defaultValue={value}
// value={dateValue}
/>
);
}else if(type === 'number'){
return (
<CurrencyInput
position={'relative'}
bg={"#F5F8F6"}
focusBorderColor="forestGreen.300"
size={"sm"}
fontSize={"sm"}
rounded={"sm"}
{...field}
{...props}
placeholder={placeHolder ? placeHolder : label}
textAlign={"right"}
_placeholder={{ fontSize: "sm" }}
maxLength={maxLength}
// defaultValue={type === "date" && "2023-07-26" : undefined}
// defaultValue={value}
// value={dateValue}
/>
);} else{
return (
<Input
bg={"#F5F8F6"}
focusBorderColor="forestGreen.300"
size={"sm"}
fontSize={"sm"}
rounded={"sm"}
type={type}
{...field}
{...props}
placeholder={placeHolder ? placeHolder : label}
textAlign={arabic || type === "number" ? "right" : align ? align : "left"}
_placeholder={{ fontSize: "sm" }}
// min={type === "date" ? today : undefined}
maxLength={maxLength}
// defaultValue={type === "date" && "2023-07-26" : undefined}
// value={"2023-07-26"}
/>
);
}
}}
/>
{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>
)}
{helperText && (
<FormHelperText color={'gray.500'} className="web-text-small">{helperText}</FormHelperText>
)}
{type === "file" && (
<FormHelperText className="web-text-small">
Maximum limit of image is 10MB.
</FormHelperText>
)}
</FormControl>
);
export default FormField;

View File

@@ -0,0 +1,256 @@
import React, { useContext, useState } from "react";
import { OPACITY_ON_LOAD } from "../../../Layout/animations";
import {
Box,
Divider,
FormControl,
FormLabel,
Heading,
Input,
Select,
Textarea,
Button,
Text,
} from "@chakra-ui/react";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { WarningTwoIcon } from "@chakra-ui/icons";
import { TiWarning } from "react-icons/ti";
import GlobalStateContext from "../../../Contexts/GlobalStateContext";
import { useNavigate } from "react-router-dom";
import FormField from "../../../Components/FormField";
import { v4 as uuidv4 } from "uuid";
import FormInputMain from "../../../Components/AddEditComponentMain";
export const addSponser = yup.object().shape({
sponserName: yup.string().required("Sponsorer name is required"),
sponserNameArabic: yup.string().required("Sponsorer name is required"),
mobileNo: yup.string().required("Mobile no is required"),
sponserAddress: yup.string().required("Sponsorer address is required"),
bankName: yup.string().required("Bank Name is required"),
accountNumber: yup.string().required("Account Number is required"),
swiftCode: yup.string().required("SWIFT/BIC Code is required"),
bankEmail: yup.string().email("Invalid email format"),
// routingNumber: yup.string().required("Routing Number is required"),
// iban: yup.string().required("IBAN is required"),
// accountType: yup.string().required("Account Type is required"),
// bankPhoneNumber: yup.string().required("Bank Phone Number is required"),
// bankBranch: yup.string().required("Bank Branch is required"),
// branchAddress: yup.string().required("Branch Address is required"),
// ifscCode: yup.string().required("IFSC Code is required"),
// accountHolderName: yup.string().required("Account Holder's Name is required"),
});
export function debounce(func, delay) {
let debounceTimer;
return function(...args) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(this, args), delay);
};
}
const AddSponser = () => {
const navigate = useNavigate();
const [bannerImageData, setBannerImageData] = useState(null);
const [selectedBannerImageData, setSelectedBannerImageData] = useState(null);
const [otherImageData, setOtherImageData] = useState(null);
const [selectedOtherImageData, setSelectedOtherImageData] = useState(null);
const { sponser, setSponser } = useContext(GlobalStateContext);
const {
control,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(addSponser),
});
const handleBannerImageChange = (e) => {
const file = e.target.files[0];
setBannerImageData(file);
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setSelectedBannerImageData(reader.result);
};
reader.readAsDataURL(file);
}
};
// Handler for file input
const handleOtherImageChange = (e) => {
const files = Array.from(e.target.files);
const newImageData = [...(otherImageData || []), ...files]; // Ensure otherImageData is an array
setOtherImageData(newImageData);
const readers = files.map((file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
});
Promise.all(readers)
.then((results) => {
setSelectedOtherImageData([
...(selectedOtherImageData || []),
...results,
]); // Ensure selectedOtherImageData is an array
})
.catch((error) => {
console.error("Error reading files:", error);
});
};
// Function to remove a specific image
const removeOtherImage = (index) => {
const newImageData = otherImageData.filter((_, i) => i !== index);
const newSelectedImageData = selectedOtherImageData.filter(
(_, i) => i !== index
);
setOtherImageData(newImageData);
setSelectedOtherImageData(newSelectedImageData);
};
const formFields = [
{
label: "Sponsrorer name",
name: "sponserName",
type: "text",
isRequired: true,
section: "Personal Details",
},
{
label: "Sponsrorer Name (Arabic)",
name: "sponserNameArabic",
type: "text",
isRequired: true,
arabic: true,
section: "Personal Details",
},
{
label: "Mobile no",
name: "mobileNo",
type: "number",
isRequired: true,
section: "Personal Details",
},
{
label: "Sponsrorer address",
name: "sponserAddress",
type: "text",
isRequired: true,
section: "Personal Details",
},
{
label: "Bank name",
name: "bankName",
type: "text",
isRequired: true,
section: "Bank Details",
},
{
label: "Account Name",
name: "accountNumber",
type: "text",
isRequired: true,
section: "Bank Details",
},
{
label: "SWIFT/BIC Code",
name: "swiftCode",
type: "number",
isRequired: true,
section: "Bank Details",
},
{
label: "Account Email",
name: "bankEmail",
type: "text",
isRequired: true,
section: "Bank Details",
},
{
label: "Banner image",
name: "banner_image",
id:"banner_image",
type: "fileNormal",
isRequired: true,
section: "Bank Details",
multiple:false,
selectedImageData:selectedBannerImageData,
setSelectedImageData:setSelectedBannerImageData,
imageData:bannerImageData,
handleImageChange:handleBannerImageChange
},
{
label: "Multi Image",
name: "OtherImage",
id:"OtherImage",
type: "fileNormal",
isRequired: true,
section: "Bank Details",
multiple:true,
selectedImageData:selectedOtherImageData,
setSelectedImageData:setSelectedOtherImageData,
imageData:otherImageData,
handleImageChange:handleOtherImageChange,
removeImage:removeOtherImage
},
];
const groupedFields = formFields.reduce((groups, field) => {
const { section } = field;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(field);
return groups;
}, {});
const onSubmit = (data) => {
setSponser([
{
...data,
status: true,
id: uuidv4(),
createdAt: new Date().toISOString(),
},
...sponser,
]);
navigate("/sponser");
};
return (
<Box {...OPACITY_ON_LOAD} overflowY={"scroll"} height={"100vh"} pb={14}>
<FormInputMain
groupedFields={groupedFields}
control={control}
errors={errors}
onSubmit={handleSubmit(onSubmit)}
/></Box>
);
};
export default AddSponser;

View File

@@ -0,0 +1,138 @@
import { Box, Button, Divider, FormHelperText, Heading, Spinner, Text } from "@chakra-ui/react";
import React from "react";
import FormField from "./FormField";
import { OPACITY_ON_LOAD } from "../Layout/animations";
import { ArrowBackIcon } from "@chakra-ui/icons";
const FormInputMain = ({
register,
groupedFields,
control,
errors,
onSubmit,
children,
onCancle,
submitTitle,
p,
w,
btnLoading,
btnhidden,
}) => {
return (
<Box mt={0} as="form" onSubmit={onSubmit}>
{Object.entries(groupedFields).map(([section, fields], index) => (
<Box key={section} mt={2}>
<Heading as="h6" size="xs" mx={5} my={0} fontWeight={"500"}>
{/* <ArrowBackIcon fontSize={'lg'} /> */}
{section}
</Heading>
<Box
as="span"
width={"100%"}
p={p ? p : 5}
display={"flex"}
flexWrap={"wrap"}
gap={4}
>
{fields.map(
(
{
label,
name,
id,
arabic,
type,
isRequired,
selectedImageData,
setSelectedImageData,
imageData,
handleImageChange,
removeImage,
placeHolder,
options,
helperText,
multiple,
width,
value,
handleInputChange,
align,
maxLength,
dateValue
},
key
) => (
<FormField
id={id}
key={key}
label={label}
type={type}
name={name}
helperText={helperText ? helperText : undefined}
options={options ? options : undefined}
placeHolder={placeHolder ? placeHolder : undefined}
control={control}
errors={errors}
multiple={multiple}
isRequired={isRequired}
arabic={arabic}
selectedImageData={selectedImageData}
setSelectedImageData={setSelectedImageData}
imageData={imageData}
handleImageChange={handleImageChange}
removeImage={removeImage}
width={width}
value={value}
handleInputChange={handleInputChange}
align={align}
maxLength={maxLength}
dateValue={dateValue}
/>
)
)}
</Box>
{index < Object.entries(groupedFields).length - 1 && <Divider />}
</Box>
))}
{children}
<Box display={"flex"} justifyContent={"end"} mt={2}>
<Box display={"flex"} justifyContent={"end"} p={2} w={"49%"} gap={4}>
{onCancle && (
<Button
size={"sm"}
width={w ? w : "44.5%"}
rounded={"sm"}
type="submit"
colorScheme="gray"
onClick={onCancle}
>
Cancel
</Button>
)}
{btnhidden ? (
""
) : (
<Button
isLoading={btnLoading}
size={"sm"}
width={w ? w : "44.5%"}
rounded={"sm"}
spinner={<Spinner size="sm" color="white" />}
type="submit"
colorScheme={"forestGreen"}
>
{submitTitle ? submitTitle : "Submit"}
</Button>
)}
</Box>
</Box>
</Box>
);
};
export default FormInputMain;

View File

@@ -0,0 +1,157 @@
import {
Box,
Button,
Divider,
FormHelperText,
FormLabel,
Heading,
Image,
Input,
InputGroup,
InputRightAddon,
Table,
Tbody,
Td,
Th,
Thead,
Tr,
} from "@chakra-ui/react";
import React from "react";
const FormInputView = ({
groupedFields,
name,
groupedFieldsTwo,
errors,
onSubmit,
children,
}) => {
return (
<form>
{Object?.entries(groupedFields, groupedFieldsTwo).map(
([section, fields], index) => (
<Box key={index}>
<Heading as="h6" size="xs" mt={index === 0 ? 3 : 4}>
{section}
</Heading>
{/* <Box display={"flex"} gap={0}> */}
<Box key={index} width={"100%"} display={"flex"} flexWrap={"wrap"} gap={4}>
{fields.map(
({ value, label, id, width, btn, arabic, type, align }, key) =>
type === "table" ? (
<Table key={id} w={"100%"} variant="simple">
<Thead>
<Tr>
{value?.map((item, index) => (
<Th
border={"none"}
p={2}
textTransform={"none"}
key={index}
>
<Box
as="span"
display={"flex"}
alignItems={"center"}
gap={2}
>
<Image
objectFit={"cover"}
opacity={0.9}
rounded={"full"}
w={6}
h={6}
src={
" https://tanami.betadelivery.com/" +
item?.logo
}
/>
{item.country === "United Arab Emirates"
? "UAE"
: item.country}
</Box>
</Th>
))}
</Tr>
</Thead>
<Tbody>
<Tr>
{value?.map((item, index) => (
<Td
p={2}
color={"gray.600"}
style={{
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
className="web-text-small"
key={index}
border={"none"}
>
<InputGroup size="sm">
<Input
readOnly={true}
isRequired={true}
bg={"#F5F8F6"}
focusBorderColor="forestGreen.300"
// border="1px solid #000"
size={"sm"}
fontSize={"sm"}
rounded={"sm"}
type="number"
value={item.value}
textAlign={"right"}
placeholder={"00.00"}
// color={"#000"}
color={"#1A202C"}
fontWeight={500}
border={"1px solid #e2e8f0"}
/>
<InputRightAddon
fontSize={"xs"}
fontWeight={600}
color={"forestGreen.500"}
>
{item?.curr}
</InputRightAddon>
</InputGroup>
</Td>
))}
</Tr>
</Tbody>
</Table>
) : (
<Box key={id} w={!width ? "49%" : width}>
<FormLabel key={id} color={"gray.500"} fontSize={"xs"}>
{label}
</FormLabel>
<FormLabel
border={"1px solid transparent"}
bg={"#ccc3"}
p={2}
pt={1.5}
pb={1.5}
rounded={"xs"}
textAlign={arabic ? "right" : align ? align : "left"}
fontSize={"sm"}
>
{type === "number" ? value : value}
</FormLabel>
</Box>
)
)}
</Box>
{/* </Box> */}
{index <
Object.entries(groupedFields, groupedFieldsTwo).length - 1 && (
<Divider />
)}
</Box>
)
)}
{children}
</form>
);
};
export default FormInputView;

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

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

View File

@@ -0,0 +1,35 @@
import { useState, useEffect } from "react";
const TimeCalculator = ({ JoiningDate }) => {
const [elapsedTime, setElapsedTime] = useState(0);
useEffect(() => {
const calculateTimeElapsed = () => {
const startDate = new Date(JoiningDate);
const currentDate = new Date();
const difference = currentDate - startDate;
setElapsedTime(Math.floor(difference / 1000)); // Convert milliseconds to seconds
};
// Calculate time elapsed initially
calculateTimeElapsed();
// Update time elapsed every second
const intervalId = setInterval(calculateTimeElapsed, 1000);
// Clear interval on component unmount
return () => clearInterval(intervalId);
}, []);
// Convert seconds to years, months, days, hours, minutes, and remaining seconds
const years = Math.floor(elapsedTime / (365 * 24 * 60 * 60));
const months = Math.floor((elapsedTime % (365 * 24 * 60 * 60)) / (30 * 24 * 60 * 60)); // Approximating a month to 30 days
const days = Math.floor((elapsedTime % (30 * 24 * 60 * 60)) / (24 * 60 * 60));
const hours = Math.floor((elapsedTime % (24 * 60 * 60)) / (60 * 60));
const minutes = Math.floor((elapsedTime % (60 * 60)) / 60);
const seconds = elapsedTime % 60;
return `${years}Y ${months}M ${hours}H ${minutes}M ${seconds}S`;
};
export default TimeCalculator;

View File

@@ -0,0 +1,11 @@
import { useToast } from "@chakra-ui/react";
export const toaster = () => {
const toast = useToast();
return toast({
title: "Loged In",
status: "success",
isClosable: true,
});
}

View File

@@ -0,0 +1,16 @@
export const formatDate = (dateString) => {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// Create a new Date object from the provided string
const date = new Date(dateString);
// Extract day, month, and year components
const day = date.getDate();
const month = months[date.getMonth()];
const year = date.getFullYear();
// Format the date in "DD-Mon-YYYY" format
const formattedDate = `${day}-${month}-${year}`;
return formattedDate;
}

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

@@ -0,0 +1,82 @@
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";
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;

View File

@@ -0,0 +1,167 @@
import React, { useContext, useState } from "react";
import {
Avatar,
Box,
Button,
HStack,
Image,
Input,
InputGroup,
InputLeftElement,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
PopoverTrigger,
Portal,
Text,
useDisclosure,
} from "@chakra-ui/react";
import { NavLink } from 'react-router-dom';
import { MdOutlineHeadsetMic, MdNotificationsNone } from "react-icons/md";
import { RiWallet3Line } from "react-icons/ri";
import { SearchIcon, ArrowLeftIcon, ArrowRightIcon } from "@chakra-ui/icons";
import { Link, useNavigate } from "react-router-dom";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import mainLogo from "../assets/optifii_logo.svg";
import PrimaryButton from "./Buttons/PrimaryButton";
import Notifications from "../Pages/Notifications/Notifications";
const HeaderMain = ({
logOutHandler,
slideDirecttion,
isDrawerOpen,
toggleDrawer,
}) => {
const navigate = useNavigate();
const { image } = useContext(GlobalStateContext);
const [searchTerm, setSearchTerm] = useState("");
// For controlling the modal
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<Box
w={"100%"}
h={{ base: "8%", xl: "6%" }}
position={"relative"}
className={`pt-2 pb-2 fw-400 border-bottom d-flex ${
slideDirecttion ? "ps-2" : ""
} justify-content-between align-items-center`}
zIndex={999}
>
<Box display={"flex"} alignItems={"center"}>
<Box
w={"230px"}
display={"flex"}
alignItems={"center"}
justifyContent={"space-between"}
>
<Box className={`d-flex ${true ? "justify-content-start" : "justify-content-center"} p-4 pt-0 pb-0 position-relative`}>
<Image
style={{ width: 95 }}
src={mainLogo}
alt="Logo"
onClick={() => navigate("/home")}
cursor={"pointer"}
/>
</Box>
<Button
colorScheme={"forestGreen"}
rounded={"lg"}
onClick={toggleDrawer}
style={{ width: "28px", height: "28px", minWidth: "28px", zIndex: 99, backgroundColor: "#6311CB29" }}
>
{isDrawerOpen ? <ArrowLeftIcon className="web-text-small" color={"#6311CB"} /> : <ArrowRightIcon className="web-text-small" color={"#6311CB"} />}
</Button>
</Box>
<InputGroup width={350} size="sm" ml={5}>
<InputLeftElement pointerEvents="none">
<SearchIcon color="gray.300" />
</InputLeftElement>
<Input
type="search"
placeholder="Type to search..."
rounded="md"
focusBorderColor="#3725EA"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</InputGroup>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Box display={"flex"} gap={2}>
<Box className="header-icon" display={"flex"} gap={2} alignItems={"center"} me={10}>
<NavLink
to="/contact-us"
style={({ isActive }) => ({
borderBottom: isActive ? '1px solid #3725EA' : 'none',
})}
>
<Button size={"sm"} bg={"none"} p={0}>
<MdOutlineHeadsetMic fontSize={"18px"} />
</Button>
</NavLink>
<NavLink
to="/your-wallet"
style={({ isActive }) => ({
borderBottom: isActive ? '1px solid #3725EA' : 'none',
})}
>
<Button size={"sm"} bg={"none"} p={0}>
<RiWallet3Line fontSize={"18px"} />
</Button>
</NavLink>
<Button size={"sm"} bg={"none"} p={0} onClick={onOpen}>
<MdNotificationsNone fontSize={"20px"} />
</Button>
</Box>
<Box me={4} gap={2} className="d-flex justify-content-center">
<Popover placement="bottom-start">
<Portal>
<PopoverContent mt={6} maxW="450px">
<PopoverArrow />
<PopoverBody
py={6}
gap={2}
display={"flex"}
justifyContent={"center"}
flexDirection={"column"}
alignItems={"center"}
>
<Avatar size="2xl" name="Segun Adebayo" src={image} />
<Text as={"span"} fontSize={"md"} fontWeight={600}>
Kartikey Gautam
</Text>
<PrimaryButton onClick={() => navigate("/profile")} title={"View Profile"} />
</PopoverBody>
</PopoverContent>
</Portal>
<PopoverTrigger>
<Box className="d-flex pointer align-items-center">
<Avatar src={image} size={"sm"} bg={"#210a33"} />
<Box style={{ display: "flex" }} className="overflow-hidden ms-3 flex-column">
<Text fontWeight={600} as={"span"} fontSize={"md"}>
Kartikey Gautam
</Text>
<Text as={"span"} fontSize={"xs"}>
Website Development India
</Text>
</Box>
</Box>
</PopoverTrigger>
</Popover>
</Box>
</Box>
</Box>
{/* Include the Notifications modal */}
<Notifications isOpen={isOpen} onClose={onClose} />
</Box>
);
};
export default HeaderMain;

View File

@@ -0,0 +1,145 @@
import React, { useContext, useState } from "react";
import {
Avatar,
Box,
Button,
HStack,
Image,
Input,
InputGroup,
InputLeftElement,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
PopoverTrigger,
Portal,
Text,
useDisclosure,
} from "@chakra-ui/react";
import { NavLink } from 'react-router-dom';
import { MdOutlineHeadsetMic, MdNotificationsNone } from "react-icons/md";
import { RiWallet3Line } from "react-icons/ri";
import { SearchIcon, ArrowLeftIcon, ArrowRightIcon } from "@chakra-ui/icons";
import { Link, useNavigate } from "react-router-dom";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import mainLogo from "../assets/optifii_white.svg";
import PrimaryButton from "./Buttons/PrimaryButton";
// import Notifications from "../Pages/Notifications/Notifications";
const HeaderMain = ({
logOutHandler,
slideDirecttion,
isDrawerOpen,
toggleDrawer,
}) => {
const navigate = useNavigate();
const { image } = useContext(GlobalStateContext);
const [searchTerm, setSearchTerm] = useState("");
// For controlling the modal
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<Box
w={"100%"}
h={{ base: "8%", xl: "6%" }}
position={"relative"}
className={`pt-2 pb-2 fw-400 border-bottom d-flex ${
slideDirecttion ? "ps-2" : ""
} justify-content-between align-items-center`}
zIndex={999}
bgGradient="linear(to-r, #1A0436, #6311CB)" // Linear gradient
color={"#fff"}
>
<Box display={"flex"} alignItems={"center"}>
<Box
w={"230px"}
display={"flex"}
alignItems={"center"}
justifyContent={"space-between"}
>
<Box className={`d-flex ${true ? "justify-content-start" : "justify-content-center"} p-4 pt-0 pb-0 position-relative`}>
<Image
style={{ width: 100 }}
src={mainLogo}
alt="Logo"
onClick={() => navigate("/home")}
cursor={"pointer"}
/>
</Box>
<Button
colorScheme={"white"}
rounded={"lg"}
onClick={toggleDrawer}
style={{ width: "28px", height: "28px", minWidth: "28px", zIndex: 99, backgroundColor: "#ffffff29" }}
>
{isDrawerOpen ? <ArrowLeftIcon className="web-text-small" color={"#fff"} /> : <ArrowRightIcon className="web-text-small" color={"#fff"} />}
</Button>
</Box>
<InputGroup width={350} size="sm" ml={5}>
<InputLeftElement pointerEvents="none">
<SearchIcon color="gray.100" />
</InputLeftElement>
<Input
type="search"
placeholder="Type to search..."
rounded="md"
bg={'purple.800'}
border={'none'}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</InputGroup>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Box display={"flex"} gap={2}>
<Box me={4} gap={2} className="d-flex justify-content-center">
<Popover placement="bottom-start">
<Portal>
<PopoverContent mt={6} maxW="450px">
<PopoverArrow />
<PopoverBody
py={6}
gap={2}
display={"flex"}
justifyContent={"center"}
flexDirection={"column"}
alignItems={"center"}
>
<Avatar size="2xl" name="Segun Adebayo" src={image} />
<Text as={"span"} fontSize={"md"} fontWeight={600}>
Kartikey Gautam
</Text>
<PrimaryButton onClick={() => navigate("/profile")} title={"View Profile"} />
</PopoverBody>
</PopoverContent>
</Portal>
<PopoverTrigger>
<Box className="d-flex pointer align-items-center">
<Avatar src={image} size={"sm"} />
<Box style={{ display: "flex" }} className="overflow-hidden ms-3 flex-column">
<Text fontWeight={600} as={"span"} fontSize={"md"}>
Kartikey Gautam
</Text>
<Text as={"span"} fontSize={"xs"}>
Website Development India
</Text>
</Box>
</Box>
</PopoverTrigger>
</Popover>
</Box>
</Box>
</Box>
{/* Include the Notifications modal */}
{/* <Notifications isOpen={isOpen} onClose={onClose} /> */}
</Box>
);
};
export default HeaderMain;

View File

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

View File

@@ -0,0 +1,38 @@
import {
Button,
Image,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useDisclosure,
} from "@chakra-ui/react";
import React from "react";
const ImageViewer = ({ src, isOpen, onClose }) => {
return (
<>
{/* <Button onClick={onOpen}>Open Modal</Button> */}
<Modal size={"xl"} isCentered isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
{/* <ModalCloseButton /> */}
<ModalBody p={4}>
<Image
rounded={6}
w={"100%"}
h={"100%"}
src={import.meta.env.VITE_IMAGE_URL + src}
/>
</ModalBody>
</ModalContent>
</Modal>
</>
);
};
export default ImageViewer;

View File

@@ -0,0 +1,44 @@
import React, { useState } from "react";
const input01Style = {
width: "100%",
border: "1px solid #ADB3BD",
outline: "none",
height: 50,
fontSize: "16px",
};
const focusedInputStyle = {
border: "1px solid #38187C", // Change border color for focused state
};
const Input01 = ({ type, placeholder, onChange, name, register }) => {
const [isFocused, setIsFocused] = useState(false);
const handleFocus = () => {
setIsFocused(true);
};
const handleBlur = () => {
setIsFocused(false);
};
return (
<input
style={
isFocused ? { ...input01Style, ...focusedInputStyle } : input01Style
}
type={type}
name={name}
className="rounded-3 ps-3 d-flex align-items-center"
onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
{...register}
/>
);
};
export default Input01;

View File

@@ -0,0 +1,182 @@
import {
Badge,
Box,
Button,
Card,
CardBody,
Heading,
Image,
Progress,
Stack,
Text,
Tooltip,
} from "@chakra-ui/react";
import { OPACITY_ON_LOAD } from "../../Layout/animations";
const InvestmentCard = ({ investment }) => {
return (
<Card
{...OPACITY_ON_LOAD}
direction={{ base: "column", sm: "row" }}
variant="outline"
display={"flex"}
alignItems={"center"}
mb={"20px"}
boxShadow={"md"}
border={"none"}
mt={2}
>
<Image
p={"14px"}
rounded={"24px"}
objectFit="cover"
w={"180px"}
h={"160px"}
src={investment?.banner_image}
alt={investment?.ioName}
/>
<Stack w={"22%"}>
<CardBody p={"18px 12px 18px 0px"}>
<Heading size="sm" fontWeight={"500"}>
{investment?.ioName}
<Badge
colorScheme={
investment?.status === "Available"
? "teal"
: investment?.status === "Upcomming"
? "green"
: "red"
}
ps={2}
pe={2}
pt={0.5}
pb={0.5}
fontSize={"xs"}
ms={3}
fontWeight={'600'}
>
{investment?.status === "Available"
? "Available"
: investment?.status === "Upcomming"
? "Upcomming"
: "Closed"}
</Badge>
</Heading>
<Text
fontSize="sm"
mb={"4px"}
display={"flex"}
justifyContent={"space-between"}
>
Sponsor:
<Text as={"span"} fontWeight={"500"}>
{investment?.sponserName}
</Text>
</Text>
<Text
fontSize="sm"
mb={"4px"}
display={"flex"}
justifyContent={"space-between"}
>
Ann return:
<Text as={"span"} fontWeight={"500"}>
{investment?.annualReturn}
</Text>
</Text>
<Text
fontSize="sm"
mb={"4px"}
display={"flex"}
justifyContent={"space-between"}
>
Ann Yield:
<Text as={"span"} fontWeight={"500"}>
{investment?.annualyield}
</Text>
</Text>
</CardBody>
</Stack>
<Stack w={"22%"} borderLeft={"1px solid #ccc"}>
<CardBody p={"38px 12px 7px 12px"}>
<Text fontSize="sm" mb={"4px"} display={'flex'} justifyContent={'space-between'}>
Tenure:
<Text as={"span"} fontWeight={"500"}>
{investment?.tenure}
</Text>
</Text>
<Text fontSize="sm" mb={"4px"} display={'flex'} justifyContent={'space-between'}>
Quaterly:
<Text as={"span"} fontWeight={"500"}>
{investment?.quaterly}
</Text>
</Text>
<Text fontSize="sm" mb={"4px"} display={'flex'} justifyContent={'space-between'}>
Destributed Amount:
<Text as={"span"} fontWeight={"500"}>
{investment?.destributedAmount}
</Text>
</Text>
</CardBody>
</Stack>
<Stack w={"22%"} borderLeft={"1px solid #ccc"}>
<CardBody p={"38px 12px 7px 12px"}>
<Text fontSize="sm" mb={"4px"} display={'flex'} justifyContent={'space-between'}>
Min.Invests:
<Text as={"span"} fontWeight={"500"}>
{investment?.miniInvest}
</Text>
</Text>
<Text fontSize="sm" mb={"4px"} display={'flex'} justifyContent={'space-between'}>
Targ Close:
<Text as={"span"} fontWeight={"500"}>
{new Date(investment?.targetClose).toLocaleDateString()}
</Text>
</Text>
<Text fontSize="sm" mb={"4px"} display={'flex'} justifyContent={'space-between'}>
Year:
<Text as={"span"} fontWeight={"500"}>
{investment?.year}
</Text>
</Text>
</CardBody>
</Stack>
<Stack w={"20%"} borderLeft={"1px solid #ccc"}>
<CardBody padding={'25px 12px 7px 12px'}>
<Box
as="span"
display={"flex"}
justifyContent={"space-between"}
mb={1}
>
<Text fontSize={"xs"} fontWeight={500} as={"span"}>
$ 500,000.450
</Text>
<Text fontSize={"xs"} fontWeight={500} as={"span"}>
75 % Funded
</Text>
</Box>
<Progress
width={"100%"}
value={75} // Assuming a static progress value
rounded={"10px"}
colorScheme={"green"}
size={"sm"}
/>
<Button
w={"100%"}
colorScheme={"gray"}
rounded={"sm"}
size={"sm"}
mt={"20px"}
>
View
</Button>
</CardBody>
</Stack>
</Card>
);
};
export default InvestmentCard;

View File

@@ -0,0 +1,105 @@
/* From Uiverse.io by abrahamcalsin */
.dot-spinner {
--uib-size: 2.8rem;
--uib-speed: .9s;
--uib-color: #004717;
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
height: var(--uib-size);
width: var(--uib-size);
}
.dot-spinner__dot {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: flex-start;
height: 100%;
width: 100%;
}
.dot-spinner__dot::before {
content: '';
height: 20%;
width: 20%;
border-radius: 50%;
background-color: var(--uib-color);
transform: scale(0);
opacity: 0.5;
animation: pulse0112 calc(var(--uib-speed) * 1.111) ease-in-out infinite;
box-shadow: 0 0 20px rgba(18, 31, 53, 0.3);
}
.dot-spinner__dot:nth-child(2) {
transform: rotate(45deg);
}
.dot-spinner__dot:nth-child(2)::before {
animation-delay: calc(var(--uib-speed) * -0.875);
}
.dot-spinner__dot:nth-child(3) {
transform: rotate(90deg);
}
.dot-spinner__dot:nth-child(3)::before {
animation-delay: calc(var(--uib-speed) * -0.75);
}
.dot-spinner__dot:nth-child(4) {
transform: rotate(135deg);
}
.dot-spinner__dot:nth-child(4)::before {
animation-delay: calc(var(--uib-speed) * -0.625);
}
.dot-spinner__dot:nth-child(5) {
transform: rotate(180deg);
}
.dot-spinner__dot:nth-child(5)::before {
animation-delay: calc(var(--uib-speed) * -0.5);
}
.dot-spinner__dot:nth-child(6) {
transform: rotate(225deg);
}
.dot-spinner__dot:nth-child(6)::before {
animation-delay: calc(var(--uib-speed) * -0.375);
}
.dot-spinner__dot:nth-child(7) {
transform: rotate(270deg);
}
.dot-spinner__dot:nth-child(7)::before {
animation-delay: calc(var(--uib-speed) * -0.25);
}
.dot-spinner__dot:nth-child(8) {
transform: rotate(315deg);
}
.dot-spinner__dot:nth-child(8)::before {
animation-delay: calc(var(--uib-speed) * -0.125);
}
@keyframes pulse0112 {
0%,
100% {
transform: scale(0);
opacity: 0.5;
}
50% {
transform: scale(1);
opacity: 1;
}
}

View File

@@ -0,0 +1,30 @@
import { Box, Spinner, Text } from "@chakra-ui/react";
import React from "react";
import './FullscreenLoaders.css'
const FullscreenLoaders = ({height}) => {
return (
<Box
display={"flex"}
justifyContent={"center"}
flexDirection={'column'}
alignItems={"center"}
w={"100%"}
h={height ? height: "100vh"}
gap={4}
><div className="dot-spinner">
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
</div>
{/* <Text color='#004717' fontSize={'md'} fontWeight={500}>Loading...</Text> */}
</Box>
);
};
export default FullscreenLoaders;

View File

@@ -0,0 +1,20 @@
import React from "react";
import './FullscreenLoaders.css'
const Loader01 = () => {
return (
<div className="dot-spinner">
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
<div className="dot-spinner__dot"></div>
</div>
);
};
export default Loader01;

View File

@@ -0,0 +1,478 @@
import {
Box,
Button,
Heading,
HStack,
Image,
Modal,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Progress,
Spinner,
Stack,
Text,
} from "@chakra-ui/react";
import React, { useEffect, useState } from "react";
import Mobile from "../assets/mobileWing.png";
import mobileBanner from "../assets/welcome.avif";
import { GrDownload } from "react-icons/gr";
import { LuClock } from "react-icons/lu";
import { GiNetworkBars } from "react-icons/gi";
import { GrLinkedinOption } from "react-icons/gr";
import { FiInstagram } from "react-icons/fi";
import { IoBatteryHalf } from "react-icons/io5";
import { BiWifi } from "react-icons/bi";
import { useGetIOByIdQuery } from "../Services/io.service";
import { useNavigate, useParams } from "react-router-dom";
import FullscreenLoaders from "./Loaders/FullscreenLoaders";
import { calculatePercentage, formatDate } from "../Constants/Constants";
import { BsFileText } from "react-icons/bs";
const MobileView = ({ isOpen, onClose, finalRef, actionId }) => {
const [time, setTime] = useState(new Date());
const navigate = useNavigate();
const params = useParams();
const id = actionId;
const {
data: IObyID,
isLoading: IObyIDisLoading,
error: IObyIDerror,
} = useGetIOByIdQuery(id, { skip: !id });
console.log(IObyID);
const keyMerits = IObyID?.data?.keyMerits || [];
const artifactsImage = IObyID?.data?.artifactsImage || [];
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
const formatTime = (date) => {
return date.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
hour12: true,
});
};
console.log(
calculatePercentage(
IObyID?.data?.totalAmtInvestmentInUSD,
IObyID?.data?.goalAmount
)
);
return (
<Modal
display={"flex"}
size={"xl"}
justifyContent={"center"}
isCentered
finalFocusRef={finalRef}
isOpen={isOpen}
onClose={onClose}
>
<ModalOverlay
backdropFilter="blur(5px)" // Add this line for backdrop blur
bg="rgba(0, 0, 0, 0.4)" // Optional: Adjust the overlay color and opacity
/>
<ModalContent backgroundColor={"transparent"} shadow={"none"}>
<HStack w={"100"} display={"flex"} justify={"center"}>
<Box
as="span"
boxShadow={"none"}
position={"relative"}
display={"flex"}
justifyContent={"center"}
h={"600px"}
w={"320px"}
sx={{
"@media (max-width: 2024px)": {
height: "695px",
width: "360px",
},
"@media (max-width: 1440px)": {
height: "600px",
width: "320px",
},
}}
>
<Image
h={"100%"}
w={"100%"}
src={Mobile}
position={"absolute"}
top={"0"}
left={"0"}
/>
<Box
backgroundColor={"#fff"}
h={"98%"}
w={"96%"}
// m={2}
borderRadius={"47px"}
pt={"36px"}
px={"15px"}
>
{IObyIDisLoading ? (
<Box
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
h={"100%"}
>
<Spinner thickness="3px" color="purple.900" size="lg" />
</Box>
) : (
<>
<Box>
<Box
display={"flex"}
alignItems={"center"}
position={"absolute"}
left={"30px"}
top={"18px"}
>
<Text ml={1} mb={0}>
<GiNetworkBars fontSize={"10px"} />
</Text>
<Text ml={1} mb={0} fontSize={"10px"}>
{formatTime(time)}
</Text>
<Text ml={"5px"} mb={0}>
<GrLinkedinOption fontSize={"10px"} />
</Text>
{/* <Text ml={1} mb={0}><FiInstagram fontSize={"10px"} /></Text> */}
</Box>
<Box
display={"flex"}
alignItems={"center"}
position={"absolute"}
right={"36px"}
top={"17px"}
>
<Text mb={0}>
<BiWifi fontSize={"14px"} />
</Text>
<Text ml={1} mb={0}>
<IoBatteryHalf fontSize={"15px"} />
</Text>
</Box>
</Box>
<Box
p={"10px"}
overflowY={"scroll"}
h={"483px"}
zIndex={"99"}
position={"relative"}
borderBottomLeftRadius={"23px"}
borderBottomRightRadius={"23px"}
sx={{
"@media (max-width: 2024px)": {
height: "575px",
},
"@media (max-width: 1440px)": {
height: "483px",
},
}}
>
<Box
mb={4}
bg={"#f5f8f6"}
borderRadius={"20px"}
boxShadow={"rgba(0, 0, 0, 0.15) 0px 2px 8px"}
>
<Box position={"relative"}>
<Text
position={"absolute"}
top={"12px"}
left={"10px"}
backgroundColor={"#e4f6ea"}
fontSize={"10px"}
fontWeight={500}
color="green"
p={"7px 12px"}
borderRadius={"20px"}
>
Stock
</Text>
<Text
position={"absolute"}
top={"12px"}
right={"10px"}
fontSize={"10px"}
display={"flex"}
alignItems={"center"}
fontWeight={500}
backgroundColor={"#fff"}
p={"7px 12px"}
borderRadius={"20px"}
>
<LuClock color="#d8804e" />{" "}
<Text mb={0} ml={1}>
Closing Date {formatDate(IObyID?.data?.closingDate)}
</Text>
</Text>
{artifactsImage?.[0]?.artifactPathName && (
<Image
borderTopLeftRadius={"20px"}
borderTopRightRadius={"20px"}
h={"130px"}
w={"100%"}
src={
"https://tanami.betadelivery.com/" +
artifactsImage[0]?.artifactPathName
}
/>
)}
</Box>
<Stack mt="3" bg={"#fff"} py={4} px={4}>
<Text
fontSize={"sm"}
fontWeight={"500"}
color={"#000"}
mb={0}
>
{IObyID?.data?.investmentType?.investmentTypeName}
</Text>
<Heading fontSize="16px" color={"#004717"}>
BHD {IObyID?.data?.goalAmount}
</Heading>
<Progress
colorScheme="green"
size="sm"
value={calculatePercentage(
IObyID?.data?.totalAmtInvestmentInUSD,
IObyID?.data?.goalAmount
)}
borderRadius={"3px"}
/>
<Text
color={"#4e4e4e"}
fontSize={"xs"}
fontWeight={600}
mb={0}
>
{parseFloat(
calculatePercentage(
IObyID?.data?.totalAmtInvestmentInUSD,
IObyID?.data?.goalAmount
) || 0
).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
% funded
</Text>
<Text
fontSize={"xs"}
fontWeight={500}
mb={0}
color={"#9d9d9d"}
>
{IObyID?.data?.descriptionEnglish}
</Text>
</Stack>
<Box py={4} px={4}>
<Box display={"flex"} justifyContent={"space-between"}>
<Text
fontSize={"xs"}
mb={1}
fontWeight={500}
color={"#616161"}
>
Sponsor name:
</Text>
<Text fontSize={"xs"} mb={1} fontWeight={500}>
{IObyID?.data?.sponsor?.sponsorName}
</Text>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Text
fontSize={"xs"}
mb={1}
fontWeight={500}
color={"#616161"}
>
Estimated return:
</Text>
<Text fontSize={"xs"} mb={1} fontWeight={500}>
{IObyID?.data?.expectedReturn}
</Text>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Text
fontSize={"xs"}
mb={1}
fontWeight={500}
color={"#616161"}
>
Hoiding period:
</Text>
<Text fontSize={"xs"} mb={1} fontWeight={500}>
{IObyID?.data?.holdingPeriod}
</Text>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Text
fontSize={"xs"}
mb={1}
fontWeight={500}
color={"#616161"}
>
Minimum investment:
</Text>
<Text fontSize={"xs"} mb={1} fontWeight={500}>
{
IObyID?.data?.minInvestmentAmt?.[0]?.country
?.minInvestmentAmt
}
</Text>
</Box>
</Box>
</Box>
<Box
mb={4}
p={5}
bg={"#f5f8f6"}
borderRadius={"20px"}
boxShadow={"rgba(0, 0, 0, 0.15) 0px 2px 8px"}
>
<Heading fontSize="14px" fontWeight={600}>
Key Merits
</Heading>
<Box>
{keyMerits &&
keyMerits.map((item, index) => (
<Box display={"flex"} alignItems={"center"}>
<Image
rounded={"md"}
display={"flex"}
p={1}
justifyContent={"center"}
alignItems={"center"}
src={
"https://tanami.betadelivery.com/" +
item?.icon.iconFilePath
}
w={8}
h={8}
/>
<Text fontSize={"xs"} mb={0}>
{item?.meritsDescription}
</Text>
</Box>
))}
</Box>
</Box>
<Box
mb={4}
p={5}
borderRadius={"20px"}
boxShadow={"rgba(0, 0, 0, 0.15) 0px 2px 8px"}
>
<Heading fontSize="14px" fontWeight={600}>
Investment Documents
</Heading>
<Box
bg={"#f5f8f6"}
w={"150px"}
p={3}
borderRadius={"10px"}
>
<Box display={"flex"} alignItems={"center"} mb={2}>
{/* <Image
me={1}
src="https://tanami.betadelivery.com/public/icons/icon8.svg"
/> */}
<BsFileText color="forestGreen" fontSize="18px" />
<Text fontSize={"xs"} mb={0} ml={2}>
{IObyID?.data?.documents?.[0]?.documentName}
</Text>
</Box>
<Box
display={"flex"}
alignItems={"center"}
justifyContent={"space-between"}
>
<Text mb={0} fontSize={"sm"}>
{IObyID?.data?.documents?.[0]?.documentSize}
</Text>
<GrDownload fontSize={"15px"} />
</Box>
</Box>
</Box>
<Box
mb={4}
p={4}
borderRadius={"20px"}
boxShadow={"rgba(0, 0, 0, 0.15) 0px 2px 8px"}
>
<Heading fontSize="14px" fontWeight={600}>
Videos
</Heading>
<video
autoPlay
loop
controls
style={{
borderRadius: "18px",
width: "100%",
height: "auto",
}}
>
<source
src={
IObyID?.data?.artifactsVideo?.[0]
?.artifactStreamingURL
}
type="video/mp4"
style={{ height: "200px" }}
/>
Your browser does not support the video tag.
</video>
</Box>
</Box>
<Box
position={"relative"}
p={4}
background={"#fff"}
padding={"24px"}
paddingBottom={"3px"}
borderBottomLeftRadius={"30px"}
borderBottomRightRadius="30px"
>
<Button
margin={"auto"}
width={"85%"}
bottom="10px"
left="0"
colorScheme="forestGreen"
mr={3}
w={"100%"}
fontWeight={500}
borderRadius={"20px"}
>
Invest
</Button>
</Box>
</>
)}
</Box>
</Box>
</HStack>
</ModalContent>
</Modal>
);
};
export default MobileView;

View File

@@ -0,0 +1,40 @@
import React from "react";
import { Link } from "react-router-dom"; // Adjust this based on your routing setup
import CustomBreadcrumb from "./CutomBreadcrumb";
import { MinusIcon } from "@chakra-ui/icons";
const NavBreadcrumbs = ({ nav }) => {
// Function to recursively flatten submenu items and add parent titles
const flattenNav = (items, parentTitle = "") => {
let breadcrumbs = [];
items.forEach((item) => {
if (item.submenu) {
// Add parent title if present
breadcrumbs.push({
label: parentTitle ? `${parentTitle} ${<MinusIcon/>} ${item.title}` : item.title,
link: null, // Adjust link as per your routing setup
});
// Recursively flatten submenu items
breadcrumbs = [
...breadcrumbs,
...flattenNav(item.submenu, `${parentTitle} ${<MinusIcon/>} ${item.title}`),
];
} else {
// If no submenu, add current item as breadcrumb
breadcrumbs.push({
label: parentTitle ? `${parentTitle} ${<MinusIcon/>} ${item.title}` : item.title,
link: item.path, // Adjust link as per your routing setup
});
}
});
return breadcrumbs;
};
// Flatten nav array into breadcrumbs
const flattenedNav = flattenNav(nav);
return <CustomBreadcrumb items={flattenedNav} />;
};
export default NavBreadcrumbs;

View File

@@ -0,0 +1,79 @@
import React, { useState } from 'react';
import { Select, HStack, Text, Box, IconButton } from '@chakra-ui/react';
import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
const Pagination = ({ pageSize, setPageSize, totalItems,isLoading, setCurrentPage, currentPage }) => {
// const [] = useState(itemsPerPageOptions[0]);
const totalPages = Math.ceil(totalItems / pageSize);
const handlePageSizeChange = (e) => {
setPageSize(Number(e.target.value));
setCurrentPage(1); // Reset to first page whenever page size changes
};
const paginationPrev = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
};
const paginationNext = () => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
};
const displayRange = {
start: (currentPage - 1) * pageSize + 1,
end: Math.min(currentPage * pageSize, totalItems),
};
return (
<HStack d="flex" justifyContent="flex-end" alignItems="center">
{/* <Text className='web-text-small'>Tanami v0.1</Text> */}
<HStack>
<Select
className="pointer web-text-small"
width={"90px"}
rounded="sm"
size="sm"
value={pageSize}
onChange={handlePageSizeChange}
>
{[ 15, 20, 30]?.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</Select>
<IconButton
mt={1}
size={'sm'}
rounded="sm"
icon={<ChevronLeftIcon />}
onClick={paginationPrev}
className="link pointer"
isDisabled={currentPage === 1}
/>
<Text w={"81px"} display={'flex'} justifyContent={'center'} className="web-text-medium" as={"span"}>
{isLoading ? "0": displayRange?.start} - {isLoading ? "00" :displayRange?.end} of {isLoading ? "00":totalItems}
</Text>
<IconButton
mt={1}
icon={<ChevronRightIcon />}
size={'sm'}
rounded="sm"
onClick={paginationNext}
className="link pointer"
isDisabled={currentPage === totalPages}
/>
</HStack>
</HStack>
);
};
export default Pagination;

View File

@@ -0,0 +1,78 @@
import { Box, Text } from "@chakra-ui/react";
import React, { useRef } from "react";
import audioClick from "../assets/click-151673.mp3";
const SwitchButton = ({ isSwitchOn, setIsSwitchOn }) => {
// const [isSwitchOn, setIsSwitchOn] = useState(false);
const audio = useRef();
const switch_onChange_handle = () => {
setIsSwitchOn(!isSwitchOn);
if (audio.current) {
audio.current.play();
}
};
return (
<Box display="flex" alignItems="center">
<Box
as="button"
display="flex"
justifyContent="normal"
alignItems="center"
// justifyContent={isSwitchOn ? "flex-end" : "flex-start"}
width="90px"
height="25px"
borderRadius="20px"
backgroundColor={isSwitchOn ? "#004118" : "#ef0000"}
onClick={switch_onChange_handle}
position="relative"
fontSize="13px"
fontWeight="100"
transition="background-color 0.2s"
_before={{
// content: '""',
// position: "absolute",
// width: "20px",
// height: "20px",
// borderRadius: "50%",
// backgroundColor: "#FFF",
// boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)",
// transform: isSwitchOn ? "translateX(65px)" : "translateX(0)",
// transition: "transform 0.2s",
// left:'2px'
content: '""',
position: "absolute",
height: "25px",
width: "25px",
left: "0px",
background:
"conic-gradient(rgb(104, 104, 104), white, rgb(104, 104, 104), white, rgb(104, 104, 104))",
borderRadius: "50%",
transitionDuration: ".3s",
boxShadow: " 5px 2px 7px rgba(8, 8, 8, 0.308)",
transform: isSwitchOn ? "translateX(65px)" : "translateX(0)",
}}
>
<Text
// color="#FFF"
fontWeight="400"
zIndex={1}
position="absolute"
mb={0}
color={isSwitchOn ? "#fff" : "#fff"}
left={isSwitchOn ? "10px" : "auto"}
right={isSwitchOn ? "auto" : "10px"}
>
{isSwitchOn ? "Active" : "InActive"}
</Text>
</Box>
<audio ref={audio} src={audioClick} />
</Box>
);
};
export default SwitchButton;

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

View File

@@ -0,0 +1,22 @@
import { CheckCircleIcon, WarningIcon } from "@chakra-ui/icons";
import { Box, Text } from "@chakra-ui/react";
import React from "react";
import { PiWarningBold } from "react-icons/pi";
const ToastBox = ({ message, status }) => {
return (
<Box
color="white"
rounded={"sm"}
className="web-text-large d-flex gap-2 align-items-center"
p={3}
bg={status === "error" ? "red.500" : status === "warn" ? "yellow.500" : status === "info" ? "blue.500" : "green.500"}
>
{status === "error" || status === "warn" ? <PiWarningBold/> : status === "info" ? <WarningIcon/> : <CheckCircleIcon /> }
<Text as={"span"}>{message}</Text>
</Box>
);
};
export default ToastBox;

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

198
src/Constants/Constants.js Normal file
View File

@@ -0,0 +1,198 @@
import dns from "node:dns"
import * as XLSX from 'xlsx';
export const generateSerialNumber = (index, currentPage, pageSize) => {
return (currentPage - 1) * pageSize + (index + 1);
};
export function getTomorrowDate() {
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
// Format the date as YYYY-MM-DD (ISO 8601)
return tomorrow.toISOString().split('T')[0];
}
export function removeTrailingZeros(value) {
// Convert the value to a number and then to a string
let number = parseFloat(value);
let result = number.toString();
// Check if the result contains a decimal point
if (result.includes('.')) {
// Remove trailing zeros if the decimal part is 0 or 00
result = result.replace(/(\.\d*?)0+$/, '$1'); // Remove trailing zeros
result = result.replace(/\.$/, ''); // Remove the decimal point if it's the last character
}
return result;
}
export function getCountdownTimer(utcDateString) {
// Parse the UTC datetime string into a Date object
const targetDate = new Date(utcDateString);
const now = new Date();
// Calculate the difference in milliseconds
const difference = targetDate - now;
if (difference <= 0) {
return 'The time has passed or is now!';
}
// Convert the difference from milliseconds to a more readable format
const seconds = Math.floor(difference / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const remainingDays = days;
const remainingHours = hours % 24;
const remainingMinutes = minutes % 60;
const remainingSeconds = seconds % 60;
return `${remainingDays === 0 ? "": remainingDays+"d"} ${remainingHours === 0 ? "": remainingHours+"h"} ${remainingMinutes}m ${remainingSeconds}s `;
}
export function startCountdown(utcDateString) {
// Function to update the countdown
const updateCountdown = () => {
const countdown = getCountdownTimer(utcDateString);
console.log(countdown);
};
// Update countdown immediately
updateCountdown();
// Set up interval to update countdown every minute (60000 milliseconds)
setInterval(updateCountdown, 60000);
}
export const getFileNameFromPath = (filePath) => {
const parts = filePath?.split("/");
return parts?.[parts?.length - 1];
};
export function debounce(func, delay) {
let debounceTimer;
return function (...args) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(this, args), delay);
};
}
async function resolveMx(domain, recordType) {
return new Promise((resolve, reject) => {
dns.resolveMx(domain, (err, mxRecords) => {
if (err) {
reject(err);
return;
}
const addresses = mxRecords.map((mxRecord) => mxRecord.exchange);
resolve(addresses);
});
});
}
// Async function to check email address validity
export async function checkEmailValidity(email) {
try {
const domain = email?.split("@")[1];
const addresses = await resolveMx(domain, "MX");
if (addresses && addresses?.length > 0) {
return true;
}
return false; // No MX record exists
} catch (err) {
return false; // Error occurred
}
}
// Function to convert timestamp to readable date format in Gulf timezone
export function formatTimestampInGulfTimezone(timestamp) {
const date = new Date(timestamp);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZone: 'Asia/Dubai', // Gulf Standard Time (GST) timezone
timeZoneName: 'short'
};
return date.toLocaleDateString('en-GB', options);
}
export function formatDate(dateString) {
const options = { year: 'numeric', month: 'short', day: 'numeric' };
const date = new Date(dateString);
return date.toLocaleDateString('en-US', options);
}
export function calculatePercentage(part, total) {
if (total === 0) {
return 0; // To avoid division by zero
}
return (part / total) * 100;
}
export const exportToExcel = (data, customHeaders, fileName = 'exported-data.xlsx') => {
// Map your data to include only the fields that match your custom headers
const mappedData = data.map(item =>
customHeaders.map(header => item[header.key] || '')
);
// Prepend the headers row
const sheetData = [customHeaders.map(header => header.label), ...mappedData];
// Create a worksheet from the data array
const worksheet = XLSX.utils.aoa_to_sheet(sheetData);
// Apply styles to header cells
customHeaders.forEach((header, index) => {
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: index }); // r: row, c: column
if (!worksheet[cellAddress]) return; // Skip if cell doesn't exist
worksheet[cellAddress].s = {
fill: {
fgColor: { rgb: "FFFF00" } // Set header background color (Yellow in this case)
},
font: {
bold: true, // Make header text bold
color: { rgb: "000000" } // Set header text color (Black in this case)
}
};
});
// Create a new workbook and append the worksheet
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
// Generate a buffer from the workbook
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
// Create a Blob object from the buffer
const dataBlob = new Blob([excelBuffer], { type: 'application/octet-stream' });
// Create a link element to trigger the download
const link = document.createElement('a');
link.href = URL.createObjectURL(dataBlob);
link.download = fileName;
// Append the link to the document body, trigger the download, and remove the link
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};

View File

@@ -0,0 +1,2 @@
export const TABLE_PAGINATION = { page: 1, size:20 }
export const IMAGE_URI = import.meta.env.VITE_API_IMAGE_URL

View File

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

File diff suppressed because it is too large Load Diff

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
src/Images/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
src/Images/logoDark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/Images/logoDarkMini.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/Images/logoLight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/Images/miniLogo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
src/Images/reactLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 MiB

View File

@@ -0,0 +1,563 @@
import React, { useContext, useEffect, useState } from "react";
import { BsDot } from "react-icons/bs";
import { useDispatch } from "react-redux";
import { RxDotFilled } from "react-icons/rx";
import {
TbArrowBadgeLeftFilled,
TbListDetails,
TbReportMoney,
TbTransactionDollar,
} from "react-icons/tb";
import {
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,
Text,
WrapItem,
Popover,
Tag,
Accordion,
AccordionItem,
AccordionButton,
AccordionIcon,
AccordionPanel,
Image,
Alert,
AlertIcon,
Breadcrumb,
Divider,
Button,
} from "@chakra-ui/react";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import Cookies from "js-cookie"; // Import the Cookies library
import HeaderMain from "../Components/HeaderMain";
import {
RiBankLine,
RiExchangeBoxLine,
RiFileUserLine,
RiMoneyDollarBoxLine,
} from "react-icons/ri";
import {
MdNotificationsNone,
MdOutlineAddChart,
MdOutlineTaskAlt,
} from "react-icons/md";
import { HiOutlineChartSquareBar } from "react-icons/hi";
import { GrManual, GrNotification } from "react-icons/gr";
import { LuContact } from "react-icons/lu";
import SplashScreen from "../Pages/SplashScreen";
const DashboardLayout = ({ isOnline }) => {
const navigate = useNavigate();
const dispach = useDispatch();
const location = useLocation();
const path = location.pathname;
const [isDrawerOpen, setIsDrawerOpen] = useState(true);
const {
setIsAuthenticate,
colorMode,
toggleColorMode,
setSlideFormRight,
slideFromRight,
} = useContext(GlobalStateContext);
const [isSplashVisible, setSplashVisible] = useState(true);
const [openIndex, setOpenIndex] = useState(null);
useEffect(() => {
const savedIndex = localStorage.getItem("openAccordionIndex");
if (savedIndex !== null) {
setOpenIndex(parseInt(savedIndex));
}
}, []);
const handleAccordionChange = (index) => {
const newIndex = openIndex === index ? null : index;
setOpenIndex(newIndex);
localStorage.setItem("openAccordionIndex", newIndex);
};
useEffect(() => {
// Set a timer to hide the splash screen after 3 seconds
const timer = setTimeout(() => {
setSplashVisible(false);
}, 1000); // 3000ms = 3 seconds
// Cleanup the timer
return () => clearTimeout(timer);
}, []);
const toggleDrawer = () => {
setIsDrawerOpen(!isDrawerOpen);
};
const logOutHandler = () => {
// dispach(loginUser(false));
setIsAuthenticate(false);
Cookies.remove("isAuthenticated");
localStorage.removeItem("refreshToken");
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshTokenExp");
navigate("/login");
};
// // Function to get the title based on the route
const getTitle = () => {
switch (true) {
case "/":
return "👋🏻 Hi, Developers";
case path.startsWith("/task"):
return (
<span className="d-flex align-items-center gap-2">
<MdOutlineTaskAlt className="h4 m-0" /> Tasks
</span>
);
case path.startsWith("/notification"):
return (
<span className="d-flex align-items-end gap-2">
<GrNotification className="h5 m-0" /> Notification
</span>
);
case path.startsWith("/exchange-rate"):
return (
<span className="d-flex align-items-end gap-2">
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
Echange rate
</span>
);
case path.startsWith("/create-io"):
if (/^\/create-io\/[A-Za-z0-9_-]+$/.test(path)) {
return (
<span className="d-flex align-items-end gap-2">
<MdOutlineAddChart className="h4 m-0 fw-normal" />
Edit IO
</span>
);
}
return (
<span className="d-flex align-items-end gap-2">
<MdOutlineAddChart className="h4 m-0 fw-normal" />
Create IO
</span>
);
case path.startsWith("/view-io"):
return (
<span className="d-flex align-items-end gap-2">
<HiOutlineChartSquareBar className="h4 m-0 fw-normal" />
View IO
</span>
);
case path.startsWith("/investor-details"):
return (
<span className="d-flex align-items-end gap-2">
<TbListDetails className="h4 m-0 fw-normal" />
Investor Details
</span>
);
case path.startsWith("/investor-transactions"):
return (
<span className="d-flex align-items-end gap-2">
<TbTransactionDollar className="h4 m-0 fw-normal" />
Investor Transactions
</span>
);
case path.startsWith("/deposit-request"):
return (
<span className="d-flex align-items-end gap-2">
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
Deposite pending request
</span>
);
case path.startsWith("/deposit-history"):
return (
<span className="d-flex align-items-end gap-2">
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
Deposite withdrawal request
</span>
);
case path.startsWith("/withdraw-request"):
return (
<span className="d-flex align-items-end gap-2">
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
Withdrawal pending request
</span>
);
case path.startsWith("/withdraw-history"):
return (
<span className="d-flex align-items-end gap-2">
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
Withdrawal request
</span>
);
case path.startsWith("/investor-request"):
return (
<span className="d-flex align-items-end gap-2">
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
Investor pending request
</span>
);
case path.startsWith("/investor-history"):
return (
<span className="d-flex align-items-end gap-2">
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
Investor request
</span>
);
case path.startsWith("/deletion-request"):
return (
<span className="d-flex align-items-end gap-2">
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
Deletion pending request
</span>
);
case path.startsWith("/deletion-history"):
return (
<span className="d-flex align-items-end gap-2">
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
Deletion request
</span>
);
case path.startsWith("/bank-investor"):
return (
<span className="d-flex align-items-end gap-2">
<TbReportMoney className="h4 m-0 fw-normal" />
Ban / Unban Investor
</span>
);
case path.startsWith("/academy"):
return (
<span className="d-flex align-items-end gap-2">
<GrManual className="h4 m-0 fw-normal" />
Academy
</span>
);
case path.startsWith("/notification"):
return (
<span className="d-flex align-items-end gap-2">
<MdNotificationsNone className="h4 m-0 fw-normal" />
Notification
</span>
);
case path.startsWith("/contact"):
return (
<span className="d-flex align-items-end gap-2">
<LuContact className="h4 m-0 fw-normal" />
Contact Details
</span>
);
case path.startsWith("/users"):
return (
<span className="d-flex align-items-end gap-2">
<RiFileUserLine className="h4 m-0 fw-normal" />
Users
</span>
);
case path.startsWith("/bank-details"):
return (
<span className="d-flex align-items-end gap-2">
<RiBankLine className="h4 m-0 fw-normal" />
Bank Details
</span>
);
case path.startsWith("/deletion-request"):
return (
<span className="d-flex align-items-end gap-2">
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
Deletion pending request
</span>
);
case path.startsWith("/deletion-history"):
return (
<span className="d-flex align-items-end gap-2">
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
Deletion request
</span>
);
case path.startsWith("/deletion-request"):
return (
<span className="d-flex align-items-end gap-2">
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
Deletion pending request
</span>
);
case path.startsWith("/deletion-history"):
return (
<span className="d-flex align-items-end gap-2">
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
Deletion request
</span>
);
default:
return <span className="d-flex align-items-end gap-2">Home</span>;
}
};
if (isSplashVisible) {
return <SplashScreen />;
}
return (
<Box height={"100vh"}>
<HeaderMain
isDrawerOpen={isDrawerOpen}
logOutHandler={logOutHandler}
toggleDrawer={toggleDrawer}
icon
title={getTitle()}
/>
<Box
h={{ base: "92%", xl: "94%" }}
style={{
width: "100%",
position: "relative",
overflow: "hidden",
}}
className="d-flex"
// pe={0.5}
>
<Alert
transition={"0.5s"}
h={53}
transform={isOnline ? "translateY(-100px)" : "translateY(0px)"}
position={"absolute"}
zIndex={999}
status="info"
variant="solid"
bgGradient="linear(to-r, #C33FAD, #C33FAD, #C33FAD)"
// bgGradient='linear(to-r, #1EBCA3, #E0EEFF)'
color={"white"}
fontWeight={600}
fontSize={"md"}
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
>
<AlertIcon color={"white"} />
No Internet !
</Alert>
<Box
className="h-100 position-relative sideBar"
style={{
width: isDrawerOpen ? 230 : 0,
transition: "width 0.3s ease-in-out, transform 0.3s ease-in-out", // Smooth transition for width and transform
backgroundColor: "#FFF",
position: "relative",
color: "gray.500",
transform: isDrawerOpen ? "translateX(0)" : "translateX(-230px)", // Move box to the left when closed
}}
boxShadow={'md'}
>
<Box
className="ps-0 scroll-bar pe-0 pt-3="
style={{
height: "100%",
overflowY: "scroll",
overflowX: "hidden",
paddingBottom: "5rem",
}}
>
<Accordion
m={0}
// p={2}
allowToggle
defaultIndex={-1}
index={openIndex}
onChange={handleAccordionChange}
>
{nav.map(
({ title, type, Icon, submenu, path, colorCode }, index) => {
if (type === "accordion") {
return (
<AccordionItem
key={index}
border={"none"}
style={{ borderRadius: "2px", marginBottom: "8px" }}
>
<AccordionButton
style={{ height: "auto", borderRadius: "2px" }}
_hover={{ bg: "#1a04361c" }}
className={`${
true
? "p-1 web-text-medium ps-3 justify-content-between"
: "p-2 ps-1 web-text-xlarge justify-content-center"
} link d-flex align-items-center gap-2 w-100 mb-1`}
>
<Box
// to="/"
as="span"
className="d-flex align-items-centre gap-2 w-100 py-1"
>
{/* {Icon && title === "Admin" ? <Image w={15} src={shield} /> : <Icon className={`web-text-large`} />} */}
{Icon && (
<Icon
fontSize={title === "Admin" ? "18px" : "15px"}
/>
)}
<Text
as={"span"}
display={true ? "flex" : "none"}
alignItems="center"
overflow="hidden"
textAlign={"left"}
w={'100%'}
>
{title}
</Text>
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel
p={0}
pb={1}
display={"flex"}
flexDirection={"column"}
gap={1}
>
{submenu?.map(
(
{
title: subMenuTitle,
path: link,
icon: SubIcon,
colorCode,
},
i
) => (
<Box
key={i}
style={{
height: "auto",
position: "relative",
}}
className={`${
true
? " web-text-medium ps-3"
: " web-text-xlarge justify-content-center"
} d-flex align-items-center p-0`}
>
<NavLink
style={{ borderRadius: "2px" }}
className={`${
true
? "p-2 ms-2 web-text-small "
: "p-2 ps-0 ms-0 zindex-3 ms-4 web-text-xlarge justify-content-center"
} link d-flex align-items-center gap-2 w-100 mx-2`}
to={link}
>
<RxDotFilled/>
<Text
as={"span"}
display={true ? "flex" : "none"}
alignItems="center"
overflow="hidden"
>
{subMenuTitle}
</Text>
</NavLink>
</Box>
)
)}
</AccordionPanel>
</AccordionItem>
);
} else if (type === "title") {
return (
<Text
as={"span"}
key={index}
className="web-text-xxsmall fw-600 text-secondary fw-bold"
ps={4}
>
{title}
</Text>
);
} else if (type === "single") {
return (
<NavLink
key={index}
style={{
height: "auto",
position: "relative",
borderRadius: "2px",
}}
className={`${
true
? "p-2 web-text-medium"
: "p-2 ps-0 web-text-xlarge justify-content-start"
} d-flex link align-items-center gap-2 w-100 mb-2 single`}
to={path}
onClick={title == "Logout" ?logOutHandler:null}
>
{Icon && <Icon className="web-text-large ms-2" />}
<Text
as={"span"}
display={true ? "flex" : "none"}
alignItems="center"
overflow="hidden"
>
{title}
</Text>
</NavLink>
);
} else {
return null;
}
}
)}
</Accordion>
</Box>
</Box>
<Box
style={{
width: `calc(100% - ${isDrawerOpen ? 230 : 0}px)`,
transition: "width 0.3s ease-in-out",
backgroundColor: "#F3F3F9",
display: "flex",
flexDirection: "column",
gap: 0,
}}
>
{/* <HeaderBox
slideDirecttion={slideFromRight}
logOutHandler={logOutHandler}
icon
title={getTitle()}
/> */}
{/* <CustomBreadcrumb /> */}
<AppContent />
</Box>
</Box>
</Box>
);
};
export default DashboardLayout;
const AppContent = () => {
return (
<Routes>
{RouteLink.map(({ path, Component }, index) => (
<Route key={index} path={path} element={<Component />} />
))}
<Route path="*" element={<NotFound />} />
</Routes>
);
};

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

@@ -0,0 +1,23 @@
import { motion } from "framer-motion";
export const OPACITY_ON_LOAD = {
as: motion.div,
initial: { opacity: 0 },
animate: { opacity: 1 }
}
export const SLIDE_IN_BOTTOM = {
as: motion.div,
initial: { opacity: 0, y: 50 },
animate: { opacity: 1, y: 0 },
transition: { duration: 1, 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" }
};

162
src/Pages/Dashbaord.jsx Normal file
View File

@@ -0,0 +1,162 @@
import { Box, HStack, Icon, position, Text, VStack } from '@chakra-ui/react'
import React from 'react'
import { HiOutlineChartSquareBar } from 'react-icons/hi'
import { RiMoneyDollarBoxLine } from 'react-icons/ri'
import { TbTransactionDollar } from 'react-icons/tb'
import { VscSymbolClass } from 'react-icons/vsc'
import { TABLE_PAGINATION } from '../Constants/Paginations'
import FullscreenLoaders from '../Components/Loaders/FullscreenLoaders'
import { useGetIOprepopulateDataQuery, useGetIOsQuery } from '../Services/io.service'
import { useGetInvestorsQuery } from '../Services/investor.details.service'
import DonutChart from '../Components/Doughnut/DonutChart'
import { GoDotFill } from "react-icons/go";
import { useNavigate } from 'react-router-dom'
import LineChart from '../Components/Doughnut/LineChart'
import { PiChartLineUpDuotone } from 'react-icons/pi'
import ApexChart from '../Components/Doughnut/ApexDonut'
import ApexLine from '../Components/Doughnut/ApexLine'
import ReactApexChart from 'react-apexcharts'
import { BsGraphUpArrow } from "react-icons/bs";
const Dashbaord = () => {
const navigate = useNavigate()
const { data, isLoading: isIoPreLoading } = useGetIOprepopulateDataQuery();
const { data: IO, isLoading: isIoLoading } = useGetIOsQuery({ page: TABLE_PAGINATION?.page, size: TABLE_PAGINATION?.size });
const { data: investorDetails, isInvestorLoading } = useGetInvestorsQuery({ page: TABLE_PAGINATION?.page, size: TABLE_PAGINATION?.size });
const sortArrayByStatus = () => {
const sortedArrays = {
open: [],
closed: [],
processing: [],
draft: []
};
IO?.data?.rows.forEach(item => {
const status = item.ioStatus?.statusAdmin;
if (status === 'Open') {
sortedArrays.open.push(item);
} else if (status === 'Closed') {
sortedArrays.closed.push(item);
} else if (status === 'Processing') {
sortedArrays.processing.push(item);
} else if (status === 'Draft') {
sortedArrays.draft.push(item);
}
});
return sortedArrays;
};
const statusData = sortArrayByStatus()
const chartData = {
labels: ['Draft', 'Open', 'Processing', 'Closed',],
backgroundColor: ['#3182ce', '#004118', '#D69E2E', '#E53E3E'],
values: [statusData?.draft?.length, statusData?.open?.length, statusData?.processing?.length, statusData?.closed?.length]
};
const series1= [{
data: [25, 66, 41, 89, 63, 25, 44, 12, 36, 9, 54]
}]
const options1= {
chart: {
type: 'line',
position:"absolute",
right:0,
width: 100,
height: 35,
sparkline: {
enabled: true
}
},
tooltip: {
fixed: {
enabled: false
},
x: {
show: false
},
y: {
title: {
formatter: function (seriesName) {
return ''
}
}
},
marker: {
show: false
}
}
}
return (
isIoPreLoading || isIoLoading || isInvestorLoading ? <FullscreenLoaders /> :
<Box height={'100vh'} bg={'#fff'} roundedTop={0} pt={5} overflowX={"hidden"}>
<Box display={'flex'} gap={6} w={'100%'} pt={3} pb={3} p={3} >
<Box position={'relative'} cursor={'pointer'} onClick={() => navigate("/investor-details")} boxShadow={'lg'} color={"#004118"} p={4} rounded={'xl'} w={'25%'} display={'flex'} bg={'#f5f8f6'} flexDirection={'column'} alignItems={'start'} >
<Icon left={"10px"} bg={'#004118'} rounded={9} p={2} color={"#fff"} as={TbTransactionDollar} mb={6} boxSize={12} />
<Text as={'span'} fontSize={'xs'} fontWeight={500}>Total Investors</Text>
<Text as={'span'} fontSize={'32px'} fontWeight={600}>{investorDetails?.data?.totalItems}</Text>
<Icon position={'absolute'} right={6} bottom={6} boxSize={8} as={BsGraphUpArrow} />
{/* <ReactApexChart position={'absolute'} right={6} bottom={6} options={options1} series={series1} type="line" height={35} width={100} /> */}
</Box>
<Box position={'relative'} cursor={'pointer'} onClick={() => navigate("/view-io")} boxShadow={'lg'} bg={'#f5f8f6'} color={"#004118"} p={3} rounded={'xl'} w={'25%'} display={'flex'} flexDirection={'column'} alignItems={'start'} >
<Icon bg={'#004118'} rounded={9} p={2} color={"#fff"} as={HiOutlineChartSquareBar} mb={6} boxSize={12} />
<Text as={'span'} fontSize={'xs'} fontWeight={500}>Total IO</Text>
<Text as={'span'} fontSize={'32px'} fontWeight={600}>{IO?.data?.totalItems}</Text>
<Icon position={'absolute'} right={6} bottom={6} boxSize={8} as={BsGraphUpArrow} />
</Box>
<Box position={'relative'} cursor={'pointer'} onClick={() => navigate("/sponser")} boxShadow={'lg'} bg={'#f5f8f6'} color={"#004118"} p={3} rounded={'xl'} w={'25%'} display={'flex'} flexDirection={'column'} alignItems={'start'} >
<Icon bg={'#004118'} rounded={9} p={2} color={"#fff"} as={RiMoneyDollarBoxLine} mb={6} boxSize={12} />
<Text as={'span'} fontSize={'xs'} fontWeight={500}>Total sponors</Text>
<Text as={'span'} fontSize={'32px'} fontWeight={600}>{data?.data?.sponsor?.length}</Text>
<Icon position={'absolute'} right={6} bottom={6} boxSize={8} as={BsGraphUpArrow} />
</Box>
<Box position={'relative'} cursor={'pointer'} onClick={() => navigate("/investment-type")} boxShadow={'lg'} bg={'#f5f8f6'} color={"#004118"} p={3} rounded={'xl'} w={'25%'} display={'flex'} flexDirection={'column'} alignItems={'start'} >
<Icon bg={'#004118'} rounded={9} p={2} color={"#fff"} as={VscSymbolClass} mb={6} boxSize={12} />
<Text as={'span'} fontSize={'xs'} fontWeight={500}>Total Investment Type</Text>
<Text as={'span'} fontSize={'32px'} fontWeight={600}>{data?.data?.investmentType?.length}</Text>
<Icon position={'absolute'} right={6} bottom={6} boxSize={8} as={BsGraphUpArrow} />
</Box>
</Box>
<Box h={'70%'} w={"100%"} display={'flex'} pe={4} mt={2}>
<Box w={'60%'} h={'100%'} p={4} pe={6} pt={1} >
<Box position={'relative'} h={'100%'} boxShadow={'lg'} display={'flex'} justifyContent={'center'} rounded={'xl'} p={5} ps={0} pe={0}>
{/* <Text position={'absolute'} top={0} left={6} as={'span'} fontSize={'sm'}>Exchange rate currency</Text> */}
{/* <LineChart /> */}
<ApexLine/>
</Box>
</Box>
<Box boxShadow={'lg'} position={"relative"} bg={'#fff'} rounded={'xl'} w={'40%'} display={'flex'} justifyContent={'space-between'} flexDirection={'column'} h={"95%"} mt={1} p={4}>
<Text as={'span'} fontSize={'sm'}>IO Status</Text>
<Box display={'flex'} w={'100%'} h={'100%'} alignItems={'center'} justifyContent={'space-around'} >
{/* <Box display={'flex'} w={'70%'} alignItems={'center'} h={325} p={6}> */}
{/* <DonutChart data={chartData} /> */}
<ApexChart data={chartData} />
{/* </Box> */}
<VStack alignItems={'start'} justifyContent={'center'} flexWrap={'wrap'}>
{chartData?.labels?.map((item, index) => <Text key={index} as={'span'} display={'flex'} gap={0.5} alignItems={'center'} fontSize={'sm'} fontWeight={600}><GoDotFill color={chartData?.backgroundColor[index]} fontSize={30} />{item}</Text>)}
</VStack>
</Box>
</Box>
</Box>
</Box>
)
}
export default Dashbaord

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

@@ -0,0 +1,289 @@
import { useNavigate } from "react-router-dom";
import Input01 from "../Components/Inputs/Input01";
import { useDispatch, useSelector } from "react-redux";
import { loginUser } from "../Redux/Slice/auth";
import { useContext, useEffect, useState } from "react";
import Button01 from "../Components/Buttons/PrimaryButton";
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/loginpat.png";
import logo from "../assets/logoOptifii.svg";
import {
Box,
Button,
FormControl,
FormLabel,
Image,
Input,
InputGroup,
InputRightElement,
Text,
useToast,
} from "@chakra-ui/react";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import Cookies from "js-cookie";
import ToastBox from "../Components/ToastBox";
import { useLoginMutation } from "../Services/token.serivce";
// import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";
const validationSchema = Yup.object().shape({
emailAddress: Yup.string()
.email("Invalid email address")
.required("Email address is required"),
password_hash: Yup.string().required("Password is required"),
});
const Login = () => {
const [show, setShow] = useState(false);
const handleClick = () => setShow(!show);
const { isAuthenticate, setIsAuthenticate } = useContext(GlobalStateContext);
const toast = useToast();
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const [login] = useLoginMutation()
useEffect(() => {
if (isAuthenticate) {
navigate("/sponser");
}
}, [navigate, isAuthenticate]);
const onSubmit = async (value) => {
setIsLoading(true);
// try {
// // const res = await login(value).unwrap();
// if (res?.statusCode === 200) {
// toast({
// render: () => (
// <ToastBox status={"success"} message={"res?.message"} />
// ),
// })
// setIsLoading(false);
// setIsAuthenticate(true);
// Cookies.set("isAuthenticated", true, { expires: 7 });
// navigate("/sponser");
// reset();
// }
// } catch (err) {
// if (err) {
// toast({
// render: () => (
// <ToastBox status={"error"} message={err?.data?.message} />
// ),
// });
// setIsLoading(false);
// }
// }
if (value.emailAddress === "admin@optifii.com" && value.password_hash === "Admin@123") {
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),
});
console.log(errors);
return (
<Box
h={"100vh"}
w={'100%'}
display={'flex'}
justifyContent={'end'}
overflow={'hidden'}
bg={'#EFEFEF'}
>
<Box position={'relative'} display={'flex'} justifyContent={'center'} alignItems={'center'} w={'55%'}>
<Image w={40} src={logo} />
</Box>
<Box
as="form"
onSubmit={handleSubmit(onSubmit)}
w={'45%'}
backgroundColor={'#fff'}
p={20}
boxShadow={'md'}
zIndex={2}
display={'flex'}
justifyContent={'center'}
alignItems={'start'}
flexDirection={'column'}
position={'relative'}
>
<Box display={'flex'} flexDirection={'column'} gap={0} mb={8}>
<Text as={'span'} fontSize={'xl'} fontWeight={600} >
Hello Again!
</Text>
<Text as={'span'} fontSize={'sm'} fontWeight={400} color={'gray.500'}>
Welcome Back
</Text>
</Box>
<FormControl mb={6}>
{/* <FormLabel className="rubix-text-dark ps-1 web-text-medium fw-bold">
E-mail <span className="text-danger">*</span>
</FormLabel> */}
<Input
{...register("emailAddress")}
focusBorderColor="purple.500"
type="text"
name="emailAddress"
variant="filled"
placeholder="Email"
size="lg"
className="web-text-medium"
/>
{errors.emailAddress && (
<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.emailAddress.message}
</span>
)}
</FormControl>
<FormControl mb={7}>
{/* <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_hash")}
className="web-text-medium"
focusBorderColor="purple.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={"purple.800"}
onClick={handleClick}
>
{show ? "Hide" : "Show"}
</Button>
</InputRightElement>
</InputGroup>
{errors.password_hash && (
<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_hash.message}
</span>
)}
</FormControl>
<Button
isLoading={isLoading}
type="submit"
className="w-100"
color={"whitesmoke"}
bg="#6211CB"
_hover={{bg:'#6211CB', opacity:0.9}}
_active={{bg:"#6211CB", opacity:1}}
size="md"
fontWeight={400}
fontSize={'sm'}
mb={4}
>
Send OTP
</Button>
<Text w={'100%'} color={'gray.500'} fontSize={'sm'} textAlign={'center'} >Forgot Password</Text>
<Text
style={{
position: "absolute",
left:0,
bottom: "0%",
fontSize: "13px",
color: "#919191",
textAlign: "center",
width: "100%",
zIndex: 2,
}}
>
Optifii v1.0.0
</Text>
</Box>
<Image
style={{
position: "absolute",
left: 0,
bottom: 0,
width: 350,
}}
src={Asset1}
alt="bg-img"
/>
</Box>
);
};
export default Login;

View File

@@ -0,0 +1,24 @@
import { Box, Image, Text } from '@chakra-ui/react'
import noInternet from "../assets/noInternet.jpg"
const NoInternetScreen = () => {
return (
<Box
h={'100vh'}
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
flexDirection={'column'}
position={"relative"}
gap={5}
>
<Image src={noInternet} w={300} />
{/* <Text color={'blue.800'} as={'span'} className='fw-bold'>No Internet !</Text> */}
<Text color={'gray.500'} fontSize={'sm'} fontWeight={'500'} position={'absolute'} bottom={0} left={'47%'}>Tanami v1.0</Text>
</Box>
)
}
export default NoInternetScreen

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

@@ -0,0 +1,22 @@
import { Box, Image, Text } from "@chakra-ui/react"
import error from "../assets/Error.svg"
import robot from "../assets/optifii_logo.png"
// import robot from "../assets/robot.png"
const NotFound = () => {
return (
<Box
h={'100vh'}
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
flexDirection={'column'}
gap={8}
>
<Image src={robot} w={"100px"} />
<Text color={'purple.800'} as={'span'} fontSize={'small'}>The requested URL was not found on this server.</Text>
</Box>
)
}
export default NotFound

View File

@@ -0,0 +1,15 @@
import React from "react";
import { Box, Button, Heading, Image, Img, Text } from "@chakra-ui/react";
import failed from "../assets/failed.gif";
const PaymentFailed = () => {
return (
<Box h={"100vh"} display={"flex"} alignItems={"center"} flexDirection={"column"} p={"15px"} justifyContent={"center"}>
<Img w={"180px"} src={failed} />
<Heading fontSize={"20px"} fontWeight={600}>Payment Failed !!</Heading>
<Text fontSize={"18px"}>Your payment was Failed</Text>
</Box>
);
};
export default PaymentFailed;

View File

@@ -0,0 +1,15 @@
import React from "react";
import { Box, Button, Heading, Image, Img, Text } from "@chakra-ui/react";
import success from "../assets/successimg.gif";
const Welcome = () => {
return (
<Box h={"100vh"} display={"flex"} alignItems={"center"} flexDirection={"column"} p={"15px"} justifyContent={"center"}>
<Img w={"180px"} src={success} />
<Heading fontSize={"20px"} fontWeight={600} textAlign={"center"}>SuccessFul !!</Heading>
<Text fontSize={"18px"} extAlign={"center"}>Your payment was done successfully</Text>
</Box>
);
};
export default Welcome;

View File

@@ -0,0 +1,21 @@
import { Box, Image, Spinner, Text } from '@chakra-ui/react'
import React from 'react'
import logo from '../assets/logoOptifii.svg'
const SplashScreen = () => {
return (
<Box
h={'100vh'}
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
flexDirection={'column'}
gap={10}
>
<Image w={150} src={logo} />
<Spinner color='purple' size='md' />
</Box>
)
}
export default SplashScreen

View File

@@ -0,0 +1,22 @@
import { Box, Image, Text } from '@chakra-ui/react'
import React from 'react'
// import noInternet from "../assets/Error.svg"
import robot from "../assets/robot.png"
const UnderConstruction = ({title, h}) => {
return (
<Box
h={h?h:'100vh'}
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
flexDirection={'column'}
gap={8}
>
<Image src={robot} w={200} />
<Text color={'green.800'} as={'span'} mt={4} fontSize={'small'}>🚧 Building Something Amazing Just for You! 🚧</Text>
</Box>
)
}
export default UnderConstruction

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

@@ -0,0 +1,18 @@
import { Box, Text } from "@chakra-ui/react";
import React from "react";
import welcome from '../Images/welcomeBanner.gif'
const WelcomePage = () => {
return (
<Box
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
w={"100%"}
h={"100%"}
>
</Box>
);
};
export default WelcomePage;

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

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

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

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

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

@@ -0,0 +1,353 @@
import { HiOutlineNewspaper } from "react-icons/hi";
import { TbBrandMedium, TbChartHistogram, TbLayoutDashboard, TbReportMoney, TbUsersPlus } from "react-icons/tb";
import {
RiBankLine,
RiDashboardLine,
RiFileUserLine,
RiMoneyDollarBoxLine,
} from "react-icons/ri";
import { RiExchangeBoxLine } from "react-icons/ri";
import { VscGitPullRequestGoToChanges, VscSymbolClass } from "react-icons/vsc";
import { FiUsers } from "react-icons/fi";
import { PiCrown, PiHandCoins } from "react-icons/pi";
import { MdOutlineAddChart, MdOutlineAdminPanelSettings } from "react-icons/md";
import { HiOutlineChartSquareBar } from "react-icons/hi";
import { TbListDetails } from "react-icons/tb";
import { TbTransactionDollar } from "react-icons/tb";
import { TbCalendarDollar } from "react-icons/tb";
import { TbDeviceDesktopDollar } from "react-icons/tb";
import { BiGift, BiMoneyWithdraw } from "react-icons/bi";
import { GrDocumentUpdate, GrManual } from "react-icons/gr";
import { MdBrowserUpdated } from "react-icons/md";
import { AiOutlineUserDelete } from "react-icons/ai";
import { MdNotificationsNone } from "react-icons/md";
import { SiAcademia } from "react-icons/si";
import { LuContact } from "react-icons/lu";
import { LiaCrownSolid } from "react-icons/lia";
import { PiCrownDuotone } from "react-icons/pi";
import { IoCardOutline } from "react-icons/io5";
import { HiOutlineGiftTop } from "react-icons/hi2";
import { FcOnlineSupport } from "react-icons/fc";
export const nav = [
// {
// title: "Dashboard",
// type: "single",
// path: "/",
// Icon: TbLayoutDashboard,
// },
{
title: "MENU",
type: "title",
},
{
title: "Dashboard",
type: "single",
path: "/dashboard",
Icon: RiDashboardLine,
},
{
title: "Corporates",
submenu: [
{
title: "Registered Corporates",
path: "/sponser",
},
{
title: "New Corporates Request",
path: "/investment-type",
},
],
type: "accordion",
Icon: TbUsersPlus,
},
{
title: "Benifits Cards",
type: "single",
path: "/logout",
Icon: IoCardOutline,
},
{
title: "Gift Card Management",
type: "single",
path: "/gift-card-management",
Icon: HiOutlineGiftTop,
},
{
title: "Voucher Management",
type: "single",
path: "/voucher-management",
Icon: BiGift,
},
{
title: "Marketing Management",
submenu: [
{
title: "FAQ",
path: "/manage-FAQ",
},
{
title: "News & Articles",
path: "/manage-news-& -articlesManage-news-&-articles",
},
{
title: "About Us",
path: "/manage-about-us",
},
{
title: "Terms & Conditions",
path: "/manage-terms-&-conditions",
},
{
title: "Privacy Policy",
path: "/manage-privacy-policy",
},
],
type: "accordion",
Icon: PiHandCoins,
},
{
title: "Support & Ticketing Module",
type: "single",
path: "/support-&-ticketing-module",
Icon: FcOnlineSupport,
},
// {
// title: "Benifits Cards",
// submenu: [
// {
// title: "Create IO",
// path: "/create-io",
// icon: MdOutlineAddChart,
// },
// {
// title: "View IO",
// path: "/view-io",
// icon: HiOutlineChartSquareBar,
// },
// ],
// type: "accordion",
// Icon: IoCardOutline,
// },
// {
// title: "Investor",
// submenu: [
// {
// title: "Investor Details",
// path: "/investor-details",
// icon: TbListDetails,
// },
// // {
// // title: "Investor Transactions",
// // path: "/investor-transactions",
// // icon: TbTransactionDollar,
// // },
// ],
// type: "accordion",
// Icon: TbCalendarDollar,
// },
// {
// title: "INVESTORS REQUEST",
// type: "title",
// },
// {
// title: "Deposit",
// submenu: [
// {
// title: "Pending Request",
// path: "/deposit-request",
// icon: RiMoneyDollarBoxLine,
// },
// {
// title: "View History",
// path: "/deposit-history",
// icon: RiExchangeBoxLine,
// },
// ],
// type: "accordion",
// Icon: BiMoneyWithdraw,
// },
// {
// title: "Withdrawal",
// submenu: [
// {
// title: "Pending Request",
// path: "/withdraw-request",
// icon: RiMoneyDollarBoxLine,
// },
// {
// title: "View History",
// path: "/withdraw-history",
// icon: RiExchangeBoxLine,
// },
// ],
// type: "accordion",
// Icon: BiMoneyWithdraw,
// },
// {
// title: "Upgradation",
// submenu: [
// {
// title: "Pending Request",
// path: "/investor-request",
// icon: RiMoneyDollarBoxLine,
// },
// {
// title: "View History",
// path: "/investor-history",
// icon: RiExchangeBoxLine,
// },
// ],
// type: "accordion",
// Icon: MdBrowserUpdated,
// },
// {
// title: "Account",
// submenu: [
// {
// title: "Pending Request",
// path: "/deletion-request",
// icon: RiMoneyDollarBoxLine,
// },
// {
// title: "View History",
// path: "/deletion-history",
// icon: RiExchangeBoxLine,
// },
// ],
// type: "accordion",
// Icon: AiOutlineUserDelete,
// },
// {
// title: "MANAGE ADMIN",
// type: "title",
// },
// {
// title: "Admin",
// submenu: [
// {
// title: "Ban / Unban Investor",
// path: "/bank-investor",
// icon: TbReportMoney,
// },
// {
// title: "Academy",
// path: "/academy",
// icon: GrManual,
// },
// {
// title: "Notification",
// path: "/notification",
// icon: MdNotificationsNone,
// },
// {
// title: "Contact Details",
// path: "/contact",
// icon: LuContact,
// },
// {
// title: "Users",
// path: "/users",
// icon: RiFileUserLine,
// },
// {
// title: "Bank Details",
// path: "/bank-details",
// icon: RiBankLine,
// },
// ],
// type: "accordion",
// Icon: MdOutlineAdminPanelSettings,
// },
{
title: "Logout",
type: "single",
path: "/login",
Icon: HiOutlineNewspaper,
},
];
export const nestedNav = [
{
title: "MAIN MENU",
type: "accordion",
accArray: [
{
title: "Master",
submenu: [
{
title: "Sponser",
path: "/sponser",
icon: RiMoneyDollarBoxLine,
},
{
title: "Exchange rate",
path: "/exchange-rate",
icon: RiExchangeBoxLine,
},
{
title: "Asset classes",
path: "/view",
icon: VscSymbolClass,
},
],
type: "accordion",
Icon: TbBrandMedium,
},
{
title: "User",
submenu: [
{
title: "Sponser",
path: "/loop",
icon: TbBrandMedium,
},
{
title: "Class",
path: "/class",
icon: TbBrandMedium,
},
{
title: "View",
path: "/view",
icon: TbBrandMedium,
},
],
type: "accordion",
Icon: HiOutlineNewspaper,
},
],
},
{
title: "User",
submenu: [
{
title: "Sponser",
path: "/loop",
icon: TbBrandMedium,
},
{
title: "Class",
path: "/class",
icon: TbBrandMedium,
},
{
title: "View",
path: "/view",
icon: TbBrandMedium,
},
],
type: "accordion",
Icon: FiUsers,
},
{
title: "SPONSER",
type: "title",
},
{
title: "Single Link",
type: "single",
path: "/",
Icon: HiOutlineNewspaper,
},
];

View File

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

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

@@ -0,0 +1,9 @@
import NotFound from "../Pages/NotFound";
import Welcome from "../Pages/PaymentSuccess";
export const RouteLink = [
// =============[ Tanami ]================
// ===============[ Management]===============
{ path: "/", Component: NotFound },
];

View File

@@ -0,0 +1,42 @@
// io.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
export const keyMerits = createApi({
reducerPath: "ioService",
baseQuery: baseQuery,
tagTypes: ["getKeyMerits"],
endpoints: (builder) => ({
// =====[get]
getKeyMerits: builder.query({
query: (id) => `/io/admin/key-merits/${id}`,
providesTags: ["getKeyMerits"],
}),
setDisplayOrder: builder.mutation({
query: ({ data }) => ({
url: `/io/admin/key-merits/resetDisplayOrder`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetKeyMeritsQuery,
useSetDisplayOrderMutation
} =
keyMerits;

View File

View File

@@ -0,0 +1,47 @@
// investorDetails.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
// Define a service using a base URL and expected endpoints
export const banInvestorDetails = createApi({
reducerPath: "banInvestorDetails",
baseQuery: baseQuery,
tagTypes: ["getBanInvestor", "getUnbanInvestor"],
endpoints: (builder) => ({
getInvestor: builder.query({
query: () => `/investorDetails/admin`,
providesTags: ["getBanInvestor"],
}),
getUnbanInvestor: builder.query({
query: () => `/investorDetails/admin/getAllUnbanned`,
providesTags: ["getBanInvestor"],
}),
getbanInvestor: builder.query({
query: () => `/investorDetails/admin/getAllBanned`,
providesTags: ["getBanInvestor"],
}),
updateUnban: builder.mutation({
query: ({ id, data }) => ({
url: `/investorDetails/admin/unBanById/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getBanInvestor"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetUnbanInvestorQuery,
useGetInvestorQuery,
useGetbanInvestorQuery,
useUpdateUnbanMutation,
} = banInvestorDetails;

View File

@@ -0,0 +1,43 @@
// investorDetails.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
// Define a service using a base URL and expected endpoints
export const bankDetails = createApi({
reducerPath: "bankDetails",
baseQuery: baseQuery,
tagTypes: ["getBank"],
endpoints: (builder) => ({
getBank: builder.query({
query: ({ page, size }) =>
`/bankDetails/admin/?page=${page}&size=${size}`,
providesTags: ["getBank"],
}),
updateBankDetails: builder.mutation({
query: ({ data, id }) => ({
url: `/bankDetails/admin/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getBank"],
}),
getBankDetails: builder.query({
query: ({ data, id }) => ({
url: `/bankDetails/admin/${id}`,
method: "GET",
body: data,
}),
invalidatesTags: ["getBank"],
}),
}),
});
// Export hooks for usage in functional components
export const { useGetBankQuery, useUpdateBankDetailsMutation,useGetBankDetailsQuery } = bankDetails;

View File

@@ -0,0 +1,37 @@
// investorDetails.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
// Define a service using a base URL and expected endpoints
export const contact = createApi({
reducerPath: "contact",
baseQuery: baseQuery,
tagTypes: ["getContact"],
endpoints: (builder) => ({
getContact: builder.query({
query: () =>
`/contactDetails/admin`,
providesTags: ["getContact"],
}),
// ========[Update Investment]=======
updateContact: builder.mutation({
query: (data) => ({
url: `/contactDetails/admin/`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getContact"],
}),
}),
});
// Export hooks for usage in functional components
export const { useGetContactQuery, useUpdateContactMutation } = contact;

View File

@@ -0,0 +1,57 @@
// investorDetails.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
// Define a service using a base URL and expected endpoints
export const deleteRequest = createApi({
reducerPath: "deleteRequest",
baseQuery: baseQuery,
tagTypes: ["getDeleteRequest", "getDeleteHistory"],
endpoints: (builder) => ({
getDeleteRequest: builder.query({
query: () => `/account/admin/pending-request`,
providesTags: ["getDepositRequest"],
}),
getDeleteRequestById: builder.query({
query: (id) => `/account/admin/detail/${id}`,
}),
approveDepositRequest: builder.mutation({
query: ({ id, data }) => ({
url: `/account/admin/approved-request/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getDeleteRequest", "getDeleteHistory"],
}),
deleteReject: builder.mutation({
query: ({ id, data }) => ({
url: `/deposit/admin/rejected/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getDeleteRequest", "getDeleteHistory"],
}),
getDeleteHistory: builder.query({
query: () => `/account/admin/history`,
providesTags: ["getDeleteHistory"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetDeleteRequestQuery,
useGetDeleteRequestByIdQuery,
useApproveDepositRequestMutation,
useGetDeleteHistoryQuery
} = deleteRequest;

View File

@@ -0,0 +1,55 @@
// investorDetails.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
// Define a service using a base URL and expected endpoints
export const depositRequest = createApi({
reducerPath: "depositRequest",
baseQuery: baseQuery,
tagTypes: ["getDepositRequest", "getDepositHistory"],
endpoints: (builder) => ({
getDepositRequest: builder.query({
query: ({page, size}) => `/deposit/admin/pending-requests?page=${page}&size=${size}`,
providesTags: ["getDepositRequest"],
}),
getDepositRequestById: builder.query({
query: (id) => `/deposit/admin/getById/${id}`,
}),
updateDepositRequest: builder.mutation({
query: ({ id, data }) => ({
url: `/deposit/admin/approved/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getDepositRequest", "getDepositHistory"],
}),
depositReject: builder.mutation({
query: ({ id, data }) => ({
url: `/deposit/admin/rejected/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getDepositRequest", "getDepositHistory"],
}),
getDepositHistory: builder.query({
query: ({page, size}) => `/deposit/admin/history?page=${page}&size=${size}`,
providesTags: ["getDepositHistory"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetDepositRequestQuery,
useGetDepositRequestByIdQuery,
useUpdateDepositRequestMutation,
useDepositRejectMutation,
useGetDepositHistoryQuery,
} = depositRequest;

View File

@@ -0,0 +1,101 @@
//sponser.service
// Need to use the React-specific entry point to import createApi
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { api } from "./api.service";
const baseUrl = api?.defaults.baseURL;
// const baseUrl = `${import.meta.env.VITE_API_BASE_URL}/${import.meta.env.VITE_API_VERSION}`
// Define a service using a base URL and expected endpoints
export const deposite = createApi({
reducerPath: "deposite",
baseQuery: fetchBaseQuery({ baseUrl: baseUrl }),
tagTypes: ["gtDeposite"],
endpoints: (builder) => ({
// ======[Get All]=====
getSponserMaster: builder.query({
query: ({ page, size }) => `/sponsor/admin?page=${page}&size=${size}`,
providesTags: ["getSponser"],
}),
// ========[Get Active]========
getActiveSponserMaster: builder.query({
query: () => `/sponsor/admin/active`,
providesTags: ["getSponser"],
}),
getSponserMasterActive: builder.query({
query: () => "/sponsor/admin/active",
}),
// ======[Get ID]=====
getSponserById: builder.query({
query: (id) => `/sponsor/admin/${id}`,
providesTags: ["getSponser"],
}),
// ========[Toggle Status]========
toggleStatus: builder.mutation({
query: ({ id }) => ({
url: `/sponsor/admin/toggle-status/${id}`,
method: "PATCH",
}),
invalidatesTags: ["getSponser"],
}),
// ========[Create Sponser]========
createSponser: builder.mutation({
query: (data) => ({
url: `/sponsor/admin`,
method: "POST",
body: data,
}),
invalidatesTags: ["getSponser"],
}),
// ========[Update Sponser]========
updateSponser: builder.mutation({
query: ({ data, id }) => ({
url: `/sponsor/admin/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getSponser"],
}),
// ========[Delete Sponser]========
deleteSponser: builder.mutation({
query: (id) => ({
url: `/sponsor/admin/delete/${id}`,
method: "DELETE",
}),
invalidatesTags: ["getSponser"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetSponserMasterQuery,
useGetSponserMasterActiveQuery,
useToggleStatusMutation,
useCreateSponserMutation,
useUpdateSponserMutation,
useGetSponserByIdQuery,
useDeleteSponserMutation,
useGetActiveSponserMasterQuery
} = sponserMaster;

View File

@@ -0,0 +1,56 @@
// investorDetails.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
// Define a service using a base URL and expected endpoints
export const drawalRequest = createApi({
reducerPath: "drawalRequest",
baseQuery: baseQuery,
tagTypes: ["getDrawalRequest", "getDepositHistory"],
endpoints: (builder) => ({
getDrawalRequest: builder.query({
query: () => `/withdrawal/admin`,
providesTags: ["getDrawalRequest"],
}),
getDrawalRequestById: builder.query({
query: (id) => `/withdrawal/admin/getById/${id}`,
}),
updateDrawalRequest: builder.mutation({
query: ({ id, data }) => ({
url: `/withdrawal/admin/updateApprove/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getDrawalRequest", "getDepositHistory"],
}),
depositReject: builder.mutation({
query: ({ id, data }) => ({
url: `/deposit/admin/rejected/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getDepositRequest", "getDepositHistory"],
}),
getDrawalHistory: builder.query({
query: () => `/withdrawal/admin/history`,
providesTags: ["getDepositHistory"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetDrawalRequestQuery,
useUpdateDrawalRequestMutation,
useGetDrawalRequestByIdQuery,
useDepositRejectMutation,
useGetDrawalHistoryQuery
} = drawalRequest;

View File

@@ -0,0 +1,49 @@
// exchangeRate.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
// Define a service using a base URL and expected endpoints
export const exchangeRate = createApi({
reducerPath: "exchangeRate",
baseQuery: baseQuery,
tagTypes: ["getAllExchangeRate", "getExchangeById"],
endpoints: (builder) => ({
getAllExchangeRates: builder.query({
query: ({ page, size }) =>
`/currencyExchange/admin?page=${page}&size=${size}`,
providesTags: ["getAllExchangeRate"],
}),
getExchangeRateById: builder.query({
query: (id) => `/currencyExchange/admin/${id}`,
providesTags: ["getExchangeById"],
}),
updateExchangeRate: builder.mutation({
query: ({ data, id }) => ({
url: `/currencyExchange/admin/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getAllExchangeRate"],
}),
getCurrencyHistoryById: builder.query({
query: (id) => `/currencyExchange/admin/history/${id}`,
providesTags: ["getAllExchangeRate"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetAllExchangeRatesQuery,
useGetExchangeRateByIdQuery,
useUpdateExchangeRateMutation,
useGetCurrencyHistoryByIdQuery,
} = exchangeRate;

View File

@@ -0,0 +1,61 @@
// io.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
export const ioService = createApi({
reducerPath: "ioService",
baseQuery: baseQuery,
tagTypes: ["getInvestmentDocuments"],
endpoints: (builder) => ({
// =====[get]
getInvestmentDocuments: builder.query({
query: ({id}) => `/io/admin/document/${id}`,
providesTags: ["getInvestmentDocuments"],
}),
// =====[create]
createInvestmentDocuments: builder.mutation({
query: ({data, id}) => ({
url: `/io/admin/document/${id}`,
method: "POST",
body: data,
}),
invalidatesTags: ["getInvestmentDocuments"],
}),
updateIO: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getInvestmentDocuments"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useCreateInvestmentDocumentsMutation,
} =
ioService;

View File

@@ -0,0 +1,72 @@
// // investmentType.service.js
// import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// // import { api } from "./api.service";
// import { baseQuery } from "./token.serivce";
// // const baseUrl = api?.defaults.baseURL;
// // Define a service using a base URL and expected endpoints
// export const investmentType = createApi({
// reducerPath: "investmentType",
// baseQuery: baseQuery,
// tagTypes: ["getInvestmentType", "getInvestmentTypeID"],
// endpoints: (builder) => ({
// // ========[Get All]=========
// getInvestmentTypes: builder.query({
// query: ({ page, size }) =>
// `/investmentType/admin?page=${page}&size=${size}`,
// providesTags: ["getInvestmentType"],
// }),
// // ========[Get ID]=========
// getInvestmentTypeById: builder.query({
// query: (id) => `/investmentType/admin/${id}`,
// providesTags: ["getInvestmentTypeID"],
// }),
// // ========[Create Investment]========
// createInvestmentType: builder.mutation({
// query: (data) => ({
// url: `/investmentType/admin/`,
// method: "POST",
// body: data,
// }),
// invalidatesTags: ["getInvestmentType"],
// }),
// // ========[Update Investment]=======
// updateInvestmentType: builder.mutation({
// query: ({ data, id }) => ({
// url: `/investmentType/admin/${id}`,
// method: "PATCH",
// body: data,
// }),
// invalidatesTags: ["getInvestmentTypeID", "getInvestmentType"],
// }),
// // ========[Delete Investment]=======
// deleteInvestmentType: builder.mutation({
// query: (id) => ({
// url: `/investmentType/admin/delete/${id}`,
// method: "DELETE",
// }),
// invalidatesTags: ["getInvestmentType"],
// }),
// }),
// });
// // Export hooks for usage in functional components
// export const {
// useGetInvestmentTypesQuery,
// useGetInvestmentTypeByIdQuery,
// useCreateInvestmentTypeMutation,
// useUpdateInvestmentTypeMutation,
// useDeleteInvestmentTypeMutation,
// } = investmentType;

View File

@@ -0,0 +1,42 @@
// investorDetails.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// import { api } from "./api.service";
import { baseQuery } from "./token.serivce";
// const baseUrl = api?.defaults.baseURL;
// Define a service using a base URL and expected endpoints
export const investorDetails = createApi({
reducerPath: "investorDetails",
baseQuery: baseQuery,
tagTypes: [],
endpoints: (builder) => ({
getInvestors: builder.query({
query: ({ page, size }) =>
`/investorDetails/admin?page=${page}&size=${size}`,
providesTags: ["getInvestors"],
}),
// =====[get investment details ]
getInvestorsDetailsById: builder.query({
query: (id) => `/investorDetails/admin/byId/${id}`,
providesTags: ["getInvestors"],
}),
// =====[get investment details ]
getInvestorsDetailsById_InInvCur: builder.query({
query: ({ id, currencyIn }) =>
currencyIn
? `/investorDetails/admin/byId/${id}?currencyIn=InvCur`
: `/investorDetails/admin/byId/${id}`,
providesTags: ["getInvestors"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetInvestorsQuery,
useGetInvestorsDetailsByIdQuery,
useGetInvestorsDetailsById_InInvCurQuery,
} = investorDetails;

Some files were not shown because too many files have changed in this diff Show More