From 3eaa56f0306ead818f64c3d99fc6d230d9b970a4 Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Wed, 18 Feb 2026 20:42:03 -0500 Subject: HERE WE GO, HERE WE GO, HERE WE GO --- jb-ui/src/pages/Preview.tsx | 288 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 jb-ui/src/pages/Preview.tsx (limited to 'jb-ui/src/pages/Preview.tsx') diff --git a/jb-ui/src/pages/Preview.tsx b/jb-ui/src/pages/Preview.tsx new file mode 100644 index 0000000..9506664 --- /dev/null +++ b/jb-ui/src/pages/Preview.tsx @@ -0,0 +1,288 @@ +import { + Leaderboard, + LeaderboardApi, + LeaderboardCode, + LeaderboardFrequency, + LeaderboardRow, +} from "@/api_fsb"; +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'; + +const showSmartRank = ( + 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; + } + } + return true; +}; + +interface FrequencyButtonsProps { + selected: LeaderboardFrequency; + onChange: (frequency: LeaderboardFrequency) => void; +} + + +const ThreeButtonToggle = ({ selected, onChange }: FrequencyButtonsProps) => { + const buttons = [ + { label: 'Day', value: LeaderboardFrequency.Daily }, + { label: 'Week', value: LeaderboardFrequency.Weekly }, + { label: 'Month', value: LeaderboardFrequency.Monthly }, + ]; + + // rounded-l + + return ( +
+ {buttons.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 + ); + + 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}`; + } + + return ( +
+
+ {/* Left spacer (hidden on small screens) */} +
+ + {/* Centered text */} +

+ Leaderboards +

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

+ {dateRange} +

+
+ +
+ + + + + + + + + + + + {payouts?.rows?.map((dataItem: LeaderboardRow) => ( + + + + + + ))} + +
+ Most earned in the past week. +
RankBonusUser
+ {dataItem.rank} + + {formatCentsToUSD(dataItem.value)} + + {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} +
+ + + + + + + + + + + + + {completes?.rows?.map((dataItem: LeaderboardRow, index: number) => ( + + + + + + ))} + +
+ Top completes in the past week. +
RankCompletesUser
+ {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.

+
+
+ ); +}; +export default Preview; \ No newline at end of file -- cgit v1.2.3