From ed201936813d9a720b51e6cb40a05267b94e7314 Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Thu, 10 Sep 2026 17:46:41 -0700 Subject: Typescript prettier Committing seperate so it's easier to see actual changes. --- jb-ui/src/pages/Work.tsx | 1452 ++++++++++++++++++++++++---------------------- 1 file changed, 767 insertions(+), 685 deletions(-) (limited to 'jb-ui/src/pages/Work.tsx') diff --git a/jb-ui/src/pages/Work.tsx b/jb-ui/src/pages/Work.tsx index c54732f..32dcc96 100644 --- a/jb-ui/src/pages/Work.tsx +++ b/jb-ui/src/pages/Work.tsx @@ -1,765 +1,847 @@ import { - BucketTask, - OfferwallApi, - OfferwallReason, - ReportApi, ReportTask, ReportValue, StatusApi, - TasksStatusResponse, TaskStatusResponse, TopNPlusBlockOfferWall + BucketTask, + OfferwallApi, + OfferwallReason, + ReportApi, + ReportTask, + ReportValue, + StatusApi, + TasksStatusResponse, + TaskStatusResponse, + TopNPlusBlockOfferWall, } from "@/api_fsb"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { - Field, - FieldDescription, - FieldGroup, - FieldLabel, - FieldLegend, - FieldSet, + Field, + FieldDescription, + FieldGroup, + FieldLabel, + FieldLegend, + FieldSet, } from "@/components/ui/field"; import { useAppDispatch, useAppSelector } from "@/hooks"; import { bpid, formatCentsToUSD, formatSource, truncate } from "@/lib/utils"; import { - getAvailabilityCount, getLOIText, - isLowBalance, - selectBucket, - setAttemptedLiveEligibleCount, - setAvailabilityCount, - setCurrentBuckets, - setEnteredTimestamp, setLOI, - setOfferwallReasons, - setTaskStatus + getAvailabilityCount, + getLOIText, + isLowBalance, + selectBucket, + setAttemptedLiveEligibleCount, + setAvailabilityCount, + setCurrentBuckets, + setEnteredTimestamp, + setLOI, + setOfferwallReasons, + setTaskStatus, } from "@/models/appSlice"; import { - createColumnHelper, flexRender, getCoreRowModel, - useReactTable + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, } from "@tanstack/react-table"; import { XIcon } from "lucide-react"; import moment from "moment/moment"; import { useEffect, useRef, useState } from "react"; -import Markdown from 'react-markdown'; +import Markdown from "react-markdown"; const Report = ({ onClose }: { onClose: () => void }) => { - const [showComplete, setShowComplete] = useState(false); - const [selectedValues, setSelectedValues] = useState([]); - - const bpuid = useAppSelector(state => state.app.bpuid) - const ts = useAppSelector(state => state.app.taskStatus) - - const handleCheckboxChange = (value: ReportValue, checked: boolean) => { - setSelectedValues(prev => - checked - ? [...prev, value] - : prev.filter(v => v !== value) - ); + const [showComplete, setShowComplete] = useState(false); + const [selectedValues, setSelectedValues] = useState([]); + + const bpuid = useAppSelector((state) => state.app.bpuid); + const ts = useAppSelector((state) => state.app.taskStatus); + + const handleCheckboxChange = (value: ReportValue, checked: boolean) => { + setSelectedValues((prev) => + checked ? [...prev, value] : prev.filter((v) => v !== value), + ); + }; + + const clicked_submit = () => { + if (!bpuid) return; + // ReportValue.NUMBER_0 + console.log("selectedValues:", selectedValues); + + const rt: ReportTask = { + bpuid: bpuid, + reasons: selectedValues, + notes: "", }; - const clicked_submit = () => { - if (!bpuid) return; - // ReportValue.NUMBER_0 - console.log("selectedValues:", selectedValues); - - const rt: ReportTask = { - 'bpuid': bpuid, - 'reasons': selectedValues, - 'notes': "" - }; - - new ReportApi().reportTaskProductIdReportPost( - bpid, // productId - rt, // reportTask - ).then(res => { - console.log("Response:", res); // Check what's actually here - - if (res.status === 400) { - // Handle error here instead - } else { - setShowComplete(true); - } - }) - } - - const reportReasons = [ - { id: 'TECHNICAL_ERROR', label: 'Technical Error', value: ReportValue.NUMBER_1 }, - { id: 'NO_REDIRECT', label: 'No Redirect', value: ReportValue.NUMBER_2 }, - { id: 'PRIVACY_INVASION', label: 'Privacy Invasion', value: ReportValue.NUMBER_3 }, - { id: 'UNCOMFORTABLE_TOPICS', label: 'Uncomfortable Topics', value: ReportValue.NUMBER_4 }, - { id: 'ASKED_FOR_NOT_ALLOWED_ACTION', label: 'Asked for Not Allowed Action', value: ReportValue.NUMBER_5 }, - { id: 'BAD_ON_MOBILE', label: 'Bad on Mobile', value: ReportValue.NUMBER_6 }, - { id: 'DIDNT_LIKE', label: "Didn't Like", value: ReportValue.NUMBER_7 }, - ]; - - if (!ts?.finished) { - return

Please finish a Task to report its status.

- } - - if (showComplete) { - return - } - - return ( -
-
-
- - Please select all reasons why you are reporting this Task: - - - You must provide at least one reason to report a Task. - - - - {reportReasons.map((reason) => ( - - - handleCheckboxChange(reason.value, checked as boolean) - } - /> - Please finish a Task to report its status.

; + } + + if (showComplete) { + return ; + } + + return ( +
+
+
+ + Please select all reasons why you are reporting this Task: + + + You must provide at least one reason to report a Task. + + + {reportReasons.map((reason) => ( + + + handleCheckboxChange(reason.value, checked as boolean) + } + /> + - {reason.label} - - - ))} - - -
-
-
- - -
-
- ) -} + onClick={clicked_submit} + > + Submit and Complete HIT + +
+
+ ); +}; const SubimtAMT = () => { - const bpuid = useAppSelector(state => state.app.bpuid) - const assignment_id = useAppSelector(state => state.app.assignment_id) - const ts = useAppSelector(state => state.app.taskStatus) - const turkSubmitTo = useAppSelector(state => state.app.turkSubmitTo) + const bpuid = useAppSelector((state) => state.app.bpuid); + const assignment_id = useAppSelector((state) => state.app.assignment_id); + const ts = useAppSelector((state) => state.app.taskStatus); + const turkSubmitTo = useAppSelector((state) => state.app.turkSubmitTo); - const formRef = useRef(null); + const formRef = useRef(null); - useEffect(() => { - if (!ts) return; - if (!bpuid) return; - if (!assignment_id) return; - if (!turkSubmitTo) return; + useEffect(() => { + if (!ts) return; + if (!bpuid) return; + if (!assignment_id) return; + if (!turkSubmitTo) return; - if (formRef.current) { - formRef.current.submit(); - } - }, [ts?.tsid, bpuid, assignment_id, turkSubmitTo]); + if (formRef.current) { + formRef.current.submit(); + } + }, [ts?.tsid, bpuid, assignment_id, turkSubmitTo]); + + return ( + <> +
+ + + + + +
+ + ); +}; - return ( - <> -
- - - - - -
- - ) -} +const SurveyMonitor = () => { + const dispatch = useAppDispatch(); + const [isPolling, setIsPolling] = useState(true); + // const [timeAgo, setTimeAgo] = useState(""); -const SurveyMonitor = () => { - const dispatch = useAppDispatch() - - const [isPolling, setIsPolling] = useState(true); - // const [timeAgo, setTimeAgo] = useState(""); - - const [showComplete, setShowComplete] = useState(false); - const [showReport, setShowReport] = useState(false); - - const bpuid = useAppSelector(state => state.app.bpuid) - const entered = useAppSelector(state => state.app.currentBucketEntered) - const isLow = useAppSelector(state => isLowBalance(state)) - - const ts = useAppSelector(state => state.app.taskStatus) - const isBaddie = ts?.status_code_1 === "SESSION_START_FAIL" || ts?.status_code_1 === "SESSION_START_QUALITY_FAIL" || ts?.status_code_1 === "SESSION_CONTINUE_QUALITY_FAIL"; - - // useEffect(() => { - // if (!ts) return; - - // const updateTimeAgo = () => { - // const time_ago = moment(ts.started).fromNow(); - // setTimeAgo(time_ago); - // }; - - // // Update immediately - // updateTimeAgo(); - - // // Then update every minute - // const intervalId = setInterval(updateTimeAgo, 60 * 1000); - - // return () => clearInterval(intervalId); - // }, [ts]); - - useEffect(() => { - if (!bpuid) return; - if (!entered) return; - if (!isPolling) return; - - const pollApi = async () => { - new StatusApi().listTaskStatusesProductIdStatusGet( - bpid, // productId - bpuid, // Worker ID - entered, // startedAfter - moment.utc().unix() // startedBefore - ).then(res => { - const d = res.data as TasksStatusResponse; - - if (!d.tasks_status || d.tasks_status.length === 0) { - // Likely still in a GRS survey - return; - } - - // if (d_len >= 2) Sentry.captureMessage("Results returned back multiple") - const ts = d.tasks_status![0] as TaskStatusResponse; - dispatch(setTaskStatus(ts)) - - if (ts.status == 2 || ts.status == 3) { - // INCOMPLETE + COMPLETE - setIsPolling(false); - } - }); - - // If there is already a TaskStatus, update the time ago - // if (ts) { - // const time_ago = moment(ts.started).fromNow(); - // setTimeAgo(time_ago); - // } - }; - - // Call immediately on mount, then set up interval - pollApi(); - const intervalId = setInterval(pollApi, 4 * 1000); - - // Cleanup: stop interval when component unmounts or isPolling changes - return () => clearInterval(intervalId); - }, [isPolling, entered]); - - if (!ts) { - return ( -

Loading

- ) - } + const [showComplete, setShowComplete] = useState(false); + const [showReport, setShowReport] = useState(false); - // This is if they are actively in a survey or we can't determine the - // status yet. We want to show the timer and keep them on this page to - // monitor for completion. This gets shown no matter what regardless of - // balance. - // - // 0 - UNKNOWN, 1 - ENTER - if (ts.status === 0 || ts.status === 1) { - return ( - <> -
-

- In Progress -

-

- We're actively monitoring your Task status. -

-
- + const bpuid = useAppSelector((state) => state.app.bpuid); + const entered = useAppSelector((state) => state.app.currentBucketEntered); + const isLow = useAppSelector((state) => isLowBalance(state)); + + const ts = useAppSelector((state) => state.app.taskStatus); + const isBaddie = + ts?.status_code_1 === "SESSION_START_FAIL" || + ts?.status_code_1 === "SESSION_START_QUALITY_FAIL" || + ts?.status_code_1 === "SESSION_CONTINUE_QUALITY_FAIL"; + + // useEffect(() => { + // if (!ts) return; + + // const updateTimeAgo = () => { + // const time_ago = moment(ts.started).fromNow(); + // setTimeAgo(time_ago); + // }; + + // // Update immediately + // updateTimeAgo(); + + // // Then update every minute + // const intervalId = setInterval(updateTimeAgo, 60 * 1000); + + // return () => clearInterval(intervalId); + // }, [ts]); + + useEffect(() => { + if (!bpuid) return; + if (!entered) return; + if (!isPolling) return; + + const pollApi = async () => { + new StatusApi() + .listTaskStatusesProductIdStatusGet( + bpid, // productId + bpuid, // Worker ID + entered, // startedAfter + moment.utc().unix(), // startedBefore ) - } + .then((res) => { + const d = res.data as TasksStatusResponse; + + if (!d.tasks_status || d.tasks_status.length === 0) { + // Likely still in a GRS survey + return; + } + + // if (d_len >= 2) Sentry.captureMessage("Results returned back multiple") + const ts = d.tasks_status![0] as TaskStatusResponse; + dispatch(setTaskStatus(ts)); + + if (ts.status == 2 || ts.status == 3) { + // INCOMPLETE + COMPLETE + setIsPolling(false); + } + }); + + // If there is already a TaskStatus, update the time ago + // if (ts) { + // const time_ago = moment(ts.started).fromNow(); + // setTimeAgo(time_ago); + // } + }; - // 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. - // - // 2 - INCOMPLETE, 3 - COMPLETE + // Call immediately on mount, then set up interval + pollApi(); + const intervalId = setInterval(pollApi, 4 * 1000); + + // Cleanup: stop interval when component unmounts or isPolling changes + return () => clearInterval(intervalId); + }, [isPolling, entered]); + + if (!ts) { + return

Loading

; + } + + // This is if they are actively in a survey or we can't determine the + // status yet. We want to show the timer and keep them on this page to + // monitor for completion. This gets shown no matter what regardless of + // balance. + // + // 0 - UNKNOWN, 1 - ENTER + if (ts.status === 0 || ts.status === 1) { return ( - <> -
-

- {ts.status === 3 ? "Survey Complete!" : "Survey Terminated."} -

- - {((ts.status === 3 || !isLow) && !isBaddie) && ( -

+ In Progress + +

+ We're actively monitoring your Task status. +

+
+ + ); + } + + // 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. + // + // 2 - INCOMPLETE, 3 - COMPLETE + return ( + <> +
+

+ {ts.status === 3 ? "Survey Complete!" : "Survey Terminated."} +

+ + {(ts.status === 3 || !isLow) && !isBaddie && ( +

- You may now submit the HIT. -

- )} -
- - {(isBaddie) ? ( - <> -
+ You may now submit the HIT. +

+ )} +
+ + {isBaddie ? ( + <> +
-

- Quality Check Failed. -

-

- Unfortunately, it looks like you did not pass the quality check for this survey. -

- -

+

+ Quality Check Failed. +

+

+ Unfortunately, it looks like you did not pass the quality check + for this survey. +

+ +

- Please return the HIT to avoid having your assignment rejected. -

-
- - - ) : ( - <> - {(ts.status === 3 || !isLow) ? ( - <> -
- -
+ + ) : ( + <> + {ts.status === 3 || !isLow ? ( + <> +
+ - - -
- - {showComplete && } - {showReport && setShowReport(false)} />} - - ) : ( -
-

- Your balance is low. You cannot submit the HIT at this time. -

- -

- Please return the HIT to avoid having your assignment rejected. -

-
- )} - - - )} - + onClick={() => setShowReport(!showReport)} + > + Report Survey + + + + {showComplete && } + {showReport && setShowReport(false)} />} + + ) : ( +
+

+ Your balance is low. You cannot submit the HIT at this time. +

+

+ Please return the HIT to avoid having your assignment rejected. +

+
+ )} - ) -} - + )} + + ); +}; const LOISelect = () => { - // This is what's responsible for being able to click on the text - // we set the global LOI times. - - const dispatch = useAppDispatch() - const loi: number = useAppSelector(state => state.app.loi) - const bucket = useAppSelector(state => selectBucket(state)) - - const toggle_loi = function () { - dispatch(setLOI(loi >= 1800 ? 600 : loi + 600)) - } - - const time_f = useAppSelector(state => getLOIText(state)) - - const avg_time_f = ((bucket?.duration.q2 ?? 0) / 60).toLocaleString( - 'en-US', { minimumFractionDigits: 0, maximumFractionDigits: 1 }) - - const formatter = new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD', - }); - const avg_pay_f: string = formatter.format((bucket?.payout.q2 ?? 0) / 100) - - return ( -
-

- I have {time_f} minutes -

-

- The average time spent is {avg_time_f} minutes and pays {avg_pay_f}

-
- ) - + // This is what's responsible for being able to click on the text + // we set the global LOI times. + + const dispatch = useAppDispatch(); + const loi: number = useAppSelector((state) => state.app.loi); + const bucket = useAppSelector((state) => selectBucket(state)); + + const toggle_loi = function () { + dispatch(setLOI(loi >= 1800 ? 600 : loi + 600)); + }; + + const time_f = useAppSelector((state) => getLOIText(state)); + + const avg_time_f = ((bucket?.duration.q2 ?? 0) / 60).toLocaleString("en-US", { + minimumFractionDigits: 0, + maximumFractionDigits: 1, + }); + + const formatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }); + const avg_pay_f: string = formatter.format((bucket?.payout.q2 ?? 0) / 100); + + return ( +
+

+ I have{" "} + + {time_f} + {" "} + minutes +

+

+ The average time spent is {avg_time_f} minutes and pays {avg_pay_f} +

+
+ ); }; const SurveyLoading = () => { - return ( -
-

- Survey Loading… -

-
- ) -} - + hover:cursor-pointer hover:bg-green-500 hover:text-white" + > +

Survey Loading…

+ + ); +}; const NoSurveys = () => { - const offerwall_reasons = useAppSelector(state => state.app.offerwall_reasons) - - const REASON_DISPLAY: Record = { - [OfferwallReason.UserBlocked]: { - label: "Account Restricted", - description: "Your account has been restricted.", - }, - [OfferwallReason.HighReconRate]: { - label: "High Reconciliation Rate", - description: "Your account has an unusually high reconciliation rate.", - }, - [OfferwallReason.UncommonDemographics]: { - label: "Demographic Mismatch", - description: "No surveys require your current demographics.", - }, - [OfferwallReason.UnderMinimumAge]: { - label: "Age Requirement Not Met", - description: "You must meet the minimum age requirement to participate.", - }, - [OfferwallReason.ExhaustedHighValueSupply]: { - label: "No Premium Offers Available", - description: "You've attempted all premium surveys.", - }, - [OfferwallReason.AllEligibleAttempted]: { - label: "All Offers Attempted", - description: "You've attempted all available surveys.", - }, - [OfferwallReason.LowCurrentSupply]: { - label: "Limited Availability", - description: "There are currently limited opportunities available.", - }, - }; - - function formatReasons(reasons: OfferwallReason[]): { label: string; description: string }[] { - return reasons.map((r) => REASON_DISPLAY[r]); - } + const offerwall_reasons = useAppSelector( + (state) => state.app.offerwall_reasons, + ); - return ( - <> -
REASON_DISPLAY[r]); + } + + return ( + <> +
-

- No Surveys available… -

-
- - {formatReasons(offerwall_reasons).map(r => { - return ( -
+

+ No Surveys available… +

+
+ + {formatReasons(offerwall_reasons).map((r) => { + return ( +
-

- {r.description} -

-
- ) - })} - - ) -} + hover:cursor-pointer hover:bg-rose-200 hover:text-rose-950 " + > +

+ {r.description} +

+
+ ); + })} + + ); +}; const BucketDetails = () => { - const bucket = useAppSelector(state => selectBucket(state)) - if (!bucket) return null; - const [showDetails, setShowDetails] = useState(false); - - const columnHelper = createColumnHelper() - const columns = [ - columnHelper.display({ - id: 'index', - header: '#', - cell: ({ row }) => row.index + 1, - size: 25, - meta: { - align: 'center' - } - }), - columnHelper.accessor('source', { - header: () => 'Marketplace', - cell: (props) => { - const s = props.getValue() - return formatSource(s) - }, - size: 85, - meta: { - align: 'left' - } - }), - columnHelper.accessor('id', { - header: () => 'Survey ID', - cell: props => { - return truncate(props.getValue(), 6, "*****").toUpperCase() - }, - size: 70, - meta: { - align: 'left' - } - }), - columnHelper.accessor('payout', { - header: () => 'Amount', - cell: (props) => { - const val = props.renderValue() as number; - return formatCentsToUSD(val) - }, - size: 50, - meta: { - align: 'center' - } - }), - columnHelper.accessor('loi', { - header: () => 'Duration', - cell: (props) => { - const val = props.renderValue() as number; - return `${Math.round(val / 60)} min` - }, - size: 50, - meta: { - align: 'center' - } - }), - ] - - const table = useReactTable({ - 'data': bucket.contents, - 'columns': columns, - getCoreRowModel: getCoreRowModel(), - }); - - return ( - <> -
row.index + 1, + size: 25, + meta: { + align: "center", + }, + }), + columnHelper.accessor("source", { + header: () => "Marketplace", + cell: (props) => { + const s = props.getValue(); + return formatSource(s); + }, + size: 85, + meta: { + align: "left", + }, + }), + columnHelper.accessor("id", { + header: () => "Survey ID", + cell: (props) => { + return truncate(props.getValue(), 6, "*****").toUpperCase(); + }, + size: 70, + meta: { + align: "left", + }, + }), + columnHelper.accessor("payout", { + header: () => "Amount", + cell: (props) => { + const val = props.renderValue() as number; + return formatCentsToUSD(val); + }, + size: 50, + meta: { + align: "center", + }, + }), + columnHelper.accessor("loi", { + header: () => "Duration", + cell: (props) => { + const val = props.renderValue() as number; + return `${Math.round(val / 60)} min`; + }, + size: 50, + meta: { + align: "center", + }, + }), + ]; + + const table = useReactTable({ + data: bucket.contents, + columns: columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( + <> +
setShowDetails(!showDetails)} - > - ({showDetails ? 'Hide' : 'Show'} details of surveys in this bucket) -
- - {showDetails && ( -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - - {headerGroup.headers.map((header) => { - const align = header.column.columnDef.meta?.align || 'left'; - const alignClass = align === 'center' ? 'text-center' : align === 'right' ? 'text-right' : 'text-left'; - - return ( - - ) - })} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => { - const align = cell.column.columnDef.meta?.align || 'left'; - const alignClass = align === 'center' ? 'text-center' : - align === 'right' ? 'text-right' : 'text-left'; - - return ( - - ) - })} - - ))} - - -
- {flexRender( - header.column.columnDef.header, - header.getContext(), - )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
-
- )} - - ) + onClick={() => setShowDetails(!showDetails)} + > + ({showDetails ? "Hide" : "Show"} details of surveys in this bucket) +
+ + {showDetails && ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const align = header.column.columnDef.meta?.align || "left"; + const alignClass = + align === "center" + ? "text-center" + : align === "right" + ? "text-right" + : "text-left"; + + return ( + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => { + const align = cell.column.columnDef.meta?.align || "left"; + const alignClass = + align === "center" + ? "text-center" + : align === "right" + ? "text-right" + : "text-left"; + + return ( + + ); + })} + + ))} + +
+ {flexRender( + header.column.columnDef.header, + header.getContext(), + )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} +
+
+ )} + + ); }; const SurveyEnter = () => { - const dispatch = useAppDispatch() - const loi = useAppSelector(state => state.app.loi) - const bpuid = useAppSelector(state => state.app.bpuid) - - const available_cnt = useAppSelector(state => getAvailabilityCount(state)) - const buckets = useAppSelector(state => state.app.currentBuckets) - const bucket = useAppSelector(state => selectBucket(state)) - const isLow = useAppSelector(state => isLowBalance(state)) - - useEffect(() => { - if (!bpuid) return; - - new OfferwallApi().topNPlusBlockOfferwallProductIdOfferwallD48cce47Get( - bpid, // productId - bpuid, // bpuid - undefined, // ip - undefined, // countryIso - undefined, // languages - undefined, // behavior - undefined, // minPayout - loi, //duration - 1 // nBins - ).then(res => { - const top_n_res = res.data.offerwall as TopNPlusBlockOfferWall; - dispatch(setAvailabilityCount(top_n_res.availability_count)) - if (top_n_res.attempted_live_eligible_count) { - dispatch(setAttemptedLiveEligibleCount(top_n_res.attempted_live_eligible_count)) - } - dispatch(setCurrentBuckets(top_n_res.buckets)) - dispatch(setOfferwallReasons(top_n_res.offerwall_reasons ?? [])) - }); - }, [loi, bpuid]); - - - const start_survey = () => { - if (!bucket?.uri) return; - - dispatch(setEnteredTimestamp()); - window.open(bucket.uri, '_blank'); - } - - if (buckets === undefined) { - return - } - - if (available_cnt === 0 || buckets.length === 0 || !bucket) { - return - } - - return ( - <> - {isLow && ( -
; + } + + if (available_cnt === 0 || buckets.length === 0 || !bucket) { + return ; + } + + return ( + <> + {isLow && ( +
-

- Your balance is low. -

-

- 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. -

-

- HIT submissions with a balance exceeding -$1.00 will - return in rejected assignments. -

-
- )} - - -
+

+ Your balance is low. +

+

+ 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. +

+

+ HIT submissions with a balance exceeding -$1.00 will return in + rejected assignments. +

+
+ )} + + + - - -
-

- - {bucket.eligibility_explanation} - -

-
- - ) + hover:cursor-pointer hover:bg-emerald-400 hover:text-emerald-50" + > +

+ + Start Task + +

+
+ + +
+

+ {bucket.eligibility_explanation} +

+
+ + ); }; - const Work = () => { - const entered = useAppSelector(state => state.app.currentBucketEntered) - const assignment_id = useAppSelector(state => state.app.assignment_id) - - if (!assignment_id) { - return ( -

- No Assignment ID found. Please access this page through - the Amazon Mechanical Turk platform. -

- ) - } + const entered = useAppSelector((state) => state.app.currentBucketEntered); + const assignment_id = useAppSelector((state) => state.app.assignment_id); + if (!assignment_id) { return ( - <> - {entered ? ( - - ) : ( - - )} - - ) +

+ No Assignment ID found. Please access this page through the Amazon + Mechanical Turk platform. +

+ ); + } + + return <>{entered ? : }; }; - -export default Work; \ No newline at end of file +export default Work; -- cgit v1.2.3 From 11d63b171501b9c29398133b795c3bcc1ae85a1b Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Fri, 11 Sep 2026 18:16:06 -0700 Subject: using RTK with session login. start of magic token request --- jb-ui/.vscode/settings.json | 2 +- jb-ui/src/AuthContext.tsx | 28 ++++++++++++ jb-ui/src/JBApp.tsx | 4 +- jb-ui/src/models/authAPI.ts | 23 ++++++++++ jb-ui/src/pages/Work.tsx | 103 ++++++++++++++++++++++++++++++++++++++++---- jb-ui/src/store.ts | 5 +++ jb/views/auth.py | 20 +++++---- 7 files changed, 164 insertions(+), 21 deletions(-) create mode 100644 jb-ui/src/AuthContext.tsx create mode 100644 jb-ui/src/models/authAPI.ts (limited to 'jb-ui/src/pages/Work.tsx') diff --git a/jb-ui/.vscode/settings.json b/jb-ui/.vscode/settings.json index 4257030..9b8250c 100644 --- a/jb-ui/.vscode/settings.json +++ b/jb-ui/.vscode/settings.json @@ -10,6 +10,6 @@ "editor.formatOnSave": true }, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" } } \ No newline at end of file diff --git a/jb-ui/src/AuthContext.tsx b/jb-ui/src/AuthContext.tsx new file mode 100644 index 0000000..85bcdb0 --- /dev/null +++ b/jb-ui/src/AuthContext.tsx @@ -0,0 +1,28 @@ +// AuthContext.jsx +import { createContext, useContext, useEffect, useState } from "react"; + +const AuthContext = createContext(null); + +export function AuthProvider({ children }) { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch("/api/me", { credentials: "include" }) + .then((res) => (res.ok ? res.json() : null)) + .then(setUser) + .finally(() => setLoading(false)); + }, []); + + return ( + + {children} + + ); +} + +export function useAuth() { + return useContext(AuthContext); +} diff --git a/jb-ui/src/JBApp.tsx b/jb-ui/src/JBApp.tsx index 7c99315..fd0eeba 100644 --- a/jb-ui/src/JBApp.tsx +++ b/jb-ui/src/JBApp.tsx @@ -225,10 +225,10 @@ function JBApp() { return ( - } /> }> }> - } /> + } /> + } /> {/* } /> */} } /> diff --git a/jb-ui/src/models/authAPI.ts b/jb-ui/src/models/authAPI.ts new file mode 100644 index 0000000..1ac7886 --- /dev/null +++ b/jb-ui/src/models/authAPI.ts @@ -0,0 +1,23 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const authApi = createApi({ + reducerPath: "authApi", + baseQuery: fetchBaseQuery({ + baseUrl: "/auth", + credentials: "include", // sends the httpOnly cookie + }), + tagTypes: ["User"], + endpoints: (builder) => ({ + getMe: builder.query({ + query: () => "/session", + providesTags: ["User"], + }), + // logout: builder.mutation({ + // query: () => ({ url: "/de-session", method: "POST" }), + // invalidatesTags: ["User"], + // }), + }), +}); + +// export const { useGetMeQuery, useLogoutMutation } = authApi; +export const { useGetMeQuery } = authApi; diff --git a/jb-ui/src/pages/Work.tsx b/jb-ui/src/pages/Work.tsx index 32dcc96..e33328e 100644 --- a/jb-ui/src/pages/Work.tsx +++ b/jb-ui/src/pages/Work.tsx @@ -1,3 +1,4 @@ +import { useGetMeQuery } from "@/models/authAPI"; import { BucketTask, OfferwallApi, @@ -828,17 +829,101 @@ const SurveyEnter = () => { ); }; +type RequestState = "idle" | "working" | "sent" | "failed"; + +const Login = () => { + const [email, setEmail] = useState(""); + const [state, setState] = useState("idle"); + const bpuid = useAppSelector((state) => state.app.bpuid); + + // const dispatch = useAppDispatch(); + const submit = async (event: FormEvent) => { + event.preventDefault(); + setState("working"); + + try { + const response = await fetch("/auth/magic-link/request", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: email, + }), + }); + + if (!response.ok) throw new Error("Account-link request failed"); + + const body = (await response.json()) as { magic_link?: string }; + console.log(body.magic_link ?? null); + } catch { + setState("failed"); + } + }; + + return ( +
+

+ Please Login +

+ +
+ setEmail(event.target.value)} + placeholder="hello@jamesbillings67.com" + /> + +
+ + {state === "sent" && ( +

+ Check your email to login. +

+ )} + + {state === "failed" &&

Unable to send the link. Please try again.

} +
+ ); +}; + const Work = () => { const entered = useAppSelector((state) => state.app.currentBucketEntered); - const assignment_id = useAppSelector((state) => state.app.assignment_id); - - if (!assignment_id) { - return ( -

- No Assignment ID found. Please access this page through the Amazon - Mechanical Turk platform. -

- ); + // const assignment_id = useAppSelector((state) => state.app.assignment_id); + // if (!assignment_id) { + // return ( + //

+ // No Assignment ID found. Please access this page through the Amazon + // Mechanical Turk platform. + //

+ // ); + // } + + const { data: user, isLoading, isError } = useGetMeQuery(); + // const location = useLocation(); + + if (isLoading) return
Loading...
; + + if (isError || !user) { + return ; } return <>{entered ? : }; diff --git a/jb-ui/src/store.ts b/jb-ui/src/store.ts index 07cac9e..baf13b7 100644 --- a/jb-ui/src/store.ts +++ b/jb-ui/src/store.ts @@ -1,6 +1,8 @@ import appReducers from "@/models/appSlice"; import grlStatsReducers from "@/models/grlStatsSlice"; import grlEventsReducers from "@/models/grlEventsSlice"; +import { authApi } from "@/models/authAPI"; + import profilingQuestionsReducers from "@/models/profilingQuestionsSlice"; import type { Action, ThunkAction } from "@reduxjs/toolkit"; import { configureStore } from "@reduxjs/toolkit"; @@ -8,10 +10,13 @@ import { configureStore } from "@reduxjs/toolkit"; export const store = configureStore({ reducer: { app: appReducers, + // auth: authReducers, + [authApi.reducerPath]: authApi.reducer, stats: grlStatsReducers, events: grlEventsReducers, profilingQuestions: profilingQuestionsReducers, }, + middleware: (getDefault) => getDefault().concat(authApi.middleware), }); // Infer the type of `store` diff --git a/jb/views/auth.py b/jb/views/auth.py index bf39b51..ce9c1e0 100644 --- a/jb/views/auth.py +++ b/jb/views/auth.py @@ -2,7 +2,7 @@ from typing import Annotated from urllib.parse import urlencode from fastapi import APIRouter, Depends, HTTPException, Response, status -from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse +from fastapi.responses import HTMLResponse, RedirectResponse from jb.api.auth import ( SESSION_COOKIE_NAME, @@ -119,7 +119,7 @@ def link_amt_account(body: AmtAccountLink) -> dict[str, str]: return {} -@auth_router.get("/link-amt/", response_class=HTMLResponse, include_in_schema=False) +@auth_router.get("/debug/", response_class=HTMLResponse, include_in_schema=False) def link_amt_account_landing_page( gr_api: Annotated[GRApiManager, Depends(get_gr_api_manager)], token: str | None = None, @@ -129,13 +129,15 @@ def link_amt_account_landing_page( # TODO! Try catch any of this, and if it fails, show the user a # TODO! failed HTML page. As of now, it shows them a failed JSON response. - if token is None: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="token is required", - ) - _response = RedirectResponse(url="/", status_code=status.HTTP_303_SEE_OTHER) - _exchange_amt_account_link(token=token, response=_response, gr_api=gr_api) + if settings.debug: + if token is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="token is required", + ) + + _response = RedirectResponse(url="/", status_code=status.HTTP_303_SEE_OTHER) + _exchange_amt_account_link(token=token, response=_response, gr_api=gr_api) return HTMLResponse( BASE_HTML, -- cgit v1.2.3 From f82ac61b6a044467ab37c0645c3fe99b40bedfc5 Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Fri, 11 Sep 2026 23:06:59 -0700 Subject: replace references to bpuid with logged in user --- jb-ui/src/JBApp.tsx | 14 +++----- jb-ui/src/components/Footer.tsx | 7 ++-- jb-ui/src/components/Profiling.tsx | 38 ++++++++++++-------- jb-ui/src/components/Wallet.tsx | 19 +++++----- jb-ui/src/models/authAPI.ts | 3 +- jb-ui/src/pages/Preview.tsx | 3 +- jb-ui/src/pages/Transfer.tsx | 9 ++--- jb-ui/src/pages/Work.tsx | 71 +++++++++----------------------------- jb-ui/src/types/authApi.ts | 6 ++++ 9 files changed, 73 insertions(+), 97 deletions(-) create mode 100644 jb-ui/src/types/authApi.ts (limited to 'jb-ui/src/pages/Work.tsx') diff --git a/jb-ui/src/JBApp.tsx b/jb-ui/src/JBApp.tsx index fd0eeba..a69cc85 100644 --- a/jb-ui/src/JBApp.tsx +++ b/jb-ui/src/JBApp.tsx @@ -16,6 +16,7 @@ import { import Footer from "@/components/Footer"; import { useAppDispatch } from "@/hooks"; import { bpid, routeBasename, tagManager } from "@/lib/utils"; +import { useGetMeQuery } from "@/models/authAPI"; import { setAssignmentID, setProductUserID, @@ -72,24 +73,19 @@ function isPingMessage(msg: Message): msg is PingMessage { function QueryParamProcessor() { const dispatch = useAppDispatch(); const [searchParams] = useSearchParams(); + const { data: user, isLoading, isError } = useGetMeQuery(); useEffect(() => { - const worker_id = searchParams.get("workerId"); const assignment_id = searchParams.get("assignmentId"); const tsid = searchParams.get("tsid"); - const turkSubmitTo = searchParams.get("turkSubmitTo"); - if (turkSubmitTo) { - dispatch(setTurkSubmitTo(turkSubmitTo)); - } - - if (worker_id) { - dispatch(setProductUserID(worker_id)); + if (user?.product_user_id) { + dispatch(setProductUserID(user.product_user_id)); new WalletApi() .getUserWalletBalanceProductIdWalletGet( bpid, // productId - worker_id, // bpuid + user.product_user_id, ) .then((res) => { const response = res.data as UserWalletBalanceResponse; diff --git a/jb-ui/src/components/Footer.tsx b/jb-ui/src/components/Footer.tsx index eede06c..ec458f2 100644 --- a/jb-ui/src/components/Footer.tsx +++ b/jb-ui/src/components/Footer.tsx @@ -1,8 +1,7 @@ -import { useAppSelector } from "@/hooks"; +import { useGetMeQuery } from "@/models/authAPI"; const Footer = () => { - const bpuid = useAppSelector((state) => state.app.bpuid); - const assignment_id = useAppSelector((state) => state.app.assignment_id); + const { data: user, isLoading, isError } = useGetMeQuery(); return (
{

help:{" "} support@jamesbillings67.com diff --git a/jb-ui/src/components/Profiling.tsx b/jb-ui/src/components/Profiling.tsx index 96b06b2..23b8457 100644 --- a/jb-ui/src/components/Profiling.tsx +++ b/jb-ui/src/components/Profiling.tsx @@ -1,4 +1,5 @@ import { UpkQuestionChoice } from "@/api_fsb"; +import { useGetMeQuery } from "@/models/authAPI"; import { ChevronRightIcon } from "lucide-react"; import { KeyboardEvent, MouseEvent, useEffect, useRef, useState } from "react"; @@ -46,7 +47,7 @@ const TextEntry = () => { // const selectAnswer = useSelector(selectAnswerForQuestion(question)); const metadata = activeQuestion?._metadata; const validation = activeQuestion?._validation; - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); if (!metadata) return null; if (!validation) return null; @@ -67,11 +68,14 @@ const TextEntry = () => { const handleKeyPress = (event: KeyboardEvent) => { if (!activeQuestion.question_id) return; - if (!bpuid) return; + if (!user?.product_user_id) return; if (event.key === "Enter") { dispatch( - saveAnswer({ questionId: activeQuestion.question_id!, bpuid: bpuid }), + saveAnswer({ + questionId: activeQuestion.question_id!, + bpuid: user.product_user_id, + }), ); } }; @@ -109,14 +113,14 @@ export default function useKeyboardMode(rowModel: RowModel) { const dispatch = useAppDispatch(); const activeQuestion = useSelector(selectActiveQuestion); - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); const keyboardSelectionIdxRef = useRef(0); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (!activeQuestion) return; - if (!bpuid) return; + if (!user?.product_user_id) return; // if (!keyboardEnabled) { // return; @@ -141,7 +145,10 @@ export default function useKeyboardMode(rowModel: RowModel) { if (event.key === "Enter") { dispatch( - saveAnswer({ questionId: activeQuestion.question_id!, bpuid: bpuid }), + saveAnswer({ + questionId: activeQuestion.question_id!, + bpuid: user.product_user_id, + }), ); } @@ -507,15 +514,18 @@ const ProfilingQuestionSubmitButton = () => { const activeQuestion = useSelector(selectActiveQuestion); const activeQuestionValidation = useSelector(selectActiveQuestionValidation); - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); const handleOnClick = () => { if (!activeQuestion) return; if (!activeQuestionValidation?.isComplete) return; - if (!bpuid) return; + if (!user?.product_user_id) return; dispatch( - saveAnswer({ questionId: activeQuestion.question_id!, bpuid: bpuid }), + saveAnswer({ + questionId: activeQuestion.question_id!, + bpuid: user.product_user_id, + }), ); }; @@ -749,14 +759,14 @@ const Profiling = () => { const [showProfiling, setShowProfiling] = useState(false); const dispatch = useAppDispatch(); - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); useEffect(() => { - if (!bpuid) return; - dispatch(fetchNewQuestions(bpuid)); - }, [bpid, bpuid]); + if (!user?.product_user_id) return; + dispatch(fetchNewQuestions(user.product_user_id)); + }, [bpid, user?.product_user_id]); - if (!bpuid) return null; + if (!user?.product_user_id) return null; return (

{ const WalletTransactions = () => { const dispatch = useAppDispatch(); - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); const userLedgerTxs = useAppSelector((state) => state.app.userLedgerTxs); const txPagination = useAppSelector((state) => state.app.txPagination); const txTotalPages = useAppSelector((state) => state.app.txTotalPages); useEffect(() => { - if (!bpuid) return; + if (!user?.product_user_id) return; new WalletApi() .getUserTransactionHistoryProductIdTransactionHistoryGet( bpid, // productId - bpuid, // bpuid + user.product_user_id, undefined, // createdAfter undefined, // createdBefore "-created", // orderBy @@ -215,7 +216,7 @@ const WalletTransactions = () => { dispatch(setTxTotalItems(response.total!)); dispatch(setTxTotalPages(response.pages!)); }); - }, [bpuid, txPagination.pageIndex, txPagination.pageSize]); + }, [user?.product_user_id, txPagination.pageIndex, txPagination.pageSize]); const columnHelper = createColumnHelper(); @@ -438,16 +439,16 @@ const WalletTransactions = () => { const Wallet = () => { const dispatch = useAppDispatch(); - const bpuid = useAppSelector((state) => state.app.bpuid); const pagination = useAppSelector((state) => state.app.txPagination); + const { data: user, isLoading, isError } = useGetMeQuery(); useEffect(() => { - if (!bpuid) return; + if (!user?.product_user_id) return; new WalletApi() .getUserTransactionHistoryProductIdTransactionHistoryGet( bpid, // productId - bpuid, // bpuid + user.product_user_id, undefined, // createdAfter undefined, // createdBefore "-created", // orderBy @@ -468,9 +469,9 @@ const Wallet = () => { dispatch(setTxTotalItems(response.total ?? 0)); dispatch(setTxTotalPages(response.pages ?? 9)); }); - }, [bpuid]); + }, [user?.product_user_id]); - if (!bpuid) return null; + if (!user?.product_user_id) return null; return (
({ - getMe: builder.query({ + getMe: builder.query({ query: () => "/session", providesTags: ["User"], }), diff --git a/jb-ui/src/pages/Preview.tsx b/jb-ui/src/pages/Preview.tsx index 6e50b0d..1926975 100644 --- a/jb-ui/src/pages/Preview.tsx +++ b/jb-ui/src/pages/Preview.tsx @@ -95,7 +95,6 @@ export const Leaderboards = () => { const [payouts, setPayouts] = useState(null); const [completes, setCompletes] = useState(null); - // const bpuid = useAppSelector(state => state.app.bpuid) const bpuid = undefined; useEffect(() => { @@ -105,7 +104,7 @@ export const Leaderboards = () => { LeaderboardCode.CompleteCount, frequency, "us", - bpuid, // bpuid + bpuid, undefined, // within_time 10, ) diff --git a/jb-ui/src/pages/Transfer.tsx b/jb-ui/src/pages/Transfer.tsx index bef40d4..4192eee 100644 --- a/jb-ui/src/pages/Transfer.tsx +++ b/jb-ui/src/pages/Transfer.tsx @@ -1,5 +1,6 @@ import { type FormEvent, useState } from "react"; import { useAppDispatch, useAppSelector } from "@/hooks"; +import { useGetMeQuery } from "@/models/authAPI"; import { Button } from "@/components/ui/button"; import max_lulu from "./max-and-lulu.jpg"; @@ -43,7 +44,7 @@ type RequestState = "idle" | "working" | "sent" | "failed"; const TransferSubmit = function ({ onSubmitSuccess }) { const [email, setEmail] = useState(""); const [state, setState] = useState("idle"); - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); const submit = async (event: FormEvent) => { event.preventDefault(); @@ -56,7 +57,7 @@ const TransferSubmit = function ({ onSubmitSuccess }) { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: email, - amt_worker_id: bpuid, + amt_worker_id: user?.product_user_id, }), }); @@ -132,10 +133,10 @@ const TransferSubmit = function ({ onSubmitSuccess }) { const Transfer = function () { const assignment_id = useAppSelector((state) => state.app.assignment_id); - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); const [submitted, setSubmitted] = useState(false); - if (!bpuid) { + if (!user?.product_user_id) { return (

No Worker ID found. Please access this page through the Amazon diff --git a/jb-ui/src/pages/Work.tsx b/jb-ui/src/pages/Work.tsx index e33328e..84cb946 100644 --- a/jb-ui/src/pages/Work.tsx +++ b/jb-ui/src/pages/Work.tsx @@ -51,8 +51,7 @@ import Markdown from "react-markdown"; const Report = ({ onClose }: { onClose: () => void }) => { const [showComplete, setShowComplete] = useState(false); const [selectedValues, setSelectedValues] = useState([]); - - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); const ts = useAppSelector((state) => state.app.taskStatus); const handleCheckboxChange = (value: ReportValue, checked: boolean) => { @@ -62,12 +61,12 @@ const Report = ({ onClose }: { onClose: () => void }) => { }; const clicked_submit = () => { - if (!bpuid) return; + if (!user?.product_user_id) return; // ReportValue.NUMBER_0 console.log("selectedValues:", selectedValues); const rt: ReportTask = { - bpuid: bpuid, + bpuid: user.product_user_id, reasons: selectedValues, notes: "", }; @@ -122,9 +121,10 @@ const Report = ({ onClose }: { onClose: () => void }) => { return

Please finish a Task to report its status.

; } - if (showComplete) { - return ; - } + // TODO! What do we do here, redirect back to / with a thank you + wallet refresh? + // if (showComplete) { + // return ; + // } return (
@@ -189,43 +189,6 @@ const Report = ({ onClose }: { onClose: () => void }) => { ); }; -const SubimtAMT = () => { - const bpuid = useAppSelector((state) => state.app.bpuid); - const assignment_id = useAppSelector((state) => state.app.assignment_id); - const ts = useAppSelector((state) => state.app.taskStatus); - const turkSubmitTo = useAppSelector((state) => state.app.turkSubmitTo); - - const formRef = useRef(null); - - useEffect(() => { - if (!ts) return; - if (!bpuid) return; - if (!assignment_id) return; - if (!turkSubmitTo) return; - - if (formRef.current) { - formRef.current.submit(); - } - }, [ts?.tsid, bpuid, assignment_id, turkSubmitTo]); - - return ( - <> -
- - - - - -
- - ); -}; - const SurveyMonitor = () => { const dispatch = useAppDispatch(); @@ -235,7 +198,7 @@ const SurveyMonitor = () => { const [showComplete, setShowComplete] = useState(false); const [showReport, setShowReport] = useState(false); - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); const entered = useAppSelector((state) => state.app.currentBucketEntered); const isLow = useAppSelector((state) => isLowBalance(state)); @@ -263,7 +226,7 @@ const SurveyMonitor = () => { // }, [ts]); useEffect(() => { - if (!bpuid) return; + if (!user?.product_user_id) return; if (!entered) return; if (!isPolling) return; @@ -271,7 +234,7 @@ const SurveyMonitor = () => { new StatusApi() .listTaskStatusesProductIdStatusGet( bpid, // productId - bpuid, // Worker ID + user.product_user_id, // Worker ID entered, // startedAfter moment.utc().unix(), // startedBefore ) @@ -418,8 +381,9 @@ const SurveyMonitor = () => { Report Survey
- - {showComplete && } + {/* TODO! What do we do here, redirect back to / with wallet */} + {/* TODO! refresh? The confetti is on a different page. */} + {/* {showComplete && } */} {showReport && setShowReport(false)} />} ) : ( @@ -731,7 +695,7 @@ const BucketDetails = () => { const SurveyEnter = () => { const dispatch = useAppDispatch(); const loi = useAppSelector((state) => state.app.loi); - const bpuid = useAppSelector((state) => state.app.bpuid); + const { data: user, isLoading, isError } = useGetMeQuery(); const available_cnt = useAppSelector((state) => getAvailabilityCount(state)); const buckets = useAppSelector((state) => state.app.currentBuckets); @@ -739,12 +703,12 @@ const SurveyEnter = () => { const isLow = useAppSelector((state) => isLowBalance(state)); useEffect(() => { - if (!bpuid) return; + if (!user?.product_user_id) return; new OfferwallApi() .topNPlusBlockOfferwallProductIdOfferwallD48cce47Get( bpid, // productId - bpuid, // bpuid + user.product_user_id, undefined, // ip undefined, // countryIso undefined, // languages @@ -766,7 +730,7 @@ const SurveyEnter = () => { dispatch(setCurrentBuckets(top_n_res.buckets)); dispatch(setOfferwallReasons(top_n_res.offerwall_reasons ?? [])); }); - }, [loi, bpuid]); + }, [loi, user?.product_user_id]); const start_survey = () => { if (!bucket?.uri) return; @@ -834,7 +798,6 @@ type RequestState = "idle" | "working" | "sent" | "failed"; const Login = () => { const [email, setEmail] = useState(""); const [state, setState] = useState("idle"); - const bpuid = useAppSelector((state) => state.app.bpuid); // const dispatch = useAppDispatch(); const submit = async (event: FormEvent) => { diff --git a/jb-ui/src/types/authApi.ts b/jb-ui/src/types/authApi.ts new file mode 100644 index 0000000..19aeee0 --- /dev/null +++ b/jb-ui/src/types/authApi.ts @@ -0,0 +1,6 @@ +export interface User { + email: string; + display_name: string | null; + blocked: boolean; + product_user_id: string; +} -- cgit v1.2.3 From b2261f9101480afb31322cf620f028cdade0671c Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Sat, 12 Sep 2026 14:30:31 -0700 Subject: new landing page that combines leaderboard + login. fixing worker_id assignment, it'll still be needed until transfer is finished --- jb-ui/src/JBApp.tsx | 13 +++- jb-ui/src/pages/Preview.tsx | 142 +++++++++++++++++++++++++++++++++++-------- jb-ui/src/pages/Transfer.tsx | 13 +--- jb-ui/src/pages/Work.tsx | 78 +----------------------- 4 files changed, 131 insertions(+), 115 deletions(-) (limited to 'jb-ui/src/pages/Work.tsx') diff --git a/jb-ui/src/JBApp.tsx b/jb-ui/src/JBApp.tsx index a69cc85..6c31164 100644 --- a/jb-ui/src/JBApp.tsx +++ b/jb-ui/src/JBApp.tsx @@ -78,6 +78,11 @@ function QueryParamProcessor() { 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)); @@ -119,7 +124,10 @@ function QueryParamProcessor() { const Layout = () => { const location = useLocation(); - const isSpecialSubview = useMatch("/work/*"); + + const isWork = useMatch("/work*"); + const isPreview = useMatch("/preview*"); + const isSpecialSubview = Boolean(isWork || isPreview); return (
@@ -225,9 +233,8 @@ function JBApp() { }> } /> } /> - {/* } /> */} - } /> + } /> } /> } /> diff --git a/jb-ui/src/pages/Preview.tsx b/jb-ui/src/pages/Preview.tsx index 1926975..85e49f2 100644 --- a/jb-ui/src/pages/Preview.tsx +++ b/jb-ui/src/pages/Preview.tsx @@ -5,6 +5,7 @@ import { LeaderboardFrequency, LeaderboardRow, } from "@/api_fsb"; +import { Button } from "@/components/ui/button"; import { useAppSelector } from "@/hooks"; import { bpid, formatCentsToUSD, truncate } from "@/lib/utils"; import { activeSurveys, activeUsers, maxPayout } from "@/models/grlStatsSlice"; @@ -244,6 +245,98 @@ export const Leaderboards = () => { ); }; +type RequestState = "idle" | "working" | "failed"; + +const LoginForm = function ({ onSubmitSuccess }) { + const [email, setEmail] = useState(""); + const [state, setState] = useState("idle"); + + // const dispatch = useAppDispatch(); + const submit = async (event: FormEvent) => { + event.preventDefault(); + setState("working"); + + try { + const response = await fetch("/auth/magic-link/request", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: email, + }), + }); + + if (!response.ok) throw new Error("Account-link request failed"); + const body = (await response.json()) as { magic_link?: string }; + console.log(body.magic_link ?? null); + onSubmitSuccess(); + } catch { + setState("failed"); + } + }; + + return ( + <> +
+
+ setEmail(event.target.value)} + placeholder="hello@jamesbillings67.com" + aria-label="Email address" + /> + + +
+
+ + {state === "failed" &&

Unable to send the link. Please try again.

} + + ); +}; + +const LoginSent = function () { + return ( +
+

+ Please check your email. +

+

+ If you don't recieve anything, please check your spam folder, or try + again shortly. +

+
+ ); +}; + +const Login = () => { + const [submitted, setSubmitted] = useState(false); + + return ( + <> + {submitted ? ( + + ) : ( + setSubmitted(true)} /> + )} + + ); +}; + // Preview Component const Preview = () => { /** @@ -267,33 +360,32 @@ const Preview = () => { ); return ( -
-
-

- {users_f} people are actively attempting {surveys_f} available surveys - right now. -

-
+ <> + + +
+
+

+ {users_f} people are actively attempting {surveys_f} available + surveys right now. +

+
- - -
-

- 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. -

-

- Each HIT is paid when you attempt to take a survey and if you complete - the survey, you will be awarded a bonus equal to that in which the - survey pays out. -

+ + +
+

+ 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. +

+
-
+ ); }; export default Preview; diff --git a/jb-ui/src/pages/Transfer.tsx b/jb-ui/src/pages/Transfer.tsx index 4192eee..0d174c1 100644 --- a/jb-ui/src/pages/Transfer.tsx +++ b/jb-ui/src/pages/Transfer.tsx @@ -132,11 +132,11 @@ const TransferSubmit = function ({ onSubmitSuccess }) { }; const Transfer = function () { - const assignment_id = useAppSelector((state) => state.app.assignment_id); + const bpuid = useAppSelector((state) => state.app.bpuid); const { data: user, isLoading, isError } = useGetMeQuery(); const [submitted, setSubmitted] = useState(false); - if (!user?.product_user_id) { + if (!bpuid) { return (

No Worker ID found. Please access this page through the Amazon @@ -145,15 +145,6 @@ const Transfer = function () { ); } - if (!assignment_id) { - return ( -

- No Assignment ID found. Please access this page through the Amazon - Mechanical Turk platform. -

- ); - } - return ( <> {submitted ? ( diff --git a/jb-ui/src/pages/Work.tsx b/jb-ui/src/pages/Work.tsx index 84cb946..dbe3601 100644 --- a/jb-ui/src/pages/Work.tsx +++ b/jb-ui/src/pages/Work.tsx @@ -11,6 +11,7 @@ import { TaskStatusResponse, TopNPlusBlockOfferWall, } from "@/api_fsb"; +import Preview from "@/pages/Preview"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; @@ -793,81 +794,6 @@ const SurveyEnter = () => { ); }; -type RequestState = "idle" | "working" | "sent" | "failed"; - -const Login = () => { - const [email, setEmail] = useState(""); - const [state, setState] = useState("idle"); - - // const dispatch = useAppDispatch(); - const submit = async (event: FormEvent) => { - event.preventDefault(); - setState("working"); - - try { - const response = await fetch("/auth/magic-link/request", { - method: "POST", - credentials: "include", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - email: email, - }), - }); - - if (!response.ok) throw new Error("Account-link request failed"); - - const body = (await response.json()) as { magic_link?: string }; - console.log(body.magic_link ?? null); - } catch { - setState("failed"); - } - }; - - return ( -
-

- Please Login -

- -
- setEmail(event.target.value)} - placeholder="hello@jamesbillings67.com" - /> - -
- - {state === "sent" && ( -

- Check your email to login. -

- )} - - {state === "failed" &&

Unable to send the link. Please try again.

} -
- ); -}; - const Work = () => { const entered = useAppSelector((state) => state.app.currentBucketEntered); // const assignment_id = useAppSelector((state) => state.app.assignment_id); @@ -886,7 +812,7 @@ const Work = () => { if (isLoading) return
Loading...
; if (isError || !user) { - return ; + return ; } return <>{entered ? : }; -- cgit v1.2.3 From 83821dff9984fb950e53f23baf99d7a74fb88abd Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Sun, 13 Sep 2026 10:41:49 -0700 Subject: refrences to HIT removed. Still can't test. Basic CashoutMethod retrival until backup updated. use of the new Wallets endpoint. --- jb-ui/src/JBApp.tsx | 69 +++++++++++++++++++++++++++------ jb-ui/src/components/Redemption.tsx | 67 ++++++++++++++++++++++++++++++++ jb-ui/src/components/Wallet.tsx | 20 ++++++---- jb-ui/src/models/app.ts | 8 ++++ jb-ui/src/models/appSlice.ts | 30 ++++++++++++++ jb-ui/src/models/cashoutMethodsSlice.ts | 37 ++++++++++++++++++ jb-ui/src/pages/Preview.tsx | 8 ++-- jb-ui/src/pages/Result.tsx | 18 +++------ jb-ui/src/pages/Work.tsx | 33 +++++++--------- jb-ui/src/store.ts | 2 + 10 files changed, 239 insertions(+), 53 deletions(-) create mode 100644 jb-ui/src/components/Redemption.tsx create mode 100644 jb-ui/src/models/cashoutMethodsSlice.ts (limited to 'jb-ui/src/pages/Work.tsx') 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,17 +86,16 @@ 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 @@ -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 = () => { > +
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 ( + <> +

+ {/* {cashout_methods.length} Redemption Methods */} + 30+ Redemption Methods +

+ + ); +}; + +const RedemptionModalBody = () => { + return ( +
+
+ Paypal, Visa, Giftcards will be available. Finishing processing the + transfer from Amazon. +
+
+ ); +}; + +const Redemption = () => { + const dispatch = useAppDispatch(); + const { data: user, isLoading, isError } = useGetMeQuery(); + const [showRedemption, setShowRedemption] = useState(false); + + return ( +
+
setShowRedemption(!showRedemption)} + > + + +

+ {/* (Click to {showRedemption ? "hide" : "show"}) */} + Details within 24hr. +

+
+ + {showRedemption && } +
+ ); +}; + +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 = () => { User Wallet: {amount >= 0 ? ( - - {`${formatCentsToUSD(Math.abs(amount))}`} - + {`${amount_string}`} ) : ( - - {`(${formatCentsToUSD(Math.abs(amount))})`} - + {`(${amount_string})`} )}
@@ -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, + ) { + state.userDisplayedWalletBalance = action.payload; + }, + + setUserWallet(state, action: PayloadAction) { + state.userWallet = action.payload; + }, + + setUserAttemptCreditWallet(state, action: PayloadAction) { + state.userAttemptCreditWallet = action.payload; + }, + + // --- --- + setUserLedgerSummary( state, action: PayloadAction, @@ -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.

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 = () => {

{ts?.user_payout_string}

-

- You may close this tab and return to submit the HIT -

+

You may close this tab

); }; @@ -35,9 +33,7 @@ export const Enter = () => {

You're currently in the Survey

-

- You may close this tab and return to submit the HIT -

+

You may close this tab

); }; @@ -50,13 +46,11 @@ export const InComplete = () => { return ( <> -

HIT Complete

+

Attempt Complete

Survey terminated the attempt.

-

- You may close this tab and return to submit the HIT -

+

You may close this tab

); }; @@ -68,9 +62,7 @@ export const Unknown = () => {

You're not currently eligible for any Surveys

-

- You may close this tab and return to submit the HIT -

+

You may close this tab

); }; 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 @@ -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.

)} @@ -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. + + Click to Refresh and Try Again +

@@ -368,7 +371,7 @@ const SurveyMonitor = () => { hover:cursor-pointer hover:bg-emerald-400 hover:text-emerald-50" onClick={() => setShowComplete(true)} > - Submit HIT + Finish Attempt