import { useGetMeQuery } from "@/models/authAPI"; import { Link } from "react-router-dom"; import { BucketTask, OfferwallApi, OfferwallReason, ReportApi, ReportTask, ReportValue, StatusApi, TasksStatusResponse, TaskStatusResponse, TopNPlusBlockOfferWall, } from "@/api_fsb"; import Preview from "@/pages/Preview"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { 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, } from "@/models/appSlice"; import { createColumnHelper, flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; import { XIcon } from "lucide-react"; import moment from "moment/moment"; import { useEffect, useState } from "react"; import Markdown from "react-markdown"; const Report = ({ onClose }: { onClose: () => void }) => { const [, setShowComplete] = useState(false); const [selectedValues, setSelectedValues] = useState([]); const { data: user } = useGetMeQuery(); 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 (!user?.product_user_id) return; // ReportValue.NUMBER_0 console.log("selectedValues:", selectedValues); const rt: ReportTask = { bpuid: user.product_user_id, 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.

; } // TODO! What do we do here, redirect back to / with a thank you + wallet refresh? // 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} ))}
); }; const SurveyMonitor = () => { const dispatch = useAppDispatch(); const [isPolling, setIsPolling] = useState(true); // const [timeAgo, setTimeAgo] = useState(""); const [, setShowComplete] = useState(false); const [showReport, setShowReport] = useState(false); const { data: user } = useGetMeQuery(); 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 (!user?.product_user_id) return; if (!entered) return; if (!isPolling) return; const pollApi = async () => { new StatusApi() .listTaskStatusesProductIdStatusGet( bpid, // productId user.product_user_id, // 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

; } // 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.

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

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

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

You may now submit the attempt.

)}
{isBaddie ? ( <>

Quality Check Failed.

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

Click to Refresh and Try Again

) : ( <> {ts.status === 3 || !isLow ? ( <>
{/* TODO! What do we do here, redirect back to / with wallet */} {/* TODO! refresh? The confetti is on a different page. */} {/* {showComplete && } */} {showReport && setShowReport(false)} />} ) : (

Your balance is low.

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

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

Survey Loading…

); }; const NoSurveys = () => { const offerwall_reasons = useAppSelector( (state) => state.app.offerwall_reasons, ); const REASON_DISPLAY: Record< OfferwallReason, { label: string; description: string } > = { [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]); } return ( <>

No Surveys available…

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

{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 ( <>
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 { data: user } = useGetMeQuery(); 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 (!user?.product_user_id) return; new OfferwallApi() .topNPlusBlockOfferwallProductIdOfferwallD48cce47Get( bpid, // productId // "7d9240132c695c05cd0ba47e2580158719f8215b", // "71.225.51.26", user.product_user_id, 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, user?.product_user_id]); 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 && (

Your balance is low.

You may continue to attempt surveys, but you will not earn an attempt credit if your survey attempt does not result in a complete.

Attempts with a balance exceeding -$1.00 will not be issued an attempt credit.

)}

{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 { data: user, isLoading, isError } = useGetMeQuery(); // const location = useLocation(); if (isLoading) return
Loading...
; if (isError || !user) { return ; } return <>{entered ? : }; }; export default Work;