diff options
Diffstat (limited to 'src/pages/Offerwall.tsx')
| -rw-r--r-- | src/pages/Offerwall.tsx | 285 |
1 files changed, 218 insertions, 67 deletions
diff --git a/src/pages/Offerwall.tsx b/src/pages/Offerwall.tsx index 178e762..0d0534e 100644 --- a/src/pages/Offerwall.tsx +++ b/src/pages/Offerwall.tsx @@ -1,30 +1,46 @@ -import React from 'react' -import {Separator} from "@/components/ui/separator" +import React, {useState} from 'react' +import { motion } from "framer-motion"; +import {Badge} from "@/components/ui/badge" import {Link} from '@mui/material'; -import {useSelector} from "react-redux"; - -import {Card, CardContent, CardFooter, CardHeader, CardTitle} from "@/components/ui/card.tsx"; -import {ScrollArea} from "@/components/ui/scroll-area.tsx"; -import {makeSelectQuestionsByIds, setNextQuestion, setQuestions} from "@/models/questionSlice.ts" -import {CheckIcon, MessageCircleQuestionIcon, XIcon} from "lucide-react" +import {Tabs, TabsContent} from "@/components/ui/tabs" +import {Button} from "@/components/ui/button" import { BodyOfferwallSoftpairPostProductIdOfferwall37d1da64OfferwallIdPost, + BucketTask, OfferwallApi, SoftPairBucket, UserQuestionAnswerIn } from "@/api" + +import {ColumnDef, flexRender, getCoreRowModel, useReactTable,} from "@tanstack/react-table" + +import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow,} from "@/components/ui/table" +import {useSelector} from "react-redux"; +import {Switch} from "@/components/ui/switch" +import {Card, CardContent, CardFooter} from "@/components/ui/card.tsx"; +import {makeSelectQuestionsByIds, ProfileQuestion} from "@/models/questionSlice.ts" +import {CheckIcon, MessageCircleQuestionIcon, XIcon} from "lucide-react" import {useAppDispatch, useAppSelector} from '@/hooks' import {Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger,} from "@/components/ui/sheet" import {ProfileQuestionFull} from "@/pages/Questions.tsx" -import {Answer, saveAnswer, selectAnswerForQuestion, submitAnswer} from "@/models/answerSlice.ts"; -import {assert} from "@/lib/utils.ts"; +import {Answer, selectAnswerForQuestion, submitAnswer} from "@/models/answerSlice.ts"; +import {assert, formatCentsToUSD, formatSeconds} from "@/lib/utils.ts"; +import {IQRBoxPlot} from "@/lib/snippets.tsx" import {setAvailabilityCount, setOfferwallId} from "@/models/appSlice.ts"; import {setBuckets} from "@/models/bucketSlice.ts"; +interface DataTableProps<TData, TValue> { + columns: ColumnDef<TData, TValue>[] + data: TData[] +} + const BucketStatus: React.FC<SoftPairBucket> = ({bucket}) => { switch (bucket.eligibility) { - case "eligible": - return <CheckIcon/>; + case "unconditional": + return < + CheckIcon + xlinkTitle="you are good" + />; case "conditional": return <MessageCircleQuestionIcon/>; case "ineligible": @@ -32,6 +48,72 @@ const BucketStatus: React.FC<SoftPairBucket> = ({bucket}) => { } } +const ContentsGrid: React.FC<SoftPairBucket> = ({bucket}) => { + + const columns: ColumnDef<BucketTask>[] = [ + { + accessorKey: "loi", + header: "Length", + cell: ({ getValue }) => formatSeconds(getValue() as number), + }, + { + accessorKey: "payout", + header: "Payout", + cell: ({ getValue}) => formatCentsToUSD(getValue() as number) + }, + ] + + const data: BucketTask[] = bucket.contents + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + return ( + <div className="max-h-[120px] overflow-y-auto border rounded-md"> + <Table className="text-sm [&_th]:px-2 [&_td]:px-2 [&_th]:py-1 [&_td]:py-1" > + <TableHeader> + {table.getHeaderGroups().map((headerGroup) => ( + <TableRow key={headerGroup.id}> + {headerGroup.headers.map((header) => { + return ( + <TableHead key={header.id}> + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + </TableHead> + ) + })} + </TableRow> + ))} + </TableHeader> + + <TableBody> + { + table.getRowModel().rows.map((row) => ( + <TableRow + key={row.id} + data-state={row.getIsSelected() && "selected"} + > + {row.getVisibleCells().map((cell) => ( + <TableCell key={cell.id}> + {flexRender(cell.column.columnDef.cell, cell.getContext())} + </TableCell> + ))} + </TableRow> + )) + } + </TableBody> + </Table> + </div> + ) +} + const ConditionalQuestions: React.FC<SoftPairBucket> = ({bucket}) => { const dispatch = useAppDispatch() @@ -102,14 +184,34 @@ const ConditionalQuestions: React.FC<SoftPairBucket> = ({bucket}) => { const CallToAction: React.FC<SoftPairBucket> = ({bucket}) => { switch (bucket.eligibility) { - case "eligible": - return <Link href={bucket.uri}> - <button type="button"> - Start Survey - </button> - </Link>; + case "unconditional": + return ( + <div className="absolute bottom-2 left-1/2 transform -translate-x-1/2 w-[90%]"> + <Link + href={bucket.uri} + target="_blank" + className="w-full h-8 cursor-pointer" + > + <Button + className="w-full h-8 cursor-pointer" + > + Start Survey + </Button> + </Link> + </div> + ) case "conditional": - return <ConditionalQuestions bucket={bucket}/> + // return <ConditionalQuestions bucket={bucket}/> + return ( + <div className="absolute bottom-2 left-1/2 transform -translate-x-1/2 w-[90%]"> + <Button + variant="secondary" + className="w-full h-8 cursor-pointer" + > + Check Eligibility + </Button> + </div> + ) case "ineligible": return <button type="button"> @@ -118,63 +220,112 @@ const CallToAction: React.FC<SoftPairBucket> = ({bucket}) => { } } -const Offerwall = () => { - const buckets = useAppSelector(state => state.buckets) +const ConditionalBucket: React.FC<SoftPairBucket> = ({bucket}) => { + const [tab, setTab] = useState("bucket_cta") + const toggleTab = () => setTab(tab === "bucket_cta" ? "bucket_details" : "bucket_cta") return ( - <div className="grid grid-cols-2 gap-2 p-2"> + <Card + key={bucket.id} + className="@container/card relative overflow-hidden h-full min-h-[140px] flex flex-col justify-between" + + > + + <Badge + className="absolute top-1 left-1 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" + variant="outline" + title={`There are ${bucket.contents.length.toLocaleString()} surveys in this bucket`} + >{bucket.contents.length.toLocaleString()} + </Badge> + + <div + className="absolute top-0.5 right-0.5 h-4" + > + <Switch id="mode" + checked={tab === "bucket_details"} + onCheckedChange={toggleTab}/> + </div> + + <CardContent className="flex items-center gap-2"> + <Tabs + value={tab} onValueChange={setTab} + defaultValue="bucket_cta" + className="w-full" + > + <TabsContent value="bucket_cta"> + <motion.h1 + initial={{opacity: 0, scale: 0.8, y: 10}} + animate={{opacity: 1, scale: 1, y: 0}} + transition={{ + duration: 0.25, + type: "spring", + stiffness: 100, + damping: 10, + }} + className="text-2xl font-bold text-center" + > + {formatCentsToUSD(bucket.payout)} + </motion.h1> + <motion.h2 + initial={{opacity: 0, scale: 0.8, y: 10}} + animate={{opacity: 1, scale: 1, y: 0}} + transition={{ + duration: 0.25, + type: "spring", + stiffness: 100, + damping: 10, + }} + className="text-2xl font-bold text-center" + > + {formatSeconds(bucket.loi)} + </motion.h2> - {buckets.map((bucket) => ( - <Card key={`${bucket.id}`}> - <CardHeader> - <BucketStatus bucket={bucket}/> - <CardTitle>Card 1</CardTitle> - </CardHeader> - - <CardContent className="flex items-center gap-2"> - {/*<StarIcon className="w-5 h-5 fill-[#FFD700]"/>*/} - {/*<span className="text-sm">4.5</span>*/} <CallToAction bucket={bucket}/> - </CardContent> - - <CardFooter> - {/*<PieChart width={100} height=60}>*/} - {/* <Pie*/} - {/* dataKey="p"*/} - {/* startAngle={180}*/} - {/* endAngle={0}*/} - {/* data={bucket.category}*/} - {/* cx="50%"*/} - {/* cy="50%"*/} - {/* // onMouseEnter={this.onPieEnter}*/} - {/* // outerRadius={80}*/} - {/* // label*/} - {/* />*/} - {/*</PieChart>*/} - - <ScrollArea className="w-48 rounded-md border"> - <div className="p-4"> - <h4 className="mb-4 text-sm font-medium leading-none">Tags</h4> - {bucket.contents.map((survey) => ( - <> - <div key={`${bucket.id}-${survey.id}`} className="text-sm"> - {survey.id_code} - {survey.loi} seconds - {survey.payout} cents - </div> - <Separator className="my-2"/> - </> - ))} - </div> - </ScrollArea> + </TabsContent> - </CardFooter> + <TabsContent value="bucket_details"> + <ContentsGrid bucket={bucket}></ContentsGrid> + </TabsContent> + </Tabs> - </Card> - ))} + {/*<StarIcon className="w-5 h-5 fill-[#FFD700]"/>*/} + {/*<span className="text-sm">4.5</span>*/} - </div> + </CardContent> + + <CardFooter> + {/*<PieChart width={100} height=60}>*/} + {/* <Pie*/} + {/* dataKey="p"*/} + {/* startAngle={180}*/} + {/* endAngle={0}*/} + {/* data={bucket.category}*/} + {/* cx="50%"*/} + {/* cy="50%"*/} + {/* // onMouseEnter={this.onPieEnter}*/} + {/* // outerRadius={80}*/} + {/* // label*/} + {/* />*/} + {/*</PieChart>*/} + </CardFooter> + </Card> + ) +} + +const Offerwall = () => { + const buckets = useAppSelector(state => state.buckets) + + return ( + <div className="grid grid-cols-3 gap-2 p-1"> + {buckets.map((bucket) => ( + <ConditionalBucket key={bucket.id} bucket={bucket}/> + ))} + </div> ) } -export {Offerwall}
\ No newline at end of file +export { + Offerwall +}
\ No newline at end of file |
