Set up
21
.eslintrc.cjs
Normal file
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||
settings: { react: { version: '18.2' } },
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react/jsx-no-target-blank': 'off',
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
28
.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Other files to ignore
|
||||
yarn.lock
|
||||
.env
|
||||
39
index.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/assets/favicons.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tanami Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script>
|
||||
|
||||
const craftedMsg = "Crafted with ❤️ by WDI Team for a better web.";
|
||||
const websiteMsg = "Website: www.wdipl.com";
|
||||
|
||||
const craftedStyles = 'font-size: 16px; font-family: monospace; background: #000; color: #E5195E; padding: 12px 19px; border: 1.8px dashed; border-right: 0px #000 solid';
|
||||
const websiteStyles = 'font-size: 16px; font-family: monospace; background: #E5195E; color: #000; padding: 12px 19px; border: 1.8px dashed #000; border-left: 0px #000 solid';
|
||||
console.log('%c' + craftedMsg + ' %c' + websiteMsg, craftedStyles, websiteStyles);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <script type="text/javascript">
|
||||
function googleTranslateElementInit() {
|
||||
new google.translate.TranslateElement({
|
||||
pageLanguage: 'en',
|
||||
includedLanguages: `'en,ur,ar,mr,hi'`,
|
||||
layout: google.translate.TranslateElement.InlineLayout.SIMPLE
|
||||
}, 'google_translate_element');
|
||||
}
|
||||
</script> -->
|
||||
<!-- <script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script> -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
7133
package-lock.json
generated
Normal file
52
package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "optifii-admin",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/icons": "^2.1.1",
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@reduxjs/toolkit": "^2.2.3",
|
||||
"apexcharts": "^3.52.0",
|
||||
"axios": "^1.7.2",
|
||||
"bootstrap": "5.3.3",
|
||||
"chart.js": "^4.4.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"framer-motion": "^11.1.5",
|
||||
"js-cookie": "^3.0.5",
|
||||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.4.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.51.3",
|
||||
"react-icons": "^5.1.0",
|
||||
"react-quill": "^0.0.2",
|
||||
"react-redux": "^9.1.1",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-persist-transform-encrypt": "^5.1.1",
|
||||
"uuid": "^10.0.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"yup": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
}
|
||||
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
491
src/App.css
Normal file
@@ -0,0 +1,491 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=League+Spartan:wght@100..900&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap");
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap');
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* font-family: "League Spartan", sans-serif !important; */
|
||||
font-family: "Poppins", sans-serif !important;
|
||||
|
||||
/* font-family: "Lato", sans-serif !important; */
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #6311CB; /* Change this to your desired color */
|
||||
color: white; /* Optional: Change the text color */
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.activee {
|
||||
text-decoration: none; /* Remove underline */
|
||||
font-weight: bold; /* Optionally change font weight for active link */
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
#7a45fb,
|
||||
#de41b5
|
||||
); /* Gradient background */
|
||||
-webkit-background-clip: text; /* Clip text to the background area */
|
||||
-webkit-text-fill-color: transparent; /* Fill text with the background color */
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #1a04361c;
|
||||
/* color: #fff; */
|
||||
/* background-color: #e2e8f01c; */
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
background-color: #1a04361c;
|
||||
/* color: #fff; */
|
||||
/* background-color: #e2e8f01c !important; */
|
||||
}
|
||||
|
||||
.active:hover {
|
||||
background-color: #1a04361c;
|
||||
/* color: #fff; */
|
||||
/* background-color: #e2e8f01c !important; */
|
||||
}
|
||||
|
||||
.web-text-small {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
.web-text-xxsmall {
|
||||
font-size: 9px !important;
|
||||
}
|
||||
.web-text-xsmall {
|
||||
font-size: 11px !important;
|
||||
}
|
||||
|
||||
.web-text-medium {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.web-text-large {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
|
||||
.rubix-text-dark {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.arrow-button {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.arrow-button:active {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.arrow-button:focus {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.arrow-button:hover {
|
||||
background: linear-gradient(90deg, #de41b5 0%, #7a45fb 100%);
|
||||
}
|
||||
|
||||
.greeting {
|
||||
text-decoration: none; /* Remove underline */
|
||||
font-weight: bold; /* Optionally change font weight for active link */
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
#7a45fb,
|
||||
#de41b5
|
||||
); /* Gradient background */
|
||||
-webkit-background-clip: text; /* Clip text to the background area */
|
||||
-webkit-text-fill-color: transparent; /* Fill text with the background color */
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background-color: #004118 !important;
|
||||
}
|
||||
|
||||
.team-slider .swiper-button-next:after {
|
||||
position: absolute;
|
||||
top: 185px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50px;
|
||||
font-size: 18px;
|
||||
color: #000;
|
||||
font-weight: 700;
|
||||
right: 110px;
|
||||
background-image: radial-gradient(
|
||||
circle,
|
||||
#ffffff,
|
||||
#eee2f2,
|
||||
#e7c3dc,
|
||||
#e5a3ba,
|
||||
#de858e
|
||||
);
|
||||
}
|
||||
.team-slider .swiper-button-prev:after {
|
||||
position: absolute;
|
||||
top: 185px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50px;
|
||||
font-size: 18px;
|
||||
color: #000;
|
||||
font-weight: 700;
|
||||
left: 110px;
|
||||
background-image: radial-gradient(
|
||||
circle,
|
||||
#ffffff,
|
||||
#eee2f2,
|
||||
#e7c3dc,
|
||||
#e5a3ba,
|
||||
#de858e
|
||||
);
|
||||
}
|
||||
|
||||
.text-animate {
|
||||
animation-name: text;
|
||||
animation-duration: 5s;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
|
||||
.table-scroll::-webkit-scrollbar{
|
||||
width: 2px !important;
|
||||
height: 10px !important;
|
||||
}
|
||||
|
||||
/* Total scrollbar width */
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* The track (background) of the scrollbar */
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
/* The draggable scrollbar handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #0041184f;
|
||||
border-radius: 0px;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* On hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #0041189a;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#google_translate_element {
|
||||
/* display: none; Hide the default Google Translate dropdown */
|
||||
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
opacity: 0.1;
|
||||
}
|
||||
.goog-te-banner-frame {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.goog-te-banner-frame.skiptranslate {
|
||||
display: none !important;
|
||||
}
|
||||
.goog-logo-link {
|
||||
display: none !important;
|
||||
}
|
||||
.goog-te-gadget {
|
||||
color: transparent !important;
|
||||
}
|
||||
|
||||
|
||||
@keyframes text {
|
||||
0% {
|
||||
color: #DE858E;
|
||||
/* margin-bottom: -40px; */
|
||||
}
|
||||
30% {
|
||||
letter-spacing: 10px;
|
||||
/* margin-bottom: -40px; */
|
||||
}
|
||||
85% {
|
||||
letter-spacing: 8px;
|
||||
/* margin-bottom: -40px; */
|
||||
}
|
||||
100% {
|
||||
/* margin-bottom: 20px; */
|
||||
}
|
||||
}
|
||||
|
||||
.rotate {
|
||||
animation: animName 10s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes animName {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.lds-ellipsis {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 64px;
|
||||
height: 10px;
|
||||
}
|
||||
.lds-ellipsis div {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||
}
|
||||
.lds-ellipsis div:nth-child(1) {
|
||||
left: 6px;
|
||||
animation: lds-ellipsis1 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(2) {
|
||||
left: 6px;
|
||||
animation: lds-ellipsis2 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(3) {
|
||||
left: 26px;
|
||||
animation: lds-ellipsis2 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(4) {
|
||||
left: 45px;
|
||||
animation: lds-ellipsis3 0.6s infinite;
|
||||
}
|
||||
@keyframes lds-ellipsis1 {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes lds-ellipsis3 {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
@keyframes lds-ellipsis2 {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate(19px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra small devices (phones, 600px and down) */
|
||||
@media only screen and (max-width: 600px) {
|
||||
body {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Small devices (portrait tablets and large phones, 600px and up) */
|
||||
@media only screen and (min-width: 600px) {
|
||||
body {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Medium devices (landscape tablets, 768px and up) */
|
||||
@media only screen and (min-width: 768px) {
|
||||
body {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Large devices (laptops/desktops, 992px and up) */
|
||||
@media only screen and (min-width: 992px) {
|
||||
body {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra large devices (large laptops and desktops, 1200px and up) */
|
||||
@media only screen and (min-width: 1200px) {
|
||||
body {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========= [ switch BTN ============ */
|
||||
|
||||
|
||||
|
||||
/* From Uiverse.io by Nawsome */
|
||||
.switch {
|
||||
display: block;
|
||||
background-color: black;
|
||||
width: 85px;
|
||||
height: 115px;
|
||||
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0 1px 2px black, inset 0 2px 2px -2px white, inset 0 0 2px 15px #47434c, inset 0 0 2px 22px black;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
perspective: 700px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.switch input:checked + .button {
|
||||
transform: translateZ(20px) rotateX(25deg);
|
||||
box-shadow: 0 -10px 20px #ff1818;
|
||||
}
|
||||
|
||||
.switch input:checked + .button .light {
|
||||
animation: flicker 0.2s infinite 0.3s;
|
||||
}
|
||||
|
||||
.switch input:checked + .button .shine {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.switch input:checked + .button .shadow {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.switch .button {
|
||||
display: block;
|
||||
transition: all 0.3s cubic-bezier(1, 0, 1, 1);
|
||||
transform-origin: center center -20px;
|
||||
transform: translateZ(20px) rotateX(-25deg);
|
||||
transform-style: preserve-3d;
|
||||
background-color: #9b0621;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background: linear-gradient(#980000 0%, #6f0000 30%, #6f0000 70%, #980000 100%);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.switch .button::before {
|
||||
content: "";
|
||||
background: linear-gradient(rgba(255, 255, 255, 0.8) 10%, rgba(255, 255, 255, 0.3) 30%, #650000 75%, #320000) 50% 50%/97% 97%, #b10000;
|
||||
background-repeat: no-repeat;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
transform-origin: top;
|
||||
transform: rotateX(-90deg);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.switch .button::after {
|
||||
content: "";
|
||||
background-image: linear-gradient(#650000, #320000);
|
||||
width: 100%;
|
||||
height: 58px;
|
||||
transform-origin: top;
|
||||
transform: translateY(50px) rotateX(-90deg);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
box-shadow: 0 50px 8px 0px black, 0 80px 20px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.switch .light {
|
||||
opacity: 0;
|
||||
animation: light-off 1s;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: radial-gradient(#ffc97e, #ff1818 40%, transparent 70%);
|
||||
}
|
||||
|
||||
.switch .dots {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: radial-gradient(transparent 30%, rgba(101, 0, 0, 0.7) 70%);
|
||||
background-size: 10px 10px;
|
||||
}
|
||||
|
||||
.switch .characters {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(white, white) 50% 20%/5% 20%, radial-gradient(circle, transparent 50%, white 52%, white 70%, transparent 72%) 50% 80%/33% 25%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.switch .shine {
|
||||
transition: all 0.3s cubic-bezier(1, 0, 1, 1);
|
||||
opacity: 0.3;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(white, transparent 3%) 50% 50%/97% 97%, linear-gradient(rgba(255, 255, 255, 0.5), transparent 50%, transparent 80%, rgba(255, 255, 255, 0.5)) 50% 50%/97% 97%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.switch .shadow {
|
||||
transition: all 0.3s cubic-bezier(1, 0, 1, 1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(transparent 70%, rgba(0, 0, 0, 0.8));
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes light-off {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
82
src/App.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Routes,
|
||||
Route,
|
||||
Navigate,
|
||||
} from "react-router-dom";
|
||||
import "./App.css"; // Import CSS file
|
||||
import DefaultLayout from "./Layout/DefaultLayout";
|
||||
import NotFound from "./Pages/NotFound";
|
||||
import Login from "./Pages/Login";
|
||||
import GlobalStateContext from "./Contexts/GlobalStateContext";
|
||||
import Cookies from "js-cookie";
|
||||
import NoInternetScreen from "./Pages/NoInternetScreen";
|
||||
import Welcome from "./Pages/PaymentSuccess";
|
||||
import PaymentFailed from "./Pages/PaymentFailed";
|
||||
|
||||
const App = () => {
|
||||
// const { isAuthenticate } = useSelector((state) => state?.auth);
|
||||
|
||||
const { isAuthenticate } = useContext(GlobalStateContext);
|
||||
const isAuthenticatedInCookie = Cookies.get("isAuthenticated");
|
||||
|
||||
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOnlineStatusChange = () => {
|
||||
setIsOnline(navigator.onLine);
|
||||
};
|
||||
|
||||
window.addEventListener("online", handleOnlineStatusChange);
|
||||
window.addEventListener("offline", handleOnlineStatusChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("online", handleOnlineStatusChange);
|
||||
window.removeEventListener("offline", handleOnlineStatusChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// const token = localStorage.getItem('accessToken')
|
||||
// console.log(token);
|
||||
|
||||
|
||||
// const PrivateRoute = ({ children }) => {
|
||||
// if (!isAuthenticate && isAuthenticatedInCookie !== "true") {
|
||||
// return <Navigate to="/login" replace />;
|
||||
// }
|
||||
// return children;
|
||||
// };
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/payment-success" element={<Welcome />} />
|
||||
<Route path="/payment-failed" element={<PaymentFailed />} />
|
||||
<Route
|
||||
path="/*"
|
||||
element={
|
||||
// isOnline ? (
|
||||
isAuthenticate || isAuthenticatedInCookie === "true" ? (
|
||||
// localStorage.getItem('accessToken') && localStorage.getItem('refreshToken') ? (
|
||||
// true ? (
|
||||
<DefaultLayout isOnline={isOnline} />
|
||||
) : (
|
||||
<Login />
|
||||
)
|
||||
// ) : (
|
||||
// <NoInternetScreen />
|
||||
// )
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
421
src/Components/Banner/AddBanner.jsx
Normal 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;
|
||||
489
src/Components/Banner/BannerEdit.jsx
Normal 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;
|
||||
63
src/Components/Banner/BannerMainCard.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Box, Button, Text } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
|
||||
const BannerMainCard = ({heading, subHeading, buttonTitle, imgLink, center}) => {
|
||||
|
||||
|
||||
const words = heading?.split(' ');
|
||||
const firstThreeWords = words.slice(0, 3).join(' ');
|
||||
const remainingWords = words.slice(3).join(' ');
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
shadow={"md"}
|
||||
rounded={8}
|
||||
w={469}
|
||||
h={250}
|
||||
borderRadius={'sm'}
|
||||
bgImage={imgLink}
|
||||
bgSize="cover"
|
||||
bgPosition="center"
|
||||
ps={8}
|
||||
pt={14}
|
||||
>
|
||||
<Box w={center ? "92%" : "100%"} display={'flex'} flexDirection={'column'} alignItems={center ? 'center' : "start"}>
|
||||
<Text textAlign={center ? "center":'start'} w={center ? "80%" : "74%"}fontSize={"19px"} className="fw-bolder" color={"#FFFFFF"} display={'block'} as={"span"}>
|
||||
<span style={{ color: '#DE858E' }}>{firstThreeWords}</span>
|
||||
{' '}
|
||||
<span>{remainingWords}</span>
|
||||
</Text>
|
||||
<Text textAlign={center ? "center":'start'} w={center ? "92%" : "74%"} fontSize={"10px"} mt={1} className=" fw-normal" color={"#ffffff"} display={'block'} as={"span"}>
|
||||
{subHeading}
|
||||
</Text>
|
||||
<Button
|
||||
fontWeight={"normal"}
|
||||
fontSize={"9px"}
|
||||
ps={4}
|
||||
pe={4}
|
||||
pt={1}
|
||||
pb={1}
|
||||
mt={2}
|
||||
color={"#ffffff"}
|
||||
_hover={{
|
||||
bgGradient:"linear(to-r, #1E114B, purple)"
|
||||
}}
|
||||
// bg={'#1E114B'}
|
||||
// bgGradient="linear(to-r, #1E114B, purple)"
|
||||
variant={"outline"}
|
||||
// colorScheme="purple"
|
||||
rounded={4}
|
||||
size={"xs"}
|
||||
// border={'1px soild #fff'}
|
||||
|
||||
>
|
||||
{buttonTitle}
|
||||
</Button></Box>
|
||||
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default BannerMainCard
|
||||
368
src/Components/Banner/BannerTable.jsx
Normal 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;
|
||||
165
src/Components/Banner/BannerView.jsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetBuildBannerByIdQuery } from "../../Services/api.service";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import FullscreenLoaders from "../../Components/Loaders/FullscreenLoaders";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
import Header from "../../Components/Header";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Image,
|
||||
StackDivider,
|
||||
Tag,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import BannerMainCard from "./BannerMainCard";
|
||||
const API_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
const BannerView = ({data, isLoading, editLink, center}) => {
|
||||
const banner = data?.data;
|
||||
|
||||
return isLoading ? (
|
||||
<FullscreenLoaders />
|
||||
) : (
|
||||
<Box
|
||||
{...OPACITY_ON_LOAD}
|
||||
overflowY={"scroll"}
|
||||
w={"100%"}
|
||||
h={"100vh"}
|
||||
className="overflow-auto "
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
>
|
||||
<Header
|
||||
title={"Banner's"}
|
||||
btnTitle={"Edit banner"}
|
||||
link={`${editLink}/${banner.id}`}
|
||||
/>
|
||||
<Box display={"flex"}>
|
||||
<Box className="col-5 d-flex flex-column gap-2 pt-4">
|
||||
<span className="web-text-large fw-bold rubix-text-dark">
|
||||
Banners Info
|
||||
</span>
|
||||
<span className="web-text-medium text-secondary">
|
||||
Select the platform for which you need to create this campaign.
|
||||
</span>
|
||||
|
||||
<Divider />
|
||||
|
||||
<span className="web-text-large fw-bold rubix-text-dark">
|
||||
Display banner
|
||||
</span>
|
||||
<span className="web-text-medium text-secondary mb-4">
|
||||
Below is the profile that will be displayed on the community page.
|
||||
</span>
|
||||
|
||||
<Box
|
||||
boxSize="sm"
|
||||
className="d-flex w-100 justify-content-center flex-column align-items-center gap-3"
|
||||
>
|
||||
{/* <Image
|
||||
shadow={"md"}
|
||||
rounded={8}
|
||||
w={500}
|
||||
h={240}
|
||||
src={`${API_URL}/${banner?.banner_image}`}
|
||||
alt="Selected Image"
|
||||
/> */}
|
||||
|
||||
<BannerMainCard
|
||||
imgLink={`${API_URL}/${banner?.banner_image}`}
|
||||
heading={banner?.Heading}
|
||||
subHeading={banner?.sub_heading}
|
||||
buttonTitle={banner?.CTO_button_title}
|
||||
center={center}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="col-7 pt-4 overflow-auto p-4">
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark mb-1">
|
||||
Status
|
||||
</Box>
|
||||
{data?.data?.status ? (
|
||||
<Tag size={"sm"} variant="solid" colorScheme="teal">
|
||||
Active
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag size={"sm"} variant="solid" colorScheme="red">
|
||||
Inactive
|
||||
</Tag>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Heading
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{banner?.Heading}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Sub heading
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{banner?.sub_heading}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Button title
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{banner?.CTO_button_title}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Button link
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{banner?.CTO_button_link}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Created At
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{formatDate(banner?.createdAt)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="mb-3">
|
||||
<Box className="web-text-large fw-bold rubix-text-dark">
|
||||
Updated At
|
||||
</Box>
|
||||
<Box className="web-text-medium text-secondary">
|
||||
{formatDate(banner?.updatedAt)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerView;
|
||||
84
src/Components/BannerStack.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ArrowForwardIcon } from "@chakra-ui/icons";
|
||||
import { Box, Button, HStack, Skeleton, Text } from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import CommunityBannerCard from "../Pages/Community/CommunityBannerCard";
|
||||
|
||||
const BannerStack = ({
|
||||
stackTitle,
|
||||
viewAllLink,
|
||||
bannerIsLoading,
|
||||
bannerArray,
|
||||
viewBannerLink,
|
||||
}) => {
|
||||
|
||||
|
||||
return (
|
||||
<Box >
|
||||
<HStack
|
||||
display={"flex"}
|
||||
justifyContent={"space-between"}
|
||||
alignItems={"center"}
|
||||
pe={1}
|
||||
>
|
||||
<Text as={"span"} color={"purple.900"} className="web-text-large fw-bold">
|
||||
{stackTitle}
|
||||
</Text>
|
||||
|
||||
<Link to={viewAllLink}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
rightIcon={<ArrowForwardIcon />}
|
||||
colorScheme={"purple"}
|
||||
size="sm"
|
||||
>
|
||||
View all
|
||||
</Button>
|
||||
</Link>
|
||||
</HStack>
|
||||
|
||||
<Box
|
||||
display={"flex"}
|
||||
// bg={"red.500"}
|
||||
alignItems={"start"}
|
||||
flexWrap={"wrap"}
|
||||
justifyContent={"start"}
|
||||
w={"100%"}
|
||||
// h={"auto"}
|
||||
h={200}
|
||||
ps={1}
|
||||
>
|
||||
{bannerIsLoading
|
||||
? Array.from({ length: 3 }).map((_, index) => (
|
||||
<Box key={index} className="col-4 p-2 ps-0">
|
||||
<Skeleton w={"100%"} rounded={"md"} height={180} />
|
||||
</Box>
|
||||
))
|
||||
: bannerArray?.map(
|
||||
({
|
||||
id,
|
||||
CTO_button_title,
|
||||
banner_image,
|
||||
Heading,
|
||||
createdAt,
|
||||
sub_heading,
|
||||
status
|
||||
}) => (
|
||||
<Link className="col-4 h-100 p-3 ps-0" key={id} to={`${viewBannerLink}/${id}`}>
|
||||
<CommunityBannerCard
|
||||
status={status}
|
||||
bgImage={banner_image}
|
||||
subHeading={sub_heading}
|
||||
heading={Heading}
|
||||
createdAt={createdAt}
|
||||
ctoBtnTitle={CTO_button_title}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BannerStack;
|
||||
30
src/Components/Buttons/PrimaryButton.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Button } from '@chakra-ui/button'
|
||||
import React from 'react'
|
||||
|
||||
const PrimaryButton = ({title, onClick, ...props}) => {
|
||||
return (
|
||||
|
||||
<Button
|
||||
{...props}
|
||||
_hover={{
|
||||
// bgGradient: "linear(to-r, #5E0FCD, #3725EA)",
|
||||
opacity: 0.8,
|
||||
}}
|
||||
bgGradient="linear(to-r, #3725EA, #5E0FCD)"
|
||||
fontSize={"xs"}
|
||||
px={8}
|
||||
fontWeight={500}
|
||||
size={"sm"}
|
||||
color={"#fff"}
|
||||
variant="solid"
|
||||
transition={"0.5s all"}
|
||||
_active={{
|
||||
// bgGradient: "linear(to-r, #5E0FCD, #3725EA)",
|
||||
opacity: 1,
|
||||
}}
|
||||
onClick={onClick}
|
||||
>{title}</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrimaryButton
|
||||
21
src/Components/Buttons/SecondaryButton.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Button } from '@chakra-ui/button'
|
||||
import React from 'react'
|
||||
|
||||
const SecondaryButton = ({title, onClick, ...props}) => {
|
||||
return (
|
||||
|
||||
<Button
|
||||
{...props}
|
||||
fontSize={"xs"}
|
||||
px={8}
|
||||
fontWeight={600}
|
||||
size={"sm"}
|
||||
variant="outline"
|
||||
transition={"0.5s all"}
|
||||
colorScheme='purple'
|
||||
onClick={onClick}
|
||||
>{title}</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default SecondaryButton
|
||||
60
src/Components/ChipSelector/ChipSelector.css
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 1rem;
|
||||
align-items: center;
|
||||
border: 1px solid rgb(212, 206, 206);
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.chips {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chip {
|
||||
background-color: #cfe1ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.2rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.chip span {
|
||||
color: #013380;
|
||||
}
|
||||
|
||||
.chip svg {
|
||||
font-size: 2.5rem;
|
||||
fill: #3270d1;
|
||||
cursor: pointer;
|
||||
transition: fill 0.2s;
|
||||
}
|
||||
|
||||
.chip svg:hover {
|
||||
fill: #2857a3;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 1.6rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 1rem;
|
||||
color: red;
|
||||
}
|
||||
|
||||
68
src/Components/ChipSelector/ChipSelector.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, FormHelperText, Input, Tag, TagCloseButton, TagLabel } from "@chakra-ui/react";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
|
||||
const ChipSelector = ({chips, setChips, type}) => {
|
||||
const [text, setText] = useState("");
|
||||
const [validationError, setValidationError] = useState("");
|
||||
|
||||
function removeChip(chipToRemove) {
|
||||
const updatedChips = chips.filter((chip, index) => index !== chipToRemove);
|
||||
setChips(updatedChips);
|
||||
}
|
||||
|
||||
|
||||
function handlePressEnter(e) {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
if (e.key !== "Enter" || !text) return;
|
||||
if (chips.includes(text)) {
|
||||
return setValidationError("Cannot add the same input more than once");
|
||||
}
|
||||
setChips((prevState) => [...prevState, text]);
|
||||
setText("");
|
||||
setValidationError("");
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Box
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
gap={2}
|
||||
className="">
|
||||
<Input
|
||||
type={type? type : "text"}
|
||||
|
||||
id="tags"
|
||||
size="sm"
|
||||
className="web-text-medium "
|
||||
placeholder="Press Enter to add tag"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onKeyDown={handlePressEnter}
|
||||
/>
|
||||
{validationError && <span className="error-message web-text-small text-danger d-flex align-items-center gap-1"><TiWarning className="fw-bold fs-5 " />{validationError}</span>}
|
||||
|
||||
<FormHelperText className="web-text-small mt-0">
|
||||
{type ? "Please select and press enter to add date." : "Please type and press enter to add tags."}
|
||||
|
||||
</FormHelperText>
|
||||
<Box
|
||||
display={'flex'}
|
||||
justifyContent={'start'}
|
||||
flexWrap={'wrap'}
|
||||
gap={2}
|
||||
>
|
||||
{chips?.map((chip, i) => (
|
||||
<Tag key={i} size="sm" ps={3} pe={3} pt={0.5} pb={0.5} rounded="full" variant='solid' backgroundColor={'#565263'}>
|
||||
<TagLabel className="text-uppercase">{chip}</TagLabel>
|
||||
<TagCloseButton onClick={() => removeChip(i)} />
|
||||
</Tag>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChipSelector;
|
||||
153
src/Components/CommunityBanner.jsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { Box, Button, Container, Text } from "@chakra-ui/react";
|
||||
import banner from "../assets/communityBanner.webp";
|
||||
|
||||
const BannerContent = [
|
||||
{
|
||||
heading1: `Welcome To The`,
|
||||
heading2: `Rubix Community`,
|
||||
},
|
||||
{
|
||||
subheading: `This is a space for enterprises, dApp developers, stakeholders
|
||||
and blockchain advocates to aggregate resources and ideas to make a difference
|
||||
through our green blockchain technology.`,
|
||||
},
|
||||
{
|
||||
btn: `Explore our community`,
|
||||
},
|
||||
];
|
||||
|
||||
const CommunityBanner = () => {
|
||||
return (
|
||||
<Box
|
||||
height={"75%"}
|
||||
backgroundImage={`url(${banner})`}
|
||||
backgroundRepeat={"no-repeat"}
|
||||
backgroundSize={"cover"}
|
||||
display={"grid"}
|
||||
placeContent={"center"}
|
||||
>
|
||||
<Container
|
||||
alignItems={"center"}
|
||||
display={"flex"}
|
||||
height={"100%"}
|
||||
alignContent={"center"}
|
||||
maxW="container.xl"
|
||||
textAlign={"left"}
|
||||
marginTop={"2rem"}
|
||||
paddingLeft={"3.5rem"}
|
||||
>
|
||||
<Box
|
||||
width={"75%"}
|
||||
>
|
||||
<Text
|
||||
fontWeight={700}
|
||||
fontSize={"30px"}
|
||||
// textTransform={"upperCase"}
|
||||
color={"#DE858E"}
|
||||
lineHeight={"42px"}
|
||||
letterSpacing={"1px"}
|
||||
sx={{
|
||||
"@media (max-width: 996px)": {
|
||||
fontSize: "46px",
|
||||
},
|
||||
"@media (max-width: 600px)": {
|
||||
fontSize: "40px",
|
||||
marginBottom: "0rem",
|
||||
lineHeight: "54px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
{BannerContent[0].heading1}
|
||||
</span>{" "}
|
||||
<br />
|
||||
{BannerContent[0].heading2}
|
||||
</Text>
|
||||
<Box
|
||||
marginTop={"0.5rem"}
|
||||
width={"80%"}
|
||||
sx={{
|
||||
"@media (max-width: 500px)": {
|
||||
width: "100%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
color={"#fff"}
|
||||
fontSize={"15px"}
|
||||
fontWeight={"400"}
|
||||
lineHeight={"27.5px"}
|
||||
fontFamily={"Poppins"}
|
||||
textTransform={"capitalize"}
|
||||
>
|
||||
{BannerContent[1].subheading}
|
||||
</Text>
|
||||
</Box>
|
||||
<Button
|
||||
position={"relative"}
|
||||
backgroundColor={"transparent"}
|
||||
cursor={"pointer"}
|
||||
transition="0.3s ease-in-out"
|
||||
color={"#fff"}
|
||||
width={"180px"}
|
||||
height={"44px"}
|
||||
fontFamily={"Poppins"}
|
||||
fontWeight={"400"}
|
||||
border={"1px solid white"}
|
||||
borderRadius={"10px"}
|
||||
fontSize={"12px"}
|
||||
zIndex={"1"}
|
||||
overflow={"hidden"}
|
||||
marginTop={"0.5rem"}
|
||||
sx={{
|
||||
"::before": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: "65px",
|
||||
height: "65px",
|
||||
borderRadius: "50%",
|
||||
transition: "0.35s linear",
|
||||
zIndex: -1,
|
||||
bgGradient:
|
||||
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
|
||||
opacity: "0",
|
||||
},
|
||||
"&:hover::before": {
|
||||
width: "100%",
|
||||
height: "120%",
|
||||
borderRadius: "0px",
|
||||
opacity: "1",
|
||||
},
|
||||
"@media (max-width: 500px)": {
|
||||
fontSize: "14px",
|
||||
width: "230px",
|
||||
height: "44px",
|
||||
marginTop: "2rem",
|
||||
bgGradient:
|
||||
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
|
||||
color: "#000",
|
||||
fontWeight: "600",
|
||||
},
|
||||
}}
|
||||
_hover={{
|
||||
color: "#000",
|
||||
border: "1px solid white",
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{BannerContent[2].btn}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommunityBanner;
|
||||
777
src/Components/CreateIOld.jsx
Normal file
@@ -0,0 +1,777 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Heading,
|
||||
Input,
|
||||
Select,
|
||||
Textarea,
|
||||
Button,
|
||||
Text,
|
||||
Image,
|
||||
Tabs,
|
||||
TabList,
|
||||
Tab,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Tooltip,
|
||||
Switch,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import {
|
||||
AddIcon,
|
||||
CloseIcon,
|
||||
DeleteIcon,
|
||||
EditIcon,
|
||||
ViewIcon,
|
||||
WarningTwoIcon,
|
||||
} from "@chakra-ui/icons";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
import GlobalStateContext from "../../Contexts/GlobalStateContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import FormField from "../../Components/FormField";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import AddIOCharges from "./AddIOCharges";
|
||||
import FormInputMain from "../../Components/FormInputMain";
|
||||
import DataTable from "../../Components/DataTable/DataTable";
|
||||
import { debounce } from "../Master/Sponser/AddSponser";
|
||||
import CustomAlertDialog from "../../Components/CustomAlertDialog";
|
||||
import { formatDate } from "../../Components/Functions/UTCConvertor";
|
||||
import InvestmentDocuments from "./InvestmentDocuments";
|
||||
|
||||
const schema = yup.object().shape({
|
||||
ioName: yup.string().required("Arabic name is required"),
|
||||
ioNameArabic: yup.string().required("Investment Object name is required"),
|
||||
discription: yup.string().required("Sponser name is required"),
|
||||
discriptionArabic: yup.string().required("Arabic name is required"),
|
||||
typeName: yup.string().required("Investment Object name is required"),
|
||||
typeNameArabic: yup.string().required("Sponser name is required"),
|
||||
sponserName: yup.string().required("Arabic name is required"),
|
||||
sponserNameArabic: yup
|
||||
.string()
|
||||
.required("Investment Object name is required"),
|
||||
holdingPeriod: yup.string().required("Sponser name is required"),
|
||||
ioStartus: yup.string().required("Investment Object name is required"),
|
||||
ioStartusArabic: yup.string().required("Sponser name is required"),
|
||||
goalAmount: yup.string().required("Arabic name is required"),
|
||||
closingDate: yup.string().required("Investment Object name is required"),
|
||||
minInvestment: yup.string().required("Sponser name is required"),
|
||||
maxInvestment: yup.string().required("Arabic name is required"),
|
||||
expectedReturn: yup.string().required("Investment Object name is required"),
|
||||
originalValue: yup.string().required("Sponser name is required"),
|
||||
keyname: yup.string().required("Arabic name is required"),
|
||||
keyNameArabic: yup.string().required("Investment Object name is required"),
|
||||
keyDescription: yup.string().required("Sponser name is required"),
|
||||
keyDescriptionArabic: yup.string().required("Sponser name is required"),
|
||||
docType: yup.string().required("Sponser name is required"),
|
||||
|
||||
destributedAmount: yup
|
||||
.number()
|
||||
.required("Distributed Amount is required")
|
||||
.positive("Must be a positive number"),
|
||||
year: yup.string().required("Year is required"),
|
||||
tenure: yup
|
||||
.number()
|
||||
.required("Tenure is required")
|
||||
.positive("Must be a positive number"),
|
||||
annualReturn: yup
|
||||
.number()
|
||||
.required("Annual Return is required")
|
||||
.positive("Must be a positive number"),
|
||||
miniInvest: yup
|
||||
.number()
|
||||
.required("Minimum Invest is required")
|
||||
.positive("Must be a positive number"),
|
||||
quaterly: yup.string().required("Quaterly is required"),
|
||||
targetClose: yup.date().required("Target close date is required"),
|
||||
annualyield: yup
|
||||
.number()
|
||||
.required("Annual Yield is required")
|
||||
.positive("Must be a positive number"),
|
||||
iconUpload: yup.mixed().required("Profile image is required"),
|
||||
bannerImages: yup.mixed().required("Profile image is required"),
|
||||
otherImage: yup.mixed().required("Profile image is required"),
|
||||
docAttach: yup.mixed().required("Profile image is required"),
|
||||
videos: yup.mixed().required("Profile image is required"),
|
||||
});
|
||||
|
||||
const startYear = 2024;
|
||||
const endYear = 2124;
|
||||
const years = Array.from(
|
||||
{ length: endYear - startYear + 1 },
|
||||
(_, i) => startYear + i
|
||||
).map((year) => ({ value: year, label: year }));
|
||||
|
||||
const CreateIO = () => {
|
||||
const navigate = useNavigate();
|
||||
const { create, setCreate, sponser, setSponser, investment, setInvestment } =
|
||||
useContext(GlobalStateContext);
|
||||
const [bannerImageData, setBannerImageData] = useState(null);
|
||||
const [otherImageData, setOtherImageData] = useState(null);
|
||||
const [selectedBannerImageData, setSelectedBannerImageData] = useState(null);
|
||||
const [selectedOtherImageData, setSelectedOtherImageData] = useState(null);
|
||||
const [charges, setCharges] = useState([]);
|
||||
const [totalCharge, setTotalCharge] = useState(0.0);
|
||||
const [totalAmount, setTotalAmount] = useState(0.0);
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [deleteAlert, setDeleteAlert] = useState(false);
|
||||
const [actionId, setActionId] = useState(false);
|
||||
const [mouseEntered, setMouseEntered] = useState(false);
|
||||
const [mouseEnteredId, setMouseEnteredId] = useState("");
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
console.log(errors);
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate loading
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 1500);
|
||||
|
||||
// Cleanup the timer on component unmount
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const tableHeadRow = [
|
||||
"Sponsor name",
|
||||
"Address",
|
||||
"Mobile no",
|
||||
"Created At",
|
||||
"Action",
|
||||
];
|
||||
|
||||
const handleUpdateStatus = debounce((id) => {
|
||||
setCreate((prevCreate) =>
|
||||
prevCreate.map((create) =>
|
||||
create.id === id ? { ...create, status: !create.status } : create
|
||||
)
|
||||
);
|
||||
toast({
|
||||
render: () => <ToastBox message={"Status changed succesfully.!"} />,
|
||||
});
|
||||
}, 300);
|
||||
|
||||
const filteredData = create?.filter((item) => {
|
||||
// Filter by name (case insensitive)
|
||||
const name = item.sponserName;
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const nameMatches = name.toLowerCase().includes(searchLower);
|
||||
|
||||
return nameMatches;
|
||||
});
|
||||
|
||||
const handleDelete = () => {
|
||||
const updatedCreate = create.filter((create) => create.id !== actionId);
|
||||
|
||||
setTimeout(() => {
|
||||
setSponser(updatedCreate);
|
||||
setDeleteAlert(false);
|
||||
setIsLoading(false);
|
||||
}, 100);
|
||||
setIsLoading(true);
|
||||
};
|
||||
|
||||
const extractedArray = filteredData?.map((item) => ({
|
||||
id: item?.id,
|
||||
"Sponsor name": (
|
||||
<Text
|
||||
justifyContent={"left"}
|
||||
as={"span"}
|
||||
color={"teal.900"}
|
||||
fontWeight={"500"}
|
||||
className="d-flex align-items-center web-text-small"
|
||||
>
|
||||
"{item.sponserName}"
|
||||
</Text>
|
||||
),
|
||||
Address: (
|
||||
<Box w={350} isTruncated={true}>
|
||||
<Text as={"span"} color={"teal.900"} fontWeight={"500"}>
|
||||
" {item.sponserAddress}"
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
"Mobile no": (
|
||||
<Box w={"auto"} isTruncated={true}>
|
||||
<Text as={"span"} color={"teal.900"} fontWeight={"500"}>
|
||||
"{item.mobileNo}"
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
|
||||
"Created At": (
|
||||
<span className="d-flex justify-content-between align-items-center">
|
||||
<Text as={"span"} color={"gray.600"} fontWeight={"500"}>
|
||||
{formatDate(item.createdAt)}
|
||||
</Text>
|
||||
</span>
|
||||
),
|
||||
Action: <Box display={"flex"} justifyContent={"space-between"}></Box>,
|
||||
}));
|
||||
|
||||
|
||||
const destributedAmount = Number(watch().destributedAmount) || 0;
|
||||
|
||||
useEffect(() => {
|
||||
const calculateTotalCharge = () => {
|
||||
const totalChargeValue = charges.reduce(
|
||||
(acc, { value }) => acc + Number(value),
|
||||
0
|
||||
);
|
||||
setTotalCharge(totalChargeValue);
|
||||
};
|
||||
|
||||
const calculateTotalAmount = () => {
|
||||
const totalChargeValue = charges.reduce(
|
||||
(acc, { value }) => acc + Number(value),
|
||||
0
|
||||
);
|
||||
setTotalAmount(destributedAmount + totalChargeValue);
|
||||
};
|
||||
|
||||
calculateTotalCharge();
|
||||
calculateTotalAmount();
|
||||
}, [charges, destributedAmount]);
|
||||
|
||||
const onSubmit = (data) => {
|
||||
// setValue("banner_image", selectedBannerImageData);
|
||||
data.banner_image = selectedBannerImageData;
|
||||
const updatedData = { ...data, status: "Available" };
|
||||
setInvestment([...investment, updatedData]);
|
||||
navigate("/view-io");
|
||||
reset();
|
||||
};
|
||||
|
||||
// Extract options for the select input
|
||||
const createOptions = create.map((item) => ({
|
||||
value: item.sponserName,
|
||||
label: item.sponserName,
|
||||
}));
|
||||
|
||||
const handleBannerImageChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
setBannerImageData(file);
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setSelectedBannerImageData(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const firstField = React.useRef();
|
||||
|
||||
// Handler for file input
|
||||
const handleOtherImageChange = (e) => {
|
||||
const files = Array.from(e.target.files);
|
||||
const newImageData = [...(otherImageData || []), ...files]; // Ensure otherImageData is an array
|
||||
|
||||
setOtherImageData(newImageData);
|
||||
|
||||
const readers = files.map((file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
});
|
||||
|
||||
Promise.all(readers)
|
||||
.then((results) => {
|
||||
setSelectedOtherImageData([
|
||||
...(selectedOtherImageData || []),
|
||||
...results,
|
||||
]); // Ensure selectedOtherImageData is an array
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error reading files:", error);
|
||||
});
|
||||
};
|
||||
// Function to remove a specific image
|
||||
const removeOtherImage = (index) => {
|
||||
const newImageData = otherImageData.filter((_, i) => i !== index);
|
||||
const newSelectedImageData = selectedOtherImageData.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
|
||||
setOtherImageData(newImageData);
|
||||
setSelectedOtherImageData(newSelectedImageData);
|
||||
};
|
||||
|
||||
const formFields = [
|
||||
{
|
||||
label: "IO Name (English)",
|
||||
placeHolder: " ",
|
||||
name: "ioName",
|
||||
type: "text",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "49%",
|
||||
},
|
||||
{
|
||||
label: "IO Name (Arabic)",
|
||||
placeHolder: " ",
|
||||
name: "ioNameArabic",
|
||||
type: "text",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "49%",
|
||||
},
|
||||
{
|
||||
label: "Description (English)",
|
||||
placeHolder: " ",
|
||||
name: "discription",
|
||||
type: "textarea",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "49%",
|
||||
},
|
||||
{
|
||||
label: "Description (Arabic)",
|
||||
placeHolder: " ",
|
||||
name: "discriptionArabic",
|
||||
type: "textarea",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "49%",
|
||||
},
|
||||
{
|
||||
label: "Investment Type (English)",
|
||||
placeHolder: " ",
|
||||
name: "typeName",
|
||||
type: "select",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "49%",
|
||||
options: [
|
||||
{
|
||||
label: "option 1",
|
||||
value: "option 1",
|
||||
},
|
||||
{
|
||||
label: "option 2",
|
||||
value: "option 2",
|
||||
},
|
||||
{
|
||||
label: "option 3",
|
||||
value: "option 3",
|
||||
},
|
||||
{
|
||||
label: "option 4",
|
||||
value: "option 4",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Investment Type (Arabic)",
|
||||
placeHolder: " ",
|
||||
name: "typeNameArabic",
|
||||
type: "select",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "49%",
|
||||
options: [
|
||||
{
|
||||
label: "option 1",
|
||||
value: "option 1",
|
||||
},
|
||||
{
|
||||
label: "option 2",
|
||||
value: "option 2",
|
||||
},
|
||||
{
|
||||
label: "option 3",
|
||||
value: "option 3",
|
||||
},
|
||||
{
|
||||
label: "option 4",
|
||||
value: "option 4",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Sponsor Name (English)",
|
||||
placeHolder: " ",
|
||||
name: "sponserName",
|
||||
type: "text",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "49%",
|
||||
},
|
||||
{
|
||||
label: "Goal Amount (English)",
|
||||
placeHolder: " ",
|
||||
name: "goalAmount",
|
||||
type: "Number",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "49%",
|
||||
},
|
||||
{
|
||||
label: "Minimum Investment Amount (English)",
|
||||
placeHolder: " ",
|
||||
name: "minInvestment",
|
||||
type: "number",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Maximum Investment Amount (English)",
|
||||
placeHolder: " ",
|
||||
name: "maxInvestment",
|
||||
type: "number",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Holding Period (English)",
|
||||
placeHolder: " ",
|
||||
name: "holdingPeriod",
|
||||
type: "number",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Expected Return Estimated (English)",
|
||||
placeHolder: " ",
|
||||
name: "expectedReturn",
|
||||
type: "number",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Closing Date (English)",
|
||||
placeHolder: " ",
|
||||
name: "closingDate",
|
||||
type: "date",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "IO Status (English)",
|
||||
placeHolder: " ",
|
||||
name: "minInvestment",
|
||||
type: "text",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
];
|
||||
|
||||
const keyMerits = [
|
||||
{
|
||||
label: "Name (English)",
|
||||
placeHolder: " ",
|
||||
name: "keyname",
|
||||
type: "text",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Name (Arabic)",
|
||||
placeHolder: " ",
|
||||
name: "keyNameArabic",
|
||||
type: "text",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Icon",
|
||||
placeHolder: " ",
|
||||
name: "iconUpload",
|
||||
type: "fileNormal",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Description (English)",
|
||||
placeHolder: " ",
|
||||
name: "keyDescription",
|
||||
type: "textarea",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Description (Arabic)",
|
||||
placeHolder: " ",
|
||||
name: "keyDescriptionArabic",
|
||||
type: "textarea",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
];
|
||||
|
||||
const images = [
|
||||
{
|
||||
label: "Banner Images ",
|
||||
placeHolder: " ",
|
||||
name: "bannerImages",
|
||||
type: "fileNormal",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
|
||||
{
|
||||
label: "Other Images",
|
||||
placeHolder: " ",
|
||||
name: "otherImage",
|
||||
type: "fileNormal",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
];
|
||||
|
||||
const documents = [
|
||||
{
|
||||
label: "Type",
|
||||
placeHolder: " ",
|
||||
name: "docType",
|
||||
type: "text",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
{
|
||||
label: "Attachment",
|
||||
placeHolder: " ",
|
||||
name: "type",
|
||||
type: "docAttach",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
];
|
||||
|
||||
const Videos = [
|
||||
{
|
||||
label: "Videos",
|
||||
placeHolder: " ",
|
||||
name: "videos",
|
||||
type: "fileNormal",
|
||||
isRequired: true,
|
||||
section: " ",
|
||||
width: "32.3%",
|
||||
},
|
||||
];
|
||||
|
||||
const groupedFields = formFields.reduce((groups, field) => {
|
||||
const { section } = field;
|
||||
if (!groups[section]) {
|
||||
groups[section] = [];
|
||||
}
|
||||
groups[section].push(field);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
const groupedFieldsTwo = keyMerits.reduce((groups, field) => {
|
||||
const { section } = field;
|
||||
if (!groups[section]) {
|
||||
groups[section] = [];
|
||||
}
|
||||
groups[section].push(field);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
const groupedFieldsThree = images.reduce((groups, field) => {
|
||||
const { section } = field;
|
||||
if (!groups[section]) {
|
||||
groups[section] = [];
|
||||
}
|
||||
groups[section].push(field);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
const groupedFieldsFour = documents.reduce((groups, field) => {
|
||||
const { section } = field;
|
||||
if (!groups[section]) {
|
||||
groups[section] = [];
|
||||
}
|
||||
groups[section].push(field);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
const groupedFieldsFive = Videos.reduce((groups, field) => {
|
||||
const { section } = field;
|
||||
if (!groups[section]) {
|
||||
groups[section] = [];
|
||||
}
|
||||
groups[section].push(field);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<Box {...OPACITY_ON_LOAD} overflowY={"scroll"} height={"100vh"} pb={14}>
|
||||
<Tabs mt={4}>
|
||||
<TabList>
|
||||
<Tab
|
||||
fontSize={"sm"}
|
||||
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
|
||||
>
|
||||
IO Details
|
||||
</Tab>
|
||||
<Tab
|
||||
fontSize={"sm"}
|
||||
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
|
||||
>
|
||||
Investment Documents
|
||||
</Tab>
|
||||
<Tab
|
||||
fontSize={"sm"}
|
||||
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
|
||||
>
|
||||
Key Merits
|
||||
</Tab>
|
||||
<Tab
|
||||
fontSize={"sm"}
|
||||
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
|
||||
>
|
||||
IO artifacts
|
||||
</Tab>
|
||||
<Tab
|
||||
fontSize={"sm"}
|
||||
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
|
||||
>
|
||||
Investors
|
||||
</Tab>
|
||||
<Tab
|
||||
fontSize={"sm"}
|
||||
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
|
||||
>
|
||||
IO Cash detail
|
||||
</Tab>
|
||||
<Tab
|
||||
fontSize={"sm"}
|
||||
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
|
||||
>
|
||||
IO NAV detail
|
||||
</Tab>
|
||||
<Tab
|
||||
fontSize={"sm"}
|
||||
_selected={{ color: "#004118", borderBottom: "2px solid #38a169" }}
|
||||
>
|
||||
Distribution
|
||||
</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<FormInputMain
|
||||
width={"23.8%"}
|
||||
groupedFields={groupedFields}
|
||||
control={control}
|
||||
errors={errors}
|
||||
></FormInputMain>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<Box display={'flex'} justifyContent={'space-between'} mb={4}>
|
||||
<Input
|
||||
type="search"
|
||||
width={300}
|
||||
placeholder="Search..."
|
||||
size="sm"
|
||||
rounded="sm"
|
||||
focusBorderColor="green.500"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<Button leftIcon={<AddIcon />} onClick={onOpen}
|
||||
size={"sm"}
|
||||
// width={"44.5%"}
|
||||
fontSize={'xs'}
|
||||
rounded={"sm"}
|
||||
colorScheme='green'
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
<InvestmentDocuments isOpen={isOpen} onClose={onClose} firstField={firstField} />
|
||||
</Box>
|
||||
<DataTable
|
||||
emptyMessage={`We don't have any Sponers `}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={extractedArray}
|
||||
isLoading={isLoading}
|
||||
viewActionId={actionId}
|
||||
setViewActionId={setActionId}
|
||||
// totalPages={10}
|
||||
|
||||
setMouseEnteredId={setMouseEnteredId}
|
||||
setMouseEntered={setMouseEntered}
|
||||
/>
|
||||
<CustomAlertDialog
|
||||
onClose={() => setDeleteAlert(false)}
|
||||
isOpen={deleteAlert}
|
||||
message={"Are you sure you want to delete sponers?"}
|
||||
alertHandler={handleDelete}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<FormInputMain
|
||||
width={"23.8%"}
|
||||
groupedFields={groupedFieldsThree}
|
||||
control={control}
|
||||
errors={errors}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
></FormInputMain>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<FormInputMain
|
||||
width={"23.8%"}
|
||||
groupedFields={groupedFieldsFour}
|
||||
control={control}
|
||||
errors={errors}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
></FormInputMain>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<FormInputMain
|
||||
width={"23.8%"}
|
||||
groupedFields={groupedFieldsFive}
|
||||
control={control}
|
||||
errors={errors}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
></FormInputMain>
|
||||
</TabPanel>
|
||||
<TabPanel></TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateIO;
|
||||
90
src/Components/CurrencyInput.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { Input } from "@chakra-ui/react";
|
||||
|
||||
export const formatCurrency = (value) => {
|
||||
if (value === undefined || value === null) return '';
|
||||
const [integer, decimal] = String(value).split('.');
|
||||
const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
return decimal !== undefined ? `${formattedInteger}.${decimal}` : formattedInteger;
|
||||
};
|
||||
const CurrencyInput = forwardRef(({ value, onChange, ...props }, ref) => {
|
||||
|
||||
const handleChange = (event) => {
|
||||
let { value } = event?.target;
|
||||
|
||||
// Remove non-numeric characters except decimal point
|
||||
value = value.replace(/[^0-9.]/g, '');
|
||||
|
||||
// Ensure only one decimal point
|
||||
const parts = value.split('.');
|
||||
if (parts.length > 2) {
|
||||
value = parts[0] + '.' + parts.slice(1).join('');
|
||||
}
|
||||
|
||||
// Restrict to two decimal places
|
||||
if (parts[1]?.length > 2) {
|
||||
value = parts[0] + '.' + parts[1].slice(0, 2);
|
||||
}
|
||||
|
||||
onChange(value); // Pass the raw value to parent
|
||||
};
|
||||
|
||||
return (
|
||||
<Input
|
||||
{...props}
|
||||
ref={ref} // Forward ref here
|
||||
type="text"
|
||||
value={formatCurrency(value)}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default CurrencyInput;
|
||||
|
||||
|
||||
|
||||
|
||||
// import React, { forwardRef } from 'react';
|
||||
// import { Input } from "@chakra-ui/react";
|
||||
|
||||
// export const formatCurrency = (value) => {
|
||||
// if (value === undefined || value === null) return ''; // Handle undefined or null values
|
||||
// const [integer, decimal] = String(value).split('.'); // Convert value to string before splitting
|
||||
// const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
// return decimal ? `${formattedInteger}.${decimal}` : formattedInteger;
|
||||
// };
|
||||
|
||||
// const CurrencyInput = forwardRef(({ value, onChange, ...props }, ref) => {
|
||||
|
||||
// const handleChange = (event) => {
|
||||
// let { value } = event?.target;
|
||||
|
||||
// // Remove non-numeric characters except decimal point
|
||||
// value = value?.replace(/[^0-9.]/g, '');
|
||||
|
||||
// // Ensure only one decimal point and restrict to two decimal places
|
||||
// const parts = value?.split('.');
|
||||
// if (parts.length > 2) {
|
||||
// value = parts[0] + '.' + parts?.slice(1)?.join('');
|
||||
// }
|
||||
|
||||
// if (parts[1]?.length > 2) {
|
||||
// value = parts[0] + '.' + parts[1]?.slice(0, 2);
|
||||
// }
|
||||
|
||||
// onChange(value); // Pass the raw value to parent or use it directly
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <Input
|
||||
// {...props}
|
||||
// ref={ref} // Forward ref here
|
||||
// type="text"
|
||||
// value={formatCurrency(value)}
|
||||
// onChange={handleChange}
|
||||
// />
|
||||
// );
|
||||
// });
|
||||
|
||||
// export default CurrencyInput;
|
||||
47
src/Components/CustomAlertDialog.jsx
Normal 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;
|
||||
106
src/Components/CutomBreadcrumb.jsx
Normal 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;
|
||||
157
src/Components/DataTable/DataTable.jsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import {
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
Skeleton,
|
||||
TableCaption,
|
||||
Box,
|
||||
} from "@chakra-ui/react";
|
||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
||||
import EmptySearchList from "../EmptySearchList";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import GlobalStateContext from "../../Contexts/GlobalStateContext";
|
||||
|
||||
const DataTable = ({
|
||||
data,
|
||||
setData,
|
||||
isLoading,
|
||||
tableHeadRow,
|
||||
tableHeadRowTwo,
|
||||
emptyMessage,
|
||||
centered,
|
||||
setMouseEntered,
|
||||
setMouseEnteredId,
|
||||
caption,
|
||||
isDraggable
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { slideFromRight } = useContext(GlobalStateContext);
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Skeleton height="32px" my="10px" key={index} />
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleDragEnd = (result) => {
|
||||
if (!result.destination) return;
|
||||
const reorderedItems = Array.from(data);
|
||||
const [removed] = reorderedItems.splice(result.source.index, 1);
|
||||
reorderedItems.splice(result.destination.index, 0, removed);
|
||||
setData(reorderedItems)
|
||||
// console.log("New Order:", reorderedItems.map((item, index) => ({ index, item })));
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer pb={8} overflowX={"scroll"} className="h-auto w-100 table-scroll">
|
||||
{data?.length === 0 ? (
|
||||
<EmptySearchList message={emptyMessage} />
|
||||
) : (
|
||||
<DragDropContext onDragEnd={handleDragEnd}>
|
||||
<Droppable droppableId="table">
|
||||
{(provided) => (
|
||||
<>
|
||||
{/* <Box mb={2}>{caption}</Box> */}
|
||||
<Table size="sm" {...provided.droppableProps} ref={provided.innerRef}>
|
||||
<TableCaption p={0}>{caption}</TableCaption>
|
||||
<Thead backgroundColor="forestGreen.100">
|
||||
<Tr>
|
||||
{tableHeadRow.map((heading, index) => (
|
||||
<Th
|
||||
textAlign={tableHeadRow.length - 1 === index || centered ? "center" : "left"}
|
||||
key={index}
|
||||
p={3}
|
||||
width="100px" // Adjust width as needed
|
||||
color={"#004118"}
|
||||
whiteSpace="normal" // Allow text to wrap
|
||||
wordBreak="normal" // Ensure long words break properly
|
||||
overflowWrap="normal" // Break long words if necessary
|
||||
>
|
||||
{heading}
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody className="web-text-small">
|
||||
{data?.map((item, index) => (
|
||||
item.id && isDraggable ? (
|
||||
<Draggable key={item.id.toString()} draggableId={item.id.toString()} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<Tr
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
transition={"0s all"}
|
||||
_hover={{
|
||||
bg: "blue.50",
|
||||
cursor: "grab",
|
||||
}}
|
||||
bg={snapshot.isDragging ? "blue.100" : index % 2 ? "white" : "forestGreen.50"}
|
||||
boxShadow={snapshot.isDragging ? "0 0 1em rgba(0, 0, 0, 0.2)" : "none"}
|
||||
|
||||
>
|
||||
{tableHeadRow.map((heading, i) => (
|
||||
<Td
|
||||
textAlign={tableHeadRow.length - 1 === i || centered ? "center" : "left"}
|
||||
color={"gray.600"}
|
||||
key={i}
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
className="web-text-small"
|
||||
>
|
||||
<Skeleton fadeDuration={index / 12} isLoaded={!isLoading}>
|
||||
{item[heading]}
|
||||
</Skeleton>
|
||||
</Td>
|
||||
))}
|
||||
</Tr>
|
||||
)}
|
||||
</Draggable>
|
||||
) : (
|
||||
<Tr bg={index % 2 ? "forestGreen.50" : "white"} key={index}>
|
||||
{tableHeadRow.map((heading, i) => (
|
||||
<Td
|
||||
textAlign={tableHeadRow.length - 1 === i || centered ? "center" : "left"}
|
||||
color={"gray.600"}
|
||||
key={i}
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
className="web-text-small"
|
||||
w={400}
|
||||
>
|
||||
<Skeleton display={'flex'} alignItems={'center'} justifyContent={tableHeadRow.length - 1 === i || centered ? "center" : "start"} h={5} fadeDuration={index / 12} isLoaded={!isLoading}>
|
||||
{item[heading]}
|
||||
</Skeleton>
|
||||
</Td>
|
||||
))}
|
||||
</Tr>
|
||||
)
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
)}
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable;
|
||||
115
src/Components/DataTable/NormalTable.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
Skeleton,
|
||||
TableCaption,
|
||||
Tfoot,
|
||||
} from "@chakra-ui/react";
|
||||
import EmptySearchList from "../EmptySearchList";
|
||||
import { TABLE_PAGINATION } from "../../Constants/Paginations";
|
||||
|
||||
const DataTable = ({
|
||||
data,
|
||||
isLoading,
|
||||
tableHeadRow,
|
||||
emptyMessage,
|
||||
centered,
|
||||
total,
|
||||
}) => {
|
||||
console.log(data);
|
||||
|
||||
const columnWidth =
|
||||
data && data[0]
|
||||
? `${(100 / Object.keys(data[0]).length).toFixed(2)}%`
|
||||
: "auto";
|
||||
return (
|
||||
<TableContainer overflowX={"auto"} className="h-auto mb-3 w-100 table-scroll">
|
||||
{data?.length === 0 ? (
|
||||
<EmptySearchList message={emptyMessage} />
|
||||
) : (
|
||||
<Table size="sm">
|
||||
<TableCaption p={total ? 0 : null}>
|
||||
{total ? total : "Tanami v1.0.0"}
|
||||
</TableCaption>
|
||||
<Thead backgroundColor="forestGreen.100">
|
||||
<Tr>
|
||||
{tableHeadRow.map((heading, index) => (
|
||||
<Th
|
||||
textAlign={
|
||||
tableHeadRow.length - 1 === index || centered
|
||||
? "center"
|
||||
: "left"
|
||||
}
|
||||
key={index}
|
||||
p={3}
|
||||
width="20px" // Adjust width as needed
|
||||
color={"#004118"}
|
||||
whiteSpace="normal" // Allow text to wrap
|
||||
wordBreak="normal" // Ensure long words break properly
|
||||
overflowWrap="normal" // Break long words if necessary
|
||||
textTransform={"none"}
|
||||
>
|
||||
{isLoading ? <Skeleton height="20px" /> : heading}
|
||||
{/* {heading} */}
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody className="web-text-small">
|
||||
{isLoading
|
||||
? Array.from({ length: TABLE_PAGINATION?.size }).map(
|
||||
(_, index) => (
|
||||
<Tr bg={index % 2 === 0 ? "" : "forestGreen.50"} key={index}>
|
||||
{tableHeadRow.map((_, i) => (
|
||||
<Td
|
||||
width={"fit-content"}
|
||||
key={i}
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
className="web-text-small"
|
||||
w={columnWidth}
|
||||
>
|
||||
<Skeleton height="20px" mb={1} mt={1} />
|
||||
</Td>
|
||||
))}
|
||||
</Tr>
|
||||
)
|
||||
)
|
||||
: data?.map((item, index) => (
|
||||
<Tr bg={index % 2 === 0 ? "" : "forestGreen.50"} key={index}>
|
||||
{tableHeadRow.map((heading, i) => (
|
||||
<Td
|
||||
textAlign={
|
||||
tableHeadRow?.length - 1 === i || centered
|
||||
? "center"
|
||||
: "left"
|
||||
}
|
||||
color={"gray.600"}
|
||||
key={i}
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
className="web-text-small"
|
||||
>
|
||||
{item[heading]}
|
||||
</Td>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable;
|
||||
55
src/Components/Doughnut/ApexDonut.jsx
Normal 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;
|
||||
66
src/Components/Doughnut/ApexLine.jsx
Normal 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;
|
||||
39
src/Components/Doughnut/DonutChart.jsx
Normal 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;
|
||||
80
src/Components/Doughnut/LineChart.jsx
Normal 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;
|
||||
41
src/Components/DummyComponent.jsx
Normal 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;
|
||||
23
src/Components/EmptySearchList.jsx
Normal 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
|
||||
549
src/Components/FormField.jsx
Normal file
@@ -0,0 +1,549 @@
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Input,
|
||||
Textarea,
|
||||
Select,
|
||||
Checkbox,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
Stack,
|
||||
Box,
|
||||
Heading,
|
||||
FormHelperText,
|
||||
Kbd,
|
||||
Image,
|
||||
Text,
|
||||
Table,
|
||||
Thead,
|
||||
Tr,
|
||||
Th,
|
||||
Tbody,
|
||||
Td,
|
||||
InputGroup,
|
||||
InputRightAddon,
|
||||
} from "@chakra-ui/react";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
import { motion } from "framer-motion";
|
||||
import { AddIcon, CloseIcon } from "@chakra-ui/icons";
|
||||
import CurrencyInput from "./CurrencyInput";
|
||||
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
export const formatDatee = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are zero-based
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
const defaultDate = "8/2/2024";
|
||||
|
||||
// Format the default date as YYYY-MM-DD
|
||||
const formattedDate = formatDatee(defaultDate);
|
||||
|
||||
const FormField = ({
|
||||
label,
|
||||
control,
|
||||
name,
|
||||
id,
|
||||
type = "text",
|
||||
options = [],
|
||||
errors,
|
||||
isRequired,
|
||||
rules,
|
||||
arabic,
|
||||
placeHolder,
|
||||
helperText,
|
||||
multiple,
|
||||
handleImageChange,
|
||||
selectedImageData,
|
||||
setSelectedImageData,
|
||||
removeImage,
|
||||
imageData,
|
||||
width,
|
||||
value,
|
||||
handleInputChange,
|
||||
align,
|
||||
maxLength,
|
||||
dateValue,
|
||||
...props
|
||||
}) => (
|
||||
<FormControl
|
||||
w={width ? width : "49%"}
|
||||
isInvalid={errors[name]}
|
||||
isRequired={type === "date" ? true: isRequired}
|
||||
mb={2}
|
||||
>
|
||||
<FormLabel textAlign={"left"} fontSize={"xs"} color={"gray.600"}>
|
||||
{label}
|
||||
</FormLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
defaultValue={value}
|
||||
rules={rules}
|
||||
render={({ field }) => {
|
||||
if (type === "select") {
|
||||
return (
|
||||
<Select
|
||||
bg={"#F5F8F6"}
|
||||
focusBorderColor="forestGreen.300"
|
||||
size={"sm"}
|
||||
{...field}
|
||||
{...props}
|
||||
cursor={"pointer"}
|
||||
fontSize={"sm"}
|
||||
placeholder={placeHolder ? placeHolder : label}
|
||||
textAlign={arabic ? "right" : "left"}
|
||||
_placeholder={{ fontSize: "sm" }}
|
||||
rounded={"sm"}
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
<option key={index} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
} else if (type === "textarea") {
|
||||
return (
|
||||
<Textarea
|
||||
focusBorderColor="forestGreen.400"
|
||||
size={"sm"}
|
||||
{...field}
|
||||
{...props}
|
||||
placeholder={placeHolder ? placeHolder : label}
|
||||
textAlign={arabic ? "right" : "left"}
|
||||
_placeholder={{ fontSize: "sm" }}
|
||||
rounded={"sm"}
|
||||
resize={"none"}
|
||||
rows={2}
|
||||
bg={"#F5F8F6"}
|
||||
maxLength={maxLength} // Set the maximum character limit
|
||||
/>
|
||||
);
|
||||
} else if (type === "checkbox") {
|
||||
return (
|
||||
<Checkbox
|
||||
size={"sm"}
|
||||
{...field}
|
||||
{...props}
|
||||
textAlign={arabic ? "right" : "left"}
|
||||
>
|
||||
{label}
|
||||
</Checkbox>
|
||||
);
|
||||
} else if (type === "radio") {
|
||||
return (
|
||||
<RadioGroup bg={"#F5F8F6"} {...field} {...props}>
|
||||
<Stack direction="row">
|
||||
{options.map((option, index) => (
|
||||
<Radio key={index} value={option.value}>
|
||||
{option.label}
|
||||
</Radio>
|
||||
))}
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
);
|
||||
} else if (type === "file") {
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
borderColor="gray.300"
|
||||
borderStyle="dashed"
|
||||
borderWidth="2px"
|
||||
rounded="md"
|
||||
shadow="sm"
|
||||
role="group"
|
||||
transition="all 150ms ease-in-out"
|
||||
_hover={{
|
||||
shadow: "md",
|
||||
}}
|
||||
as={motion.div}
|
||||
initial="rest"
|
||||
animate="rest"
|
||||
whileHover="hover"
|
||||
height={"105px"}
|
||||
className="pointer"
|
||||
>
|
||||
<Box position="relative" height="100%" width="100%">
|
||||
<Box
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
height="100%"
|
||||
width="100%"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Stack
|
||||
height="100%"
|
||||
width="100%"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
>
|
||||
<span
|
||||
className="d-flex flex-column align-items-center pointer"
|
||||
spacing="1"
|
||||
>
|
||||
<Heading
|
||||
fontSize="lg"
|
||||
color="gray.700"
|
||||
fontWeight="bold"
|
||||
cursor={"pointer"}
|
||||
>
|
||||
Drop images here
|
||||
</Heading>
|
||||
<span
|
||||
fontWeight="light"
|
||||
className="web-text-large text-secondary text-center pointer"
|
||||
>
|
||||
or click to upload
|
||||
</span>
|
||||
</span>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Input
|
||||
{...field}
|
||||
{...props}
|
||||
type="file"
|
||||
height="100%"
|
||||
width="100%"
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
opacity="0"
|
||||
aria-hidden="true"
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
handleImageChange(e);
|
||||
}}
|
||||
onDrop={(e) => {
|
||||
field.onChange(e);
|
||||
handleImageChange(e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
} else if (type === "fileNormal") {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
id={id}
|
||||
{...field}
|
||||
{...props}
|
||||
multiple={multiple} // Support for multiple file uploads
|
||||
accept="image/*"
|
||||
type="file"
|
||||
className="web-text-medium form-control rounded-1"
|
||||
size="sm"
|
||||
bg={"#F5F8F6"}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
handleImageChange(e);
|
||||
}}
|
||||
/>
|
||||
{multiple && (
|
||||
<FormHelperText className="web-text-small">
|
||||
You can select multiple images using{" "}
|
||||
<span className="text-dark">
|
||||
<Kbd size={"sm"} className="text-dark">
|
||||
ctrl
|
||||
</Kbd>{" "}
|
||||
+ <Kbd className="text-dark">select</Kbd>
|
||||
</span>
|
||||
</FormHelperText>
|
||||
)}
|
||||
{selectedImageData &&
|
||||
(multiple ? (
|
||||
selectedImageData?.length > 0 && (
|
||||
<Box
|
||||
mt={4}
|
||||
width={"100%"}
|
||||
display="flex"
|
||||
flexWrap="wrap"
|
||||
gap={4}
|
||||
>
|
||||
{selectedImageData?.map((imageDataLink, index) => (
|
||||
<Box key={index} width={"100px"}>
|
||||
<Image
|
||||
rounded={"md"}
|
||||
objectFit={"cover"}
|
||||
src={imageDataLink}
|
||||
alt={`profile-${index}`}
|
||||
width={100}
|
||||
height={100}
|
||||
/>
|
||||
<Box
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
position={"relative"}
|
||||
>
|
||||
<CloseIcon
|
||||
onClick={() => removeImage(index)}
|
||||
bg={"#fff"}
|
||||
className="link pointer"
|
||||
p={1}
|
||||
fontSize={"lg"}
|
||||
color={"red"}
|
||||
fontWeight={"500"}
|
||||
rounded={"md"}
|
||||
position={"absolute"}
|
||||
bottom={0}
|
||||
right={0}
|
||||
/>
|
||||
<Text
|
||||
as={"span"}
|
||||
fontSize={"sm"}
|
||||
fontWeight={"500"}
|
||||
mt={1}
|
||||
isTruncated={true}
|
||||
>
|
||||
{imageData[index]?.name}
|
||||
</Text>
|
||||
<Text
|
||||
as={"span"}
|
||||
fontSize={"sm"}
|
||||
fontStyle={"italic"}
|
||||
>
|
||||
{(imageData[index]?.size / (1024 * 1024)).toFixed(
|
||||
2
|
||||
)}{" "}
|
||||
mb
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
<AddIcon
|
||||
onClick={() => document.getElementById(id).click()}
|
||||
rounded={"md"}
|
||||
width={50}
|
||||
height={50}
|
||||
mt={26}
|
||||
p={4}
|
||||
cursor={"pointer"}
|
||||
className="link"
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
) : (
|
||||
<Box mt={5} width={"49%"}>
|
||||
<Image
|
||||
rounded={"md"}
|
||||
objectFit={"cover"}
|
||||
src={selectedImageData}
|
||||
alt="profile"
|
||||
width={100}
|
||||
height={100}
|
||||
/>
|
||||
<Box
|
||||
w={"30%"}
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
position={"relative"}
|
||||
>
|
||||
<CloseIcon
|
||||
onClick={() => setSelectedImageData(null)}
|
||||
className="link pointer"
|
||||
p={1}
|
||||
fontSize={"lg"}
|
||||
color={"red"}
|
||||
fontWeight={"500"}
|
||||
rounded={"md"}
|
||||
position={"absolute"}
|
||||
top={1}
|
||||
right={0}
|
||||
/>
|
||||
<Text
|
||||
as={"span"}
|
||||
fontSize={"xs"}
|
||||
w={"70%"}
|
||||
fontWeight={"500"}
|
||||
mt={1}
|
||||
isTruncated={true}
|
||||
>
|
||||
{imageData?.name}
|
||||
</Text>
|
||||
<Text as={"span"} fontSize={"xs"} fontStyle={"italic"}>
|
||||
{(imageData?.size / (1024 * 1024)).toFixed(2)} mb
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
} else if (type === "table") {
|
||||
return (
|
||||
<Table w={"100%"} variant="simple">
|
||||
<Thead>
|
||||
<Tr>
|
||||
{value?.map((item, index) => (
|
||||
<Th
|
||||
border={"none"}
|
||||
p={2}
|
||||
textTransform={"none"}
|
||||
key={index}
|
||||
>
|
||||
<Box
|
||||
as="span"
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
gap={2}
|
||||
>
|
||||
<Image
|
||||
objectFit={"cover"}
|
||||
opacity={0.9}
|
||||
rounded={"full"}
|
||||
w={6}
|
||||
h={6}
|
||||
src={
|
||||
import.meta.env.VITE_IMAGE_URL +
|
||||
item?.logo
|
||||
}
|
||||
/>
|
||||
{item.country === "United Arab Emirates"
|
||||
? "UAE"
|
||||
: item.country}
|
||||
</Box>
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
{value?.map((item, index) => (
|
||||
<Td
|
||||
p={2}
|
||||
color={"gray.600"}
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
className="web-text-small"
|
||||
key={index}
|
||||
border={"none"}
|
||||
>
|
||||
<InputGroup size="sm">
|
||||
<Input
|
||||
isRequired={true}
|
||||
bg={"#F5F8F6"}
|
||||
focusBorderColor="forestGreen.300"
|
||||
// border="1px solid #000"
|
||||
size={"sm"}
|
||||
fontSize={"sm"}
|
||||
rounded={"sm"}
|
||||
type="number"
|
||||
value={item.value}
|
||||
textAlign={"right"}
|
||||
placeholder={"00.00"}
|
||||
onChange={(e) =>
|
||||
handleInputChange(index, e.target.value)
|
||||
}
|
||||
border={"1px solid #e2e8f0"}
|
||||
/>
|
||||
<InputRightAddon
|
||||
fontSize={"xs"}
|
||||
fontWeight={600}
|
||||
color={"forestGreen.500"}
|
||||
>
|
||||
{item?.curr}
|
||||
</InputRightAddon>
|
||||
</InputGroup>
|
||||
</Td>
|
||||
))}
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
} else if(type === 'date'){
|
||||
return (
|
||||
<Input
|
||||
position={'relative'}
|
||||
bg={"#F5F8F6"}
|
||||
focusBorderColor="forestGreen.300"
|
||||
size={"sm"}
|
||||
fontSize={"sm"}
|
||||
rounded={"sm"}
|
||||
type={"date"}
|
||||
{...field}
|
||||
{...props}
|
||||
placeholder={placeHolder ? placeHolder : label}
|
||||
textAlign={arabic ? "right" : align ? align : "left"}
|
||||
_placeholder={{ fontSize: "sm" }}
|
||||
// min={type === "date" ? today : undefined}
|
||||
// maxLength={maxLength}
|
||||
// defaultValue={type === "date" && "2023-07-26" : undefined}
|
||||
// defaultValue={value}
|
||||
// value={dateValue}
|
||||
|
||||
/>
|
||||
);
|
||||
|
||||
}else if(type === 'number'){
|
||||
return (
|
||||
<CurrencyInput
|
||||
position={'relative'}
|
||||
bg={"#F5F8F6"}
|
||||
focusBorderColor="forestGreen.300"
|
||||
size={"sm"}
|
||||
fontSize={"sm"}
|
||||
rounded={"sm"}
|
||||
{...field}
|
||||
{...props}
|
||||
placeholder={placeHolder ? placeHolder : label}
|
||||
textAlign={"right"}
|
||||
_placeholder={{ fontSize: "sm" }}
|
||||
maxLength={maxLength}
|
||||
// defaultValue={type === "date" && "2023-07-26" : undefined}
|
||||
// defaultValue={value}
|
||||
// value={dateValue}
|
||||
|
||||
/>
|
||||
);} else{
|
||||
return (
|
||||
<Input
|
||||
bg={"#F5F8F6"}
|
||||
focusBorderColor="forestGreen.300"
|
||||
size={"sm"}
|
||||
fontSize={"sm"}
|
||||
rounded={"sm"}
|
||||
type={type}
|
||||
{...field}
|
||||
{...props}
|
||||
placeholder={placeHolder ? placeHolder : label}
|
||||
textAlign={arabic || type === "number" ? "right" : align ? align : "left"}
|
||||
_placeholder={{ fontSize: "sm" }}
|
||||
// min={type === "date" ? today : undefined}
|
||||
maxLength={maxLength}
|
||||
// defaultValue={type === "date" && "2023-07-26" : undefined}
|
||||
// value={"2023-07-26"}
|
||||
|
||||
/>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{errors[name] && (
|
||||
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
|
||||
<TiWarning className="fw-bold fs-5 " /> {errors[name].message}
|
||||
</span>
|
||||
)}
|
||||
{helperText && (
|
||||
<FormHelperText color={'gray.500'} className="web-text-small">{helperText}</FormHelperText>
|
||||
)}
|
||||
{type === "file" && (
|
||||
<FormHelperText className="web-text-small">
|
||||
Maximum limit of image is 10MB.
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
export default FormField;
|
||||
256
src/Components/FormFieldMainReference.jsx
Normal 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;
|
||||
138
src/Components/FormInputMain.jsx
Normal 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;
|
||||
157
src/Components/FormInputView.jsx
Normal 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;
|
||||
13
src/Components/Functions/DebounceFunction.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
|
||||
const DebounceFunction = (func, delay) => {
|
||||
let timeoutId;
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
|
||||
export default DebounceFunction;
|
||||
8
src/Components/Functions/FileNameAlter.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
const extractFilename = (filePath) => {
|
||||
// Use the split method to break the path into parts based on '/'
|
||||
const parts = filePath.split('/');
|
||||
// Return the last part, which is the filename
|
||||
return parts[parts.length - 1];
|
||||
};
|
||||
|
||||
export default extractFilename ;
|
||||
35
src/Components/Functions/TimeCalculator.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const TimeCalculator = ({ JoiningDate }) => {
|
||||
const [elapsedTime, setElapsedTime] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const calculateTimeElapsed = () => {
|
||||
const startDate = new Date(JoiningDate);
|
||||
const currentDate = new Date();
|
||||
const difference = currentDate - startDate;
|
||||
setElapsedTime(Math.floor(difference / 1000)); // Convert milliseconds to seconds
|
||||
};
|
||||
|
||||
// Calculate time elapsed initially
|
||||
calculateTimeElapsed();
|
||||
|
||||
// Update time elapsed every second
|
||||
const intervalId = setInterval(calculateTimeElapsed, 1000);
|
||||
|
||||
// Clear interval on component unmount
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
// Convert seconds to years, months, days, hours, minutes, and remaining seconds
|
||||
const years = Math.floor(elapsedTime / (365 * 24 * 60 * 60));
|
||||
const months = Math.floor((elapsedTime % (365 * 24 * 60 * 60)) / (30 * 24 * 60 * 60)); // Approximating a month to 30 days
|
||||
const days = Math.floor((elapsedTime % (30 * 24 * 60 * 60)) / (24 * 60 * 60));
|
||||
const hours = Math.floor((elapsedTime % (24 * 60 * 60)) / (60 * 60));
|
||||
const minutes = Math.floor((elapsedTime % (60 * 60)) / 60);
|
||||
const seconds = elapsedTime % 60;
|
||||
|
||||
return `${years}Y ${months}M ${hours}H ${minutes}M ${seconds}S`;
|
||||
};
|
||||
|
||||
export default TimeCalculator;
|
||||
11
src/Components/Functions/Toaster.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useToast } from "@chakra-ui/react";
|
||||
|
||||
export const toaster = () => {
|
||||
const toast = useToast();
|
||||
|
||||
return toast({
|
||||
title: "Loged In",
|
||||
status: "success",
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
16
src/Components/Functions/UTCConvertor.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export const formatDate = (dateString) => {
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
// Create a new Date object from the provided string
|
||||
const date = new Date(dateString);
|
||||
|
||||
// Extract day, month, and year components
|
||||
const day = date.getDate();
|
||||
const month = months[date.getMonth()];
|
||||
const year = date.getFullYear();
|
||||
|
||||
// Format the date in "DD-Mon-YYYY" format
|
||||
const formattedDate = `${day}-${month}-${year}`;
|
||||
|
||||
return formattedDate;
|
||||
}
|
||||
82
src/Components/Header.jsx
Normal 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;
|
||||
167
src/Components/HeaderMain copy.jsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
HStack,
|
||||
Image,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { MdOutlineHeadsetMic, MdNotificationsNone } from "react-icons/md";
|
||||
import { RiWallet3Line } from "react-icons/ri";
|
||||
import { SearchIcon, ArrowLeftIcon, ArrowRightIcon } from "@chakra-ui/icons";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import GlobalStateContext from "../Contexts/GlobalStateContext";
|
||||
import mainLogo from "../assets/optifii_logo.svg";
|
||||
import PrimaryButton from "./Buttons/PrimaryButton";
|
||||
import Notifications from "../Pages/Notifications/Notifications";
|
||||
|
||||
const HeaderMain = ({
|
||||
logOutHandler,
|
||||
slideDirecttion,
|
||||
isDrawerOpen,
|
||||
toggleDrawer,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { image } = useContext(GlobalStateContext);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
// For controlling the modal
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<Box
|
||||
w={"100%"}
|
||||
h={{ base: "8%", xl: "6%" }}
|
||||
position={"relative"}
|
||||
className={`pt-2 pb-2 fw-400 border-bottom d-flex ${
|
||||
slideDirecttion ? "ps-2" : ""
|
||||
} justify-content-between align-items-center`}
|
||||
zIndex={999}
|
||||
>
|
||||
<Box display={"flex"} alignItems={"center"}>
|
||||
<Box
|
||||
w={"230px"}
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<Box className={`d-flex ${true ? "justify-content-start" : "justify-content-center"} p-4 pt-0 pb-0 position-relative`}>
|
||||
<Image
|
||||
style={{ width: 95 }}
|
||||
src={mainLogo}
|
||||
alt="Logo"
|
||||
onClick={() => navigate("/home")}
|
||||
cursor={"pointer"}
|
||||
/>
|
||||
</Box>
|
||||
<Button
|
||||
colorScheme={"forestGreen"}
|
||||
rounded={"lg"}
|
||||
onClick={toggleDrawer}
|
||||
style={{ width: "28px", height: "28px", minWidth: "28px", zIndex: 99, backgroundColor: "#6311CB29" }}
|
||||
>
|
||||
{isDrawerOpen ? <ArrowLeftIcon className="web-text-small" color={"#6311CB"} /> : <ArrowRightIcon className="web-text-small" color={"#6311CB"} />}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<InputGroup width={350} size="sm" ml={5}>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<SearchIcon color="gray.300" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Type to search..."
|
||||
rounded="md"
|
||||
focusBorderColor="#3725EA"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Box>
|
||||
<Box display={"flex"} justifyContent={"space-between"}>
|
||||
<Box display={"flex"} gap={2}>
|
||||
<Box className="header-icon" display={"flex"} gap={2} alignItems={"center"} me={10}>
|
||||
<NavLink
|
||||
to="/contact-us"
|
||||
style={({ isActive }) => ({
|
||||
borderBottom: isActive ? '1px solid #3725EA' : 'none',
|
||||
})}
|
||||
>
|
||||
<Button size={"sm"} bg={"none"} p={0}>
|
||||
<MdOutlineHeadsetMic fontSize={"18px"} />
|
||||
</Button>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/your-wallet"
|
||||
style={({ isActive }) => ({
|
||||
borderBottom: isActive ? '1px solid #3725EA' : 'none',
|
||||
})}
|
||||
>
|
||||
<Button size={"sm"} bg={"none"} p={0}>
|
||||
<RiWallet3Line fontSize={"18px"} />
|
||||
</Button>
|
||||
</NavLink>
|
||||
<Button size={"sm"} bg={"none"} p={0} onClick={onOpen}>
|
||||
<MdNotificationsNone fontSize={"20px"} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box me={4} gap={2} className="d-flex justify-content-center">
|
||||
<Popover placement="bottom-start">
|
||||
<Portal>
|
||||
<PopoverContent mt={6} maxW="450px">
|
||||
<PopoverArrow />
|
||||
<PopoverBody
|
||||
py={6}
|
||||
gap={2}
|
||||
display={"flex"}
|
||||
justifyContent={"center"}
|
||||
flexDirection={"column"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Avatar size="2xl" name="Segun Adebayo" src={image} />
|
||||
<Text as={"span"} fontSize={"md"} fontWeight={600}>
|
||||
Kartikey Gautam
|
||||
</Text>
|
||||
<PrimaryButton onClick={() => navigate("/profile")} title={"View Profile"} />
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
|
||||
<PopoverTrigger>
|
||||
<Box className="d-flex pointer align-items-center">
|
||||
<Avatar src={image} size={"sm"} bg={"#210a33"} />
|
||||
<Box style={{ display: "flex" }} className="overflow-hidden ms-3 flex-column">
|
||||
<Text fontWeight={600} as={"span"} fontSize={"md"}>
|
||||
Kartikey Gautam
|
||||
</Text>
|
||||
<Text as={"span"} fontSize={"xs"}>
|
||||
Website Development India
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</PopoverTrigger>
|
||||
</Popover>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Include the Notifications modal */}
|
||||
<Notifications isOpen={isOpen} onClose={onClose} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderMain;
|
||||
|
||||
145
src/Components/HeaderMain.jsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
HStack,
|
||||
Image,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { MdOutlineHeadsetMic, MdNotificationsNone } from "react-icons/md";
|
||||
import { RiWallet3Line } from "react-icons/ri";
|
||||
import { SearchIcon, ArrowLeftIcon, ArrowRightIcon } from "@chakra-ui/icons";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import GlobalStateContext from "../Contexts/GlobalStateContext";
|
||||
import mainLogo from "../assets/optifii_white.svg";
|
||||
import PrimaryButton from "./Buttons/PrimaryButton";
|
||||
// import Notifications from "../Pages/Notifications/Notifications";
|
||||
|
||||
const HeaderMain = ({
|
||||
logOutHandler,
|
||||
slideDirecttion,
|
||||
isDrawerOpen,
|
||||
toggleDrawer,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { image } = useContext(GlobalStateContext);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
// For controlling the modal
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<Box
|
||||
w={"100%"}
|
||||
h={{ base: "8%", xl: "6%" }}
|
||||
position={"relative"}
|
||||
className={`pt-2 pb-2 fw-400 border-bottom d-flex ${
|
||||
slideDirecttion ? "ps-2" : ""
|
||||
} justify-content-between align-items-center`}
|
||||
zIndex={999}
|
||||
bgGradient="linear(to-r, #1A0436, #6311CB)" // Linear gradient
|
||||
color={"#fff"}
|
||||
>
|
||||
<Box display={"flex"} alignItems={"center"}>
|
||||
<Box
|
||||
w={"230px"}
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<Box className={`d-flex ${true ? "justify-content-start" : "justify-content-center"} p-4 pt-0 pb-0 position-relative`}>
|
||||
<Image
|
||||
style={{ width: 100 }}
|
||||
src={mainLogo}
|
||||
alt="Logo"
|
||||
onClick={() => navigate("/home")}
|
||||
cursor={"pointer"}
|
||||
/>
|
||||
</Box>
|
||||
<Button
|
||||
colorScheme={"white"}
|
||||
rounded={"lg"}
|
||||
onClick={toggleDrawer}
|
||||
style={{ width: "28px", height: "28px", minWidth: "28px", zIndex: 99, backgroundColor: "#ffffff29" }}
|
||||
>
|
||||
{isDrawerOpen ? <ArrowLeftIcon className="web-text-small" color={"#fff"} /> : <ArrowRightIcon className="web-text-small" color={"#fff"} />}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<InputGroup width={350} size="sm" ml={5}>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<SearchIcon color="gray.100" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Type to search..."
|
||||
rounded="md"
|
||||
bg={'purple.800'}
|
||||
border={'none'}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Box>
|
||||
<Box display={"flex"} justifyContent={"space-between"}>
|
||||
<Box display={"flex"} gap={2}>
|
||||
<Box me={4} gap={2} className="d-flex justify-content-center">
|
||||
<Popover placement="bottom-start">
|
||||
<Portal>
|
||||
<PopoverContent mt={6} maxW="450px">
|
||||
<PopoverArrow />
|
||||
<PopoverBody
|
||||
py={6}
|
||||
gap={2}
|
||||
display={"flex"}
|
||||
justifyContent={"center"}
|
||||
flexDirection={"column"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Avatar size="2xl" name="Segun Adebayo" src={image} />
|
||||
<Text as={"span"} fontSize={"md"} fontWeight={600}>
|
||||
Kartikey Gautam
|
||||
</Text>
|
||||
<PrimaryButton onClick={() => navigate("/profile")} title={"View Profile"} />
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
|
||||
<PopoverTrigger>
|
||||
<Box className="d-flex pointer align-items-center">
|
||||
<Avatar src={image} size={"sm"} />
|
||||
<Box style={{ display: "flex" }} className="overflow-hidden ms-3 flex-column">
|
||||
<Text fontWeight={600} as={"span"} fontSize={"md"}>
|
||||
Kartikey Gautam
|
||||
</Text>
|
||||
<Text as={"span"} fontSize={"xs"}>
|
||||
Website Development India
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</PopoverTrigger>
|
||||
</Popover>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Include the Notifications modal */}
|
||||
{/* <Notifications isOpen={isOpen} onClose={onClose} /> */}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderMain;
|
||||
|
||||
77
src/Components/ImageDropBox.jsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
AspectRatio,
|
||||
Box,
|
||||
Container,
|
||||
Heading,
|
||||
Input,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
|
||||
import { motion, useAnimation } from "framer-motion";
|
||||
|
||||
const ImageDropBox = () => {
|
||||
return (
|
||||
<Box
|
||||
borderColor="gray.300"
|
||||
borderStyle="dashed"
|
||||
borderWidth="2px"
|
||||
rounded="md"
|
||||
shadow="sm"
|
||||
role="group"
|
||||
transition="all 150ms ease-in-out"
|
||||
_hover={{
|
||||
shadow: "md",
|
||||
}}
|
||||
as={motion.div}
|
||||
initial="rest"
|
||||
animate="rest"
|
||||
whileHover="hover"
|
||||
height={"105px"}
|
||||
className="pointer"
|
||||
>
|
||||
<Box position="relative" height="100%" width="100%">
|
||||
<Box
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
height="100%"
|
||||
width="100%"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Stack
|
||||
height="100%"
|
||||
width="100%"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
>
|
||||
<span className="d-flex flex-column align-items-center" spacing="1">
|
||||
<Heading fontSize="lg" color="gray.700" fontWeight="bold">
|
||||
Drop images here
|
||||
</Heading>
|
||||
<span fontWeight="light" className="web-text-large text-secondary text-center">or click to upload</span>
|
||||
</span>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Input
|
||||
type="file"
|
||||
height="100%"
|
||||
width="100%"
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
opacity="0"
|
||||
aria-hidden="true"
|
||||
accept="image/*"
|
||||
// onDragEnter={startAnimation}
|
||||
// onDragLeave={stopAnimation}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageDropBox;
|
||||
38
src/Components/ImageViewer.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
Button,
|
||||
Image,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
|
||||
const ImageViewer = ({ src, isOpen, onClose }) => {
|
||||
return (
|
||||
<>
|
||||
{/* <Button onClick={onOpen}>Open Modal</Button> */}
|
||||
|
||||
<Modal size={"xl"} isCentered isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
{/* <ModalCloseButton /> */}
|
||||
<ModalBody p={4}>
|
||||
<Image
|
||||
rounded={6}
|
||||
w={"100%"}
|
||||
h={"100%"}
|
||||
src={import.meta.env.VITE_IMAGE_URL + src}
|
||||
/>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageViewer;
|
||||
44
src/Components/Inputs/Input01.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const input01Style = {
|
||||
width: "100%",
|
||||
border: "1px solid #ADB3BD",
|
||||
outline: "none",
|
||||
height: 50,
|
||||
fontSize: "16px",
|
||||
};
|
||||
|
||||
const focusedInputStyle = {
|
||||
border: "1px solid #38187C", // Change border color for focused state
|
||||
};
|
||||
|
||||
const Input01 = ({ type, placeholder, onChange, name, register }) => {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const handleFocus = () => {
|
||||
setIsFocused(true);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsFocused(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<input
|
||||
style={
|
||||
|
||||
isFocused ? { ...input01Style, ...focusedInputStyle } : input01Style
|
||||
}
|
||||
type={type}
|
||||
name={name}
|
||||
className="rounded-3 ps-3 d-flex align-items-center"
|
||||
onChange={onChange}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
placeholder={placeholder}
|
||||
{...register}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Input01;
|
||||
182
src/Components/InvestmentCard/InvestmentCard.jsx
Normal 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;
|
||||
105
src/Components/Loaders/FullscreenLoaders.css
Normal file
@@ -0,0 +1,105 @@
|
||||
/* From Uiverse.io by abrahamcalsin */
|
||||
.dot-spinner {
|
||||
--uib-size: 2.8rem;
|
||||
--uib-speed: .9s;
|
||||
--uib-color: #004717;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: var(--uib-size);
|
||||
width: var(--uib-size);
|
||||
}
|
||||
|
||||
.dot-spinner__dot {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dot-spinner__dot::before {
|
||||
content: '';
|
||||
height: 20%;
|
||||
width: 20%;
|
||||
border-radius: 50%;
|
||||
background-color: var(--uib-color);
|
||||
transform: scale(0);
|
||||
opacity: 0.5;
|
||||
animation: pulse0112 calc(var(--uib-speed) * 1.111) ease-in-out infinite;
|
||||
box-shadow: 0 0 20px rgba(18, 31, 53, 0.3);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(2) {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(2)::before {
|
||||
animation-delay: calc(var(--uib-speed) * -0.875);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(3) {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(3)::before {
|
||||
animation-delay: calc(var(--uib-speed) * -0.75);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(4) {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(4)::before {
|
||||
animation-delay: calc(var(--uib-speed) * -0.625);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(5) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(5)::before {
|
||||
animation-delay: calc(var(--uib-speed) * -0.5);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(6) {
|
||||
transform: rotate(225deg);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(6)::before {
|
||||
animation-delay: calc(var(--uib-speed) * -0.375);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(7) {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(7)::before {
|
||||
animation-delay: calc(var(--uib-speed) * -0.25);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(8) {
|
||||
transform: rotate(315deg);
|
||||
}
|
||||
|
||||
.dot-spinner__dot:nth-child(8)::before {
|
||||
animation-delay: calc(var(--uib-speed) * -0.125);
|
||||
}
|
||||
|
||||
@keyframes pulse0112 {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(0);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
30
src/Components/Loaders/FullscreenLoaders.jsx
Normal 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;
|
||||
20
src/Components/Loaders/Loader01.jsx
Normal 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;
|
||||
478
src/Components/MobileView.jsx
Normal file
@@ -0,0 +1,478 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Heading,
|
||||
HStack,
|
||||
Image,
|
||||
Modal,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Progress,
|
||||
Spinner,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Mobile from "../assets/mobileWing.png";
|
||||
import mobileBanner from "../assets/welcome.avif";
|
||||
import { GrDownload } from "react-icons/gr";
|
||||
import { LuClock } from "react-icons/lu";
|
||||
import { GiNetworkBars } from "react-icons/gi";
|
||||
import { GrLinkedinOption } from "react-icons/gr";
|
||||
import { FiInstagram } from "react-icons/fi";
|
||||
import { IoBatteryHalf } from "react-icons/io5";
|
||||
import { BiWifi } from "react-icons/bi";
|
||||
import { useGetIOByIdQuery } from "../Services/io.service";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import FullscreenLoaders from "./Loaders/FullscreenLoaders";
|
||||
import { calculatePercentage, formatDate } from "../Constants/Constants";
|
||||
import { BsFileText } from "react-icons/bs";
|
||||
|
||||
const MobileView = ({ isOpen, onClose, finalRef, actionId }) => {
|
||||
const [time, setTime] = useState(new Date());
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const id = actionId;
|
||||
|
||||
const {
|
||||
data: IObyID,
|
||||
isLoading: IObyIDisLoading,
|
||||
error: IObyIDerror,
|
||||
} = useGetIOByIdQuery(id, { skip: !id });
|
||||
|
||||
console.log(IObyID);
|
||||
|
||||
const keyMerits = IObyID?.data?.keyMerits || [];
|
||||
const artifactsImage = IObyID?.data?.artifactsImage || [];
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setTime(new Date());
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const formatTime = (date) => {
|
||||
return date.toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
};
|
||||
|
||||
console.log(
|
||||
calculatePercentage(
|
||||
IObyID?.data?.totalAmtInvestmentInUSD,
|
||||
IObyID?.data?.goalAmount
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
display={"flex"}
|
||||
size={"xl"}
|
||||
justifyContent={"center"}
|
||||
isCentered
|
||||
finalFocusRef={finalRef}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalOverlay
|
||||
backdropFilter="blur(5px)" // Add this line for backdrop blur
|
||||
bg="rgba(0, 0, 0, 0.4)" // Optional: Adjust the overlay color and opacity
|
||||
/>
|
||||
<ModalContent backgroundColor={"transparent"} shadow={"none"}>
|
||||
<HStack w={"100"} display={"flex"} justify={"center"}>
|
||||
<Box
|
||||
as="span"
|
||||
boxShadow={"none"}
|
||||
position={"relative"}
|
||||
display={"flex"}
|
||||
justifyContent={"center"}
|
||||
h={"600px"}
|
||||
w={"320px"}
|
||||
sx={{
|
||||
"@media (max-width: 2024px)": {
|
||||
height: "695px",
|
||||
width: "360px",
|
||||
},
|
||||
"@media (max-width: 1440px)": {
|
||||
height: "600px",
|
||||
width: "320px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
h={"100%"}
|
||||
w={"100%"}
|
||||
src={Mobile}
|
||||
position={"absolute"}
|
||||
top={"0"}
|
||||
left={"0"}
|
||||
/>
|
||||
<Box
|
||||
backgroundColor={"#fff"}
|
||||
h={"98%"}
|
||||
w={"96%"}
|
||||
// m={2}
|
||||
borderRadius={"47px"}
|
||||
pt={"36px"}
|
||||
px={"15px"}
|
||||
>
|
||||
{IObyIDisLoading ? (
|
||||
<Box
|
||||
display={"flex"}
|
||||
justifyContent={"center"}
|
||||
alignItems={"center"}
|
||||
h={"100%"}
|
||||
>
|
||||
<Spinner thickness="3px" color="purple.900" size="lg" />
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<Box>
|
||||
<Box
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
position={"absolute"}
|
||||
left={"30px"}
|
||||
top={"18px"}
|
||||
>
|
||||
<Text ml={1} mb={0}>
|
||||
<GiNetworkBars fontSize={"10px"} />
|
||||
</Text>
|
||||
<Text ml={1} mb={0} fontSize={"10px"}>
|
||||
{formatTime(time)}
|
||||
</Text>
|
||||
<Text ml={"5px"} mb={0}>
|
||||
<GrLinkedinOption fontSize={"10px"} />
|
||||
</Text>
|
||||
{/* <Text ml={1} mb={0}><FiInstagram fontSize={"10px"} /></Text> */}
|
||||
</Box>
|
||||
<Box
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
position={"absolute"}
|
||||
right={"36px"}
|
||||
top={"17px"}
|
||||
>
|
||||
<Text mb={0}>
|
||||
<BiWifi fontSize={"14px"} />
|
||||
</Text>
|
||||
<Text ml={1} mb={0}>
|
||||
<IoBatteryHalf fontSize={"15px"} />
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
p={"10px"}
|
||||
overflowY={"scroll"}
|
||||
h={"483px"}
|
||||
zIndex={"99"}
|
||||
position={"relative"}
|
||||
borderBottomLeftRadius={"23px"}
|
||||
borderBottomRightRadius={"23px"}
|
||||
sx={{
|
||||
"@media (max-width: 2024px)": {
|
||||
height: "575px",
|
||||
},
|
||||
"@media (max-width: 1440px)": {
|
||||
height: "483px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
mb={4}
|
||||
bg={"#f5f8f6"}
|
||||
borderRadius={"20px"}
|
||||
boxShadow={"rgba(0, 0, 0, 0.15) 0px 2px 8px"}
|
||||
>
|
||||
<Box position={"relative"}>
|
||||
<Text
|
||||
position={"absolute"}
|
||||
top={"12px"}
|
||||
left={"10px"}
|
||||
backgroundColor={"#e4f6ea"}
|
||||
fontSize={"10px"}
|
||||
fontWeight={500}
|
||||
color="green"
|
||||
p={"7px 12px"}
|
||||
borderRadius={"20px"}
|
||||
>
|
||||
Stock
|
||||
</Text>
|
||||
<Text
|
||||
position={"absolute"}
|
||||
top={"12px"}
|
||||
right={"10px"}
|
||||
fontSize={"10px"}
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
fontWeight={500}
|
||||
backgroundColor={"#fff"}
|
||||
p={"7px 12px"}
|
||||
borderRadius={"20px"}
|
||||
>
|
||||
<LuClock color="#d8804e" />{" "}
|
||||
<Text mb={0} ml={1}>
|
||||
Closing Date {formatDate(IObyID?.data?.closingDate)}
|
||||
</Text>
|
||||
</Text>
|
||||
{artifactsImage?.[0]?.artifactPathName && (
|
||||
<Image
|
||||
borderTopLeftRadius={"20px"}
|
||||
borderTopRightRadius={"20px"}
|
||||
h={"130px"}
|
||||
w={"100%"}
|
||||
src={
|
||||
"https://tanami.betadelivery.com/" +
|
||||
artifactsImage[0]?.artifactPathName
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Stack mt="3" bg={"#fff"} py={4} px={4}>
|
||||
<Text
|
||||
fontSize={"sm"}
|
||||
fontWeight={"500"}
|
||||
color={"#000"}
|
||||
mb={0}
|
||||
>
|
||||
{IObyID?.data?.investmentType?.investmentTypeName}
|
||||
</Text>
|
||||
<Heading fontSize="16px" color={"#004717"}>
|
||||
BHD {IObyID?.data?.goalAmount}
|
||||
</Heading>
|
||||
<Progress
|
||||
colorScheme="green"
|
||||
size="sm"
|
||||
value={calculatePercentage(
|
||||
IObyID?.data?.totalAmtInvestmentInUSD,
|
||||
IObyID?.data?.goalAmount
|
||||
)}
|
||||
borderRadius={"3px"}
|
||||
/>
|
||||
<Text
|
||||
color={"#4e4e4e"}
|
||||
fontSize={"xs"}
|
||||
fontWeight={600}
|
||||
mb={0}
|
||||
>
|
||||
{parseFloat(
|
||||
calculatePercentage(
|
||||
IObyID?.data?.totalAmtInvestmentInUSD,
|
||||
IObyID?.data?.goalAmount
|
||||
) || 0
|
||||
).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})}
|
||||
% funded
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={"xs"}
|
||||
fontWeight={500}
|
||||
mb={0}
|
||||
color={"#9d9d9d"}
|
||||
>
|
||||
{IObyID?.data?.descriptionEnglish}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Box py={4} px={4}>
|
||||
<Box display={"flex"} justifyContent={"space-between"}>
|
||||
<Text
|
||||
fontSize={"xs"}
|
||||
mb={1}
|
||||
fontWeight={500}
|
||||
color={"#616161"}
|
||||
>
|
||||
Sponsor name:
|
||||
</Text>
|
||||
<Text fontSize={"xs"} mb={1} fontWeight={500}>
|
||||
{IObyID?.data?.sponsor?.sponsorName}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box display={"flex"} justifyContent={"space-between"}>
|
||||
<Text
|
||||
fontSize={"xs"}
|
||||
mb={1}
|
||||
fontWeight={500}
|
||||
color={"#616161"}
|
||||
>
|
||||
Estimated return:
|
||||
</Text>
|
||||
<Text fontSize={"xs"} mb={1} fontWeight={500}>
|
||||
{IObyID?.data?.expectedReturn}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box display={"flex"} justifyContent={"space-between"}>
|
||||
<Text
|
||||
fontSize={"xs"}
|
||||
mb={1}
|
||||
fontWeight={500}
|
||||
color={"#616161"}
|
||||
>
|
||||
Hoiding period:
|
||||
</Text>
|
||||
<Text fontSize={"xs"} mb={1} fontWeight={500}>
|
||||
{IObyID?.data?.holdingPeriod}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box display={"flex"} justifyContent={"space-between"}>
|
||||
<Text
|
||||
fontSize={"xs"}
|
||||
mb={1}
|
||||
fontWeight={500}
|
||||
color={"#616161"}
|
||||
>
|
||||
Minimum investment:
|
||||
</Text>
|
||||
<Text fontSize={"xs"} mb={1} fontWeight={500}>
|
||||
{
|
||||
IObyID?.data?.minInvestmentAmt?.[0]?.country
|
||||
?.minInvestmentAmt
|
||||
}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
mb={4}
|
||||
p={5}
|
||||
bg={"#f5f8f6"}
|
||||
borderRadius={"20px"}
|
||||
boxShadow={"rgba(0, 0, 0, 0.15) 0px 2px 8px"}
|
||||
>
|
||||
<Heading fontSize="14px" fontWeight={600}>
|
||||
Key Merits
|
||||
</Heading>
|
||||
<Box>
|
||||
{keyMerits &&
|
||||
keyMerits.map((item, index) => (
|
||||
<Box display={"flex"} alignItems={"center"}>
|
||||
<Image
|
||||
rounded={"md"}
|
||||
display={"flex"}
|
||||
p={1}
|
||||
justifyContent={"center"}
|
||||
alignItems={"center"}
|
||||
src={
|
||||
"https://tanami.betadelivery.com/" +
|
||||
item?.icon.iconFilePath
|
||||
}
|
||||
w={8}
|
||||
h={8}
|
||||
/>
|
||||
<Text fontSize={"xs"} mb={0}>
|
||||
{item?.meritsDescription}
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
mb={4}
|
||||
p={5}
|
||||
borderRadius={"20px"}
|
||||
boxShadow={"rgba(0, 0, 0, 0.15) 0px 2px 8px"}
|
||||
>
|
||||
<Heading fontSize="14px" fontWeight={600}>
|
||||
Investment Documents
|
||||
</Heading>
|
||||
<Box
|
||||
bg={"#f5f8f6"}
|
||||
w={"150px"}
|
||||
p={3}
|
||||
borderRadius={"10px"}
|
||||
>
|
||||
<Box display={"flex"} alignItems={"center"} mb={2}>
|
||||
{/* <Image
|
||||
me={1}
|
||||
src="https://tanami.betadelivery.com/public/icons/icon8.svg"
|
||||
/> */}
|
||||
<BsFileText color="forestGreen" fontSize="18px" />
|
||||
<Text fontSize={"xs"} mb={0} ml={2}>
|
||||
{IObyID?.data?.documents?.[0]?.documentName}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<Text mb={0} fontSize={"sm"}>
|
||||
{IObyID?.data?.documents?.[0]?.documentSize}
|
||||
</Text>
|
||||
<GrDownload fontSize={"15px"} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
mb={4}
|
||||
p={4}
|
||||
borderRadius={"20px"}
|
||||
boxShadow={"rgba(0, 0, 0, 0.15) 0px 2px 8px"}
|
||||
>
|
||||
<Heading fontSize="14px" fontWeight={600}>
|
||||
Videos
|
||||
</Heading>
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
controls
|
||||
style={{
|
||||
borderRadius: "18px",
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
}}
|
||||
>
|
||||
<source
|
||||
src={
|
||||
IObyID?.data?.artifactsVideo?.[0]
|
||||
?.artifactStreamingURL
|
||||
}
|
||||
type="video/mp4"
|
||||
style={{ height: "200px" }}
|
||||
/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
position={"relative"}
|
||||
p={4}
|
||||
background={"#fff"}
|
||||
padding={"24px"}
|
||||
paddingBottom={"3px"}
|
||||
borderBottomLeftRadius={"30px"}
|
||||
borderBottomRightRadius="30px"
|
||||
>
|
||||
<Button
|
||||
margin={"auto"}
|
||||
width={"85%"}
|
||||
bottom="10px"
|
||||
left="0"
|
||||
colorScheme="forestGreen"
|
||||
mr={3}
|
||||
w={"100%"}
|
||||
fontWeight={500}
|
||||
borderRadius={"20px"}
|
||||
>
|
||||
Invest
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</HStack>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default MobileView;
|
||||
40
src/Components/NavBreadCrumbs.jsx
Normal 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;
|
||||
79
src/Components/Pagination.jsx
Normal 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;
|
||||
78
src/Components/SwitchButton.jsx
Normal 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;
|
||||
136
src/Components/TabularView/TabularView.jsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Box, HStack, Input, Select, Text } from "@chakra-ui/react";
|
||||
import { OPACITY_ON_LOAD } from "../../Layout/animations";
|
||||
import { TABLE_PAGINATION } from "../../Constants/Paginations";
|
||||
import { useGetVideosQuery } from "../../Services/api.service";
|
||||
import { useState } from "react";
|
||||
import Header from "../../Components/Header";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@chakra-ui/icons";
|
||||
import DataTable from "../../Components/DataTable/DataTable";
|
||||
|
||||
const TabularView = ({
|
||||
apiData,
|
||||
tableHeadRow,
|
||||
title,
|
||||
btnTitle,
|
||||
link,
|
||||
extractedArray,
|
||||
searchTerm,
|
||||
setSearchTerm,
|
||||
statusFilter,
|
||||
setStatusFilter,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
totalPages,
|
||||
noDataTitle,
|
||||
totalItems,
|
||||
}) => {
|
||||
const [displayRange, setDisplayRange] = useState({
|
||||
start: TABLE_PAGINATION?.page,
|
||||
end: pageSize,
|
||||
});
|
||||
// ====================================================[Pagination Setup]================================================================
|
||||
const paginationPrev = () => {
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
updateDisplayRange(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const paginationNext = () => {
|
||||
if (currentPage < totalPages) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
updateDisplayRange(currentPage + 1);
|
||||
}
|
||||
};
|
||||
const updateDisplayRange = (page) => {
|
||||
const start = (page - 1) * pageSize + 1;
|
||||
const end = Math.min(start + pageSize - 1, totalItems);
|
||||
setDisplayRange({ start, end });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
overflowY={"scroll"}
|
||||
paddingBottom={50}
|
||||
height={"100vh"}
|
||||
{...OPACITY_ON_LOAD}
|
||||
>
|
||||
<Header title={title} btnTitle={btnTitle} link={link} />
|
||||
|
||||
<Box bg="white.500">
|
||||
<HStack
|
||||
display={"flex"}
|
||||
justifyContent={"space-between"}
|
||||
ps={1}
|
||||
pe={1}
|
||||
pb={4}
|
||||
pt={4}
|
||||
spacing="24px"
|
||||
>
|
||||
<Input
|
||||
type="search"
|
||||
width={300}
|
||||
placeholder="Search..."
|
||||
size="sm"
|
||||
rounded="sm"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<HStack>
|
||||
<Select
|
||||
className="pointer web-text-small"
|
||||
width={"90px"}
|
||||
rounded="sm"
|
||||
size="sm"
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
>
|
||||
<option value="all">All</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
className="pointer web-text-small"
|
||||
width={"90px"}
|
||||
rounded="sm"
|
||||
size="sm"
|
||||
value={pageSize}
|
||||
onChange={(e) => setPageSize(e.target.value)}
|
||||
>
|
||||
<option value={5}>{5}</option>
|
||||
<option value={10}>{10}</option>
|
||||
<option value={15}>{15}</option>
|
||||
</Select>
|
||||
|
||||
<HStack>
|
||||
<ChevronLeftIcon
|
||||
onClick={paginationPrev}
|
||||
className=" link rounded-3 pointer"
|
||||
/>
|
||||
<Text className="web-text-medium" as={"span"}>
|
||||
{displayRange.start} - {displayRange.end} of {totalItems}
|
||||
</Text>
|
||||
<ChevronRightIcon
|
||||
onClick={paginationNext}
|
||||
className=" link rounded-3 pointer"
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* ====================================================[ Table ]================================================================ */}
|
||||
<DataTable
|
||||
emptyMessage={`We don't have any ${noDataTitle} `}
|
||||
tableHeadRow={tableHeadRow}
|
||||
data={extractedArray}
|
||||
isLoading={apiData?.isLoading}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabularView;
|
||||
22
src/Components/ToastBox.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { CheckCircleIcon, WarningIcon } from "@chakra-ui/icons";
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import { PiWarningBold } from "react-icons/pi";
|
||||
|
||||
const ToastBox = ({ message, status }) => {
|
||||
return (
|
||||
<Box
|
||||
color="white"
|
||||
rounded={"sm"}
|
||||
className="web-text-large d-flex gap-2 align-items-center"
|
||||
p={3}
|
||||
bg={status === "error" ? "red.500" : status === "warn" ? "yellow.500" : status === "info" ? "blue.500" : "green.500"}
|
||||
>
|
||||
|
||||
{status === "error" || status === "warn" ? <PiWarningBold/> : status === "info" ? <WarningIcon/> : <CheckCircleIcon /> }
|
||||
<Text as={"span"}>{message}</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToastBox;
|
||||
68
src/Components/WebButton.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
import { Button } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
|
||||
const WebButton = ({title}) => {
|
||||
return ( <Button
|
||||
position={"relative"}
|
||||
backgroundColor={"transparent"}
|
||||
cursor={"pointer"}
|
||||
transition="0.3s ease-in-out"
|
||||
color={"#fff"}
|
||||
width={"auto"}
|
||||
height={"auto"}
|
||||
p={6}
|
||||
pt={2}
|
||||
pb={2}
|
||||
fontFamily={"Poppins"}
|
||||
fontWeight={"600"}
|
||||
border={"1px solid white"}
|
||||
borderRadius={"4px"}
|
||||
fontSize={"14px"}
|
||||
zIndex={"1"}
|
||||
overflow={"hidden"}
|
||||
sx={{
|
||||
"::before": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: "65px",
|
||||
height: "65px",
|
||||
borderRadius: "50%",
|
||||
transition: "0.35s linear",
|
||||
zIndex: -1,
|
||||
bgGradient:
|
||||
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
|
||||
opacity: "0",
|
||||
},
|
||||
"&:hover::before": {
|
||||
width: "100%",
|
||||
height: "120%",
|
||||
borderRadius: "0px",
|
||||
opacity: "1",
|
||||
},
|
||||
"@media (max-width: 500px)": {
|
||||
fontSize: "14px",
|
||||
width: "230px",
|
||||
height: "44px",
|
||||
marginTop: "2rem",
|
||||
bgGradient:
|
||||
"radial-gradient(circle, #ffffff, #eee2f2, #e7c3dc, #e5a3ba, #de858e)",
|
||||
color: "#000",
|
||||
fontWeight: "600",
|
||||
},
|
||||
}}
|
||||
_hover={{
|
||||
color: "#000",
|
||||
border: "1px solid white",
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default WebButton
|
||||
BIN
src/Components/mobileWing.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
198
src/Constants/Constants.js
Normal file
@@ -0,0 +1,198 @@
|
||||
|
||||
import dns from "node:dns"
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
|
||||
export const generateSerialNumber = (index, currentPage, pageSize) => {
|
||||
return (currentPage - 1) * pageSize + (index + 1);
|
||||
};
|
||||
|
||||
export function getTomorrowDate() {
|
||||
const today = new Date();
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
|
||||
// Format the date as YYYY-MM-DD (ISO 8601)
|
||||
return tomorrow.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
|
||||
export function removeTrailingZeros(value) {
|
||||
// Convert the value to a number and then to a string
|
||||
let number = parseFloat(value);
|
||||
let result = number.toString();
|
||||
|
||||
// Check if the result contains a decimal point
|
||||
if (result.includes('.')) {
|
||||
// Remove trailing zeros if the decimal part is 0 or 00
|
||||
result = result.replace(/(\.\d*?)0+$/, '$1'); // Remove trailing zeros
|
||||
result = result.replace(/\.$/, ''); // Remove the decimal point if it's the last character
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function getCountdownTimer(utcDateString) {
|
||||
// Parse the UTC datetime string into a Date object
|
||||
const targetDate = new Date(utcDateString);
|
||||
const now = new Date();
|
||||
|
||||
// Calculate the difference in milliseconds
|
||||
const difference = targetDate - now;
|
||||
|
||||
if (difference <= 0) {
|
||||
return 'The time has passed or is now!';
|
||||
}
|
||||
|
||||
// Convert the difference from milliseconds to a more readable format
|
||||
const seconds = Math.floor(difference / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
const remainingDays = days;
|
||||
const remainingHours = hours % 24;
|
||||
const remainingMinutes = minutes % 60;
|
||||
const remainingSeconds = seconds % 60;
|
||||
|
||||
return `${remainingDays === 0 ? "": remainingDays+"d"} ${remainingHours === 0 ? "": remainingHours+"h"} ${remainingMinutes}m ${remainingSeconds}s `;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function startCountdown(utcDateString) {
|
||||
// Function to update the countdown
|
||||
const updateCountdown = () => {
|
||||
const countdown = getCountdownTimer(utcDateString);
|
||||
console.log(countdown);
|
||||
};
|
||||
|
||||
// Update countdown immediately
|
||||
updateCountdown();
|
||||
|
||||
// Set up interval to update countdown every minute (60000 milliseconds)
|
||||
setInterval(updateCountdown, 60000);
|
||||
}
|
||||
|
||||
export const getFileNameFromPath = (filePath) => {
|
||||
const parts = filePath?.split("/");
|
||||
return parts?.[parts?.length - 1];
|
||||
};
|
||||
|
||||
export function debounce(func, delay) {
|
||||
let debounceTimer;
|
||||
return function (...args) {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => func.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function resolveMx(domain, recordType) {
|
||||
return new Promise((resolve, reject) => {
|
||||
dns.resolveMx(domain, (err, mxRecords) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const addresses = mxRecords.map((mxRecord) => mxRecord.exchange);
|
||||
resolve(addresses);
|
||||
});
|
||||
});
|
||||
}
|
||||
// Async function to check email address validity
|
||||
export async function checkEmailValidity(email) {
|
||||
try {
|
||||
const domain = email?.split("@")[1];
|
||||
const addresses = await resolveMx(domain, "MX");
|
||||
|
||||
if (addresses && addresses?.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return false; // No MX record exists
|
||||
} catch (err) {
|
||||
return false; // Error occurred
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Function to convert timestamp to readable date format in Gulf timezone
|
||||
export function formatTimestampInGulfTimezone(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
timeZone: 'Asia/Dubai', // Gulf Standard Time (GST) timezone
|
||||
timeZoneName: 'short'
|
||||
};
|
||||
return date.toLocaleDateString('en-GB', options);
|
||||
}
|
||||
|
||||
|
||||
export function formatDate(dateString) {
|
||||
const options = { year: 'numeric', month: 'short', day: 'numeric' };
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', options);
|
||||
}
|
||||
|
||||
export function calculatePercentage(part, total) {
|
||||
if (total === 0) {
|
||||
return 0; // To avoid division by zero
|
||||
}
|
||||
return (part / total) * 100;
|
||||
}
|
||||
|
||||
export const exportToExcel = (data, customHeaders, fileName = 'exported-data.xlsx') => {
|
||||
// Map your data to include only the fields that match your custom headers
|
||||
const mappedData = data.map(item =>
|
||||
customHeaders.map(header => item[header.key] || '')
|
||||
);
|
||||
|
||||
// Prepend the headers row
|
||||
const sheetData = [customHeaders.map(header => header.label), ...mappedData];
|
||||
|
||||
// Create a worksheet from the data array
|
||||
const worksheet = XLSX.utils.aoa_to_sheet(sheetData);
|
||||
|
||||
// Apply styles to header cells
|
||||
customHeaders.forEach((header, index) => {
|
||||
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: index }); // r: row, c: column
|
||||
if (!worksheet[cellAddress]) return; // Skip if cell doesn't exist
|
||||
|
||||
worksheet[cellAddress].s = {
|
||||
fill: {
|
||||
fgColor: { rgb: "FFFF00" } // Set header background color (Yellow in this case)
|
||||
},
|
||||
font: {
|
||||
bold: true, // Make header text bold
|
||||
color: { rgb: "000000" } // Set header text color (Black in this case)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Create a new workbook and append the worksheet
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
|
||||
|
||||
// Generate a buffer from the workbook
|
||||
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
|
||||
|
||||
// Create a Blob object from the buffer
|
||||
const dataBlob = new Blob([excelBuffer], { type: 'application/octet-stream' });
|
||||
|
||||
// Create a link element to trigger the download
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(dataBlob);
|
||||
link.download = fileName;
|
||||
|
||||
// Append the link to the document body, trigger the download, and remove the link
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
2
src/Constants/Paginations.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const TABLE_PAGINATION = { page: 1, size:20 }
|
||||
export const IMAGE_URI = import.meta.env.VITE_API_IMAGE_URL
|
||||
6
src/Contexts/GlobalStateContext.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
// GlobalStateContext.js
|
||||
import { createContext } from 'react';
|
||||
|
||||
const GlobalStateContext = createContext();
|
||||
|
||||
export default GlobalStateContext;
|
||||
1537
src/Contexts/GlobalStateProvider.jsx
Normal file
BIN
src/Images/dark-bg.png
Normal file
|
After Width: | Height: | Size: 167 KiB |
BIN
src/Images/light-bg.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
src/Images/logo.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/Images/logoDark.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src/Images/logoDarkMini.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/Images/logoLight.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src/Images/miniLogo.jpg
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
src/Images/reactLogo.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
src/Images/welcomeBanner.gif
Normal file
|
After Width: | Height: | Size: 7.7 MiB |
563
src/Layout/DefaultLayout.jsx
Normal file
@@ -0,0 +1,563 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { BsDot } from "react-icons/bs";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { RxDotFilled } from "react-icons/rx";
|
||||
import {
|
||||
TbArrowBadgeLeftFilled,
|
||||
TbListDetails,
|
||||
TbReportMoney,
|
||||
TbTransactionDollar,
|
||||
} from "react-icons/tb";
|
||||
import {
|
||||
NavLink,
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from "react-router-dom";
|
||||
import { RouteLink } from "../Routes/Routes";
|
||||
import NotFound from "../Pages/NotFound";
|
||||
import { nav } from "../Routes/Nav";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Text,
|
||||
WrapItem,
|
||||
Popover,
|
||||
Tag,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionPanel,
|
||||
Image,
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Breadcrumb,
|
||||
Divider,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import GlobalStateContext from "../Contexts/GlobalStateContext";
|
||||
import Cookies from "js-cookie"; // Import the Cookies library
|
||||
import HeaderMain from "../Components/HeaderMain";
|
||||
import {
|
||||
RiBankLine,
|
||||
RiExchangeBoxLine,
|
||||
RiFileUserLine,
|
||||
RiMoneyDollarBoxLine,
|
||||
} from "react-icons/ri";
|
||||
import {
|
||||
MdNotificationsNone,
|
||||
MdOutlineAddChart,
|
||||
MdOutlineTaskAlt,
|
||||
} from "react-icons/md";
|
||||
import { HiOutlineChartSquareBar } from "react-icons/hi";
|
||||
import { GrManual, GrNotification } from "react-icons/gr";
|
||||
import { LuContact } from "react-icons/lu";
|
||||
import SplashScreen from "../Pages/SplashScreen";
|
||||
|
||||
const DashboardLayout = ({ isOnline }) => {
|
||||
const navigate = useNavigate();
|
||||
const dispach = useDispatch();
|
||||
const location = useLocation();
|
||||
const path = location.pathname;
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(true);
|
||||
const {
|
||||
setIsAuthenticate,
|
||||
colorMode,
|
||||
toggleColorMode,
|
||||
setSlideFormRight,
|
||||
slideFromRight,
|
||||
} = useContext(GlobalStateContext);
|
||||
const [isSplashVisible, setSplashVisible] = useState(true);
|
||||
const [openIndex, setOpenIndex] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const savedIndex = localStorage.getItem("openAccordionIndex");
|
||||
if (savedIndex !== null) {
|
||||
setOpenIndex(parseInt(savedIndex));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAccordionChange = (index) => {
|
||||
const newIndex = openIndex === index ? null : index;
|
||||
setOpenIndex(newIndex);
|
||||
localStorage.setItem("openAccordionIndex", newIndex);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Set a timer to hide the splash screen after 3 seconds
|
||||
const timer = setTimeout(() => {
|
||||
setSplashVisible(false);
|
||||
}, 1000); // 3000ms = 3 seconds
|
||||
|
||||
// Cleanup the timer
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setIsDrawerOpen(!isDrawerOpen);
|
||||
};
|
||||
|
||||
const logOutHandler = () => {
|
||||
// dispach(loginUser(false));
|
||||
setIsAuthenticate(false);
|
||||
Cookies.remove("isAuthenticated");
|
||||
localStorage.removeItem("refreshToken");
|
||||
localStorage.removeItem("accessToken");
|
||||
localStorage.removeItem("refreshTokenExp");
|
||||
navigate("/login");
|
||||
};
|
||||
|
||||
// // Function to get the title based on the route
|
||||
const getTitle = () => {
|
||||
switch (true) {
|
||||
case "/":
|
||||
return "👋🏻 Hi, Developers";
|
||||
case path.startsWith("/task"):
|
||||
return (
|
||||
<span className="d-flex align-items-center gap-2">
|
||||
<MdOutlineTaskAlt className="h4 m-0" /> Tasks
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/notification"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<GrNotification className="h5 m-0" /> Notification
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/exchange-rate"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
|
||||
Echange rate
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/create-io"):
|
||||
if (/^\/create-io\/[A-Za-z0-9_-]+$/.test(path)) {
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<MdOutlineAddChart className="h4 m-0 fw-normal" />
|
||||
Edit IO
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<MdOutlineAddChart className="h4 m-0 fw-normal" />
|
||||
Create IO
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/view-io"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<HiOutlineChartSquareBar className="h4 m-0 fw-normal" />
|
||||
View IO
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/investor-details"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<TbListDetails className="h4 m-0 fw-normal" />
|
||||
Investor Details
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/investor-transactions"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<TbTransactionDollar className="h4 m-0 fw-normal" />
|
||||
Investor Transactions
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/deposit-request"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
|
||||
Deposite pending request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/deposit-history"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
|
||||
Deposite withdrawal request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/withdraw-request"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
|
||||
Withdrawal pending request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/withdraw-history"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
|
||||
Withdrawal request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/investor-request"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
|
||||
Investor pending request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/investor-history"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
|
||||
Investor request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/deletion-request"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
|
||||
Deletion pending request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/deletion-history"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
|
||||
Deletion request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/bank-investor"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<TbReportMoney className="h4 m-0 fw-normal" />
|
||||
Ban / Unban Investor
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/academy"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<GrManual className="h4 m-0 fw-normal" />
|
||||
Academy
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/notification"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<MdNotificationsNone className="h4 m-0 fw-normal" />
|
||||
Notification
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/contact"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<LuContact className="h4 m-0 fw-normal" />
|
||||
Contact Details
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/users"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiFileUserLine className="h4 m-0 fw-normal" />
|
||||
Users
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/bank-details"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiBankLine className="h4 m-0 fw-normal" />
|
||||
Bank Details
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/deletion-request"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
|
||||
Deletion pending request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/deletion-history"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
|
||||
Deletion request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/deletion-request"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiMoneyDollarBoxLine className="h4 m-0 fw-normal" />
|
||||
Deletion pending request
|
||||
</span>
|
||||
);
|
||||
case path.startsWith("/deletion-history"):
|
||||
return (
|
||||
<span className="d-flex align-items-end gap-2">
|
||||
<RiExchangeBoxLine className="h4 m-0 fw-normal" />
|
||||
Deletion request
|
||||
</span>
|
||||
);
|
||||
|
||||
default:
|
||||
return <span className="d-flex align-items-end gap-2">Home</span>;
|
||||
}
|
||||
};
|
||||
|
||||
if (isSplashVisible) {
|
||||
return <SplashScreen />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box height={"100vh"}>
|
||||
<HeaderMain
|
||||
isDrawerOpen={isDrawerOpen}
|
||||
logOutHandler={logOutHandler}
|
||||
toggleDrawer={toggleDrawer}
|
||||
icon
|
||||
title={getTitle()}
|
||||
/>
|
||||
<Box
|
||||
h={{ base: "92%", xl: "94%" }}
|
||||
style={{
|
||||
width: "100%",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className="d-flex"
|
||||
|
||||
// pe={0.5}
|
||||
>
|
||||
<Alert
|
||||
transition={"0.5s"}
|
||||
h={53}
|
||||
transform={isOnline ? "translateY(-100px)" : "translateY(0px)"}
|
||||
position={"absolute"}
|
||||
zIndex={999}
|
||||
status="info"
|
||||
variant="solid"
|
||||
bgGradient="linear(to-r, #C33FAD, #C33FAD, #C33FAD)"
|
||||
// bgGradient='linear(to-r, #1EBCA3, #E0EEFF)'
|
||||
color={"white"}
|
||||
fontWeight={600}
|
||||
fontSize={"md"}
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
>
|
||||
<AlertIcon color={"white"} />
|
||||
No Internet !
|
||||
</Alert>
|
||||
|
||||
<Box
|
||||
className="h-100 position-relative sideBar"
|
||||
style={{
|
||||
width: isDrawerOpen ? 230 : 0,
|
||||
transition: "width 0.3s ease-in-out, transform 0.3s ease-in-out", // Smooth transition for width and transform
|
||||
backgroundColor: "#FFF",
|
||||
position: "relative",
|
||||
color: "gray.500",
|
||||
transform: isDrawerOpen ? "translateX(0)" : "translateX(-230px)", // Move box to the left when closed
|
||||
}}
|
||||
boxShadow={'md'}
|
||||
>
|
||||
<Box
|
||||
className="ps-0 scroll-bar pe-0 pt-3="
|
||||
style={{
|
||||
height: "100%",
|
||||
overflowY: "scroll",
|
||||
overflowX: "hidden",
|
||||
paddingBottom: "5rem",
|
||||
}}
|
||||
|
||||
|
||||
>
|
||||
<Accordion
|
||||
m={0}
|
||||
// p={2}
|
||||
allowToggle
|
||||
defaultIndex={-1}
|
||||
index={openIndex}
|
||||
onChange={handleAccordionChange}
|
||||
>
|
||||
{nav.map(
|
||||
({ title, type, Icon, submenu, path, colorCode }, index) => {
|
||||
if (type === "accordion") {
|
||||
return (
|
||||
<AccordionItem
|
||||
key={index}
|
||||
border={"none"}
|
||||
style={{ borderRadius: "2px", marginBottom: "8px" }}
|
||||
|
||||
>
|
||||
<AccordionButton
|
||||
style={{ height: "auto", borderRadius: "2px" }}
|
||||
_hover={{ bg: "#1a04361c" }}
|
||||
className={`${
|
||||
true
|
||||
? "p-1 web-text-medium ps-3 justify-content-between"
|
||||
: "p-2 ps-1 web-text-xlarge justify-content-center"
|
||||
} link d-flex align-items-center gap-2 w-100 mb-1`}
|
||||
>
|
||||
<Box
|
||||
// to="/"
|
||||
as="span"
|
||||
className="d-flex align-items-centre gap-2 w-100 py-1"
|
||||
>
|
||||
{/* {Icon && title === "Admin" ? <Image w={15} src={shield} /> : <Icon className={`web-text-large`} />} */}
|
||||
{Icon && (
|
||||
<Icon
|
||||
fontSize={title === "Admin" ? "18px" : "15px"}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
as={"span"}
|
||||
display={true ? "flex" : "none"}
|
||||
alignItems="center"
|
||||
overflow="hidden"
|
||||
textAlign={"left"}
|
||||
w={'100%'}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel
|
||||
p={0}
|
||||
pb={1}
|
||||
display={"flex"}
|
||||
flexDirection={"column"}
|
||||
gap={1}
|
||||
>
|
||||
{submenu?.map(
|
||||
(
|
||||
{
|
||||
title: subMenuTitle,
|
||||
path: link,
|
||||
icon: SubIcon,
|
||||
colorCode,
|
||||
},
|
||||
i
|
||||
) => (
|
||||
<Box
|
||||
key={i}
|
||||
style={{
|
||||
height: "auto",
|
||||
position: "relative",
|
||||
}}
|
||||
className={`${
|
||||
true
|
||||
? " web-text-medium ps-3"
|
||||
: " web-text-xlarge justify-content-center"
|
||||
} d-flex align-items-center p-0`}
|
||||
>
|
||||
<NavLink
|
||||
style={{ borderRadius: "2px" }}
|
||||
className={`${
|
||||
true
|
||||
? "p-2 ms-2 web-text-small "
|
||||
: "p-2 ps-0 ms-0 zindex-3 ms-4 web-text-xlarge justify-content-center"
|
||||
} link d-flex align-items-center gap-2 w-100 mx-2`}
|
||||
to={link}
|
||||
>
|
||||
<RxDotFilled/>
|
||||
<Text
|
||||
as={"span"}
|
||||
display={true ? "flex" : "none"}
|
||||
alignItems="center"
|
||||
overflow="hidden"
|
||||
>
|
||||
{subMenuTitle}
|
||||
</Text>
|
||||
</NavLink>
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
} else if (type === "title") {
|
||||
return (
|
||||
<Text
|
||||
as={"span"}
|
||||
key={index}
|
||||
className="web-text-xxsmall fw-600 text-secondary fw-bold"
|
||||
ps={4}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
);
|
||||
} else if (type === "single") {
|
||||
return (
|
||||
<NavLink
|
||||
key={index}
|
||||
style={{
|
||||
height: "auto",
|
||||
position: "relative",
|
||||
borderRadius: "2px",
|
||||
}}
|
||||
className={`${
|
||||
true
|
||||
? "p-2 web-text-medium"
|
||||
: "p-2 ps-0 web-text-xlarge justify-content-start"
|
||||
} d-flex link align-items-center gap-2 w-100 mb-2 single`}
|
||||
to={path}
|
||||
onClick={title == "Logout" ?logOutHandler:null}
|
||||
>
|
||||
{Icon && <Icon className="web-text-large ms-2" />}
|
||||
<Text
|
||||
as={"span"}
|
||||
display={true ? "flex" : "none"}
|
||||
alignItems="center"
|
||||
overflow="hidden"
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</NavLink>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
)}
|
||||
</Accordion>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
style={{
|
||||
width: `calc(100% - ${isDrawerOpen ? 230 : 0}px)`,
|
||||
transition: "width 0.3s ease-in-out",
|
||||
backgroundColor: "#F3F3F9",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 0,
|
||||
}}
|
||||
|
||||
>
|
||||
{/* <HeaderBox
|
||||
slideDirecttion={slideFromRight}
|
||||
logOutHandler={logOutHandler}
|
||||
icon
|
||||
title={getTitle()}
|
||||
/> */}
|
||||
|
||||
{/* <CustomBreadcrumb /> */}
|
||||
|
||||
<AppContent />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLayout;
|
||||
|
||||
const AppContent = () => {
|
||||
return (
|
||||
<Routes>
|
||||
{RouteLink.map(({ path, Component }, index) => (
|
||||
<Route key={index} path={path} element={<Component />} />
|
||||
))}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
23
src/Layout/animations.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export const OPACITY_ON_LOAD = {
|
||||
as: motion.div,
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 }
|
||||
}
|
||||
|
||||
export const SLIDE_IN_BOTTOM = {
|
||||
as: motion.div,
|
||||
initial: { opacity: 0, y: 50 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
transition: { duration: 1, ease: "easeInOut" }
|
||||
};
|
||||
|
||||
|
||||
export const FADE_IN_SCALE_UP = {
|
||||
as: motion.div,
|
||||
initial: { opacity: 0, scale: 0.9 },
|
||||
animate: { opacity: 1, scale: 1 },
|
||||
transition: { duration: 0.5, ease: "easeInOut" }
|
||||
};
|
||||
|
||||
162
src/Pages/Dashbaord.jsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { Box, HStack, Icon, position, Text, VStack } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import { HiOutlineChartSquareBar } from 'react-icons/hi'
|
||||
import { RiMoneyDollarBoxLine } from 'react-icons/ri'
|
||||
import { TbTransactionDollar } from 'react-icons/tb'
|
||||
import { VscSymbolClass } from 'react-icons/vsc'
|
||||
import { TABLE_PAGINATION } from '../Constants/Paginations'
|
||||
import FullscreenLoaders from '../Components/Loaders/FullscreenLoaders'
|
||||
import { useGetIOprepopulateDataQuery, useGetIOsQuery } from '../Services/io.service'
|
||||
import { useGetInvestorsQuery } from '../Services/investor.details.service'
|
||||
import DonutChart from '../Components/Doughnut/DonutChart'
|
||||
import { GoDotFill } from "react-icons/go";
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import LineChart from '../Components/Doughnut/LineChart'
|
||||
import { PiChartLineUpDuotone } from 'react-icons/pi'
|
||||
import ApexChart from '../Components/Doughnut/ApexDonut'
|
||||
import ApexLine from '../Components/Doughnut/ApexLine'
|
||||
import ReactApexChart from 'react-apexcharts'
|
||||
import { BsGraphUpArrow } from "react-icons/bs";
|
||||
|
||||
|
||||
const Dashbaord = () => {
|
||||
const navigate = useNavigate()
|
||||
const { data, isLoading: isIoPreLoading } = useGetIOprepopulateDataQuery();
|
||||
const { data: IO, isLoading: isIoLoading } = useGetIOsQuery({ page: TABLE_PAGINATION?.page, size: TABLE_PAGINATION?.size });
|
||||
const { data: investorDetails, isInvestorLoading } = useGetInvestorsQuery({ page: TABLE_PAGINATION?.page, size: TABLE_PAGINATION?.size });
|
||||
const sortArrayByStatus = () => {
|
||||
const sortedArrays = {
|
||||
open: [],
|
||||
closed: [],
|
||||
processing: [],
|
||||
draft: []
|
||||
};
|
||||
|
||||
IO?.data?.rows.forEach(item => {
|
||||
const status = item.ioStatus?.statusAdmin;
|
||||
if (status === 'Open') {
|
||||
sortedArrays.open.push(item);
|
||||
} else if (status === 'Closed') {
|
||||
sortedArrays.closed.push(item);
|
||||
} else if (status === 'Processing') {
|
||||
sortedArrays.processing.push(item);
|
||||
} else if (status === 'Draft') {
|
||||
sortedArrays.draft.push(item);
|
||||
}
|
||||
});
|
||||
return sortedArrays;
|
||||
};
|
||||
const statusData = sortArrayByStatus()
|
||||
const chartData = {
|
||||
labels: ['Draft', 'Open', 'Processing', 'Closed',],
|
||||
backgroundColor: ['#3182ce', '#004118', '#D69E2E', '#E53E3E'],
|
||||
values: [statusData?.draft?.length, statusData?.open?.length, statusData?.processing?.length, statusData?.closed?.length]
|
||||
};
|
||||
|
||||
|
||||
|
||||
const series1= [{
|
||||
data: [25, 66, 41, 89, 63, 25, 44, 12, 36, 9, 54]
|
||||
}]
|
||||
const options1= {
|
||||
chart: {
|
||||
type: 'line',
|
||||
position:"absolute",
|
||||
right:0,
|
||||
width: 100,
|
||||
height: 35,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
fixed: {
|
||||
enabled: false
|
||||
},
|
||||
x: {
|
||||
show: false
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
formatter: function (seriesName) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
isIoPreLoading || isIoLoading || isInvestorLoading ? <FullscreenLoaders /> :
|
||||
<Box height={'100vh'} bg={'#fff'} roundedTop={0} pt={5} overflowX={"hidden"}>
|
||||
<Box display={'flex'} gap={6} w={'100%'} pt={3} pb={3} p={3} >
|
||||
<Box position={'relative'} cursor={'pointer'} onClick={() => navigate("/investor-details")} boxShadow={'lg'} color={"#004118"} p={4} rounded={'xl'} w={'25%'} display={'flex'} bg={'#f5f8f6'} flexDirection={'column'} alignItems={'start'} >
|
||||
<Icon left={"10px"} bg={'#004118'} rounded={9} p={2} color={"#fff"} as={TbTransactionDollar} mb={6} boxSize={12} />
|
||||
<Text as={'span'} fontSize={'xs'} fontWeight={500}>Total Investors</Text>
|
||||
<Text as={'span'} fontSize={'32px'} fontWeight={600}>{investorDetails?.data?.totalItems}</Text>
|
||||
<Icon position={'absolute'} right={6} bottom={6} boxSize={8} as={BsGraphUpArrow} />
|
||||
{/* <ReactApexChart position={'absolute'} right={6} bottom={6} options={options1} series={series1} type="line" height={35} width={100} /> */}
|
||||
</Box>
|
||||
<Box position={'relative'} cursor={'pointer'} onClick={() => navigate("/view-io")} boxShadow={'lg'} bg={'#f5f8f6'} color={"#004118"} p={3} rounded={'xl'} w={'25%'} display={'flex'} flexDirection={'column'} alignItems={'start'} >
|
||||
<Icon bg={'#004118'} rounded={9} p={2} color={"#fff"} as={HiOutlineChartSquareBar} mb={6} boxSize={12} />
|
||||
<Text as={'span'} fontSize={'xs'} fontWeight={500}>Total IO</Text>
|
||||
<Text as={'span'} fontSize={'32px'} fontWeight={600}>{IO?.data?.totalItems}</Text>
|
||||
<Icon position={'absolute'} right={6} bottom={6} boxSize={8} as={BsGraphUpArrow} />
|
||||
</Box>
|
||||
<Box position={'relative'} cursor={'pointer'} onClick={() => navigate("/sponser")} boxShadow={'lg'} bg={'#f5f8f6'} color={"#004118"} p={3} rounded={'xl'} w={'25%'} display={'flex'} flexDirection={'column'} alignItems={'start'} >
|
||||
<Icon bg={'#004118'} rounded={9} p={2} color={"#fff"} as={RiMoneyDollarBoxLine} mb={6} boxSize={12} />
|
||||
<Text as={'span'} fontSize={'xs'} fontWeight={500}>Total sponors</Text>
|
||||
<Text as={'span'} fontSize={'32px'} fontWeight={600}>{data?.data?.sponsor?.length}</Text>
|
||||
<Icon position={'absolute'} right={6} bottom={6} boxSize={8} as={BsGraphUpArrow} />
|
||||
</Box>
|
||||
<Box position={'relative'} cursor={'pointer'} onClick={() => navigate("/investment-type")} boxShadow={'lg'} bg={'#f5f8f6'} color={"#004118"} p={3} rounded={'xl'} w={'25%'} display={'flex'} flexDirection={'column'} alignItems={'start'} >
|
||||
<Icon bg={'#004118'} rounded={9} p={2} color={"#fff"} as={VscSymbolClass} mb={6} boxSize={12} />
|
||||
<Text as={'span'} fontSize={'xs'} fontWeight={500}>Total Investment Type</Text>
|
||||
<Text as={'span'} fontSize={'32px'} fontWeight={600}>{data?.data?.investmentType?.length}</Text>
|
||||
<Icon position={'absolute'} right={6} bottom={6} boxSize={8} as={BsGraphUpArrow} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box h={'70%'} w={"100%"} display={'flex'} pe={4} mt={2}>
|
||||
<Box w={'60%'} h={'100%'} p={4} pe={6} pt={1} >
|
||||
<Box position={'relative'} h={'100%'} boxShadow={'lg'} display={'flex'} justifyContent={'center'} rounded={'xl'} p={5} ps={0} pe={0}>
|
||||
{/* <Text position={'absolute'} top={0} left={6} as={'span'} fontSize={'sm'}>Exchange rate currency</Text> */}
|
||||
{/* <LineChart /> */}
|
||||
<ApexLine/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
|
||||
<Box boxShadow={'lg'} position={"relative"} bg={'#fff'} rounded={'xl'} w={'40%'} display={'flex'} justifyContent={'space-between'} flexDirection={'column'} h={"95%"} mt={1} p={4}>
|
||||
<Text as={'span'} fontSize={'sm'}>IO Status</Text>
|
||||
<Box display={'flex'} w={'100%'} h={'100%'} alignItems={'center'} justifyContent={'space-around'} >
|
||||
{/* <Box display={'flex'} w={'70%'} alignItems={'center'} h={325} p={6}> */}
|
||||
{/* <DonutChart data={chartData} /> */}
|
||||
|
||||
<ApexChart data={chartData} />
|
||||
{/* </Box> */}
|
||||
<VStack alignItems={'start'} justifyContent={'center'} flexWrap={'wrap'}>
|
||||
{chartData?.labels?.map((item, index) => <Text key={index} as={'span'} display={'flex'} gap={0.5} alignItems={'center'} fontSize={'sm'} fontWeight={600}><GoDotFill color={chartData?.backgroundColor[index]} fontSize={30} />{item}</Text>)}
|
||||
</VStack>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashbaord
|
||||
289
src/Pages/Login.jsx
Normal file
@@ -0,0 +1,289 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Input01 from "../Components/Inputs/Input01";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { loginUser } from "../Redux/Slice/auth";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import Button01 from "../Components/Buttons/PrimaryButton";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { TiWarning } from "react-icons/ti";
|
||||
import Loader01 from "../Components/Loaders/Loader01";
|
||||
import Asset1 from "../assets/loginpat.png";
|
||||
import logo from "../assets/logoOptifii.svg";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Image,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Text,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import GlobalStateContext from "../Contexts/GlobalStateContext";
|
||||
import Cookies from "js-cookie";
|
||||
import ToastBox from "../Components/ToastBox";
|
||||
import { useLoginMutation } from "../Services/token.serivce";
|
||||
|
||||
|
||||
// import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as Yup from "yup";
|
||||
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
emailAddress: Yup.string()
|
||||
.email("Invalid email address")
|
||||
.required("Email address is required"),
|
||||
password_hash: Yup.string().required("Password is required"),
|
||||
});
|
||||
|
||||
|
||||
const Login = () => {
|
||||
const [show, setShow] = useState(false);
|
||||
const handleClick = () => setShow(!show);
|
||||
const { isAuthenticate, setIsAuthenticate } = useContext(GlobalStateContext);
|
||||
const toast = useToast();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [login] = useLoginMutation()
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticate) {
|
||||
navigate("/sponser");
|
||||
}
|
||||
}, [navigate, isAuthenticate]);
|
||||
|
||||
const onSubmit = async (value) => {
|
||||
setIsLoading(true);
|
||||
// try {
|
||||
// // const res = await login(value).unwrap();
|
||||
|
||||
// if (res?.statusCode === 200) {
|
||||
// toast({
|
||||
// render: () => (
|
||||
// <ToastBox status={"success"} message={"res?.message"} />
|
||||
// ),
|
||||
// })
|
||||
// setIsLoading(false);
|
||||
// setIsAuthenticate(true);
|
||||
// Cookies.set("isAuthenticated", true, { expires: 7 });
|
||||
// navigate("/sponser");
|
||||
// reset();
|
||||
// }
|
||||
|
||||
// } catch (err) {
|
||||
// if (err) {
|
||||
// toast({
|
||||
// render: () => (
|
||||
// <ToastBox status={"error"} message={err?.data?.message} />
|
||||
// ),
|
||||
// });
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (value.emailAddress === "admin@optifii.com" && value.password_hash === "Admin@123") {
|
||||
return setTimeout(() => {
|
||||
// dispatch(loginUser(true));
|
||||
setIsAuthenticate(true);
|
||||
setIsLoading(false);
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"success"} message={"Login Successfully"} />
|
||||
),
|
||||
});
|
||||
|
||||
Cookies.set("isAuthenticated", true, { expires: 7 });
|
||||
navigate("/");
|
||||
}, 2000); // 3-second delay
|
||||
} else {
|
||||
return setTimeout(() => {
|
||||
// dispatch(loginUser(true));
|
||||
setIsAuthenticate(false);
|
||||
setIsLoading(false);
|
||||
|
||||
toast({
|
||||
render: () => (
|
||||
<ToastBox status={"error"} message={"Invalid credentials"} />
|
||||
),
|
||||
});
|
||||
reset();
|
||||
navigate("/login");
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
console.log(errors);
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
h={"100vh"}
|
||||
w={'100%'}
|
||||
display={'flex'}
|
||||
justifyContent={'end'}
|
||||
|
||||
overflow={'hidden'}
|
||||
bg={'#EFEFEF'}
|
||||
>
|
||||
|
||||
<Box position={'relative'} display={'flex'} justifyContent={'center'} alignItems={'center'} w={'55%'}>
|
||||
<Image w={40} src={logo} />
|
||||
|
||||
</Box>
|
||||
<Box
|
||||
as="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
w={'45%'}
|
||||
backgroundColor={'#fff'}
|
||||
p={20}
|
||||
boxShadow={'md'}
|
||||
zIndex={2}
|
||||
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'start'}
|
||||
flexDirection={'column'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box display={'flex'} flexDirection={'column'} gap={0} mb={8}>
|
||||
<Text as={'span'} fontSize={'xl'} fontWeight={600} >
|
||||
Hello Again!
|
||||
</Text>
|
||||
<Text as={'span'} fontSize={'sm'} fontWeight={400} color={'gray.500'}>
|
||||
Welcome Back
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<FormControl mb={6}>
|
||||
{/* <FormLabel className="rubix-text-dark ps-1 web-text-medium fw-bold">
|
||||
E-mail <span className="text-danger">*</span>
|
||||
</FormLabel> */}
|
||||
|
||||
<Input
|
||||
{...register("emailAddress")}
|
||||
focusBorderColor="purple.500"
|
||||
type="text"
|
||||
name="emailAddress"
|
||||
variant="filled"
|
||||
placeholder="Email"
|
||||
size="lg"
|
||||
className="web-text-medium"
|
||||
/>
|
||||
{errors.emailAddress && (
|
||||
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
|
||||
<TiWarning className="fw-bold fs-5 " /> {errors.emailAddress.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl mb={7}>
|
||||
{/* <FormLabel className="rubix-text-dark ps-1 web-text-medium fw-bold">
|
||||
Password <span className="text-danger">*</span>
|
||||
</FormLabel> */}
|
||||
|
||||
<InputGroup size="lg">
|
||||
<Input
|
||||
{...register("password_hash")}
|
||||
className="web-text-medium"
|
||||
focusBorderColor="purple.500"
|
||||
variant="filled"
|
||||
pr="4.5rem"
|
||||
type={show ? "text" : "password"}
|
||||
placeholder="Enter password"
|
||||
|
||||
/>
|
||||
<InputRightElement width="4.5rem">
|
||||
<Button
|
||||
h="1.75rem"
|
||||
size="sm"
|
||||
fontSize={"xs"}
|
||||
color={"purple.800"}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{show ? "Hide" : "Show"}
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
{errors.password_hash && (
|
||||
<span className="text-danger web-text-small fw-bold ps-2 d-flex align-items-center gap-1 mt-1">
|
||||
<TiWarning className="fw-bold fs-5 " /> {errors.password_hash.message}
|
||||
</span>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
type="submit"
|
||||
className="w-100"
|
||||
color={"whitesmoke"}
|
||||
bg="#6211CB"
|
||||
_hover={{bg:'#6211CB', opacity:0.9}}
|
||||
_active={{bg:"#6211CB", opacity:1}}
|
||||
size="md"
|
||||
fontWeight={400}
|
||||
fontSize={'sm'}
|
||||
mb={4}
|
||||
>
|
||||
Send OTP
|
||||
</Button>
|
||||
|
||||
<Text w={'100%'} color={'gray.500'} fontSize={'sm'} textAlign={'center'} >Forgot Password</Text>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
position: "absolute",
|
||||
left:0,
|
||||
bottom: "0%",
|
||||
fontSize: "13px",
|
||||
color: "#919191",
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
Optifii v1.0.0
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<Image
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: 350,
|
||||
}}
|
||||
src={Asset1}
|
||||
alt="bg-img"
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
24
src/Pages/NoInternetScreen.jsx
Normal 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
@@ -0,0 +1,22 @@
|
||||
import { Box, Image, Text } from "@chakra-ui/react"
|
||||
import error from "../assets/Error.svg"
|
||||
import robot from "../assets/optifii_logo.png"
|
||||
// import robot from "../assets/robot.png"
|
||||
const NotFound = () => {
|
||||
return (
|
||||
|
||||
<Box
|
||||
h={'100vh'}
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
flexDirection={'column'}
|
||||
gap={8}
|
||||
>
|
||||
<Image src={robot} w={"100px"} />
|
||||
<Text color={'purple.800'} as={'span'} fontSize={'small'}>The requested URL was not found on this server.</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFound
|
||||
15
src/Pages/PaymentFailed.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import { Box, Button, Heading, Image, Img, Text } from "@chakra-ui/react";
|
||||
import failed from "../assets/failed.gif";
|
||||
|
||||
const PaymentFailed = () => {
|
||||
return (
|
||||
<Box h={"100vh"} display={"flex"} alignItems={"center"} flexDirection={"column"} p={"15px"} justifyContent={"center"}>
|
||||
<Img w={"180px"} src={failed} />
|
||||
<Heading fontSize={"20px"} fontWeight={600}>Payment Failed !!</Heading>
|
||||
<Text fontSize={"18px"}>Your payment was Failed</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentFailed;
|
||||
15
src/Pages/PaymentSuccess.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import { Box, Button, Heading, Image, Img, Text } from "@chakra-ui/react";
|
||||
import success from "../assets/successimg.gif";
|
||||
|
||||
const Welcome = () => {
|
||||
return (
|
||||
<Box h={"100vh"} display={"flex"} alignItems={"center"} flexDirection={"column"} p={"15px"} justifyContent={"center"}>
|
||||
<Img w={"180px"} src={success} />
|
||||
<Heading fontSize={"20px"} fontWeight={600} textAlign={"center"}>SuccessFul !!</Heading>
|
||||
<Text fontSize={"18px"} extAlign={"center"}>Your payment was done successfully</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Welcome;
|
||||
21
src/Pages/SplashScreen.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Box, Image, Spinner, Text } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import logo from '../assets/logoOptifii.svg'
|
||||
|
||||
const SplashScreen = () => {
|
||||
return (
|
||||
<Box
|
||||
h={'100vh'}
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
flexDirection={'column'}
|
||||
gap={10}
|
||||
>
|
||||
<Image w={150} src={logo} />
|
||||
<Spinner color='purple' size='md' />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default SplashScreen
|
||||
22
src/Pages/UnderConstruction.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Box, Image, Text } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
// import noInternet from "../assets/Error.svg"
|
||||
import robot from "../assets/robot.png"
|
||||
|
||||
const UnderConstruction = ({title, h}) => {
|
||||
return (
|
||||
<Box
|
||||
h={h?h:'100vh'}
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
flexDirection={'column'}
|
||||
gap={8}
|
||||
>
|
||||
<Image src={robot} w={200} />
|
||||
<Text color={'green.800'} as={'span'} mt={4} fontSize={'small'}>🚧 Building Something Amazing Just for You! 🚧</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default UnderConstruction
|
||||
18
src/Pages/WelcomePage.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Box, Text } from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import welcome from '../Images/welcomeBanner.gif'
|
||||
|
||||
const WelcomePage = () => {
|
||||
return (
|
||||
<Box
|
||||
display={"flex"}
|
||||
justifyContent={"center"}
|
||||
alignItems={"center"}
|
||||
w={"100%"}
|
||||
h={"100%"}
|
||||
>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default WelcomePage;
|
||||
80
src/Redux/Slice/auth.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
// export const AuthMe = createAsyncThunk(
|
||||
// "auth/Me",
|
||||
// async (args, { rejectWithValue, dispatch }) => {
|
||||
// try {
|
||||
// const res = await authMeService();
|
||||
// if (res?.status === 200) return res.data;
|
||||
// return rejectWithValue(res);
|
||||
// } catch (err) {
|
||||
// return rejectWithValue(err.response);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
const initialState = {
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
error: null,
|
||||
message: null,
|
||||
isAuthenticate: false,
|
||||
};
|
||||
|
||||
const authMeSlice = createSlice({
|
||||
name: "auth",
|
||||
initialState: initialState,
|
||||
reducers: {
|
||||
logoutUser: (state, action) => {
|
||||
state = initialState;
|
||||
},
|
||||
loginUser: (state, action) => {
|
||||
state.isAuthenticate = action.payload;
|
||||
},
|
||||
},
|
||||
// extraReducers: (builder) => {
|
||||
// builder
|
||||
// .addCase(AuthMe.pending, (state) => {
|
||||
// state.isLoading = true;
|
||||
// })
|
||||
// .addCase(AuthMe.fulfilled, (state, action) => {
|
||||
// state.isLoading = false;
|
||||
// state.profileData = action.payload?.data;
|
||||
// })
|
||||
// .addCase(AuthMe.rejected, (state, action) => {
|
||||
// state.isLoading = false;
|
||||
// })
|
||||
// .addCase(AuthReGenrateAccessToken.pending, (state) => {
|
||||
// state.isLoading = true;
|
||||
// })
|
||||
// .addCase(AuthReGenrateAccessToken.fulfilled, (state, action) => {
|
||||
// state.isLoading = false;
|
||||
// state.accessToken = action.payload?.accessToken || null;
|
||||
// })
|
||||
// .addCase(AuthReGenrateAccessToken.rejected, (state) => {
|
||||
// state.isAuthenticate = false;
|
||||
// state.isLoading = false;
|
||||
// })
|
||||
// .addCase(AuthLogin.pending, (state) => {
|
||||
// state.isLoading = true;
|
||||
// })
|
||||
// .addCase(AuthLogin.fulfilled, (state, action) => {
|
||||
// state.isLoading = false;
|
||||
// state.isAuthenticate = true;
|
||||
// state.role = action.payload.role[0].title;
|
||||
// state.group = action.payload.group[0].title;
|
||||
// state.actions = action.payload.role[0].actions;
|
||||
// state.resources = action.payload.resources;
|
||||
// state.accessToken = action.payload.accessToken;
|
||||
// state.refreshToken = action.payload.refreshToken;
|
||||
// })
|
||||
// .addCase(AuthLogin.rejected, (state, action) => {
|
||||
// state.isLoading = false;
|
||||
// state.isError = true;
|
||||
// state.error = action.payload;
|
||||
// });
|
||||
// },
|
||||
});
|
||||
|
||||
export const { logoutUser, loginUser } = authMeSlice.actions;
|
||||
export default authMeSlice.reducer;
|
||||
43
src/Redux/Store.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { combineReducers } from "redux";
|
||||
import { persistReducer, persistStore } from "redux-persist";
|
||||
import storage from "redux-persist/lib/storage"; // defaults to localStorage for web
|
||||
import { encryptTransform } from "redux-persist-transform-encrypt";
|
||||
import auth from "./Slice/auth";
|
||||
|
||||
// Import your reducers and combine them
|
||||
const rootReducer = combineReducers({
|
||||
// Add your reducers here
|
||||
auth: auth,
|
||||
});
|
||||
|
||||
// Define encryption for persisted state
|
||||
const encryptor = encryptTransform({
|
||||
secretKey: "webStore",
|
||||
onError: function (error) {
|
||||
// console.log(error);
|
||||
},
|
||||
});
|
||||
|
||||
// Configuration for persisting the Redux store
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
storage,
|
||||
transforms: [encryptor],
|
||||
};
|
||||
|
||||
// Create a persisted reducer
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||
|
||||
// Create the Redux store
|
||||
export const store = configureStore({
|
||||
reducer: persistedReducer,
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
serializableCheck: false,
|
||||
}), // This line already includes thunk middleware
|
||||
});
|
||||
|
||||
// Export store state and persistor
|
||||
export const RootState = store.getState();
|
||||
export const persistor = persistStore(store);
|
||||
353
src/Routes/Nav.js
Normal file
@@ -0,0 +1,353 @@
|
||||
import { HiOutlineNewspaper } from "react-icons/hi";
|
||||
import { TbBrandMedium, TbChartHistogram, TbLayoutDashboard, TbReportMoney, TbUsersPlus } from "react-icons/tb";
|
||||
import {
|
||||
RiBankLine,
|
||||
RiDashboardLine,
|
||||
RiFileUserLine,
|
||||
RiMoneyDollarBoxLine,
|
||||
} from "react-icons/ri";
|
||||
import { RiExchangeBoxLine } from "react-icons/ri";
|
||||
import { VscGitPullRequestGoToChanges, VscSymbolClass } from "react-icons/vsc";
|
||||
import { FiUsers } from "react-icons/fi";
|
||||
import { PiCrown, PiHandCoins } from "react-icons/pi";
|
||||
import { MdOutlineAddChart, MdOutlineAdminPanelSettings } from "react-icons/md";
|
||||
import { HiOutlineChartSquareBar } from "react-icons/hi";
|
||||
import { TbListDetails } from "react-icons/tb";
|
||||
import { TbTransactionDollar } from "react-icons/tb";
|
||||
import { TbCalendarDollar } from "react-icons/tb";
|
||||
import { TbDeviceDesktopDollar } from "react-icons/tb";
|
||||
import { BiGift, BiMoneyWithdraw } from "react-icons/bi";
|
||||
import { GrDocumentUpdate, GrManual } from "react-icons/gr";
|
||||
import { MdBrowserUpdated } from "react-icons/md";
|
||||
import { AiOutlineUserDelete } from "react-icons/ai";
|
||||
import { MdNotificationsNone } from "react-icons/md";
|
||||
import { SiAcademia } from "react-icons/si";
|
||||
import { LuContact } from "react-icons/lu";
|
||||
import { LiaCrownSolid } from "react-icons/lia";
|
||||
import { PiCrownDuotone } from "react-icons/pi";
|
||||
import { IoCardOutline } from "react-icons/io5";
|
||||
import { HiOutlineGiftTop } from "react-icons/hi2";
|
||||
import { FcOnlineSupport } from "react-icons/fc";
|
||||
|
||||
export const nav = [
|
||||
// {
|
||||
// title: "Dashboard",
|
||||
// type: "single",
|
||||
// path: "/",
|
||||
// Icon: TbLayoutDashboard,
|
||||
// },
|
||||
{
|
||||
title: "MENU",
|
||||
type: "title",
|
||||
},
|
||||
{
|
||||
title: "Dashboard",
|
||||
type: "single",
|
||||
path: "/dashboard",
|
||||
Icon: RiDashboardLine,
|
||||
},
|
||||
{
|
||||
title: "Corporates",
|
||||
submenu: [
|
||||
{
|
||||
title: "Registered Corporates",
|
||||
path: "/sponser",
|
||||
},
|
||||
{
|
||||
title: "New Corporates Request",
|
||||
path: "/investment-type",
|
||||
},
|
||||
],
|
||||
type: "accordion",
|
||||
Icon: TbUsersPlus,
|
||||
},
|
||||
{
|
||||
title: "Benifits Cards",
|
||||
type: "single",
|
||||
path: "/logout",
|
||||
Icon: IoCardOutline,
|
||||
},
|
||||
{
|
||||
title: "Gift Card Management",
|
||||
type: "single",
|
||||
path: "/gift-card-management",
|
||||
Icon: HiOutlineGiftTop,
|
||||
},
|
||||
{
|
||||
title: "Voucher Management",
|
||||
type: "single",
|
||||
path: "/voucher-management",
|
||||
Icon: BiGift,
|
||||
},
|
||||
{
|
||||
title: "Marketing Management",
|
||||
submenu: [
|
||||
{
|
||||
title: "FAQ",
|
||||
path: "/manage-FAQ",
|
||||
},
|
||||
{
|
||||
title: "News & Articles",
|
||||
path: "/manage-news-& -articlesManage-news-&-articles",
|
||||
},
|
||||
{
|
||||
title: "About Us",
|
||||
path: "/manage-about-us",
|
||||
},
|
||||
{
|
||||
title: "Terms & Conditions",
|
||||
path: "/manage-terms-&-conditions",
|
||||
},
|
||||
{
|
||||
title: "Privacy Policy",
|
||||
path: "/manage-privacy-policy",
|
||||
},
|
||||
],
|
||||
type: "accordion",
|
||||
Icon: PiHandCoins,
|
||||
},
|
||||
{
|
||||
title: "Support & Ticketing Module",
|
||||
type: "single",
|
||||
path: "/support-&-ticketing-module",
|
||||
Icon: FcOnlineSupport,
|
||||
},
|
||||
// {
|
||||
// title: "Benifits Cards",
|
||||
// submenu: [
|
||||
// {
|
||||
// title: "Create IO",
|
||||
// path: "/create-io",
|
||||
// icon: MdOutlineAddChart,
|
||||
// },
|
||||
// {
|
||||
// title: "View IO",
|
||||
// path: "/view-io",
|
||||
// icon: HiOutlineChartSquareBar,
|
||||
// },
|
||||
// ],
|
||||
// type: "accordion",
|
||||
// Icon: IoCardOutline,
|
||||
// },
|
||||
// {
|
||||
// title: "Investor",
|
||||
// submenu: [
|
||||
// {
|
||||
// title: "Investor Details",
|
||||
// path: "/investor-details",
|
||||
// icon: TbListDetails,
|
||||
// },
|
||||
// // {
|
||||
// // title: "Investor Transactions",
|
||||
// // path: "/investor-transactions",
|
||||
// // icon: TbTransactionDollar,
|
||||
// // },
|
||||
// ],
|
||||
// type: "accordion",
|
||||
// Icon: TbCalendarDollar,
|
||||
// },
|
||||
// {
|
||||
// title: "INVESTORS REQUEST",
|
||||
// type: "title",
|
||||
// },
|
||||
// {
|
||||
// title: "Deposit",
|
||||
// submenu: [
|
||||
// {
|
||||
// title: "Pending Request",
|
||||
// path: "/deposit-request",
|
||||
// icon: RiMoneyDollarBoxLine,
|
||||
// },
|
||||
// {
|
||||
// title: "View History",
|
||||
// path: "/deposit-history",
|
||||
// icon: RiExchangeBoxLine,
|
||||
// },
|
||||
// ],
|
||||
// type: "accordion",
|
||||
// Icon: BiMoneyWithdraw,
|
||||
// },
|
||||
// {
|
||||
// title: "Withdrawal",
|
||||
// submenu: [
|
||||
// {
|
||||
// title: "Pending Request",
|
||||
// path: "/withdraw-request",
|
||||
// icon: RiMoneyDollarBoxLine,
|
||||
// },
|
||||
// {
|
||||
// title: "View History",
|
||||
// path: "/withdraw-history",
|
||||
// icon: RiExchangeBoxLine,
|
||||
// },
|
||||
// ],
|
||||
// type: "accordion",
|
||||
// Icon: BiMoneyWithdraw,
|
||||
// },
|
||||
// {
|
||||
// title: "Upgradation",
|
||||
// submenu: [
|
||||
// {
|
||||
// title: "Pending Request",
|
||||
// path: "/investor-request",
|
||||
// icon: RiMoneyDollarBoxLine,
|
||||
// },
|
||||
// {
|
||||
// title: "View History",
|
||||
// path: "/investor-history",
|
||||
// icon: RiExchangeBoxLine,
|
||||
// },
|
||||
// ],
|
||||
// type: "accordion",
|
||||
// Icon: MdBrowserUpdated,
|
||||
// },
|
||||
// {
|
||||
// title: "Account",
|
||||
// submenu: [
|
||||
// {
|
||||
// title: "Pending Request",
|
||||
// path: "/deletion-request",
|
||||
// icon: RiMoneyDollarBoxLine,
|
||||
// },
|
||||
// {
|
||||
// title: "View History",
|
||||
// path: "/deletion-history",
|
||||
// icon: RiExchangeBoxLine,
|
||||
// },
|
||||
// ],
|
||||
// type: "accordion",
|
||||
// Icon: AiOutlineUserDelete,
|
||||
// },
|
||||
// {
|
||||
// title: "MANAGE ADMIN",
|
||||
// type: "title",
|
||||
// },
|
||||
// {
|
||||
// title: "Admin",
|
||||
// submenu: [
|
||||
// {
|
||||
// title: "Ban / Unban Investor",
|
||||
// path: "/bank-investor",
|
||||
// icon: TbReportMoney,
|
||||
// },
|
||||
// {
|
||||
// title: "Academy",
|
||||
// path: "/academy",
|
||||
// icon: GrManual,
|
||||
// },
|
||||
// {
|
||||
// title: "Notification",
|
||||
// path: "/notification",
|
||||
// icon: MdNotificationsNone,
|
||||
// },
|
||||
// {
|
||||
// title: "Contact Details",
|
||||
// path: "/contact",
|
||||
// icon: LuContact,
|
||||
// },
|
||||
// {
|
||||
// title: "Users",
|
||||
// path: "/users",
|
||||
// icon: RiFileUserLine,
|
||||
// },
|
||||
// {
|
||||
// title: "Bank Details",
|
||||
// path: "/bank-details",
|
||||
// icon: RiBankLine,
|
||||
// },
|
||||
// ],
|
||||
// type: "accordion",
|
||||
// Icon: MdOutlineAdminPanelSettings,
|
||||
// },
|
||||
{
|
||||
title: "Logout",
|
||||
type: "single",
|
||||
path: "/login",
|
||||
Icon: HiOutlineNewspaper,
|
||||
},
|
||||
];
|
||||
|
||||
export const nestedNav = [
|
||||
{
|
||||
title: "MAIN MENU",
|
||||
type: "accordion",
|
||||
accArray: [
|
||||
{
|
||||
title: "Master",
|
||||
submenu: [
|
||||
{
|
||||
title: "Sponser",
|
||||
path: "/sponser",
|
||||
icon: RiMoneyDollarBoxLine,
|
||||
},
|
||||
{
|
||||
title: "Exchange rate",
|
||||
path: "/exchange-rate",
|
||||
icon: RiExchangeBoxLine,
|
||||
},
|
||||
{
|
||||
title: "Asset classes",
|
||||
path: "/view",
|
||||
icon: VscSymbolClass,
|
||||
},
|
||||
],
|
||||
type: "accordion",
|
||||
Icon: TbBrandMedium,
|
||||
},
|
||||
{
|
||||
title: "User",
|
||||
submenu: [
|
||||
{
|
||||
title: "Sponser",
|
||||
path: "/loop",
|
||||
icon: TbBrandMedium,
|
||||
},
|
||||
{
|
||||
title: "Class",
|
||||
path: "/class",
|
||||
icon: TbBrandMedium,
|
||||
},
|
||||
{
|
||||
title: "View",
|
||||
path: "/view",
|
||||
icon: TbBrandMedium,
|
||||
},
|
||||
],
|
||||
type: "accordion",
|
||||
Icon: HiOutlineNewspaper,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "User",
|
||||
submenu: [
|
||||
{
|
||||
title: "Sponser",
|
||||
path: "/loop",
|
||||
icon: TbBrandMedium,
|
||||
},
|
||||
{
|
||||
title: "Class",
|
||||
path: "/class",
|
||||
icon: TbBrandMedium,
|
||||
},
|
||||
{
|
||||
title: "View",
|
||||
path: "/view",
|
||||
icon: TbBrandMedium,
|
||||
},
|
||||
],
|
||||
type: "accordion",
|
||||
Icon: FiUsers,
|
||||
},
|
||||
{
|
||||
title: "SPONSER",
|
||||
type: "title",
|
||||
},
|
||||
{
|
||||
title: "Single Link",
|
||||
type: "single",
|
||||
path: "/",
|
||||
Icon: HiOutlineNewspaper,
|
||||
},
|
||||
];
|
||||
18
src/Routes/PrivateRoute.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
|
||||
import GlobalStateContext from '../Contexts/GlobalStateContext';
|
||||
|
||||
|
||||
const PrivateRoute = ({ children }) => {
|
||||
const navigate = useNavigate()
|
||||
const { isAuthenticate } = useContext(GlobalStateContext);
|
||||
const isAuthenticatedInCookie = Cookies.get("isAuthenticated");
|
||||
|
||||
|
||||
if (!isAuthenticate && isAuthenticatedInCookie !== "true") {
|
||||
return navigate('/login');
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
||||
9
src/Routes/Routes.js
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
import NotFound from "../Pages/NotFound";
|
||||
import Welcome from "../Pages/PaymentSuccess";
|
||||
|
||||
export const RouteLink = [
|
||||
// =============[ Tanami ]================
|
||||
// ===============[ Management]===============
|
||||
{ path: "/", Component: NotFound },
|
||||
];
|
||||
42
src/Services/Key.merits.service.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// io.service.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
// import { api } from "./api.service";
|
||||
import { baseQuery } from "./token.serivce";
|
||||
|
||||
// const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
export const keyMerits = createApi({
|
||||
reducerPath: "ioService",
|
||||
baseQuery: baseQuery,
|
||||
tagTypes: ["getKeyMerits"],
|
||||
endpoints: (builder) => ({
|
||||
// =====[get]
|
||||
getKeyMerits: builder.query({
|
||||
query: (id) => `/io/admin/key-merits/${id}`,
|
||||
providesTags: ["getKeyMerits"],
|
||||
}),
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
setDisplayOrder: builder.mutation({
|
||||
query: ({ data }) => ({
|
||||
url: `/io/admin/key-merits/resetDisplayOrder`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getIOById"],
|
||||
}),
|
||||
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const {
|
||||
useGetKeyMeritsQuery,
|
||||
useSetDisplayOrderMutation
|
||||
} =
|
||||
keyMerits;
|
||||
0
src/Services/api.service.js
Normal file
47
src/Services/ban.investor.service.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// investorDetails.service.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
// import { api } from "./api.service";
|
||||
import { baseQuery } from "./token.serivce";
|
||||
|
||||
// const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const banInvestorDetails = createApi({
|
||||
reducerPath: "banInvestorDetails",
|
||||
baseQuery: baseQuery,
|
||||
tagTypes: ["getBanInvestor", "getUnbanInvestor"],
|
||||
endpoints: (builder) => ({
|
||||
getInvestor: builder.query({
|
||||
query: () => `/investorDetails/admin`,
|
||||
providesTags: ["getBanInvestor"],
|
||||
}),
|
||||
|
||||
getUnbanInvestor: builder.query({
|
||||
query: () => `/investorDetails/admin/getAllUnbanned`,
|
||||
providesTags: ["getBanInvestor"],
|
||||
}),
|
||||
|
||||
getbanInvestor: builder.query({
|
||||
query: () => `/investorDetails/admin/getAllBanned`,
|
||||
providesTags: ["getBanInvestor"],
|
||||
}),
|
||||
|
||||
updateUnban: builder.mutation({
|
||||
query: ({ id, data }) => ({
|
||||
url: `/investorDetails/admin/unBanById/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getBanInvestor"],
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const {
|
||||
useGetUnbanInvestorQuery,
|
||||
useGetInvestorQuery,
|
||||
useGetbanInvestorQuery,
|
||||
useUpdateUnbanMutation,
|
||||
} = banInvestorDetails;
|
||||
43
src/Services/bank.details.service.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// investorDetails.service.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
// import { api } from "./api.service";
|
||||
import { baseQuery } from "./token.serivce";
|
||||
|
||||
// const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const bankDetails = createApi({
|
||||
reducerPath: "bankDetails",
|
||||
baseQuery: baseQuery,
|
||||
tagTypes: ["getBank"],
|
||||
|
||||
endpoints: (builder) => ({
|
||||
getBank: builder.query({
|
||||
query: ({ page, size }) =>
|
||||
`/bankDetails/admin/?page=${page}&size=${size}`,
|
||||
providesTags: ["getBank"],
|
||||
}),
|
||||
|
||||
updateBankDetails: builder.mutation({
|
||||
query: ({ data, id }) => ({
|
||||
url: `/bankDetails/admin/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getBank"],
|
||||
}),
|
||||
|
||||
getBankDetails: builder.query({
|
||||
query: ({ data, id }) => ({
|
||||
url: `/bankDetails/admin/${id}`,
|
||||
method: "GET",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getBank"],
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const { useGetBankQuery, useUpdateBankDetailsMutation,useGetBankDetailsQuery } = bankDetails;
|
||||
37
src/Services/contact.service.js
Normal 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;
|
||||
57
src/Services/delete.request.service.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// investorDetails.service.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
// import { api } from "./api.service";
|
||||
import { baseQuery } from "./token.serivce";
|
||||
|
||||
// const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const deleteRequest = createApi({
|
||||
reducerPath: "deleteRequest",
|
||||
baseQuery: baseQuery,
|
||||
tagTypes: ["getDeleteRequest", "getDeleteHistory"],
|
||||
endpoints: (builder) => ({
|
||||
|
||||
|
||||
getDeleteRequest: builder.query({
|
||||
query: () => `/account/admin/pending-request`,
|
||||
providesTags: ["getDepositRequest"],
|
||||
}),
|
||||
|
||||
getDeleteRequestById: builder.query({
|
||||
query: (id) => `/account/admin/detail/${id}`,
|
||||
}),
|
||||
|
||||
approveDepositRequest: builder.mutation({
|
||||
query: ({ id, data }) => ({
|
||||
url: `/account/admin/approved-request/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getDeleteRequest", "getDeleteHistory"],
|
||||
}),
|
||||
|
||||
deleteReject: builder.mutation({
|
||||
query: ({ id, data }) => ({
|
||||
url: `/deposit/admin/rejected/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getDeleteRequest", "getDeleteHistory"],
|
||||
}),
|
||||
|
||||
getDeleteHistory: builder.query({
|
||||
query: () => `/account/admin/history`,
|
||||
providesTags: ["getDeleteHistory"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const {
|
||||
useGetDeleteRequestQuery,
|
||||
useGetDeleteRequestByIdQuery,
|
||||
useApproveDepositRequestMutation,
|
||||
useGetDeleteHistoryQuery
|
||||
|
||||
} = deleteRequest;
|
||||
55
src/Services/deposit.request.service.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// investorDetails.service.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
// import { api } from "./api.service";
|
||||
import { baseQuery } from "./token.serivce";
|
||||
|
||||
// const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const depositRequest = createApi({
|
||||
reducerPath: "depositRequest",
|
||||
baseQuery: baseQuery,
|
||||
tagTypes: ["getDepositRequest", "getDepositHistory"],
|
||||
endpoints: (builder) => ({
|
||||
getDepositRequest: builder.query({
|
||||
query: ({page, size}) => `/deposit/admin/pending-requests?page=${page}&size=${size}`,
|
||||
providesTags: ["getDepositRequest"],
|
||||
}),
|
||||
|
||||
getDepositRequestById: builder.query({
|
||||
query: (id) => `/deposit/admin/getById/${id}`,
|
||||
}),
|
||||
|
||||
updateDepositRequest: builder.mutation({
|
||||
query: ({ id, data }) => ({
|
||||
url: `/deposit/admin/approved/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getDepositRequest", "getDepositHistory"],
|
||||
}),
|
||||
|
||||
depositReject: builder.mutation({
|
||||
query: ({ id, data }) => ({
|
||||
url: `/deposit/admin/rejected/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getDepositRequest", "getDepositHistory"],
|
||||
}),
|
||||
|
||||
getDepositHistory: builder.query({
|
||||
query: ({page, size}) => `/deposit/admin/history?page=${page}&size=${size}`,
|
||||
providesTags: ["getDepositHistory"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const {
|
||||
useGetDepositRequestQuery,
|
||||
useGetDepositRequestByIdQuery,
|
||||
useUpdateDepositRequestMutation,
|
||||
useDepositRejectMutation,
|
||||
useGetDepositHistoryQuery,
|
||||
} = depositRequest;
|
||||
101
src/Services/deposite.service.js
Normal file
@@ -0,0 +1,101 @@
|
||||
//sponser.service
|
||||
// Need to use the React-specific entry point to import createApi
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
import { api } from "./api.service";
|
||||
|
||||
const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
// const baseUrl = `${import.meta.env.VITE_API_BASE_URL}/${import.meta.env.VITE_API_VERSION}`
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const deposite = createApi({
|
||||
reducerPath: "deposite",
|
||||
baseQuery: fetchBaseQuery({ baseUrl: baseUrl }),
|
||||
tagTypes: ["gtDeposite"],
|
||||
endpoints: (builder) => ({
|
||||
|
||||
|
||||
|
||||
// ======[Get All]=====
|
||||
|
||||
getSponserMaster: builder.query({
|
||||
query: ({ page, size }) => `/sponsor/admin?page=${page}&size=${size}`,
|
||||
providesTags: ["getSponser"],
|
||||
}),
|
||||
|
||||
|
||||
// ========[Get Active]========
|
||||
|
||||
getActiveSponserMaster: builder.query({
|
||||
query: () => `/sponsor/admin/active`,
|
||||
providesTags: ["getSponser"],
|
||||
}),
|
||||
|
||||
getSponserMasterActive: builder.query({
|
||||
query: () => "/sponsor/admin/active",
|
||||
}),
|
||||
|
||||
|
||||
// ======[Get ID]=====
|
||||
|
||||
getSponserById: builder.query({
|
||||
query: (id) => `/sponsor/admin/${id}`,
|
||||
providesTags: ["getSponser"],
|
||||
}),
|
||||
|
||||
// ========[Toggle Status]========
|
||||
|
||||
toggleStatus: builder.mutation({
|
||||
query: ({ id }) => ({
|
||||
url: `/sponsor/admin/toggle-status/${id}`,
|
||||
method: "PATCH",
|
||||
}),
|
||||
invalidatesTags: ["getSponser"],
|
||||
}),
|
||||
|
||||
// ========[Create Sponser]========
|
||||
|
||||
createSponser: builder.mutation({
|
||||
query: (data) => ({
|
||||
url: `/sponsor/admin`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getSponser"],
|
||||
}),
|
||||
|
||||
// ========[Update Sponser]========
|
||||
|
||||
updateSponser: builder.mutation({
|
||||
query: ({ data, id }) => ({
|
||||
url: `/sponsor/admin/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getSponser"],
|
||||
}),
|
||||
|
||||
// ========[Delete Sponser]========
|
||||
|
||||
deleteSponser: builder.mutation({
|
||||
query: (id) => ({
|
||||
url: `/sponsor/admin/delete/${id}`,
|
||||
method: "DELETE",
|
||||
}),
|
||||
invalidatesTags: ["getSponser"],
|
||||
}),
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const {
|
||||
useGetSponserMasterQuery,
|
||||
useGetSponserMasterActiveQuery,
|
||||
useToggleStatusMutation,
|
||||
useCreateSponserMutation,
|
||||
useUpdateSponserMutation,
|
||||
useGetSponserByIdQuery,
|
||||
useDeleteSponserMutation,
|
||||
useGetActiveSponserMasterQuery
|
||||
} = sponserMaster;
|
||||
56
src/Services/drawal.request.service.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// investorDetails.service.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
// import { api } from "./api.service";
|
||||
import { baseQuery } from "./token.serivce";
|
||||
|
||||
// const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const drawalRequest = createApi({
|
||||
reducerPath: "drawalRequest",
|
||||
baseQuery: baseQuery,
|
||||
tagTypes: ["getDrawalRequest", "getDepositHistory"],
|
||||
endpoints: (builder) => ({
|
||||
|
||||
getDrawalRequest: builder.query({
|
||||
query: () => `/withdrawal/admin`,
|
||||
providesTags: ["getDrawalRequest"],
|
||||
}),
|
||||
|
||||
getDrawalRequestById: builder.query({
|
||||
query: (id) => `/withdrawal/admin/getById/${id}`,
|
||||
}),
|
||||
|
||||
updateDrawalRequest: builder.mutation({
|
||||
query: ({ id, data }) => ({
|
||||
url: `/withdrawal/admin/updateApprove/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getDrawalRequest", "getDepositHistory"],
|
||||
}),
|
||||
|
||||
depositReject: builder.mutation({
|
||||
query: ({ id, data }) => ({
|
||||
url: `/deposit/admin/rejected/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getDepositRequest", "getDepositHistory"],
|
||||
}),
|
||||
|
||||
getDrawalHistory: builder.query({
|
||||
query: () => `/withdrawal/admin/history`,
|
||||
providesTags: ["getDepositHistory"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const {
|
||||
useGetDrawalRequestQuery,
|
||||
useUpdateDrawalRequestMutation,
|
||||
useGetDrawalRequestByIdQuery,
|
||||
useDepositRejectMutation,
|
||||
useGetDrawalHistoryQuery
|
||||
} = drawalRequest;
|
||||
49
src/Services/exchange.rate.service.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// exchangeRate.service.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
// import { api } from "./api.service";
|
||||
import { baseQuery } from "./token.serivce";
|
||||
|
||||
// const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const exchangeRate = createApi({
|
||||
reducerPath: "exchangeRate",
|
||||
baseQuery: baseQuery,
|
||||
tagTypes: ["getAllExchangeRate", "getExchangeById"],
|
||||
|
||||
endpoints: (builder) => ({
|
||||
|
||||
getAllExchangeRates: builder.query({
|
||||
query: ({ page, size }) =>
|
||||
`/currencyExchange/admin?page=${page}&size=${size}`,
|
||||
providesTags: ["getAllExchangeRate"],
|
||||
}),
|
||||
|
||||
getExchangeRateById: builder.query({
|
||||
query: (id) => `/currencyExchange/admin/${id}`,
|
||||
providesTags: ["getExchangeById"],
|
||||
}),
|
||||
|
||||
updateExchangeRate: builder.mutation({
|
||||
query: ({ data, id }) => ({
|
||||
url: `/currencyExchange/admin/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ["getAllExchangeRate"],
|
||||
}),
|
||||
|
||||
getCurrencyHistoryById: builder.query({
|
||||
query: (id) => `/currencyExchange/admin/history/${id}`,
|
||||
providesTags: ["getAllExchangeRate"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const {
|
||||
useGetAllExchangeRatesQuery,
|
||||
useGetExchangeRateByIdQuery,
|
||||
useUpdateExchangeRateMutation,
|
||||
useGetCurrencyHistoryByIdQuery,
|
||||
} = exchangeRate;
|
||||
61
src/Services/investment.documents.service.js
Normal 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;
|
||||
72
src/Services/investment.type.service.js
Normal 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;
|
||||
42
src/Services/investor.details.service.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// investorDetails.service.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
// import { api } from "./api.service";
|
||||
import { baseQuery } from "./token.serivce";
|
||||
|
||||
// const baseUrl = api?.defaults.baseURL;
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const investorDetails = createApi({
|
||||
reducerPath: "investorDetails",
|
||||
baseQuery: baseQuery,
|
||||
tagTypes: [],
|
||||
endpoints: (builder) => ({
|
||||
getInvestors: builder.query({
|
||||
query: ({ page, size }) =>
|
||||
`/investorDetails/admin?page=${page}&size=${size}`,
|
||||
providesTags: ["getInvestors"],
|
||||
}),
|
||||
|
||||
// =====[get investment details ]
|
||||
getInvestorsDetailsById: builder.query({
|
||||
query: (id) => `/investorDetails/admin/byId/${id}`,
|
||||
providesTags: ["getInvestors"],
|
||||
}),
|
||||
|
||||
// =====[get investment details ]
|
||||
getInvestorsDetailsById_InInvCur: builder.query({
|
||||
query: ({ id, currencyIn }) =>
|
||||
currencyIn
|
||||
? `/investorDetails/admin/byId/${id}?currencyIn=InvCur`
|
||||
: `/investorDetails/admin/byId/${id}`,
|
||||
providesTags: ["getInvestors"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const {
|
||||
useGetInvestorsQuery,
|
||||
useGetInvestorsDetailsByIdQuery,
|
||||
useGetInvestorsDetailsById_InInvCurQuery,
|
||||
} = investorDetails;
|
||||