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}
-
-
- ))}
-
-
-
-
-
-
+
-
-
- Submit and Complete HIT
-
-
- )
-}
+ 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) ? (
- <>
-
-
-
+ Please return the HIT to avoid having your assignment rejected.
+
+
+ >
+ ) : (
+ <>
+ {ts.status === 3 || !isLow ? (
+ <>
+
+ setShowComplete(true)}
- >Submit HIT
-
- 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.
-
-
- )}
- >
-
- )}
-
+ 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 (
-
- )
-}
-
+ 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 (
+
- )
- })}
- >
- )
-}
+ 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 (
-
- {flexRender(
- header.column.columnDef.header,
- header.getContext(),
- )}
-
- )
- })}
-
- ))}
-
-
- {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(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 (
+
+ {flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {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(
+ 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
+
+
+
+
+ {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 (
+ <>
+
+
+ {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
-
-
-
-
- {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
{
className="
text-red-600 text-lg font-semibold text-center"
>
- Your balance is low. You cannot submit the HIT at this time.
-
-
-
- Please return the HIT to avoid having your assignment rejected.
+ Your balance is low.
)}
@@ -709,6 +704,8 @@ const SurveyEnter = () => {
new OfferwallApi()
.topNPlusBlockOfferwallProductIdOfferwallD48cce47Get(
bpid, // productId
+ // "7d9240132c695c05cd0ba47e2580158719f8215b",
+ // "71.225.51.26",
user.product_user_id,
undefined, // ip
undefined, // countryIso
@@ -760,12 +757,12 @@ const SurveyEnter = () => {
Your balance is low.
- You may continue to attempt surveys, but you will be advised to
- return the HIT if your survey attempt does not result in a complete.
+ You may continue to attempt surveys, but you will not earn an
+ attempt credit if your survey attempt does not result in a complete.
- HIT submissions with a balance exceeding -$1.00 will return in
- rejected assignments.
+ Attempts with a balance exceeding -$1.00 will not be issued an
+ attempt credit.
)}
diff --git a/jb-ui/src/store.ts b/jb-ui/src/store.ts
index baf13b7..081de9f 100644
--- a/jb-ui/src/store.ts
+++ b/jb-ui/src/store.ts
@@ -4,6 +4,7 @@ import grlEventsReducers from "@/models/grlEventsSlice";
import { authApi } from "@/models/authAPI";
import profilingQuestionsReducers from "@/models/profilingQuestionsSlice";
+import cashoutMethodReducers from "@/models/cashoutMethodsSlice";
import type { Action, ThunkAction } from "@reduxjs/toolkit";
import { configureStore } from "@reduxjs/toolkit";
@@ -15,6 +16,7 @@ export const store = configureStore({
stats: grlStatsReducers,
events: grlEventsReducers,
profilingQuestions: profilingQuestionsReducers,
+ cashoutMethods: cashoutMethodReducers,
},
middleware: (getDefault) => getDefault().concat(authApi.middleware),
});
--
cgit v1.2.3
From b464e311b44a5f3baeb1568ba55b6322a1a11638 Mon Sep 17 00:00:00 2001
From: Max Nanis
Date: Sun, 13 Sep 2026 11:43:52 -0700
Subject: Fix pnpm build error requirements
---
jb-ui/src/AuthContext.tsx | 28 ----------------------------
jb-ui/src/JBApp.tsx | 8 +-------
jb-ui/src/components/Footer.tsx | 2 +-
jb-ui/src/components/Profiling.tsx | 10 +++++-----
jb-ui/src/components/Redemption.tsx | 14 ++++----------
jb-ui/src/components/Wallet.tsx | 4 ++--
jb-ui/src/pages/Preview.tsx | 9 ++++++---
jb-ui/src/pages/Transfer.tsx | 11 +++++++----
jb-ui/src/pages/Work.tsx | 12 ++++++------
9 files changed, 32 insertions(+), 66 deletions(-)
delete mode 100644 jb-ui/src/AuthContext.tsx
(limited to 'jb-ui/src/pages/Work.tsx')
diff --git a/jb-ui/src/AuthContext.tsx b/jb-ui/src/AuthContext.tsx
deleted file mode 100644
index 85bcdb0..0000000
--- a/jb-ui/src/AuthContext.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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 4870e9b..c55cd73 100644
--- a/jb-ui/src/JBApp.tsx
+++ b/jb-ui/src/JBApp.tsx
@@ -13,7 +13,6 @@ import {
UserWalletBalanceResponse,
UserLedgerWallets,
UserLedgerWalletAccountTypeEnum,
- UserDisplayedWalletBalance,
WalletApi,
CashoutMethodsResponse,
} from "@/api_fsb";
@@ -33,9 +32,7 @@ import { addCashoutMethods } from "@/models/cashoutMethodsSlice";
import { addEvent } from "@/models/grlEventsSlice";
import { addStatsData } from "@/models/grlStatsSlice";
-import Home from "@/pages/Home";
import MagicLink from "@/pages/MagicLink";
-import Preview from "@/pages/Preview";
import Result from "@/pages/Result";
import clsx from "clsx";
@@ -44,7 +41,6 @@ import Transfer from "@/pages/Transfer";
import {
BrowserRouter,
Outlet,
- useLocation,
useMatch,
Route,
Routes,
@@ -83,7 +79,7 @@ function isPingMessage(msg: Message): msg is PingMessage {
function QueryParamProcessor() {
const dispatch = useAppDispatch();
const [searchParams] = useSearchParams();
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
useEffect(() => {
if (user?.product_user_id) {
@@ -169,8 +165,6 @@ function QueryParamProcessor() {
}
const Layout = () => {
- const location = useLocation();
-
const isWork = useMatch("/work/*");
const isPreview = useMatch("/preview/*");
const isSpecialSubview = Boolean(isWork || isPreview);
diff --git a/jb-ui/src/components/Footer.tsx b/jb-ui/src/components/Footer.tsx
index ec458f2..e65255f 100644
--- a/jb-ui/src/components/Footer.tsx
+++ b/jb-ui/src/components/Footer.tsx
@@ -1,7 +1,7 @@
import { useGetMeQuery } from "@/models/authAPI";
const Footer = () => {
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
return (
{
// const selectAnswer = useSelector(selectAnswerForQuestion(question));
const metadata = activeQuestion?._metadata;
const validation = activeQuestion?._validation;
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
if (!metadata) return null;
if (!validation) return null;
@@ -113,7 +113,7 @@ export default function useKeyboardMode(rowModel: RowModel) {
const dispatch = useAppDispatch();
const activeQuestion = useSelector(selectActiveQuestion);
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
const keyboardSelectionIdxRef = useRef(0);
@@ -514,7 +514,7 @@ const ProfilingQuestionSubmitButton = () => {
const activeQuestion = useSelector(selectActiveQuestion);
const activeQuestionValidation = useSelector(selectActiveQuestionValidation);
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
const handleOnClick = () => {
if (!activeQuestion) return;
@@ -759,7 +759,7 @@ const Profiling = () => {
const [showProfiling, setShowProfiling] = useState(false);
const dispatch = useAppDispatch();
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
useEffect(() => {
if (!user?.product_user_id) return;
diff --git a/jb-ui/src/components/Redemption.tsx b/jb-ui/src/components/Redemption.tsx
index 0a91ab3..1d76f17 100644
--- a/jb-ui/src/components/Redemption.tsx
+++ b/jb-ui/src/components/Redemption.tsx
@@ -1,14 +1,10 @@
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";
+import { useState } from "react";
+// import { useSelector } from "react-redux";
+// import { cashoutMethodsSelectors } from "@/models/cashoutMethodsSlice";
const RedemptionHeader = () => {
- const cashout_methods = useSelector(cashoutMethodsSelectors.selectAll);
+ // const cashout_methods = useSelector(cashoutMethodsSelectors.selectAll);
return (
<>
@@ -32,8 +28,6 @@ const RedemptionModalBody = () => {
};
const Redemption = () => {
- const dispatch = useAppDispatch();
- const { data: user, isLoading, isError } = useGetMeQuery();
const [showRedemption, setShowRedemption] = useState(false);
return (
diff --git a/jb-ui/src/components/Wallet.tsx b/jb-ui/src/components/Wallet.tsx
index 3bb23a9..819eff7 100644
--- a/jb-ui/src/components/Wallet.tsx
+++ b/jb-ui/src/components/Wallet.tsx
@@ -194,7 +194,7 @@ const WalletTransactionsHeader = () => {
const WalletTransactions = () => {
const dispatch = useAppDispatch();
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
const userLedgerTxs = useAppSelector((state) => state.app.userLedgerTxs);
const txPagination = useAppSelector((state) => state.app.txPagination);
@@ -446,7 +446,7 @@ const WalletTransactions = () => {
const Wallet = () => {
const dispatch = useAppDispatch();
const pagination = useAppSelector((state) => state.app.txPagination);
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
useEffect(() => {
if (!user?.product_user_id) return;
diff --git a/jb-ui/src/pages/Preview.tsx b/jb-ui/src/pages/Preview.tsx
index 900621e..c58a85e 100644
--- a/jb-ui/src/pages/Preview.tsx
+++ b/jb-ui/src/pages/Preview.tsx
@@ -5,13 +5,12 @@ 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";
import { clsx } from "clsx";
import moment from "moment";
-import { useEffect, useState } from "react";
+import { FormEvent, useEffect, useState } from "react";
const showSmartRank = (
item: LeaderboardRow,
@@ -247,7 +246,11 @@ export const Leaderboards = () => {
type RequestState = "idle" | "working" | "failed";
-const LoginForm = function ({ onSubmitSuccess }) {
+interface LoginFormProps {
+ onSubmitSuccess: () => void;
+}
+
+const LoginForm = function ({ onSubmitSuccess }: LoginFormProps) {
const [email, setEmail] = useState("");
const [state, setState] = useState("idle");
diff --git a/jb-ui/src/pages/Transfer.tsx b/jb-ui/src/pages/Transfer.tsx
index 0d174c1..8079da2 100644
--- a/jb-ui/src/pages/Transfer.tsx
+++ b/jb-ui/src/pages/Transfer.tsx
@@ -1,5 +1,5 @@
import { type FormEvent, useState } from "react";
-import { useAppDispatch, useAppSelector } from "@/hooks";
+import { useAppSelector } from "@/hooks";
import { useGetMeQuery } from "@/models/authAPI";
import { Button } from "@/components/ui/button";
import max_lulu from "./max-and-lulu.jpg";
@@ -41,10 +41,14 @@ const TransferComplete = function () {
type RequestState = "idle" | "working" | "sent" | "failed";
-const TransferSubmit = function ({ onSubmitSuccess }) {
+interface TransferSubmitProps {
+ onSubmitSuccess: () => void;
+}
+
+const TransferSubmit = function ({ onSubmitSuccess }: TransferSubmitProps) {
const [email, setEmail] = useState("");
const [state, setState] = useState("idle");
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
const submit = async (event: FormEvent) => {
event.preventDefault();
@@ -133,7 +137,6 @@ const TransferSubmit = function ({ onSubmitSuccess }) {
const Transfer = function () {
const bpuid = useAppSelector((state) => state.app.bpuid);
- const { data: user, isLoading, isError } = useGetMeQuery();
const [submitted, setSubmitted] = useState(false);
if (!bpuid) {
diff --git a/jb-ui/src/pages/Work.tsx b/jb-ui/src/pages/Work.tsx
index 50ed930..6d0b1c1 100644
--- a/jb-ui/src/pages/Work.tsx
+++ b/jb-ui/src/pages/Work.tsx
@@ -47,13 +47,13 @@ import {
} from "@tanstack/react-table";
import { XIcon } from "lucide-react";
import moment from "moment/moment";
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useState } from "react";
import Markdown from "react-markdown";
const Report = ({ onClose }: { onClose: () => void }) => {
- const [showComplete, setShowComplete] = useState(false);
+ const [, setShowComplete] = useState(false);
const [selectedValues, setSelectedValues] = useState([]);
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
const ts = useAppSelector((state) => state.app.taskStatus);
const handleCheckboxChange = (value: ReportValue, checked: boolean) => {
@@ -197,10 +197,10 @@ const SurveyMonitor = () => {
const [isPolling, setIsPolling] = useState(true);
// const [timeAgo, setTimeAgo] = useState("");
- const [showComplete, setShowComplete] = useState(false);
+ const [, setShowComplete] = useState(false);
const [showReport, setShowReport] = useState(false);
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
const entered = useAppSelector((state) => state.app.currentBucketEntered);
const isLow = useAppSelector((state) => isLowBalance(state));
@@ -691,7 +691,7 @@ const BucketDetails = () => {
const SurveyEnter = () => {
const dispatch = useAppDispatch();
const loi = useAppSelector((state) => state.app.loi);
- const { data: user, isLoading, isError } = useGetMeQuery();
+ const { data: user } = useGetMeQuery();
const available_cnt = useAppSelector((state) => getAvailabilityCount(state));
const buckets = useAppSelector((state) => state.app.currentBuckets);
--
cgit v1.2.3