Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c901e5d0d | |||
| 3bcbaae4c3 | |||
| 17e972d603 | |||
| dbc821a804 | |||
| 0e4b1af1ce | |||
| ec88f304ab | |||
| 8d795a29b4 | |||
| da12f5905c | |||
| 99d4301588 | |||
| 8139fbb090 | |||
| 926837c656 | |||
| 14c894da6c | |||
| dbdc028ed4 | |||
| c3a5a2fe34 | |||
| eed2e62f62 | |||
| 824f46fd9f | |||
| ff5a73bec7 | |||
| ad37885445 | |||
| 0c3425a97a | |||
| 92ca50e02f | |||
| 4eed8abac1 | |||
| a6627555d9 | |||
| e127c79df2 | |||
| 00278e32b6 | |||
| 255da58588 | |||
| 0464025eff | |||
| c300d0d7f4 | |||
| 47ae14bc7a | |||
| 185ca14750 | |||
| 1ef92207fb | |||
| 0b8cf50088 | |||
| 57be599bb5 | |||
| 9ec512777e | |||
| 9dde87277c | |||
| a176e8e6d5 | |||
| 8106f6a049 | |||
| aa26a63fcf | |||
| 3fbcfe5797 | |||
| 510545bf82 | |||
| 7a149b87bd | |||
| 391f6e0abd | |||
| 057691b2f9 | |||
| d085636843 | |||
| 0150caf4bb | |||
| dd580cadfd | |||
| a59588d4a7 | |||
| 393b4e7b37 | |||
| b2411285bc | |||
| 091faec9bd | |||
| 15fc2e6c9b | |||
| 3ef68adf7e | |||
| 71f987cbff | |||
| e8c3dca731 | |||
| 85d15ac2f1 | |||
| 236dfeae2c | |||
| 734acd9dd5 | |||
| 3f9534b968 | |||
| 13d69efbaf | |||
| 151aa4ba0c | |||
| 4985256c3a | |||
| 5c552fe006 | |||
| b14272f040 | |||
| 5e4887c01c | |||
| f6e55718bb | |||
| 98ddc5aaae | |||
| 53d8b0f411 | |||
| b3d33f87cf | |||
| 5922064c42 | |||
| eae8dc43ec | |||
| 34e6322126 | |||
| b3db82000b | |||
| fa565437f0 | |||
| 59f407ba26 | |||
| e7afc12334 | |||
| 5f81c0b847 | |||
| 0756747143 | |||
| 99eb1591a2 | |||
| 85b9aab229 | |||
| 67a0a34288 | |||
| 814e4c9693 | |||
| e74eb38caf | |||
| 67db1a72bf | |||
| 41e46d8030 | |||
| 8707411dda | |||
| 327a4a42ae | |||
| 455f4001f7 | |||
| 775bcd5005 | |||
| 492bda021f | |||
| eacfae19f0 | |||
| 376cf44a9c | |||
| 1aab0c2910 | |||
| 1f5fb8c7c3 | |||
| a303b24b53 | |||
| 322927045c | |||
| 3cf1dc7166 | |||
| 8ed331e909 | |||
| 2428923f73 | |||
| 139f87c919 | |||
| c24fbedf2c | |||
| 1a817e723e | |||
| c023911cd4 | |||
| 907dc298ab | |||
| 8095006386 | |||
| d9d28791da | |||
| a98d967ba4 | |||
| 59a5e84ee2 | |||
| 35ed1d48a3 | |||
| f9d385dfec | |||
| 033af03221 | |||
| b3afb94030 | |||
| 0b1aa4e7d9 | |||
| 18c4f31322 | |||
| 97e3344953 | |||
| 9565ca0d35 | |||
| 51cc4edc1d | |||
| d12a1fd579 | |||
| 21f1173e66 | |||
| cf5ae81918 | |||
| 235bb7d667 | |||
| 18a319a437 | |||
| d3e2cc6744 | |||
| a08d8feb7d | |||
| 7cb5cc558b | |||
| 36025ade56 | |||
| b80c21358a | |||
| f5ad68f7bb | |||
| 27cba1ac68 | |||
| dbb21d8a08 | |||
| a4597599b7 | |||
| 894f986574 | |||
| e42a358da6 | |||
| 23c2e840c2 | |||
| 7e7176ae5c | |||
| 306fa1300d | |||
| 7b3178bd1a | |||
| 5e94ddee48 | |||
| 32171ae3ce | |||
| 2c99fedd8c | |||
| 7e926cc7bc | |||
| 587bcaa411 | |||
| 756628ee1a | |||
| c77d8e5693 | |||
| 7e9d734e6f | |||
| 0c1db6b4b5 |
@@ -95,4 +95,7 @@ REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6Ld_qKooAAAAADNL1TPzRLmcBA8vlpvx__39Rj39
|
||||
|
||||
#FAMILY MEMBER MINIMUM AGE
|
||||
REACT_APP_FAMILY_MINIMUM_AGE=4
|
||||
REACT_APP_FAMILY_MAXIMUM_AGE=18
|
||||
REACT_APP_FAMILY_MAXIMUM_AGE=18
|
||||
|
||||
#CHANGE LOGIN LAYOUT
|
||||
REACT_APP_NEW_LOGIN_LAYOUT=1
|
||||
@@ -63,4 +63,7 @@ REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6Ld_qKooAAAAADNL1TPzRLmcBA8vlpvx__39Rj39
|
||||
|
||||
#FAMILY MEMBER MINIMUM AGE
|
||||
REACT_APP_FAMILY_MINIMUM_AGE=4
|
||||
REACT_APP_FAMILY_MAXIMUM_AGE=18
|
||||
REACT_APP_FAMILY_MAXIMUM_AGE=18
|
||||
|
||||
#CHANGE LOGIN LAYOUT
|
||||
REACT_APP_NEW_LOGIN_LAYOUT=1
|
||||
@@ -70,3 +70,6 @@ REACT_APP_GOOGLE_RECAPTCHA_SITEKEY=6Ld_qKooAAAAADNL1TPzRLmcBA8vlpvx__39Rj39
|
||||
#FAMILY MEMBER MINIMUM AGE
|
||||
REACT_APP_FAMILY_MINIMUM_AGE=4
|
||||
REACT_APP_FAMILY_MAXIMUM_AGE=18
|
||||
|
||||
#CHANGE LOGIN LAYOUT
|
||||
REACT_APP_NEW_LOGIN_LAYOUT=0
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -1,16 +1,22 @@
|
||||
import Toaster from "./components/Helpers/Toaster";
|
||||
import Routers from "./Routers";
|
||||
import Default from "./components/Partials/Default";
|
||||
import { Navigate, useLocation } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Default>
|
||||
<>
|
||||
<Routers />
|
||||
<Toaster />
|
||||
</>
|
||||
</Default>
|
||||
);
|
||||
const {pathname} = useLocation()
|
||||
return (
|
||||
<Default>
|
||||
<>
|
||||
{pathname.startsWith('/@') ?
|
||||
<Navigate to="/app" replace={true} />
|
||||
:
|
||||
<Routers />
|
||||
}
|
||||
<Toaster />
|
||||
</>
|
||||
</Default>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -8,9 +8,11 @@ import AcitveBidsPage from "./views/AcitveBidsPage";
|
||||
import AuthProfilePage from "./views/AuthProfilePage";
|
||||
import CollectionItemPage from "./views/CollectionItemPage";
|
||||
import ForgotPasswordPages from "./views/ForgotPasswordPages";
|
||||
import ForgotPasswordPagesTwo from "./views/ForgotPasswordPagesTwo";
|
||||
import HistoryPage from "./views/HistoryPage";
|
||||
import HomePages from "./views/HomePages";
|
||||
import LoginPage from "./views/LoginPage";
|
||||
import LoginPageTwo from "./views/LoginPageTwo";
|
||||
import MarketPlacePage from "./views/MarketPlacePage";
|
||||
import MyWalletPage from "./views/MyWalletPage";
|
||||
import SavedPage from "./views/SavedPage";
|
||||
@@ -18,11 +20,15 @@ import SellPage from "./views/SellPage";
|
||||
import SettingsPage from "./views/SettingsPage";
|
||||
import ShopDetailsPage from "./views/ShopDetailsPage";
|
||||
import SignupPage from "./views/SignupPage";
|
||||
import SignupPageTwo from "./views/SignupPageTwo";
|
||||
import UpdatePasswordPages from "./views/UpdatePasswordPages";
|
||||
import UpdatePasswordPagesTwo from "./views/UpdatePasswordPagesTwo";
|
||||
import UploadProductPage from "./views/UploadProductPage";
|
||||
import UserProfilePage from "./views/UserProfilePage";
|
||||
import VerifyYouPages from "./views/VerifyYouPages";
|
||||
import VerifyYouPagesTwo from "./views/VerifyYouPagesTwo";
|
||||
import VerifyPasswordPages from "./views/VerifyPasswordPages";
|
||||
import VerifyPasswordPagesTwo from "./views/VerifyPasswordPagesTwo";
|
||||
import RemindersPage from './views/RemindersPage';
|
||||
import TrackingPage from "./views/TrackingPage";
|
||||
import CalendarPage from "./views/CalendarPage";
|
||||
@@ -31,6 +37,7 @@ import MyTaskPage from "./views/MyTaskPage";
|
||||
import MyJobsPage from "./views/MyJobsPage";
|
||||
import ReferralPage from "./views/ReferralPage";
|
||||
import VerifyLinkPages from "./views/VerifyLinkPages";
|
||||
import VerifyLinkPagesTwo from "./views/VerifyLinkPagesTwo";
|
||||
import MyActiveJobsPage from "./views/MyActiveJobsPage";
|
||||
import FamilyAccPage from "./views/FamilyAccPage";
|
||||
import StartJob from "./components/MyJobs/StartJob";
|
||||
@@ -50,34 +57,46 @@ import FamilyMarketPage from "./views/FamilyMarketPage";
|
||||
import FacebookRedirect from "./views/FacebookRedirect";
|
||||
import AppleRedirectPage from "./views/AppleRedirectPage";
|
||||
import LndPage from "./views/LndPage";
|
||||
import FamilySettingsPage from "./views/FamilySettingsPage";
|
||||
import AppDownloadPage from "./views/AppDownloadPage";
|
||||
import JobGroupsPage from "./views/JobGroupsPage";
|
||||
import YourPages from "./views/YourPage_";
|
||||
|
||||
export default function Routers() {
|
||||
return (
|
||||
<ScrollToTop>
|
||||
<Routes>
|
||||
{/* guest routes */}
|
||||
<Route exact path="/login" element={<LoginPage />} />
|
||||
<Route exact path="/eoffer" element={<LoginPage />} />
|
||||
<Route exact path="/invite" element={<LoginPage />} />
|
||||
|
||||
<Route exact path="/signup" element={<SignupPage />} />
|
||||
{process.env.REACT_APP_NEW_LOGIN_LAYOUT == 1 ?
|
||||
<>
|
||||
<Route exact path="/login" element={<LoginPageTwo />} />
|
||||
<Route exact path="/signup" element={<SignupPageTwo />} />
|
||||
<Route exact path="/forgot-password" element={<ForgotPasswordPagesTwo />} />
|
||||
<Route exact path="/update-password" element={<UpdatePasswordPagesTwo />} />
|
||||
<Route path="/vemail" element={<VerifyLinkPagesTwo />} />
|
||||
<Route path="/complereset" element={<VerifyPasswordPagesTwo />} />
|
||||
<Route exact path="/outmessage" element={<VerifyYouPagesTwo />} />
|
||||
<Route exact path="/eoffer" element={<LoginPageTwo />} />
|
||||
<Route exact path="/invite" element={<LoginPageTwo />} />
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<Route exact path="/login" element={<LoginPage />} />
|
||||
<Route exact path="/signup" element={<SignupPage />} />
|
||||
<Route exact path="/forgot-password" element={<ForgotPasswordPages />} />
|
||||
<Route exact path="/update-password" element={<UpdatePasswordPages />} />
|
||||
<Route path="/vemail" element={<VerifyLinkPages />} />
|
||||
<Route path="/complereset" element={<VerifyPasswordPages />} />
|
||||
<Route exact path="/outmessage" element={<VerifyYouPages />} />
|
||||
<Route exact path="/eoffer" element={<LoginPage />} />
|
||||
<Route exact path="/invite" element={<LoginPage />} />
|
||||
</>
|
||||
}
|
||||
<Route exact path="/login/auth" element={<AuthRedirect />} />
|
||||
<Route exact path="/login/auth/flogin" element={<FacebookRedirect />} />
|
||||
<Route exact path="/login/auth/apple" element={<AppleRedirectPage />} />
|
||||
<Route
|
||||
exact
|
||||
path="/forgot-password"
|
||||
element={<ForgotPasswordPages />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/update-password"
|
||||
element={<UpdatePasswordPages />}
|
||||
/>
|
||||
<Route path="/vemail" element={<VerifyLinkPages />} />
|
||||
<Route path="/complereset" element={<VerifyPasswordPages />} />
|
||||
<Route exact path="/outmessage" element={<VerifyYouPages />} />
|
||||
<Route exact path="/lnd/*" element={<LndPage />} />
|
||||
<Route exact path="/app" element={<AppDownloadPage />} />
|
||||
|
||||
{/* private route */}
|
||||
<Route element={<AuthRoute />}>
|
||||
@@ -98,6 +117,7 @@ export default function Routers() {
|
||||
<Route exact path="/market-place" element={<MarketPlacePage />} />
|
||||
<Route exact path="/market" element={<MarketPlacePage />} />
|
||||
<Route exact path="/familymarket" element={<FamilyMarketPage />} />
|
||||
<Route exact path="/familysettings" element={<FamilySettingsPage />} />
|
||||
<Route exact path="/notification" element={<Notification />} />
|
||||
<Route exact path="/mytask" element={<MyTaskPage />} />
|
||||
<Route exact path="/myjobs" element={<MyJobsPage />} />
|
||||
@@ -110,6 +130,7 @@ export default function Routers() {
|
||||
<Route exact path="/acc-family" element={<FamilyAccPage />} />
|
||||
<Route exact path="/manage-family" element={<FamilyManagePage />} />
|
||||
<Route exact path="/start-job" element={<StartJob />} />
|
||||
<Route exact path="/yourpage" element={<YourPages />} />
|
||||
<Route exact path="/manage-active-job" element={<ManageActiveJobs />} />
|
||||
<Route exact path="/blog-page" element={<BlogPage />} />
|
||||
<Route exact path="/offer-interest" element={<OffersInterestPage />} />
|
||||
@@ -130,6 +151,7 @@ export default function Routers() {
|
||||
<Route exact path="/user-profile" element={<UserProfilePage />} />
|
||||
<Route exact path="/settings" element={<SettingsPage />} />
|
||||
<Route exact path="/referral" element={<ReferralPage />} />
|
||||
<Route exact path="/job-groups" element={<JobGroupsPage />} />
|
||||
</Route>
|
||||
<Route path="*" element={<FourZeroFour />} />
|
||||
</Routes>
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" id="DownloadArrow"><g fill="#4687ba" class="color9a7baa svgShape"><path d="M30.179 3.525V1.857c0-1.551-1.784-2.415-3.011-1.449L24 2.907 20.824.402c-1.216-.959-3.002-.093-3.002 1.456v1.668a1 1 0 0 0 1 1h10.357c.552-.001 1-.449 1-1.001zM30.179 25.172v-2.524a1 1 0 0 0-1-1H18.822a1 1 0 0 0-1 1v2.524a1 1 0 0 0 1 1h10.357a1 1 0 0 0 1-1zM30.179 10.74V8.217a1 1 0 0 0-1-1H18.822a1 1 0 0 0-1 1v2.524a1 1 0 0 0 1 1h10.357c.552-.001 1-.448 1-1.001zM30.179 17.956v-2.524a1 1 0 0 0-1-1H18.822a1 1 0 0 0-1 1v2.524a1 1 0 0 0 1 1h10.357a1 1 0 0 0 1-1zM25.748 47.029l9.336-15.018c.852-1.371-.133-3.145-1.748-3.145H14.664c-1.614 0-2.6 1.774-1.748 3.145l9.336 15.018a2.058 2.058 0 0 0 3.496 0z" fill="#000000" class="color000000 svgShape"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 799 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 32 28" viewBox="0 0 32 28" id="List"><circle cx="2" cy="2" r="2" fill="#767fad" class="color4e4e50 svgShape"></circle><path fill="#767fad" d="M8 0h24v4H8z" class="color4e4e50 svgShape"></path><circle cx="10" cy="10" r="2" fill="#767fad" class="color4e4e50 svgShape"></circle><path fill="#767fad" d="M16 8h16v4H16z" class="color4e4e50 svgShape"></path><circle cx="10" cy="26" r="2" fill="#767fad" class="color4e4e50 svgShape"></circle><path fill="#767fad" d="M16 24h16v4H16z" class="color4e4e50 svgShape"></path><circle cx="18" cy="18" r="2" fill="#767fad" class="color4e4e50 svgShape"></circle><path fill="#767fad" d="M24 16h8v4h-8z" class="color4e4e50 svgShape"></path></svg>
|
||||
|
After Width: | Height: | Size: 743 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Group" x="0" y="0" version="1.1" viewBox="0 0 52 52" xml:space="preserve"><path d="M26.003 13.05c2.44 0 4.43-1.99 4.43-4.43a4.44 4.44 0 0 0-4.43-4.44c-2.45 0-4.44 1.99-4.44 4.44a4.44 4.44 0 0 0 4.44 4.43zM11.293 38.77c2.44 0 4.43-1.99 4.43-4.43a4.44 4.44 0 0 0-4.43-4.44c-2.45 0-4.43 1.99-4.43 4.44 0 2.44 1.98 4.43 4.43 4.43z" fill="#4687ba" class="color000000 svgShape"></path><path d="M49.493 41.93a10.091 10.091 0 0 0-3.643-3.739 6.418 6.418 0 0 1-5.138 2.58 6.392 6.392 0 0 1-4.371-1.737.975.975 0 0 0-.158-.258l-9.184-9.903V22.1h4.344c1.41 0 2.68-.73 3.4-1.94.72-1.23.73-2.7.04-3.95a10.09 10.09 0 0 0-3.643-3.739 6.418 6.418 0 0 1-5.138 2.579 6.43 6.43 0 0 1-5.144-2.58 10.085 10.085 0 0 0-3.645 3.74c-.69 1.25-.67 2.72.05 3.95a3.9 3.9 0 0 0 3.39 1.94h4.346v6.624c-.01.022-.016.044-.025.066l-9.22 9.941a.978.978 0 0 0-.22.42 6.378 6.378 0 0 1-4.242 1.62 6.417 6.417 0 0 1-5.14-2.584 10.086 10.086 0 0 0-3.65 3.743c-.69 1.25-.67 2.72.05 3.95a3.9 3.9 0 0 0 3.39 1.94h10.69c1.41 0 2.68-.73 3.4-1.94.72-1.23.73-2.7.04-3.95a10.03 10.03 0 0 0-2.134-2.612l8.01-8.636 8.055 8.685c-.815.73-1.53 1.58-2.08 2.563-.69 1.25-.67 2.72.05 3.95a3.9 3.9 0 0 0 3.39 1.94h10.69c1.41 0 2.68-.73 3.4-1.94.72-1.23.73-2.7.04-3.95z" fill="#4687ba" class="color000000 svgShape"></path><path d="M40.713 38.77c2.44 0 4.43-1.99 4.43-4.43a4.44 4.44 0 0 0-4.43-4.44c-2.45 0-4.44 1.99-4.44 4.44a4.44 4.44 0 0 0 4.44 4.43z" fill="#4687ba" class="color000000 svgShape"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" id="List"><switch><g fill="#767fad" class="color000000 svgShape"><g fill="#4687ba" class="color0ac5ab svgShape"><path d="M20 0H8a8 8 0 00-8 8v12a8 8 0 008 8h12a8 8 0 008-8V8a8 8 0 00-8-8zM56 0H44a8 8 0 00-8 8v12a8 8 0 008 8h12a8 8 0 008-8V8a8 8 0 00-8-8zM20 36H8a8 8 0 00-8 8v12a8 8 0 008 8h12a8 8 0 008-8V44a8 8 0 00-8-8zM56 36H44a8 8 0 00-8 8v12a8 8 0 008 8h12a8 8 0 008-8V44a8 8 0 00-8-8z" fill="#767fad" class="color000000 svgShape"></path></g></g></switch></svg>
|
||||
|
After Width: | Height: | Size: 527 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" id="List"><path d="M0 0v3h3V0H0zm4 0v1h4V0H4zm0 2v1h3V2H4zM0 4v3h3V4H0zm4 0v1h4V4H4zm0 2v1h3V6H4z" fill="#b22b7d" class="color000000 svgShape"></path></svg>
|
||||
|
After Width: | Height: | Size: 214 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" id="List"><path d="M9 3a6 6 0 1 0 6 6A6 6 0 0 0 9 3zM9 13a4 4 0 1 1 4-4A4 4 0 0 1 9 13zM17 9H28a1 1 0 0 0 0-2H17a1 1 0 0 0 0 2zM17 13h6a1 1 0 0 0 0-2H17a1 1 0 0 0 0 2zM9 17a6 6 0 1 0 6 6A6 6 0 0 0 9 17zM9 27a4 4 0 1 1 4-4A4 4 0 0 1 9 27zM28 21H17a1 1 0 0 0 0 2H28a1 1 0 0 0 0-2zM23 25H17a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2z" fill="#4687ba" class="color000000 svgShape"></path></svg>
|
||||
|
After Width: | Height: | Size: 437 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 50" id="Page"><path fill-rule="evenodd" d="M0 13V2a2 2 0 0 1 2-2h64a2 2 0 0 1 2 2v11H0Zm0 4v31a2 2 0 0 0 2 2h20V17H0Zm26 33h40a2 2 0 0 0 2-2V17H26v33Z" fill="#767fad" class="color000000 svgShape"></path></svg>
|
||||
|
After Width: | Height: | Size: 265 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 64 64" id="delete"><path d="M44.41 19.59a2 2 0 0 0-2.83 0L32 29.18l-9.59-9.59a2 2 0 0 0-2.83 2.83L29.17 32l-9.59 9.59a2 2 0 1 0 2.83 2.83L32 34.83l9.59 9.59a2 2 0 0 0 2.83-2.83L34.83 32l9.59-9.59a2 2 0 0 0-.01-2.82Z" fill="#b22b7d" class="color000000 svgShape"></path><path d="M32 3a29 29 0 1 0 29 29A29 29 0 0 0 32 3Zm0 54a25 25 0 1 1 25-25 25 25 0 0 1-25 25Z" fill="#b22b7d" class="color000000 svgShape"></path></svg>
|
||||
|
After Width: | Height: | Size: 492 B |
|
After Width: | Height: | Size: 235 KiB |
@@ -1,74 +1,28 @@
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import * as Yup from "yup";
|
||||
import usersService from "../../services/UsersService";
|
||||
import { tableReload } from "../../store/TableReloads";
|
||||
import InputCom from "../Helpers/Inputs/InputCom";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import {
|
||||
validationSchema as VS,
|
||||
useDispatch,
|
||||
useSelector,
|
||||
usersService,
|
||||
initialValues as IV,
|
||||
initialReqState,
|
||||
useState,
|
||||
tableReload,
|
||||
Formik,
|
||||
InputCom,
|
||||
Field,
|
||||
Form,
|
||||
LoadingSpinner,
|
||||
} from "./settings";
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
country: Yup.string()
|
||||
.min(1, "Minimum 3 characters")
|
||||
.max(25, "Maximum 25 characters")
|
||||
.required("Currency is required"),
|
||||
price: Yup.string()
|
||||
.typeError("Invalid number")
|
||||
.min(1, "Price must be greater than 0")
|
||||
.test("no-e", "Invalid number", (value) => {
|
||||
if (value && /\d+e/.test(value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.required("Price is required"),
|
||||
title: Yup.string()
|
||||
.min(5, "Minimum 5 characters")
|
||||
.max(149, "Maximum 149 characters")
|
||||
.required("Title is required"),
|
||||
description: Yup.string()
|
||||
.min(5, "Minimum 5 characters")
|
||||
.max(299, "Maximum 299 characters")
|
||||
.required("Description is required"),
|
||||
job_detail: Yup.string()
|
||||
.min(3, "Minimum 3 characters")
|
||||
.max(499, "Maximum 499 characters")
|
||||
.required("Details is required"),
|
||||
timeline_days: Yup.number()
|
||||
.typeError("you must specify a number")
|
||||
.min(1, "Price must be greater than 0")
|
||||
.required("Timeline is required"),
|
||||
category: Yup.array().min(1, "Select at least one checkbox"),
|
||||
});
|
||||
const validationSchema = VS;
|
||||
|
||||
function AddJob({ popUpHandler, categories }) {
|
||||
const ApiCall = new usersService();
|
||||
const { walletDetails } = useSelector((state) => state.walletDetails);
|
||||
|
||||
let dispatch = useDispatch();
|
||||
|
||||
let initialValues = {
|
||||
// initial values for formik
|
||||
country: "",
|
||||
price: "",
|
||||
title: "",
|
||||
description: "",
|
||||
job_detail: "",
|
||||
timeline_days: "",
|
||||
category: [],
|
||||
};
|
||||
|
||||
let [requestStatus, setRequestStatus] = useState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
}); // Holds state when submit button is pressed
|
||||
|
||||
const getWalletDetail = (country) => { // A FUNCTION TO GET USER BALANCE BASED ON COUNTRY SELECTED
|
||||
const walletChecker = walletDetails?.data.find(
|
||||
(item) => item.country === country
|
||||
);
|
||||
return walletChecker ? walletChecker.amount : 0;
|
||||
};
|
||||
const [requestStatus, setRequestStatus] = useState(initialReqState); // Holds state when submit button is pressed
|
||||
|
||||
const handleAddJob = async (values, helpers) => {
|
||||
const reqData = {
|
||||
@@ -81,21 +35,6 @@ function AddJob({ popUpHandler, categories }) {
|
||||
category: values.category?.join("@"),
|
||||
};
|
||||
|
||||
const walletAmount = getWalletDetail(reqData.country); // GETTING USER BALANCE BASED ON COUNTRY SELECTED
|
||||
|
||||
if (reqData.price > walletAmount) {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Insufficient Balance",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 1500);
|
||||
return;
|
||||
}
|
||||
|
||||
setRequestStatus({ loading: true, status: false, message: "" });
|
||||
|
||||
try {
|
||||
@@ -136,7 +75,7 @@ function AddJob({ popUpHandler, categories }) {
|
||||
return (
|
||||
<div className="add-job p-5 w-full bg-white dark:bg-dark-white dark:text-white rounded-md flex flex-col justify-between">
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
initialValues={IV}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleAddJob}
|
||||
>
|
||||
@@ -167,7 +106,7 @@ function AddJob({ popUpHandler, categories }) {
|
||||
onChange={props.handleChange}
|
||||
onBlur={props.handleBlur}
|
||||
>
|
||||
{walletDetails.loading ? (
|
||||
{walletDetails?.loading ? (
|
||||
<option className="text-slate-500 text-lg" value="">
|
||||
Loading...
|
||||
</option>
|
||||
@@ -258,7 +197,7 @@ function AddJob({ popUpHandler, categories }) {
|
||||
<div className="sm:w-[60%] w-full">
|
||||
<label
|
||||
htmlFor="Job Delivery Details"
|
||||
className='className="input-label text-[#181c32] dark:text-white text-[13.975px] leading-[20.9625px] font-semibold flex items-center gap-1'
|
||||
className="input-label text-[#181c32] dark:text-white text-[13.975px] leading-[20.9625px] font-semibold flex items-center gap-1"
|
||||
>
|
||||
Job Delivery Details
|
||||
{props.errors.job_detail &&
|
||||
@@ -400,13 +339,12 @@ function AddJob({ popUpHandler, categories }) {
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{requestStatus.loading ? (
|
||||
{requestStatus?.loading ? (
|
||||
<LoadingSpinner size="8" color="sky-blue" />
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
className="w-[152px] h-[46px] 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'
|
||||
>
|
||||
Add Job
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Field, Form, Formik } from "formik";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import * as Yup from "yup";
|
||||
import usersService from "../../services/UsersService";
|
||||
import { tableReload } from "../../store/TableReloads";
|
||||
import InputCom from "../Helpers/Inputs/InputCom";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
|
||||
// Initialize state for request values
|
||||
const initialReqState = {
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
};
|
||||
|
||||
// For form initial values
|
||||
const initialValues = {
|
||||
// initial values for formik
|
||||
country: "",
|
||||
price: "",
|
||||
title: "",
|
||||
description: "",
|
||||
job_detail: "",
|
||||
timeline_days: "",
|
||||
category: [],
|
||||
};
|
||||
|
||||
// const getWalletDetail = (country) => { // A FUNCTION TO GET USER BALANCE BASED ON COUNTRY SELECTED
|
||||
// const walletChecker = walletDetails?.data.find(
|
||||
// (item) => item.country === country
|
||||
// );
|
||||
// return walletChecker ? walletChecker.amount : 0;
|
||||
// };
|
||||
|
||||
// To get the validation schema
|
||||
const validationSchema = Yup.object().shape({
|
||||
country: Yup.string()
|
||||
.min(1, "Minimum 3 characters")
|
||||
.max(25, "Maximum 25 characters")
|
||||
.required("Currency is required"),
|
||||
price: Yup.string()
|
||||
.typeError("Invalid number")
|
||||
.min(1, "Price must be greater than 0")
|
||||
.test("no-e", "Invalid number", (value) => {
|
||||
if (value && /\d+e/.test(value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.required("Price is required"),
|
||||
title: Yup.string()
|
||||
.min(5, "Minimum 5 characters")
|
||||
.max(149, "Maximum 149 characters")
|
||||
.required("Title is required"),
|
||||
description: Yup.string()
|
||||
.min(5, "Minimum 5 characters")
|
||||
.max(299, "Maximum 299 characters")
|
||||
.required("Description is required"),
|
||||
job_detail: Yup.string()
|
||||
.min(3, "Minimum 3 characters")
|
||||
.max(499, "Maximum 499 characters")
|
||||
.required("Details is required"),
|
||||
timeline_days: Yup.number()
|
||||
.typeError("you must specify a number")
|
||||
.min(1, "Price must be greater than 0")
|
||||
.required("Timeline is required"),
|
||||
category: Yup.array().min(1, "Select category"),
|
||||
});
|
||||
|
||||
const getWalletDetail = (countryParams, walletDetails) => {
|
||||
// A FUNCTION TO GET USER BALANCE BASED ON COUNTRY SELECTED
|
||||
const walletChecker = walletDetails?.data.find(
|
||||
(item) => item.country === countryParams
|
||||
);
|
||||
return walletChecker
|
||||
? {
|
||||
description: walletChecker.description,
|
||||
country: walletChecker.country,
|
||||
}
|
||||
: "";
|
||||
};
|
||||
|
||||
export {
|
||||
Field,
|
||||
Form,
|
||||
Formik,
|
||||
useState,
|
||||
useEffect,
|
||||
useDispatch,
|
||||
useSelector,
|
||||
usersService,
|
||||
InputCom,
|
||||
LoadingSpinner,
|
||||
initialReqState,
|
||||
initialValues,
|
||||
validationSchema,
|
||||
getWalletDetail,
|
||||
tableReload
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function AppDownload() {
|
||||
return (
|
||||
<div>
|
||||
<div className='h-screen flex justify-center items-center'>App Download Content Comes here</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { lazy } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { localImgLoad } from "../../lib";
|
||||
|
||||
export default function LoginLayout({ slogan, children }) {
|
||||
const bgImg = localImgLoad('images/left-wrenchboard.jpg')
|
||||
|
||||
return (
|
||||
<div className={`layout-wrapper login`}>
|
||||
<div className={`w-full min-h-screen overflow-y-auto lg:grid grid-cols-2`}>
|
||||
<div
|
||||
className={`auth-bg hidden lg:block bg-blue-50 relative bg-cover bg-no-repeat border-0 after:content-[''] after:absolute after:inset-0`}
|
||||
style={{backgroundImage: `url(${bgImg})`}}
|
||||
>
|
||||
</div>
|
||||
<div className="p-5 sm:p-7 flex place-content-center">
|
||||
<div className="py-10 w-11/12 h-full flex flex-col justify-between items-center content-wrapper login shadow-md xl:bg-white dark:bg-dark-white rounded-[0.475rem]">
|
||||
<div className="w-full flex justify-center items-center">
|
||||
{children && children}
|
||||
</div>
|
||||
<div className="w-full flex flex-col justify-center items-center px-10">
|
||||
<div className="w-full flex justify-center items-center pt-5">
|
||||
<div className="flex items-center">
|
||||
<a
|
||||
href="https://www.wrenchboard.com/about-us"
|
||||
className="text-[#a1a5b7] text-[15px] px-2 font-medium hover:text-[#009ef7]"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
<a
|
||||
href="https://www.wrenchboard.com/service"
|
||||
className="text-[#a1a5b7] text-[15px] px-2 font-medium hover:text-[#009ef7]"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Services
|
||||
</a>
|
||||
<a
|
||||
href="https://www.wrenchboard.com/contact"
|
||||
className="text-[#a1a5b7] text-[15px] px-2 font-medium hover:text-[#009ef7]"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Contact Us
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p className="py-1 text-black text-[15px] px-2 font-medium flex items-center gap-1">
|
||||
<span className="">© {new Date().getFullYear()} -</span>
|
||||
<Link to="/" className="text-[#009ef7] ml-1">
|
||||
WrenchBoard
|
||||
</Link>{" "}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export default function ForgotPassword() {
|
||||
}
|
||||
|
||||
if (email !== "" && checked) {
|
||||
const reqData = { email, action:11013 };
|
||||
const reqData = { email };
|
||||
setResetLoading(true);
|
||||
try {
|
||||
const res = await userApi.StartResetPassword(reqData);
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import WrenchBoard from "../../../assets/images/wrenchboard-logo-text.png";
|
||||
import usersService from "../../../services/UsersService";
|
||||
import InputCom from "../../Helpers/Inputs/InputCom";
|
||||
import AuthLayout from "../AuthLayout2";
|
||||
import EmailValidator from "../../../lib/EmailValidator";
|
||||
import ForgetPwdResponse from "../ForgetPwdResponse";
|
||||
|
||||
import ReCAPTCHA from "react-google-recaptcha";
|
||||
|
||||
export default function ForgotPassword() {
|
||||
const [checked, setValue] = useState(false);
|
||||
const [resetLoading, setResetLoading] = useState(false);
|
||||
// email
|
||||
const [email, setMail] = useState("");
|
||||
const [msgError, setMsgError] = useState("");
|
||||
const [msgSuccess, setMsgSuccess] = useState(null);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const userApi = new usersService();
|
||||
|
||||
const handleEmail = (e) => {
|
||||
setMail(e?.target.value);
|
||||
};
|
||||
|
||||
// const humanChecker = () => {
|
||||
// setValue(!checked);
|
||||
// };
|
||||
|
||||
function humanChecker(value) {
|
||||
// console.log("Captcha value:", value);
|
||||
if(value){
|
||||
setValue(true)
|
||||
}else{
|
||||
setValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
const resetHandler = async () => {
|
||||
if (email == "") {
|
||||
setMsgError("An email is required");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
if (!checked) {
|
||||
setMsgError("Check if you are human");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
|
||||
if(!EmailValidator(email)){ // CHECKS IF EMAIL IS VALID
|
||||
setMsgError("Invalid Email");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
|
||||
if (email !== "" && checked) {
|
||||
const reqData = { email };
|
||||
setResetLoading(true);
|
||||
try {
|
||||
const res = await userApi.StartResetPassword(reqData);
|
||||
if (res.status === 200) {
|
||||
setMsgSuccess(true);
|
||||
setMail("");
|
||||
setValue(false);
|
||||
setResetLoading(false);
|
||||
}else{
|
||||
setMsgSuccess(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setMsgSuccess(false);
|
||||
setResetLoading(false);
|
||||
setMail("");
|
||||
setMsgError("An error occurred");
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to WrenchBoard">
|
||||
<div className="w-full">
|
||||
<div className="mb-12">
|
||||
<Link to="#">
|
||||
<img
|
||||
src={WrenchBoard}
|
||||
alt="wrenchboard"
|
||||
className="h-10 mx-auto"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex place-content-center">
|
||||
<div className="w-11/12 sm:max-w-[400px]">
|
||||
{msgSuccess == null ?
|
||||
<>
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
Forget Password
|
||||
</h1>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
Enter your email to reset your password.
|
||||
</span>
|
||||
</div>
|
||||
<div className="input-area">
|
||||
<div className="input-item mb-10">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
placeholder="Your Username/Email"
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={email}
|
||||
inputHandler={handleEmail}
|
||||
iconName="message"
|
||||
/>
|
||||
</div>
|
||||
{/* hCaptha clone for the time being */}
|
||||
<div className="mb-10 flex justify-center w-full">
|
||||
<ReCAPTCHA
|
||||
sitekey={process.env.REACT_APP_GOOGLE_RECAPTCHA_SITEKEY}
|
||||
onChange={humanChecker}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="mb-10">
|
||||
<div className="w-[303px] h-[78px] mx-auto overflow-hidden">
|
||||
<div className="w-[300px] h-[74px] bg-white bottom-[1px] rounded border-gray-100 overflow-hidden cursor-pointer">
|
||||
|
||||
<div className="h-full relative inline-block">
|
||||
<div className="relative table top-0 h-full">
|
||||
<div className="table-cell align-middle">
|
||||
<div className="relative w-[30px] h-[30px] mx-[15px]">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="human-checkbox"
|
||||
id="human-checkbox"
|
||||
className="w-[28px] h-[28px] border-[1px] rounded border-gray-400 checked:bg-white"
|
||||
checked={checked}
|
||||
onChange={humanChecker}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full relative inline-block w-[170px]">
|
||||
<label className="relative table top-0 h-full">
|
||||
<label className="table-cell align-middle">
|
||||
<label
|
||||
className="text-800 text-sm"
|
||||
htmlFor="human-checkbox"
|
||||
>
|
||||
I am human
|
||||
</label>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
<div className="h-full relative inline-block w-16"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
{msgError && (
|
||||
<div className="relative p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]">
|
||||
{msgError}
|
||||
</div>
|
||||
)}
|
||||
<div className="signin-area mb-3.5">
|
||||
<div className="flex justify-center items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate("/login")}
|
||||
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={`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>
|
||||
) : (
|
||||
<span>Send Code</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<ForgetPwdResponse
|
||||
title={'Forget Password'}
|
||||
message={msgSuccess? `Check your email for the link to continue password reset. Note the reset link will expire short time` : 'We are unable to continue with your request. Please try another username or contact us for help'}
|
||||
type={msgSuccess}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -17,14 +17,16 @@ import { updateUserDetails } from "../../../store/UserDetails";
|
||||
import ReCAPTCHA from "react-google-recaptcha";
|
||||
|
||||
export default function Login() {
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const queryParams = new URLSearchParams(location?.search);
|
||||
const sessionExpired = queryParams.get("sessionExpired")
|
||||
// const sessionExpired = queryParams.get("sessionExpired");
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { state } = useLocation();
|
||||
|
||||
const [validCaptcha, setValidCaptcha] = useState({show: false, valid:''}); // FOR CAPTCHA
|
||||
const [sessionExpired, setSessionExpired] = useState(queryParams.get("sessionExpired"))
|
||||
|
||||
const [validCaptcha, setValidCaptcha] = useState({ show: false, valid: "" }); // FOR CAPTCHA
|
||||
|
||||
let [loginType, setLoginType] = useState("");
|
||||
|
||||
@@ -96,7 +98,6 @@ export default function Login() {
|
||||
password: password,
|
||||
sessionid: "STARTING",
|
||||
login_mode: 1100,
|
||||
action: 11025,
|
||||
};
|
||||
} else if (name == "family") {
|
||||
// Post Data Info for family Login
|
||||
@@ -105,7 +106,6 @@ export default function Login() {
|
||||
pin: password,
|
||||
sessionid: "20067A92714",
|
||||
login_mode: 1105,
|
||||
action: 11025,
|
||||
};
|
||||
} else {
|
||||
setLoginLoading(false);
|
||||
@@ -115,7 +115,8 @@ 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
|
||||
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(() => {
|
||||
@@ -136,12 +137,15 @@ export default function Login() {
|
||||
// setMsgError("Wrong, email/password");
|
||||
setLoginError(true);
|
||||
setLoginLoading(false);
|
||||
setValidCaptcha(prev => ({...prev, show:true})) // DISPLAYS CAPTCHA IF ERROR
|
||||
setValidCaptcha((prev) => ({ ...prev, show: true })); // DISPLAYS CAPTCHA IF ERROR
|
||||
return;
|
||||
}
|
||||
localStorage.setItem("member_id", `${res.data.member_id}`);
|
||||
localStorage.setItem("uid", `${res.data.uid}`);
|
||||
localStorage.setItem("session_token", `${res.data.session}`);
|
||||
if (name === "family") {
|
||||
sessionStorage.setItem("family_uid", res.data?.family_uid);
|
||||
}
|
||||
// localStorage.setItem("session", `${res.data.session}`);
|
||||
dispatch(updateUserDetails({ ...res.data }));
|
||||
setTimeout(() => {
|
||||
@@ -152,7 +156,7 @@ export default function Login() {
|
||||
.catch((error) => {
|
||||
setMsgError("Unable to login, try again");
|
||||
setLoginLoading(false);
|
||||
setValidCaptcha(prev => ({...prev, show:true})) // DISPLAYS CAPTCHA IF ERROR
|
||||
setValidCaptcha((prev) => ({ ...prev, show: true })); // DISPLAYS CAPTCHA IF ERROR
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
@@ -163,11 +167,12 @@ export default function Login() {
|
||||
});
|
||||
};
|
||||
|
||||
function captchaChecker(value) { // FUNCTION TO VALIDATE CAPTCHA
|
||||
if(value){
|
||||
setValidCaptcha({show: true, valid:value})
|
||||
}else{
|
||||
setValidCaptcha({show: true, valid:''})
|
||||
function captchaChecker(value) {
|
||||
// FUNCTION TO VALIDATE CAPTCHA
|
||||
if (value) {
|
||||
setValidCaptcha({ show: true, valid: value });
|
||||
} else {
|
||||
setValidCaptcha({ show: true, valid: "" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +235,20 @@ export default function Login() {
|
||||
setPassword("");
|
||||
}, [loginType]);
|
||||
|
||||
|
||||
// EFFECT TO CLEAR SESSION EXPIRY IF IT EXISTS AFTER SOME SECONDS
|
||||
useEffect(()=>{
|
||||
let timer;
|
||||
if(sessionExpired == "true"){
|
||||
timer = setTimeout(()=>{
|
||||
setSessionExpired(false)
|
||||
},5000)
|
||||
}
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to WrenchBoard">
|
||||
@@ -245,31 +264,32 @@ export default function Login() {
|
||||
</div>
|
||||
<div className="content-wrapper login shadow-md w-full lg:max-w-[530px] mx-auto flex justify-center items-center xl:bg-white dark:bg-dark-white 2xl:w-[828px] rounded-[0.475rem] sm:p-7 p-5">
|
||||
<div className="w-full">
|
||||
|
||||
{/* HIDES THIS IF USER SESSION HAS EXPIRED */}
|
||||
{sessionExpired != 'true' &&
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
{/* <h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
{sessionExpired != "true" && (
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
{/* <h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
Sign In to WrenchBoard
|
||||
</h1> */}
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
New Here?{" "}
|
||||
<Link
|
||||
to="/signup"
|
||||
className="font-semibold text-[#4687ba] hover:text-[#009ef7] transition"
|
||||
>
|
||||
Create an Account
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
New Here?{" "}
|
||||
<Link
|
||||
to="/signup"
|
||||
className="font-semibold text-[#4687ba] hover:text-[#009ef7] transition"
|
||||
>
|
||||
Create an Account
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SHOWS THIS IF USER SESSION HAS EXPIRED */}
|
||||
{sessionExpired == 'true' &&
|
||||
<div className="w-full p-1 mb-7">
|
||||
<p className="text-red-500 text-base text-center">Your session expired and will need to login again</p>
|
||||
</div>
|
||||
}
|
||||
{sessionExpired == "true" && (
|
||||
<div className="w-full p-1 mb-7">
|
||||
<p className="text-red-500 text-base text-center">
|
||||
Your session expired and will need to login again
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* switch login component */}
|
||||
<div className="ml-7 flex justify-start items-center gap-3">
|
||||
@@ -335,14 +355,16 @@ export default function Login() {
|
||||
</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}
|
||||
{validCaptcha.show && (
|
||||
<div className="mb-5 flex justify-center w-full">
|
||||
<ReCAPTCHA
|
||||
sitekey={
|
||||
process.env.REACT_APP_GOOGLE_RECAPTCHA_SITEKEY
|
||||
}
|
||||
onChange={captchaChecker}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</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]">
|
||||
@@ -499,7 +521,8 @@ export default function Login() {
|
||||
|
||||
{loginType == "full" && (
|
||||
<div className="pt-5 text-[#181c32] text-center font-semibold text-[13.975px] leading-[20.9625px]">
|
||||
This site is protected by a Captcha. Our Privacy Policy and Terms of Service apply.
|
||||
This site is protected by a Captcha. Our Privacy Policy and
|
||||
Terms of Service apply.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,589 @@
|
||||
import React, { useEffect, useLayoutEffect, useState } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import linkedInLogo from "../../../assets/images/Linkedin.png";
|
||||
import appleLogo from "../../../assets/images/apple-black.svg";
|
||||
import facebookLogo from "../../../assets/images/facebook-4.svg";
|
||||
import googleLogo from "../../../assets/images/google-logo.svg";
|
||||
import WrenchBoard from "../../../assets/images/wrenchboard-logo-text.png";
|
||||
import usersService from "../../../services/UsersService";
|
||||
import InputCom from "../../Helpers/Inputs/InputCom";
|
||||
import AuthLayout from "../AuthLayout2";
|
||||
// import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||
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() {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const queryParams = new URLSearchParams(location?.search);
|
||||
// const sessionExpired = queryParams.get("sessionExpired");
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { state } = useLocation();
|
||||
|
||||
const [sessionExpired, setSessionExpired] = useState(queryParams.get("sessionExpired"))
|
||||
|
||||
const [validCaptcha, setValidCaptcha] = useState({ show: false, valid: "" }); // FOR CAPTCHA
|
||||
|
||||
let [loginType, setLoginType] = useState("");
|
||||
|
||||
const [loginLoading, setLoginLoading] = useState(false);
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
//login error state
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
// for the catch error
|
||||
const [msgError, setMsgError] = useState("");
|
||||
|
||||
// To Show and Hide Password
|
||||
const togglePasswordVisibility = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
//FUNCTION TO DETERMINE/CHANGE LOGIN COMPONENT
|
||||
const handleLoginType = ({ target: { name } }) => {
|
||||
setLoginType(name);
|
||||
let currentDate = new Date();
|
||||
let expirationDate = new Date(currentDate.getTime() + 24 * 60 * 60 * 1000);
|
||||
// Convert the expiration date to the appropriate format
|
||||
let expirationDateString = expirationDate.toUTCString();
|
||||
document.cookie = `loginType=${name}; expires=${expirationDateString}; path=/;`;
|
||||
};
|
||||
|
||||
// email
|
||||
const [email, setMail] = useState("");
|
||||
const handleEmail = (e) => {
|
||||
setMail(e.target.value);
|
||||
};
|
||||
// password
|
||||
const [password, setPassword] = useState("");
|
||||
const handlePassword = (e) => {
|
||||
setPassword(e.target.value);
|
||||
};
|
||||
const navigate = useNavigate();
|
||||
const userApi = new usersService();
|
||||
|
||||
// FUNCTION TO HANDLE USER LOGIN
|
||||
const doLogin = ({ target: { name } }) => {
|
||||
setMsgError("");
|
||||
setLoginError(false);
|
||||
setLoginLoading(true);
|
||||
let postData; // Post Data for API
|
||||
if (!email || !password) {
|
||||
setLoginLoading(false);
|
||||
setMsgError("Please fill all the fields");
|
||||
setTimeout(() => {
|
||||
setMsgError("");
|
||||
}, Number(process.env.REACT_APP_LOGIN_ERROR_TIMEOUT));
|
||||
return;
|
||||
}
|
||||
|
||||
if (name == "full") {
|
||||
//checks if email is a valid email address
|
||||
let regEx = /^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/;
|
||||
if (regEx.test(email) == false) {
|
||||
setLoginLoading(false);
|
||||
setMsgError("Invalid Email");
|
||||
return setTimeout(() => {
|
||||
setMsgError("");
|
||||
}, 3000);
|
||||
}
|
||||
// Post Data Info for normal Login
|
||||
postData = {
|
||||
username: email,
|
||||
password: password,
|
||||
sessionid: "STARTING",
|
||||
login_mode: 1100
|
||||
};
|
||||
} else if (name == "family") {
|
||||
// Post Data Info for family Login
|
||||
postData = {
|
||||
username: email,
|
||||
pin: password,
|
||||
sessionid: "20067A92714",
|
||||
login_mode: 1105
|
||||
};
|
||||
} else {
|
||||
setLoginLoading(false);
|
||||
setMsgError("Invalid Login Type. Consider refreshing the page");
|
||||
setTimeout(() => {
|
||||
setMsgError("");
|
||||
}, 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) => {
|
||||
if (
|
||||
res.status != 200 ||
|
||||
res.data.internal_return < 0 ||
|
||||
!res.data.member_id ||
|
||||
!res.data.uid ||
|
||||
!res.data.session
|
||||
) {
|
||||
// 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}`);
|
||||
localStorage.setItem("uid", `${res.data.uid}`);
|
||||
localStorage.setItem("session_token", `${res.data.session}`);
|
||||
if (name === "family") {
|
||||
sessionStorage.setItem("family_uid", res.data?.family_uid);
|
||||
}
|
||||
// localStorage.setItem("session", `${res.data.session}`);
|
||||
dispatch(updateUserDetails({ ...res.data }));
|
||||
setTimeout(() => {
|
||||
navigate("/", { replace: true });
|
||||
setLoginLoading(false);
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
setMsgError("Unable to login, try again");
|
||||
setLoginLoading(false);
|
||||
setValidCaptcha((prev) => ({ ...prev, show: true })); // DISPLAYS CAPTCHA IF ERROR
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
setLoginError(false);
|
||||
setMsgError("");
|
||||
setLoginLoading(false);
|
||||
}, Number(process.env.REACT_APP_LOGIN_ERROR_TIMEOUT));
|
||||
});
|
||||
};
|
||||
|
||||
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",
|
||||
redirect_uri: process.env.REACT_APP_GOOGLE_REDIRECT_URL,
|
||||
onSuccess: async (codeResponse) => {
|
||||
console.log("GOOGLE LOGIN GOOD --- ", codeResponse);
|
||||
},
|
||||
onError: (errorResponse) => console.log(errorResponse),
|
||||
});
|
||||
|
||||
// In order to update the selected login type whenever the component renders
|
||||
// useEffect(() => {
|
||||
// Clear the loginType cookie if the user switches to loginfull
|
||||
// document.cookie ="loginType=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||
// }, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// checks the cookie in order to set the login type before components mounts
|
||||
// if(document.cookie.includes("loginType=family")){
|
||||
// setLoginType('family')
|
||||
// }else if(document.cookie.includes("loginType=full")){
|
||||
// setLoginType('full')
|
||||
// }else{
|
||||
// setLoginType('full')
|
||||
// }
|
||||
function readCookie(cname) {
|
||||
// checks the cookie in order to set the login type before components mounts
|
||||
let name = cname + "=";
|
||||
let decoded_cookie = decodeURIComponent(document.cookie);
|
||||
let carr = decoded_cookie.split(";");
|
||||
for (let i = 0; i < carr.length; i++) {
|
||||
let c = carr[i];
|
||||
while (c.charAt(0) == " ") {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "full";
|
||||
}
|
||||
let loginValue = readCookie("loginType");
|
||||
setLoginType(loginValue);
|
||||
|
||||
if (state?.error) {
|
||||
//check if the login path has an error state indicating any social handle login with error
|
||||
setMsgError("Unexpected Error, Please try again soon.");
|
||||
setTimeout(() => {
|
||||
setMsgError("");
|
||||
navigate("/login", { replace: true });
|
||||
}, 4000);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setMail("");
|
||||
setPassword("");
|
||||
}, [loginType]);
|
||||
|
||||
|
||||
// EFFECT TO CLEAR SESSION EXPIRY IF IT EXISTS AFTER SOME SECONDS
|
||||
useEffect(()=>{
|
||||
let timer;
|
||||
if(sessionExpired == "true"){
|
||||
timer = setTimeout(()=>{
|
||||
setSessionExpired(false)
|
||||
},5000)
|
||||
}
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to WrenchBoard">
|
||||
<div className="w-full">
|
||||
<div className="mb-5">
|
||||
<Link to="#">
|
||||
<img
|
||||
src={WrenchBoard}
|
||||
alt="wrenchboard"
|
||||
className="h-10 mx-auto"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
{/* <div className="content-wrapper login shadow-md w-10/12 mx-auto flex justify-center items-center xl:bg-white dark:bg-dark-white 2xl:w-[828px] rounded-[0.475rem] sm:p-7 p-5"> */}
|
||||
<div className="flex place-content-center">
|
||||
<div className="w-11/12 sm:max-w-[530px]">
|
||||
{/* HIDES THIS IF USER SESSION HAS EXPIRED */}
|
||||
{sessionExpired != "true" && (
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
{/* <h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
Sign In to WrenchBoard
|
||||
</h1> */}
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
New Here?{" "}
|
||||
<Link
|
||||
to="/signup"
|
||||
className="font-semibold text-[#4687ba] hover:text-[#009ef7] transition"
|
||||
>
|
||||
Create an Account
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SHOWS THIS IF USER SESSION HAS EXPIRED */}
|
||||
{sessionExpired == "true" && (
|
||||
<div className="w-full p-1 mb-7">
|
||||
<p className="text-red-500 text-base text-center">
|
||||
Your session expired and will need to login again
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* switch login component */}
|
||||
<div className="ml-7 flex justify-start items-center gap-3">
|
||||
<button
|
||||
name="full"
|
||||
className={`login-type-btn px-4 py-1 rounded-t-2xl ${
|
||||
loginType == "full"
|
||||
? "bg-[#4687ba] border-[2px] border-[#4687ba] text-white"
|
||||
: "bg-white text-[#000] border-t-[2px]"
|
||||
}`}
|
||||
onClick={handleLoginType}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
name="family"
|
||||
className={`login-type-btn px-4 py-1 rounded-t-2xl ${
|
||||
loginType == "family"
|
||||
? "bg-[#4687ba] border-[2px] border-[#4687ba] text-white"
|
||||
: "bg-white text-[#000] border-t-[2px]"
|
||||
}`}
|
||||
onClick={handleLoginType}
|
||||
>
|
||||
Family Account
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* END of switch login component */}
|
||||
|
||||
{/* for login component */}
|
||||
{
|
||||
loginType == "full" ? (
|
||||
//user login component
|
||||
<div className="p-6 input-area login-area border-2 border-[#4687ba] rounded-2xl">
|
||||
<div className="input-item mb-5">
|
||||
<InputCom
|
||||
labelClass="tracking-wider"
|
||||
fieldClass="sm:px-6 px-2"
|
||||
value={email}
|
||||
inputHandler={handleEmail}
|
||||
placeholder="Your Email"
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
iconName="message"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="input-item mb-5">
|
||||
<InputCom
|
||||
labelClass="tracking-wider"
|
||||
fieldClass="sm:px-6 px-2 tracking-[0.25em] text-2xl"
|
||||
value={password}
|
||||
inputHandler={handlePassword}
|
||||
placeholder="● ● ● ● ● ●"
|
||||
label="Password"
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
onClick={togglePasswordVisibility}
|
||||
passIcon={showPassword ? "password" : "password"}
|
||||
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{" "}
|
||||
<Link to="/#" className="text-[#009ef7]">
|
||||
reset your password
|
||||
</Link>{" "}
|
||||
or{" "}
|
||||
<Link to="/signup" className="text-[#009ef7]">
|
||||
create a new account
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{msgError && (
|
||||
<div className="relative p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]">
|
||||
{msgError}
|
||||
</div>
|
||||
)}
|
||||
<div className="signin-area mb-3.5">
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
name="full"
|
||||
onClick={doLogin}
|
||||
type="button"
|
||||
disabled={loginLoading}
|
||||
className={`btn-login rounded-full mb-6 text-xl text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center text-[15px]`}
|
||||
>
|
||||
{loginLoading ? (
|
||||
<div className="signup btn-loader"></div>
|
||||
) : (
|
||||
<>Continue</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="sm:grid grid-cols-2 gap-1">
|
||||
<div className="w-full">
|
||||
<BrandBtn
|
||||
link="#"
|
||||
imgSrc={googleLogo}
|
||||
brand="Google"
|
||||
onClick={googleLogin}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`w-full ${
|
||||
process.env.REACT_APP_APPLE_SOCIAL_LOGIN !== 0 &&
|
||||
"hidden"
|
||||
}`}
|
||||
>
|
||||
<BrandBtn
|
||||
// link={`https://appleid.apple.com/auth/authorize?response_type=code&response_mode=form_post&client_id=${process.env.REACT_APP_APPLE_CLIENT_ID}&redirect_uri=https%3A%2F%2Fwork.wrenchboard.com%2Flogin%2Fauth%2Fapple&state=4b2c4456b7&scope=name+email`}
|
||||
link={`https://appleid.apple.com/auth/authorize?response_type=code&response_mode=form_post&client_id=${process.env.REACT_APP_APPLE_CLIENT_ID}&redirect_uri=${process.env.REACT_APP_APPLE_REDIRECT_URL}&state=4b2c4456b7&scope=name+email`}
|
||||
imgSrc={appleLogo}
|
||||
brand="Apple"
|
||||
isAnchor={true}
|
||||
// style={{visibility: 'hidden'}}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<BrandBtn
|
||||
link={`https://www.facebook.com/v14.0/dialog/oauth?client_id=${process.env.REACT_APP_FACEBOOK_CLIENT_ID}&redirect_uri=${process.env.REACT_APP_FACEBOOK_REDIRECT_URL}&scope=${process.env.REACT_APP_FACEBOOK_CLIENT_SCOPE}`}
|
||||
imgSrc={facebookLogo}
|
||||
brand="Facebook"
|
||||
isAnchor={true}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`w-full ${
|
||||
process.env.REACT_APP_LINKEDIN_SOCIAL_LOGIN !== 0 &&
|
||||
"hidden"
|
||||
}`}
|
||||
>
|
||||
<BrandBtn
|
||||
// link="https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&scope=comma-separated-list-of-scopes&state=YOUR_STATE_VALUE"
|
||||
imgSrc={linkedInLogo}
|
||||
brand="LinkedIn"
|
||||
isAnchor={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// END of user login compoenent
|
||||
// family login compoenent
|
||||
<div className="p-6 input-area login-area border-2 border-[#4687ba] rounded-2xl">
|
||||
<div className="input-item mb-5">
|
||||
<InputCom
|
||||
labelClass="tracking-wider"
|
||||
fieldClass="px-6"
|
||||
value={email}
|
||||
inputHandler={handleEmail}
|
||||
placeholder="Account ID"
|
||||
label="Username"
|
||||
name="email"
|
||||
type="email"
|
||||
iconName="family-id"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="input-item mb-5">
|
||||
<InputCom
|
||||
labelClass="tracking-wider"
|
||||
fieldClass="px-6"
|
||||
value={password}
|
||||
inputHandler={handlePassword}
|
||||
placeholder="● ● ● ● ● ●"
|
||||
label="Pin"
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
onClick={togglePasswordVisibility}
|
||||
passIcon={showPassword ? "family-pin" : "family-pin"}
|
||||
/>
|
||||
</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{" "}
|
||||
{/* <Link to="/#" className="text-[#009ef7]">
|
||||
reset your password
|
||||
</Link>{" "}
|
||||
or{" "}
|
||||
<Link to="/signup" className="text-[#009ef7]">
|
||||
create a new account
|
||||
</Link> */}
|
||||
</div>
|
||||
)}
|
||||
{msgError && (
|
||||
<div className="relative p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]">
|
||||
{msgError}
|
||||
</div>
|
||||
)}
|
||||
<div className="signin-area mb-1.5">
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
name="family"
|
||||
onClick={doLogin}
|
||||
disabled={loginLoading}
|
||||
type="button"
|
||||
className={`btn-login rounded-full text-xl text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center text-[15px]`}
|
||||
>
|
||||
{loginLoading ? (
|
||||
<div className="signup btn-loader"></div>
|
||||
) : (
|
||||
<>Continue</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
// END of family login compoenent
|
||||
}
|
||||
{/* END of login component */}
|
||||
|
||||
{loginType == "full" && (
|
||||
<div className="pt-5 text-[#181c32] text-center font-semibold text-[13.975px] leading-[20.9625px]">
|
||||
This site is protected by a Captcha. Our Privacy Policy and
|
||||
Terms of Service apply.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const BrandBtn = ({
|
||||
link,
|
||||
imgSrc,
|
||||
brand,
|
||||
onClick,
|
||||
isAnchor = false,
|
||||
style = { visibility: "visible" },
|
||||
}) => {
|
||||
// const doGoogle = async () => {
|
||||
// alert("start google");
|
||||
// };
|
||||
|
||||
// onSuccess: (codeResponse) => setUser(codeResponse),
|
||||
|
||||
// const doGoogle = useGoogleLogin({
|
||||
// onSuccess: (codeResponse) => console.log('Login onSuccess:', codeResponse),
|
||||
// onError: (error) => console.log('Login Failed:', error)
|
||||
// });
|
||||
|
||||
// const doApple = async () => {
|
||||
// alert("start apple");
|
||||
// };
|
||||
|
||||
// const doFacebook = async () => {
|
||||
// alert("start facebook");
|
||||
// };
|
||||
return (
|
||||
<div className="w-full flex justify-center bottomMargin" style={style}>
|
||||
{isAnchor ? (
|
||||
<a
|
||||
href={link}
|
||||
className="w-full border border-light-purple dark:border-[#5356fb29] rounded-[0.475rem] h-[48px] flex justify-center bg-[#FAFAFA] hover:bg-[#eff2f5] hover:text-[#7e8299] transition duration-300 dark:bg-[#11131F] items-center font-medium cursor-pointer"
|
||||
>
|
||||
<img className="mr-3 h-6" src={imgSrc} alt="logo-icon(s)" />
|
||||
<span className="text-lg text-thin-light-gray font-normal text-[15px]">
|
||||
Continue with {brand}
|
||||
</span>
|
||||
</a>
|
||||
) : (
|
||||
<button
|
||||
onClick={onClick}
|
||||
// href="#dd"
|
||||
className="w-full border border-light-purple dark:border-[#5356fb29] rounded-[0.475rem] h-[48px] flex justify-center bg-[#FAFAFA] hover:bg-[#eff2f5] hover:text-[#7e8299] transition duration-300 dark:bg-[#11131F] items-center font-medium cursor-pointer"
|
||||
>
|
||||
<img className="mr-3 h-6" src={imgSrc} alt="logo-icon(s)" />
|
||||
<span className="text-lg text-thin-light-gray font-normal text-[15px]">
|
||||
Continue with {brand}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -47,21 +47,20 @@ export default function SignUp() {
|
||||
|
||||
// Get Country Api
|
||||
const getCountryList = useCallback(async () => {
|
||||
const res = await userApi.getSignupCountryData();
|
||||
|
||||
|
||||
try {
|
||||
if (res.status === 200) {
|
||||
const { signup_country } = await res.data;
|
||||
// setCountries(signup_country);
|
||||
const res = await userApi.getSignupCountryData();
|
||||
if (res.status === 200 && res.data.internal_return >= 0) {
|
||||
const { result_list } = await res.data;
|
||||
if(country){ // IF LINK/PATHNAME HAS CNT QUERY VALUE
|
||||
let cnt = signup_country.filter(item => item[0]==country) // test to see country passed in query param exist from list of countries supplied by API
|
||||
let cnt = result_list.filter(item => item.code == country) // test to see country passed in query param exist from list of countries supplied by API
|
||||
if(!cnt.length){ // IF CNT EMPTY, SET FORMDATA COUNTRY BACK TO EMPTY STRING: RE: THIS IS BCOS WE INITAIL SET COUNTRY VALUE IN FORMDATA, IF COUNTRY PARAM IS PRESENT IN LINK
|
||||
setFormData(prev => ({...prev, country: ''}))
|
||||
return setCountries({loading: false, data: signup_country});
|
||||
return setCountries({loading: false, data: result_list});
|
||||
}
|
||||
return setCountries({loading: false, data: cnt});
|
||||
}
|
||||
setCountries({loading: false, data:signup_country});
|
||||
setCountries({loading: false, data:result_list});
|
||||
} else if (res.data.result !== 100) {
|
||||
setCountries({loading: false, data:[]});
|
||||
}
|
||||
@@ -375,17 +374,17 @@ const SelectOption = ({
|
||||
{data?.data?.length > 1 ?
|
||||
<>
|
||||
<option value={""}>Select your Country</option>
|
||||
{data?.data?.map((item, idx) => (
|
||||
<option value={item[0]} key={idx}>
|
||||
{item[1]}
|
||||
{data?.data?.map((item) => (
|
||||
<option value={item.code} key={item.uid}>
|
||||
{item.country}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
:
|
||||
data?.data?.length == 1 ?
|
||||
data?.data?.map((item, idx) => (
|
||||
<option value={item[0]} key={idx}>
|
||||
{item[1]}
|
||||
data?.data?.map((item) => (
|
||||
<option value={item.code} key={item.uid}>
|
||||
{item.country}
|
||||
</option>
|
||||
))
|
||||
:
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import WrenchBoard from "../../../assets/images/wrenchboard-logo-text.png";
|
||||
import usersService from "../../../services/UsersService";
|
||||
import InputCom from "../../Helpers/Inputs/InputCom";
|
||||
import AuthLayout from "../AuthLayout2";
|
||||
|
||||
export default function SignUp() {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const queryParams = new URLSearchParams(location?.search);
|
||||
const country = queryParams.get("cnt")?.toUpperCase();
|
||||
|
||||
const {pathname} = useLocation()
|
||||
const currentPath = country ? `${pathname}?cnt=${country.toLowerCase()}`:pathname // Determines the new pathname is country query params exist
|
||||
|
||||
const [signUpLoading, setSignUpLoading] = useState(false);
|
||||
const [checked, setValue] = useState(false);
|
||||
// for the catch error
|
||||
const [msgError, setMsgError] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [countries, setCountries] = useState({loading:true, data:[]});
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
country: country? country : "",
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value } = event?.target;
|
||||
setFormData({ ...formData, [name]: value });
|
||||
};
|
||||
|
||||
// To Show and Hide Password
|
||||
const togglePasswordVisibility = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
const rememberMe = () => {
|
||||
setValue(!checked);
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
const userApi = new usersService();
|
||||
|
||||
// Get Country Api
|
||||
const getCountryList = useCallback(async () => {
|
||||
|
||||
try {
|
||||
const res = await userApi.getSignupCountryData();
|
||||
if (res.status === 200 && res.data.internal_return >= 0) {
|
||||
const { result_list } = await res.data;
|
||||
if(country){ // IF LINK/PATHNAME HAS CNT QUERY VALUE
|
||||
let cnt = result_list.filter(item => item.code == country) // test to see country passed in query param exist from list of countries supplied by API
|
||||
if(!cnt.length){ // IF CNT EMPTY, SET FORMDATA COUNTRY BACK TO EMPTY STRING: RE: THIS IS BCOS WE INITAIL SET COUNTRY VALUE IN FORMDATA, IF COUNTRY PARAM IS PRESENT IN LINK
|
||||
setFormData(prev => ({...prev, country: ''}))
|
||||
return setCountries({loading: false, data: result_list});
|
||||
}
|
||||
return setCountries({loading: false, data: cnt});
|
||||
}
|
||||
setCountries({loading: false, data:result_list});
|
||||
} else if (res.data.result !== 100) {
|
||||
setCountries({loading: false, data:[]});
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSignUp = async () => {
|
||||
let { country, first_name, last_name, email, password } = formData;
|
||||
try {
|
||||
if (
|
||||
email !== "" &&
|
||||
password !== "" &&
|
||||
first_name !== "" &&
|
||||
last_name !== "" &&
|
||||
country !== ""
|
||||
) {
|
||||
//checks if email is a valid email address
|
||||
let regEx = /^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/;
|
||||
if (regEx.test(email) == false) {
|
||||
setMsgError("Invalid Email");
|
||||
return setTimeout(() => {
|
||||
setMsgError("");
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
//checks if terms and condition is checked
|
||||
if (!checked) {
|
||||
setMsgError("Terms and condition required");
|
||||
return setTimeout(() => {
|
||||
setMsgError("");
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
setSignUpLoading(true);
|
||||
const reqData = {
|
||||
country: country,
|
||||
firstname: first_name,
|
||||
lastname: last_name,
|
||||
email: email,
|
||||
username: email,
|
||||
password: password,
|
||||
terms: 1,
|
||||
news: 1,
|
||||
};
|
||||
|
||||
const res = await userApi.CreateUser(reqData);
|
||||
|
||||
if (res.status === 200) {
|
||||
const { data } = res;
|
||||
if (data && data.acc === "DULPICATE") {
|
||||
setMsgError(
|
||||
"Unable to use this username. Please try another username."
|
||||
);
|
||||
setSignUpLoading(false);
|
||||
}
|
||||
if (data && data.status === "1") {
|
||||
setTimeout(() => {
|
||||
navigate("/outmessage", { replace: true });
|
||||
setSignUpLoading(false);
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
setSignUpLoading(false);
|
||||
setMsgError("An error occurred");
|
||||
}
|
||||
} else {
|
||||
setMsgError("Please fill in fields");
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_SIGNUP_ERROR_TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getCountryList();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to WrenchBoard">
|
||||
<div className="w-full">
|
||||
<div className="mb-5">
|
||||
<Link to={currentPath}>
|
||||
<img
|
||||
src={WrenchBoard}
|
||||
alt="wrenchboard"
|
||||
className="h-10 mx-auto"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex place-content-center">
|
||||
<div className="w-11/12 sm:max-w-[530px]">
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
Create Account
|
||||
</h1>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
Already have an account?{" "}
|
||||
<Link
|
||||
to="/login"
|
||||
className="font-semibold text-[#4687ba] hover:text-[#009ef7] transition"
|
||||
>
|
||||
Sign in here
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full flex items-center gap-2">
|
||||
<div className="border-b border-[#eff2f5] max-w-[50%] w-full"></div>
|
||||
<span className="text-[#b5b5c3] font-medium text-[0.7rem] w-[2%]">
|
||||
OR
|
||||
</span>
|
||||
<div className="border-b border-[#eff2f5] max-w-[50%] w-full"></div>
|
||||
</div>
|
||||
<div className="input-area">
|
||||
<SelectOption
|
||||
label="Country"
|
||||
data={countries}
|
||||
name="country"
|
||||
value={formData.country}
|
||||
inputHandler={handleInputChange}
|
||||
disable={country && countries?.data?.length <= 1 ? true : false}
|
||||
/>
|
||||
<div className="input-fl-name mb-4 sm:flex w-full sm:space-x-6 ">
|
||||
<div className="input-item sm:w-1/2 w-full mb-4 sm:mb-0">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
placeholder="Firstname"
|
||||
label="First Name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
value={formData.first_name}
|
||||
inputHandler={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-item flex-1">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
placeholder="Lastname"
|
||||
label="Last Name"
|
||||
name="last_name"
|
||||
type="text"
|
||||
value={formData.last_name}
|
||||
inputHandler={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="input-item mb-4">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
placeholder="support@mermsemr.com"
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
inputHandler={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-item mb-4">
|
||||
<InputCom
|
||||
fieldClass="sm:px-6 px-2 tracking-[0.25em] text-2xl"
|
||||
placeholder="● ● ● ● ● ●"
|
||||
label="Password"
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
onClick={togglePasswordVisibility}
|
||||
passIcon={showPassword ? "show-password" : "hide-password"}
|
||||
value={formData.password}
|
||||
inputHandler={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
{msgError && (
|
||||
<div className="p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px]">
|
||||
{msgError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="forgot-password-area flex justify-between items-center mb-6">
|
||||
<div className="remember-checkbox flex items-center space-x-2.5 group cursor-pointer">
|
||||
<button
|
||||
onClick={rememberMe}
|
||||
type="button"
|
||||
className={`w-6 h-6 border-[#4687ba] text-white flex justify-center items-center border rounded-[.45em] group-checked:text-white transition-all duration-200 group-checked:cursor-default ${
|
||||
checked && "text-white bg-[#4687ba]"
|
||||
}`}
|
||||
>
|
||||
{checked && (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<span
|
||||
onClick={rememberMe}
|
||||
className="cursor-default text-dark-gray dark:text-white text-[15px] group-checked:text-white transition-all duration-200 group-checked:cursor-default"
|
||||
>
|
||||
I agree with all
|
||||
<Link
|
||||
href="#"
|
||||
className="text-base text-[#4687ba] hover:text-[#009ef7] mx-1 inline-block"
|
||||
>
|
||||
terms and condition
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Forgot Password */}
|
||||
{/* <div className="forgot-password-area flex justify-between items-center mb-6">
|
||||
<div className="remember-checkbox flex items-center space-x-2.5">
|
||||
<button
|
||||
onClick={rememberMe}
|
||||
type="button"
|
||||
className="w-6 h-6 bg-[#4687ba] text-white flex justify-center items-center border border-light-gray rounded-[.45em]"
|
||||
>
|
||||
{checked && (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<span
|
||||
onClick={rememberMe}
|
||||
className="cursor-default text-dark-gray dark:text-white text-[15px]"
|
||||
>
|
||||
I agree with all
|
||||
<Link
|
||||
href="#"
|
||||
className="text-base text-[#4687ba] hover:text-[#009ef7] mx-1 inline-block"
|
||||
>
|
||||
terms and condition
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="signin-area mb-1">
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
disabled={countries.loading}
|
||||
type="button"
|
||||
onClick={handleSignUp}
|
||||
className={`rounded-full mb-6 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`}
|
||||
>
|
||||
{signUpLoading ? (
|
||||
<div className="signup btn-loader"></div>
|
||||
) : (
|
||||
<span>Sign Up</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const SelectOption = ({
|
||||
label,
|
||||
name,
|
||||
inputHandler,
|
||||
value,
|
||||
data, // passing the data from parent
|
||||
disable
|
||||
}) => {
|
||||
return (
|
||||
<div className="input-com mb-7">
|
||||
<div className="flex items-center justify-between">
|
||||
<label
|
||||
className="input-label text-[#181c32] dark:text-white text-[15px] font-semibold block mb-2.5"
|
||||
htmlFor={name}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
disabled={disable}
|
||||
name={name}
|
||||
id={name}
|
||||
className="input-wrapper border border-[#f5f8fa] dark:border-[#5e6278] w-full rounded-full h-[42px] 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 "
|
||||
onChange={inputHandler}
|
||||
value={value}
|
||||
>
|
||||
{data?.data?.length > 1 ?
|
||||
<>
|
||||
<option value={""}>Select your Country</option>
|
||||
{data?.data?.map((item) => (
|
||||
<option value={item.code} key={item.uid}>
|
||||
{item.country}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
:
|
||||
data?.data?.length == 1 ?
|
||||
data?.data?.map((item) => (
|
||||
<option value={item.code} key={item.uid}>
|
||||
{item.country}
|
||||
</option>
|
||||
))
|
||||
:
|
||||
data?.data?.length < 1 && data.loading ?
|
||||
<option value=''>
|
||||
Loading...
|
||||
</option>
|
||||
:
|
||||
<option value=''>
|
||||
No Country Found!
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import React, { useState } from "react";
|
||||
import titleShape from "../../../assets/images/shape/title-shape-two.svg";
|
||||
import InputCom from "../../Helpers/Inputs/InputCom";
|
||||
import AuthLayout from "../AuthLayout2";
|
||||
import ThankYou from "../ThankYou";
|
||||
|
||||
export default function UpdatePassword() {
|
||||
const [updated, setValue] = useState(false);
|
||||
const [message, setMessage] = useState(false);
|
||||
const updatePassword = () => {
|
||||
setValue(!updated);
|
||||
setTimeout(() => {
|
||||
setMessage(!message);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to myFit">
|
||||
{updated === false ? (
|
||||
<div className="flex place-content-center">
|
||||
<div className="w-11/12 sm:max-w-[600px]">
|
||||
<div className="title-area relative flex flex-col justify-center items-center mb-7">
|
||||
<h1 className="sm:text-5xl text-4xl font-bold leading-[74px] text-dark-gray dark:text-white">
|
||||
Update Password
|
||||
</h1>
|
||||
{/* w-[341px] absolute top-14 left-12 */}
|
||||
<div className="shape sm:w-[341px] w-[270px] -mt-5 sm:-mt-1 ml-5">
|
||||
<img src={titleShape} alt="shape" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="input-area">
|
||||
<div className="input-item mb-5">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
placeholder="*********"
|
||||
label="Old Password"
|
||||
name="password"
|
||||
type="password"
|
||||
iconName="password"
|
||||
/>
|
||||
</div>
|
||||
<div className="input-item mb-5">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
placeholder="*********"
|
||||
label="New Password"
|
||||
name="password"
|
||||
type="password"
|
||||
iconName="password"
|
||||
/>
|
||||
</div>
|
||||
<div className="input-item mb-5">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
placeholder="*********"
|
||||
label="Re-enter Password"
|
||||
name="password"
|
||||
type="password"
|
||||
iconName="password"
|
||||
/>
|
||||
</div>
|
||||
<div className="signin-area mb-3.5">
|
||||
<button
|
||||
onClick={updatePassword}
|
||||
type="button"
|
||||
className="w-full rounded-[50px] mb-5 h-[58px] text-xl text-white font-bold flex justify-center bg-purple items-center"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ThankYou className={`thankyou-section ${message ? "active" : ""}`} />
|
||||
)}
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -36,10 +36,7 @@ export default function VerifyLink() {
|
||||
var postData = {
|
||||
username: email,
|
||||
password: password,
|
||||
login_mode: 100,
|
||||
sessionid: "STARTER-NOTREAL",
|
||||
verify_link: token,
|
||||
action: 11012,
|
||||
};
|
||||
const res = await userApi?.CompleteSignUp(postData);
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import WrenchBoard from "../../../assets/images/wrenchboard-logo-text.png";
|
||||
import debounce from "../../../hooks/debounce";
|
||||
import usersService from "../../../services/UsersService";
|
||||
import InputCom from "../../Helpers/Inputs/InputCom";
|
||||
import AuthLayout from "../AuthLayout2";
|
||||
|
||||
export default function VerifyLink() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [msgError, setMsgError] = useState("");
|
||||
const [linkLoader, setLinkLoader] = useState(false);
|
||||
const [pageLoader, setPageLoader] = useState(true);
|
||||
const [linkSuccess, setLinkSuccess] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location?.search);
|
||||
const token = queryParams.get("vlnk");
|
||||
const userApi = new usersService();
|
||||
|
||||
// email
|
||||
const handleEmail = (e) => {
|
||||
setEmail(e.target.value);
|
||||
};
|
||||
// password
|
||||
const handlePassword = (e) => {
|
||||
setPassword(e.target.value);
|
||||
};
|
||||
|
||||
// if verification is okay. set a complete signup form
|
||||
const completeSignup = async () => {
|
||||
try {
|
||||
if (email !== "" && password !== "") {
|
||||
setLinkLoader(true);
|
||||
var postData = {
|
||||
username: email,
|
||||
password: password,
|
||||
verify_link: token,
|
||||
};
|
||||
const res = await userApi?.CompleteSignUp(postData);
|
||||
|
||||
if (res.status === 200) {
|
||||
const { data } = res;
|
||||
if (
|
||||
data?.status > 0 &&
|
||||
data?.internal_return == 100 &&
|
||||
data?.session != ""
|
||||
) {
|
||||
localStorage.setItem("email", `${data?.email}`);
|
||||
localStorage.setItem("member_id", `${data?.member_id}`);
|
||||
localStorage.setItem("session_token", `${data?.session}`);
|
||||
localStorage.setItem("session", `${data?.session}`);
|
||||
localStorage.setItem("uid", data?.uid)
|
||||
|
||||
|
||||
navigate("/", { replace: true });
|
||||
setLinkLoader(false);
|
||||
} else {
|
||||
setLinkLoader(false);
|
||||
setMsgError("Invalid Link or Password Combination");
|
||||
}
|
||||
} else {
|
||||
setLinkLoader(false);
|
||||
setLinkSuccess(false);
|
||||
setMsgError("An error occurred");
|
||||
}
|
||||
} else {
|
||||
setMsgError("Please fill in fields");
|
||||
}
|
||||
} catch (error) {
|
||||
setLinkLoader(false);
|
||||
setLinkSuccess(false);
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_SIGNUP_ERROR_TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
// for verifying the incoming verification link and render the correct component
|
||||
const verifyEmail = useCallback(async (code) => {
|
||||
try {
|
||||
const verifyRes = await userApi.verifyEmail(code);
|
||||
if (verifyRes.status === 200) {
|
||||
let { data } = verifyRes;
|
||||
console.log('TESTING VERIFY',data)
|
||||
if (
|
||||
data &&
|
||||
data.internal_return >= 0 &&
|
||||
data.status == 0 &&
|
||||
data.pending_id != '' &&
|
||||
data.pending_uid != '' &&
|
||||
data.username != '' &&
|
||||
data.status_text === "Link Verified"
|
||||
) {
|
||||
setPageLoader(false);
|
||||
} else {
|
||||
setPageLoader(false);
|
||||
setLinkSuccess(false);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setPageLoader(false);
|
||||
setLinkSuccess(false);
|
||||
throw new Error(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// delay verify requests by 10000ms
|
||||
const debouncedEmail = debounce(verifyEmail, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
debouncedEmail(token);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to WrenchBoard">
|
||||
{pageLoader ? (
|
||||
<img src={WrenchBoard} alt="wrenchboard" className="h-10 mx-auto" />
|
||||
) : (
|
||||
<div className="w-full">
|
||||
<div className="mb-12">
|
||||
<Link to="#">
|
||||
<img
|
||||
src={WrenchBoard}
|
||||
alt="wrenchboard"
|
||||
className="h-10 mx-auto"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex place-content-center">
|
||||
<div className="w-11/12 sm:max-w-[500px]">
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
{linkSuccess
|
||||
? "Sign In to WrenchBoard"
|
||||
: "Invalid verification link"}
|
||||
</h1>
|
||||
</div>
|
||||
{/* If the verification was a success */}
|
||||
{linkSuccess ? (
|
||||
<SuccessfulComponent
|
||||
email={email}
|
||||
password={password}
|
||||
handleEmail={handleEmail}
|
||||
handlePassword={handlePassword}
|
||||
onSubmit={completeSignup}
|
||||
msgErr={msgError}
|
||||
loader={linkLoader}
|
||||
/>
|
||||
) : (
|
||||
<ErrorComponent onClick={() => navigate("/login")} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const SuccessfulComponent = ({
|
||||
onSubmit,
|
||||
password,
|
||||
handlePassword,
|
||||
email,
|
||||
handleEmail,
|
||||
msgErr,
|
||||
loader,
|
||||
}) => (
|
||||
<div className="input-area">
|
||||
{/* INPUT */}
|
||||
<div className="mb-5">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
value={email}
|
||||
inputHandler={handleEmail}
|
||||
placeholder="support@mermsemr.com"
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
iconName="message"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
value={password}
|
||||
inputHandler={handlePassword}
|
||||
placeholder="● ● ● ● ● ●"
|
||||
label="Password"
|
||||
name="password"
|
||||
type="password"
|
||||
iconName="password"
|
||||
/>
|
||||
</div>
|
||||
{msgErr && (
|
||||
<div className="relative p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]">
|
||||
{msgErr}
|
||||
</div>
|
||||
)}
|
||||
<div className="signin-area mb-3.5">
|
||||
<button
|
||||
onClick={onSubmit}
|
||||
type="button"
|
||||
className={`btn-login rounded-[0.475rem] mb-6 text-xl text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center text-[15px]`}
|
||||
>
|
||||
{loader ? (
|
||||
<div className="signup btn-loader"></div>
|
||||
) : (
|
||||
<span>Continue</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ErrorComponent = ({ onClick }) => (
|
||||
<div className="input-area">
|
||||
<div className="my-5">
|
||||
<p className="text-[14px] leading-[19px] text-center text-[#181c32]">
|
||||
This error occurs because you have already verified this link or the
|
||||
link has expired. Try login or reset password. If none worked, try to
|
||||
create the account from the start.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="signin-area flex justify-center mb-3.5">
|
||||
<button
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
className={`rounded-[0.475rem] mb-6 text-[15px] font-semibold text-[#009ef7] hover:text-white flex justify-center bg-[#f1faff] hover:bg-[#009ef7] transition-all duration-300 items-center py-[0.8875rem] px-[1.81rem]`}
|
||||
>
|
||||
<span>Return Home</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -11,7 +11,11 @@ import LoadingSpinner from "../../Spinners/LoadingSpinner";
|
||||
const VerifyPassword = () => {
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [requestStatus, setRequestStatus] = useState({loading: true, status:false, data: []})
|
||||
const [requestStatus, setRequestStatus] = useState({
|
||||
loading: true,
|
||||
status: false,
|
||||
data: [],
|
||||
});
|
||||
const [msgError, setMsgError] = useState("");
|
||||
const [linkLoader, setLinkLoader] = useState(false);
|
||||
const [linkSuccess, setLinkSuccess] = useState(null);
|
||||
@@ -35,60 +39,64 @@ const VerifyPassword = () => {
|
||||
};
|
||||
|
||||
const completeReset = async () => {
|
||||
if(!password || !confirmPassword){ // CHECKS IF PASSWORD IS EMPTY
|
||||
if (!password || !confirmPassword) {
|
||||
// CHECKS IF PASSWORD IS EMPTY
|
||||
setMsgError("Please fill in fields");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
if(password != confirmPassword){ // CHECKS IF PASSWORD EQUALS CONFIRM PASSWORD
|
||||
if (password != confirmPassword) {
|
||||
// CHECKS IF PASSWORD EQUALS CONFIRM PASSWORD
|
||||
setMsgError("Passwords does not match");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
if(password.length < 6){ // CHECKS IF PASSWORD LENGTH IS UPTO 6 CHARACTERS
|
||||
if (password.length < 6) {
|
||||
// CHECKS IF PASSWORD LENGTH IS UPTO 6 CHARACTERS
|
||||
setMsgError("Password must be upto six characters");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
if(!PasswordValidator(password)){ // CHECKS IF PASSWORD IS VALID
|
||||
setMsgError("Password must contain alphanumeric, uppercase and special character: eg: Password1@");
|
||||
if (!PasswordValidator(password)) {
|
||||
// CHECKS IF PASSWORD IS VALID
|
||||
setMsgError(
|
||||
"Password must contain alphanumeric, uppercase and special character: eg: Password1@"
|
||||
);
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
try {
|
||||
setLinkLoader(true);
|
||||
var reqData = {
|
||||
sessionid: "DUMMY-CANNOT_BE_EMPTY",
|
||||
reset_link: token,
|
||||
newpass: password,
|
||||
m_uid: requestStatus.data?.m_uid || '',
|
||||
reset_uid: requestStatus.data?.reset_uid || '',
|
||||
step: 300,
|
||||
action: 730,
|
||||
};
|
||||
const res = await userApi?.CompleteResetPassword(reqData);
|
||||
setLinkLoader(true);
|
||||
var reqData = {
|
||||
reset_link: token,
|
||||
newpass: password,
|
||||
m_uid: requestStatus.data?.m_uid || "",
|
||||
reset_uid: requestStatus.data?.reset_uid || "",
|
||||
step: 300,
|
||||
};
|
||||
const res = await userApi?.CompleteResetPassword(reqData);
|
||||
|
||||
if (res.status == 200) {
|
||||
const { data } = res;
|
||||
if (data?.internal_return >= 0) {
|
||||
// setTimeout(() => {
|
||||
// navigate("/login", { replace: true });
|
||||
// setLinkLoader(false);
|
||||
// }, 2000);
|
||||
setLinkSuccess(true);
|
||||
} else {
|
||||
setLinkLoader(false);
|
||||
setMsgError("An error occurred");
|
||||
setLinkSuccess(false);
|
||||
}
|
||||
} else {
|
||||
setLinkLoader(false);
|
||||
setLinkSuccess(false);
|
||||
}
|
||||
if (res.status == 200) {
|
||||
const { data } = res;
|
||||
if (data?.internal_return >= 0) {
|
||||
// setTimeout(() => {
|
||||
// navigate("/login", { replace: true });
|
||||
// setLinkLoader(false);
|
||||
// }, 2000);
|
||||
setLinkSuccess(true);
|
||||
} else {
|
||||
setLinkLoader(false);
|
||||
setMsgError("An error occurred");
|
||||
setLinkSuccess(false);
|
||||
}
|
||||
} else {
|
||||
setLinkLoader(false);
|
||||
setLinkSuccess(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setLinkLoader(false);
|
||||
setLinkSuccess(false);
|
||||
@@ -100,31 +108,33 @@ const VerifyPassword = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const verifyResetPwd = () => { // FUNCTION TO VERIFY RESET PASSWORD LINK
|
||||
setRequestStatus({loading: true, status:false, data: []})
|
||||
const verifyResetPwd = () => {
|
||||
// FUNCTION TO VERIFY RESET PASSWORD LINK
|
||||
setRequestStatus({ loading: true, status: false, data: [] });
|
||||
var reqData = {
|
||||
sessionid: "DUMMY-CANNOT_BE_EMPTY",
|
||||
reset_link: token,
|
||||
step: 200,
|
||||
action: 730,
|
||||
};
|
||||
userApi.CompleteResetPassword(reqData).then(res => {
|
||||
if(res.status != 200 || res.data.internal_return < 0){
|
||||
return setRequestStatus({loading: false, status:false, data: []})
|
||||
}
|
||||
setRequestStatus({loading: false, status:true, data: res.data})
|
||||
}).catch(error => {
|
||||
setRequestStatus({loading: false, status:false, data: []})
|
||||
})
|
||||
}
|
||||
userApi
|
||||
.CompleteResetPassword(reqData)
|
||||
.then((res) => {
|
||||
if (res.status != 200 || res.data.internal_return < 0) {
|
||||
return setRequestStatus({ loading: false, status: false, data: [] });
|
||||
}
|
||||
setRequestStatus({ loading: false, status: true, data: res.data });
|
||||
})
|
||||
.catch((error) => {
|
||||
setRequestStatus({ loading: false, status: false, data: [] });
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
// little checker for the validity of the token
|
||||
if (token==null || token?.length != 64) {
|
||||
return setRequestStatus({loading: false, status:false, data: []});
|
||||
}
|
||||
verifyResetPwd()
|
||||
},[])
|
||||
if (token == null || token?.length != 64) {
|
||||
return setRequestStatus({ loading: false, status: false, data: [] });
|
||||
}
|
||||
verifyResetPwd();
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to WrenchBoard">
|
||||
@@ -139,59 +149,68 @@ const VerifyPassword = () => {
|
||||
</Link>
|
||||
</div>
|
||||
<div className="content-wrapper login shadow-md w-full lg:max-w-[500px] mx-auto flex justify-center items-center dark:bg-dark-white 2xl:w-[828px] rounded-[0.475rem] sm:p-7 p-5">
|
||||
{requestStatus.loading ?
|
||||
<LoadingSpinner color='sky-blue' size='16' height='h-300px' />
|
||||
:
|
||||
!requestStatus.loading && requestStatus.status ?
|
||||
<div className="w-full">
|
||||
{linkSuccess == null ?
|
||||
<>
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
Password Reset
|
||||
</h1>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
Enter a new password to reset
|
||||
</span>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
We'll send an email to confirm reset
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-red-500 font-semibold mb-3 leading-[27.3px] text-[13px]">
|
||||
Must include a special, numeric, uppercase and lowercase character
|
||||
</p>
|
||||
</div>
|
||||
<SuccessfulComponent
|
||||
password={password}
|
||||
confirmPassword={confirmPassword}
|
||||
handlePassword={handlePassword}
|
||||
onSubmit={completeReset}
|
||||
msgErr={msgError}
|
||||
loader={linkLoader}
|
||||
showPassword={showPassword}
|
||||
onClick={togglePasswordVisibility}
|
||||
navigateHandler={() => navigate("/login")}
|
||||
{requestStatus.loading ? (
|
||||
<LoadingSpinner color="sky-blue" size="16" height="h-300px" />
|
||||
) : !requestStatus.loading && requestStatus.status ? (
|
||||
<div className="w-full">
|
||||
{linkSuccess == null ? (
|
||||
<>
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
Password Reset
|
||||
</h1>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
Enter a new password to reset
|
||||
</span>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
We'll send an email to confirm reset
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-red-500 font-semibold mb-3 leading-[27.3px] text-[13px]">
|
||||
Must include a special, numeric, uppercase and lowercase
|
||||
character
|
||||
</p>
|
||||
</div>
|
||||
<SuccessfulComponent
|
||||
password={password}
|
||||
confirmPassword={confirmPassword}
|
||||
handlePassword={handlePassword}
|
||||
onSubmit={completeReset}
|
||||
msgErr={msgError}
|
||||
loader={linkLoader}
|
||||
showPassword={showPassword}
|
||||
onClick={togglePasswordVisibility}
|
||||
navigateHandler={() => navigate("/login")}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ForgetPwdResponse
|
||||
title={
|
||||
linkSuccess
|
||||
? "Password Reset Complete"
|
||||
: "Password Reset Error"
|
||||
}
|
||||
message={
|
||||
linkSuccess
|
||||
? "Password Reset Complete. You can login now with your new credentials"
|
||||
: "Password Reset Error. Please get in touch with support for further support"
|
||||
}
|
||||
type={linkSuccess}
|
||||
/>
|
||||
</>
|
||||
:
|
||||
<ForgetPwdResponse
|
||||
title={linkSuccess? 'Password Reset Complete' : 'Password Reset Error'}
|
||||
message={linkSuccess? 'Password Reset Complete. You can login now with your new credentials' : 'Password Reset Error. Please get in touch with support for further support'
|
||||
}
|
||||
type={linkSuccess}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
:
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<ForgetPwdResponse
|
||||
title={'Forget Password'}
|
||||
message={'We are unable to continue to reset process. This error is usually due to expired links. Please start all over or contact us'}
|
||||
type={requestStatus.status}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<ForgetPwdResponse
|
||||
title={"Forget Password"}
|
||||
message={
|
||||
"We are unable to continue to reset process. This error is usually due to expired links. Please start all over or contact us"
|
||||
}
|
||||
type={requestStatus.status}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import WrenchBoard from "../../../assets/images/wrenchboard-logo-text.png";
|
||||
import usersService from "../../../services/UsersService";
|
||||
import InputCom from "../../Helpers/Inputs/InputCom";
|
||||
import AuthLayout from "../AuthLayout2";
|
||||
import ForgetPwdResponse from "../ForgetPwdResponse";
|
||||
import PasswordValidator from "../../../lib/PasswordValidator";
|
||||
import LoadingSpinner from "../../Spinners/LoadingSpinner";
|
||||
|
||||
const VerifyPassword = () => {
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [requestStatus, setRequestStatus] = useState({
|
||||
loading: true,
|
||||
status: false,
|
||||
data: [],
|
||||
});
|
||||
const [msgError, setMsgError] = useState("");
|
||||
const [linkLoader, setLinkLoader] = useState(false);
|
||||
const [linkSuccess, setLinkSuccess] = useState(null);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location?.search);
|
||||
const token = queryParams.get("passlink");
|
||||
const userApi = new usersService();
|
||||
|
||||
// To Show and Hide Password
|
||||
const togglePasswordVisibility = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
// Password
|
||||
const handlePassword = (e) => {
|
||||
let { name, value } = e?.target;
|
||||
if (name == "password") setPassword(value);
|
||||
if (name == "confirm_password") setConfirmPassword(value);
|
||||
};
|
||||
|
||||
const completeReset = async () => {
|
||||
if (!password || !confirmPassword) {
|
||||
// CHECKS IF PASSWORD IS EMPTY
|
||||
setMsgError("Please fill in fields");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
if (password != confirmPassword) {
|
||||
// CHECKS IF PASSWORD EQUALS CONFIRM PASSWORD
|
||||
setMsgError("Passwords does not match");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
if (password.length < 6) {
|
||||
// CHECKS IF PASSWORD LENGTH IS UPTO 6 CHARACTERS
|
||||
setMsgError("Password must be upto six characters");
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
if (!PasswordValidator(password)) {
|
||||
// CHECKS IF PASSWORD IS VALID
|
||||
setMsgError(
|
||||
"Password must contain alphanumeric, uppercase and special character: eg: Password1@"
|
||||
);
|
||||
return setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_RESET_START_ERROR_TIMEOUT);
|
||||
}
|
||||
try {
|
||||
setLinkLoader(true);
|
||||
var reqData = {
|
||||
reset_link: token,
|
||||
newpass: password,
|
||||
m_uid: requestStatus.data?.m_uid || "",
|
||||
reset_uid: requestStatus.data?.reset_uid || "",
|
||||
step: 300,
|
||||
};
|
||||
const res = await userApi?.CompleteResetPassword(reqData);
|
||||
|
||||
if (res.status == 200) {
|
||||
const { data } = res;
|
||||
if (data?.internal_return >= 0) {
|
||||
// setTimeout(() => {
|
||||
// navigate("/login", { replace: true });
|
||||
// setLinkLoader(false);
|
||||
// }, 2000);
|
||||
setLinkSuccess(true);
|
||||
} else {
|
||||
setLinkLoader(false);
|
||||
setMsgError("An error occurred");
|
||||
setLinkSuccess(false);
|
||||
}
|
||||
} else {
|
||||
setLinkLoader(false);
|
||||
setLinkSuccess(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setLinkLoader(false);
|
||||
setLinkSuccess(false);
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setMsgError(null);
|
||||
}, process.env.REACT_APP_SIGNUP_ERROR_TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
const verifyResetPwd = () => {
|
||||
// FUNCTION TO VERIFY RESET PASSWORD LINK
|
||||
setRequestStatus({ loading: true, status: false, data: [] });
|
||||
var reqData = {
|
||||
reset_link: token,
|
||||
step: 200,
|
||||
};
|
||||
userApi
|
||||
.CompleteResetPassword(reqData)
|
||||
.then((res) => {
|
||||
if (res.status != 200 || res.data.internal_return < 0) {
|
||||
return setRequestStatus({ loading: false, status: false, data: [] });
|
||||
}
|
||||
setRequestStatus({ loading: false, status: true, data: res.data });
|
||||
})
|
||||
.catch((error) => {
|
||||
setRequestStatus({ loading: false, status: false, data: [] });
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// little checker for the validity of the token
|
||||
if (token == null || token?.length != 64) {
|
||||
return setRequestStatus({ loading: false, status: false, data: [] });
|
||||
}
|
||||
verifyResetPwd();
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to WrenchBoard">
|
||||
<div className="w-full">
|
||||
<div className="mb-12">
|
||||
<Link to="#">
|
||||
<img
|
||||
src={WrenchBoard}
|
||||
alt="wrenchboard"
|
||||
className="h-10 mx-auto"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex place-content-center">
|
||||
{requestStatus.loading ? (
|
||||
<LoadingSpinner color="sky-blue" size="16" height="h-300px" />
|
||||
) : !requestStatus.loading && requestStatus.status ? (
|
||||
<div className="w-11/12 sm:max-w-[500px]">
|
||||
{linkSuccess == null ? (
|
||||
<>
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
Password Reset
|
||||
</h1>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
Enter a new password to reset
|
||||
</span>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
We'll send an email to confirm reset
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-red-500 font-semibold mb-3 leading-[27.3px] text-[13px]">
|
||||
Must include a special, numeric, uppercase and lowercase
|
||||
character
|
||||
</p>
|
||||
</div>
|
||||
<SuccessfulComponent
|
||||
password={password}
|
||||
confirmPassword={confirmPassword}
|
||||
handlePassword={handlePassword}
|
||||
onSubmit={completeReset}
|
||||
msgErr={msgError}
|
||||
loader={linkLoader}
|
||||
showPassword={showPassword}
|
||||
onClick={togglePasswordVisibility}
|
||||
navigateHandler={() => navigate("/login")}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ForgetPwdResponse
|
||||
title={
|
||||
linkSuccess
|
||||
? "Password Reset Complete"
|
||||
: "Password Reset Error"
|
||||
}
|
||||
message={
|
||||
linkSuccess
|
||||
? "Password Reset Complete. You can login now with your new credentials"
|
||||
: "Password Reset Error. Please get in touch with support for further support"
|
||||
}
|
||||
type={linkSuccess}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-11/12 sm:max-w-[500px] title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<ForgetPwdResponse
|
||||
title={"Forget Password"}
|
||||
message={
|
||||
"We are unable to continue to reset process. This error is usually due to expired links. Please start all over or contact us"
|
||||
}
|
||||
type={requestStatus.status}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyPassword;
|
||||
|
||||
const SuccessfulComponent = ({
|
||||
onSubmit,
|
||||
navigateHandler,
|
||||
showPassword,
|
||||
onClick,
|
||||
password,
|
||||
confirmPassword,
|
||||
handlePassword,
|
||||
msgErr,
|
||||
loader,
|
||||
}) => (
|
||||
<div className="input-area">
|
||||
{/* INPUT */}
|
||||
<div className="mb-5">
|
||||
<InputCom
|
||||
fieldClass="sm:px-6 px-2 tracking-[0.25em] text-2xl"
|
||||
value={password}
|
||||
inputHandler={handlePassword}
|
||||
placeholder="● ● ● ● ● ●"
|
||||
label="Password"
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
onClick={onClick}
|
||||
passIcon={showPassword ? "show-password" : "hide-password"}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<InputCom
|
||||
fieldClass="sm:px-6 px-2 tracking-[0.25em] text-2xl"
|
||||
value={confirmPassword}
|
||||
inputHandler={handlePassword}
|
||||
placeholder="● ● ● ● ● ●"
|
||||
label="Confirm Password"
|
||||
name="confirm_password"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
{msgErr && (
|
||||
<div className="relative p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]">
|
||||
{msgErr}
|
||||
</div>
|
||||
)}
|
||||
<div className="signin-area mb-3.5">
|
||||
<button
|
||||
onClick={onSubmit}
|
||||
type="button"
|
||||
className={`btn-login rounded-[0.475rem] mb-6 text-xl text-white flex justify-center bg-[#4687ba] hover:bg-[#009ef7] transition-all duration-300 items-center text-[15px]`}
|
||||
>
|
||||
{loader ? (
|
||||
<div className="signup btn-loader"></div>
|
||||
) : (
|
||||
<span>Continue</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="signin-area mb-3.5">
|
||||
<button
|
||||
onClick={navigateHandler}
|
||||
type="button"
|
||||
className={`rounded-[0.475rem] mb-6 text-[15px] font-semibold text-[#009ef7] hover:text-white flex justify-center bg-[#f1faff] hover:bg-[#009ef7] transition-all duration-300 items-center py-[0.8875rem] px-[1.81rem] btn-login`}
|
||||
>
|
||||
<span>Return Home</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import AuthLayout from "../AuthLayout2";
|
||||
import WrenchBoard from "../../../assets/images/wrenchboard-logo-text.png";
|
||||
|
||||
export default function VerifyYou() {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<>
|
||||
<AuthLayout slogan="Welcome to WrenchBoard">
|
||||
<div className="w-full">
|
||||
<div className="mb-12">
|
||||
<Link to="#">
|
||||
<img
|
||||
src={WrenchBoard}
|
||||
alt="wrenchboard"
|
||||
className="h-10 mx-auto"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex place-content-center">
|
||||
<div className="w-11/12 sm:max-w-[500px]">
|
||||
<div className="title-area flex flex-col justify-center items-center relative text-center mb-7">
|
||||
<h1 className="text-[#181c32] font-semibold dark:text-white mb-3 leading-[27.3px] text-[22.75px]">
|
||||
Let's verify your email now
|
||||
</h1>
|
||||
<span className="text-gray-400 font-medium text-[16.25px] leading-[24.375px]">
|
||||
Check your email.
|
||||
</span>
|
||||
</div>
|
||||
<div className="input-area">
|
||||
<div className="mb-5">
|
||||
<p className="text-[14px] leading-[19px] text-center text-[#181c32]">
|
||||
<b>Verify Email.</b> Help us secure your WrenchBoard account
|
||||
by verifying your email registration address. Verification
|
||||
will let you access all of WrenchBoard's features.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<p className="text-[14px] leading-[19px] text-center text-[#181c32]">
|
||||
If you do not receive the confirmation message within a few
|
||||
minutes of signing up, please check your Junk E-mail folder
|
||||
just in case the confirmation email got delivered there
|
||||
instead of your inbox. If so, select the confirmation
|
||||
message and click Not Junk, which will allow future messages
|
||||
to get through.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="signin-area flex justify-center mb-3.5">
|
||||
<button
|
||||
onClick={() => navigate("/")}
|
||||
type="button"
|
||||
className={`rounded-[0.475rem] mb-6 text-[15px] font-semibold text-[#009ef7] hover:text-white flex justify-center bg-[#f1faff] hover:bg-[#009ef7] transition-all duration-300 items-center py-[0.8875rem] px-[1.81rem]`}
|
||||
>
|
||||
<span>Return Home</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export default function BlogItem(props) {
|
||||
</h1>
|
||||
</div>
|
||||
<div className="notification-wrapper w-full bg-white p-8 rounded-2xl">
|
||||
{blogdata.loading ?
|
||||
{blogdata?.loading ?
|
||||
<LoadingSpinner size='8' color='sky-blue' height='h-[100px]' />
|
||||
:
|
||||
blogdata?.data?.blogdata && blogdata.data?.blogdata.length ?
|
||||
|
||||
@@ -5,7 +5,7 @@ import localImgLoad from "../../lib/localImgLoad";
|
||||
import CountDown from "../Helpers/CountDown";
|
||||
import Icons from "../Helpers/Icons";
|
||||
|
||||
export default function FamilyActiveJobsCard({ datas, hidden = false }) {
|
||||
export default function FamilyActiveJobsCard({ datas, hidden = false, image_server }) {
|
||||
|
||||
let { pathname } = useLocation();
|
||||
|
||||
@@ -22,7 +22,8 @@ export default function FamilyActiveJobsCard({ datas, hidden = false }) {
|
||||
}
|
||||
};
|
||||
//debugger;
|
||||
const bannerName = datas.banner == null ?'default.jpg':datas.banner;
|
||||
// const bannerName = datas.banner == null ?'default.jpg':datas.banner;
|
||||
let image = `${image_server}${localStorage.getItem('session_token')}/job/${datas.origin_job_uid}`
|
||||
return (
|
||||
<div className="card-style-one flex flex-col justify-between w-full h-[387px] bg-white dark:bg-dark-white p-3 pb rounded-2xl">
|
||||
<div className="content">
|
||||
@@ -31,10 +32,13 @@ export default function FamilyActiveJobsCard({ datas, hidden = false }) {
|
||||
{/* thumbnail image */}
|
||||
<div
|
||||
className="thumbnail w-full h-full rounded-xl overflow-hidden px-4 pt-4"
|
||||
// style={{
|
||||
// background: `url(${localImgLoad(
|
||||
// `images/taskbanners/${bannerName}`
|
||||
// )}) center / contain no-repeat`,
|
||||
// }}
|
||||
style={{
|
||||
background: `url(${localImgLoad(
|
||||
`images/taskbanners/${bannerName}`
|
||||
)}) center / contain no-repeat`,
|
||||
background: `url(${image}) center / contain no-repeat`,
|
||||
}}
|
||||
>
|
||||
{/* <div className="product-options flex justify-between relative">*/}
|
||||
|
||||
@@ -14,11 +14,7 @@ export default function OfferCard({
|
||||
datas?.currency
|
||||
);
|
||||
|
||||
let image = localStorage.getItem("session_token")
|
||||
? `${image_server}${localStorage.getItem("session_token")}/job/${
|
||||
datas.job_uid
|
||||
}`
|
||||
: "";
|
||||
let image = `${image_server}${localStorage.getItem("session_token")}/job/${datas.job_uid}`
|
||||
|
||||
return (
|
||||
<div className="card-style-one flex flex-col justify-between w-full h-[387px] bg-white dark:bg-dark-white p-3 pb rounded-2xl">
|
||||
|
||||
@@ -13,6 +13,7 @@ import usersService from "../../services/UsersService";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import AssignTaskPopout from "./FamilyPopout/AssignTaskPopout";
|
||||
import FamilyWallet from "./Tabs/FamilyWallet";
|
||||
import { apiConst } from "../../lib/apiConst";
|
||||
|
||||
// Lazy Imports for components
|
||||
const FamilyWaitlist = lazy(() => import("./Tabs/FamilyWaitlist"));
|
||||
@@ -127,7 +128,7 @@ export default function FamilyManageTabs({
|
||||
file_type: uploadedFile?.type?.split("/")[0]?.toLowerCase(),
|
||||
file_data: base64Img,
|
||||
msg_type: 'FILE',
|
||||
action: 11305
|
||||
action: apiConst.WRENCHBOARD_PICTURE_FAMMEMBER
|
||||
}
|
||||
setUploadStatus({loading: true, status: false, message:'Loading...'})
|
||||
apiCall.sendFiles(reqData).then(res=>{
|
||||
@@ -285,7 +286,7 @@ export default function FamilyManageTabs({
|
||||
limit: 30,
|
||||
offset: 0,
|
||||
job_type: "FAMILY",
|
||||
action: 13005,
|
||||
action: apiConst.WRENCHBOARD_PICTURE_FAMMEMBER,
|
||||
};
|
||||
|
||||
if (checkFamilyTask) {
|
||||
|
||||
@@ -5,6 +5,8 @@ import { PriceFormatter } from "../../Helpers/PriceFormatter";
|
||||
import LoadingSpinner from "../../Spinners/LoadingSpinner";
|
||||
import Detail from "../../jobPopout/popoutcomponent/Detail";
|
||||
import { NewTasks } from "./forms";
|
||||
import { tableReload } from "../../../store/TableReloads";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
const AssignTaskPopout = React.memo(
|
||||
({
|
||||
@@ -19,6 +21,8 @@ const AssignTaskPopout = React.memo(
|
||||
}) => {
|
||||
const apiCall = new usersService();
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
let [requestStatus, setRequestStatus] = useState({
|
||||
loading: false,
|
||||
status: false,
|
||||
@@ -90,11 +94,11 @@ const AssignTaskPopout = React.memo(
|
||||
const requiredFields = {
|
||||
banner,
|
||||
// category,
|
||||
country,
|
||||
currency:country,
|
||||
description,
|
||||
job_detail,
|
||||
'job detail':job_detail,
|
||||
price,
|
||||
timeline_days,
|
||||
timeline:timeline_days,
|
||||
title,
|
||||
};
|
||||
|
||||
@@ -104,7 +108,7 @@ const AssignTaskPopout = React.memo(
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: `${field} is empty`,
|
||||
message: `${field[0].toUpperCase()+field.slice(1).toLowerCase()} is empty`,
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
@@ -145,6 +149,7 @@ const AssignTaskPopout = React.memo(
|
||||
message: "action successful",
|
||||
});
|
||||
setUpdatePage(prev => !prev) // Updates family task page by calling the useeffect hook
|
||||
dispatch(tableReload({ type: "WALLETTABLE" })); // RELOADS USER WALLET
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
action(); // FUNCTION THAT CLOSES THE MODAL BOX
|
||||
@@ -167,9 +172,8 @@ const AssignTaskPopout = React.memo(
|
||||
<ModalCom
|
||||
action={action}
|
||||
situation={situation}
|
||||
className="assign-task-popup"
|
||||
>
|
||||
<div className="w-full h-full lg:w-[700px] lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl">
|
||||
<div className="w-11/12 lg:w-[700px] bg-white dark:bg-dark-white lg:rounded-2xl overflow-y-auto">
|
||||
<div className="w-full flex items-center justify-between lg:px-10 lg:py-8 px-[30px] py-[23px] border-b dark:border-[#5356fb29] border-light-purple">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
Assign task to{" "}
|
||||
@@ -201,7 +205,7 @@ const AssignTaskPopout = React.memo(
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{familyTask.loading ? (
|
||||
{familyTask?.loading ? (
|
||||
<div className="h-[100px] w-full flex justify-center items-center">
|
||||
<LoadingSpinner color="sky-blue" size="16" />
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function NewTasks({ formState, setFormState }) {
|
||||
onChange={handleInputChange}
|
||||
// onBlur={props.handleBlur}
|
||||
>
|
||||
{currency.loading ? (
|
||||
{currency?.loading ? (
|
||||
<option className="text-slate-500 text-[13.975px]" value="">
|
||||
Loading...
|
||||
</option>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const AddFamily = () => {
|
||||
return (
|
||||
<div>Add Family</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddFamily
|
||||
@@ -0,0 +1,154 @@
|
||||
import React, { useMemo, useRef, useState } from "react";
|
||||
import usersService from "../../../../services/UsersService";
|
||||
import { apiConst } from "../../../../lib/apiConst";
|
||||
|
||||
const FamilyBanner = ({ imageServer }) => {
|
||||
const uploadedImage = `${imageServer}${localStorage.getItem(
|
||||
"session_token"
|
||||
)}/familybanner/${localStorage.getItem("uid")}`;
|
||||
|
||||
const familyBannerRef = useRef(null);
|
||||
const jobApi = useMemo(() => new usersService(), []);
|
||||
|
||||
const [familyBannerImage, setFamilyBannerImage] = useState(uploadedImage);
|
||||
const [uploadStatus, setUploadStatus] = useState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles the change event of the family 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 familyBannerHandler = (e) => {
|
||||
setUploadStatus({ loading: false, status: false, message: "" });
|
||||
let acceptedFormat = ["jpeg", "jpg", "png", "bmp"]; // ARRAY OF SUPPORTED FORMATS
|
||||
let uploadedFile = e.target.files[0]; //UPLOADED FILE
|
||||
|
||||
const fileFormat = uploadedFile?.type?.split("/")[1]?.toLowerCase();
|
||||
if (!acceptedFormat.includes(fileFormat)) {
|
||||
//CHECKING FOR CORRECT UPLOAD FORMAT
|
||||
const msg = `Please select ${acceptedFormat
|
||||
.slice(0, -1)
|
||||
.join(", ")} or ${acceptedFormat.slice(-1)}`;
|
||||
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) => {
|
||||
let base64Img = imgReader.result.split(",")[1];
|
||||
let reqData = {
|
||||
// PAYLOAD FOR API CALL
|
||||
job_uid: localStorage.getItem("uid"),
|
||||
file_name: uploadedFile?.name.slice(0, 19),
|
||||
file_size: uploadedFile?.size,
|
||||
file_type: uploadedFile?.type?.split("/")[0]?.toLowerCase(),
|
||||
file_data: base64Img,
|
||||
msg_type: "FILE",
|
||||
action: apiConst.WRENCHBOARD_PICTURE_JOB,
|
||||
};
|
||||
setUploadStatus({
|
||||
loading: true,
|
||||
status: false,
|
||||
message: "Loading...",
|
||||
});
|
||||
jobApi
|
||||
.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",
|
||||
});
|
||||
setFamilyBannerImage(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]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="w-full lg:h-[400px] rounded-2xl">
|
||||
<img
|
||||
src={familyBannerImage}
|
||||
alt="familyBanner"
|
||||
className="w-full h-full object-cover rounded-2xl"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between w-full">
|
||||
<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>
|
||||
<input
|
||||
ref={familyBannerRef}
|
||||
className="hidden"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={familyBannerHandler}
|
||||
/>
|
||||
<button
|
||||
className="btn-gradient w-[153px] h-[46px] rounded-full text-white lg:flex hidden justify-center items-center"
|
||||
onClick={() => familyBannerRef.current.click()}
|
||||
>
|
||||
Change Banner
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FamilyBanner;
|
||||
@@ -0,0 +1,229 @@
|
||||
import React, { useState } from 'react'
|
||||
import ModalCom from '../../../Helpers/ModalCom';
|
||||
import LoadingSpinner from '../../../Spinners/LoadingSpinner';
|
||||
import InputCom from '../../../Helpers/Inputs/InputCom/index';
|
||||
import { Formik, Form } from 'formik';
|
||||
import * as Yup from "yup";
|
||||
import usersService from '../../../../services/UsersService';
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.email("Wrong email format")
|
||||
.matches(
|
||||
/^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/,
|
||||
"Invalid email format"
|
||||
)
|
||||
.min(3, "Minimum 3 characters")
|
||||
.max(50, "Maximum 50 characters")
|
||||
.required("Email is required"),
|
||||
firstname: Yup.string()
|
||||
.min(3, "Minimum 3 characters")
|
||||
.max(25, "Maximum 25 characters")
|
||||
.required("Firstname is required"),
|
||||
lastname: Yup.string()
|
||||
.min(3, "Minimum 3 characters")
|
||||
.max(25, "Maximum 25 characters")
|
||||
.required("Lastname is required"),
|
||||
family_type: Yup.string()
|
||||
.min(3, "Minimum 3 characters")
|
||||
.max(25, "Maximum 25 characters")
|
||||
.required("Family Type is required"),
|
||||
});
|
||||
|
||||
const initialValues = {
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
family_type: '',
|
||||
email: ''
|
||||
};
|
||||
|
||||
|
||||
export default function InviteRelative({action, situation, setReloadRelList}) {
|
||||
|
||||
const api = new usersService()
|
||||
|
||||
let [requestStatus, setRequestStatus] = useState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
}); // STATE FOR KNOWING WHEN A REQUEST IS MADE TO THE SERVER
|
||||
|
||||
const handleInvite = (values) => {
|
||||
setRequestStatus({loading: true, status: false, message: ""})
|
||||
api.inviteFamilyRelative(values).then(response => {
|
||||
let {status, data} = response
|
||||
if(data?.internal_return < 0){
|
||||
setRequestStatus({loading: false, status: false, message: "Unable to complete"})
|
||||
return setTimeout(()=>{
|
||||
setRequestStatus({ loading: false, status: false, message: ""})
|
||||
},3000)
|
||||
}
|
||||
setRequestStatus({loading: false, status: true, message: "Relative Added"})
|
||||
//RELOAD RELATIVE LSIT TABLE
|
||||
setReloadRelList(prev => !prev)
|
||||
setTimeout(()=>{
|
||||
action()
|
||||
},3000)
|
||||
}).catch(error => {
|
||||
setRequestStatus({loading: false, status: false, message: "Something went wrong, try again!"})
|
||||
setTimeout(()=>{
|
||||
setRequestStatus({ loading: false, status: false, message: ""})
|
||||
},3000)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalCom
|
||||
action={action}
|
||||
situation={situation}
|
||||
>
|
||||
<div className="lg:w-[600px] w-11/12 lg:overflow-hidden lg:rounded-2xl bg-white dark:bg-dark-white dark:text-white">
|
||||
<div className="logout-modal-header w-full flex items-center justify-between lg:px-10 lg:py-8 px-[30px] py-[23px] border-b border-light-purple dark:border-[#5356fb29] ">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
Invite Parent/Relative
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="text-[#374557] dark:text-red-500"
|
||||
onClick={action}
|
||||
>
|
||||
<svg
|
||||
width="36"
|
||||
height="36"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
className="fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M36 16.16C36 17.4399 36 18.7199 36 20.0001C35.7911 20.0709 35.8636 20.2554 35.8385 20.4001C34.5321 27.9453 30.246 32.9248 22.9603 35.2822C21.9006 35.6251 20.7753 35.7657 19.6802 35.9997C18.4003 35.9997 17.1204 35.9997 15.8401 35.9997C15.5896 35.7086 15.2189 35.7732 14.9034 35.7093C7.77231 34.2621 3.08728 30.0725 0.769671 23.187C0.435002 22.1926 0.445997 21.1199 0 20.1599C0 18.7198 0 17.2798 0 15.8398C0.291376 15.6195 0.214408 15.2656 0.270759 14.9808C1.71321 7.69774 6.02611 2.99691 13.0428 0.700951C14.0118 0.383805 15.0509 0.386897 15.9999 0C17.2265 0 18.4532 0 19.6799 0C19.7156 0.124041 19.8125 0.136067 19.9225 0.146719C27.3 0.868973 33.5322 6.21922 35.3801 13.427C35.6121 14.3313 35.7945 15.2484 36 16.16ZM33.011 18.0787C33.0433 9.77105 26.3423 3.00309 18.077 2.9945C9.78479 2.98626 3.00344 9.658 2.98523 17.8426C2.96667 26.1633 9.58859 32.9601 17.7602 33.0079C26.197 33.0577 32.9787 26.4186 33.011 18.0787Z"
|
||||
fill=""
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
d="M15.9309 18.023C13.9329 16.037 12.007 14.1207 10.0787 12.2072C9.60071 11.733 9.26398 11.2162 9.51996 10.506C9.945 9.32677 11.1954 9.0811 12.1437 10.0174C13.9067 11.7585 15.6766 13.494 17.385 15.2879C17.9108 15.8401 18.1633 15.7487 18.6375 15.258C20.3586 13.4761 22.1199 11.7327 23.8822 9.99096C24.8175 9.06632 26.1095 9.33639 26.4967 10.517C26.7286 11.2241 26.3919 11.7413 25.9133 12.2178C24.1757 13.9472 22.4477 15.6855 20.7104 17.4148C20.5228 17.6018 20.2964 17.7495 20.0466 17.9485C22.0831 19.974 24.0372 21.8992 25.9689 23.8468C26.9262 24.8119 26.6489 26.1101 25.4336 26.4987C24.712 26.7292 24.2131 26.3441 23.7455 25.8757C21.9945 24.1227 20.2232 22.3892 18.5045 20.6049C18.0698 20.1534 17.8716 20.2269 17.4802 20.6282C15.732 22.4215 13.9493 24.1807 12.1777 25.951C11.7022 26.4262 11.193 26.7471 10.4738 26.4537C9.31345 25.9798 9.06881 24.8398 9.98589 23.8952C11.285 22.5576 12.6138 21.2484 13.9387 19.9355C14.5792 19.3005 15.2399 18.6852 15.9309 18.023Z"
|
||||
fill="#"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="logout-modal-body w-full flex flex-col items-center px-10 py-8">
|
||||
<div className="personal-info-tab w-full flex flex-col justify-between">
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleInvite}
|
||||
>
|
||||
{(props) => {
|
||||
return (
|
||||
<Form>
|
||||
<div className="w-full flex flex-col-reverse sm:flex-row">
|
||||
<div className="fields w-full">
|
||||
{/* inputs starts here */}
|
||||
{/* Email */}
|
||||
<div className="field w-full mb-6">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
label="Email"
|
||||
type="text"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
value={props.values.email}
|
||||
inputHandler={props.handleChange}
|
||||
blurHandler={props.handleBlur}
|
||||
error={(props.errors.email && props.touched.email) ? props.errors.email : '' }
|
||||
/>
|
||||
</div>
|
||||
{/* first name and last name */}
|
||||
<div className="md:flex md:space-x-7 mb-6">
|
||||
<div className="field w-full mb-6 md:mb-0">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
label="First Name"
|
||||
type="text"
|
||||
name="firstname"
|
||||
placeholder="First Name"
|
||||
value={props.values.firstname}
|
||||
inputHandler={props.handleChange}
|
||||
blurHandler={props.handleBlur}
|
||||
error={(props.errors.firstname && props.touched.firstname) ? props.errors.firstname : '' }
|
||||
/>
|
||||
</div>
|
||||
<div className="field w-full">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
label="Last Name"
|
||||
type="text"
|
||||
name="lastname"
|
||||
placeholder="Last Name"
|
||||
value={props.values.lastname}
|
||||
inputHandler={props.handleChange}
|
||||
blurHandler={props.handleBlur}
|
||||
error={(props.errors.lastname && props.touched.lastname) ? props.errors.lastname : '' }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Type */}
|
||||
<div className="md:flex md:space-x-7 items-end mb-6">
|
||||
<div className="field w-full mb-6 md:mb-0">
|
||||
<InputCom
|
||||
fieldClass="px-6"
|
||||
label="Type"
|
||||
type="text"
|
||||
name="family_type"
|
||||
placeholder="Family Type"
|
||||
value={props.values.family_type}
|
||||
inputHandler={props.handleChange}
|
||||
blurHandler={props.handleBlur}
|
||||
error={(props.errors.family_type && props.touched.family_type) ? props.errors.family_type : '' }
|
||||
/>
|
||||
</div>
|
||||
<div className="field w-full flex justify-end">
|
||||
<div className="flex">
|
||||
{requestStatus.loading ? (
|
||||
<LoadingSpinner size="8" color="sky-blue" />
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
className={`text-white btn-gradient text-lg tracking-wide px-5 py-2 rounded-full ${(requestStatus.status || requestStatus.loading) && 'opacity-50'}`}
|
||||
disabled={requestStatus.status || requestStatus.loading}
|
||||
>
|
||||
Send Message
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</div>
|
||||
|
||||
{/* ERROR DISPLAY AND SUBMIT BUTTON */}
|
||||
{requestStatus.message != "" &&
|
||||
(!requestStatus.status ? (
|
||||
<div
|
||||
className={`w-full relative p-4 my-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
) : (
|
||||
requestStatus.status && (
|
||||
<div
|
||||
className={`relative p-4 my-4 text-green-700 bg-slate-200 border-slate-800 mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
{/* End of error or success display */}
|
||||
</div>
|
||||
</div>
|
||||
</ModalCom>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useState } from 'react'
|
||||
import { handlePagingFunc } from '../../../Pagination/HandlePagination';
|
||||
import PaginatedList from '../../../Pagination/PaginatedList';
|
||||
|
||||
export default function RelativeTable({relativeList}) {
|
||||
|
||||
// Handle Pagination
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const indexOfFirstItem = Number(currentPage);
|
||||
const indexOfLastItem =Number(indexOfFirstItem) + Number(process.env.REACT_APP_ITEM_PER_PAGE);
|
||||
|
||||
const currentRelativeList = relativeList?.slice(indexOfFirstItem, indexOfLastItem);
|
||||
|
||||
const handlePagination = (e) => {
|
||||
handlePagingFunc(e, setCurrentPage);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`w-full overflow-hidden rounded-2xl`}>
|
||||
<div className="relative w-full overflow-x-auto sm:rounded-lg flex flex-col justify-between min-h-[400px]">
|
||||
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400">
|
||||
<tbody>
|
||||
<>
|
||||
{relativeList && relativeList?.length > 0 ? (
|
||||
currentRelativeList.map((value, index) => (
|
||||
<tr key={value.uid || index} className="border-b dark:border-[#5356fb29] hover:bg-gray-50">
|
||||
<td className='p-2'>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="font-bold text-xl text-dark-gray dark:text-white">
|
||||
{value.firstname && value.firstname} {value.lastname && value.lastname}
|
||||
</h1>
|
||||
<span className="text-sm text-thin-light-gray">
|
||||
{value.email && value.email}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className='p-2'>
|
||||
{/* <span>Family Type</span> */}
|
||||
<span>{value.family_type && value.family_type.toUpperCase()}</span>
|
||||
</td>
|
||||
<td className='p-2 text-right'>
|
||||
{value.status && value.status}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr className="font-bold text-xl text-dark-gray dark:text-white whitespace-nowrap">
|
||||
<td className="p-2">No Members Found</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* PAGINATION BUTTON */}
|
||||
<PaginatedList
|
||||
onClick={handlePagination}
|
||||
prev={currentPage == 0 ? true : false}
|
||||
next={
|
||||
currentPage + Number(process.env.REACT_APP_ITEM_PER_PAGE) >=
|
||||
relativeList?.length
|
||||
? true
|
||||
: false
|
||||
}
|
||||
data={relativeList}
|
||||
start={indexOfFirstItem}
|
||||
stop={indexOfLastItem}
|
||||
/>
|
||||
{/* END OF PAGINATION BUTTON */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import RelativeTable from './RelativeTable'
|
||||
import InviteRelative from './InviteRelative'
|
||||
import usersService from '../../../../services/UsersService'
|
||||
import LoadingSpinner from '../../../Spinners/LoadingSpinner'
|
||||
|
||||
const Relatives = () => {
|
||||
|
||||
const api = new usersService()
|
||||
|
||||
const [reloadRelList, setReloadRelList] = useState(false)
|
||||
|
||||
const [invitePopout, setInvitePopout] = useState(false)
|
||||
|
||||
const [relativeList, setRelativeList] = useState({loading: true, data:[]})
|
||||
|
||||
const showInviteMemberPopout = () => {
|
||||
setInvitePopout(true)
|
||||
}
|
||||
|
||||
const getRelativeList = () => {
|
||||
setRelativeList(prev => ({...prev, loading: true}))
|
||||
api.getFamilyRelativeList().then(response => {
|
||||
let {data:{result_list}} = response
|
||||
if(!result_list || result_list?.length <= 0){
|
||||
setRelativeList({loading:false, data:[]})
|
||||
return
|
||||
}
|
||||
setRelativeList({loading:false, data:result_list})
|
||||
}).catch(error => {
|
||||
setRelativeList({loading:false, data:[]})
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getRelativeList()
|
||||
},[reloadRelList])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className=''>
|
||||
<div className='py-4'>
|
||||
<button onClick={showInviteMemberPopout} className='text-white btn-gradient text-lg tracking-wide px-5 py-2 rounded-full'>Invite</button>
|
||||
<div className='my-4 border-b-2'></div>
|
||||
</div>
|
||||
<div className='min-h-[500px]'>
|
||||
{relativeList.loading ?
|
||||
<LoadingSpinner size='8' height='h-full' />
|
||||
:
|
||||
<RelativeTable relativeList={relativeList.data} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* INVITE RELATIVE POPOUT */}
|
||||
{invitePopout &&
|
||||
<InviteRelative
|
||||
action={()=>setInvitePopout(false)}
|
||||
situation={invitePopout}
|
||||
setReloadRelList={setReloadRelList}
|
||||
/>
|
||||
}
|
||||
{/* END OF INVITE RELATIVE POPOUT */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Relatives
|
||||
@@ -0,0 +1,5 @@
|
||||
import AddFamily from "./AddFamily";
|
||||
import FamilyBanner from "./FamilyBanner";
|
||||
import Relatives from "./Relatives";
|
||||
|
||||
export {AddFamily, FamilyBanner, Relatives}
|
||||
@@ -0,0 +1,107 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import Icons from "../../Helpers/Icons";
|
||||
import Layout from "../../Partials/Layout";
|
||||
import { AddFamily, FamilyBanner, Relatives } from "./Tabs";
|
||||
|
||||
const FamilySettings = () => {
|
||||
let {state} = useLocation()
|
||||
const tabs = [
|
||||
{
|
||||
id: 1,
|
||||
name: "add_family",
|
||||
title: "Add Family",
|
||||
iconName: "new-family",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "relatives",
|
||||
title: "Relatives",
|
||||
iconName: "people-hover",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "family_banner",
|
||||
title: "Family Banner",
|
||||
iconName: "people-hover",
|
||||
},
|
||||
];
|
||||
|
||||
const [tab, setTab] = useState(() => {
|
||||
// Retrieve the active tab from local storage or use the default tab
|
||||
return localStorage.getItem("activeTab") || tabs[0].name;
|
||||
});
|
||||
|
||||
const tabHandler = (value) => {
|
||||
setTab(value);
|
||||
};
|
||||
|
||||
// Update local storage when the tab changes
|
||||
useEffect(() => {
|
||||
localStorage.setItem("activeTab", tab);
|
||||
}, [tab]);
|
||||
|
||||
const tabComponents = {
|
||||
add_family: <AddFamily />,
|
||||
relatives: <Relatives />,
|
||||
family_banner: <FamilyBanner imageServer={state.imageServer} />,
|
||||
};
|
||||
|
||||
const defaultTabComponent = Array(tabComponents)[0].add_family;
|
||||
|
||||
const selectedComponent = tabComponents[tab] || defaultTabComponent;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="notification-page w-full mb-10">
|
||||
<div className="notification-wrapper w-full">
|
||||
{/* heading */}
|
||||
<div className="sm:flex justify-between items-center mb-6">
|
||||
<div className="mb-5 sm:mb-0">
|
||||
<h1 className="text-26 font-bold inline-flex gap-3 text-dark-gray dark:text-white items-center">
|
||||
<span className={``}>Family Settings</span>
|
||||
</h1>
|
||||
</div>
|
||||
<Link to="/acc-family" className="flex gap-2 items-center text-dark-gray dark:text-white">
|
||||
<svg className="w-5 h-5 rtl:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6.75 15.75L3 12m0 0l3.75-3.75M3 12h18" />
|
||||
</svg>
|
||||
Family
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-white dark:bg-dark-white overflow-y-auto rounded-2xl section-shadow h-full ">
|
||||
<div className="update-table w-full h-full p-4 bg-white dark:bg-dark-white overflow-y-auto rounded-2xl section-shadow min-h-[520px] lg:flex lg:px-10 px-4 justify-between">
|
||||
<div className="content-tab-items lg:w-[230px] w-full mr-2">
|
||||
<ul className="overflow-hidden mb-10 lg:mb-0 py-8">
|
||||
{tabs.map(({ name, id, title, iconName }) => (
|
||||
<li
|
||||
onClick={() => tabHandler(name)}
|
||||
key={id}
|
||||
className={`flex lg:space-x-4 space-x-2 hover:text-purple transition-all duration-300 ease-in-out items-center cursor-pointer lg:mb-11 mb-2 mr-6 lg:mr-0 float-left lg:float-none overflow-hidden ${
|
||||
tab === name ? "text-purple" : " text-thin-light-gray"
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
<Icons name={iconName} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-18 tracking-wide">{title}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-[1px] bg-[#E3E4FE] dark:bg-[#a7a9b533] mr-10"></div>
|
||||
<div className="flex-1">
|
||||
<div className="tab-item">{selectedComponent}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default FamilySettings;
|
||||
@@ -77,7 +77,7 @@ export default function FamilyTable({
|
||||
>
|
||||
<td className="py-4">
|
||||
<div className="flex space-x-2 items-center w-full">
|
||||
<div className="w-[60px] h-[60px] rounded-full overflow-hidden flex justify-center items-center flex-[0.1]">
|
||||
<div className="w-[60px] h-[60px] rounded-full overflow-hidden flex justify-center items-center">
|
||||
<img
|
||||
// src={profile_picture}
|
||||
src={image || profile_picture || localImgLoad(`images/icons/${banner}`)}
|
||||
@@ -87,7 +87,7 @@ export default function FamilyTable({
|
||||
</div>
|
||||
<div className="flex flex-col flex-[0.9]">
|
||||
<h1 className="font-bold text-xl text-dark-gray dark:text-white whitespace-nowrap">
|
||||
{`${firstname} ${lastname} (${age})`}
|
||||
{`${firstname} ${lastname} (${age < 10 ? `0${age}` : age})`}
|
||||
</h1>
|
||||
<span className="text-sm text-thin-light-gray">
|
||||
Added: <span className="text-purple ml-1">{addedDate}</span>
|
||||
@@ -149,7 +149,7 @@ export default function FamilyTable({
|
||||
className="w-[0.7rem]"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M.366 19.708c.405.39 1.06.39 1.464 0l8.563-8.264a1.95 1.95 0 0 0 0-2.827L1.768.292A1.063 1.063 0 0 0 .314.282a.976.976 0 0 0-.011 1.425l7.894 7.617a.975.975 0 0 1 0 1.414L.366 18.295a.974.974 0 0 0 0 1.413"
|
||||
// fill=""
|
||||
className="color000000 svgShape fill-[#fff]"
|
||||
|
||||
@@ -55,7 +55,6 @@ export default function FamilyProfile({ familyData, className }) {
|
||||
year: familyData?.year,
|
||||
month: familyData?.month,
|
||||
enable_traking: familyData?.enable_traking,
|
||||
action: 22020,
|
||||
};
|
||||
|
||||
// const handleChange = ({ target: { name, value } }) => {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import ModalCom from '../../../Helpers/ModalCom'
|
||||
import InputCom from '../../../Helpers/Inputs/InputCom'
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ModalCom from "../../../Helpers/ModalCom";
|
||||
import InputCom from "../../../Helpers/Inputs/InputCom";
|
||||
import { Form, Formik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
|
||||
import {AmountTo2DP} from '../../../Helpers/PriceFormatter'
|
||||
import usersService from '../../../../services/UsersService';
|
||||
import LoadingSpinner from '../../../Spinners/LoadingSpinner';
|
||||
import { PriceFormatter } from '../../../Helpers/PriceFormatter';
|
||||
import { tableReload } from '../../../../store/TableReloads';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { AmountTo2DP } from "../../../Helpers/PriceFormatter";
|
||||
import usersService from "../../../../services/UsersService";
|
||||
import LoadingSpinner from "../../../Spinners/LoadingSpinner";
|
||||
import { PriceFormatter } from "../../../Helpers/PriceFormatter";
|
||||
import { tableReload } from "../../../../store/TableReloads";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { apiConst } from "../../../../lib/apiConst";
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
// amount: Yup.string()
|
||||
@@ -22,104 +23,149 @@ const validationSchema = Yup.object().shape({
|
||||
// return true;
|
||||
// })
|
||||
// .required("Amount is required"),
|
||||
amount: Yup.number('Please enter a number')
|
||||
.min(1, "Price must be greater than 0")
|
||||
.required("Amount is required"),
|
||||
comment: Yup.string()
|
||||
.required("Comment is required"),
|
||||
amount: Yup.number("Please enter a number")
|
||||
.min(1, "Price must be greater than 0")
|
||||
.required("Amount is required"),
|
||||
comment: Yup.string().required("Comment is required"),
|
||||
});
|
||||
|
||||
function FamilyAddFundPopout({action, situation, wallet, familyData}) {
|
||||
function FamilyAddFundPopout({ action, situation, wallet, familyData }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const apiUrl = new usersService();
|
||||
|
||||
const apiUrl = new usersService()
|
||||
|
||||
const [startTransfer, setStartTransfer] = useState({loading:true, data: {}})
|
||||
|
||||
const [requestStatus, setRequestStatus] = useState({loading:false, status:false, message:''})
|
||||
const [startTransfer, setStartTransfer] = useState({
|
||||
loading: true,
|
||||
data: {},
|
||||
});
|
||||
|
||||
const [requestStatus, setRequestStatus] = useState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
});
|
||||
|
||||
// initial values for formik
|
||||
let initialValues = {
|
||||
amount: '',
|
||||
from : AmountTo2DP(startTransfer?.data?.origing_current_balance*0.01),
|
||||
amount: "",
|
||||
from: AmountTo2DP(startTransfer?.data?.origing_current_balance * 0.01),
|
||||
to: `${familyData.firstname} ${familyData.lastname}`,
|
||||
comment: ''
|
||||
comment: "",
|
||||
};
|
||||
// FUNCTION TO PERFORM FAMILY TRANSFER
|
||||
const handleAddFund = (values) => {
|
||||
setRequestStatus({loading:true, status:false, message:''})
|
||||
setRequestStatus({ loading: true, status: false, message: "" });
|
||||
|
||||
let senderBal = startTransfer?.data?.origing_current_balance || '' // SENDER'S ACCOUNT BALANCE
|
||||
let senderLimit = startTransfer?.data?.origing_transfer_limit || '' // SENDER'S TRANSFER LIMIT
|
||||
let senderBal = startTransfer?.data?.origing_current_balance || ""; // SENDER'S ACCOUNT BALANCE
|
||||
let senderLimit = startTransfer?.data?.origing_transfer_limit || ""; // SENDER'S TRANSFER LIMIT
|
||||
|
||||
let reqData = { // API REQUEST DATA
|
||||
family_uid : familyData.uid,
|
||||
wallet_uid : wallet.wallet_uid,
|
||||
origing_wallet_uid : startTransfer?.data?.origing_wallet_uid,
|
||||
currency : startTransfer?.data?.currency,
|
||||
amount : values.amount*100,
|
||||
description : values.comment,
|
||||
family_transfer_mode : 100,
|
||||
action : 22014
|
||||
let reqData = {
|
||||
// API REQUEST DATA
|
||||
family_uid: familyData.uid,
|
||||
wallet_uid: wallet.wallet_uid,
|
||||
origing_wallet_uid: startTransfer?.data?.origing_wallet_uid,
|
||||
currency: startTransfer?.data?.currency,
|
||||
amount: values.amount * 100,
|
||||
description: values.comment,
|
||||
family_transfer_mode: 100,
|
||||
action: apiConst.WRENCHBOARD_FAMILY_TRANSFER,
|
||||
};
|
||||
|
||||
if (!senderBal || !senderLimit) {
|
||||
// RETURNS UNAUTHORIZED, IF SENDER BAL OR LIMIT IS NOT AVAILABLE
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Unauthorized, try again later",
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
if(!senderBal || !senderLimit){ // RETURNS UNAUTHORIZED, IF SENDER BAL OR LIMIT IS NOT AVAILABLE
|
||||
setRequestStatus({loading:false, status:false, message:'Unauthorized, try again later'})
|
||||
return setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message:''})
|
||||
}, 5000)
|
||||
if (values.amount > senderBal * 0.01) {
|
||||
// CHECKS TO SEE IF SENDER IS SENDING MORE THAN HIS BALANCE
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "You cannot send more than your balance",
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
if(values.amount > senderBal*0.01){ // CHECKS TO SEE IF SENDER IS SENDING MORE THAN HIS BALANCE
|
||||
setRequestStatus({loading:false, status:false, message:'You cannot send more than your balance'})
|
||||
return setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message:''})
|
||||
}, 5000)
|
||||
if (values.amount > senderLimit * 0.01) {
|
||||
// CHECKS TO SEE IF SENDER IS SENDING MORE THAN HIS LIMIT
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: `You cannot exceed ${senderLimit * 0.01} ${
|
||||
startTransfer?.data?.origing_currency.charAt(0).toUpperCase() +
|
||||
startTransfer?.data?.origing_currency.slice(1).toLowerCase()
|
||||
}`,
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
if(values.amount > senderLimit*0.01){ // CHECKS TO SEE IF SENDER IS SENDING MORE THAN HIS LIMIT
|
||||
setRequestStatus({loading:false, status:false, message:`You cannot exceed ${senderLimit*0.01} ${startTransfer?.data?.origing_currency.charAt(0).toUpperCase() + startTransfer?.data?.origing_currency.slice(1).toLowerCase()}`})
|
||||
return setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message:''})
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
apiUrl.familyTransfer(reqData).then(({data}) => {
|
||||
if(data.internal_return < 0 || data.credit_confirm == '' || data.pay_confirm == ''){
|
||||
setRequestStatus({loading:false, status:false, message:'Transfer Failed'})
|
||||
return setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message:''})
|
||||
}, 5000)
|
||||
}
|
||||
setRequestStatus({loading:false, status:true, message:'Transfer Successful'})
|
||||
setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message:''})
|
||||
dispatch(tableReload({ type: "WALLETTABLE" })); // UPDATES PARENT WALLET ACCOUNT
|
||||
action() // TO CLOSE THE MODAL
|
||||
}, 5000)
|
||||
}).catch(error => {
|
||||
setRequestStatus({loading:false, status:false, message:'Network Error, try again'})
|
||||
setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message:''})
|
||||
}, 5000)
|
||||
})
|
||||
}
|
||||
apiUrl
|
||||
.familyTransfer(reqData)
|
||||
.then(({ data }) => {
|
||||
if (
|
||||
data.internal_return < 0 ||
|
||||
data.credit_confirm == "" ||
|
||||
data.pay_confirm == ""
|
||||
) {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Transfer Failed",
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 5000);
|
||||
}
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: true,
|
||||
message: "Transfer Successful",
|
||||
});
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
dispatch(tableReload({ type: "WALLETTABLE" })); // UPDATES PARENT WALLET ACCOUNT
|
||||
action(); // TO CLOSE THE MODAL
|
||||
}, 5000);
|
||||
})
|
||||
.catch((error) => {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Network Error, try again",
|
||||
});
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 5000);
|
||||
});
|
||||
};
|
||||
|
||||
// LOAD FAMILY START TRANSFER
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
let reqData = {
|
||||
family_uid: familyData.uid,
|
||||
wallet_uid: wallet.wallet_uid,
|
||||
action: 22013
|
||||
}
|
||||
apiUrl.familyTransferStart(reqData).then(response => {
|
||||
setStartTransfer({loading:false, data:response?.data })
|
||||
}).catch(err => {
|
||||
setStartTransfer({loading:false, data: {}})
|
||||
})
|
||||
},[])
|
||||
action: apiConst.WRENCHBOARD_FAMILY_TRANSFERSTART,
|
||||
};
|
||||
apiUrl
|
||||
.familyTransferStart(reqData)
|
||||
.then((response) => {
|
||||
setStartTransfer({ loading: false, data: response?.data });
|
||||
})
|
||||
.catch((err) => {
|
||||
setStartTransfer({ loading: false, data: {} });
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ModalCom action={action} situation={situation}>
|
||||
@@ -155,116 +201,125 @@ function FamilyAddFundPopout({action, situation, wallet, familyData}) {
|
||||
</button>
|
||||
</div>
|
||||
<div className="logout-modal-body w-full flex flex-col items-center px-10 py-8">
|
||||
{startTransfer.loading && <LoadingSpinner size='16' color='sky-blue' height={'h-64'} />}
|
||||
|
||||
{ !startTransfer.loading &&
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleAddFund}
|
||||
>
|
||||
{(props) => (
|
||||
<Form className="w-full">
|
||||
<div className="flex flex-col-reverse sm:flex-row">
|
||||
<div className="fields w-full">
|
||||
{/* AMOUNT */}
|
||||
<div className="field w-full mb-[0.5rem]">
|
||||
<InputCom
|
||||
placeholder="0"
|
||||
label={`Amount (${startTransfer?.data?.currency})`}
|
||||
name="amount"
|
||||
type="text"
|
||||
parentClass="flex items-center gap-1 w-full"
|
||||
labelClass="flex-[0.3] mb-0"
|
||||
inputClass={`flex-[0.7] input-curve lg border border-[#dce4e9] ${props.errors.amount && props.touched.amount ? 'border border-red-500' : ''}`}
|
||||
fieldClass="px-2 text-right"
|
||||
value={props.values.amount}
|
||||
inputHandler={props.handleChange}
|
||||
/>
|
||||
</div>
|
||||
{startTransfer.loading && (
|
||||
<LoadingSpinner size="16" color="sky-blue" height={"h-64"} />
|
||||
)}
|
||||
|
||||
{/* FROM */}
|
||||
<div className="field w-full mb-[0.5rem]">
|
||||
<InputCom
|
||||
placeholder="From"
|
||||
label={`From (${startTransfer?.data?.origing_currency})`}
|
||||
name="from"
|
||||
type="text"
|
||||
parentClass="flex items-center gap-1 w-full"
|
||||
labelClass="flex-[0.3] mb-0"
|
||||
inputClass={`flex-[0.7] input-curve lg border border-[#dce4e9]`}
|
||||
fieldClass="px-2 text-right"
|
||||
value={props.values.from}
|
||||
disable={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* TO */}
|
||||
<div className="field w-full mb-[0.5rem]">
|
||||
<InputCom
|
||||
placeholder="To"
|
||||
label="To:"
|
||||
name="to"
|
||||
type="text"
|
||||
parentClass="flex items-center gap-1 w-full"
|
||||
labelClass="flex-[0.3] mb-0"
|
||||
inputClass={`flex-[0.7] input-curve lg border border-[#dce4e9]`}
|
||||
fieldClass="px-2 text-right"
|
||||
value={props.values.to}
|
||||
disable={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* COMMENT */}
|
||||
<div className="field w-full mb-[0.5rem]">
|
||||
<div className="w-full">
|
||||
<label
|
||||
htmlFor="Job Delivery Details"
|
||||
className="input-label text-[#181c32] dark:text-white text-[13.975px] leading-[20.9625px] font-semibold flex items-center gap-1"
|
||||
>
|
||||
Comment
|
||||
{/* {props.errors.comment && props.touched.comment && <span className='text-sm text-red-500'>{' '}{props.errors.comment}</span>} */}
|
||||
</label>
|
||||
<textarea
|
||||
// id="Job Delivery Details"
|
||||
rows="2"
|
||||
className={`input-field px-3 py-2 placeholder:text-base text-dark-gray dark:text-white w-full bg-slate-100 dark:bg-[#11131F] focus:ring-0 focus:outline-[#dce4e9] rounded-[10px] border ${props.errors.comment && props.touched.comment ? 'border border-red-500' : ''}`}
|
||||
style={{ resize: "none" }}
|
||||
name="comment"
|
||||
value={props.values.comment}
|
||||
onChange={props.handleChange}
|
||||
/>
|
||||
{!startTransfer.loading && (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleAddFund}
|
||||
>
|
||||
{(props) => (
|
||||
<Form className="w-full">
|
||||
<div className="flex flex-col-reverse sm:flex-row">
|
||||
<div className="fields w-full">
|
||||
{/* AMOUNT */}
|
||||
<div className="field w-full mb-[0.5rem]">
|
||||
<InputCom
|
||||
placeholder="0"
|
||||
label={`Amount (${startTransfer?.data?.currency})`}
|
||||
name="amount"
|
||||
type="text"
|
||||
parentClass="flex items-center gap-1 w-full"
|
||||
labelClass="flex-[0.3] mb-0"
|
||||
inputClass={`flex-[0.7] input-curve lg border border-[#dce4e9] ${
|
||||
props.errors.amount && props.touched.amount
|
||||
? "border border-red-500"
|
||||
: ""
|
||||
}`}
|
||||
fieldClass="px-2 text-right"
|
||||
value={props.values.amount}
|
||||
inputHandler={props.handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* FROM */}
|
||||
<div className="field w-full mb-[0.5rem]">
|
||||
<InputCom
|
||||
placeholder="From"
|
||||
label={`From (${startTransfer?.data?.origing_currency})`}
|
||||
name="from"
|
||||
type="text"
|
||||
parentClass="flex items-center gap-1 w-full"
|
||||
labelClass="flex-[0.3] mb-0"
|
||||
inputClass={`flex-[0.7] input-curve lg border border-[#dce4e9]`}
|
||||
fieldClass="px-2 text-right"
|
||||
value={props.values.from}
|
||||
disable={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* TO */}
|
||||
<div className="field w-full mb-[0.5rem]">
|
||||
<InputCom
|
||||
placeholder="To"
|
||||
label="To:"
|
||||
name="to"
|
||||
type="text"
|
||||
parentClass="flex items-center gap-1 w-full"
|
||||
labelClass="flex-[0.3] mb-0"
|
||||
inputClass={`flex-[0.7] input-curve lg border border-[#dce4e9]`}
|
||||
fieldClass="px-2 text-right"
|
||||
value={props.values.to}
|
||||
disable={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* COMMENT */}
|
||||
<div className="field w-full mb-[0.5rem]">
|
||||
<div className="w-full">
|
||||
<label
|
||||
htmlFor="Job Delivery Details"
|
||||
className="input-label text-[#181c32] dark:text-white text-[13.975px] leading-[20.9625px] font-semibold flex items-center gap-1"
|
||||
>
|
||||
Comment
|
||||
{/* {props.errors.comment && props.touched.comment && <span className='text-sm text-red-500'>{' '}{props.errors.comment}</span>} */}
|
||||
</label>
|
||||
<textarea
|
||||
// id="Job Delivery Details"
|
||||
rows="2"
|
||||
className={`input-field px-3 py-2 placeholder:text-base text-dark-gray dark:text-white w-full bg-slate-100 dark:bg-[#11131F] focus:ring-0 focus:outline-[#dce4e9] rounded-[10px] border ${
|
||||
props.errors.comment && props.touched.comment
|
||||
? "border border-red-500"
|
||||
: ""
|
||||
}`}
|
||||
style={{ resize: "none" }}
|
||||
name="comment"
|
||||
value={props.values.comment}
|
||||
onChange={props.handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* inputs ends here */}
|
||||
</div>
|
||||
{/* inputs ends here */}
|
||||
</div>
|
||||
</div>
|
||||
{/* ERROR DISPLAY AND SUBMIT BUTTON */}
|
||||
<div className="content-footer w-full">
|
||||
{/* error or success display */}
|
||||
{requestStatus.message != "" &&
|
||||
(!requestStatus.status ? (
|
||||
<div
|
||||
className={`relative p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
// className={`pb-1 absolute bottom-0 left-1/2 -translate-x-1/2 text-[#912741] rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
) : (
|
||||
requestStatus.status && (
|
||||
{/* ERROR DISPLAY AND SUBMIT BUTTON */}
|
||||
<div className="content-footer w-full">
|
||||
{/* error or success display */}
|
||||
{requestStatus.message != "" &&
|
||||
(!requestStatus.status ? (
|
||||
<div
|
||||
className={`relative p-4 text-green-700 bg-slate-200 border-slate-800 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
// className={`pb-1 absolute bottom-0 left-1/2 -translate-x-1/2 text-green-700 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
className={`relative p-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
// className={`pb-1 absolute bottom-0 left-1/2 -translate-x-1/2 text-[#912741] rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
{/* End of error or success display */}
|
||||
) : (
|
||||
requestStatus.status && (
|
||||
<div
|
||||
className={`relative p-4 text-green-700 bg-slate-200 border-slate-800 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
// className={`pb-1 absolute bottom-0 left-1/2 -translate-x-1/2 text-green-700 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
{/* End of error or success display */}
|
||||
|
||||
<div className="pt-2 w-full border-t border-light-purple dark:border-[#5356fb29] flex justify-end items-center">
|
||||
<div className="w-full flex justify-between gap-2 items-center">
|
||||
<div className="pt-2 w-full border-t border-light-purple dark:border-[#5356fb29] flex justify-end items-center">
|
||||
<div className="w-full flex justify-between gap-2 items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={action}
|
||||
@@ -273,29 +328,31 @@ function FamilyAddFundPopout({action, situation, wallet, familyData}) {
|
||||
Cancel
|
||||
</button>
|
||||
<>
|
||||
{requestStatus.loading ?
|
||||
<LoadingSpinner size='6' color='sky-blue' />
|
||||
:
|
||||
<button
|
||||
type="submit"
|
||||
className={`w-[150px] h-[48px] rounded-full text-base text-white bg-sky-500 hover:bg-sky-400 ${requestStatus.status ? 'opacity-50' : ''}`}
|
||||
disabled={requestStatus.status}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
}
|
||||
{requestStatus.loading ? (
|
||||
<LoadingSpinner size="6" color="sky-blue" />
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
className={`w-[150px] h-[48px] rounded-full text-base text-white bg-sky-500 hover:bg-sky-400 ${
|
||||
requestStatus.status ? "opacity-50" : ""
|
||||
}`}
|
||||
disabled={requestStatus.status}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
}
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ModalCom>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default FamilyAddFundPopout
|
||||
export default FamilyAddFundPopout;
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, {
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import usersService from "../../services/UsersService";
|
||||
import InputCom from "../Helpers/Inputs/InputCom";
|
||||
import ModalCom from "../Helpers/ModalCom";
|
||||
@@ -13,7 +14,6 @@ import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import FamilyTable from "./FamilyTable";
|
||||
|
||||
export default function FamilyAcc() {
|
||||
const [selectTab, setValue] = useState("today");
|
||||
// State to store the selected year and month
|
||||
const [selectedYear, setSelectedYear] = useState("");
|
||||
const [selectedMonth, setSelectedMonth] = useState("");
|
||||
@@ -33,10 +33,6 @@ export default function FamilyAcc() {
|
||||
setPopUp((prev) => !prev);
|
||||
};
|
||||
|
||||
const filterHandler = (value) => {
|
||||
setValue(value);
|
||||
};
|
||||
|
||||
// Handle year selection
|
||||
const handleYearChange = (e) => {
|
||||
setSelectedYear(e.target.value);
|
||||
@@ -106,17 +102,11 @@ export default function FamilyAcc() {
|
||||
const memberList = useCallback(async () => {
|
||||
setLoader(true);
|
||||
try {
|
||||
const reqData = {
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
action: 22010,
|
||||
};
|
||||
|
||||
const res = await apiCall.familyListings(reqData);
|
||||
const res = await apiCall.familyListings();
|
||||
const { data } = res;
|
||||
if (data?.internal_return >= 0 && data?.status === "OK") {
|
||||
const { result_list, session_image_server } = data;
|
||||
setFamilyList({result_list, session_image_server});
|
||||
setFamilyList({ result_list, session_image_server });
|
||||
setLoader(false);
|
||||
} else {
|
||||
return;
|
||||
@@ -146,12 +136,9 @@ export default function FamilyAcc() {
|
||||
<div className="sm:flex justify-between items-center mb-6">
|
||||
<div className="mb-5 sm:mb-0">
|
||||
<h1 className="text-26 font-bold inline-flex gap-3 text-dark-gray dark:text-white items-center">
|
||||
<span
|
||||
className={`${selectTab === "today" ? "block" : "hidden"}`}
|
||||
>
|
||||
Family Accounts
|
||||
</span>
|
||||
{familyList.length < process.env.REACT_APP_MAX_FAMILY_MEMBERS &&
|
||||
<span className={``}>Family Accounts</span>
|
||||
{familyList?.result_list?.length <
|
||||
process.env.REACT_APP_MAX_FAMILY_MEMBERS &&
|
||||
!loader && (
|
||||
<button
|
||||
onClick={popUpHandler}
|
||||
@@ -163,12 +150,31 @@ export default function FamilyAcc() {
|
||||
)}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="slider-btns flex space-x-4">
|
||||
<div
|
||||
onClick={() => filterHandler("today")}
|
||||
className="relative"
|
||||
></div>
|
||||
</div>
|
||||
<Link
|
||||
to={`/familysettings`}
|
||||
state={{ imageServer: familyList?.session_image_server }}
|
||||
className="slider-btns flex space-x-4 w-12 h-12 rounded-md shadow-sm justify-center items-center cursor-pointer dark:bg-[linear-gradient(134.38deg,#f539f8_0%,#c342f9_43.55%,#5356fb_104.51%)]"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6 dark:stroke-white"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
<Suspense fallback={<LoadingSpinner color="sky-blue" size="16" />}>
|
||||
<FamilyTable
|
||||
@@ -214,7 +220,7 @@ const FamilyForm = ({
|
||||
popUpHandler,
|
||||
}) => {
|
||||
return (
|
||||
<div className="logout-modal-wrapper lg:w-[460px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl">
|
||||
<div className="logout-modal-wrapper w-11/12 lg:w-[460px] 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:px-10 lg:py-8 px-[30px] py-[23px] border-b dark:border-[#5356fb29] border-light-purple dark:border-[#5356fb29] ">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
Add Members
|
||||
|
||||
@@ -462,6 +462,15 @@ export default function Icons({ name }) {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
) : name === "my-page" ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 50" id="Page">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M0 13V2a2 2 0 0 1 2-2h64a2 2 0 0 1 2 2v11H0Zm0 4v31a2 2 0 0 0 2 2h20V17H0Zm26 33h40a2 2 0 0 0 2-2V17H26v33Z"
|
||||
fill="#ffffff"
|
||||
className="color000000 svgShape"
|
||||
></path>
|
||||
</svg>
|
||||
) : name === "accordion-minus" ? (
|
||||
<svg
|
||||
width="20"
|
||||
@@ -478,13 +487,13 @@ export default function Icons({ name }) {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
@@ -510,20 +519,66 @@ export default function Icons({ name }) {
|
||||
) : name === "master-card" ? (
|
||||
<img className="w-[30px]" src={MasterCard} alt="card" />
|
||||
) : name === "new-dashboard" ? (
|
||||
<img className="w-[17px] h-[17px]" src={localImgLoad('images/icons/dashboard.svg')} alt="dashboard" />
|
||||
<img
|
||||
className="w-[17px] h-[17px]"
|
||||
src={localImgLoad("images/icons/dashboard.svg")}
|
||||
alt="dashboard"
|
||||
/>
|
||||
) : name === "new-family" ? (
|
||||
<img className="w-[17px] h-[17px]" src={localImgLoad('images/icons/family.svg')} alt="family" />
|
||||
<img
|
||||
className="w-[17px] h-[17px]"
|
||||
src={localImgLoad("images/icons/family.svg")}
|
||||
alt="family"
|
||||
/>
|
||||
) : name === "new-logout" ? (
|
||||
<img className="w-[45px] rounded-full bg-white p-1" src={localImgLoad('images/icons/logout.svg')} alt="logout" />
|
||||
<img
|
||||
className="w-[45px] rounded-full bg-white p-1"
|
||||
src={localImgLoad("images/icons/logout.svg")}
|
||||
alt="logout"
|
||||
/>
|
||||
) : name === "new-market" ? (
|
||||
<img className="w-[17px] h-[17px]" src={localImgLoad('images/icons/market.svg')} alt="market" />
|
||||
<img
|
||||
className="w-[17px] h-[17px]"
|
||||
src={localImgLoad("images/icons/market.svg")}
|
||||
alt="market"
|
||||
/>
|
||||
) : name === "new-mytask" ? (
|
||||
<img className="w-[17px] h-[17px]" src={localImgLoad('images/icons/my-task.svg')} alt="task" />
|
||||
<img
|
||||
className="w-[17px] h-[17px]"
|
||||
src={localImgLoad("images/icons/my-task.svg")}
|
||||
alt="task"
|
||||
/>
|
||||
) : name === "family-id" ? (
|
||||
<img className="w-[20px] h-[20px]" src={localImgLoad('images/icons/family-id.svg')} alt="family-id" />
|
||||
<img
|
||||
className="w-[20px] h-[20px]"
|
||||
src={localImgLoad("images/icons/family-id.svg")}
|
||||
alt="family-id"
|
||||
/>
|
||||
) : name === "family-pin" ? (
|
||||
<img className="w-[20px] h-[20px]" src={localImgLoad('images/icons/family-pin.svg')} alt="family-pin" />
|
||||
): (
|
||||
<img
|
||||
className="w-[20px] h-[20px]"
|
||||
src={localImgLoad("images/icons/family-pin.svg")}
|
||||
alt="family-pin"
|
||||
/>
|
||||
) : name === "pending-job" ? (
|
||||
<img
|
||||
className="w-[19px] h-[19px]"
|
||||
src={localImgLoad("images/icons/job_pending.svg")}
|
||||
alt="Pending Job"
|
||||
/>
|
||||
) : name === "active-job" ? (
|
||||
<img
|
||||
className="w-[17px] h-[17px]"
|
||||
src={localImgLoad("images/icons/job_active.svg")}
|
||||
alt="Active Job"
|
||||
/>
|
||||
) : name === "job-list" ? (
|
||||
<img
|
||||
className="w-[17px] h-[17px]"
|
||||
src={localImgLoad("images/icons/job_list.svg")}
|
||||
alt="Job List"
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function ModalCom({ action, children, situation, isOpen, classNam
|
||||
onClick={action || isOpen}
|
||||
className="fixed top-0 left-0 w-full lg:h-[100vh] h-full bg-black bg-opacity-40 backdrop-filter backdrop-blur-sm z-50"
|
||||
></div>
|
||||
<div className={`children-element fixed lg:h-100vh h-full z-[99999999999999] w-full lg:w-auto ${className}`}>
|
||||
<div className={`fixed lg:h-100vh h-full z-[99999999999999] w-full lg:w-auto inset-0 flex flex-col justify-center items-center ${className}`}>
|
||||
{children && children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import Image from '../../assets/images/taskbanners/default.jpg'
|
||||
|
||||
import usersService from '../../services/UsersService';
|
||||
import { handlePagingFunc } from '../../components/Pagination/HandlePagination';
|
||||
import PaginatedList from '../../components/Pagination/PaginatedList';
|
||||
|
||||
import LoadingSpinner from '../Spinners/LoadingSpinner';
|
||||
|
||||
import { AmountTo2DP } from '../Helpers/PriceFormatter';
|
||||
|
||||
function JobsCompleted() {
|
||||
|
||||
const apiCall = new usersService()
|
||||
|
||||
let [familyRewardHistory, setFamilyRewardHistory] = useState({ // FOR PURCHASE HISTORY
|
||||
loading: true,
|
||||
data: [],
|
||||
error: false
|
||||
})
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const indexOfFirstItem = Number(currentPage);
|
||||
const indexOfLastItem = Number(indexOfFirstItem)+Number(process.env.REACT_APP_ITEM_PER_PAGE);
|
||||
const currentReward = familyRewardHistory?.data?.slice(indexOfFirstItem, indexOfLastItem);
|
||||
|
||||
const handlePagination = (e) => {
|
||||
handlePagingFunc(e,setCurrentPage)
|
||||
}
|
||||
|
||||
|
||||
//FUNCTION TO GET FAMILY REWARD HISTORY
|
||||
const getJobCompletedHistory = ()=>{
|
||||
// apiCall.getFamilyRewardHx().then((res)=>{
|
||||
// if(res.data.internal_return < 0){ // success but no data
|
||||
// setFamilyRewardHistory(prev => ({...prev, loading: false}))
|
||||
// return
|
||||
// }
|
||||
// setFamilyRewardHistory(prev => ({...prev, loading: false, data: res.data.result_list}))
|
||||
// }).catch((error)=>{
|
||||
// setFamilyRewardHistory(prev => ({...prev, loading: false, error: true}))
|
||||
// })
|
||||
setTimeout(()=>{
|
||||
setFamilyRewardHistory(prev => ({...prev, loading: false, error:true}))
|
||||
},3000)
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getJobCompletedHistory()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col justify-between min-h-[500px]'>
|
||||
{familyRewardHistory.loading ?
|
||||
<LoadingSpinner size='16' color='sky-blue' height='h-[500px]' />
|
||||
: familyRewardHistory.data.length ?
|
||||
<table className="wallet-activity w-full table-auto border-collapse text-left">
|
||||
<thead className='border-b-2'>
|
||||
<tr className='text-slate-600'>
|
||||
<th className="p-2"></th>
|
||||
<th className="p-2">Amount</th>
|
||||
<th className="p-2">Date</th>
|
||||
<th className="p-2">Confirmation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{currentReward.map((item, index) => {
|
||||
let date = new Date(item.added).toLocaleDateString()
|
||||
return (
|
||||
<tr key={index} className='text-slate-500'>
|
||||
<td className="p-2">
|
||||
<div className='flex items-center gap-2'>
|
||||
<img src={item.icon} className='min-w-[60px] max-w-[60px] min-h-[60px] max-h-[60px] rounded-full bg-slate-500' alt='Reward Logo' />
|
||||
<div className='flex flex-col'>
|
||||
<h1 className='text-lg font-bold'>Reward to {item.rec_firstname} {item.rec_lastname}</h1>
|
||||
<p className='text-sm'>{item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-2">{AmountTo2DP(item.amount*0.01)} {item.currency}</td>
|
||||
<td className="p-2">{date}</td>
|
||||
<td className="p-2">{item.confirmation}</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
:familyRewardHistory.error ?
|
||||
<div className="p-2 text-slate-500 flex flex-col grow justify-center items-center">
|
||||
<span>Opps! an error occurred. Please try again!</span>
|
||||
</div>
|
||||
:
|
||||
<div className="p-2 text-slate-500 flex flex-col grow justify-center items-center">
|
||||
<span>No Completed Job History Found!</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* PAGINATION BUTTON */}
|
||||
<PaginatedList onClick={handlePagination} prev={currentPage == 0 ? true : false} next={currentPage+Number(process.env.REACT_APP_ITEM_PER_PAGE) >= familyRewardHistory?.data?.length ? true : false} data={familyRewardHistory?.data} start={indexOfFirstItem} stop={indexOfLastItem} />
|
||||
{/* END OF PAGINATION BUTTON */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default JobsCompleted
|
||||
@@ -11,6 +11,7 @@ import PurchasesTable from "../MyWallet/WalletComponent/PurchasesTable";
|
||||
import RecentActivityTable from "../MyWallet/WalletComponent/RecentActivityTable";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import RewardsTable from "./RewardsTable";
|
||||
import JobsCompleted from "./JobsCompleted";
|
||||
|
||||
export default function History() {
|
||||
|
||||
@@ -249,6 +250,15 @@ export default function History() {
|
||||
>
|
||||
Rewards
|
||||
</button>
|
||||
<button
|
||||
name="jobs_completed"
|
||||
onClick={(e) => setTab(e.target.name)}
|
||||
className={`px-4 py-1 rounded-t-2xl ${
|
||||
tab == "jobs_completed" ? "bg-[#4687ba] border-[2px] border-[#4687ba] text-white" : "bg-white text-[#000] border-t-[2px]"
|
||||
}`}
|
||||
>
|
||||
Jobs Completed
|
||||
</button>
|
||||
</div>
|
||||
{/* END OF switch button */}
|
||||
<div className="history-tables w-full">
|
||||
@@ -286,6 +296,14 @@ export default function History() {
|
||||
</div>
|
||||
}
|
||||
{/* END OF REWARD SECTION */}
|
||||
|
||||
{/* JOBS COMPLETED SECTION */}
|
||||
{tab == 'jobs_completed' &&
|
||||
<div className="wallet w-full border-t">
|
||||
<JobsCompleted />
|
||||
</div>
|
||||
}
|
||||
{/* END OF JOBS COMPLETED SECTION */}
|
||||
</div>
|
||||
</div>
|
||||
{/*<HistoryTable />*/}
|
||||
|
||||
@@ -4,7 +4,7 @@ import Icons from "../Helpers/Icons";
|
||||
import SliderCom from "../Helpers/SliderCom";
|
||||
import FamilyActiveJobsCard from "../Cards/FamilyActiveJobsCard";
|
||||
|
||||
export default function FamilyActiveLSlde({ className, trending }) {
|
||||
export default function FamilyActiveLSlde({ className, trending, image_server }) {
|
||||
const settings = {
|
||||
arrows: false,
|
||||
slidesToShow: 3,
|
||||
@@ -98,7 +98,7 @@ export default function FamilyActiveLSlde({ className, trending }) {
|
||||
{trending &&
|
||||
trending.length > 0 &&
|
||||
trending.map((item) => (
|
||||
<FamilyActiveJobsCard key={item.id} datas={item} />
|
||||
<FamilyActiveJobsCard key={item.id} datas={item} image_server={image_server} />
|
||||
))}
|
||||
</SliderCom>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function FamilyDash({ familyOffers, MyActiveJobList }) {
|
||||
/>
|
||||
)}
|
||||
{trending && trending.length > 0 && (
|
||||
<FamilyActiveLSlde trending={trending} className="mb-10" />
|
||||
<FamilyActiveLSlde trending={trending} className="mb-10" image_server={familyOffers?.session_image_server} />
|
||||
)}
|
||||
|
||||
{/*<TopSellerTopBuyerSliderSection className="mb-10" />*/}
|
||||
|
||||
@@ -34,6 +34,7 @@ export default function FullAccountDash(props) {
|
||||
<MyJobTable
|
||||
ActiveJobList={props.MyActiveJobList}
|
||||
Account={userDetails}
|
||||
imageServer={props.offersList?.data?.session_image_server}
|
||||
/>
|
||||
</>
|
||||
) : !props.offersList?.loading && !props.MyActiveJobList?.loading ? (
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import ModalCom from "../Helpers/ModalCom";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import InputCom from "../Helpers/Inputs/InputCom/index";
|
||||
|
||||
import usersService from "../../services/UsersService";
|
||||
import { apiConst } from "../../lib/apiConst";
|
||||
|
||||
export default function AddGroup({ action, situation, setUpdateList }) {
|
||||
const api = new usersService();
|
||||
|
||||
const [name, setName] = useState("");
|
||||
|
||||
const handleChange = ({ target: { name, value } }) => {
|
||||
setName(value);
|
||||
};
|
||||
|
||||
let [requestStatus, setRequestStatus] = useState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
}); // STATE FOR KNOWING WHEN A REQUEST IS MADE TO THE SERVER
|
||||
|
||||
const addGroup = () => {
|
||||
setRequestStatus({ loading: true, status: false, message: "" });
|
||||
if (name == "") {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Please enter a group name",
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 3000);
|
||||
}
|
||||
if (name.length < 6) {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Group name must be up to six characters",
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 3000);
|
||||
}
|
||||
api
|
||||
.jobGroupAdd({
|
||||
group_name: name,
|
||||
action: apiConst.WRENCHBOARD_JOB_OFFER_SYSTEM,
|
||||
})
|
||||
.then((response) => {
|
||||
let { status, data } = response;
|
||||
if (status != 200 || data.internal_return < 0) {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Unable to add Group",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: true,
|
||||
message: "Group Added",
|
||||
});
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
// close modal
|
||||
action();
|
||||
// reload group page
|
||||
setUpdateList((prev) => !prev);
|
||||
}, 3000);
|
||||
})
|
||||
.catch((error) => {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Unable to add Group, try again later",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalCom action={action} situation={situation}>
|
||||
<div className="logout-modal-wrapper lg:w-[500px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl">
|
||||
<div className="logout-modal-header w-full flex items-center justify-between lg:px-10 lg:py-8 px-[30px] py-[23px] border-b border-light-purple dark:border-[#5356fb29] ">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
Add Group
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="text-[#374557] dark:text-red-500"
|
||||
onClick={action}
|
||||
>
|
||||
<svg
|
||||
width="36"
|
||||
height="36"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
className="fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M36 16.16C36 17.4399 36 18.7199 36 20.0001C35.7911 20.0709 35.8636 20.2554 35.8385 20.4001C34.5321 27.9453 30.246 32.9248 22.9603 35.2822C21.9006 35.6251 20.7753 35.7657 19.6802 35.9997C18.4003 35.9997 17.1204 35.9997 15.8401 35.9997C15.5896 35.7086 15.2189 35.7732 14.9034 35.7093C7.77231 34.2621 3.08728 30.0725 0.769671 23.187C0.435002 22.1926 0.445997 21.1199 0 20.1599C0 18.7198 0 17.2798 0 15.8398C0.291376 15.6195 0.214408 15.2656 0.270759 14.9808C1.71321 7.69774 6.02611 2.99691 13.0428 0.700951C14.0118 0.383805 15.0509 0.386897 15.9999 0C17.2265 0 18.4532 0 19.6799 0C19.7156 0.124041 19.8125 0.136067 19.9225 0.146719C27.3 0.868973 33.5322 6.21922 35.3801 13.427C35.6121 14.3313 35.7945 15.2484 36 16.16ZM33.011 18.0787C33.0433 9.77105 26.3423 3.00309 18.077 2.9945C9.78479 2.98626 3.00344 9.658 2.98523 17.8426C2.96667 26.1633 9.58859 32.9601 17.7602 33.0079C26.197 33.0577 32.9787 26.4186 33.011 18.0787Z"
|
||||
fill=""
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
d="M15.9309 18.023C13.9329 16.037 12.007 14.1207 10.0787 12.2072C9.60071 11.733 9.26398 11.2162 9.51996 10.506C9.945 9.32677 11.1954 9.0811 12.1437 10.0174C13.9067 11.7585 15.6766 13.494 17.385 15.2879C17.9108 15.8401 18.1633 15.7487 18.6375 15.258C20.3586 13.4761 22.1199 11.7327 23.8822 9.99096C24.8175 9.06632 26.1095 9.33639 26.4967 10.517C26.7286 11.2241 26.3919 11.7413 25.9133 12.2178C24.1757 13.9472 22.4477 15.6855 20.7104 17.4148C20.5228 17.6018 20.2964 17.7495 20.0466 17.9485C22.0831 19.974 24.0372 21.8992 25.9689 23.8468C26.9262 24.8119 26.6489 26.1101 25.4336 26.4987C24.712 26.7292 24.2131 26.3441 23.7455 25.8757C21.9945 24.1227 20.2232 22.3892 18.5045 20.6049C18.0698 20.1534 17.8716 20.2269 17.4802 20.6282C15.732 22.4215 13.9493 24.1807 12.1777 25.951C11.7022 26.4262 11.193 26.7471 10.4738 26.4537C9.31345 25.9798 9.06881 24.8398 9.98589 23.8952C11.285 22.5576 12.6138 21.2484 13.9387 19.9355C14.5792 19.3005 15.2399 18.6852 15.9309 18.023Z"
|
||||
fill="#"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="logout-modal-body w-full flex flex-col items-center px-10 py-8">
|
||||
<div className="mb-6 w-full">
|
||||
<div className="input-item">
|
||||
<InputCom
|
||||
labelClass="tracking-wider"
|
||||
fieldClass="sm:px-6 px-2"
|
||||
value={name}
|
||||
placeholder="Group Name"
|
||||
label="Group Name"
|
||||
name="name"
|
||||
type="text"
|
||||
inputHandler={handleChange}
|
||||
// iconName="message"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex justify-between items-center gap-4">
|
||||
<button
|
||||
onClick={action}
|
||||
type="button"
|
||||
className="text-base text-light-red tracking-wide "
|
||||
>
|
||||
<span className="border-b dark:border-[#5356fb29] border-light-red">
|
||||
Cancel
|
||||
</span>
|
||||
</button>
|
||||
{requestStatus.loading ? (
|
||||
<LoadingSpinner size="8" color="sky-blue" />
|
||||
) : (
|
||||
<button
|
||||
onClick={() => addGroup()}
|
||||
type="button"
|
||||
className="w-[152px] h-[46px] flex justify-center items-center btn-gradient text-base rounded-full text-white"
|
||||
>
|
||||
Add Group
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ERROR DISPLAY AND SUBMIT BUTTON */}
|
||||
{requestStatus.message != "" &&
|
||||
(!requestStatus.status ? (
|
||||
<div
|
||||
className={`relative p-4 my-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
) : (
|
||||
requestStatus.status && (
|
||||
<div
|
||||
className={`relative p-4 my-4 text-green-700 bg-slate-200 border-slate-800 mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
{/* End of error or success display */}
|
||||
</div>
|
||||
</div>
|
||||
</ModalCom>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import ModalCom from '../Helpers/ModalCom'
|
||||
import LoadingSpinner from '../Spinners/LoadingSpinner'
|
||||
|
||||
export default function DeleteGroup({action, situation, details}) {
|
||||
|
||||
let [requestStatus, setRequestStatus] = useState({
|
||||
laoding: false,
|
||||
status: false,
|
||||
message: "",
|
||||
}); // STATE FOR KNOWING WHEN A REQUEST IS MADE TO THE SERVER
|
||||
|
||||
const deleteGroup = () => {
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalCom
|
||||
action={action}
|
||||
situation={situation}
|
||||
>
|
||||
<div className="logout-modal-wrapper lg:w-[500px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl">
|
||||
<div className="logout-modal-header w-full flex items-center justify-between lg:px-10 lg:py-8 px-[30px] py-[23px] border-b border-light-purple dark:border-[#5356fb29] ">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
Delete Group
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="text-[#374557] dark:text-red-500"
|
||||
onClick={action}
|
||||
>
|
||||
<svg
|
||||
width="36"
|
||||
height="36"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
className="fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M36 16.16C36 17.4399 36 18.7199 36 20.0001C35.7911 20.0709 35.8636 20.2554 35.8385 20.4001C34.5321 27.9453 30.246 32.9248 22.9603 35.2822C21.9006 35.6251 20.7753 35.7657 19.6802 35.9997C18.4003 35.9997 17.1204 35.9997 15.8401 35.9997C15.5896 35.7086 15.2189 35.7732 14.9034 35.7093C7.77231 34.2621 3.08728 30.0725 0.769671 23.187C0.435002 22.1926 0.445997 21.1199 0 20.1599C0 18.7198 0 17.2798 0 15.8398C0.291376 15.6195 0.214408 15.2656 0.270759 14.9808C1.71321 7.69774 6.02611 2.99691 13.0428 0.700951C14.0118 0.383805 15.0509 0.386897 15.9999 0C17.2265 0 18.4532 0 19.6799 0C19.7156 0.124041 19.8125 0.136067 19.9225 0.146719C27.3 0.868973 33.5322 6.21922 35.3801 13.427C35.6121 14.3313 35.7945 15.2484 36 16.16ZM33.011 18.0787C33.0433 9.77105 26.3423 3.00309 18.077 2.9945C9.78479 2.98626 3.00344 9.658 2.98523 17.8426C2.96667 26.1633 9.58859 32.9601 17.7602 33.0079C26.197 33.0577 32.9787 26.4186 33.011 18.0787Z"
|
||||
fill=""
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
d="M15.9309 18.023C13.9329 16.037 12.007 14.1207 10.0787 12.2072C9.60071 11.733 9.26398 11.2162 9.51996 10.506C9.945 9.32677 11.1954 9.0811 12.1437 10.0174C13.9067 11.7585 15.6766 13.494 17.385 15.2879C17.9108 15.8401 18.1633 15.7487 18.6375 15.258C20.3586 13.4761 22.1199 11.7327 23.8822 9.99096C24.8175 9.06632 26.1095 9.33639 26.4967 10.517C26.7286 11.2241 26.3919 11.7413 25.9133 12.2178C24.1757 13.9472 22.4477 15.6855 20.7104 17.4148C20.5228 17.6018 20.2964 17.7495 20.0466 17.9485C22.0831 19.974 24.0372 21.8992 25.9689 23.8468C26.9262 24.8119 26.6489 26.1101 25.4336 26.4987C24.712 26.7292 24.2131 26.3441 23.7455 25.8757C21.9945 24.1227 20.2232 22.3892 18.5045 20.6049C18.0698 20.1534 17.8716 20.2269 17.4802 20.6282C15.732 22.4215 13.9493 24.1807 12.1777 25.951C11.7022 26.4262 11.193 26.7471 10.4738 26.4537C9.31345 25.9798 9.06881 24.8398 9.98589 23.8952C11.285 22.5576 12.6138 21.2484 13.9387 19.9355C14.5792 19.3005 15.2399 18.6852 15.9309 18.023Z"
|
||||
fill="#"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="logout-modal-body w-full flex flex-col items-center px-10 py-8">
|
||||
<div className="what-icon mb-6 cursor-pointer">
|
||||
<svg
|
||||
width="136"
|
||||
height="136"
|
||||
viewBox="0 0 136 136"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="68" cy="68" r="68" fill="#4687ba" />
|
||||
<path
|
||||
d="M69.8844 35.7891C71.1588 36.0357 72.4569 36.1967 73.7044 36.5423C81.5447 38.7098 87.2705 45.5378 87.9574 53.6156C88.5113 60.1147 86.3075 65.6006 81.5043 70.0195C79.8359 71.5545 78.0497 72.9604 76.3408 74.4534C76.127 74.6397 75.9654 75.0037 75.9604 75.2872C75.9284 77.2752 75.9435 79.2649 75.9435 81.2965C70.8895 81.2965 65.8758 81.2965 60.7915 81.2965C60.7915 81.0616 60.7915 80.8385 60.7915 80.6137C60.7915 76.5454 60.7999 72.4772 60.7797 68.4106C60.778 67.9392 60.9312 67.649 61.2831 67.3537C64.5643 64.5957 67.8271 61.8175 71.1033 59.0545C72.2616 58.0781 72.9215 56.8702 72.9081 55.3419C72.8878 52.916 70.8608 50.9146 68.423 50.8911C65.9701 50.8693 63.9145 52.8053 63.832 55.2328C63.8084 55.8988 63.8286 56.5665 63.8286 57.2695C58.7745 57.2695 53.7744 57.2695 48.6917 57.2695C48.6917 56.3149 48.6462 55.3385 48.6984 54.3655C49.222 44.699 56.7442 36.8745 66.4331 35.8914C66.5762 35.8763 66.7142 35.8243 66.854 35.7891C67.8641 35.7891 68.8742 35.7891 69.8844 35.7891Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M67.485 100.21C66.1617 99.9268 64.9041 99.5091 63.803 98.6787C61.3804 96.8484 60.2877 93.7699 61.0386 90.7888C61.7726 87.8747 64.2138 85.6703 67.2089 85.2157C71.273 84.6 75.2024 87.3681 75.8135 91.277C76.4937 95.6153 73.8202 99.3782 69.544 100.103C69.4429 100.12 69.3487 100.172 69.2527 100.209C68.6635 100.21 68.0742 100.21 67.485 100.21Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<p className="text-xl text-center tracking-wide text-dark-gray dark:text-white">
|
||||
Are you sure, you want to delete <br /> <span>'{details?.group_name}'</span> group?
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-2.5">
|
||||
<button
|
||||
onClick={action}
|
||||
type="button"
|
||||
className=" border-gradient text-18 tracking-wide px-4 py-3 rounded-full"
|
||||
>
|
||||
<span className="text-gradient">Cancel</span>
|
||||
</button>
|
||||
{requestStatus.laoding ? (
|
||||
<LoadingSpinner size="8" color="sky-blue" />
|
||||
) : (
|
||||
<button
|
||||
onClick={() => deleteGroup()}
|
||||
type="button"
|
||||
className="text-white primary-gradient text-18 tracking-wide px-4 py-3 rounded-full"
|
||||
>
|
||||
Confirm Delete
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ERROR DISPLAY AND SUBMIT BUTTON */}
|
||||
{requestStatus.message != "" &&
|
||||
(!requestStatus.status ? (
|
||||
<div
|
||||
className={`relative p-4 my-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
) : (
|
||||
requestStatus.status && (
|
||||
<div
|
||||
className={`relative p-4 my-4 text-green-700 bg-slate-200 border-slate-800 mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
{/* End of error or success display */}
|
||||
</div>
|
||||
</div>
|
||||
</ModalCom>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import ModalCom from '../Helpers/ModalCom'
|
||||
import LoadingSpinner from '../Spinners/LoadingSpinner'
|
||||
|
||||
export default function DeleteMember({action, situation, details}) {
|
||||
|
||||
let [requestStatus, setRequestStatus] = useState({
|
||||
laoding: false,
|
||||
status: false,
|
||||
message: "",
|
||||
}); // STATE FOR KNOWING WHEN A REQUEST IS MADE TO THE SERVER
|
||||
|
||||
const deleteMember = () => {
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalCom
|
||||
action={action}
|
||||
situation={situation}
|
||||
>
|
||||
<div className="logout-modal-wrapper lg:w-[500px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl">
|
||||
<div className="logout-modal-header w-full flex items-center justify-between lg:px-10 lg:py-8 px-[30px] py-[23px] border-b border-light-purple dark:border-[#5356fb29] ">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
Remove Member
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="text-[#374557] dark:text-red-500"
|
||||
onClick={action}
|
||||
>
|
||||
<svg
|
||||
width="36"
|
||||
height="36"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
className="fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M36 16.16C36 17.4399 36 18.7199 36 20.0001C35.7911 20.0709 35.8636 20.2554 35.8385 20.4001C34.5321 27.9453 30.246 32.9248 22.9603 35.2822C21.9006 35.6251 20.7753 35.7657 19.6802 35.9997C18.4003 35.9997 17.1204 35.9997 15.8401 35.9997C15.5896 35.7086 15.2189 35.7732 14.9034 35.7093C7.77231 34.2621 3.08728 30.0725 0.769671 23.187C0.435002 22.1926 0.445997 21.1199 0 20.1599C0 18.7198 0 17.2798 0 15.8398C0.291376 15.6195 0.214408 15.2656 0.270759 14.9808C1.71321 7.69774 6.02611 2.99691 13.0428 0.700951C14.0118 0.383805 15.0509 0.386897 15.9999 0C17.2265 0 18.4532 0 19.6799 0C19.7156 0.124041 19.8125 0.136067 19.9225 0.146719C27.3 0.868973 33.5322 6.21922 35.3801 13.427C35.6121 14.3313 35.7945 15.2484 36 16.16ZM33.011 18.0787C33.0433 9.77105 26.3423 3.00309 18.077 2.9945C9.78479 2.98626 3.00344 9.658 2.98523 17.8426C2.96667 26.1633 9.58859 32.9601 17.7602 33.0079C26.197 33.0577 32.9787 26.4186 33.011 18.0787Z"
|
||||
fill=""
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
d="M15.9309 18.023C13.9329 16.037 12.007 14.1207 10.0787 12.2072C9.60071 11.733 9.26398 11.2162 9.51996 10.506C9.945 9.32677 11.1954 9.0811 12.1437 10.0174C13.9067 11.7585 15.6766 13.494 17.385 15.2879C17.9108 15.8401 18.1633 15.7487 18.6375 15.258C20.3586 13.4761 22.1199 11.7327 23.8822 9.99096C24.8175 9.06632 26.1095 9.33639 26.4967 10.517C26.7286 11.2241 26.3919 11.7413 25.9133 12.2178C24.1757 13.9472 22.4477 15.6855 20.7104 17.4148C20.5228 17.6018 20.2964 17.7495 20.0466 17.9485C22.0831 19.974 24.0372 21.8992 25.9689 23.8468C26.9262 24.8119 26.6489 26.1101 25.4336 26.4987C24.712 26.7292 24.2131 26.3441 23.7455 25.8757C21.9945 24.1227 20.2232 22.3892 18.5045 20.6049C18.0698 20.1534 17.8716 20.2269 17.4802 20.6282C15.732 22.4215 13.9493 24.1807 12.1777 25.951C11.7022 26.4262 11.193 26.7471 10.4738 26.4537C9.31345 25.9798 9.06881 24.8398 9.98589 23.8952C11.285 22.5576 12.6138 21.2484 13.9387 19.9355C14.5792 19.3005 15.2399 18.6852 15.9309 18.023Z"
|
||||
fill="#"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="logout-modal-body w-full flex flex-col items-center px-10 py-8">
|
||||
<div className="what-icon mb-6 cursor-pointer">
|
||||
<svg
|
||||
width="136"
|
||||
height="136"
|
||||
viewBox="0 0 136 136"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="68" cy="68" r="68" fill="#4687ba" />
|
||||
<path
|
||||
d="M69.8844 35.7891C71.1588 36.0357 72.4569 36.1967 73.7044 36.5423C81.5447 38.7098 87.2705 45.5378 87.9574 53.6156C88.5113 60.1147 86.3075 65.6006 81.5043 70.0195C79.8359 71.5545 78.0497 72.9604 76.3408 74.4534C76.127 74.6397 75.9654 75.0037 75.9604 75.2872C75.9284 77.2752 75.9435 79.2649 75.9435 81.2965C70.8895 81.2965 65.8758 81.2965 60.7915 81.2965C60.7915 81.0616 60.7915 80.8385 60.7915 80.6137C60.7915 76.5454 60.7999 72.4772 60.7797 68.4106C60.778 67.9392 60.9312 67.649 61.2831 67.3537C64.5643 64.5957 67.8271 61.8175 71.1033 59.0545C72.2616 58.0781 72.9215 56.8702 72.9081 55.3419C72.8878 52.916 70.8608 50.9146 68.423 50.8911C65.9701 50.8693 63.9145 52.8053 63.832 55.2328C63.8084 55.8988 63.8286 56.5665 63.8286 57.2695C58.7745 57.2695 53.7744 57.2695 48.6917 57.2695C48.6917 56.3149 48.6462 55.3385 48.6984 54.3655C49.222 44.699 56.7442 36.8745 66.4331 35.8914C66.5762 35.8763 66.7142 35.8243 66.854 35.7891C67.8641 35.7891 68.8742 35.7891 69.8844 35.7891Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M67.485 100.21C66.1617 99.9268 64.9041 99.5091 63.803 98.6787C61.3804 96.8484 60.2877 93.7699 61.0386 90.7888C61.7726 87.8747 64.2138 85.6703 67.2089 85.2157C71.273 84.6 75.2024 87.3681 75.8135 91.277C76.4937 95.6153 73.8202 99.3782 69.544 100.103C69.4429 100.12 69.3487 100.172 69.2527 100.209C68.6635 100.21 68.0742 100.21 67.485 100.21Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<p className="text-xl text-center tracking-wide text-dark-gray dark:text-white">
|
||||
Are you sure, you want to remove <br /> <span>'{details?.firstname} {details.lastname}'</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-2.5">
|
||||
<button
|
||||
onClick={action}
|
||||
type="button"
|
||||
className=" border-gradient text-18 tracking-wide px-4 py-3 rounded-full"
|
||||
>
|
||||
<span className="text-gradient">Cancel</span>
|
||||
</button>
|
||||
{requestStatus.laoding ? (
|
||||
<LoadingSpinner size="8" color="sky-blue" />
|
||||
) : (
|
||||
<button
|
||||
onClick={() => deleteMember()}
|
||||
type="button"
|
||||
className="text-white primary-gradient text-18 tracking-wide px-4 py-3 rounded-full"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ERROR DISPLAY AND SUBMIT BUTTON */}
|
||||
{requestStatus.message != "" &&
|
||||
(!requestStatus.status ? (
|
||||
<div
|
||||
className={`relative p-4 my-4 text-[#912741] bg-[#fcd9e2] border-[#fbc6d3] mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
) : (
|
||||
requestStatus.status && (
|
||||
<div
|
||||
className={`relative p-4 my-4 text-green-700 bg-slate-200 border-slate-800 mb-4 rounded-[0.475rem] text-md font-light leading-[19.5px] text-[13px]`}
|
||||
>
|
||||
{requestStatus.message}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
{/* End of error or success display */}
|
||||
</div>
|
||||
</div>
|
||||
</ModalCom>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import AddGroup from "./AddGroup";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
import { localImgLoad } from "../../lib";
|
||||
|
||||
export default function GroupList({
|
||||
groupList,
|
||||
selectedGroup,
|
||||
changeSelectedGroup,
|
||||
setUpdateList,
|
||||
}) {
|
||||
const [deletePopout, setDeletePopout] = useState({
|
||||
status: false,
|
||||
data: {},
|
||||
});
|
||||
|
||||
const [addGroupPopout, setAddGroupPopout] = useState(false);
|
||||
|
||||
const handleAddGroup = () => {
|
||||
setAddGroupPopout(true);
|
||||
};
|
||||
|
||||
const handleDeleteGroup = (item) => {
|
||||
setDeletePopout({
|
||||
status: true,
|
||||
data: { ...item },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-5 w-full lg:w-[400px] min-h-[300px] bg-sky-100 dark:bg-dark-gray rounded-2xl">
|
||||
{/* <h1 className='mb-5 text-lg lg:text-2xl tracking-wide font-bold text-slate-900 dark:text-slate-100'>Jobs Groups</h1> */}
|
||||
<div className="flex justify-end items-center">
|
||||
<button
|
||||
onClick={handleAddGroup}
|
||||
className="py-2 px-4 flex justify-center items-center bg-sky-blue hover:bg-sky-600 text-base rounded-full text-white font-bold"
|
||||
>
|
||||
Add Group
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{groupList && groupList.length < 1 ? (
|
||||
<h1 className="my-5 text-lg tracking-wide text-slate-900 dark:text-slate-100">
|
||||
No Group Found!
|
||||
</h1>
|
||||
) : (
|
||||
<div className="my-4 max-h-[400px] overflow-y-auto">
|
||||
<div className="flex flex-col">
|
||||
{groupList.map((item) => (
|
||||
<div
|
||||
key={item.group_uid}
|
||||
className="p-2 flex gap-2 items-center justify-between w-full"
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="grouplist"
|
||||
value={item.group_id}
|
||||
checked={selectedGroup?.id == item?.group_id}
|
||||
onChange={changeSelectedGroup}
|
||||
className="w-[20px] h-[20px] outline-none"
|
||||
/>
|
||||
<p className="text-sm lg:text-base text-slate-900 dark:text-slate-100">
|
||||
{item.group_name}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
handleDeleteGroup(item);
|
||||
}}
|
||||
className="flex relative items-center justify-center border-0 w-6 h-6"
|
||||
>
|
||||
<img
|
||||
src={localImgLoad("images/icons/remove_grp.svg")}
|
||||
alt="remove-icon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{deletePopout.status && (
|
||||
<DeleteGroup
|
||||
action={() => setDeletePopout({ status: false, data: {} })}
|
||||
situation={deletePopout.status}
|
||||
details={deletePopout.data}
|
||||
/>
|
||||
)}
|
||||
|
||||
{addGroupPopout && (
|
||||
<AddGroup
|
||||
action={() => setAddGroupPopout(false)}
|
||||
situation={addGroupPopout}
|
||||
setUpdateList={setUpdateList}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import React, { useState } from "react";
|
||||
import { handlePagingFunc } from "../Pagination/HandlePagination";
|
||||
import PaginatedList from "../Pagination/PaginatedList";
|
||||
import DeleteMember from "./DeleteMember";
|
||||
import { localImgLoad } from "../../lib";
|
||||
|
||||
export default function GroupMemberTable({ selectedList }) {
|
||||
// Handle Pagination
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const indexOfFirstItem = Number(currentPage);
|
||||
const indexOfLastItem =
|
||||
Number(indexOfFirstItem) + Number(process.env.REACT_APP_ITEM_PER_PAGE);
|
||||
|
||||
const currentSelectedList = selectedList?.slice(
|
||||
indexOfFirstItem,
|
||||
indexOfLastItem
|
||||
);
|
||||
|
||||
const handlePagination = (e) => {
|
||||
handlePagingFunc(e, setCurrentPage);
|
||||
};
|
||||
|
||||
const [deletePopout, setDeletePopout] = useState({
|
||||
status: false,
|
||||
data: {},
|
||||
});
|
||||
|
||||
const handleDeleteMember = (item) => {
|
||||
setDeletePopout({
|
||||
status: true,
|
||||
data: { ...item },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full p-8 bg-white dark:bg-dark-gray overflow-hidden rounded-2xl section-shadow`}
|
||||
>
|
||||
<div className="relative w-full overflow-x-auto sm:rounded-lg flex flex-col justify-between min-h-[400px]">
|
||||
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400">
|
||||
<tbody>
|
||||
<>
|
||||
{selectedList && selectedList?.length > 0 ? (
|
||||
currentSelectedList.map((value, index) => (
|
||||
<tr
|
||||
key={value.uid}
|
||||
className="bg-white dark:bg-dark-white border-b dark:border-[#5356fb29] hover:bg-gray-50"
|
||||
>
|
||||
{/* <td className="p-1">{value?.firstname}</td>
|
||||
<td className="p-1">{value?.lastname}</td>
|
||||
<td className="p-1">{value?.email}</td>
|
||||
<td className="p-1 text-right">
|
||||
<button onClick={()=>{handleDeleteMember(value)}} className='rounded-lg text-sm bg-red-500 hover:bg-red-400 text-white font-bold py-1 px-2.5'>X</button>
|
||||
</td> */}
|
||||
<td className="py-2">
|
||||
<div className="flex flex-col">
|
||||
<h1 className="font-bold text-xl text-dark-gray dark:text-white">
|
||||
{value.firstname && value.firstname}{" "}
|
||||
{value.lastname && value.lastname}
|
||||
</h1>
|
||||
<span className="text-sm text-thin-light-gray">
|
||||
{value.email && value.email}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-2 text-right">
|
||||
<button
|
||||
onClick={() => {
|
||||
handleDeleteMember(value);
|
||||
}}
|
||||
className="flex relative items-center justify-center border-0 w-8 h-8"
|
||||
>
|
||||
<img
|
||||
src={localImgLoad("images/icons/remove_grp.svg")}
|
||||
alt="remove-icon"
|
||||
/>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr className="font-bold text-xl text-dark-gray dark:text-white whitespace-nowrap">
|
||||
<td className="p-2">No Members Found</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* PAGINATION BUTTON */}
|
||||
<PaginatedList
|
||||
onClick={handlePagination}
|
||||
prev={currentPage == 0 ? true : false}
|
||||
next={
|
||||
currentPage + Number(process.env.REACT_APP_ITEM_PER_PAGE) >=
|
||||
selectedList?.length
|
||||
? true
|
||||
: false
|
||||
}
|
||||
data={selectedList}
|
||||
start={indexOfFirstItem}
|
||||
stop={indexOfLastItem}
|
||||
/>
|
||||
{/* END OF PAGINATION BUTTON */}
|
||||
</div>
|
||||
|
||||
{/* DELETE MEMBER POPOUT */}
|
||||
{deletePopout.status && (
|
||||
<DeleteMember
|
||||
action={() => setDeletePopout({ status: false, data: {} })}
|
||||
situation={deletePopout.status}
|
||||
details={deletePopout.data}
|
||||
/>
|
||||
)}
|
||||
{/* END OF DELETE MEMBER POPOUT */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Layout from "../Partials/Layout";
|
||||
|
||||
import GroupList from "./GroupList";
|
||||
import MemberList from "./MemberList";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
|
||||
import usersService from "../../services/UsersService";
|
||||
|
||||
export default function JobGroups() {
|
||||
const userApi = new usersService();
|
||||
|
||||
const [updateList, setUpdateList] = useState(false);
|
||||
|
||||
const [groupList, setGroupList] = useState({
|
||||
loading: true,
|
||||
groups: [],
|
||||
members: [],
|
||||
});
|
||||
|
||||
const [selectedGroup, setSelectedGroup] = useState({
|
||||
id: "",
|
||||
name: "",
|
||||
data: [],
|
||||
});
|
||||
|
||||
const changeSelectedGroup = (e) => {
|
||||
let groupID = e.target.value;
|
||||
const activeMembers = groupList?.members?.filter(
|
||||
(item) => item.group_id == groupID
|
||||
);
|
||||
const activeGroup = groupList?.groups?.filter(
|
||||
(item) => item.group_id == groupID
|
||||
);
|
||||
setSelectedGroup({
|
||||
id: groupID,
|
||||
name: activeGroup[0]?.group_name,
|
||||
data: activeMembers,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setGroupList({ loading: true, groups: [], members: [] });
|
||||
userApi
|
||||
.jobGroupList({})
|
||||
.then((res) => {
|
||||
const { status, data } = res;
|
||||
if (status != 200 || data?.internal_return < 0) {
|
||||
setGroupList({ loading: false, groups: [], members: [] });
|
||||
return;
|
||||
}
|
||||
if (data.result_list.length < 0) {
|
||||
setGroupList({ loading: false, groups: [], members: [] });
|
||||
return;
|
||||
}
|
||||
setGroupList({
|
||||
loading: false,
|
||||
groups: data.result_list,
|
||||
members: data.result_list_member,
|
||||
});
|
||||
if (selectedGroup.id == "") {
|
||||
let activeGroupId = data.result_list[0].group_id;
|
||||
let activeGroup = data.result_list[0].group_name;
|
||||
let activeMembers = data.result_list_member?.filter(
|
||||
(item) => item.group_id == activeGroupId
|
||||
);
|
||||
setSelectedGroup({
|
||||
id: activeGroupId,
|
||||
name: activeGroup,
|
||||
data: activeMembers,
|
||||
});
|
||||
} else {
|
||||
let activeMembers = data.result_list_member?.filter(
|
||||
(item) => item.group_id == selectedGroup?.id
|
||||
);
|
||||
setSelectedGroup({
|
||||
id: selectedGroup?.id,
|
||||
name: selectedGroup?.name,
|
||||
data: activeMembers,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setGroupList({ loading: false, groups: [], members: [] });
|
||||
console.log(error);
|
||||
});
|
||||
}, [updateList]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div>
|
||||
<h1 className="mb-5 text-lg lg:text-2xl tracking-wide font-bold text-slate-900 dark:text-slate-100">
|
||||
Jobs Groups
|
||||
</h1>
|
||||
</div>
|
||||
<div className="p-5 w-full min-h-[400px] flex flex-col lg:flex-row gap-3 lg:gap-6 rounded-lg shadow-md bg-white dark:bg-dark-white">
|
||||
{groupList.loading ? (
|
||||
<div className="w-full h-[400px] flex justify-center items-center">
|
||||
<LoadingSpinner size="16" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<GroupList
|
||||
groupList={groupList?.groups}
|
||||
selectedGroup={selectedGroup}
|
||||
changeSelectedGroup={changeSelectedGroup}
|
||||
setUpdateList={setUpdateList}
|
||||
/>
|
||||
<MemberList
|
||||
groupList={groupList?.groups}
|
||||
selectedGroup={selectedGroup}
|
||||
setUpdateList={setUpdateList}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import InputCom from "../../components/Helpers/Inputs/InputCom/index";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import GroupMemberTable from "./GroupMemberTable";
|
||||
|
||||
import EmailValidator from "../../lib/EmailValidator";
|
||||
|
||||
import { apiConst } from "../../lib/apiConst";
|
||||
import usersService from "../../services/UsersService";
|
||||
|
||||
export default function MemberList({
|
||||
groupList,
|
||||
selectedGroup,
|
||||
setUpdateList,
|
||||
}) {
|
||||
const api = new usersService();
|
||||
|
||||
const [fields, setFields] = useState({
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
email: "",
|
||||
});
|
||||
|
||||
const handleFieldsChange = ({ target: { name, value } }) => {
|
||||
setFields((prev) => ({ ...prev, [name]: value }));
|
||||
// let error = requestState?.errors?.indexOf(name) //// checks if the input field was in error array and removes it when the input changes
|
||||
// if(error >= 0){
|
||||
// let oldErrorArr = requestState.errors
|
||||
// let newErrorArr = oldErrorArr.splice(error, 1)
|
||||
// setRequestState(prev => ({...prev, errors:oldErrorArr}))
|
||||
// }
|
||||
|
||||
if (value == "") {
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
data: [],
|
||||
errors: [name],
|
||||
});
|
||||
} else {
|
||||
let error = requestState?.errors?.indexOf(name); //// checks if the input field was in error array and removes it when the input changes
|
||||
if (error >= 0) {
|
||||
let oldErrorArr = requestState.errors;
|
||||
let newErrorArr = oldErrorArr.splice(error, 1);
|
||||
setRequestState((prev) => ({ ...prev, errors: oldErrorArr }));
|
||||
}
|
||||
}
|
||||
|
||||
if (name == "email") {
|
||||
//checks if email is a valid email address
|
||||
let regEx = /^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/;
|
||||
if (!EmailValidator(value)) {
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
data: [],
|
||||
errors: ["email"],
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const [requestState, setRequestState] = useState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
data: [],
|
||||
errors: ["email", "firstname", "lastname"],
|
||||
});
|
||||
|
||||
const addMember = () => {
|
||||
// let errors = Object.keys(fields).filter((item) => { // CHECKS FOR EMPTY STRINGS
|
||||
// if(typeof item == 'string' && fields[item] === ''){
|
||||
// return item
|
||||
// }
|
||||
// })
|
||||
|
||||
// if(errors.length){
|
||||
// setRequestState({
|
||||
// loading: false,
|
||||
// status: false,
|
||||
// message: '',
|
||||
// data: [],
|
||||
// errors: [...errors]
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
//checks if email is a valid email address
|
||||
let regEx = /^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/;
|
||||
if (!EmailValidator(fields.email)) {
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Email is invalid",
|
||||
data: [],
|
||||
errors: [],
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
data: [],
|
||||
errors: [],
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
setRequestState({
|
||||
loading: true,
|
||||
status: false,
|
||||
message: "",
|
||||
data: [],
|
||||
errors: [],
|
||||
});
|
||||
const requestData = {
|
||||
firstname: fields.firstname,
|
||||
lastname: fields.lastname,
|
||||
email: fields.email,
|
||||
group_id: selectedGroup?.id,
|
||||
action: apiConst.WRENCHBOARD_JOB_GROUP_MEMBER,
|
||||
};
|
||||
|
||||
api
|
||||
.groupMemberAdd(requestData)
|
||||
.then((response) => {
|
||||
let { status, data } = response;
|
||||
if (status != 200 || data?.internal_return < 0) {
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Unable to add member",
|
||||
data: [],
|
||||
errors: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: true,
|
||||
message: "Member added",
|
||||
data: [],
|
||||
errors: [],
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
// trigger group page reload
|
||||
setUpdateList((prev) => !prev);
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
data: [],
|
||||
errors: [],
|
||||
});
|
||||
}, 3000);
|
||||
})
|
||||
.catch((error) => {
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Something went wrong, try again",
|
||||
data: [],
|
||||
errors: [],
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
setRequestState({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "",
|
||||
data: [],
|
||||
errors: [],
|
||||
});
|
||||
setFields({
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
email: "",
|
||||
});
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-5 w-full min-h-[600px] overflow-y-auto">
|
||||
{groupList && groupList.length < 1 ? (
|
||||
<>
|
||||
<h1 className="my-5 text-lg lg:text-xl tracking-wide text-slate-900 dark:text-slate-100">
|
||||
You Currently Do not have any Group, Please Add Group
|
||||
</h1>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h1 className="mb-5 text-lg lg:text-2xl tracking-wide font-bold text-slate-900 dark:text-slate-100">
|
||||
{selectedGroup?.name}
|
||||
</h1>
|
||||
<div className="w-full flex flex-col-reverse lg:flex-col">
|
||||
<div className="py-3 w-full">
|
||||
<div className="relative grid grid-cols-1 sm:grid-cols-2 gap-2 place-content-center">
|
||||
<div className="input-item">
|
||||
<InputCom
|
||||
labelClass="tracking-wider"
|
||||
fieldClass="sm:px-6 px-2"
|
||||
value={fields.firstname}
|
||||
inputHandler={handleFieldsChange}
|
||||
placeholder="First Name"
|
||||
// label="Firstname"
|
||||
name="firstname"
|
||||
type="text"
|
||||
// iconName="message"
|
||||
/>
|
||||
</div>
|
||||
<div className="input-item">
|
||||
<InputCom
|
||||
labelClass="tracking-wider"
|
||||
fieldClass="sm:px-6 px-2"
|
||||
value={fields.lastname}
|
||||
inputHandler={handleFieldsChange}
|
||||
placeholder="Last Name"
|
||||
// label="Lastname"
|
||||
name="lastname"
|
||||
type="text"
|
||||
// iconName="message"
|
||||
/>
|
||||
</div>
|
||||
<div className="input-item w-full sm:w-[150%]">
|
||||
<InputCom
|
||||
labelClass="tracking-wider"
|
||||
fieldClass="sm:px-6 px-2"
|
||||
value={fields.email}
|
||||
inputHandler={handleFieldsChange}
|
||||
placeholder="Email"
|
||||
// label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
// iconName="message"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end items-end">
|
||||
{requestState.loading ? (
|
||||
<LoadingSpinner size="8" color="sky-blue" />
|
||||
) : (
|
||||
<button
|
||||
onClick={addMember}
|
||||
disabled={
|
||||
requestState.loading ||
|
||||
requestState.status ||
|
||||
requestState.errors.length
|
||||
}
|
||||
className={`py-2 px-4 h-[42px] flex justify-center items-center text-base rounded-full text-white font-bold transition-all duration-500 ${
|
||||
requestState.loading ||
|
||||
requestState.status ||
|
||||
requestState.errors.length
|
||||
? "bg-sky-blue/50"
|
||||
: "bg-sky-blue hover:bg-sky-600"
|
||||
} `}
|
||||
>
|
||||
Add Member
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!requestState.loading && requestState.message && (
|
||||
<p
|
||||
className={`text-lg absolute -bottom-7 left-0 ${
|
||||
requestState.status ? "text-green-500" : "text-red-500"
|
||||
}`}
|
||||
>
|
||||
{requestState.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-2 flex flex-col min-h-[300px]">
|
||||
{selectedGroup?.data?.length < 1 ? (
|
||||
<h1 className="my-5 text-lg lg:text-xl tracking-wide text-slate-900 dark:text-slate-100">
|
||||
No Member Found, Please Add
|
||||
</h1>
|
||||
) : (
|
||||
<GroupMemberTable selectedList={selectedGroup?.data} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -117,8 +117,8 @@ const MarketPopUp = ({ details, onClose, situation, marketInt }) => {
|
||||
.replace(/&/g, "&");
|
||||
|
||||
return (
|
||||
<ModalCom action={onClose} situation={situation} className="edit-popup">
|
||||
<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">
|
||||
<ModalCom action={onClose} situation={situation}>
|
||||
<div className="logout-modal-wrapper w-11/12 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}
|
||||
|
||||
@@ -30,21 +30,26 @@ export default function ActiveJobMessage({ activeJobMesList }) {
|
||||
{activeJobMesList?.data?.length ?
|
||||
(
|
||||
<tbody>
|
||||
{activeJobMesList.data.map((item, index) => (
|
||||
<tr key={index} className='text-slate-500'>
|
||||
<td>
|
||||
<div className={`msg_box ${item.who}`}>
|
||||
<div className="msg_header">{item.msg_date} {item.msg_firstname}</div>
|
||||
{item.msg_type == 'FILE' ?
|
||||
<a href='' className="p-2" dangerouslySetInnerHTML={{__html: item.message}}></a>
|
||||
:
|
||||
<span className="p-2" dangerouslySetInnerHTML={{__html: item.message}}></span>
|
||||
}
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{activeJobMesList.data.map((item, index) =>
|
||||
{
|
||||
let imageLink = `${activeJobMesList?.image}${localStorage.getItem('session_token')}/contracts/${item.msg_uid}`
|
||||
return (
|
||||
<tr key={index} className='text-slate-500'>
|
||||
<td>
|
||||
<div className={`msg_box ${item.who}`}>
|
||||
<div className="msg_header">{item.msg_date} {item.msg_firstname}</div>
|
||||
{item.msg_type == 'FILE' ?
|
||||
<a href={imageLink} target="_blank" className="p-2" dangerouslySetInnerHTML={{__html: item.message}}></a>
|
||||
:
|
||||
<span className="p-2" dangerouslySetInnerHTML={{__html: item.message}}></span>
|
||||
}
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</tbody>
|
||||
)
|
||||
:
|
||||
|
||||
@@ -600,10 +600,10 @@ const PopModal = ({
|
||||
myRef,
|
||||
}) => {
|
||||
return (
|
||||
<ModalCom action={popUpHandler} situation={popUp} className="edit-popup">
|
||||
<ModalCom action={popUpHandler} situation={popUp}>
|
||||
<div
|
||||
ref={myRef}
|
||||
className="message-modal-wrapper min-w-[500px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl"
|
||||
className="message-modal-wrapper w-11/12 min-w-[350px] max-w-[700px] bg-white dark:bg-dark-white lg:rounded-2xl overflow-y-auto"
|
||||
>
|
||||
<div className="message-modal-header w-full flex items-center justify-between lg:px-10 lg:py-8 px-[30px] py-[23px] border-b dark:border-[#5356fb29] border-light-purple">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
|
||||
@@ -34,7 +34,7 @@ function PastDueJobAction({jobDetails}) {
|
||||
let reqData = { // API PAYLOADS
|
||||
contract: jobDetails.contract,
|
||||
contract_uid: jobDetails.contract_uid,
|
||||
job_action: 'REQUEST_CANCEL',
|
||||
job_action: 'CANCEL_CONTRACT',
|
||||
}
|
||||
|
||||
if(!checked){ // CHECKS IF CHECKBOX IS SELECTED
|
||||
|
||||
@@ -3,9 +3,12 @@ import ModalCom from '../../Helpers/ModalCom'
|
||||
import LoadingSpinner from '../../Spinners/LoadingSpinner'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import usersService from '../../../services/UsersService'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { tableReload } from '../../../store/TableReloads'
|
||||
|
||||
function ReviewJobAction({jobDetails}) {
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const apiCall = new usersService()
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -48,6 +51,7 @@ function ReviewJobAction({jobDetails}) {
|
||||
return
|
||||
}
|
||||
setReqStatus({loading:false, status: true, message: 'job completion accepted successfully'})
|
||||
dispatch(tableReload({ type: "WALLETTABLE" }));
|
||||
setTimeout(()=>{ // Sets popout to false and navigates user to /my-review-jobs after 3 seconds
|
||||
popUpHandler()
|
||||
navigate('/my-review-jobs', {replace: true})
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import localImgLoad from "../../lib/localImgLoad";
|
||||
import { PriceFormatter } from "../Helpers/PriceFormatter";
|
||||
import { handlePagingFunc } from "../Pagination/HandlePagination";
|
||||
import PaginatedList from "../Pagination/PaginatedList";
|
||||
|
||||
export default function MyActiveJobTable({ MyJobList, className }) {
|
||||
|
||||
const navigate = useNavigate();
|
||||
let { pathname } = useLocation();
|
||||
|
||||
@@ -45,7 +43,11 @@ export default function MyActiveJobTable({ MyJobList, className }) {
|
||||
value?.currency_code,
|
||||
value?.currency
|
||||
);
|
||||
let image = `${MyJobList.session_image_server}${localStorage.getItem('session_token')}/job/${value.job_uid}`
|
||||
let image = `${
|
||||
MyJobList.session_image_server
|
||||
}${localStorage.getItem("session_token")}/job/${
|
||||
value.job_uid
|
||||
}`;
|
||||
return (
|
||||
<tr
|
||||
key={index}
|
||||
@@ -112,7 +114,7 @@ export default function MyActiveJobTable({ MyJobList, className }) {
|
||||
className="px-4 h-11 flex justify-center items-center btn-gradient text-base rounded-full text-white"
|
||||
>
|
||||
{value.owner_status == "OWNER"
|
||||
? "Manage"
|
||||
? "Review"
|
||||
: "Send Updates"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function MyPastDueJobs(props) {
|
||||
<span
|
||||
className={`${selectTab === "today" ? "block" : "hidden"}`}
|
||||
>
|
||||
Pass Due Job(s)
|
||||
Past Due Job(s)
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -42,16 +42,16 @@ export default function MyJobTable({ MyJobList, reloadJobList, className }) {
|
||||
|
||||
// Get Country Api
|
||||
const getCountryList = useCallback(async () => {
|
||||
const res = await userApi.getSignupCountryData();
|
||||
|
||||
|
||||
try {
|
||||
if (res.status === 200) {
|
||||
const res = await userApi.getSignupCountryData();
|
||||
if (res.status === 200 && res.data.internal_return >= 0) {
|
||||
const {
|
||||
data: { signup_country },
|
||||
data: { result_list },
|
||||
} = await res;
|
||||
let checkCountry = signup_country
|
||||
?.filter((item) => item[0] == country)
|
||||
?.map((item, idx) => item[1])
|
||||
let checkCountry = result_list
|
||||
?.filter((item) => item.code == country)
|
||||
?.map((item) => item.country)
|
||||
.join("");
|
||||
setCountries(checkCountry);
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export default function MyJobTable({ MyJobList, reloadJobList, className }) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}, [userApi, country]);
|
||||
|
||||
console.log('MY COUNTRY', myCountry)
|
||||
useEffect(() => {
|
||||
getCountryList();
|
||||
}, [getCountryList]);
|
||||
@@ -220,7 +220,7 @@ export default function MyJobTable({ MyJobList, reloadJobList, className }) {
|
||||
contentBodyClasses="w-auto min-w-max"
|
||||
/>
|
||||
</div>
|
||||
{MyJobList.loading ? (
|
||||
{MyJobList?.loading ? (
|
||||
<LoadingSpinner size="16" color="sky-blue" />
|
||||
) : (
|
||||
<div className="relative w-full overflow-x-auto sm:rounded-lg flex flex-col justify-between min-h-[520px]">
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import usersService from "../../services/UsersService";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { apiConst } from "../../lib/apiConst";
|
||||
|
||||
function DeleteTaskModal({ details, onClose, situation, setReloadList }) {
|
||||
let dispatch = useDispatch();
|
||||
@@ -19,34 +19,50 @@ function DeleteTaskModal({ details, onClose, situation, setReloadList }) {
|
||||
|
||||
// FUNCTION TO DELETE TASK
|
||||
const deleteTask = () => {
|
||||
setRequestStatus({loading:true, status:false, message: ''})
|
||||
setRequestStatus({ loading: true, status: false, message: "" });
|
||||
|
||||
let reqData = { // REQUEST PAYLOAD
|
||||
let reqData = {
|
||||
// REQUEST PAYLOAD
|
||||
suggest_uid: details.uid,
|
||||
suggest_action: 555,
|
||||
offset: 0
|
||||
}
|
||||
ApiCall.suggestStatus(reqData).then((response)=>{ // API CALL TO DELETE SUGGESTED TASK
|
||||
let {data} = response
|
||||
if(data.internal_return < 0){
|
||||
setRequestStatus({loading:false, status:false, message: 'Unable to delete, Try again'})
|
||||
return setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message: ''})
|
||||
},3000)
|
||||
}
|
||||
setRequestStatus({loading:false, status:true, message: 'Family Suggest Deleted'})
|
||||
setReloadList(prev => !prev) // RELOADS THE FAMILY SUGGEST LIST TABLE
|
||||
setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message: ''})
|
||||
onClose()
|
||||
},3000)
|
||||
}).catch(error => {
|
||||
setRequestStatus({loading:false, status:false, message: 'Unable to delete, Try again'})
|
||||
setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message: ''})
|
||||
},3000)
|
||||
})
|
||||
}
|
||||
suggest_action: apiConst.WRENCHBOARD_COMPLETE_SENDMONEY_INTERSW,
|
||||
offset: 0,
|
||||
};
|
||||
ApiCall.suggestStatus(reqData)
|
||||
.then((response) => {
|
||||
// API CALL TO DELETE SUGGESTED TASK
|
||||
let { data } = response;
|
||||
if (data.internal_return < 0) {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Unable to delete, Try again",
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 3000);
|
||||
}
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: true,
|
||||
message: "Family Suggest Deleted",
|
||||
});
|
||||
setReloadList((prev) => !prev); // RELOADS THE FAMILY SUGGEST LIST TABLE
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
onClose();
|
||||
}, 3000);
|
||||
})
|
||||
.catch((error) => {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Unable to delete, Try again",
|
||||
});
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalCom action={onClose} situation={situation}>
|
||||
|
||||
@@ -6,9 +6,7 @@ import PendingJobsPopout from "../jobPopout/PendingJobsPopout";
|
||||
import { PriceFormatter } from "../Helpers/PriceFormatter";
|
||||
import localImgLoad from "../../lib/localImgLoad";
|
||||
|
||||
|
||||
export default function MyPendingJobTable({ MyJobList, className }) {
|
||||
|
||||
let [jobPopout, setJobPopout] = useState({ show: false, data: {} }); // STATE TO HOLD THE VALUE OF THE ALERT DETAILS AND DETERMINE WHEN TO SHOW
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
@@ -19,7 +17,7 @@ export default function MyPendingJobTable({ MyJobList, className }) {
|
||||
indexOfFirstItem,
|
||||
indexOfLastItem
|
||||
);
|
||||
|
||||
|
||||
const handlePagination = (e) => {
|
||||
handlePagingFunc(e, setCurrentPage);
|
||||
};
|
||||
@@ -46,7 +44,11 @@ export default function MyPendingJobTable({ MyJobList, className }) {
|
||||
value?.currency_code,
|
||||
value?.currency
|
||||
);
|
||||
let image = `${MyJobList.session_image_server}${localStorage.getItem('session_token')}/job/${value.job_uid}`
|
||||
let image = `${
|
||||
MyJobList.session_image_server
|
||||
}${localStorage.getItem("session_token")}/job/${
|
||||
value.job_uid
|
||||
}`;
|
||||
return (
|
||||
<tr
|
||||
key={index}
|
||||
@@ -107,7 +109,9 @@ export default function MyPendingJobTable({ MyJobList, className }) {
|
||||
}}
|
||||
className="px-4 h-11 flex justify-center items-center btn-gradient text-base rounded-full text-white"
|
||||
>
|
||||
{value.owner_status == 'OWNER' ? 'Manage' : 'Send Updates'}
|
||||
{value.owner_status == "OWNER"
|
||||
? "Manage"
|
||||
: "View"}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import usersService from "../../services/UsersService";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { apiConst } from "../../lib/apiConst";
|
||||
|
||||
function SendReminderModal({ details, onClose, situation, setReloadList }) {
|
||||
let dispatch = useDispatch();
|
||||
@@ -19,34 +19,50 @@ function SendReminderModal({ details, onClose, situation, setReloadList }) {
|
||||
|
||||
// FUNCTION TO SEND REMINDER
|
||||
const sendReminder = () => {
|
||||
setRequestStatus({loading:true, status:false, message: ''})
|
||||
setRequestStatus({ loading: true, status: false, message: "" });
|
||||
|
||||
let reqData = { // REQUEST PAYLOAD
|
||||
let reqData = {
|
||||
// REQUEST PAYLOAD
|
||||
suggest_uid: details.uid,
|
||||
suggest_action: 222,
|
||||
offset: 0
|
||||
}
|
||||
ApiCall.suggestStatus(reqData).then((response)=>{ // API CALL TO DELETE SUGGESTED TASK
|
||||
let {data} = response
|
||||
if(data.internal_return < 0){
|
||||
setRequestStatus({loading:false, status:false, message: 'Unable to send reminder, Try again1111'})
|
||||
return setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message: ''})
|
||||
},3000)
|
||||
}
|
||||
setRequestStatus({loading:false, status:true, message: 'Reminder Sent'})
|
||||
setReloadList(prev => !prev) // RELOADS THE FAMILY SUGGEST LIST TABLE
|
||||
setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message: ''})
|
||||
onClose()
|
||||
},3000)
|
||||
}).catch(error => {
|
||||
setRequestStatus({loading:false, status:false, message: 'Unable to send reminder, Try againNETWORK'})
|
||||
setTimeout(()=>{
|
||||
setRequestStatus({loading:false, status:false, message: ''})
|
||||
},3000)
|
||||
})
|
||||
}
|
||||
suggest_action: apiConst.OFFER_CANCEL,
|
||||
offset: 0,
|
||||
};
|
||||
ApiCall.suggestStatus(reqData)
|
||||
.then((response) => {
|
||||
// API CALL TO DELETE SUGGESTED TASK
|
||||
let { data } = response;
|
||||
if (data.internal_return < 0) {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Unable to send reminder, Try again1111",
|
||||
});
|
||||
return setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 3000);
|
||||
}
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: true,
|
||||
message: "Reminder Sent",
|
||||
});
|
||||
setReloadList((prev) => !prev); // RELOADS THE FAMILY SUGGEST LIST TABLE
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
onClose();
|
||||
}, 3000);
|
||||
})
|
||||
.catch((error) => {
|
||||
setRequestStatus({
|
||||
loading: false,
|
||||
status: false,
|
||||
message: "Unable to send reminder, Try againNETWORK",
|
||||
});
|
||||
setTimeout(() => {
|
||||
setRequestStatus({ loading: false, status: false, message: "" });
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalCom action={onClose} situation={situation}>
|
||||
@@ -82,27 +98,32 @@ function SendReminderModal({ details, onClose, situation, setReloadList }) {
|
||||
</button>
|
||||
</div>
|
||||
<div className="logout-modal-body w-full flex flex-col items-center p-8">
|
||||
|
||||
<div className="mb-6 w-full flex gap-4 items-center">
|
||||
<div className="mb-6 w-full flex gap-4 items-center">
|
||||
<div className="icon max-w-[150px] min-w-[150px] max-h-[150px] min-h-[150px] flex justify-center items-center">
|
||||
<img src={require(`../../assets/images/family/${details.banner || "default.jpg"}`)} alt="" className="w-full h-full" />
|
||||
<img
|
||||
src={require(`../../assets/images/family/${
|
||||
details.banner || "default.jpg"
|
||||
}`)}
|
||||
alt=""
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<p className="text-xl font-bold text-dark-gray dark:text-white mb-2 capitalize line-clamp-1">
|
||||
{details.title}
|
||||
</p>
|
||||
<p className="text-xl font-bold text-dark-gray dark:text-white mb-2 capitalize line-clamp-1">
|
||||
{details.title}
|
||||
</p>
|
||||
|
||||
<p className="text-sm mb-2 text-thin-light-gray font-medium">
|
||||
{details.description}
|
||||
</p>
|
||||
{
|
||||
details.remind &&
|
||||
<p className="text-sm mb-2 text-thin-light-gray font-medium">
|
||||
{details.description}
|
||||
</p>
|
||||
{details.remind && (
|
||||
<p className="text-xl font-bold text-dark-gray dark:text-white mb-2 capitalize line-clamp-1">
|
||||
Last Remind: {new Date(details.remind).toLocaleString().split(',')[0]}
|
||||
Last Remind:{" "}
|
||||
{new Date(details.remind).toLocaleString().split(",")[0]}
|
||||
</p>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex justify-end">
|
||||
{/* <button
|
||||
|
||||
@@ -10,7 +10,7 @@ import localImgLoad from "../../lib/localImgLoad";
|
||||
const noTasksBg = require("../../assets/images/no-task-background.jpg");
|
||||
const noFamilyTasksBg = require("../../assets/images/family-no-task-background.jpg");
|
||||
|
||||
export default function MyJobTable({ className, ActiveJobList, Account }) {
|
||||
export default function MyJobTable({ className, ActiveJobList, Account, imageServer }) {
|
||||
let navigate = useNavigate();
|
||||
let { pathname } = useLocation();
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function MyJobTable({ className, ActiveJobList, Account }) {
|
||||
{!ActiveJobList?.data.length && accountType && (
|
||||
<div className="absolute inset-0 bg-black opacity-30"></div>
|
||||
)}
|
||||
{ActiveJobList.loading ?
|
||||
{ActiveJobList?.loading ?
|
||||
<div className="w-full h-[520px] flex items-center justify-center">
|
||||
<LoadingSpinner size="16" color="sky-blue" />
|
||||
</div>
|
||||
@@ -71,6 +71,7 @@ export default function MyJobTable({ className, ActiveJobList, Account }) {
|
||||
task?.currency_code,
|
||||
task?.currency
|
||||
);
|
||||
let image = `${imageServer}${localStorage.getItem('session_token')}/job/${task.origin_job_uid}`
|
||||
return (
|
||||
<div
|
||||
className="bg-white dark:bg-dark-white border-b dark:border-[#5356fb29] w-full flex justify-between items-center hover:bg-gray-50"
|
||||
@@ -80,7 +81,7 @@ export default function MyJobTable({ className, ActiveJobList, Account }) {
|
||||
<div className="flex space-x-2 items-center">
|
||||
<div className="w-full min-w-[60px] max-w-[60px] flex-[0.1] h-[60px] rounded-full overflow-hidden flex justify-center items-center">
|
||||
<img
|
||||
src={localImgLoad(`images/taskbanners/${task?.banner}`)}
|
||||
src={image}
|
||||
alt="data"
|
||||
className="w-full h-full"
|
||||
/>
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function MyOffersTable({ className, MyActiveOffersList }) {
|
||||
MyActiveOffersList?.result_list?.length > 0 &&
|
||||
MyActiveOffersList.result_list.map((item) => {
|
||||
return (
|
||||
<OfferCard key={item.id} datas={item} setOfferPopout={setOfferPopout} />
|
||||
<OfferCard key={item.id} datas={item} setOfferPopout={setOfferPopout} image_server={MyActiveOffersList.session_image_server} />
|
||||
)
|
||||
})}
|
||||
</SliderCom>
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function MyTasks({
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<MyJobTable ActiveJobList={ActiveJobList} Account={userDetails} />
|
||||
<MyJobTable ActiveJobList={ActiveJobList} Account={userDetails} imageServer={MyActiveOffersList.session_image_server} />
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
@@ -64,17 +64,21 @@ const initialValues = {
|
||||
};
|
||||
|
||||
function AddFundDollars(props) {
|
||||
let MaxNoOfCards = process.env.REACT_APP_MAX_CREDIT_CARDS // HOLDS THE VALUE OF THE MAX NUMBER OF CARDS USER CAN ADD
|
||||
|
||||
let MaxNoOfCards = process.env.REACT_APP_MAX_CREDIT_CARDS; // HOLDS THE VALUE OF THE MAX NUMBER OF CARDS USER CAN ADD
|
||||
|
||||
const apiCall = new usersService();
|
||||
let countryWallet = props.walletItem.country;
|
||||
const [tab, setTab] = useState("previous");
|
||||
const [selectedOption, setSelectedOption] = useState("previous");
|
||||
const { userDetails } = useSelector((state) => state?.userDetails);
|
||||
const [prevCardDetails, setPrevCardDetails] = useState({});
|
||||
const [payListCards, setPayListCards] = useState({ loading: true, data: [] });
|
||||
const [cardIcons, setCardIcons] = useState("atm-card");
|
||||
const [prevCardError, setPrevCardError] = useState("");
|
||||
|
||||
const handleOptionChange = (event) => {
|
||||
setSelectedOption(event.target.value);
|
||||
};
|
||||
|
||||
const { firstname, lastname } = userDetails;
|
||||
|
||||
// Handling Card Icons
|
||||
@@ -144,7 +148,12 @@ function AddFundDollars(props) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab === "previous") {
|
||||
let stateData = {
|
||||
amount: Number(props.input) * 100,
|
||||
currency: props.walletItem?.code,
|
||||
};
|
||||
|
||||
if (selectedOption === "previous") {
|
||||
// To check if card is empty
|
||||
if (Object.keys(prevCardDetails).length === 0) {
|
||||
setPrevCardError("No card selected!");
|
||||
@@ -158,10 +167,26 @@ function AddFundDollars(props) {
|
||||
show: { awaitConfirm: { loader: true } },
|
||||
}));
|
||||
|
||||
let stateData = {
|
||||
amount: Number(props.input) * 100,
|
||||
currency: props.walletItem?.code,
|
||||
};
|
||||
// Extracting card_uid from the previous card details
|
||||
const paymentCardValue = prevCardDetails["payment-card"];
|
||||
|
||||
if (paymentCardValue) {
|
||||
try {
|
||||
const paymentCardObject = JSON.parse(paymentCardValue);
|
||||
stateData = {
|
||||
...stateData,
|
||||
card_uid: paymentCardObject.card_uid,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error parsing JSON:", error);
|
||||
}
|
||||
} else {
|
||||
// For the new card details
|
||||
stateData = {
|
||||
...stateData,
|
||||
card_uid: "",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await apiCall.getStartCredit(stateData);
|
||||
@@ -171,12 +196,11 @@ function AddFundDollars(props) {
|
||||
}
|
||||
|
||||
const _response = res.data;
|
||||
stateData.amount = Number(props.input);
|
||||
stateData.card =
|
||||
tab === "previous"
|
||||
selectedOption === "previous"
|
||||
? prevCardDetails["payment-card"]
|
||||
: { ...values, cvv: values.cvv };
|
||||
stateData.cardType = tab === "previous" ? "prev" : "new";
|
||||
stateData.cardType = selectedOption === "previous" ? "prev" : "new";
|
||||
stateData = { ...stateData, ..._response };
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -224,36 +248,45 @@ function AddFundDollars(props) {
|
||||
</h1>
|
||||
<div className="my-1 flex items-center gap-2">
|
||||
<label
|
||||
onClick={() => setTab("previous")}
|
||||
htmlFor="previous"
|
||||
className="cursor-pointer flex items-center gap-1"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id="previous"
|
||||
value="previous"
|
||||
name="card-option"
|
||||
checked={tab === "previous"}
|
||||
onChange={handleOptionChange}
|
||||
checked={selectedOption === "previous"}
|
||||
className={`p-2 text-lg font-bold text-slate-600 dark:text-white border pointer-events-none w-7 h-7 ${
|
||||
tab == "previous" ? "" : ""
|
||||
selectedOption == "previous" ? "" : ""
|
||||
} tracking-wide transition duration-200`}
|
||||
/>
|
||||
Previous Cards
|
||||
</label>
|
||||
<label
|
||||
onClick={() => setTab("new")}
|
||||
htmlFor="new"
|
||||
className={`cursor-pointer flex items-center gap-1 ${payListCards.data.length >= MaxNoOfCards ? 'pointer-events-none':''}`}
|
||||
className={`cursor-pointer flex items-center gap-1 ${
|
||||
payListCards.data.length >= MaxNoOfCards
|
||||
? "pointer-events-none"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
id="new"
|
||||
type="radio"
|
||||
name="card-option"
|
||||
checked={tab === "new"}
|
||||
value="new"
|
||||
onChange={handleOptionChange}
|
||||
checked={selectedOption === "new"}
|
||||
className={`p-2 text-lg font-bold text-slate-600 dark:text-white border pointer-events-none w-7 h-7 ${
|
||||
tab == "new" ? "" : ""
|
||||
selectedOption == "new" ? "" : ""
|
||||
} tracking-wide transition duration-200`}
|
||||
/>
|
||||
Add New Card {payListCards.data.length >= MaxNoOfCards && <span className="text-[14px] text-red-500">Max Reached</span>}
|
||||
Add New Card{" "}
|
||||
{payListCards.data.length >= MaxNoOfCards && (
|
||||
<span className="text-[14px] text-red-500">Max Reached</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
@@ -261,8 +294,8 @@ function AddFundDollars(props) {
|
||||
<hr />
|
||||
{/* END OF switch button */}
|
||||
|
||||
{/* previous tab */}
|
||||
{tab === "previous" && (
|
||||
{/* previous selectedOption */}
|
||||
{selectedOption === "previous" && (
|
||||
<div className="p-4 previous-details w-full min-h-[16.5rem] flex flex-col">
|
||||
{payListCards.loading ? (
|
||||
<LoadingSpinner size="10" color="sky-blue" />
|
||||
@@ -278,10 +311,11 @@ function AddFundDollars(props) {
|
||||
{currentPreviousCards.map((item, index) => (
|
||||
<option
|
||||
key={index}
|
||||
className={index !== 0 && "border-t-2"}
|
||||
className={index !== 0 ? "border-t-2" : undefined}
|
||||
value={JSON.stringify(item)}
|
||||
title={`${item.description} Card\nBank **************${item.digits}`}
|
||||
>
|
||||
<div className="my-2 flex items-center gap-5">
|
||||
{/* <div className="my-2 flex items-center gap-5">
|
||||
<div className="card-details">
|
||||
<h1 className="text-lg font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
{item.description} Card
|
||||
@@ -290,7 +324,8 @@ function AddFundDollars(props) {
|
||||
Bank **************{item.digits}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
{item.description} Card - Bank **************{item.digits}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -300,7 +335,7 @@ function AddFundDollars(props) {
|
||||
No Previous Card Found!
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setTab("new")}
|
||||
onClick={() => setSelectedOption("new")}
|
||||
type="button"
|
||||
className="my-5 px-2 py-1 h-11 flex justify-center items-center btn-gradient text-base rounded-full text-white"
|
||||
>
|
||||
@@ -314,13 +349,13 @@ function AddFundDollars(props) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === "new" && (
|
||||
{selectedOption === "new" && (
|
||||
<div className="new-details w-full max-h-[22rem]">
|
||||
{payListCards.loading ?
|
||||
{payListCards.loading ? (
|
||||
<div className="pt-10 flex w-full h-full justify-center items-center">
|
||||
<LoadingSpinner size='10' color='sky-blue' />
|
||||
<LoadingSpinner size="10" color="sky-blue" />
|
||||
</div>
|
||||
:payListCards.data.length < MaxNoOfCards ?
|
||||
) : payListCards.data.length < MaxNoOfCards ? (
|
||||
<div className="w-full flex flex-col justify-between">
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
@@ -379,7 +414,8 @@ function AddFundDollars(props) {
|
||||
*
|
||||
</span>
|
||||
<span className="text-[12px] text-red-500 ml-1">
|
||||
{props.errors.expirationMonth && "**"}
|
||||
{props.errors.expirationMonth &&
|
||||
"**"}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -431,7 +467,8 @@ function AddFundDollars(props) {
|
||||
*
|
||||
</span>
|
||||
<span className="text-[12px] text-red-500 italic">
|
||||
{props.errors.expirationYear && "**"}
|
||||
{props.errors.expirationYear &&
|
||||
"**"}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -560,14 +597,12 @@ function AddFundDollars(props) {
|
||||
}}
|
||||
</Formik>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{tab == "previous" && (
|
||||
{selectedOption == "previous" && (
|
||||
<div className="md:py-8 add-fund-btn flex justify-end items-center gap-2 py-4">
|
||||
<button
|
||||
className="px-4 py-1 h-11 max-w-[100px] w-full flex justify-center bg-[#f5a430] text-black items-center text-base rounded-full"
|
||||
|
||||
@@ -71,6 +71,7 @@ function AddFundPop({
|
||||
// Prepare state data for API call
|
||||
let stateData = {
|
||||
amount: Number(input) * 100,
|
||||
card_uid: "", //added card_uid as empty string
|
||||
currency: walletItem?.code,
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ function ThePaymentText({ value, type }) {
|
||||
* @returns {JSX.Element} - The rendered component.
|
||||
*/
|
||||
function AmountSection({ currency, amount, country }) {
|
||||
const formattedAmount = amount?.toFixed(2);
|
||||
const formattedAmount = (+amount * 0.01)?.toFixed(2);
|
||||
const gapClassName = country === "US" ? "gap-14" : "gap-4";
|
||||
|
||||
return (
|
||||
@@ -87,7 +87,7 @@ function TransactionFeeSection({ currency, fee, country }) {
|
||||
*/
|
||||
function TotalSection({ currency, amount, fee, country }) {
|
||||
const total = Number(amount) + Number(fee);
|
||||
const formattedTotal = total?.toFixed(2);
|
||||
const formattedTotal = (total * 0.01)?.toFixed(2);
|
||||
|
||||
const gap = country === "US" ? "gap-[8rem]" : "gap-[6.3rem]";
|
||||
|
||||
@@ -130,7 +130,7 @@ function ConfirmAddFund({
|
||||
public_key: __confirmData?.flutterwave_key,
|
||||
tx_ref: __confirmData?.credit_reference,
|
||||
currency: "NGN",
|
||||
amount: Number(__confirmData.amount),
|
||||
amount: Number(__confirmData.amount) * 0.01,
|
||||
payment_options: "card,mobilemoney,ussd",
|
||||
customer: {
|
||||
email: userDetails.email,
|
||||
@@ -224,7 +224,7 @@ function ConfirmAddFund({
|
||||
|
||||
// Create request data object with required parameters for making the payment
|
||||
const reqData = {
|
||||
amount: amount * 100,
|
||||
amount: amount,
|
||||
card_uid,
|
||||
credit_reference,
|
||||
currency,
|
||||
@@ -289,7 +289,7 @@ function ConfirmAddFund({
|
||||
|
||||
// Prepare request data
|
||||
const reqData = {
|
||||
amount: amount * 100,
|
||||
amount: amount,
|
||||
cardnumber: cardNum.replace(/\s/g, ""),
|
||||
credit_reference,
|
||||
cvc: cvv,
|
||||
|
||||
@@ -104,8 +104,8 @@ function ConfirmNairaWithdraw({
|
||||
};
|
||||
|
||||
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">
|
||||
<ModalCom action={action} situation={situation}>
|
||||
<div className="logout-modal-wrapper w-[90%] md:w-[768px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl overflow-y-auto">
|
||||
<div className="w-full">
|
||||
<div className="logout-modal-header w-full flex items-center justify-between lg:p-6 px-[30px] py-[23px] border-b dark:border-[#5356fb29] border-light-purple">
|
||||
<h2 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
@@ -170,9 +170,9 @@ function ConfirmNairaWithdraw({
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="green"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="feather feather-check-circle"
|
||||
>
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
||||
@@ -186,9 +186,9 @@ function ConfirmNairaWithdraw({
|
||||
width="100"
|
||||
height="100"
|
||||
stroke="red"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="feather feather-x-circle"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
|
||||
@@ -35,7 +35,7 @@ const CreditPopup = ({ details, onClose, situation, walletItem }) => {
|
||||
situation={situation}
|
||||
className="assign-task-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 overflow-y-auto">
|
||||
<div className="logout-modal-wrapper w-[90%] md:w-[768px] h-full lg:h-auto 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] border-b dark:border-[#5356fb29] border-light-purple">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
{confirmCredit?.show?.acceptConfirm?.loader
|
||||
|
||||
@@ -25,7 +25,7 @@ function NairaWithdraw({
|
||||
},
|
||||
newAccount: {
|
||||
country: wallet.walletCountry
|
||||
? wallet.walletCountry[0][0]
|
||||
? wallet.walletCountry[0]?.code
|
||||
: wallet.country,
|
||||
bank: state?.newAccount?.amount || "",
|
||||
accountNumber: state?.newAccount?.amount || "",
|
||||
@@ -34,7 +34,6 @@ function NairaWithdraw({
|
||||
city: state?.newAccount?.amount || "",
|
||||
},
|
||||
};
|
||||
|
||||
const [errorMsgs, setErrorMsgs] = useState(initialValues);
|
||||
|
||||
let [sendMoneyFee, setSendMoneyFee] = useState({
|
||||
@@ -69,7 +68,7 @@ function NairaWithdraw({
|
||||
// Handling card change
|
||||
const handleBankOptions = (event) => {
|
||||
let bankCountry = wallet.walletCountry
|
||||
? wallet.walletCountry[0][0]
|
||||
? wallet.walletCountry[0]?.code
|
||||
: wallet.country
|
||||
setBankName((prev) => ({ loading: true, data: [] }));
|
||||
apiCall
|
||||
@@ -120,7 +119,7 @@ function NairaWithdraw({
|
||||
}
|
||||
setAllCountries((prev) => ({
|
||||
loading: false,
|
||||
data: res.data.signup_country,
|
||||
data: res.data.result_list,
|
||||
}));
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -287,11 +286,11 @@ function NairaWithdraw({
|
||||
getRecipients();
|
||||
}, []);
|
||||
|
||||
console.log("Testing Wallet Country", wallet?.walletCountry[0][0]);
|
||||
console.log("Testing Wallet Country", wallet?.walletCountry[0]?.code);
|
||||
|
||||
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">
|
||||
<ModalCom action={action} situation={situation}>
|
||||
<div className="logout-modal-wrapper w-[90%] md:w-[768px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl overflow-y-auto">
|
||||
<div className="w-full">
|
||||
<div className="logout-modal-header w-full flex items-center justify-between lg:p-6 px-[30px] py-[23px] border-b dark:border-[#5356fb29] border-light-purple">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
@@ -560,7 +559,7 @@ function NairaWithdraw({
|
||||
</label>
|
||||
<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]}
|
||||
{wallet.walletCountry[0]?.country}
|
||||
</span>
|
||||
</div>
|
||||
{/* <select
|
||||
|
||||
@@ -41,13 +41,13 @@ const WalletRoutes = () => {
|
||||
apiCall
|
||||
.getSignupCountryData()
|
||||
.then((res) => {
|
||||
if (res.data.internal_return < 0) {
|
||||
if (res?.data?.internal_return < 0) {
|
||||
setAllCountries((prev) => ({ loading: false, data: [] }));
|
||||
return;
|
||||
}
|
||||
setAllCountries((prev) => ({
|
||||
loading: false,
|
||||
data: res.data.signup_country,
|
||||
data: res.data.result_list,
|
||||
}));
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import WalletItemCard from "./WalletItemCard";
|
||||
import WalletItemCardFamily from "./WalletItemCardFamily";
|
||||
|
||||
/**
|
||||
* Renders a list of wallet items or a loading spinner depending on the state of the `wallet` object.
|
||||
@@ -7,9 +9,27 @@ import WalletItemCard from "./WalletItemCard";
|
||||
export default function WalletBox({ wallet, payment, countries }) {
|
||||
const { loading, data } = wallet;
|
||||
|
||||
const { userDetails } = useSelector((state) => state.userDetails);
|
||||
const accountType = userDetails?.account_type === "FAMILY";
|
||||
|
||||
return (
|
||||
<div className="my-wallet-wrapper w-full mb-10">
|
||||
<div className="main-wrapper w-full">
|
||||
{accountType ?
|
||||
<div className="balance-inquery w-auto grid sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-[repeat(auto-fill,_minmax(354px,_1fr))] min-[1440px]:grid-cols-[repeat(auto-fill,_minmax(415px,_1fr))] gap-5 mb-11 h-auto">
|
||||
{loading ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<LoadingSpinner size="16" color="sky-blue" />
|
||||
</div>
|
||||
) : (
|
||||
data.length > 0 && data.map((item) => (
|
||||
<div key={item.wallet_uid} className="lg:w-full h-full mb-10 lg:mb-0">
|
||||
<WalletItemCardFamily walletItem={item} payment={payment} countries={countries} />
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
:
|
||||
<div className="balance-inquery w-auto grid sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-[repeat(auto-fill,_minmax(354px,_1fr))] min-[1440px]:grid-cols-[repeat(auto-fill,_minmax(415px,_1fr))] gap-5 mb-11 h-auto">
|
||||
{loading ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
@@ -23,6 +43,7 @@ export default function WalletBox({ wallet, payment, countries }) {
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,8 +11,7 @@ import WalletAction from "./WalletAction";
|
||||
* Renders a card displaying information about a wallet item.
|
||||
*/
|
||||
export default function WalletItemCard({ walletItem, payment, countries }) {
|
||||
const { userDetails } = useSelector((state) => state.userDetails);
|
||||
const accountType = userDetails?.account_type === "FAMILY";
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [creditPopup, setCreditPopup] = useState({ show: false, data: {} });
|
||||
|
||||
@@ -36,8 +35,8 @@ export default function WalletItemCard({ walletItem, payment, countries }) {
|
||||
};
|
||||
|
||||
const currentWalletCurrency = countries
|
||||
.map((country) => country)
|
||||
.filter((country) => country[0] === walletItem.country);
|
||||
// .map((country) => country)
|
||||
.filter((country) => country.code === walletItem.country);
|
||||
|
||||
const image = walletItem.code
|
||||
? `${walletItem.code.toLowerCase()}.svg`
|
||||
@@ -91,13 +90,11 @@ export default function WalletItemCard({ walletItem, payment, countries }) {
|
||||
|
||||
<div className="my-2 w-full h-[1px] bg-white"></div>
|
||||
|
||||
{!accountType && (
|
||||
<WalletAction
|
||||
walletItem={{ ...walletItem, walletCountry: currentWalletCurrency }}
|
||||
payment={payment}
|
||||
openPopUp={openPopUp}
|
||||
/>
|
||||
)}
|
||||
<WalletAction
|
||||
walletItem={{ ...walletItem, walletCountry: currentWalletCurrency }}
|
||||
payment={payment}
|
||||
openPopUp={openPopUp}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{creditPopup.show && (
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, { useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import background from "../../assets/images/bg-sky-blue.jpg"; //shape/balance-bg.svg";
|
||||
import localImgLoad from "../../lib/localImgLoad";
|
||||
import { tableReload } from "../../store/TableReloads";
|
||||
import { PriceFormatter } from "../Helpers/PriceFormatter";
|
||||
import CreditPopup from "./Popup/CreditPopup";
|
||||
import WalletAction from "./WalletAction";
|
||||
|
||||
/**
|
||||
* Renders a card displaying information about a wallet item.
|
||||
*/
|
||||
export default function WalletItemCardFamily({ walletItem, payment, countries }) {
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [creditPopup, setCreditPopup] = useState({ show: false, data: {} });
|
||||
|
||||
/**
|
||||
* Opens the credit popup.
|
||||
* @param {Object} value - The value object.
|
||||
*/
|
||||
const openPopUp = (value) => {
|
||||
setCreditPopup({
|
||||
show: true,
|
||||
data: { ...value },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the credit popup and dispatches a table reload action.
|
||||
*/
|
||||
const closePopUp = () => {
|
||||
setCreditPopup({ show: false, data: {} });
|
||||
dispatch(tableReload({ type: "WALLETTABLE" }));
|
||||
};
|
||||
|
||||
const currentWalletCurrency = countries
|
||||
// .map((country) => country)
|
||||
.filter((country) => country.code === walletItem.country);
|
||||
|
||||
const image = walletItem.code
|
||||
? `${walletItem.code.toLowerCase()}.svg`
|
||||
: "default.png";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="current-balance-widget w-full h-full rounded-2xl overflow-hidden flex flex-col items-center gap-2 p-8 justify-between"
|
||||
style={{
|
||||
background: `url(${background}) 0% 0% / cover no-repeat`,
|
||||
}}
|
||||
>
|
||||
<div className="wallet w-full flex justify-between items-center gap-3">
|
||||
<div className="min-w-[100px] min-h-[100px] max-w-min md:max-w-[150px] max-h-min md:max-h-[150px] rounded-full bg-[#e3e3e3] flex justify-center items-center">
|
||||
<img
|
||||
src={localImgLoad(`images/currency/${image}`)}
|
||||
className="w-full h-full"
|
||||
alt="currency-icon"
|
||||
/>
|
||||
</div>
|
||||
<div className="balance w-full mt-2 flex justify-center">
|
||||
<div className="">
|
||||
<p className="text-base sm:text-lg text-white opacity-[70%] tracking-wide mb-2 sm:mb-6">
|
||||
Current Balance
|
||||
</p>
|
||||
<p className="text-[44px] lg:text-[62px] font-bold text-white tracking-wide leading-10 xxs:scale-100 lg:scale-100 xl:scale-125">
|
||||
{PriceFormatter(
|
||||
walletItem.amount * 0.01,
|
||||
walletItem.code,
|
||||
undefined,
|
||||
"text-[2rem]"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-2 w-full h-[1px] bg-white"></div>
|
||||
|
||||
{/* <WalletAction
|
||||
walletItem={{ ...walletItem, walletCountry: currentWalletCurrency }}
|
||||
payment={payment}
|
||||
openPopUp={openPopUp}
|
||||
/> */}
|
||||
</div>
|
||||
|
||||
{creditPopup.show && (
|
||||
<CreditPopup
|
||||
details={creditPopup.data}
|
||||
walletItem={walletItem}
|
||||
onClose={closePopUp}
|
||||
situation={openPopUp}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,8 +6,11 @@ import CommonHead from "../UserHeader/CommonHead";
|
||||
import usersService from "../../services/UsersService";
|
||||
import LoadingSpinner from "../Spinners/LoadingSpinner";
|
||||
import OthersInterestedTable from "./OthersInterestedTable";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { tableReload } from "../../store/TableReloads";
|
||||
|
||||
export default function ManageInterestOffer(props) {
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
const apiCall = new usersService()
|
||||
|
||||
@@ -89,6 +92,7 @@ export default function ManageInterestOffer(props) {
|
||||
setRedirectTime(prev => prev - 1)
|
||||
}, 1000);
|
||||
setRequestStatus({loading: false, status: true, message: `Offer ${name}ed`, processType: ''})
|
||||
dispatch(tableReload({ type: "WALLETTABLE" }));
|
||||
setTimeout(()=>{
|
||||
navigate('/offer-interest', {replace: true})
|
||||
clearInterval(intervalTime)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import bank1 from "../../assets/images/bank-1.png";
|
||||
import bank2 from "../../assets/images/bank-2.png";
|
||||
@@ -37,11 +37,17 @@ export default function Header({ logoutModalHandler, sidebarHandler }) {
|
||||
const { walletDetails } = useSelector((state) => state?.walletDetails); // WALLET STORE
|
||||
|
||||
const image = localStorage.getItem("session_token")
|
||||
? `${userDetails.session_image_server}${localStorage.getItem(
|
||||
"session_token"
|
||||
)}/profile/${userDetails.uid}`
|
||||
? userDetails.account_type === "FAMILY"
|
||||
? `${userDetails.session_image_server}${localStorage.getItem(
|
||||
"session_token"
|
||||
)}/family/${sessionStorage.getItem("family_uid")}`
|
||||
: `${userDetails.session_image_server}${localStorage.getItem(
|
||||
"session_token"
|
||||
)}/profile/${userDetails.uid}`
|
||||
: "";
|
||||
|
||||
// 9308RDR122
|
||||
|
||||
const handlerBalance = () => {
|
||||
setbalanceValue.toggle();
|
||||
if (notificationDropdown) {
|
||||
@@ -232,6 +238,9 @@ export default function Header({ logoutModalHandler, sidebarHandler }) {
|
||||
</div>
|
||||
</button>
|
||||
{/* balance */}
|
||||
|
||||
{/* My Page Button */}
|
||||
<PageButton />
|
||||
|
||||
{/*<div className="lg:hidden block"></div>*/}
|
||||
<WalletHeader
|
||||
@@ -406,6 +415,17 @@ export default function Header({ logoutModalHandler, sidebarHandler }) {
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
{userDetails && userDetails?.account_type !== "FAMILY" && (
|
||||
<li className="content-item my-2 hover:bg-slate-100 transition duration-500 rounded-lg">
|
||||
<Link to="/yourpage" className="notifications">
|
||||
<div className="name">
|
||||
<p className="text-sm py-2 px-4 text-dark-gray dark:text-white hover:text-sky-blue transition font-medium">
|
||||
My Page
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{userDetails && userDetails?.account_type !== "FAMILY" && (
|
||||
<>
|
||||
<li className="content-item my-2 hover:bg-slate-100 transition duration-500 rounded-lg">
|
||||
@@ -650,3 +670,29 @@ export default function Header({ logoutModalHandler, sidebarHandler }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const PageButton = () => {
|
||||
return (
|
||||
<Link to="/yourpage" className="lg:flex hidden user-balance cursor-pointer lg:w-[152px] w-[150px] h-[48px] items-center rounded-full relative bg-sky-blue pr-1.5 pl-4">
|
||||
<div
|
||||
className="flex items-center lg:justify-between justify-center w-full h-full"
|
||||
>
|
||||
<span className="lg:block hidden w-[25px]">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 50" id="Page">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M0 13V2a2 2 0 0 1 2-2h64a2 2 0 0 1 2 2v11H0Zm0 4v31a2 2 0 0 0 2 2h20V17H0Zm26 33h40a2 2 0 0 0 2-2V17H26v33Z"
|
||||
fill="#ffffff"
|
||||
className="color000000 svgShape"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<p className="lg:text-xl text-lg font-bold text-white">My Page</p>
|
||||
<span className="lg:block hidden">
|
||||
{/* <Icons name="deep-plus" /> */}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export default function Layout({ children }) {
|
||||
localStorage.removeItem("session_token");
|
||||
localStorage.removeItem("member_id");
|
||||
localStorage.removeItem("uid");
|
||||
sessionStorage.removeItem("family_uid");
|
||||
// localStorage.clear();
|
||||
// toast.success("Come Back Soon", {
|
||||
// icon: `🙂`,
|
||||
@@ -78,7 +79,7 @@ export default function Layout({ children }) {
|
||||
{children && children}
|
||||
</div>
|
||||
<div className="nft-right-side-content 2xl:w-[270px] w-full h-full 2xl:flex justify-center relative flex-[20%]">
|
||||
<RightSideBar />
|
||||
<RightSideBar myJobList={userJobList} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,7 +87,7 @@ export default function Layout({ children }) {
|
||||
</div>
|
||||
{logoutModal && (
|
||||
<ModalCom action={logoutModalHandler} situation={logoutModal}>
|
||||
<div className="logout-modal-wrapper lg:w-[460px] h-full lg:h-auto bg-white dark:bg-dark-white lg:rounded-2xl">
|
||||
<div className="logout-modal-wrapper w-11/12 sm:w-[460px] bg-white dark:bg-dark-white lg:rounded-2xl">
|
||||
<div className="logout-modal-header w-full flex items-center justify-between lg:px-10 lg:py-8 px-[30px] py-[23px] border-b dark:border-[#5356fb29] border-light-purple dark:border-[#5356fb29] ">
|
||||
<h1 className="text-26 font-bold text-dark-gray dark:text-white tracking-wide">
|
||||
Confirm
|
||||
|
||||
@@ -190,16 +190,16 @@ export default function MobileSidebar({
|
||||
<div className="items">
|
||||
<ul className="flex flex-col space-y-6">
|
||||
{[
|
||||
{ name: "List", path: "/myjobs", iconName: "people-two" },
|
||||
{ name: "List", path: "/myjobs", iconName: "job-list" },
|
||||
{
|
||||
name: "Pending",
|
||||
path: "/my-pending-jobs",
|
||||
iconName: "people-two",
|
||||
iconName: "pending-job",
|
||||
},
|
||||
{
|
||||
name: "Active",
|
||||
path: "/my-active-jobs",
|
||||
iconName: "people-two",
|
||||
iconName: "active-job",
|
||||
},
|
||||
].map(({ name, path, iconName }, idx) => (
|
||||
<ListItem
|
||||
|
||||
@@ -2,8 +2,9 @@ import React, { useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { NavLink } from "react-router-dom";
|
||||
//import SideStatistics from "./SideStatistics";
|
||||
import { localImgLoad } from "../../lib";
|
||||
|
||||
export default function RightSideBar() {
|
||||
export default function RightSideBar({myJobList}) {
|
||||
const filterDatas = ["Last 15 days", "Last Month", "Last 6 month"];
|
||||
const [filterDataSet, setFilterDataSet] = useState([10, 30, 20, 40]);
|
||||
const dataSetHandler = (value) => {
|
||||
@@ -154,7 +155,77 @@ export default function RightSideBar() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/*<SideStatistics />*/}
|
||||
|
||||
{/*JOB LINKS*/}
|
||||
{myJobList?.data?.result_list?.length > 0 &&
|
||||
<div className="top-platform mt-6 bg-white dark:bg-dark-white rounded-2xl py-8 2xl:w-[268px] w-full 2xl:mb-10 2xl:border-none border ">
|
||||
{/* heading */}
|
||||
<div className="px-8 heading flex justify-between items-center mb-3.5">
|
||||
<h3 className="text-xl font-bold text-dark-gray dark:text-white">
|
||||
Job Links
|
||||
</h3>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div className="platform-list">
|
||||
<div className="px-8 item flex space-x-3 items-center mb-4">
|
||||
{/* image */}
|
||||
<div className="w-8 h-8 p-[4px] rounded-full">
|
||||
<img src={localImgLoad('images/icons/job_active.svg')} className="w-full h-full" alt='Active Task' />
|
||||
</div>
|
||||
{/* name */}
|
||||
<div>
|
||||
<p className="text-thin-light-gray text-base font-medium">
|
||||
<NavLink to="/my-active-jobs">Active Task</NavLink>
|
||||
</p>
|
||||
</div>
|
||||
{/* action */}
|
||||
</div>
|
||||
|
||||
<div className="px-8 item flex space-x-3 items-center mb-4">
|
||||
{/* image */}
|
||||
<div className="w-8 h-8 p-[4px] rounded-full">
|
||||
<img src={localImgLoad('images/icons/job_pending.svg')} className="w-full h-full" alt='Review Task' />
|
||||
</div>
|
||||
{/* name */}
|
||||
<div>
|
||||
<p className="text-thin-light-gray text-base font-medium">
|
||||
<NavLink to="/my-review-jobs">Review Pending</NavLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-8 item flex space-x-3 items-center mb-4">
|
||||
{/* image */}
|
||||
<div className="w-8 h-8 p-[4px] rounded-full">
|
||||
<img src={localImgLoad('images/icons/job_past_due.svg')} className="w-full h-full" alt='Past Due Task' />
|
||||
</div>
|
||||
{/* name */}
|
||||
<div>
|
||||
<p className="text-thin-light-gray text-base font-medium">
|
||||
<NavLink to="/my-pastdue-jobs">Past Due</NavLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Line */}
|
||||
<div className="my-4 mx-auto w-10/12 h-[2px] bg-slate-500 dark:bg-white rounded-full"></div>
|
||||
|
||||
<div className="px-8 item flex space-x-3 items-center mb-4">
|
||||
{/* image */}
|
||||
<div className="w-8 h-8 p-[4px] rounded-full">
|
||||
<img src={localImgLoad('images/icons/job_group.svg')} className="w-full h-full" alt='Job Groups' />
|
||||
</div>
|
||||
{/* name */}
|
||||
<div>
|
||||
<p className="text-thin-light-gray text-base font-medium">
|
||||
<NavLink to="/job-groups">Job Groups</NavLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -215,17 +215,17 @@ export default function Sidebar({
|
||||
{
|
||||
name: "List",
|
||||
path: "/myjobs",
|
||||
iconName: "people-two",
|
||||
iconName: "job-list",
|
||||
},
|
||||
{
|
||||
name: "Pending",
|
||||
path: "/my-pending-jobs",
|
||||
iconName: "people-two",
|
||||
iconName: "pending-job",
|
||||
},
|
||||
{
|
||||
name: "Active",
|
||||
path: "/my-active-jobs",
|
||||
iconName: "people-two",
|
||||
iconName: "active-job",
|
||||
},
|
||||
].map(({ name, path, iconName }, idx) => (
|
||||
<ListItem
|
||||
|
||||
@@ -63,6 +63,7 @@ export default function Resources(props) {
|
||||
loading: false,
|
||||
msg: "success",
|
||||
data: res?.result_list,
|
||||
image:res?.session_image_server
|
||||
}));
|
||||
} catch (error) {
|
||||
setUploadedFiles((prev) => ({
|
||||
|
||||
@@ -13,7 +13,6 @@ export default function MyUploadedFiles({ uploadedFiles }) {
|
||||
indexOfFirstItem,
|
||||
indexOfLastItem
|
||||
);
|
||||
|
||||
const handlePagination = (e) => {
|
||||
handlePagingFunc(e, setCurrentPage);
|
||||
};
|
||||
@@ -45,10 +44,11 @@ export default function MyUploadedFiles({ uploadedFiles }) {
|
||||
currentFiles.map((value, idx) => {
|
||||
let addedDate = value?.added?.split(" ")[0];
|
||||
let formattedSize = formatFileSize(value?.file_size);
|
||||
let imageLink = `${uploadedFiles?.image}${localStorage.getItem('session_token')}/myfile/${value.file_uid}`
|
||||
return (
|
||||
<tr
|
||||
key={value?.file_uid}
|
||||
className="bg-white dark:bg-dark-white border-b dark:border-[#5356fb29] hover:bg-gray-50"
|
||||
className="bg-white dark:bg-dark-white border-b dark:border-[#5356fb29] hover:bg-gray-50"
|
||||
>
|
||||
<td className=" py-4">
|
||||
<div className="flex space-x-2 items-center w-full">
|
||||
@@ -102,17 +102,17 @@ export default function MyUploadedFiles({ uploadedFiles }) {
|
||||
</td>
|
||||
<td className="text-right py-4 px-2">
|
||||
<div className="flex justify-center items-center">
|
||||
<button
|
||||
type="button"
|
||||
// onClick={() => {
|
||||
// navigate("/manage-active-job", {
|
||||
// state: { ...value, pathname },
|
||||
// });
|
||||
// }}
|
||||
className="w-20 h-11 flex justify-center items-center btn-gradient text-base rounded-full text-white"
|
||||
<a
|
||||
href={imageLink}
|
||||
title="download"
|
||||
// className="w-20 h-11 flex justify-center items-center btn-gradient text-base rounded-full text-white"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
<img
|
||||
src={localImgLoad('images/icons/download-arrow.svg')}
|
||||
alt='download-link'
|
||||
className="w-auto h-6 flex justify-center items-center"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -11,6 +11,7 @@ import profileImage from "../../../assets/images/profile.jpg";
|
||||
|
||||
import { Form, Formik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { apiConst } from "../../../lib/apiConst";
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
@@ -147,7 +148,7 @@ export default function PersonalInfoTab({
|
||||
file_type: uploadedFile?.type?.split("/")[0]?.toLowerCase(),
|
||||
file_data: base64Img,
|
||||
msg_type: "FILE",
|
||||
action: 11300,
|
||||
action: apiConst.WRENCHBOARD_PICTURE_PROFILE,
|
||||
};
|
||||
|
||||
setUploadStatus({
|
||||
|
||||