diff options
| -rw-r--r-- | jb-ui/src/JBApp.tsx | 69 | ||||
| -rw-r--r-- | jb-ui/src/components/Redemption.tsx | 67 | ||||
| -rw-r--r-- | jb-ui/src/components/Wallet.tsx | 20 | ||||
| -rw-r--r-- | jb-ui/src/models/app.ts | 8 | ||||
| -rw-r--r-- | jb-ui/src/models/appSlice.ts | 30 | ||||
| -rw-r--r-- | jb-ui/src/models/cashoutMethodsSlice.ts | 37 | ||||
| -rw-r--r-- | jb-ui/src/pages/Preview.tsx | 8 | ||||
| -rw-r--r-- | jb-ui/src/pages/Result.tsx | 18 | ||||
| -rw-r--r-- | jb-ui/src/pages/Work.tsx | 33 | ||||
| -rw-r--r-- | jb-ui/src/store.ts | 2 |
10 files changed, 239 insertions, 53 deletions
diff --git a/jb-ui/src/JBApp.tsx b/jb-ui/src/JBApp.tsx index 6c31164..4870e9b 100644 --- a/jb-ui/src/JBApp.tsx +++ b/jb-ui/src/JBApp.tsx @@ -11,7 +11,11 @@ import { TaskStatusResponse, UserWalletBalance, UserWalletBalanceResponse, + UserLedgerWallets, + UserLedgerWalletAccountTypeEnum, + UserDisplayedWalletBalance, WalletApi, + CashoutMethodsResponse, } from "@/api_fsb"; import Footer from "@/components/Footer"; import { useAppDispatch } from "@/hooks"; @@ -20,8 +24,13 @@ import { useGetMeQuery } from "@/models/authAPI"; import { setAssignmentID, setProductUserID, - setTurkSubmitTo, + setUserDisplayedWalletBalance, + setUserWallet, + setUserAttemptCreditWallet, } from "@/models/appSlice"; + +import { addCashoutMethods } from "@/models/cashoutMethodsSlice"; + import { addEvent } from "@/models/grlEventsSlice"; import { addStatsData } from "@/models/grlStatsSlice"; import Home from "@/pages/Home"; @@ -50,6 +59,7 @@ import Wallet from "@/components/Wallet"; import "@/index.css"; import { setTaskStatus, setUserWalletBalance } from "@/models/appSlice"; import { Profiling } from "./components/Profiling"; +import Redemption from "./components/Redemption"; type Message = | PingMessage @@ -76,18 +86,17 @@ function QueryParamProcessor() { const { data: user, isLoading, isError } = useGetMeQuery(); useEffect(() => { - const assignment_id = searchParams.get("assignmentId"); - const tsid = searchParams.get("tsid"); - const worker_id = searchParams.get("workerId"); - - if (worker_id) { - dispatch(setProductUserID(worker_id)); - } - if (user?.product_user_id) { dispatch(setProductUserID(user.product_user_id)); new WalletApi() + .getCashoutMethodsProductIdCashoutMethodsGet(bpid, user.product_user_id) + .then((res) => { + const response = res.data as CashoutMethodsResponse; + dispatch(addCashoutMethods(response.cashout_methods)); + }); + + new WalletApi() .getUserWalletBalanceProductIdWalletGet( bpid, // productId user.product_user_id, @@ -97,6 +106,43 @@ function QueryParamProcessor() { const balance = response.wallet as UserWalletBalance; dispatch(setUserWalletBalance(balance)); }); + + new WalletApi() + .getUserWalletsProductIdWalletsGet(bpid, user.product_user_id) + .then((res) => { + const response = res.data as UserLedgerWallets; + + if (response.displayed_balances) { + setUserDisplayedWalletBalance(response.displayed_balances[0]); + } + + const userWallet = response.wallets?.find( + (w) => + w.account_type === UserLedgerWalletAccountTypeEnum.UserWallet, + ); + if (userWallet) { + setUserWallet(userWallet); + } + + const userAttemptCreditWallet = response.wallets?.find( + (w) => + w.account_type === + UserLedgerWalletAccountTypeEnum.UserAttemptCredit, + ); + if (userAttemptCreditWallet) { + setUserAttemptCreditWallet(userAttemptCreditWallet); + } + }); + } + }, [user]); + + useEffect(() => { + const assignment_id = searchParams.get("assignmentId"); + const tsid = searchParams.get("tsid"); + const worker_id = searchParams.get("workerId"); + + if (worker_id) { + dispatch(setProductUserID(worker_id)); } if (assignment_id) { @@ -125,8 +171,8 @@ function QueryParamProcessor() { const Layout = () => { const location = useLocation(); - const isWork = useMatch("/work*"); - const isPreview = useMatch("/preview*"); + const isWork = useMatch("/work/*"); + const isPreview = useMatch("/preview/*"); const isSpecialSubview = Boolean(isWork || isPreview); return ( @@ -152,6 +198,7 @@ const Layout = () => { > <Wallet /> <Profiling /> + <Redemption /> <Outlet /> </div> </div> diff --git a/jb-ui/src/components/Redemption.tsx b/jb-ui/src/components/Redemption.tsx new file mode 100644 index 0000000..0a91ab3 --- /dev/null +++ b/jb-ui/src/components/Redemption.tsx @@ -0,0 +1,67 @@ +import { clsx } from "clsx"; +import { useAppDispatch, useAppSelector } from "@/hooks"; +import { UpkQuestionChoice } from "@/api_fsb"; +import { useGetMeQuery } from "@/models/authAPI"; +import { ChevronRightIcon } from "lucide-react"; +import { KeyboardEvent, MouseEvent, useEffect, useRef, useState } from "react"; +import { useSelector } from "react-redux"; +import { cashoutMethodsSelectors } from "@/models/cashoutMethodsSlice"; + +const RedemptionHeader = () => { + const cashout_methods = useSelector(cashoutMethodsSelectors.selectAll); + + return ( + <> + <h2 className="text-xs font-mono font-bold p-0 m-0"> + {/* {cashout_methods.length} Redemption Methods */} + 30+ Redemption Methods + </h2> + </> + ); +}; + +const RedemptionModalBody = () => { + return ( + <div className="grid grid-cols-[auto_1fr] text-xs"> + <div className="label text-center p-2"> + Paypal, Visa, Giftcards will be available. Finishing processing the + transfer from Amazon. + </div> + </div> + ); +}; + +const Redemption = () => { + const dispatch = useAppDispatch(); + const { data: user, isLoading, isError } = useGetMeQuery(); + const [showRedemption, setShowRedemption] = useState(false); + + return ( + <div + className={clsx( + "fixed rounded bg-blue-500 text-white shadow-lg z-60 \ + top-1 sm:top-6 \ + right-1 sm:right-2", + showRedemption ? "w-[98vw] sm:w-[400px]" : "w-[220px]", + )} + > + <div + className="bg-blue-600 rounded border-white-300 + cursor-pointer py-1 px-4 + hover:bg-blue-700 transition-colors duration-200" + onClick={() => setShowRedemption(!showRedemption)} + > + <RedemptionHeader /> + + <h4 className="text-[10px] text-center italic text-white-300 py-0"> + {/* (Click to {showRedemption ? "hide" : "show"}) */} + Details within 24hr. + </h4> + </div> + + {showRedemption && <RedemptionModalBody />} + </div> + ); +}; + +export default Redemption; diff --git a/jb-ui/src/components/Wallet.tsx b/jb-ui/src/components/Wallet.tsx index 3f5c6a4..3bb23a9 100644 --- a/jb-ui/src/components/Wallet.tsx +++ b/jb-ui/src/components/Wallet.tsx @@ -54,7 +54,13 @@ const getBackgroundColor = (balance: number | undefined): string => { const WalletHeader = () => { const amount = - useAppSelector((state) => state.app.userWalletBalance)?.amount ?? 0; + useAppSelector((state) => state.app.userDisplayedWalletBalance?.amount) ?? + 0; + + const amount_string = + useAppSelector( + (state) => state.app.userDisplayedWalletBalance?.amount_string, + ) ?? "$0.00"; const bgStyle = typeof getBackgroundColor(amount) === "string" && @@ -77,13 +83,9 @@ const WalletHeader = () => { <span className="text-left">User Wallet:</span> {amount >= 0 ? ( - <span className="text-emerald-50"> - {`${formatCentsToUSD(Math.abs(amount))}`} - </span> + <span className="text-emerald-50">{`${amount_string}`}</span> ) : ( - <span className="text-rose-50"> - {`(${formatCentsToUSD(Math.abs(amount))})`} - </span> + <span className="text-rose-50">{`(${amount_string})`}</span> )} </h2> </div> @@ -93,6 +95,10 @@ const WalletHeader = () => { const WalletSummary = () => { const summary = useAppSelector((state) => state.app.userLedgerSummary); + const userWallet = useAppSelector((state) => state.app.userWallet); + + userWallet; + const survey_completes = summary?.bp_payment?.entry_count ?? 0; const survey_completes_f = (survey_completes ?? 0).toLocaleString("en-US"); diff --git a/jb-ui/src/models/app.ts b/jb-ui/src/models/app.ts index 4c3e69e..db54e0d 100644 --- a/jb-ui/src/models/app.ts +++ b/jb-ui/src/models/app.ts @@ -5,6 +5,8 @@ import { UserLedgerTransactionTypesSummary, UserLedgerTransactionsResponseTransactionsInner, OfferwallReason, + UserLedgerWallet, + UserDisplayedWalletBalance, } from "@/api_fsb"; import { PaginationState } from "@tanstack/react-table"; @@ -33,6 +35,12 @@ export interface App { userLedgerTxCount?: number; userLedgerTxs: UserLedgerTransactionsResponseTransactionsInner[]; + // --- new approach + userDisplayedWalletBalance?: UserDisplayedWalletBalance; + userWallet?: UserLedgerWallet; + userAttemptCreditWallet?: UserLedgerWallet; + // --- + txPagination: PaginationState; txTotalItems?: number; txTotalPages?: number; diff --git a/jb-ui/src/models/appSlice.ts b/jb-ui/src/models/appSlice.ts index 46c6a2f..42a776e 100644 --- a/jb-ui/src/models/appSlice.ts +++ b/jb-ui/src/models/appSlice.ts @@ -4,6 +4,8 @@ import { UserLedgerTransactionsResponseTransactionsInner, UserLedgerTransactionTypesSummary, UserWalletBalance, + UserDisplayedWalletBalance, + UserLedgerWallet, OfferwallReason, } from "@/api_fsb"; import { App } from "@/models/app"; @@ -26,6 +28,12 @@ const initialState: App = { userLedgerTxCount: undefined, userLedgerTxs: [], + // --- new approach + userDisplayedWalletBalance: undefined, + userWallet: undefined, + userAttemptCreditWallet: undefined, + // --- + txPagination: { pageIndex: 0, pageSize: 10 }, txTotalItems: undefined, txTotalPages: undefined, @@ -91,6 +99,24 @@ const appSlice = createSlice({ state.userWalletBalance = action.payload; }, + // --- New approach --- + setUserDisplayedWalletBalance( + state, + action: PayloadAction<UserDisplayedWalletBalance>, + ) { + state.userDisplayedWalletBalance = action.payload; + }, + + setUserWallet(state, action: PayloadAction<UserLedgerWallet>) { + state.userWallet = action.payload; + }, + + setUserAttemptCreditWallet(state, action: PayloadAction<UserLedgerWallet>) { + state.userAttemptCreditWallet = action.payload; + }, + + // --- --- + setUserLedgerSummary( state, action: PayloadAction<UserLedgerTransactionTypesSummary>, @@ -138,6 +164,10 @@ export const { setUserLedgerSummary, setUserLedgerTxs, + setUserDisplayedWalletBalance, + setUserWallet, + setUserAttemptCreditWallet, + setTxPagination, setTxTotalItems, setTxTotalPages, diff --git a/jb-ui/src/models/cashoutMethodsSlice.ts b/jb-ui/src/models/cashoutMethodsSlice.ts new file mode 100644 index 0000000..92c03da --- /dev/null +++ b/jb-ui/src/models/cashoutMethodsSlice.ts @@ -0,0 +1,37 @@ +import { CashoutMethodOut } from "@/api_fsb"; +import type { RootState } from "@/store"; +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; + +// Entity adapter for normalized storage +const cashoutMethodsAdapter = createEntityAdapter({ + selectId: (model: CashoutMethodOut) => model.id!, + sortComparer: (a, b) => { + return (b.min_value ?? 0) - (a.min_value ?? 0); + }, +}); + +const cashoutMethodsSlice = createSlice({ + name: "cashoutMethods", + initialState: cashoutMethodsAdapter.getInitialState(), + + reducers: { + addCashoutMethod: cashoutMethodsAdapter.addOne, + addCashoutMethods: cashoutMethodsAdapter.addMany, + upsertCashoutMethod: cashoutMethodsAdapter.upsertOne, // Add or Update + }, +}); + +export const { addCashoutMethod, addCashoutMethods, upsertCashoutMethod } = + cashoutMethodsSlice.actions; + +export const cashoutMethodsSelectors = cashoutMethodsAdapter.getSelectors( + (state: RootState) => state.cashoutMethods, +); + +export const selectAllCashoutMethods = ( + state: RootState, +): CashoutMethodOut[] => { + return cashoutMethodsSelectors.selectAll(state) ?? []; +}; + +export default cashoutMethodsSlice.reducer; diff --git a/jb-ui/src/pages/Preview.tsx b/jb-ui/src/pages/Preview.tsx index 85e49f2..900621e 100644 --- a/jb-ui/src/pages/Preview.tsx +++ b/jb-ui/src/pages/Preview.tsx @@ -378,10 +378,10 @@ const Preview = () => { Get paid to take surveys. Our algorithm attempts to pair you with the highest paying survey available at the moment (right now, the highest paying survey is {survey_max_cpi_f}) that you are eligible - for, each attempt is a valid HIT. Try as many as you can, our system - will self regulate you: we want to ensure you have a high chance of - being paired with a survey based on what surveys are available, as - their availability constantly changes. + for. Try as many as you can, our system will self regulate you: we + want to ensure you have a high chance of being paired with a survey + based on what surveys are available, as their availability + constantly changes. </p> </div> </div> diff --git a/jb-ui/src/pages/Result.tsx b/jb-ui/src/pages/Result.tsx index 477f77e..6385bc7 100644 --- a/jb-ui/src/pages/Result.tsx +++ b/jb-ui/src/pages/Result.tsx @@ -21,9 +21,7 @@ export const Complete = () => { <p className="text-emerald-700 text-2xl font-bold"> {ts?.user_payout_string} </p> - <p className="text-slate-300"> - You may close this tab and return to submit the HIT - </p> + <p className="text-slate-300">You may close this tab</p> </> ); }; @@ -35,9 +33,7 @@ export const Enter = () => { <p className="text-slate-800 text-lg font-bold"> You're currently in the Survey </p> - <p className="text-slate-300"> - You may close this tab and return to submit the HIT - </p> + <p className="text-slate-300">You may close this tab</p> </> ); }; @@ -50,13 +46,11 @@ export const InComplete = () => { return ( <> - <h2 className="text-lg">HIT Complete</h2> + <h2 className="text-lg">Attempt Complete</h2> <p className="text-rose-800 text-lg font-bold"> Survey terminated the attempt. </p> - <p className="text-slate-300"> - You may close this tab and return to submit the HIT - </p> + <p className="text-slate-300">You may close this tab</p> </> ); }; @@ -68,9 +62,7 @@ export const Unknown = () => { <p className="text-rose-800 text-lg font-bold"> You're not currently eligible for any Surveys </p> - <p className="text-slate-300"> - You may close this tab and return to submit the HIT - </p> + <p className="text-slate-300">You may close this tab</p> </> ); }; diff --git a/jb-ui/src/pages/Work.tsx b/jb-ui/src/pages/Work.tsx index dbe3601..50ed930 100644 --- a/jb-ui/src/pages/Work.tsx +++ b/jb-ui/src/pages/Work.tsx @@ -1,4 +1,5 @@ import { useGetMeQuery } from "@/models/authAPI"; +import { Link } from "react-router-dom"; import { BucketTask, OfferwallApi, @@ -183,7 +184,7 @@ const Report = ({ onClose }: { onClose: () => void }) => { hover:cursor-pointer hover:bg-rose-400 hover:text-rose-50" onClick={clicked_submit} > - Submit and Complete HIT + Submit and Complete Attempt </Button> </div> </div> @@ -306,7 +307,7 @@ const SurveyMonitor = () => { } // This is if they have finished a survey complete and it resulted in a - // COMPLETE or a INCOMPLETE (doesn't matter). They can now submit the HIT. + // COMPLETE or a INCOMPLETE (doesn't matter). They can now submit the Attempt. // // 2 - INCOMPLETE, 3 - COMPLETE return ( @@ -326,7 +327,7 @@ const SurveyMonitor = () => { py-2 text-zinc-500 text-md font-medium text-center" > - You may now submit the HIT. + You may now submit the attempt. </p> )} </div> @@ -351,7 +352,9 @@ const SurveyMonitor = () => { pt-0 pb-4 text-red-950 text-sm font-semibold text-center" > - Please return the HIT to avoid having your assignment rejected. + <Link to="/" reloadDocument> + Click to Refresh and Try Again + </Link> </p> </div> </> @@ -368,7 +371,7 @@ const SurveyMonitor = () => { hover:cursor-pointer hover:bg-emerald-400 hover:text-emerald-50" onClick={() => setShowComplete(true)} > - Submit HIT + Finish Attempt </button> <button @@ -393,15 +396,7 @@ const SurveyMonitor = () => { className=" text-red-600 text-lg font-semibold text-center" > - Your balance is low. You cannot submit the HIT at this time. - </p> - - <p - className=" - pt-0 pb-4 - text-red-950 text-sm font-semibold text-center" - > - Please return the HIT to avoid having your assignment rejected. + Your balance is low. </p> </div> )} @@ -709,6 +704,8 @@ const SurveyEnter = () => { new OfferwallApi() .topNPlusBlockOfferwallProductIdOfferwallD48cce47Get( bpid, // productId + // "7d9240132c695c05cd0ba47e2580158719f8215b", + // "71.225.51.26", user.product_user_id, undefined, // ip undefined, // countryIso @@ -760,12 +757,12 @@ const SurveyEnter = () => { Your balance is low. </h2> <p className="px-4 py-2 text-center text-sm font-medium text-white"> - You may continue to attempt surveys, but you will be advised to - return the HIT if your survey attempt does not result in a complete. + You may continue to attempt surveys, but you will not earn an + attempt credit if your survey attempt does not result in a complete. </p> <p className="px-4 py-2 text-center text-sm font-bold text-white"> - HIT submissions with a balance exceeding -$1.00 will return in - rejected assignments. + Attempts with a balance exceeding -$1.00 will not be issued an + attempt credit. </p> </div> )} diff --git a/jb-ui/src/store.ts b/jb-ui/src/store.ts index baf13b7..081de9f 100644 --- a/jb-ui/src/store.ts +++ b/jb-ui/src/store.ts @@ -4,6 +4,7 @@ import grlEventsReducers from "@/models/grlEventsSlice"; import { authApi } from "@/models/authAPI"; import profilingQuestionsReducers from "@/models/profilingQuestionsSlice"; +import cashoutMethodReducers from "@/models/cashoutMethodsSlice"; import type { Action, ThunkAction } from "@reduxjs/toolkit"; import { configureStore } from "@reduxjs/toolkit"; @@ -15,6 +16,7 @@ export const store = configureStore({ stats: grlStatsReducers, events: grlEventsReducers, profilingQuestions: profilingQuestionsReducers, + cashoutMethods: cashoutMethodReducers, }, middleware: (getDefault) => getDefault().concat(authApi.middleware), }); |
