Lets Go 🎉

This commit is contained in:
2024-09-03 12:40:08 +05:30
parent b4c3e5bc6d
commit 5cef911f02
150 changed files with 18667 additions and 0 deletions

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="https://www.wdipl.com/public/img/wdi_logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Webzilla</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>

6470
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": "tanami-admin-dashboard",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@chakra-ui/icons": "^2.1.1",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@hookform/resolvers": "^3.3.4",
"@reduxjs/toolkit": "^2.2.3",
"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

486
src/App.css Normal file
View File

@@ -0,0 +1,486 @@
@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; */
}
.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: #E5195E;
color: #fff;
/* background-color: #ced8e6a2; */
}
.link {
text-decoration: none;
transition: all 0.2s ease-in-out;
font-weight: 400;
}
.link:hover {
background-color: #ced8e6a2;
/* color: #fff; */
/* background-color: #e2e8f01c !important; */
}
.active:hover {
background-color: #E5195E;
/* color: #fff; */
/* background-color: #ced8e6a2 !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: #E5195E !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;
}
}

76
src/App.jsx Normal file
View File

@@ -0,0 +1,76 @@
import React, { useContext, useEffect, useState } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.bundle.min.js";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
} from "react-router-dom";
import "./App.css"; // Import CSS file
import DefaultLayout from "./Layout/DefaultLayout";
import NotFound from "./Pages/NotFound";
import Login from "./Pages/Login";
import GlobalStateContext from "./Contexts/GlobalStateContext";
import Cookies from "js-cookie";
import NoInternetScreen from "./Pages/NoInternetScreen";
const App = () => {
// const { isAuthenticate } = useSelector((state) => state?.auth);
const { isAuthenticate } = useContext(GlobalStateContext);
const isAuthenticatedInCookie = Cookies.get("isAuthenticated");
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnlineStatusChange = () => {
setIsOnline(navigator.onLine);
};
window.addEventListener("online", handleOnlineStatusChange);
window.addEventListener("offline", handleOnlineStatusChange);
return () => {
window.removeEventListener("online", handleOnlineStatusChange);
window.removeEventListener("offline", handleOnlineStatusChange);
};
}, []);
// const 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="/*"
element={
// isOnline ? (
isAuthenticate || isAuthenticatedInCookie === "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,25 @@
import Loader01 from "../Loaders/Loader01";
const Button01 = ({ title, onClick, type, backgroundColor, hover, isLoading }) => {
return (
<button
style={{
backgroundColor: backgroundColor,
outline: "none",
border: "none",
height: 50,
transition: "opacity 0.3s", // Add transition for smooth hover effect
}}
type={type}
onClick={onClick ? onClick : null}
className=" rounded-3 p-2 w-100 text-white p-3 fs-6 fw-bold border-none"
onMouseEnter={(e) => (e.target.style.backgroundColor = hover)}
onMouseLeave={(e) => (e.target.style.backgroundColor = backgroundColor)}
>
{isLoading ? <Loader01/> : title}
</button>
);
};
export default Button01;

View File

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

View File

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

View File

@@ -0,0 +1,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 = [
"Sponsorer 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,
"Sponsorer 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: "Sponsorer 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,41 @@
import React, { forwardRef } from 'react';
import { Input } from "@chakra-ui/react";
export const formatCurrency = (value) => {
if (!value) return '';
const [integer, decimal] = value.split('.');
const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return decimal ? `${formattedInteger}.${decimal}` : formattedInteger;
};
const CurrencyInput = forwardRef(({ value, onChange, ...props }, ref) => {
console.log(props);
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('');
}
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="gray.50">
<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" : "white"}
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 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,112 @@
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,
}) => {
console.log(data);
const columnWidth =
data && data[0]
? `${(100 / Object.keys(data[0]).length).toFixed(2)}%`
: "auto";
return (
<TableContainer overflowX={"hidden"} className="h-auto mb-3 w-100">
{data?.length === 0 ? (
<EmptySearchList message={emptyMessage} />
) : (
<Table size="sm">
<TableCaption>Tanami v1.0.0</TableCaption>
<Thead backgroundColor="gray.50">
<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 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 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" ? false: 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={
" 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
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,123 @@
import { AddIcon, ArrowBackIcon } from "@chakra-ui/icons";
import {
Avatar,
Box,
Button,
HStack,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
PopoverFooter,
PopoverTrigger,
Portal,
Text,
useColorMode,
} from "@chakra-ui/react";
import React, { useContext } from "react";
import { Link, useNavigate } from "react-router-dom";
import { IoMdDownload } from "react-icons/io";
import * as XLSX from "xlsx";
import profile from "../assets/proavatar.webp";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import { MdOutlineDarkMode, MdOutlineLightMode } from "react-icons/md";
import logoMini from "../assets/propic.png"
import { BsBack } from "react-icons/bs";
const HeaderMain = ({
link,
btnTitle,
title,
icon,
logOutHandler,
slideDirecttion,
}) => {
const navigate = useNavigate()
const { colorMode, toggleColorMode } = useContext(GlobalStateContext);
return (
<Box
className={` pt-2 pb-2 fw-400 border-bottom d-flex ${
slideDirecttion ? "flex-row-reverse ps-2" : ""
} justify-content-between align-items-center`}
// boxShadow={"0 0px 8px rgba(0, 0, 0, 0.2)"}
zIndex={999}
>
<HStack>
{/* <ArrowBackIcon onClick={()=>navigate(-1)} /> */}
<Text
as={"span"}
fontWeight={"500"}
// color={"forestGreen.500"}
color={'black.100'}
fontSize={'sm'}
>
{/* <icon /> */}
{title}
</Text>
</HStack>
<Box me={4} gap={2} className="d-flex justify-content-center ">
<Popover placement="bottom">
<Portal>
<PopoverContent maxW="200px" className="">
<PopoverArrow />
<PopoverBody className="web-text-medium pointer link">
Profile
</PopoverBody>
<Link to={"/help-and-support"}>
<PopoverBody className="web-text-medium pointer ">
Help & Support
</PopoverBody>
</Link>
<PopoverFooter
onClick={logOutHandler}
className="web-text-medium pointer link"
>
Log Out
</PopoverFooter>
</PopoverContent>
</Portal>
<PopoverTrigger>
<Box
// onClick={logOutHandler}
className="d-flex pointer align-items-center"
>
<Avatar
size="sm"
boxSize={37}
// name="D"
// src={logoMini}
bg={"#000"}
// p={1}
/>
<Box
style={{
display: "flex",
}}
className=" overflow-hidden ms-3 flex-column "
>
<Text as={"span"} className="web-text-small">
Hello, Developers
</Text>
<Text as={"span"} className="web-text-xsmall">
wdi@tanami.com
</Text>
</Box>
</Box>
</PopoverTrigger>
</Popover>
{/* <Box onClick={() => toggleColorMode()} as="span" p={2} rounded={'lg'} className="link pointer">
{colorMode === "light"? <MdOutlineDarkMode /> :<MdOutlineLightMode />}
</Box> */}
</Box>
</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={"https://tanami.betadelivery.com/" + 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: #E5195E;
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,228 @@
import {
Box,
Button,
Heading,
HStack,
Image,
Modal,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Progress,
Stack,
Text,
} from "@chakra-ui/react";
import React 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";
const MobileView = ({ isOpen, onClose, finalRef }) => {
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={"330px"}
>
<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={"30px"}
px={"15px"}
>
<Box
p={"10px"}
overflowY={"scroll"}
h={"483px"}
zIndex={"99"}
position={"relative"}
borderBottomLeftRadius={"23px"}
borderBottomRightRadius={"23px"}
>
<Box
mb={4}
bg={"#f5f8f6"}
borderRadius={"20px"}
boxShadow={
"rgba(0, 0, 0, 0.15) 0px 2px 8px"
}
>
<Box>
<Box display={"flex"} justifyContent={"space-between"} alignItems={"center"}>
<Text fontSize={"xs"} fontWeight={500} color="green">Stock</Text>
<Text fontSize={"xs"} display={"flex"} alignItems={"center"} fontWeight={500}><LuClock /> Closing Date Aug 23 2024</Text>
</Box>
<Image
borderTopLeftRadius={"20px"}
borderTopRightRadius={"20px"}
h={"130px"}
w={"100%"}
src={mobileBanner}
/>
</Box>
<Stack mt="3" bg={"#fff"} py={4} px={4}>
<Text
fontSize={"sm"}
fontWeight={"500"}
color={"#000"}
mb={0}
>
Guinevere Gates
</Text>
<Heading fontSize="16px" color={"#004717"}>
BHD 46,258
</Heading>
<Progress
colorScheme="green"
size="sm"
value={20}
borderRadius={"3px"}
/>
<Text fontSize={"xs"} fontWeight={500} mb={0}>
0.0% funded
</Text>
<Text fontSize={"sm"} fontWeight={500} mb={0}>
fugit eligendi dolore dolore et
</Text>
</Stack>
<Box py={4} px={4}>
<Box display={"flex"} justifyContent={"space-between"}>
<Text fontSize={"xs"} mb={1} fontWeight={600}>
Sponsor name:
</Text>
<Text fontSize={"xs"} mb={1} fontWeight={600}>
Scott Simon
</Text>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Text fontSize={"xs"} mb={1} fontWeight={600}>
Estimated return:
</Text>
<Text fontSize={"xs"} mb={1} fontWeight={600}>
A provident veniam
</Text>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Text fontSize={"xs"} mb={1} fontWeight={600}>
Hoiding period:
</Text>
<Text fontSize={"xs"} mb={1} fontWeight={600}>
Eius eiusmod exericit
</Text>
</Box>
<Box display={"flex"} justifyContent={"space-between"}>
<Text fontSize={"xs"} mb={1} fontWeight={600}>
Minimum investment:
</Text>
<Text fontSize={"xs"} mb={1} fontWeight={600}>
BHD 1
</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="15px" fontWeight={600}>Key Merits</Heading>
<Box display={"flex"} alignItems={"center"}>
<Image
width={"30px"}
me={2}
src="https://tanami.betadelivery.com/public/icons/icon3.svg"
/>
<Text fontSize={"xs"} mb={0}>
Sit sunt consequunt Dolores minim suscip
</Text>
</Box>
</Box>
<Box
mb={4}
p={5}
borderRadius={"20px"}
boxShadow={
"rgba(0, 0, 0, 0.15) 0px 2px 8px"
}
>
<Heading fontSize="15px" fontWeight={600}>Investment Documents</Heading>
<Box bg={"#f5f8f6"} w={"150px"} p={3} borderRadius={"10px"}>
<Box display={"flex"} alignItems={"center"} mb={2}>
<Image src="https://tanami.betadelivery.com/public/icons/icon8.svg" />
<Text fontSize={"xs"} mb={0}>
Merrill Rocha
</Text>
</Box>
<Box
display={"flex"}
alignItems={"center"}
justifyContent={"space-between"}
>
<Text mb={0} fontSize={"sm"}>
0.03 mb
</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="15px" fontWeight={600}>Videos</Heading>
<video autoPlay style={{borderRadius:"18px"}}>
<source src="https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4" type="video/mp4" />
</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";
const ToastBox = ({ message, status }) => {
return (
<Box
className="web-text-large d-flex gap-2 align-items-center"
p={3}
color={status === "error" ? "red.500" : status === "warn" ? "blue.500" : "green.500"}
bg={"#fff"}
boxShadow={'md'}
rounded={'sm'}
>
{status === "error" || status === "warn" ? <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

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

@@ -0,0 +1,132 @@
import dns from "node:dns"
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 `;
}
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);
}

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,672 @@
import React, { useContext, useEffect, useState } from "react";
import logo from "../assets/logo2.png";
import logoDark from "../assets/logo.png";
import logoMini from "../assets/logo-min.png";
import logoMiniDark from "../assets/favicon.png";
import { useDispatch } from "react-redux";
import { loginUser } from "../Redux/Slice/auth";
import Button02 from "../Components/Buttons/Button02";
import {
TbArrowBadgeLeftFilled,
TbListDetails,
TbReportMoney,
TbTransactionDollar,
} from "react-icons/tb";
import { TbArrowBadgeRightFilled } from "react-icons/tb";
import { ArrowBackIcon, ArrowLeftIcon, ArrowRightIcon } from "@chakra-ui/icons";
import {
Link,
NavLink,
Route,
Routes,
useLocation,
useNavigate,
} from "react-router-dom";
import { RouteLink } from "../Routes/Routes";
import NotFound from "../Pages/NotFound";
import { nav } from "../Routes/Nav";
import {
Avatar,
Box,
Button,
PopoverArrow,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
Portal,
Text,
WrapItem,
Popover,
Tag,
Accordion,
AccordionItem,
AccordionButton,
AccordionIcon,
AccordionPanel,
Image,
Alert,
AlertIcon,
Breadcrumb,
Divider,
} from "@chakra-ui/react";
import GlobalStateContext from "../Contexts/GlobalStateContext";
import Cookies from "js-cookie"; // Import the Cookies library
import Header from "../Components/Header";
import HeaderMain from "../Components/HeaderMain";
import { IoMdSwap } from "react-icons/io";
import {
RiBankLine,
RiExchangeBoxLine,
RiFileUserLine,
RiMoneyDollarBoxLine,
} from "react-icons/ri";
import { VscSymbolClass } from "react-icons/vsc";
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 shield from "../assets/shield.png";
import SplashScreen from "../Pages/SplashScreen";
import CutomBreadcrumb from "../Components/CutomBreadcrumb";
import CustomBreadcrumb from "../Components/CutomBreadcrumb";
import { getCountdownTimer } from "../Constants/Constants";
import { FiHome } from "react-icons/fi";
const DashboardLayout = ({ isOnline }) => {
const navigate = useNavigate();
const dispach = useDispatch();
const location = useLocation();
const path = location.pathname;
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [openDrawerClick, setOpenDrawerClick] = useState(true);
const {
setIsAuthenticate,
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 openDrawerOnClick = () => {
setOpenDrawerClick(!openDrawerClick);
};
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">
<FiHome className="fs-5" /> Home
</span>
}
};
if (isSplashVisible) {
return <SplashScreen />;
}
return (
<Box
style={{
height: "100vh",
width: "100vw",
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, red.500, yellow.500, red.500)"
// bgGradient='linear(to-r, #1EBCA3, #22CAB3)'
color={"white"}
fontWeight={600}
fontSize={"md"}
>
<AlertIcon color={"white"} />
No Internet !
</Alert>
{/* <Box
bottom={4}
right={!slideFromRight ? 4 : "auto"}
left={slideFromRight ? 4 : "auto"}
backgroundColor={"#fff"}
rounded={"full"}
p={2}
w={8}
h={8}
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
color={"#004118"}
fontWeight={"800"}
cursor={"pointer"}
position={"absolute"}
transition={"0.5s"}
boxShadow={"md"}
onClick={() => setSlideFormRight(!slideFromRight)}
_hover={{
opacity: 1,
}}
zIndex={999}
>
<IoMdSwap />
</Box> */}
{slideFromRight ? null : (
<aside
className="h-100 position-relative sideBar "
// onMouseOver={() => setIsDrawerOpen(true)}
// onMouseLeave={() => setIsDrawerOpen(false)}
style={{
width: isDrawerOpen || openDrawerClick ? 230 : 74,
transition: "width 0.3s ease-in-out", // Smooth transition for width change
// overflow: "hidden",
backgroundColor: "#f1f2f6",
position: "relative",
// backgroundColor: "#002F0F",
}}
>
<div
className={`d-flex ${
isDrawerOpen || openDrawerClick
? "justify-content-start"
: "justify-content-center"
} p-3 pt-3 pb-3 position-relative `}
height={"10%"}
>
{isDrawerOpen || openDrawerClick ? (
<Image
style={{
width: 30,
}}
src={"https://www.wdipl.com/public/img/black_logo.svg"}
alt="Logo"
onClick={()=> navigate('/')}
cursor={"pointer"}
/>
) : (
<Image
style={{
width: 30,
}}
src={"https://www.wdipl.com/public/img/black_logo.svg"}
alt="Logo"
onClick={()=> navigate('/')}
cursor={"pointer"}
/>
)}
</div>
<Box
className="ps-2 scroll-bar pe-1"
style={{
height: "90%",
overflowY: "scroll",
overflowX: "hidden",
paddingBottom: "5rem",
}}
>
<Accordion
m={0}
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"}>
<AccordionButton
style={{ height: "auto" }}
_hover={{bg:"#ced8e6a2"}}
className={`${
isDrawerOpen || openDrawerClick
? "p-2 web-text-medium ps-3 justify-content-between"
: "p-2 ps-1 web-text-xlarge justify-content-center"
} rounded-1 link d-flex align-items-center gap-2 w-100 mb-1`}
>
<Box
as="span"
display={"flex"}
gap={2}
alignItems={"center"}
>
{/* {Icon && title === "Admin" ? <Image w={15} src={shield} /> : <Icon className={`web-text-large`} />} */}
{Icon && (
<Icon
fontSize={title === "Admin" ? "18px" : "15px"}
/>
)}
<Text
as={"span"}
display={
isDrawerOpen || openDrawerClick ? "flex" : "none"
}
alignItems="center"
overflow="hidden"
textAlign={"left"}
>
{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={`${
isDrawerOpen || openDrawerClick
? " web-text-medium ps-4"
: " web-text-xlarge justify-content-center"
} d-flex align-items-center p-0`}
>
<Box
backgroundColor={"gray.300"}
style={{
position: "absolute",
top: 0,
width: 2,
left: 22,
height:
i === submenu?.length - 1 ? "55%" : "120%",
borderRadius: "0 0 10px 10px",
}}
/>
<Box
backgroundColor={"gray.300"}
style={{
position: "absolute",
width: 10,
left: 22,
height: 2,
}}
/>
<NavLink
className={`${
isDrawerOpen || openDrawerClick
? "p-2 ps-1 ms-2 web-text-medium "
: "p-2 ps-0 ms-0 zindex-3 ms-4 web-text-xlarge justify-content-center"
} rounded-1 link d-flex align-items-center gap-2 w-100 `}
to={link}
>
{SubIcon && (
<SubIcon
className="web-text-large ms-2"
style={{ zIndex: 111, color:colorCode?colorCode:"" }}
/>
)}
<Text
as={"span"}
display={
isDrawerOpen || openDrawerClick
? "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"
>
{title}
</Text>
);
} else if (type === "single") {
return (
<NavLink
key={index}
style={{ height: "auto", position: "relative" }}
className={`${
isDrawerOpen || openDrawerClick
? "p-2 web-text-medium"
: "p-2 ps-0 web-text-xlarge justify-content-start"
} rounded-1 link d-flex align-items-center gap-2 w-100 mb-2`}
to={path}
>
{Icon && <Icon className="web-text-large ms-2" />}
<Text
as={"span"}
display={
isDrawerOpen || openDrawerClick ? "flex" : "none"
}
alignItems="center"
overflow="hidden"
>
{title}
</Text>
</NavLink>
);
} else {
return null;
}
})}
</Accordion>
</Box>
{/* <Button
colorScheme={"forestGreen"}
rounded={"lg"}
// onMouseOver={() => setIsDrawerOpen(true)}
// onMouseLeave={() => setIsDrawerOpen(false)}
onClick={openDrawerOnClick}
style={{
width: 18,
height: 26,
position: "absolute",
right: -19,
bottom: 28,
zIndex: 99,
}}
>
{isDrawerOpen || openDrawerClick ? (
<ArrowLeftIcon className="web-text-small" />
) : (
<ArrowRightIcon className="web-text-small " />
)}
</Button> */}
<Text textAlign={'center'} fontWeight={500} fontSize={'xs'} color={"gray.600"}>{getCountdownTimer(localStorage.getItem('accessTokenExp'))}</Text>
</aside>
)}
<main
className={`h-100 ${slideFromRight ? "pe-3" : "ps-3"} d-flex flex-column gap-0`}
style={{
width: `calc(100% - ${isDrawerOpen || openDrawerClick ? 230 : 74}px)`,
transition: "width 0.3s ease-in-out",
}}
>
{/* <header className="p-2 ps-0 pt-3 fw-400 border-bottom">
<span className="fs-5">{getTitle()}</span>
</header> */}
<HeaderMain
slideDirecttion={slideFromRight}
logOutHandler={logOutHandler}
icon
title={getTitle()}
/>
{/* <CustomBreadcrumb /> */}
<AppContent />
</main>
</Box>
);
};
export default DashboardLayout;
const AppContent = () => {
return (
<Routes>
{RouteLink.map(({ path, Component }, index) => (
<Route key={index} path={path} element={<Component />} />
))}
<Route path="*" element={<NotFound />} />
</Routes>
);
};

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

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

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

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

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

@@ -0,0 +1,302 @@
import { useNavigate } from "react-router-dom";
import Input01 from "../Components/Inputs/Input01";
import logo from "../assets/logo2.png";
import { useDispatch, useSelector } from "react-redux";
import { loginUser } from "../Redux/Slice/auth";
import { useContext, useEffect, useState } from "react";
import Button01 from "../Components/Buttons/Button01";
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
import { TiWarning } from "react-icons/ti";
import Loader01 from "../Components/Loaders/Loader01";
import Asset1 from "../assets/asset1.png";
import Asset2 from "../assets/asset2.png";
import {
Box,
Button,
FormControl,
FormLabel,
Image,
Input,
InputGroup,
InputRightElement,
Spinner,
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 { isAuthenticate } = useSelector((state) => state?.auth);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const [login] = useLoginMutation()
useEffect(() => {
if (isAuthenticate) {
navigate("/");
}
}, [navigate, isAuthenticate]);
const onSubmit = async (value) => {
setIsLoading(true);
if (value.emailAddress === "admin@wdi.com" && value.password_hash === "Admin@123") {
return setTimeout(() => {
// dispatch(loginUser(true));
setIsAuthenticate(true);
setIsLoading(false);
toast({
// position: "bottom-left",
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 (
<div
style={{
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
overflow: "hidden",
backgroundColor: "#f1f2f6",
flexDirection:"column",
gap:30
}}
className="rubix-primary"
>
<Box width={"450px"} display={'flex'} justifyContent={'center'}>
<Image
w={300}
src={"https://www.wdipl.com/public/img/black_logo.svg"}
alt="img"
/></Box>
<Box
as="form"
onSubmit={handleSubmit(onSubmit)}
style={{
width: "90%",
maxWidth: "450px",
height: "auto",
background: "#fff",
// borderRadius: "10px",
padding: "1.5rem",
// boxShadow: "0 24px 64px #26214a1a",
boxShadow:
"rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px",
zIndex: 2,
}}
rounded={'sm'}
>
{/* <div className="d-flex flex-column"> */}
<Text fontSize={'lg'} fontWeight={600} color={'gray.900'} className="mt-2 rubix-text-dark text-center">
LOGIN
</Text>
{/* <span className="fw-500 web-text-large text-secondary text-start">
Login to manage tanami.
</span> */}
{/* </div> */}
<FormControl className=" mb-3">
<FormLabel className="rubix-text-dark ps-1 web-text-medium fw-bold">
E-mail <span className="text-danger">*</span>
</FormLabel>
<Input
{...register("emailAddress")}
focusBorderColor="#E5195E"
rounded={'sm'}
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 className="mb-4">
<FormLabel className="rubix-text-dark ps-1 web-text-medium fw-bold">
Password <span className="text-danger">*</span>
</FormLabel>
<InputGroup size="lg">
<Input
{...register("password_hash")}
className="web-text-medium"
focusBorderColor="#E5195E"
rounded={'sm'}
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={"#E5195E"}
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}
spinner={<Spinner />}
type="submit"
className="w-100 primary-btn"
color={"whitesmoke"}
// colorScheme="red.500"
size="lg"
rounded={'sm'}
>
Log In
</Button>
</Box>
<div
style={{
position: "absolute",
bottom: "0%",
fontSize: "13px",
color: "#919191",
textAlign: "center",
width: "100%",
zIndex: 2,
}}
>
WDI v1.0.0
</div>
{/* <img
style={{
position: "absolute",
top: 0,
right: 0,
// width:100
}}
src={Asset1}
alt="bg-img"
/>
<img
style={{
position: "absolute",
top: 0,
right: 0,
// width:400
}}
src={Asset2}
alt="bg-img"
/> */}
{/* <img
style={{
position: "absolute",
right: 0,
bottom: 0,
width: 150,
}}
src={Asset1}
alt="bg-img"
/> */}
{/* <img
style={{
position: "absolute",
top: 0,
left: 0,
}}
src={Asset1}
alt="bg-img"
/> */}
{/* <img
style={{
position: "absolute",
top: 0,
left: 0,
}}
src={Asset2}
alt="bg-img"
/> */}
</div>
);
};
export default Login;

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/commingsoon2.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={"500px"} />
<Text color={'green.800'} as={'span'} fontSize={'small'}>🚧 Developing something amazing for you. 🚧</Text>
</Box>
)
}
export default NotFound

View File

@@ -0,0 +1,31 @@
import { Box, Image, Spinner, Text } from '@chakra-ui/react'
import React from 'react'
import logo from '../assets/logo2.png'
const SplashScreen = () => {
return (
<Box
h={'100vh'}
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
flexDirection={'column'}
gap={10}
>
<Image width={40} src={"https://www.wdipl.com/public/img/black_logo.svg"} />
{/* <Spinner color='green.900' size='md' /> */}
{/* <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> */}
</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

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

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

@@ -0,0 +1,339 @@
import { HiOutlineNewspaper } from "react-icons/hi";
import { TbBrandMedium, TbChartHistogram, TbLayoutDashboard, TbReportMoney, TbSquareRoundedFilled } from "react-icons/tb";
import {
RiBankLine,
RiFileUserLine,
RiMoneyDollarBoxLine,
} from "react-icons/ri";
import { RiExchangeBoxLine } from "react-icons/ri";
import { VscGitPullRequestGoToChanges, VscSymbolClass } from "react-icons/vsc";
import { FiHome, FiUsers } from "react-icons/fi";
import { PiCrown } from "react-icons/pi";
import { MdOutlineAddChart, MdOutlineAdminPanelSettings, MdOutlineEditCalendar, MdOutlinePolicy, MdOutlineTaskAlt, MdWorkspacesOutline } 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 { BiMoneyWithdraw } from "react-icons/bi";
import { GrDocumentUpdate, GrManual, GrNotification } 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 { IoMdNotificationsOutline } from "react-icons/io";
import { BellIcon } from "@chakra-ui/icons";
export const nav = [
// {
// title: "My menu",
// type: "title",
// },
{
title: "Home",
type: "single",
path: "/",
Icon: FiHome,
},
{
title: "My tasks",
type: "single",
path: "/task",
Icon: MdOutlineTaskAlt,
},
{
title: "Notification",
type: "single",
path: "/notification",
Icon: GrNotification,
},
// {
// title: "Projects",
// type: "title",
// },
{
title: "Projects",
submenu: [
{
title: "Tanami",
path: "/tanami",
icon: TbSquareRoundedFilled,
colorCode:"#70a1ff"
},
{
title: "ReGroup",
path: "/ReGroup",
icon: TbSquareRoundedFilled,
colorCode:"#7bed9f"
},
{
title: "Woka",
path: "/Woka",
icon: TbSquareRoundedFilled,
colorCode:"#eccc68"
},
],
type: "accordion",
Icon: MdWorkspacesOutline,
},
{
title: "Officials",
type: "title",
},
{
title: "Leaves",
type: "single",
path: "/leaves",
Icon: MdOutlineEditCalendar,
},
{
title: "Policy",
type: "single",
path: "/policy",
Icon: MdOutlinePolicy,
},
// {
// title: "IO Management",
// submenu: [
// {
// title: "Create IO",
// path: "/create-io",
// icon: MdOutlineAddChart,
// },
// {
// title: "View IO",
// path: "/view-io",
// icon: HiOutlineChartSquareBar,
// },
// ],
// type: "accordion",
// Icon: TbDeviceDesktopDollar,
// },
// {
// title: "Investor Management",
// 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: "Investor Upgradation",
// submenu: [
// {
// title: "Pending Request",
// path: "/investor-request",
// icon: RiMoneyDollarBoxLine,
// },
// {
// title: "View History",
// path: "/investor-history",
// icon: RiExchangeBoxLine,
// },
// ],
// type: "accordion",
// Icon: MdBrowserUpdated,
// },
// {
// title: "Account Deletion",
// 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: "Single Link",
// type: "single",
// path: "/logout",
// 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;

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

@@ -0,0 +1,6 @@
import Dashbaord from "../Pages/Dashbaord";
export const RouteLink = [
{ path: "/", Component: Dashbaord },
];

View File

@@ -0,0 +1,26 @@
// 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"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetKeyMeritsQuery,
} =
keyMerits;

View File

@@ -0,0 +1,66 @@
import axios from "axios";
// Create an Axios instance for API calls
export const api = axios.create({
// baseURL: `https://tanami.betadelivery.com/api/v1`,
baseURL: `https://sprint4.tanami.betadelivery.com/api/v1`, // Replace with your API base URL
timeout: 10000, // Adjust timeout as needed
headers: {
"Content-Type": "application/json",
},
});
// Add Axios request interceptor to refresh token if expired
api.interceptors.request.use(
(config) => {
console.log(config);
// Modify headers or add tokens as needed
// const token = localStorage.getItem("accessToken");
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
return config;
},
(error) => {
return Promise.reject(error);
}
);
// // Add Axios response interceptor to handle token refreshing
api.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
// Example logic for handling token expiration and refreshing
if (
error.response.status === 401 &&
!originalRequest._retry &&
localStorage.getItem("refreshToken")
) {
originalRequest._retry = true;
try {
const response = await api.post("/refresh_token", {
refreshToken: localStorage.getItem("refreshToken"),
});
if (response.status === 200) {
// Update tokens in local storage
localStorage.setItem("accessTokenn", response.data.accessToken);
localStorage.setItem("refreshTokenn", response.data.refreshToken);
// Retry the original request with the new tokens
return api(originalRequest);
}
} catch (error) {
console.error("Failed to refresh token:", error);
// Handle token refresh failure (e.g., redirect to login)
}
}
return Promise.reject(error);
}
);

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 bankDetails = createApi({
reducerPath: "bankDetails",
baseQuery: baseQuery,
tagTypes: ["getBank"],
endpoints: (builder) => ({
getBank: builder.query({
query: ({ page, size }) =>
`/bankDetails/admin/?page=${page}&size=${size}`,
providesTags: ["getBank"],
}),
// ========[Update Sponser]========
updateBankDetails: builder.mutation({
query: ({ data, id }) => ({
url: `/bankDetails/admin/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getBank"],
}),
}),
});
// Export hooks for usage in functional components
export const { useGetBankQuery,useUpdateBankDetailsMutation } = 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,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: () => `/deposit/admin/pending-requests`,
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: () => `/deposit/admin/history`,
providesTags: ["getDepositHistory"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetDepositRequestQuery,
useGetDepositRequestByIdQuery,
useUpdateDepositRequestMutation,
useDepositRejectMutation,
useGetDepositHistoryQuery,
} = depositRequest;

View File

@@ -0,0 +1,51 @@
// 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: ["getAllExchangeRate"],
}),
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,33 @@
// 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/${id}`,
providesTags: ["getInvestors"],
}),
}),
});
// Export hooks for usage in functional components
export const { useGetInvestorsQuery, useGetInvestorsDetailsByIdQuery } = investorDetails;

View File

@@ -0,0 +1,23 @@
// investorTransaction.service.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./token.serivce";
// const baseUrl = import.meta.env.VITE_API_BASE_URL + "/api";
// Define a service using a base URL and expected endpoints
export const investorTransaction = createApi({
reducerPath: "investorTransaction",
baseQuery: baseQuery,
tagTypes: [],
endpoints: (builder) => ({
getTransactions: builder.query({
query: () => '/getTransactions',
}),
getTransactionById: builder.query({
query: (id) => `/getTransaction/${id}`,
}),
}),
});
// Export hooks for usage in functional components
export const { useGetTransactionsQuery, useGetTransactionByIdQuery } = investorTransaction;

303
src/Services/io.service.js Normal file
View File

@@ -0,0 +1,303 @@
// 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;
// Define a service using a base URL and expected endpoints
export const ioService = createApi({
reducerPath: "ioService",
baseQuery: baseQuery,
tagTypes: [
"prePopulate",
"getIO",
"getKeyMerits",
"getArtifactsVideo",
"getInvestmentDocuments",
"getIOById",
],
endpoints: (builder) => ({
// =====[get prepopulate data]
getIOprepopulateData: builder.query({
query: () => `/io/admin/pre-populate`,
providesTags: ["prePopulate"],
}),
// =====[get]
getIOs: builder.query({
query: ({ page, size }) => `/io/admin?page=${page}&size=${size}`,
providesTags: ["getIO"],
}),
getIOById: builder.query({
query: (id) => ({ url: `/io/admin/${id}` }),
providesTags: ["getIOById"],
}),
// =====[create]
createIO: builder.mutation({
query: (data) => ({
url: `/io/admin`,
method: "POST",
body: data,
}),
invalidatesTags: ["getIO"],
}),
updateIO: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getIOById", "getIO"],
}),
// =====[Key Merits]
getKeyMerits: builder.query({
query: (id) => `/io/admin/key-merits/${id}`,
providesTags: ["getKeyMerits"],
}),
createKeyMerits: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/key-merits/${id}`,
method: "POST",
body: data,
// No need to manually set 'Content-Type'
}),
invalidatesTags: ["getIOById"],
}),
deleteKeyMerits: builder.mutation({
query: (id) => ({
url: `/io/admin/key-merits/hard-delete/${id}`,
method: "DELETE",
}),
invalidatesTags: ["getIOById"],
}),
updateKeyMerits: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/key-merits/byId/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
// =====[getIODocument]
createInvestmentDocuments: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/document/${id}`,
method: "POST",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
updateInvestmentDocuments: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/document/byId/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
getInvestmentDocuments: builder.query({
query: (id) => `/io/admin/document/${id}`,
providesTags: ["getInvestmentDocuments"],
}),
deleteIODocs: builder.mutation({
query: (id) => ({
url: `/io/admin/document/hard-delete/${id}`,
method: "DELETE",
}),
invalidatesTags: ["getIOById"],
}),
// =====[Artifacts]
getArtifactsVideo: builder.query({
query: (id) => `/io/artifact/artifactVideo/${id}`,
providesTags: ["getArtifactsVideo"],
}),
// =====[createImageArtifacts]
createImageArtifacts: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/artifact/image/${id}`,
method: "POST",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
updateImageArtifacts: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/artifact/image/byId/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
// =====[createVideoArtifacts]
createVideoArtifacts: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/artifact/video/${id}`,
method: "POST",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
deleteVideoArtifacts: builder.mutation({
query: (id) => ({
url: `/io/admin/artifact/video/byId/${id}`,
method: "DELETE",
}),
invalidatesTags: ["getIOById"],
}),
deleteImageArtifacts: builder.mutation({
query: (id) => ({
url: `/io/admin/artifact/image/byId/${id}`,
method: "DELETE",
}),
invalidatesTags: ["getIOById"],
}),
updateVideoArtifacts: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/artifact/video/byId/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
setDisplayOrder: builder.mutation({
query: ({ data }) => ({
url: `/io/artifact/artifactImage/resetDisplayOrder/`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
updateStatusIo: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/update-status/${id}`,
method: "PATCH",
body: data,
}),
invalidatesTags: ["getIOById", 'getIO'],
}),
createIoCash: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/io-cash/${id}`,
method: "POST",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
createIoNav: builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/io-nav/${id}`,
method: "POST",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
// =====[ Amount Investment ]
amountIvestment : builder.mutation({
query: ({ data, id }) => ({
url: `/io/admin/amount-invested/${id}`,
method: "POST",
body: data,
}),
invalidatesTags: ["getIOById"],
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetIOprepopulateDataQuery,
useGetIOsQuery,
useGetIOByIdQuery,
useCreateIOMutation,
useUpdateIOMutation,
useGetKeyMeritsQuery,
useCreateKeyMeritsMutation,
useDeleteKeyMeritsMutation,
useUpdateKeyMeritsMutation,
useGetInvestmentDocumentsQuery,
useCreateInvestmentDocumentsMutation,
useDeleteIODocsMutation,
useUpdateInvestmentDocumentsMutation,
useCreateImageArtifactsMutation,
useUpdateImageArtifactsMutation,
useUpdateVideoArtifactsMutation,
useGetArtifactsVideoQuery,
useCreateVideoArtifactsMutation,
useDeleteVideoArtifactsMutation,
useDeleteImageArtifactsMutation,
useSetDisplayOrderMutation,
useCreateIoCashMutation,
useCreateIoNavMutation,
useUpdateStatusIoMutation,
useAmountIvestmentMutation,
} = ioService;

View File

@@ -0,0 +1,105 @@
//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";
import { baseQuery } from "./token.serivce";
import { ioService } from "./io.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 sponserMaster = createApi({
reducerPath: "sponserMaster",
baseQuery: baseQuery,
tagTypes: ["getSponser", "prePopulate"],
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`,
}),
getSponserMasterActive: builder.query({
query: () => "/sponsor/admin/active",
}),
// ======[Get ID]=====
getSponserById: builder.query({
query: (id) => `/sponsor/admin/${id}`,
}),
// ========[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","prePopulate"],
}),
// ========[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,107 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
// Define a base query function with RTK Query
// export const baseQuery = fetchBaseQuery({
// baseUrl: 'https://sprint4.tanami.betadelivery.com/api/v1',
// prepareHeaders: (headers) => {
// const token = localStorage.getItem('accessToken');
// if (token) {
// headers.set('x-auth-token', `${token}`);
// }
// return headers;
// },
// });
// Define a base query function with token refresh logic
export const baseQuery = async (args, api, extraOptions) => {
let result = await fetchBaseQuery({
baseUrl: 'https://sprint4.tanami.betadelivery.com/api/v1',
prepareHeaders: (headers) => {
const token = localStorage.getItem('accessToken');
if (token) {
headers.set('x-auth-token', token);
}
return headers;
},
})(args, api, extraOptions);
if (result.error && result.error.status === 401) {
// Handle token refresh
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
try {
const refreshResult = await fetchBaseQuery({
baseUrl: 'https://sprint4.tanami.betadelivery.com/api/v1',
})({
url: '/auth/user/regenerate-token',
method: 'POST',
body: { refreshToken },
}, api, extraOptions);
if (refreshResult.data) {
// Save new tokens
localStorage.setItem('accessToken', refreshResult.data.access.token);
localStorage.setItem('refreshToken', refreshResult.data.refresh.token);
localStorage.setItem('refreshTokenExp', refreshResult.data.refresh.expires);
// Retry the original request with the new token
result = await fetchBaseQuery({
baseUrl: 'https://sprint4.tanami.betadelivery.com/api/v1',
prepareHeaders: (headers) => {
const token = localStorage.getItem('accessToken');
if (token) {
headers.set('x-auth-token', token);
}
return headers;
},
})(args, api, extraOptions);
}
} catch (err) {
console.error('Failed to refresh token:', err);
// Handle refresh failure (e.g., redirect to login)
}
}
}
return result;
};
// Create an RTK Query API slice
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: baseQuery,
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
url: '/auth/admin',
method: 'POST',
body: credentials,
}),
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
// Store tokens in local storage
localStorage.setItem('accessToken', data?.data?.access?.token) ;
localStorage.setItem('refreshToken', data?.data?.refresh?.token);
// localStorage.setItem('refreshTokenExp', data?.data?.refresh?.expires);
localStorage.setItem('accessTokenExp', data?.data?.access?.expires);
} catch (error) {
console.error('Login failed:', error);
}
},
}),
refreshToken: builder.mutation({
query: (refreshToken) => ({
url: '/auth/user/regenerate-token',
method: 'POST',
body: { refreshToken },
}),
}),
}),
});
export const { useLoginMutation, useRefreshTokenMutation } = apiSlice;

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

@@ -0,0 +1,55 @@
import { configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
import { sponserMaster } from "../Services/sponser.service";
import { investmentType } from "../Services/investment.type.service";
import { exchangeRate } from "../Services/exchange.rate.service";
import { ioService } from "../Services/io.service";
import { investorDetails } from "../Services/investor.details.service";
import { investorTransaction } from "../Services/investor.transaction.service";
// import { api } from "../Services/api.service";
// import { keyMerits } from "../Services/Key.merits.service";
import { bankDetails } from "../Services/bank.details.service";
import { contact } from "../Services/contact.service";
import { depositRequest } from "../Services/deposit.request.service";
import { apiSlice, baseQuery } from "../Services/token.serivce";
export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer,
[sponserMaster.reducerPath]: sponserMaster.reducer,
[investmentType.reducerPath]: investmentType.reducer,
[exchangeRate.reducerPath]: exchangeRate.reducer,
[ioService.reducerPath]: ioService.reducer,
[investorDetails.reducerPath]: investorDetails.reducer,
[investorTransaction.reducerPath]: investorTransaction.reducer,
[bankDetails.reducerPath]: bankDetails.reducer,
[contact.reducerPath]: contact.reducer,
[depositRequest.reducerPath]: depositRequest.reducer,
// Add other reducers as needed
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: {
extraArgument: baseQuery, // Pass Axios instance as extra argument
},
}).concat(
apiSlice.middleware,
sponserMaster.middleware,
investmentType.middleware,
exchangeRate.middleware,
ioService.middleware,
investorDetails.middleware,
investorTransaction.middleware,
bankDetails.middleware,
contact.middleware,
depositRequest.middleware,
),
});
setupListeners(store.dispatch);
export default store;

56
src/Theme/Theme.js Normal file
View File

@@ -0,0 +1,56 @@
// theme.js
import { extendTheme } from "@chakra-ui/react";
const customTheme = extendTheme({
colors: {
customPink: {
50: "#ffe5e9",
100: "#ffbcc9",
200: "#ff93a8",
300: "#ff6a87",
400: "#ff4166",
500: "#ff1845", // you can choose your custom color values here
600: "#db1139",
700: "#b70d2d",
800: "#930921",
900: "#700616",
},
forestGreen: {
50: "#ffe6e9",
100: "#f8c2c7",
200: "#ef9da4",
300: "#e67882",
400: "#dd5460",
500: "#DE858E", // primary shade for your custom color
600: "#DE858E",
700: "#DE858E",
800: "#DE858E",
900: "#DE858E",
},
forestGreen: {
50: "#e6f3e9",
100: "#c2e1c7",
200: "#9dcda4",
300: "#78b982",
400: "#54a560",
500: "#004118", // primary shade for your custom color
600: "#003b14",
700: "#003310",
800: "#002b0c",
900: "#002308",
},
},
components: {
// Switch: {
// baseStyle: {
// track: {
// _checked: {
// bg: 'forestGreen.500', // using your custom color here
// },
// },
// },
// },
},
});
export default customTheme;

View File

@@ -0,0 +1,811 @@
import * as Yup from "yup";
export const validationSchema = Yup.object().shape({
name: Yup.string().required("Email name is required"),
password: Yup.string().required("Password is required"),
});
export const addCommunitySchema = Yup.object().shape({
member_name: Yup.string().required("Name is required"),
designation: Yup.string().required("Designation is required"),
description: Yup.string().required("Description is required"),
linkedin: Yup.string()
.url("Invalid LinkedIn URL")
.required("Linked In link is required"),
profile_image: Yup.mixed()
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
});
export const schemaEdit = Yup.object().shape({
member_name: Yup.string().required("Name is required"),
designation: Yup.string().required("Designation is required"),
description: Yup.string().required("Description is required"),
linkedin: Yup.string()
.url("Invalid LinkedIn URL")
.required("LinkedIn is required"),
});
export const addCommunityBannerSchema = Yup.object().shape({
heading: Yup.string().required("Name is required"),
sub_heading: Yup.string().required("Designation is required"),
CTO_button_title: Yup.string().required("Description is required"),
CTO_button_link: Yup.string()
.url("Invalid LinkedIn URL")
.required("LinkedIn is required"),
banner_image: Yup.mixed()
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
});
export const editCommunityBannerSchema = Yup.object().shape({
heading: Yup.string().required("Name is required"),
sub_heading: Yup.string().required("Designation is required"),
CTO_button_title: Yup.string().required("Description is required"),
CTO_button_link: Yup.string()
.url("Invalid LinkedIn URL")
.required("LinkedIn is required"),
});
export const addBlogSchema = Yup.object().shape({
author_name: Yup.string().required("Author is required"),
author_designation: Yup.string().required("Author designation is required"),
title: Yup.string().required("Title is required"),
meta_description: Yup.string().required("Description is required"),
content: Yup.string(),
summary: Yup.string().required("Summary is required"),
profile_image: Yup.mixed()
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.test("file_formate", "Image file has unsupported format.", (files) => {
// // // console.log(files[0].type)
const SUPPORTED_FORMATS = [
"image/jpeg",
"image/jpg",
"image/png",
// "image/gif",
"image/tiff",
"image/svg+xml",
];
try {
if (files.length !== 0) {
return files && SUPPORTED_FORMATS.includes(files[0].type);
}
return true;
} catch (error) {
return false;
}
})
.optional(),
content_image_large: Yup.mixed()
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
});
export const editBlogSchema = Yup.object().shape({
author_name: Yup.string().required("Author is required"),
author_designation: Yup.string().required("Author designation is required"),
title: Yup.string().required("Title is required"),
meta_description: Yup.string().required("Description is required"),
content: Yup.string(),
summary: Yup.string().required("Summary is required"),
profile_image: Yup.mixed()
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.test("file_formate", "Image file has unsupported format.", (files) => {
// // // console.log(files[0].type)
const SUPPORTED_FORMATS = [
"image/jpeg",
"image/jpg",
"image/png",
// "image/gif",
"image/tiff",
"image/svg+xml",
];
try {
if (files.length !== 0) {
return files && SUPPORTED_FORMATS.includes(files[0].type);
}
return true;
} catch (error) {
return false;
}
})
.optional(),
content_image_large: Yup.mixed()
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
});
export const addNews = Yup.object().shape({
title: Yup.string().required("Author is required"),
release_date: Yup.date().required("Release date is required"),
meta_description: Yup.string().required("Description is required"),
content: Yup.string().required("Content is required"),
banner_image: Yup.mixed()
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/png",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
.optional(),
});
export const editNews = Yup.object().shape({
title: Yup.string(),
release_date: Yup.date(),
meta_description: Yup.string(),
content: Yup.string(),
banner_image: Yup.mixed()
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
// .test(
// "fileSize",
// " The maximum size of profile picture is 10MB.",
// (files) => {
// //if u want to allow only certain file sizes
// try {
// if (files.length !== 0) {
// return files[0].size <= 10000000;
// }
// return true;
// } catch (error) {
// return false;
// }
// }
// )
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/png",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
.optional(),
});
export const addEvents = Yup.object().shape({
title: Yup.string().required("title is required"),
content: Yup.string().required("content is required"),
location: Yup.string().required("location is required"),
organizer_name: Yup.string().required("Org name date is required"),
eventDates: Yup.string(),
organizer_mobile_number: Yup.string()
.required("Org contact is required")
.matches(/^[0-9]{10}$/, "Mobile number must be 10 digits"),
organizer_email: Yup.string()
.required("Org email is required")
.email("Please enter valid email"),
banner_image: Yup.mixed()
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
// .test(
// "fileSize",
// " The maximum size of profile picture is 10MB.",
// (files) => {
// //if u want to allow only certain file sizes
// try {
// if (files.length !== 0) {
// return files[0].size <= 10000000;
// }
// return true;
// } catch (error) {
// return false;
// }
// }
// )
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/png",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
.optional(),
});
export const addWhitePapers = Yup.object().shape({
title: Yup.string().required("title is required"),
image: Yup.mixed()
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/jpg",
// "image/png",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
.optional(),
document: Yup.mixed()
// .test("required", "You need to provide a file", (files) => {
// // return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
// if (files) return true;
// return false;
// })
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/png",
// "image/jpg",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
});
// test("fileSize", "Image must be at least 10MB", (value) => {
// if (!value) return true;
// const fileSizeInBytes = value.size;
// const fileSizeInMB = fileSizeInBytes / (1024 * 1024);
// return fileSizeInMB >= 10;
// })
export const addVideos = Yup.object().shape({
title: Yup.string().required("Name is required"),
thumbnail: Yup.mixed()
.required("Thumbnail is required")
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
),
description: Yup.string().required("Description is required"),
duration: Yup.string().required("LinkedIn is required"),
embeddedCode: Yup.string().required("LinkedIn is required"),
});
export const addUsecase = Yup.object().shape({
title: Yup.string().required("Name is required"),
meta_description: Yup.string().required("Description is required"),
attachment: Yup.mixed(),
content: Yup.string(),
banner_image: Yup.mixed()
// .required("Thumbnail is required")
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
icon: Yup.mixed()
// .required("Icon is required")
.test("required", "You need to provide a file", (files) => {
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
});
export const editUsecase = Yup.object().shape({
title: Yup.string().required("Name is required"),
meta_description: Yup.string().required("Description is required"),
attachment: Yup.mixed(),
content: Yup.string(),
banner_image: Yup.mixed()
// .required("Thumbnail is required")
.test("required", "You need to provide a file", (files) => {
// return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
//if u want to allow only certain file sizes
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
icon: Yup.mixed()
// .required("Icon is required")
.test("required", "You need to provide a file", (files) => {
if (files) return true;
return false;
})
.test(
"fileSize",
" The maximum size of profile picture is 10MB.",
(files) => {
try {
if (files.length !== 0) {
return files[0].size <= 10000000;
}
return true;
} catch (error) {
return false;
}
}
)
.optional(),
});
export const addTerms = Yup.object().shape({
title: Yup.string().required("title is required"),
content: Yup.string(),
banner_image: Yup.mixed()
// .test("required", "You need to provide a file", (files) => {
// // return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
// if (files) return true;
// return false;
// })
// .test(
// "fileSize",
// " The maximum size of profile picture is 10MB.",
// (files) => {
// //if u want to allow only certain file sizes
// try {
// if (files.length !== 0) {
// return files[0].size <= 10000000;
// }
// return true;
// } catch (error) {
// return false;
// }
// }
// )
.optional(),
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/png",
// "image/jpg",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
});
export const addPolicy = Yup.object().shape({
title: Yup.string().required("title is required"),
content: Yup.string(),
banner_image: Yup.mixed()
// .test("required", "You need to provide a file", (files) => {
// // return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
// if (files) return true;
// return false;
// })
// .test(
// "fileSize",
// " The maximum size of profile picture is 10MB.",
// (files) => {
// //if u want to allow only certain file sizes
// try {
// if (files.length !== 0) {
// return files[0].size <= 10000000;
// }
// return true;
// } catch (error) {
// return false;
// }
// }
// )
.optional(),
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/png",
// "image/jpg",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
});
export const addPartnerCard = Yup.object().shape({
website_link: Yup.string().required("link is required"),
description: Yup.string().required("description is required"),
banner_image: Yup.mixed()
.required("logo is required")
// .test("required", "You need to provide a file", (files) => {
// // return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
// if (files) return true;
// return false;
// })
// .test(
// "fileSize",
// " The maximum size of profile picture is 10MB.",
// (files) => {
// //if u want to allow only certain file sizes
// try {
// if (files.length !== 0) {
// return files[0].size <= 10000000;
// }
// return true;
// } catch (error) {
// return false;
// }
// }
// )
.optional(),
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/png",
// "image/jpg",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
});
export const addFaq = Yup.object().shape({
question: Yup.string().required("Qustion is required"),
answer: Yup.string()
// banner_image: Yup.mixed()
// .test("required", "You need to provide a file", (files) => {
// // return file && file.size <-- u can use this if you don't want to allow empty files to be uploaded;
// if (files) return true;
// return false;
// })
// .test(
// "fileSize",
// " The maximum size of profile picture is 10MB.",
// (files) => {
// //if u want to allow only certain file sizes
// try {
// if (files.length !== 0) {
// return files[0].size <= 10000000;
// }
// return true;
// } catch (error) {
// return false;
// }
// }
// )
.optional(),
// .test("file_formate", "Image file has unsupported format.", (files) => {
// // // // console.log(files[0].type)
// const SUPPORTED_FORMATS = [
// "image/jpeg",
// "image/png",
// "image/jpg",
// "image/gif",
// "image/tiff",
// "image/svg+xml",
// ];
// try {
// if (files.length !== 0) {
//
// return files && SUPPORTED_FORMATS.includes(files[0].type);
// }
// return true;
// } catch (error) {
// return false;
// }
// })
});
// Tanami Schema
export const investmentSchema = Yup.object().shape({
name: Yup.string().required("Name is required"),
address: Yup.string().required("Type is required"),
mobile: Yup.string().required("Total Fund is required"),
bankDetails: Yup.string().required("Total Investor is required"),
bankAccount: Yup.string().required("Predicted Income is required"),
status: Yup.string()
.oneOf(["available", "upcoming", "closed"], "Invalid status")
.required("Status is required"),
});

BIN
src/assets/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

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