Compare commits

...

49 Commits

Author SHA1 Message Date
Chief Bube 57953a604c fixed height issues 2023-10-25 13:46:37 -07:00
Chief Bube d762ef42e8 Fixed positioning 2023-10-25 07:43:47 -07:00
Chief Bube a2340af314 tiny bug fix and checker added 2023-10-24 19:09:09 -07:00
Chief Bube 2a445fd2d4 tiny feature added 2023-10-24 18:55:37 -07:00
Chief Bube b5e52fb34e Fixed button position 2023-10-24 18:48:17 -07:00
ameye 87512bbc23 Merge branch 'family-balance' of WrenchBoard/Users-Wrench into master 2023-10-24 18:07:43 +00:00
victorAnumudu 1e59059394 Merge master into family-balance 2023-10-24 17:47:13 +01:00
victorAnumudu e59c83d216 made balance container take whole width 2023-10-24 17:45:10 +01:00
ameye 395569dab5 Merge branch 'job-increse-bug-fix' of WrenchBoard/Users-Wrench into master 2023-10-24 15:47:03 +00:00
Chief Bube 25a537c5bc changed duplicate 2023-10-24 08:25:52 -07:00
Chief Bube 5244a2875e Interest Popup Fix 2023-10-24 08:18:32 -07:00
victorAnumudu 54560b3fec Merge master into family-balance 2023-10-24 15:27:23 +01:00
victorAnumudu 078d26317f family balance added 2023-10-24 15:26:24 +01:00
ameye 8dbf638c7f Merge branch 'interest-popout-size' of WrenchBoard/Users-Wrench into master 2023-10-24 11:39:09 +00:00
CHIEFSOFT\ameye a68af7c3a4 cooment 2023-10-24 06:38:38 -04:00
CHIEFSOFT\ameye 905d48ceb7 package json lock 2023-10-24 06:34:30 -04:00
victorAnumudu 1e5dac0104 adjusted interest popout style 2023-10-24 11:22:51 +01:00
ameye 11e52a02f6 Merge branch 'family-wallet' of WrenchBoard/Users-Wrench into master 2023-10-24 08:51:02 +00:00
ameye 4e74198ae5 Merge branch 'job-increse-bug-fix' of WrenchBoard/Users-Wrench into master 2023-10-24 08:50:55 +00:00
ameye 5d673a630c Merge branch 'banner-fix' of WrenchBoard/Users-Wrench into master 2023-10-24 08:50:47 +00:00
victorAnumudu adcb33764b added family wallet icon 2023-10-24 07:26:27 +01:00
Chief Bube 305bff1167 Job count increase - bug fixed 2023-10-23 22:51:47 -07:00
victorAnumudu 1ebe9fe12b Fixed banner image 2023-10-23 20:37:10 +01:00
ameye 3fbaf6b78a Merge branch 'update-family-validation' of WrenchBoard/Users-Wrench into master 2023-10-23 16:37:19 +00:00
victorAnumudu 421a24c185 added validation to update family component 2023-10-23 17:23:43 +01:00
ameye 6386fbcda6 Merge branch 'family-update' of WrenchBoard/Users-Wrench into master 2023-10-23 11:34:35 +00:00
victorAnumudu 2e09d77597 bug fix 2023-10-23 12:27:54 +01:00
victorAnumudu 10dae9193c added family update component 2023-10-23 12:17:32 +01:00
ameye 3f487337f4 Merge branch 'interest-style-fix' of WrenchBoard/Users-Wrench into master 2023-10-22 18:08:02 +00:00
victorAnumudu 7eabee8855 updated interest popout btn and font style 2023-10-22 19:04:14 +01:00
ameye 360f0500a9 Merge branch 'btn-customization' of WrenchBoard/Users-Wrench into master 2023-10-22 12:16:34 +00:00
victorAnumudu 9b6c5b72ad changed cancel btn style 2023-10-22 05:52:47 +01:00
ameye afc5e25cfc Merge branch 'login-captcha' of WrenchBoard/Users-Wrench into master 2023-10-17 17:40:06 +00:00
victorAnumudu 58fe5eb82c captcha added in login page 2023-10-17 18:26:46 +01:00
CHIEFSOFT\ameye 2f3fdc0695 google captach settings 2023-10-17 07:01:05 -04:00
ameye 0a465fceb0 Merge branch 'btn-height-adjustment' of WrenchBoard/Users-Wrench into master 2023-10-16 20:26:23 +00:00
victorAnumudu 5e968db2f5 adjusted btn height 2023-10-16 21:08:06 +01:00
ameye 1916bc1a65 Merge branch 'btn-rename' of WrenchBoard/Users-Wrench into master 2023-10-16 18:41:55 +00:00
ameye 1103127cae Merge branch 'btn-customized' of WrenchBoard/Users-Wrench into master 2023-10-16 18:41:47 +00:00
ameye 751369071c Merge branch 'family-task-reload' of WrenchBoard/Users-Wrench into master 2023-10-16 18:41:39 +00:00
ameye 4cc8f27c14 Merge branch 'profile-upload-revert' of WrenchBoard/Users-Wrench into master 2023-10-16 18:41:07 +00:00
ameye aab455c2c3 Merge branch 'Changed-Naira-Wallet-Country-Display' of WrenchBoard/Users-Wrench into master 2023-10-16 13:52:03 +00:00
Ebube 21843c4bc7 Changed Naira Wallet Country Display 2023-10-16 13:43:48 +01:00
Ebube 8702728ffa Merge branch 'master' of https://gitlab.chiefsoft.net/WrenchBoard/Users-Wrench into Recent-Activities 2023-10-16 13:12:24 +01:00
victorAnumudu a8416a8433 made family page to reload after adding a task 2023-10-10 10:17:22 +01:00
victorAnumudu 70c9d1778c renamed edit job btn to save 2023-10-10 07:02:52 +01:00
Ebube 930559c96b Family Picture 2023-10-09 13:32:07 +01:00
victorAnumudu c4af2dfcc8 changed timeout duration 2023-10-09 11:29:11 +01:00
victorAnumudu b186549b8d added profile upload API 2023-10-09 11:22:38 +01:00
30 changed files with 725 additions and 139 deletions
+6 -1
View File
@@ -90,4 +90,9 @@ REACT_APP_SHOW_OFFER_GROUP_JOB=0
REACT_APP_SHOW_UPLOAD_PROFILE_PICTURE=0
#GOOGLE RECAPTCHA SITEKEY
REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6Ld_qKooAAAAADNL1TPzRLmcBA8vlpvx__39Rj39
#6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
#FAMILY MEMBER MINIMUM AGE
REACT_APP_FAMILY_MINIMUM_AGE=4
REACT_APP_FAMILY_MAXIMUM_AGE=18
+6 -1
View File
@@ -58,4 +58,9 @@ REACT_APP_SHOW_OFFER_GROUP_JOB=0
REACT_APP_SHOW_UPLOAD_PROFILE_PICTURE=0
#GOOGLE RECAPTCHA SITEKEY
REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6Ld_qKooAAAAADNL1TPzRLmcBA8vlpvx__39Rj39
#6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
#FAMILY MEMBER MINIMUM AGE
REACT_APP_FAMILY_MINIMUM_AGE=4
REACT_APP_FAMILY_MAXIMUM_AGE=18
+6 -1
View File
@@ -64,4 +64,9 @@ REACT_APP_SHOW_OFFER_GROUP_JOB=0
REACT_APP_SHOW_UPLOAD_PROFILE_PICTURE=0
#GOOGLE RECAPTCHA SITEKEY
REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6Ld_qKooAAAAADNL1TPzRLmcBA8vlpvx__39Rj39
#6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
#FAMILY MEMBER MINIMUM AGE
REACT_APP_FAMILY_MINIMUM_AGE=4
REACT_APP_FAMILY_MAXIMUM_AGE=18
+2
View File
@@ -19,6 +19,8 @@
.env.test.local
.env.production.local
#package-lock.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+74
View File
@@ -22,13 +22,17 @@
"flutterwave-react-v3": "^1.3.0",
"formik": "^2.2.9",
"react": "^18.2.0",
"react-apple-login": "^1.1.6",
"react-chartjs-2": "^4.1.0",
"react-countup": "^6.2.0",
"react-dom": "^18.2.0",
"react-google-recaptcha": "^3.1.0",
"react-qr-code": "^2.0.11",
"react-redux": "^8.0.5",
"react-router-dom": "^6.0.2",
"react-scripts": "5.0.1",
"react-slick": "^0.29.0",
"react-to-print": "^2.14.12",
"react-toastify": "^9.0.1",
"redux": "^4.2.0",
"slick-carousel": "^1.8.1",
@@ -14801,6 +14805,11 @@
"teleport": ">=0.2.0"
}
},
"node_modules/qr.js": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@@ -14944,6 +14953,32 @@
"url": "https://opencollective.com/core-js"
}
},
"node_modules/react-apple-login": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/react-apple-login/-/react-apple-login-1.1.6.tgz",
"integrity": "sha512-ySV6ax0aB+ksA7lKzhr4MvsgjwSH068VtdHJXS+7rL380IJnNQNl14SszR31k3UqB8q8C1H1oyjJFGq4MyO6tw==",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"peerDependencies": {
"prop-types": "^15.5.4",
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-async-script": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz",
"integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==",
"dependencies": {
"hoist-non-react-statics": "^3.3.0",
"prop-types": "^15.5.0"
},
"peerDependencies": {
"react": ">=16.4.1"
}
},
"node_modules/react-chartjs-2": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.3.1.tgz",
@@ -15044,11 +15079,41 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
},
"node_modules/react-google-recaptcha": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz",
"integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==",
"dependencies": {
"prop-types": "^15.5.0",
"react-async-script": "^1.2.0"
},
"peerDependencies": {
"react": ">=16.4.1"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-qr-code": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.12.tgz",
"integrity": "sha512-k+pzP5CKLEGBRwZsDPp98/CAJeXlsYRHM2iZn1Sd5Th/HnKhIZCSg27PXO58zk8z02RaEryg+60xa4vyywMJwg==",
"dependencies": {
"prop-types": "^15.8.1",
"qr.js": "0.0.0"
},
"peerDependencies": {
"react": "^16.x || ^17.x || ^18.x",
"react-native-svg": "*"
},
"peerDependenciesMeta": {
"react-native-svg": {
"optional": true
}
}
},
"node_modules/react-redux": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
@@ -15218,6 +15283,15 @@
"react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-to-print": {
"version": "2.14.15",
"resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.14.15.tgz",
"integrity": "sha512-SKnwOzU2cJ8eaAkoJO7+gNhvfEDmm+Y34IdcHsjtHioUevUPhprqbVtvNJlZ2JkGJ8ExK2QNWM9pXECTDR5D8w==",
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-toastify": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.1.tgz",
+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 64 64" viewBox="0 0 64 64" id="wallet"><path d="M60.94,33.76H59.6V19.85c0-1.69-1.37-3.06-3.06-3.06h-1.61l-0.47-6.54c-0.12-1.67-1.57-2.94-3.27-2.84l-7.3,0.52
l-0.18-2.48c-0.12-1.67-1.57-2.95-3.27-2.84L7.83,4.93c-0.82,0.06-1.56,0.43-2.1,1.05C5.19,6.6,4.93,7.39,4.99,8.2l0.61,8.59H3.06
C1.37,16.79,0,18.17,0,19.85v38.48c0,1.69,1.37,3.06,3.06,3.06h53.48c1.69,0,3.06-1.37,3.06-3.06V44.43h1.33
c1.69,0,3.06-1.37,3.06-3.06v-4.55C64,35.13,62.63,33.76,60.94,33.76z M51.42,9.94c0.27,0,0.51,0.22,0.53,0.49l0.45,6.36H18.55
l-0.28-3.97c-0.01-0.19,0.07-0.32,0.13-0.38c0.05-0.06,0.17-0.17,0.36-0.18L51.42,9.94z M7.65,7.64C7.7,7.58,7.82,7.47,8.01,7.46
l32.66-2.32c0.27,0,0.51,0.22,0.53,0.49l0.18,2.48L18.58,9.73c-0.82,0.06-1.56,0.43-2.1,1.05c-0.54,0.62-0.8,1.41-0.74,2.22
l0.27,3.79H8.14L7.52,8.02C7.5,7.83,7.59,7.7,7.65,7.64z M57.07,58.34c0,0.28-0.24,0.53-0.53,0.53H3.06
c-0.28,0-0.53-0.24-0.53-0.53V19.85c0-0.29,0.24-0.53,0.53-0.53h53.48c0.28,0,0.53,0.24,0.53,0.53v13.91h-4.43
c-1.69,0-3.06,1.37-3.06,3.06v4.55c0,1.69,1.37,3.06,3.06,3.06h4.43V58.34z M61.47,41.37c0,0.28-0.24,0.53-0.53,0.53h-8.3
c-0.28,0-0.53-0.24-0.53-0.53v-4.55c0-0.28,0.24-0.53,0.53-0.53h8.3c0.29,0,0.53,0.24,0.53,0.53V41.37z"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -24,7 +24,7 @@ const ForgetPwdResponse = ({title, message, type}) => {
<button
type="button"
onClick={() => navigate("/login")}
className={`rounded-[0.475rem] mb-6 text-[15px] font-semibold text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center py-[0.8875rem] px-[1.81rem]`}
className={`h-[48px] rounded-full mb-6 text-[15px] font-semibold text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center py-[0.8875rem] px-[1.81rem]`}
>
<span>Home</span>
</button>
@@ -176,14 +176,14 @@ export default function ForgotPassword() {
<button
type="button"
onClick={() => navigate("/login")}
className={`rounded-full mb-6 text-[15px] font-semibold text-white hover:text-white flex justify-center bg-red-500 hover:bg-red-600 transition-all duration-300 items-center py-[0.8875rem] px-[1.8125rem] `}
className={`h-[48px] rounded-full mb-6 text-[15px] font-semibold text-white hover:text-white flex justify-center bg-red-500 hover:bg-red-600 transition-all duration-300 items-center py-[0.8875rem] px-[1.8125rem] `}
>
Cancel
</button>
<button
type="button"
onClick={resetHandler}
className={`rounded-full mb-6 text-[15px] font-semibold text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center py-[0.8875rem] px-[1.81rem]`}
className={`h-[48px] rounded-full mb-6 text-[15px] font-semibold text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center py-[0.8875rem] px-[1.81rem]`}
>
{resetLoading ? (
<div className="signup btn-loader"></div>
+33
View File
@@ -14,10 +14,14 @@ import { useGoogleLogin } from "@react-oauth/google";
import { useDispatch } from "react-redux";
import { updateUserDetails } from "../../../store/UserDetails";
import ReCAPTCHA from "react-google-recaptcha";
export default function Login() {
const dispatch = useDispatch();
const { state } = useLocation();
const [validCaptcha, setValidCaptcha] = useState({show: false, valid:''}); // FOR CAPTCHA
let [loginType, setLoginType] = useState("");
const [loginLoading, setLoginLoading] = useState(false);
@@ -107,6 +111,14 @@ export default function Login() {
}, Number(process.env.REACT_APP_LOGIN_ERROR_TIMEOUT));
return;
}
if(name == "full" && !validCaptcha.valid && validCaptcha.show){ // RUNS AND DISPLAYS CAPTCHA, IF ERROR OCCURED DURING LOGIN FOR FULL LOGIN ALONE
setMsgError("Please Verify Captcha");
setLoginLoading(false);
setTimeout(() => {
setMsgError("");
}, Number(process.env.REACT_APP_LOGIN_ERROR_TIMEOUT));
return;
}
userApi
.logInUser(postData)
.then((res) => {
@@ -120,6 +132,7 @@ export default function Login() {
// setMsgError("Wrong, email/password");
setLoginError(true);
setLoginLoading(false);
setValidCaptcha(prev => ({...prev, show:true})) // DISPLAYS CAPTCHA IF ERROR
return;
}
localStorage.setItem("member_id", `${res.data.member_id}`);
@@ -135,6 +148,7 @@ export default function Login() {
.catch((error) => {
setMsgError("Unable to login, try again");
setLoginLoading(false);
setValidCaptcha(prev => ({...prev, show:true})) // DISPLAYS CAPTCHA IF ERROR
})
.finally(() => {
setTimeout(() => {
@@ -145,6 +159,14 @@ export default function Login() {
});
};
function captchaChecker(value) { // FUNCTION TO VALIDATE CAPTCHA
if(value){
setValidCaptcha({show: true, valid:value})
}else{
setValidCaptcha({show: true, valid:''})
}
}
const googleLogin = useGoogleLogin({
flow: "auth-code",
ux_mode: "redirect",
@@ -296,6 +318,17 @@ export default function Login() {
forgotPassword
/>
</div>
{/* hCaptha clone for the time being */}
{validCaptcha.show &&
<div className="mb-5 flex justify-center w-full">
<ReCAPTCHA
sitekey={process.env.REACT_APP_GOOGLE_RECAPTCHA_SITEKEY}
onChange={captchaChecker}
/>
</div>
}
{loginError && (
<div className="relative p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-thin leading-[19.5px] text-[13px]">
Invalid username or password- Please{" "}
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import dataImage2 from "../../assets/images/data-table-user-2.png";
import dataImage2 from "../../assets/images/taskbanners/default.jpg";
import { PriceFormatter } from "../Helpers/PriceFormatter";
import MarketPopUp from "../MarketPlace/PopUp/MarketPopUp";
+44 -13
View File
@@ -7,11 +7,12 @@ import React, {
useState,
} from "react";
import { useReactToPrint } from "react-to-print";
import profile from "../../assets/images/profile-info-profile.png";
import profile from "../../assets/images/icons/family.svg";
import localImgLoad from "../../lib/localImgLoad";
import usersService from "../../services/UsersService";
import LoadingSpinner from "../Spinners/LoadingSpinner";
import AssignTaskPopout from "./FamilyPopout/AssignTaskPopout";
import FamilyWallet from "./Tabs/FamilyWallet";
// Lazy Imports for components
const FamilyWaitlist = lazy(() => import("./Tabs/FamilyWaitlist"));
@@ -51,6 +52,8 @@ export default function FamilyManageTabs({
});
};
const [updatePage, setUpdatePage] = useState(false) // State to determine when to update the page
// State for family task data
const [familyTask, setFamilyTask] = useState({ loading: false, data: [] });
@@ -82,14 +85,29 @@ export default function FamilyManageTabs({
profileImgInput.current.click();
};
// Function to handle changes in the profile image input
/**
* Handles the change event of the profile image input field.
* Checks if the selected file exceeds the maximum file size limit and displays an alert if it does.
* If the file is within the size limit, it reads the file using the FileReader API and sets the profile image state with the result.
*/
const profileImgChangeHandler = (e) => {
if (e.target.value !== "") {
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const file = e.target.files[0];
if (file && file.size > MAX_FILE_SIZE) {
alert("File size exceeds the limit.");
return;
}
if (file) {
const imgReader = new FileReader();
imgReader.onload = (event) => {
setProfileImg(event.target.result);
imgReader.onload = () => {
const imageDataUrl = imgReader.result;
// Set the profile image
setProfileImg(imageDataUrl);
};
imgReader.readAsDataURL(e.target.files[0]);
imgReader.readAsDataURL(file);
}
};
@@ -148,7 +166,8 @@ export default function FamilyManageTabs({
handlePrint={useHandlePrint}
/>
),
Profile: <FamilyProfile />,
Profile: <FamilyProfile familyData={details.familyDetails.data} />,
wallet: <FamilyWallet familyData={details.familyDetails.data} />,
};
// Default tab component
@@ -220,7 +239,7 @@ export default function FamilyManageTabs({
// Invoke the manageFamily function when the component mounts
manageFamily();
}, []);
}, [updatePage]);
// Effect to manage family tasks
useEffect(() => {
@@ -280,17 +299,17 @@ export default function FamilyManageTabs({
browseProfileImg={browseProfileImg}
accountDetails={accountDetails}
/>
<div className="mt-4 flex justify-start items-center gap-2">
<div className="mt-4 flex flex-col justify-center items-center gap-2 lg:flex-row lg:justify-center lg:items-center xl:flex-col xl:justify-center xl:items-center 2xl:flex-row 2xl:justify-center 2xl:items-center 2xl:gap-[2px]">
<button
onClick={() => tabHandler("Account")}
className="family-icon p-2 border-2 border-sky-blue rounded-2xl flex flex-col justify-between items-center max-w-[65px] w-full"
>
<img
src={localImgLoad("images/icons/account.svg")}
className="max-w-[30px] w-full"
className="max-w-[30px]"
alt="Settings-Icon"
/>
<p className="text-lg text-sky-blue">Acc.</p>
<p className="text-[16px] text-sky-blue">Acc.</p>
</button>
<button
onClick={() => tabHandler("Profile")}
@@ -298,10 +317,21 @@ export default function FamilyManageTabs({
>
<img
src={localImgLoad("images/icons/profile.svg")}
className="max-w-[30px] w-full"
className="max-w-[30px]"
alt="Settings-Icon"
/>
<p className="text-lg text-sky-blue">Profile</p>
<p className="text-[16px] text-sky-blue">Profile</p>
</button>
<button
onClick={() => tabHandler("wallet")}
className="family-icon p-2 border-2 border-sky-blue rounded-2xl flex flex-col justify-between items-center max-w-[65px] w-full"
>
<img
src={localImgLoad("images/icons/wallet.svg")}
className="max-w-[30px]"
alt="Settings-Icon"
/>
<p className="text-[16px] text-sky-blue">Wallet</p>
</button>
</div>
</div>
@@ -356,6 +386,7 @@ export default function FamilyManageTabs({
setActiveTask={setActiveTask}
activeTask={activeTask}
familyDetailsData={details.familyDetails.data}
setUpdatePage={setUpdatePage}
/>
)}
</div>
@@ -15,6 +15,7 @@ const AssignTaskPopout = React.memo(
familyTask,
activeTask,
setActiveTask,
setUpdatePage
}) => {
const apiCall = new usersService();
@@ -143,6 +144,7 @@ const AssignTaskPopout = React.memo(
status: true,
message: "action successful",
});
setUpdatePage(prev => !prev) // Updates family task page by calling the useeffect hook
setTimeout(() => {
setRequestStatus({ loading: false, status: false, message: "" });
action(); // FUNCTION THAT CLOSES THE MODAL BOX
+2 -2
View File
@@ -139,8 +139,8 @@ export default function FamilyTable({
</tr>
</thead>
<tbody className="h-full">
{currentFamilyList?.map((familyMember) => {
return <FamilyRow {...familyMember} />;
{currentFamilyList?.map((familyMember, index) => {
return <FamilyRow key={index} {...familyMember} />;
})}
</tbody>
</table>
+213 -3
View File
@@ -1,11 +1,221 @@
export default function FamilyProfile({ className }) {
import { useState } from "react";
import InputCom from "../../Helpers/Inputs/InputCom";
import LoadingSpinner from "../../Spinners/LoadingSpinner";
import usersService from "../../../services/UsersService";
import { useNavigate } from "react-router-dom";
import { Formik, Form } from "formik";
import * as Yup from "yup";
export default function FamilyProfile({familyData, className }) {
const validationSchema = Yup.object().shape({
firstname: Yup.string()
.required("Firstname is required"),
lastname: Yup.string()
// .min(3, "Minimum 3 characters")
// .max(25, "Maximum 25 characters")
.required("Lastname is required"),
year: Yup.number()
.required("Year is required")
.min(new Date().getFullYear()-process.env.REACT_APP_FAMILY_MAXIMUM_AGE, `Age must be within ${process.env.REACT_APP_FAMILY_MINIMUM_AGE} to ${process.env.REACT_APP_FAMILY_MAXIMUM_AGE} years`)
.max(new Date().getFullYear()-process.env.REACT_APP_FAMILY_MINIMUM_AGE, `Age must be within ${process.env.REACT_APP_FAMILY_MINIMUM_AGE} to ${process.env.REACT_APP_FAMILY_MAXIMUM_AGE} years`),
month: Yup.number()
.required("Month is required")
.min(1, "Month is required"),
});
const navigate = useNavigate()
const api = new usersService()
let [requestStatus, setRequestStatus] = useState({loading:false, status:false, message: ''})
// let [updateDetails, setUpdateDetails] = useState({
// family_uid: familyData.uid,
// firstname: familyData.firstname,
// lastname: familyData.lastname,
// year: familyData.year,
// month: familyData.month,
// action: 22020
// })
// initial values for formik
let initialValues = {
family_uid: familyData.uid,
firstname: familyData.firstname,
lastname: familyData.lastname,
year: familyData.year,
month: familyData.month,
action: 22020
};
const handleChange = ({target:{name, value}})=>{
setUpdateDetails(prev => ({...prev, [name]:value}))
}
const updateFamily = (values, helpers) => {
setRequestStatus({loading:true, status:false, message: ''})
// let {firstname, lastname, year, month} = updateDetails
// if(!firstname || !lastname || year == 0 || month == 0) {
// setRequestStatus({loading:false, status:false, message: 'Please fill all fields'})
// return setTimeout(()=>{
// setRequestStatus({loading:false, status:false, message: ''})
// }, 5000)
// }
api.getFamilyUpdate(values).then(res=>{
if(res.data.internal_return < 0){
return setRequestStatus({loading:false, status:false, message: 'Failed, try again!'})
}
setRequestStatus({loading:false, status:true, message: 'Family account updated'})
setTimeout(()=>{
navigate('/acc-family', {replace:true})
}, 5000)
}).catch(error => {
setRequestStatus({loading:false, status:false, message: 'Unable to update, try again!'})
}).finally(()=>{
setTimeout(()=>{
setRequestStatus({loading:false, status:false, message: ''})
}, 5000)
})
}
return (
<div
className={`update-table w-full bg-white dark:bg-dark-white overflow-hidden rounded-2xl section-shadow lg:min-h-[538px] p-3 ${
className={`w-full bg-white dark:bg-dark-white overflow-hidden rounded-2xl section-shadow lg:min-h-[538px] p-3 ${
className || ""
}`}
>
<h1>Profile</h1>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={updateFamily}
>
{(props) => {
return (
<Form className="logout-modal-body w-full lg:w-[500px] lg:mx-auto flex flex-col items-center px-10 py-8 gap-4">
<InputCom
placeholder="Firstname"
label="First Name:"
name="firstname"
type="text"
parentClass="flex items-center gap-1 w-full"
labelClass="flex-[0.2] mb-0"
inputClass={`flex-[0.8] input-curve lg border border-[#dce4e9] ${props.errors.firstname && props.touched.firstname ? 'border border-red-500' : ''}`}
fieldClass="px-2"
value={props.values.firstname}
inputHandler={props.handleChange}
/>
<InputCom
placeholder="Lastname"
label="Last Name:"
name="lastname"
type="text"
parentClass="flex items-center gap-1 w-full"
labelClass="flex-[0.2] mb-0"
inputClass={`flex-[0.8] input-curve lg border border-[#dce4e9] ${props.errors.lastname && props.touched.lastname ? 'border border-red-500' : ''}`}
fieldClass="px-2"
value={props.values?.lastname}
inputHandler={props.handleChange}
/>
<div className="input-com mb-7 flex flex-col gap-1 w-full">
{/* Age dropdown */}
<div className="">
<label
className="input-label text-[#181c32] dark:text-white text-[15px] font-semibold"
htmlFor="age-selection"
>
Birthday: (Year/Month)
</label>
{((props.errors.year && props.touched.year) || (props.errors.month && props.touched.month)) && (
<span className="text-[12px] text-red-500">
{' '}{props.errors.year || props.errors.month}
</span>
)}
</div>
<div className="flex max-w-[330px] w-full self-end gap-4">
<select
name='year'
id="yearDropdown"
value={props.values.year}
onChange={props.handleChange}
className={`input-wrapper border border-[#f5f8fa] ${props.errors.year && props.touched.year ? 'border border-red-500' : ''} dark:border-[#5e6278] w-56 rounded-[35px] h-10 overflow-hidden relative font-medium leading-6 bg-clip-padding text-[#5e6278] dark:text-gray-100 bg-[#f5f8fa] dark:bg-[#5e6278] text-base focus-visible:border-transparent focus-visible:outline-0 focus-visible:ring-transparent px-4`}
>
<option value="">Select a Year</option>
{years.map((year) => (
<option key={year} value={year}>
{year}
</option>
))}
</select>
<select
name='month'
id="monthDropdown"
// value={(months.filter(month => month.id == selectedMonth)[0]?.id) || ''}
value={(months.filter(month => month.id == props.values.month)[0]?.id) || 0}
onChange={props.handleChange}
className={`input-wrapper border border-[#f5f8fa] ${props.errors.month && props.touched.month ? 'border border-red-500' : ''} dark:border-[#5e6278] w-56 rounded-[35px] h-10 overflow-hidden relative font-medium leading-6 bg-clip-padding text-[#5e6278] dark:text-gray-100 bg-[#f5f8fa] dark:bg-[#5e6278] text-base focus-visible:border-transparent focus-visible:outline-0 focus-visible:ring-transparent px-4`}
>
<option value="">Select a Month</option>
{months.map(({id, name}) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
</div>
</div>
{requestStatus.message && (
<div className={`relative p-2 ${!requestStatus.status ? 'text-[#912741] bg-[#fcd9e2] border-[#fbc6d3]' : 'text-green-700'} mb-2 rounded-[0.475rem] text-xs font-light leading-[19.5px]`}>
{requestStatus.message}
</div>
)}
<div className="flex justify-end w-full">
{requestStatus.loading ?
<>
<div className="signup btn-loader"></div>
<LoadingSpinner size='4' />
</>
:
<button
type="submit"
// onClick={updateFamily}
// className={`rounded-[0.475rem] text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center h-[42px] py-[0.8875rem] px-[1.81rem] text-[14.95px] btn-login`}
className="text-white btn-gradient text-lg tracking-wide px-6 py-2 rounded-full"
>
Update
</button>
}
</div>
</Form>
);
}}
</Formik>
</div>
);
}
// Get the current year
// const currentYear = new Date().getFullYear() - process.env.REACT_APP_FAMILY_MINIMUM_AGE;
const currentYear = new Date().getFullYear();
// Generate an array of years from the current year to (currentYear - 19)
const years = Array.from({ length: 19 }, (_, index) => currentYear - index);
const months = [
{id:'1', name:"January"},
{id:'2', name:"February"},
{id:'3', name:"March"},
{id:'4', name:"April"},
{id:'5', name:"May"},
{id:'6', name:"June"},
{id:'7', name:"July"},
{id:'8', name:"August"},
{id:'9', name:"September"},
{id:'10', name:"October"},
{id:'11', name:"November"},
{id:'12', name:"December"},
];
@@ -0,0 +1,75 @@
import React, { useEffect, useState } from 'react'
import usersService from '../../../services/UsersService'
import LoadingSpinner from '../../Spinners/LoadingSpinner'
import { PriceFormatter } from '../../Helpers/PriceFormatter'
import { localImgLoad } from '../../../lib'
import background from '../../../assets/images/bg-sky-blue.jpg'
function FamilyWallet({familyData}) {
const apiUrl = new usersService()
let [familyWallet, setFamilyWallet] = useState({loading:true, data: []})
useEffect(()=>{
apiUrl.getFamilyWallet({family_uid:familyData?.uid}).then(res => {
setFamilyWallet({loading:false, data: res?.data?.result_list || []})
}).catch(error => {
setFamilyWallet({loading:false, data: []})
})
},[])
return (
<div className='p-3 w-full h-full bg-white dark:bg-dark-white flex flex-col justify-start items-start'>
{familyWallet.loading ?
<div className='w-full h-20 flex justify-center items-center'>
<LoadingSpinner size='8' color='sky-blue' />
</div>
:
familyWallet?.data?.length > 0 ?
<div className='w-full p-4 flex flex-col gap-2'>
{familyWallet?.data?.map((wallet, index)=>(
<div key={index} className='w-full p-4 bg-[aliceblue] rounded-lg'
// style={{
// background: `url(${background}) 0% 0% / cover no-repeat`,
// }}
>
<div className="w-full flex justify-start items-center gap-3">
<div className="min-w-[50px] min-h-[50px] max-w-min md:max-w-[80px] max-h-min md:max-h-[80px] rounded-full bg-[#e3e3e3] flex justify-center items-center">
<img
src={localImgLoad(`images/currency/${(wallet.code).toLowerCase()}.svg`)}
className="w-full h-full"
alt="currency-icon"
/>
</div>
<div className="balance mt-2 flex justify-center">
<div className="flex gap-2">
<p className="text-base md:text-lg text-thin-light-gray tracking-wide mb-2 sm:mb-6">
Balance:
</p>
<p
className="text-base md:text-lg font-bold text-purple tracking-wide leading-10"
// className="text-[44px] lg:text-[62px] font-bold text-white tracking-wide leading-10 xxs:scale-100 lg:scale-100 xl:scale-125"
>
{PriceFormatter(
Number(wallet.amount) * 0.01,
wallet.code,
wallet.country,
""
)}
</p>
</div>
</div>
</div>
</div>
))}
</div>
:
<p className='text-lg text-gray-500 dark:text-gray-400'>No Wallet Found!</p>
}
</div>
)
}
export default FamilyWallet
+10 -5
View File
@@ -1,26 +1,31 @@
import React from "react";
import localImgLoad from "../../../lib/localImgLoad";
export default function ProfileInfo({
profileImg,
profileImgInput,
profileImgChangHandler,
profileImgChangeHandler,
browseProfileImg,
accountDetails,
}) {
// console.log(accountDetails.banner)
return (
<div className="flex flex-col items-center gap-4">
<div className="flex justify-center">
<div className="w-full relative">
<img
src={localImgLoad(`images/icons/${accountDetails.banner}`)}
alt=""
src={
profileImg
? profileImg
: localImgLoad(`images/icons/${accountDetails.banner}`)
}
alt="profile"
className="sm:w-[180px] sm:h-[180px] w-[120px] h-[120px] rounded-full overflow-hidden object-cover"
/>
<input
ref={profileImgInput}
onChange={(e) => profileImgChangHandler(e)}
accept="image/*"
onChange={profileImgChangeHandler}
type="file"
className="hidden"
/>
@@ -7,6 +7,10 @@ import LoadingSpinner from "../../Spinners/LoadingSpinner";
const MarketPopUp = ({ details, onClose, situation, marketInt }) => {
const [textValue, setTextValue] = useState("");
const [errMsg, setErrMsg] = useState({
market: false,
manage: false,
});
const apiCall = useMemo(() => new usersService(), []);
const handleInputChange = ({ target: { value } }) => {
@@ -48,20 +52,17 @@ const MarketPopUp = ({ details, onClose, situation, marketInt }) => {
return;
}
toast.success("Message sent", {
autoClose: 2500,
hideProgressBar: true,
});
setMarketMsg({ data: marketMessageRes, state: true });
setTimeout(() => onClose(), 2000);
} catch (error) {
setErrMsg({ market: true });
setMarketMsg({ loading: false, state: false });
throw new Error(error);
} finally {
setTextValue("");
setTimeout(() => {
setTextValue("");
setMarketMsg({ loading: false });
}, 2000);
setMarketMsg({ loading: false, state: false });
setErrMsg({ market: false });
}, 5000);
}
};
@@ -117,7 +118,7 @@ const MarketPopUp = ({ details, onClose, situation, marketInt }) => {
return (
<ModalCom action={onClose} situation={situation} className="edit-popup">
<div className="logout-modal-wrapper md:w-[750px] md:h-[660px] h-full bg-white dark:bg-dark-white lg:rounded-2xl overflow-y-auto">
<div className="logout-modal-wrapper md:w-[650px] md:h-[580px] h-full bg-white dark:bg-dark-white lg:rounded-2xl overflow-y-auto">
<div className="logout-modal-header w-full flex items-center justify-between lg:p-6 px-[30px] py-[23px]">
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
{details.offer_code}
@@ -127,7 +128,7 @@ const MarketPopUp = ({ details, onClose, situation, marketInt }) => {
<div className="md:flex bg-white dark:bg-dark-white text-slate-900 dark:text-white rounded-lg">
<div className="p-4 w-full md:w-[75%] md:border-r-1">
<div className="min-h-[263px]">
<div className="max-h-[240px] h-full">
<h2 className="font-semibold text-slate-900 dark:text-white tracking-wide">
{details?.title}
</h2>
@@ -158,9 +159,9 @@ const MarketPopUp = ({ details, onClose, situation, marketInt }) => {
<div
className={`w-full md:w-3/4 text-slate-900 dark:text-white market-pop ${
name !== "Delivery Detail"
? " h-full max-h-28 flex items-center"
: " overflow-y-auto max-h-[100px]"
}`}
? " h-full flex items-center"
: " overflow-y-auto max-h-[80px]"
} ${name === "Description" && "max-h-14 h-full overflow-auto"}`}
>
{danger ? (
<p
@@ -195,40 +196,46 @@ const MarketPopUp = ({ details, onClose, situation, marketInt }) => {
))}
</div>
<hr />
<div className="my-3 w-full flex flex-col gap-3">
<div className="w-full flex flex-col gap-3">
<div className="w-full">
<label className="w-full text-slate-900 dark:text-white tracking-wide">
<label className="w-full text-slate-900 dark:text-white tracking-wide font-semibold">
If you have any questions about this task:
</label>
<textarea
className={`p-1 w-full text-sm text-slate-900 dark:text-white bg-transparent outline-none border border-slate-300 rounded-md`}
rows="5"
className={`p-1 w-full text-sm text-slate-900 dark:text-white ${
marketMsg.loading && "italic text-[#9CA3AF]"
} bg-transparent outline-none border-2 border-slate-300 rounded-md`}
rows="3"
style={{ resize: "none" }}
placeholder="Enter message here ..."
value={textValue}
value={marketMsg.loading ? "Sending..." : textValue}
onChange={handleInputChange}
/>
</div>
<button
className="self-end w-[150px] h-[52px] rounded-md text-base bg-yellow-500 text-white"
name="market-message"
onClick={MarketDetail}
>
{marketMsg.loading ? (
<LoadingSpinner size={5} color="white" />
) : !marketMsg.state ? (
"Send Message"
) : (
"Message Sent"
)}
</button>
<div className="relative flex w-full justify-end ">
<span className="text-sm text-[#57cd89] absolute left-[8rem] top-4">
{marketMsg.state && "Message Sent!"}
{errMsg.market && "Something went wrong"}
</span>
<button
className="self-end w-[150px] h-[48px] rounded-full text-base bg-yellow-500 text-white"
name="market-message"
onClick={MarketDetail}
>
{marketMsg.loading ? (
<LoadingSpinner size={5} color="white" />
) : (
"Send Message"
)}
</button>
</div>
</div>
</div>
<div className="w-full md:w-[23%] h-full ">
<div className="mx-auto bg-[#f1f8ff] dark:bg-[#C2C8D3] p-4 rounded-md md:min-h-[498px] flex flex-col justify-between">
<div className="w-full flex flex-col justify-center py-4 gap-2">
<p className="w-full text-slate-900 tracking-wide my-1">
<div className="w-full md:w-[23%] h-full flex flex-col">
<div className="mx-auto bg-[#f1f8ff] dark:bg-[#C2C8D3] px-4 rounded-md md:min-h-[420px] flex flex-col justify-between">
<div className="w-full flex flex-col justify-center pb-4 gap-2">
<p className="w-full text-slate-900 tracking-wide my-1 font-semibold">
Interested in the task?
</p>
<hr />
@@ -270,7 +277,7 @@ const MarketPopUp = ({ details, onClose, situation, marketInt }) => {
</div>
</div>
<button
className="self-end w-[150px] mt-2 h-[52px] rounded-md text-base bg-transparent border border-red-500 text-red-500 mx-auto"
className="self-center w-[150px] mt-2 h-[48px] rounded-full text-base bg-transparent border border-red-500 text-red-500 mx-auto"
name="cancel"
onClick={onClose}
>
@@ -118,7 +118,11 @@ function PastDueJobAction({jobDetails}) {
<tr>
<td>
<div className="flex justify-center items-center">
<button type="button" onClick={popUpHandler} className="w-[180px] h-11 flex justify-center items-center btn-gradient text-base rounded-full text-white">
<button
type="button"
onClick={popUpHandler}
className="px-4 h-[48px] flex justify-center items-center btn-gradient text-base rounded-full text-white"
>
Cancel or Extend Timeline
</button>
</div>
@@ -230,8 +234,12 @@ function PastDueJobAction({jobDetails}) {
{/* cancel btn */}
<div className='flex justify-end items-center'>
<button onClick={popUpHandler} type="button" className="w-20 h-11 flex justify-center items-center border-gradient text-base rounded-full text-white">
<span className='text-gradient'>Cancel</span>
<button onClick={popUpHandler} type="button"
// className="w-20 h-11 flex justify-center items-center border-gradient text-base rounded-full text-white"
className='w-[150px] mt-2 h-[48px] rounded-full text-base bg-transparent border border-red-500 text-red-500'
>
Cancel
{/* <span className='text-gradient'>Cancel</span> */}
</button>
</div>
</div>
+44 -52
View File
@@ -12,8 +12,7 @@ function NairaWithdraw({
state,
setShowConfirmNairaWithdraw,
}) {
let MaxNoOfBanks = process.env.REACT_APP_MAX_CREDIT_BANK_ACCOUNT // HOLDS THE VALUE OF THE MAX NUMBER OF BANKS USER CAN ADD
let MaxNoOfBanks = process.env.REACT_APP_MAX_CREDIT_BANK_ACCOUNT; // HOLDS THE VALUE OF THE MAX NUMBER OF BANKS USER CAN ADD
const apiCall = new usersService();
const [tab, setTab] = useState("previous");
let [requestStatus, setRequestStatus] = useState(false);
@@ -25,7 +24,9 @@ function NairaWithdraw({
recipientID: state?.previousAccount?.recipientID || "",
},
newAccount: {
country: state?.newAccount?.amount || "",
country: wallet.walletCountry
? wallet.walletCountry[0][0]
: wallet.country,
bank: state?.newAccount?.amount || "",
accountNumber: state?.newAccount?.amount || "",
accountType: state?.newAccount?.amount || "",
@@ -67,10 +68,14 @@ function NairaWithdraw({
// Handling card change
const handleBankOptions = (event) => {
const { value } = event.target;
let bankCountry = wallet.walletCountry
? wallet.walletCountry[0][0]
: wallet.country
setBankName((prev) => ({ loading: true, data: [] }));
apiCall
.getCountryBank({ country: value })
.getCountryBank({
country: bankCountry
})
.then((res) => {
if (res.data.internal_return < 0) {
setBankName((prev) => ({ loading: false, data: [] }));
@@ -140,6 +145,7 @@ function NairaWithdraw({
};
useEffect(() => {
handleBankOptions()
getCountry(); // TO LOAD LIST COUNTRY
getAccountTypes(); // TO LOAD LIST ACCOUNT TYPES
}, []);
@@ -226,7 +232,7 @@ function NairaWithdraw({
setShowConfirmNairaWithdraw({ show: true, state: stateData });
}, 1000);
}
if (tab === "new") {
const { accountNumber, accountType, bank, city, country, state } =
values?.newAccount;
@@ -281,6 +287,8 @@ function NairaWithdraw({
getRecipients();
}, []);
console.log("Testing Wallet Country", wallet?.walletCountry[0][0]);
return (
<ModalCom action={action} situation={situation} className="edit-popup">
<div className="logout-modal-wrapper lw-[90%] md:w-[768px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl">
@@ -424,7 +432,11 @@ function NairaWithdraw({
<label
onClick={() => setTab("new")}
htmlFor="new"
className={`cursor-pointer flex items-center gap-1 ${recipients.data.length >= MaxNoOfBanks ? 'pointer-events-none':''}`}
className={`cursor-pointer flex items-center gap-1 ${
recipients.data.length >= MaxNoOfBanks
? "pointer-events-none"
: ""
}`}
>
<input
id="new"
@@ -436,7 +448,12 @@ function NairaWithdraw({
tab == "new" ? "" : ""
} tracking-wide transition duration-200`}
/>
New Account{" "} {recipients.data.length >= MaxNoOfBanks && <span className="text-[14px] text-red-500">Max Reached</span>}
New Account{" "}
{recipients.data.length >= MaxNoOfBanks && (
<span className="text-[14px] text-red-500">
Max Reached
</span>
)}
</label>
</div>
</div>
@@ -523,12 +540,12 @@ function NairaWithdraw({
</>
)}
{tab == "new" && (
recipients.loading ?
{tab == "new" &&
(recipients.loading ? (
<div className="mt-3 flex flex-col w-full h-[188px] justify-center items-center">
<LoadingSpinner size='10' color='sky-blue' />
<LoadingSpinner size="10" color="sky-blue" />
</div>
:recipients.data.length < MaxNoOfBanks ?
) : recipients.data.length < MaxNoOfBanks ? (
<div className="w-full mt-3 rounded-md bg-slate-100">
<div className="relative fields w-full flex flex-col p-4">
<div className="flex flex-[2] min-h-[52px]">
@@ -541,7 +558,12 @@ function NairaWithdraw({
Country{" "}
<span className="text-red-500">*</span>
</label>
<select
<div className="w-full text-base p-2 text-dark-gray dark:text-white border border-slate-300 outline-0 flex-[0.6] rounded-full h-[42px] overflow-hidden relative font-medium leading-6 bg-clip-padding tracking-[1.5px] flex items-center pointer-events-none">
<span className="text-slate-500 text-lg italic">
{wallet.walletCountry[0][1]}
</span>
</div>
{/* <select
className="w-full text-base p-2 text-dark-gray dark:text-white border border-slate-300 outline-0 flex-[0.6] rounded-full h-[42px] overflow-hidden relative font-medium leading-6 bg-clip-padding tracking-[1.5px]"
name="newAccount.country"
value={props.values.newAccount?.country}
@@ -552,7 +574,7 @@ function NairaWithdraw({
>
{allCountries.loading ? (
<option
className="text-slate-500 text-lg"
className="text-slate-500 text-lg"
value=""
>
Loading...
@@ -585,13 +607,7 @@ function NairaWithdraw({
No Options Found!
</option>
)}
</select>
{/* {props.errors.country &&
props.touched.country && (
<p className="text-sm text-red-500">
{props.errors.country}
</p>
)} */}
</select> */}
</div>
{/* bank name */}
@@ -646,11 +662,6 @@ function NairaWithdraw({
</option>
)}
</select>
{/* {props.errors.bank && props.touched.bank && (
<p className="text-sm text-red-500">
{props.errors.bank}
</p>
)} */}
</div>
</div>
@@ -671,16 +682,12 @@ function NairaWithdraw({
name="newAccount.accountNumber"
placeholder="Account No"
maxLength={10}
value={props.values.newAccount?.accountNumber}
value={
props.values.newAccount?.accountNumber
}
inputHandler={props.handleChange}
blurHandler={props.handleBlur}
/>
{/* {props.errors.accountNumber &&
props.touched.accountNumber && (
<p className="text-sm text-red-500">
{props.errors.accountNumber}
</p>
)} */}
</div>
{/* Account Type */}
@@ -732,12 +739,6 @@ function NairaWithdraw({
</option>
)}
</select>
{/* {props.errors.accountType &&
props.touched.accountType && (
<p className="text-sm text-red-500">
{props.errors.accountType}
</p>
)} */}
</div>
</div>
@@ -748,7 +749,8 @@ function NairaWithdraw({
htmlFor="newAccount.state"
className="input-label text-[#181c32] dark:text-white text-base font-semibold inline-flex flex-[0.4]"
>
State <span className="text-red-500">*</span>
State{" "}
<span className="text-red-500">*</span>
</label>
<InputCom
fieldClass="px-6 tracking-[1.5px]"
@@ -760,11 +762,6 @@ function NairaWithdraw({
inputHandler={props.handleChange}
blurHandler={props.handleBlur}
/>
{/* {props.errors.state && props.touched.state && (
<p className="text-sm text-red-500">
{props.errors.state}
</p>
)} */}
</div>
{/* city */}
@@ -785,20 +782,15 @@ function NairaWithdraw({
inputHandler={props.handleChange}
blurHandler={props.handleBlur}
/>
{/* {props.errors.city && props.touched.city && (
<p className="text-sm text-red-500">
{props.errors.city}
</p>
)} */}
</div>
</div>
{/* end of inputs for new accounts */}
</div>
</div>
:
) : (
<div className="mt-3 flex w-full h-[188px] justify-center items-center"></div>
)}
))}
</div>
<div className="transfer-fund-btn flex justify-end items-center gap-2 py-4">
+38 -2
View File
@@ -5,7 +5,6 @@ import Layout from "../Partials/Layout";
import LoadingSpinner from "../Spinners/LoadingSpinner";
const WalletBox = lazy(() => import("./WalletBox"));
const WalletRoutes = () => {
const apiCall = new usersService();
const { walletDetails } = useSelector((state) => state?.walletDetails); // WALLET STORE
@@ -16,6 +15,12 @@ const WalletRoutes = () => {
data: [],
});
const [allCountries, setAllCountries] = useState({
// STATE TO HOLD LIST OF COUNTRIES
loading: true,
data: [],
});
const getPaymentHistory = () => {
apiCall
.getPaymentHx()
@@ -31,14 +36,45 @@ const WalletRoutes = () => {
});
};
// FUNCTION TO GET COUNTRIES
const getCountry = () => {
apiCall
.getSignupCountryData()
.then((res) => {
if (res.data.internal_return < 0) {
setAllCountries((prev) => ({ loading: false, data: [] }));
return;
}
setAllCountries((prev) => ({
loading: false,
data: res.data.signup_country,
}));
})
.catch((error) => {
setAllCountries((prev) => ({ loading: false, data: [] }));
});
};
useEffect(() => {
getCountry();
getPaymentHistory();
}, [walletTable]);
console.log(
"Testing all country: ",
allCountries,
"Testing wallet: ",
walletDetails
);
return (
<Layout>
<Suspense fallback={<LoadingSpinner size="16" color="sky-blue" />}>
<WalletBox wallet={walletDetails} payment={paymentHistory} />
<WalletBox
wallet={walletDetails}
payment={paymentHistory}
countries={allCountries.data}
/>
</Suspense>
</Layout>
);
+2 -2
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useState } from "react";
import React, { useEffect, useState } from "react";
import usersService from "../../services/UsersService";
import ConfirmNairaWithdraw from "./Popup/ConfirmNairaWithdraw";
import NairaWithdraw from "./Popup/NairaWithdraw";
@@ -8,7 +8,7 @@ function WalletAction({ walletItem, payment, openPopUp }) {
show: false,
state: {},
}); // DETERMINES WHEN NAIRA WITHDRAWAL POPS UP
const [showConfirmNairaWithdraw, setShowConfirmNairaWithdraw] = useState({
show: false,
state: {},
+2 -2
View File
@@ -4,7 +4,7 @@ import WalletItemCard from "./WalletItemCard";
/**
* Renders a list of wallet items or a loading spinner depending on the state of the `wallet` object.
*/
export default function WalletBox({ wallet, payment }) {
export default function WalletBox({ wallet, payment, countries }) {
const { loading, data } = wallet;
return (
@@ -18,7 +18,7 @@ export default function WalletBox({ wallet, payment }) {
) : (
data.length > 0 && data.map((item) => (
<div key={item.wallet_uid} className="lg:w-full h-full mb-10 lg:mb-0">
<WalletItemCard walletItem={item} payment={payment} />
<WalletItemCard walletItem={item} payment={payment} countries={countries} />
</div>
))
)}
+7 -2
View File
@@ -10,7 +10,7 @@ import WalletAction from "./WalletAction";
/**
* Renders a card displaying information about a wallet item.
*/
export default function WalletItemCard({ walletItem, payment }) {
export default function WalletItemCard({ walletItem, payment, countries }) {
const { userDetails } = useSelector((state) => state.userDetails);
const accountType = userDetails?.account_type === "FAMILY";
const dispatch = useDispatch();
@@ -35,10 +35,15 @@ export default function WalletItemCard({ walletItem, payment }) {
dispatch(tableReload({ type: "WALLETTABLE" }));
};
const currentWalletCurrency = countries
.map((country) => country)
.filter((country) => country[0] === walletItem.country);
const image = walletItem.code
? `${walletItem.code.toLowerCase()}.svg`
: "default.png";
return (
<>
<div
@@ -88,7 +93,7 @@ export default function WalletItemCard({ walletItem, payment }) {
{!accountType && (
<WalletAction
walletItem={walletItem}
walletItem={{ ...walletItem, walletCountry: currentWalletCurrency }}
payment={payment}
openPopUp={openPopUp}
/>
+1
View File
@@ -8,6 +8,7 @@ import {
import DarkModeContext from "../Contexts/DarkModeContext";
import Icons from "../Helpers/Icons";
export default function Sidebar({
sidebar,
action,
@@ -51,6 +51,7 @@ export default function PersonalInfoTab({
coverImgInput,
browseCoverImg,
coverImgChangHandler,
uploadStatus
}) {
let { userDetails } = useSelector((state) => state.userDetails);
@@ -361,7 +362,7 @@ export default function PersonalInfoTab({
{/* inputs ends here */}
</div>
</div>
{process.env.REACT_APP_SHOW_UPLOAD_PROFILE_PICTURE != 0 &&
{/* {process.env.REACT_APP_SHOW_UPLOAD_PROFILE_PICTURE != 0 && */}
<div className="w-[232px] mb-10">
<div className="update-profile w-full mb-9">
<h1 className="text-xl tracking-wide font-bold text-dark-gray dark:text-white flex items-center mb-2">
@@ -417,9 +418,11 @@ export default function PersonalInfoTab({
</div>
</div>
</div>
{uploadStatus.message && !uploadStatus.loading && <p className={`text-center ${uploadStatus.status ? 'text-green-500':'text-red-500'}`}>{uploadStatus.message}</p>}
{uploadStatus.loading && <p className="text-center">{uploadStatus.message}</p>}
</div>
</div>
}
{/* } */}
</div>
<div className="content-footer w-full">
+62 -4
View File
@@ -24,10 +24,12 @@ import {
import RecipientAccountTab from "./Tabs/RecipientAccountTab";
export default function Settings({ faq }) {
const apiCall = new usersService();
const { userDetails } = useSelector((state) => state?.userDetails);
const [profileImg, setProfileImg] = useState(
userDetails?.profile_pic_url ? userDetails.profile_pic_url : profile
);
let [uploadStatus, setUploadStatus] = useState({loading: false, status: false, message:''}) // HOLDS STATE FOR UPLOAD PROFILE PICTURE STATUS
const [coverImg, setCoverImg] = useState(cover);
const [reloadCardList, setReloadCardList] = useState(false); // STATE TO DETERMINE WHEN CARD LIST RELOADS. EG: WHEN USER DELETES A CARD
@@ -36,12 +38,68 @@ export default function Settings({ faq }) {
const browseProfileImg = () => {
profileImgInput.current.click();
};
const profileImgChangHandler = (e) => {
// if (e.target.value !== "") {
// const imgReader = new FileReader();
// imgReader.onload = (event) => {
// setProfileImg(event.target.result);
// };
// imgReader.readAsDataURL(e.target.files[0]);
// }
setUploadStatus({loading: false, status: false, message:''})
let acceptedFormat = ["jpeg", "jpg", "png", "bmp", "gif"] // ARRAY OF SUPPORTED FORMATS
let uploadedFile = e.target.files[0] //UPLOADED FILE
if(!acceptedFormat.includes(uploadedFile?.type?.split("/")[1]?.toLowerCase())){ //CHECKING FOR CORRECT UPLOAD FORMAT
let msg = 'Please select '
for(let i=0; i<=acceptedFormat.length-1; i++){
if(i == acceptedFormat.length-1){
msg+=`or ${acceptedFormat[i]}`
}else{
msg+=`${acceptedFormat[i]}, `
}
}
setUploadStatus({loading: false, status: false, message:msg})
return setTimeout(()=>{
profileImgInput.current.value = '' // clear the input
setUploadStatus({loading: false, status: false, message:''})
},5000)
}
if(uploadedFile.size > 5*1048576){ // CHECKING FOR CORRECT FILE SIZE
setUploadStatus({loading: false, status: false, message:'File must not exceed 5MB'})
return setTimeout(()=>{
profileImgInput.current.value = '' // clear the input
setUploadStatus({loading: false, status: false, message:''})
},5000)
}
if (e.target.value !== "") {
const imgReader = new FileReader();
imgReader.onload = (event) => {
setProfileImg(event.target.result);
let reqData = { // PAYLOAD FOR API CALL
file_name: uploadedFile?.name,
file_size: uploadedFile?.size,
file_type: uploadedFile?.type?.split("/")[0]?.toLowerCase(),
file_data: event?.target?.result,
msg_type: 'FILE',
action: 'WRENCHBOARD_PICTURE_PROFILE',
// action: 11307
}
setUploadStatus({loading: true, status: false, message:'Loading...'})
apiCall.sendFiles(reqData).then(res=>{
if(res.status != 200 || res.data.internal_return < 0){
return setUploadStatus({loading: false, status: false, message: 'Something went wrong, try again'})
}
setUploadStatus({loading: false, status: true, message: 'Uploaded successfully'})
setProfileImg(event.target.result);
}).catch(error=>{
setUploadStatus({loading: false, status: false, message: 'Network error, try again'})
}).finally(()=>{
setTimeout(()=>{
setUploadStatus({loading: false, status: false, message: ''})
},5000)
})
};
imgReader.readAsDataURL(e.target.files[0]);
}
@@ -61,7 +119,6 @@ export default function Settings({ faq }) {
}
};
const apiCall = useMemo(() => new usersService(), []);
// Tabs Handling
const tabs = [
{
@@ -113,7 +170,7 @@ export default function Settings({ faq }) {
iconName: "page-right",
},
{
id: 8,
id: 9,
name: "terms",
title: "Terms and Conditions",
iconName: "page-right",
@@ -217,6 +274,7 @@ export default function Settings({ faq }) {
coverImgChangHandler={coverImgChangHandler}
browseCoverImg={browseCoverImg}
coverImgInput={coverImgInput}
uploadStatus={uploadStatus}
/>
</div>
)}
+1 -1
View File
@@ -351,7 +351,7 @@ const EditJobPopOut = ({
className="w-[120px] h-[40px] flex justify-center items-center btn-gradient text-base rounded-full text-white"
// className='w-20 h-11 flex justify-center items-center btn-gradient text-base rounded-full text-white'
>
Edit Job
Save
</button>
)}
</div>
@@ -6,6 +6,8 @@ import InputCom from "../Helpers/Inputs/InputCom/index";
import ModalCom from "../Helpers/ModalCom";
import LoadingSpinner from "../Spinners/LoadingSpinner";
import Detail from "./popoutcomponent/Detail";
import { tableReload } from "../../store/TableReloads";
import { useDispatch } from "react-redux";
const validationSchema = Yup.object().shape({
family: Yup.string().required("This is required "),
@@ -21,6 +23,8 @@ const validationSchema = Yup.object().shape({
});
function JobListPopout({ details, onClose, situation }) {
const dispatch = useDispatch()
const [familyList, setFamilyList] = useState([]);
let [loader, setLoader] = useState({
member: false,
@@ -159,6 +163,7 @@ function JobListPopout({ details, onClose, situation }) {
const res = await apiCall.assignJobTask(reqData);
let { data } = await res;
setLoader({ jobFields: false });
dispatch(tableReload({ type: "JOBTABLE" }));
onClose();
throw new Response(data);
} catch (error) {
+2 -1
View File
@@ -11,6 +11,7 @@ import { updateNotifications } from "../store/notifications";
import { updateUserJobList } from "../store/userJobList";
import { updateWalletDetails } from "../store/walletDetails";
import { formattedDate } from "../lib";
import { tableReload } from "../store/TableReloads";
const AuthRoute = ({ redirectPath = "/login", children }) => {
const apiCall = useMemo(() => new usersService(), []);
@@ -204,7 +205,7 @@ const AuthRoute = ({ redirectPath = "/login", children }) => {
}
};
getMarketActiveJobList();
}, [apiCall, dispatch]);
}, [apiCall, dispatch, jobListTable]);
//FUNCTION TO GET COMMON HEAD DATA
useEffect(() => {
+14 -1
View File
@@ -578,13 +578,14 @@ class usersService {
return this.postAuxEnd("/familyadd", postData);
}
getFamilyUpdate() {
getFamilyUpdate(reqdata) {
var postData = {
uuid: localStorage.getItem("uid"),
member_id: localStorage.getItem("member_id"),
sessionid: localStorage.getItem("session_token"),
page: 0,
limit: 100,
...reqdata
};
return this.postAuxEnd("/familyupdate", postData);
}
@@ -1077,6 +1078,18 @@ class usersService {
return this.postAuxEnd("/suggeststatus", postData);
}
// FUNCTION TO GET FAMILY WALLET
getFamilyWallet(reqData) {
var postData = {
uid: localStorage.getItem("uid"),
member_id: localStorage.getItem("member_id"),
sessionid: localStorage.getItem("session_token"),
action: 22012,
...reqData,
};
return this.postAuxEnd("/familywallet", postData);
}
/*
- 20:27:30.118 FLOG_MAX [757411]: REQ_STRING(username)
- 20:27:30.118 FLOG_MAX [757411]: REQ_STRING(password)