aboutsummaryrefslogtreecommitdiff
path: root/jb-ui/src/pages/Preview.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'jb-ui/src/pages/Preview.tsx')
-rw-r--r--jb-ui/src/pages/Preview.tsx523
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;