diff options
Diffstat (limited to 'jb-ui/src/pages/Preview.tsx')
| -rw-r--r-- | jb-ui/src/pages/Preview.tsx | 523 |
1 files changed, 268 insertions, 255 deletions
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 ( - <div className="inline-flex rounded shadow-sm" role="group"> - {frequency_map.map((button, idx) => ( - <button - type="button" - key={button.value} - onClick={() => onChange(button.value)} - className={clsx( - "px-2 py-1 text-[10px] font-medium", - "border-t border-b border-gray-200", - "hover:cursor-pointer hover:bg-zinc-200 hover:text-zinc-950", - idx === 0 && "rounded-l", - idx === frequency_map.length - 1 && "rounded-r", - selected === button.value - ? 'text-zinc-50 bg-zinc-400' - : 'text-zinc-950 bg-zinc-50', - )}> - {button.label} - </button> - ))} - </div> - ); + return ( + <div className="inline-flex rounded shadow-sm" role="group"> + {frequency_map.map((button, idx) => ( + <button + type="button" + key={button.value} + onClick={() => onChange(button.value)} + className={clsx( + "px-2 py-1 text-[10px] font-medium", + "border-t border-b border-gray-200", + "hover:cursor-pointer hover:bg-zinc-200 hover:text-zinc-950", + idx === 0 && "rounded-l", + idx === frequency_map.length - 1 && "rounded-r", + selected === button.value + ? "text-zinc-50 bg-zinc-400" + : "text-zinc-950 bg-zinc-50", + )} + > + {button.label} + </button> + ))} + </div> + ); }; - - // 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>( - 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>( + LeaderboardFrequency.Daily, + ); + + const [payouts, setPayouts] = useState<Leaderboard | null>(null); + const [completes, setCompletes] = useState<Leaderboard | null>(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<Leaderboard | null>(null); - const [completes, setCompletes] = useState<Leaderboard | null>(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 ( - <div className="my-2 mx-4 py-0"> - <div className="w-full flex items-center justify-between my-1"> - {/* Left spacer (hidden on small screens) */} - <div className="hidden md:block md:w-1/3"></div> - - {/* Centered text */} - <h3 className="text-center flex-1 md:1/3"> - Leaderboards - </h3> - - {/* Right-aligned button group */} - <div className="flex md:w-1/3 justify-end"> - <ThreeButtonToggle - selected={frequency} - onChange={setFrequency} - /> - </div> - </div> - - <div className="grid grid-cols-1 mb-2 text-zinc-700"> - <p className="text-center text-[10px] font-thin"> - {dateRange} - </p> - </div> - - <div className="grid grid-cols-1 gap-4 px-12 md:px-0 - md:grid-cols-2"> - <table className="border-collapse text-emerald-950 text-[8px] md:text-sm"> - <caption className="caption-top text-xs font-semibold"> - Most earned in the past {freq_label}. - </caption> - <thead> - <tr className=" + 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 ( + <div className="my-2 mx-4 py-0"> + <div className="w-full flex items-center justify-between my-1"> + {/* Left spacer (hidden on small screens) */} + <div className="hidden md:block md:w-1/3"></div> + + {/* Centered text */} + <h3 className="text-center flex-1 md:1/3">Leaderboards</h3> + + {/* Right-aligned button group */} + <div className="flex md:w-1/3 justify-end"> + <ThreeButtonToggle selected={frequency} onChange={setFrequency} /> + </div> + </div> + + <div className="grid grid-cols-1 mb-2 text-zinc-700"> + <p className="text-center text-[10px] font-thin">{dateRange}</p> + </div> + + <div + className="grid grid-cols-1 gap-4 px-12 md:px-0 + md:grid-cols-2" + > + <table className="border-collapse text-emerald-950 text-[8px] md:text-sm"> + <caption className="caption-top text-xs font-semibold"> + Most earned in the past {freq_label}. + </caption> + <thead> + <tr + className=" font-mono font-semibold text-center - border border-zinc-150"> - <th>Rank</th> - <th>Bonus</th> - <th>User</th> - </tr> - </thead> - <tbody className="font-mono"> - - {payouts?.rows?.map((dataItem: LeaderboardRow) => ( - <tr key={dataItem.bpuid} className=" + border border-zinc-150" + > + <th>Rank</th> + <th>Bonus</th> + <th>User</th> + </tr> + </thead> + <tbody className="font-mono"> + {payouts?.rows?.map((dataItem: LeaderboardRow) => ( + <tr + key={dataItem.bpuid} + className=" text-center - border border-zinc-100"> - <td className="font-medium"> - {dataItem.rank} - </td> - <td className="font-semibold"> - {formatCentsToUSD(dataItem.value)} - </td> - <td className="font-light"> - {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} - </td> - </tr> - ))} - </tbody> - </table> - - <table className="border-collapse border border-gray-400 - text-emerald-950 text-[8px] md:text-sm"> - <caption className="caption-top text-xs font-semibold"> - Top completes in the past {freq_label}. - </caption> - <thead> - <tr className=" + border border-zinc-100" + > + <td className="font-medium">{dataItem.rank}</td> + <td className="font-semibold"> + {formatCentsToUSD(dataItem.value)} + </td> + <td className="font-light"> + {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} + </td> + </tr> + ))} + </tbody> + </table> + + <table + className="border-collapse border border-gray-400 + text-emerald-950 text-[8px] md:text-sm" + > + <caption className="caption-top text-xs font-semibold"> + Top completes in the past {freq_label}. + </caption> + <thead> + <tr + className=" font-mono font-semibold text-center - border border-zinc-150 rounded"> - <th>Rank</th> - <th>Completes</th> - <th>User</th> - </tr> - </thead> - <tbody className="font-mono"> - - {completes?.rows?.map((dataItem: LeaderboardRow, index: number) => ( - <tr key={dataItem.bpuid} className=" + border border-zinc-150 rounded" + > + <th>Rank</th> + <th>Completes</th> + <th>User</th> + </tr> + </thead> + <tbody className="font-mono"> + {completes?.rows?.map((dataItem: LeaderboardRow, index: number) => ( + <tr + key={dataItem.bpuid} + className=" text-center - border border-zinc-100"> - <td className="font-medium"> - {dataItem.rank ?? showSmartRank(dataItem, index, completes?.rows)} - </td> - <td className="font-semibold"> - {dataItem.value} - </td> - <td className="font-light"> - {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} - </td> - </tr> - ))} - </tbody> - </table> - </div> - </div> - ); + border border-zinc-100" + > + <td className="font-medium"> + {dataItem.rank ?? + showSmartRank(dataItem, index, completes?.rows)} + </td> + <td className="font-semibold">{dataItem.value}</td> + <td className="font-light"> + {truncate(dataItem.bpuid, 6, "*****").toUpperCase()} + </td> + </tr> + ))} + </tbody> + </table> + </div> + </div> + ); }; - - // 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 ( - <div className="max-h-[calc(100vh-10rem)] overflow-y-auto"> - - <div className="bg-zinc-100 rounded py-2 m-0"> - <p className="text-center text-sm italic text-zinc-600"> - {users_f} people are actively attempting {surveys_f} available surveys right now. - </p> - </div> - - <Leaderboards /> - - <div className="bg-zinc-100 rounded py-2 px-6 m-0 text-xs text-center"> - <p className="py-1"> - 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.</p> - <p className="py-1"> - 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.</p> - </div> - </div> - ); + /** + * + * @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 ( + <div className="max-h-[calc(100vh-10rem)] overflow-y-auto"> + <div className="bg-zinc-100 rounded py-2 m-0"> + <p className="text-center text-sm italic text-zinc-600"> + {users_f} people are actively attempting {surveys_f} available surveys + right now. + </p> + </div> + + <Leaderboards /> + + <div className="bg-zinc-100 rounded py-2 px-6 m-0 text-xs text-center"> + <p className="py-1"> + 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. + </p> + <p className="py-1"> + 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. + </p> + </div> + </div> + ); }; -export default Preview;
\ No newline at end of file +export default Preview; |
