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