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/Home.tsx | 12 +- jb-ui/src/pages/LinkAmtAccount.tsx | 131 ++-- jb-ui/src/pages/MagicLink.tsx | 72 +- jb-ui/src/pages/Preview.tsx | 523 ++++++------- jb-ui/src/pages/Result.tsx | 206 ++--- jb-ui/src/pages/Transfer.tsx | 50 ++ jb-ui/src/pages/Work.tsx | 1452 +++++++++++++++++++----------------- 7 files changed, 1304 insertions(+), 1142 deletions(-) create mode 100644 jb-ui/src/pages/Transfer.tsx (limited to 'jb-ui/src/pages') diff --git a/jb-ui/src/pages/Home.tsx b/jb-ui/src/pages/Home.tsx index 5a5c953..8d509b6 100644 --- a/jb-ui/src/pages/Home.tsx +++ b/jb-ui/src/pages/Home.tsx @@ -1,13 +1,7 @@ -import max_lulu from "./max-and-lulu.jpg" +import max_lulu from "./max-and-lulu.jpg"; const Home = function () { - return ( - Max and Lulu - ) + return Max and Lulu; }; -export default Home; \ No newline at end of file +export default Home; diff --git a/jb-ui/src/pages/LinkAmtAccount.tsx b/jb-ui/src/pages/LinkAmtAccount.tsx index 0c9dab3..a72626f 100644 --- a/jb-ui/src/pages/LinkAmtAccount.tsx +++ b/jb-ui/src/pages/LinkAmtAccount.tsx @@ -5,69 +5,80 @@ import { Button } from "@/components/ui/button"; type RequestState = "idle" | "working" | "sent" | "failed" | "missing-worker"; const LinkAmtAccount = function () { - const [email, setEmail] = useState(""); - const [state, setState] = useState("idle"); - const [mockLink, setMockLink] = useState(null); - const amtWorkerId = new URLSearchParams(window.location.search).get("workerId"); + const [email, setEmail] = useState(""); + const [state, setState] = useState("idle"); + const [mockLink, setMockLink] = useState(null); + const amtWorkerId = new URLSearchParams(window.location.search).get( + "workerId", + ); - const submit = async (event: FormEvent) => { - event.preventDefault(); - if (!amtWorkerId) { - setState("missing-worker"); - return; - } + const submit = async (event: FormEvent) => { + event.preventDefault(); + if (!amtWorkerId) { + setState("missing-worker"); + return; + } - setState("working"); - setMockLink(null); - try { - const response = await fetch("/auth/link-amt/request", { - method: "POST", - credentials: "include", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - "email": email, - "amt_worker_id": amtWorkerId, - }), - }); - if (!response.ok) throw new Error("Account-link request failed"); - const body = await response.json() as {magic_link?: string}; - setMockLink(body.magic_link ?? null); - setState("sent"); - } catch { - setState("failed"); - } - }; + setState("working"); + setMockLink(null); + try { + const response = await fetch("/auth/link-amt/request", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: email, + amt_worker_id: amtWorkerId, + }), + }); + if (!response.ok) throw new Error("Account-link request failed"); + const body = (await response.json()) as { magic_link?: string }; + setMockLink(body.magic_link ?? null); + setState("sent"); + } catch { + setState("failed"); + } + }; - return ( -
-
-

Link your MTurk account

-

Enter the email address you want to use for James Billings.

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

Check your email to finish linking your account.

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

Unable to send the link. Please try again.

} - {state === "missing-worker" &&

This page is missing your MTurk worker ID.

} - {mockLink && ( -

- Development link: continue -

- )} -
-
- ); + return ( +
+
+

Link your MTurk account

+

Enter the email address you want to use for James Billings.

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

Check your email to finish linking your account.

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

Unable to send the link. Please try again.

+ )} + {state === "missing-worker" && ( +

This page is missing your MTurk worker ID.

+ )} + {mockLink && ( +

+ Development link:{" "} + + continue + +

+ )} +
+
+ ); }; export default LinkAmtAccount; diff --git a/jb-ui/src/pages/MagicLink.tsx b/jb-ui/src/pages/MagicLink.tsx index 4042624..eb272a6 100644 --- a/jb-ui/src/pages/MagicLink.tsx +++ b/jb-ui/src/pages/MagicLink.tsx @@ -3,42 +3,44 @@ import { useEffect, useRef, useState } from "react"; type ExchangeState = "working" | "failed" | "missing"; const MagicLink = function () { - const started = useRef(false); - const [state, setState] = useState("working"); - - useEffect(() => { - // React Strict Mode runs effects twice in development. Never redeem twice. - if (started.current) return; - started.current = true; - - const params = new URLSearchParams(window.location.search); - const token = params.get("token"); - if (!token) { - setState("missing"); - return; - } - - // Keep the credential out of browser history and subsequent Referer headers. - window.history.replaceState({}, "", window.location.pathname); - - void fetch("/auth/magic-link/exchange", { - method: "POST", - credentials: "include", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({"token": token}), - }).then((response) => { - if (!response.ok) throw new Error("Magic-link exchange failed"); - window.location.replace("/"); - }).catch(() => setState("failed")); - }, []); - - if (state === "missing") { - return

This login link is missing its token.

; + const started = useRef(false); + const [state, setState] = useState("working"); + + useEffect(() => { + // React Strict Mode runs effects twice in development. Never redeem twice. + if (started.current) return; + started.current = true; + + const params = new URLSearchParams(window.location.search); + const token = params.get("token"); + if (!token) { + setState("missing"); + return; } - if (state === "failed") { - return

This login link is invalid or has expired.

; - } - return

Signing you in…

; + + // Keep the credential out of browser history and subsequent Referer headers. + window.history.replaceState({}, "", window.location.pathname); + + void fetch("/auth/magic-link/exchange", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ token: token }), + }) + .then((response) => { + if (!response.ok) throw new Error("Magic-link exchange failed"); + window.location.replace("/"); + }) + .catch(() => setState("failed")); + }, []); + + if (state === "missing") { + return

This login link is missing its token.

; + } + if (state === "failed") { + return

This login link is invalid or has expired.

; + } + return

Signing you in…

; }; export default MagicLink; diff --git a/jb-ui/src/pages/Preview.tsx b/jb-ui/src/pages/Preview.tsx index 153b4bb..6e50b0d 100644 --- a/jb-ui/src/pages/Preview.tsx +++ b/jb-ui/src/pages/Preview.tsx @@ -1,287 +1,300 @@ import { - Leaderboard, - LeaderboardApi, - LeaderboardCode, - LeaderboardFrequency, - LeaderboardRow, + Leaderboard, + LeaderboardApi, + LeaderboardCode, + LeaderboardFrequency, + LeaderboardRow, } from "@/api_fsb"; import { useAppSelector } from "@/hooks"; -import { bpid, formatCentsToUSD, truncate, } from "@/lib/utils"; +import { bpid, formatCentsToUSD, truncate } from "@/lib/utils"; import { activeSurveys, activeUsers, maxPayout } from "@/models/grlStatsSlice"; import { clsx } from "clsx"; -import moment from 'moment'; -import { useEffect, useState } from 'react'; +import moment from "moment"; +import { useEffect, useState } from "react"; const showSmartRank = ( - item: LeaderboardRow, - index: number, - items: LeaderboardRow[] | undefined + item: LeaderboardRow, + index: number, + items: LeaderboardRow[] | undefined, ): boolean => { - /** - * Smart rank calculation - this determines if we should show the rank - * value. It's confusing to show people that multiple individuals ranked - * in the same place, so this will only display the next ranked value, eg: - * - * - 1st - * - ___ - * - 3rd - * - ___ - * - ___ - * - 6th - * - * @returns number - */ - if (!items) return false; - - const thisRank = item.rank; - if (index >= 1) { - const prevRank = items[index - 1].rank; - if (thisRank === prevRank) { - return false; - } + /** + * Smart rank calculation - this determines if we should show the rank + * value. It's confusing to show people that multiple individuals ranked + * in the same place, so this will only display the next ranked value, eg: + * + * - 1st + * - ___ + * - 3rd + * - ___ + * - ___ + * - 6th + * + * @returns number + */ + if (!items) return false; + + const thisRank = item.rank; + if (index >= 1) { + const prevRank = items[index - 1].rank; + if (thisRank === prevRank) { + return false; } - return true; + } + return true; }; interface FrequencyButtonsProps { - selected: LeaderboardFrequency; - onChange: (frequency: LeaderboardFrequency) => void; + selected: LeaderboardFrequency; + onChange: (frequency: LeaderboardFrequency) => void; } const frequency_map = [ - { label: 'Day', value: LeaderboardFrequency.Daily }, - { label: 'Week', value: LeaderboardFrequency.Weekly }, - { label: 'Month', value: LeaderboardFrequency.Monthly }, + { label: "Day", value: LeaderboardFrequency.Daily }, + { label: "Week", value: LeaderboardFrequency.Weekly }, + { label: "Month", value: LeaderboardFrequency.Monthly }, ]; const ThreeButtonToggle = ({ selected, onChange }: FrequencyButtonsProps) => { - return ( -
- {frequency_map.map((button, idx) => ( - - ))} -
- ); + return ( +
+ {frequency_map.map((button, idx) => ( + + ))} +
+ ); }; - - // Leaderboards Component export const Leaderboards = () => { - /** - * Leaderboards do not do any caching, or better state management. This is okay because - * the API is handled to take it. However, it can easily handled better client - * side rather than making the - * **/ - - const [frequency, setFrequency] = useState( - LeaderboardFrequency.Daily + /** + * Leaderboards do not do any caching, or better state management. This is okay because + * the API is handled to take it. However, it can easily handled better client + * side rather than making the + * **/ + + const [frequency, setFrequency] = useState( + LeaderboardFrequency.Daily, + ); + + const [payouts, setPayouts] = useState(null); + const [completes, setCompletes] = useState(null); + + // const bpuid = useAppSelector(state => state.app.bpuid) + const bpuid = undefined; + + useEffect(() => { + new LeaderboardApi() + .timespanLeaderboardProductIdLeaderboardTimespanBoardCodeGet( + bpid, + LeaderboardCode.CompleteCount, + frequency, + "us", + bpuid, // bpuid + undefined, // within_time + 10, + ) + .then((res) => { + setCompletes(res.data.leaderboard as Leaderboard); + }); + + new LeaderboardApi() + .timespanLeaderboardProductIdLeaderboardTimespanBoardCodeGet( + bpid, + LeaderboardCode.SumUserPayout, + frequency, + "us", + bpuid, + undefined, + 10, + ) + .then((res) => { + setPayouts(res.data.leaderboard as Leaderboard); + }); + }, [frequency]); + + let dateRange = " - "; + if (completes) { + const start = moment(completes?.start_timestamp * 1000).format( + "MMM Do, ha", ); - - const [payouts, setPayouts] = useState(null); - const [completes, setCompletes] = useState(null); - - // const bpuid = useAppSelector(state => state.app.bpuid) - const bpuid = undefined - - useEffect(() => { - new LeaderboardApi().timespanLeaderboardProductIdLeaderboardTimespanBoardCodeGet( - bpid, - LeaderboardCode.CompleteCount, - frequency, - "us", - bpuid, // bpuid - undefined, // within_time - 10) - .then(res => { - setCompletes(res.data.leaderboard as Leaderboard) - }) - - new LeaderboardApi().timespanLeaderboardProductIdLeaderboardTimespanBoardCodeGet( - bpid, - LeaderboardCode.SumUserPayout, - frequency, - "us", - bpuid, - undefined, - 10) - .then(res => { - setPayouts(res.data.leaderboard as Leaderboard) - }) - }, [frequency]); - - let dateRange = " - " - if (completes) { - const start = moment(completes?.start_timestamp * 1000).format("MMM Do, ha"); - const end = moment(completes?.end_timestamp * 1000).format("MMM Do, h:mm:ssa"); - //dateRange = `${start} – ${end} (${completes?.timezone_name})`; - dateRange = `${start} – ${end}`; - } - - const freq_label = frequency_map.find(f => f.value === frequency)?.label.toLocaleLowerCase() - - return ( -
-
- {/* Left spacer (hidden on small screens) */} -
- - {/* Centered text */} -

- Leaderboards -

- - {/* Right-aligned button group */} -
- -
-
- -
-

- {dateRange} -

-
- -
- - - - f.value === frequency) + ?.label.toLocaleLowerCase(); + + return ( +
+
+ {/* Left spacer (hidden on small screens) */} +
+ + {/* Centered text */} +

Leaderboards

+ + {/* Right-aligned button group */} +
+ +
+
+ +
+

{dateRange}

+
+ +
+
- Most earned in the past {freq_label}. -
+ + + - - - - - - - - {payouts?.rows?.map((dataItem: LeaderboardRow) => ( - + + + + + + + {payouts?.rows?.map((dataItem: LeaderboardRow) => ( + - - - - - ))} - -
+ Most earned in the past {freq_label}. +
RankBonusUser
RankBonusUser
- {dataItem.rank} - - {formatCentsToUSD(dataItem.value)} - - {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} -
- - - - - + + + + + ))} + +
- Top completes in the past {freq_label}. -
{dataItem.rank} + {formatCentsToUSD(dataItem.value)} + + {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} +
+ + + + + - - - - - - - - {completes?.rows?.map((dataItem: LeaderboardRow, index: number) => ( - + + + + + + + {completes?.rows?.map((dataItem: LeaderboardRow, index: number) => ( + - - - - - ))} - -
+ Top completes in the past {freq_label}. +
RankCompletesUser
RankCompletesUser
- {dataItem.rank ?? showSmartRank(dataItem, index, completes?.rows)} - - {dataItem.value} - - {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} -
-
-
- ); + border border-zinc-100" + > + + {dataItem.rank ?? + showSmartRank(dataItem, index, completes?.rows)} + + {dataItem.value} + + {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} + + + ))} + + + + + ); }; - - // Preview Component const Preview = () => { - /** - * - * @returns {JSX.Element} - */ - - const users = useAppSelector(state => activeUsers(state)) - const users_f: string = users?.toLocaleString() ?? " – " - - const surveys = useAppSelector(state => activeSurveys(state)) - const surveys_f: string = surveys?.toLocaleString() ?? " – " - - const survey_max_cpi = useAppSelector(state => maxPayout(state)) - const formatter = new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD', - }); - const survey_max_cpi_f: string = formatter.format((survey_max_cpi ?? 0) / 100) - - return ( -
- -
-

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

-
-
- ); + /** + * + * @returns {JSX.Element} + */ + + const users = useAppSelector((state) => activeUsers(state)); + const users_f: string = users?.toLocaleString() ?? " – "; + + const surveys = useAppSelector((state) => activeSurveys(state)); + const surveys_f: string = surveys?.toLocaleString() ?? " – "; + + const survey_max_cpi = useAppSelector((state) => maxPayout(state)); + const formatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }); + const survey_max_cpi_f: string = formatter.format( + (survey_max_cpi ?? 0) / 100, + ); + + return ( +
+
+

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

+
+
+ ); }; -export default Preview; \ No newline at end of file +export default Preview; diff --git a/jb-ui/src/pages/Result.tsx b/jb-ui/src/pages/Result.tsx index 47af248..477f77e 100644 --- a/jb-ui/src/pages/Result.tsx +++ b/jb-ui/src/pages/Result.tsx @@ -10,114 +10,124 @@ import { useEffect } from "react"; // } export const Complete = () => { - const ts = useAppSelector(state => state.app.taskStatus) - - return ( - <> -

Survey Completed

-

Congratulations! You completed the Survey.

-

{ts?.user_payout_string}

-

You may close this tab and return to submit the HIT

- - ) -} + const ts = useAppSelector((state) => state.app.taskStatus); + + return ( + <> +

Survey Completed

+

+ Congratulations! You completed the Survey. +

+

+ {ts?.user_payout_string} +

+

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

+ + ); +}; export const Enter = () => { - return ( - <> -

Entered

-

You're currently in the Survey

-

You may close this tab and return to submit the HIT

- - ) -} + return ( + <> +

Entered

+

+ You're currently in the Survey +

+

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

+ + ); +}; export const InComplete = () => { - const ts = useAppSelector(state => state.app.taskStatus) - if (!ts) { return null; } - - return ( - <> -

HIT Complete

-

Survey terminated the attempt.

-

You may close this tab and return to submit the HIT

- - - ) -} + const ts = useAppSelector((state) => state.app.taskStatus); + if (!ts) { + return null; + } + + return ( + <> +

HIT Complete

+

+ Survey terminated the attempt. +

+

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

+ + ); +}; export const Unknown = () => { - - return ( - <> -

Task Issue

-

You're not currently eligible for any Surveys

-

You may close this tab and return to submit the HIT

- - ) -} + return ( + <> +

Task Issue

+

+ You're not currently eligible for any Surveys +

+

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

+ + ); +}; export const Loading = () => { - - return ( -

Loading...

- ) -} - + return

Loading...

; +}; const Result = () => { - - const ts = useAppSelector(state => state.app.taskStatus) - - const confettiCannons = () => { - const end = Date.now() + 3 * 1000 - const colors = ["#84cc16", "#1e3a8a", "#ec4899"] - const frame = () => { - if (Date.now() > end) return - confetti({ - particleCount: 3, - angle: 60, - spread: 55, - startVelocity: 80, - gravity: 0.7, - origin: { x: 0, y: 0.8 }, - colors: colors, - }) - confetti({ - particleCount: 3, - angle: 120, - spread: 55, - startVelocity: 80, - gravity: 0.7, - origin: { x: 1, y: 0.8 }, - colors: colors, - }) - requestAnimationFrame(frame) - } - frame() + const ts = useAppSelector((state) => state.app.taskStatus); + + const confettiCannons = () => { + const end = Date.now() + 3 * 1000; + const colors = ["#84cc16", "#1e3a8a", "#ec4899"]; + const frame = () => { + if (Date.now() > end) return; + confetti({ + particleCount: 3, + angle: 60, + spread: 55, + startVelocity: 80, + gravity: 0.7, + origin: { x: 0, y: 0.8 }, + colors: colors, + }); + confetti({ + particleCount: 3, + angle: 120, + spread: 55, + startVelocity: 80, + gravity: 0.7, + origin: { x: 1, y: 0.8 }, + colors: colors, + }); + requestAnimationFrame(frame); + }; + frame(); + }; + + useEffect(() => { + if (ts?.status === 3) { + confettiCannons(); } - - useEffect(() => { - if (ts?.status === 3) { - confettiCannons() - } - }, [ts]) - - - switch (ts?.status) { - case 0: // UNKNOWN - return ; - case 1: // ENTER - return ; - case 2: // INCOMPLETE - return ; - case 3: // COMPLETE - return ; - default: - return ; - } - + }, [ts]); + + switch (ts?.status) { + case 0: // UNKNOWN + return ; + case 1: // ENTER + return ; + case 2: // INCOMPLETE + return ; + case 3: // COMPLETE + return ; + default: + return ; + } }; - -export default Result; \ No newline at end of file +export default Result; diff --git a/jb-ui/src/pages/Transfer.tsx b/jb-ui/src/pages/Transfer.tsx new file mode 100644 index 0000000..6e087d6 --- /dev/null +++ b/jb-ui/src/pages/Transfer.tsx @@ -0,0 +1,50 @@ +import { clsx } from "clsx"; + +import { useSelector } from "react-redux"; + +const Transfer = function () { + let error = []; + + const handleInputChange = ( + event: React.KeyboardEvent, + ) => {}; + + const handleKeyPress = (event: KeyboardEvent) => {}; + + const handleSubmit = (e) => { + e.preventDefault(); + console.log("Form submitted successfully!"); + }; + + return ( +
+

+ Transfer Required +

+

+ James Billings is finished 😭 Just Kidding!!! +

+ + +
+ ); +}; + +export default Transfer; 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