diff options
| author | Max Nanis | 2025-06-09 16:05:52 +0700 |
|---|---|---|
| committer | Max Nanis | 2025-06-09 16:05:52 +0700 |
| commit | 74890e251dee3e0f195583431cb48b9f3a58ecc9 (patch) | |
| tree | a27ceee03999f18fd3ef2e0d44ba7deb39f0b6c8 /src/pages | |
| parent | a674d2e03de3bd048714d9c06e4bba9d9ecdb328 (diff) | |
| download | panel-ui-74890e251dee3e0f195583431cb48b9f3a58ecc9.tar.gz panel-ui-74890e251dee3e0f195583431cb48b9f3a58ecc9.zip | |
Cashout Methods page: adding walletSlice and cashoutmethodsSlice so they're in the stored state. Iterating with fix vs variable filters. Pulling old validators from old code and setting up the wallet fetch.
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/CashoutMethods.tsx | 207 | ||||
| -rw-r--r-- | src/pages/Offerwall.tsx | 89 |
2 files changed, 232 insertions, 64 deletions
diff --git a/src/pages/CashoutMethods.tsx b/src/pages/CashoutMethods.tsx index 9656087..fca6af0 100644 --- a/src/pages/CashoutMethods.tsx +++ b/src/pages/CashoutMethods.tsx @@ -1,45 +1,202 @@ -import React, {useEffect, useState} from 'react' +import React, {useState} from 'react' +import {Card, CardContent, CardHeader,} from "@/components/ui/card" +import {Tabs, TabsContent, TabsList, TabsTrigger,} from "@/components/ui/tabs" +import {Badge} from "@/components/ui/badge" + +import {selectFixedCashoutMethods, selectVariableCashoutMethods} from "@/models/cashoutMethodSlice.ts"; +import {CashoutMethodOut, UserWalletBalance} from "@/api" +import {formatCentsToUSD} from "@/lib/utils.ts"; +import {useSelector} from 'react-redux' +import {motion} from "framer-motion"; +import {Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle,} from "@/components/ui/drawer" +import {useAppSelector} from "@/hooks.ts"; + +const CashoutAcknowledgement = () => { + return ( + <> + <p>Your request has been successfully submitted!</p> + <div className="form-group"> + <label htmlFor="cashout-acknowledgement-transaction-id-input">Transaction ID</label> + <input type="email" className="form-control" + id="cashout-acknowledgement-transaction-id-input" + aria-describedby="transactionIdHelp" + readOnly/> + <small id="transactionIdHelp" className="form-text">Request Status: + <strong>Pending</strong></small> + </div> + <p>Your redemption link will be on the <a href="/history/">history page</a>.</p> + </> + ) +} + +const CashoutReview: React.FC<{ cashout_method: CashoutMethodOut }> = ({cashout_method}) => { + return ( + <> + <p>You are about to redeem <strong id="cashout-review-redeem-amount"></strong> to a card.</p> + <img src="<%= image_url %>" alt="..."/> + <p>{cashout_method.name}</p> + {/*<p>{data.terms}</p>*/} + <button id="cashout-review-cancel" type="button" + className="btn btn-block">Cancel + </button> + <button id="cashout-review-submit" type="button" + className="btn btn-block">Submit + </button> + <p id="cashout-review-msg"></p> + </> + ) +} + +const VariableCashoutMethodPreview: React.FC<{ cashout_method: CashoutMethodOut }> = ({cashout_method}) => { + return ( + <> + <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="font-bold text-center" + > + {cashout_method.name} + </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-center" + > + {formatCentsToUSD(cashout_method.min_value)} – {formatCentsToUSD(cashout_method.max_value)} + </motion.h2> + </> + ) +} + +const FixedCashoutMethodPreview: React.FC<{ cashout_method: CashoutMethodOut }> = ({cashout_method}) => { + return ( + <> + <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="font-bold text-center" + > + {cashout_method.name} + </motion.h1> + </> + ) +} -import {Card, CardContent, CardHeader} from "@/components/ui/card.tsx"; -import {CashoutMethodOut, CashoutMethodsResponse, WalletApi} from "@/api" const CashoutMethodPreview: React.FC<{ cashout_method: CashoutMethodOut }> = ({cashout_method}) => { + const [open, setOpen] = useState(false) + + const renderContent = () => { + switch (cashout_method.data.value_type) { + case 'fixed': + return <FixedCashoutMethodPreview cashout_method={cashout_method}/> + case 'variable': + return <VariableCashoutMethodPreview cashout_method={cashout_method}/> + } + }; + return ( - <Card key={cashout_method.id}> + <Card + key={cashout_method.id} + className="@container/card relative overflow-hidden h-full min-h-[140px] flex flex-col justify-between cursor-pointer" + > <CardHeader> - {cashout_method.name} + {renderContent()} </CardHeader> <CardContent> - <img className="blur-xs grayscale" src={cashout_method.imageUrl}/> + <img + className="grayscale blur-[1px] transition-all duration-300 hover:grayscale-0 hover:blur-none" + alt="Cashout method" + src={cashout_method.image_url}/> </CardContent> + + <Badge + className="absolute bottom-1 right-1 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" + variant="outline" + title="Cashout Details" + onClick={() => setOpen(true)} + >Details + </Badge> + + <Drawer open={open} onOpenChange={setOpen}> + <DrawerContent> + <DrawerHeader> + <DrawerTitle>{cashout_method.name} Details</DrawerTitle> + <DrawerDescription> + <div dangerouslySetInnerHTML={{__html: cashout_method.description}}/> + </DrawerDescription> + </DrawerHeader> + </DrawerContent> + </Drawer> + </Card> ) } -const CashoutMethodsPage: React.FC<GRLWidgetSettings> = ({settings}) => { - const [cashoutMethods, setCashoutMethods] = useState([]); - useEffect(() => { - const x = new WalletApi(); - x.getCashoutMethodsProductIdCashoutMethodsGet(settings.bpid, settings.bpuid) - .then(res => { - const data: CashoutMethodsResponse = res.data; - setCashoutMethods(data.cashout_methods); - }) - .catch(err => console.log(err)); - }, []); // ← empty array means "run once" +const CashoutMethodsPage = () => { + const variableCashoutMethods = useSelector(selectVariableCashoutMethods) + const fixedCashoutMethods = useSelector(selectFixedCashoutMethods) + const wallet: UserWalletBalance = useAppSelector(state => state.wallet) + return ( - <div className="grid grid-cols-3 gap-1 p-1"> - { - cashoutMethods.map((m, index) => { - return <CashoutMethodPreview key={index} cashout_method={m}/>; - }) - } - </div> - ); + <> + <p>Your balance is <strong>{formatCentsToUSD(wallet.amount)}</strong>.</p> + <p>You can redeem <strong>{formatCentsToUSD(wallet.redeemable_amount)}</strong> now.</p> + <p><small>(a portion of each survey is delayed by 30 days)</small></p> + + <Tabs defaultValue="dynamic"> + <TabsList + className="cursor-pointer" + > + <TabsTrigger value="dynamic">Variable</TabsTrigger> + <TabsTrigger value="fixed">Fixed</TabsTrigger> + </TabsList> + + <TabsContent value="dynamic"> + <div className="grid grid-cols-3 gap-1 p-1"> + { + variableCashoutMethods.map((m, index) => { + return <CashoutMethodPreview key={index} cashout_method={m}/>; + }) + } + </div> + </TabsContent> + <TabsContent value="fixed"> + <div className="grid grid-cols-3 gap-1 p-1"> + { + fixedCashoutMethods.map((m, index) => { + return <CashoutMethodPreview key={index} cashout_method={m}/>; + }) + } + </div> + </TabsContent> + </Tabs> + </> + ) } + export {CashoutMethodsPage}
\ No newline at end of file diff --git a/src/pages/Offerwall.tsx b/src/pages/Offerwall.tsx index 0d0534e..19c4515 100644 --- a/src/pages/Offerwall.tsx +++ b/src/pages/Offerwall.tsx @@ -1,5 +1,5 @@ import React, {useState} from 'react' -import { motion } from "framer-motion"; +import {motion} from "framer-motion"; import {Badge} from "@/components/ui/badge" import {Link} from '@mui/material'; import {Tabs, TabsContent} from "@/components/ui/tabs" @@ -18,14 +18,13 @@ import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow,} from "@/ 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 {makeSelectQuestionsByIds} 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, 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"; @@ -54,12 +53,12 @@ const ContentsGrid: React.FC<SoftPairBucket> = ({bucket}) => { { accessorKey: "loi", header: "Length", - cell: ({ getValue }) => formatSeconds(getValue() as number), + cell: ({getValue}) => formatSeconds(getValue() as number), }, { accessorKey: "payout", header: "Payout", - cell: ({ getValue}) => formatCentsToUSD(getValue() as number) + cell: ({getValue}) => formatCentsToUSD(getValue() as number) }, ] @@ -73,7 +72,7 @@ const ContentsGrid: React.FC<SoftPairBucket> = ({bucket}) => { 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" > + <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}> @@ -123,6 +122,7 @@ const ConditionalQuestions: React.FC<SoftPairBucket> = ({bucket}) => { const app = useAppSelector(state => state.app) console.log("Conditional bucket:", questions, question, answer) + const [open, setOpen] = useState(false) const submitEvt = () => { dispatch(submitAnswer({question: question})) @@ -153,32 +153,41 @@ const ConditionalQuestions: React.FC<SoftPairBucket> = ({bucket}) => { } return ( - <Sheet> - <SheetTrigger>Open</SheetTrigger> - <SheetContent - side="right" - className="md:w-[900px], lg:w-[1000px]"> - - <SheetHeader> - <SheetTitle>Bucket Questions</SheetTitle> - <SheetDescription> - This survey has some unanswered questions. Answer these to determine if you're - eligible for the Survey Bucket - </SheetDescription> - </SheetHeader> - - { - questions.map(q => { - return <ProfileQuestionFull - key={q.question_id} - question={q} - submitAnswerEvt={submitEvt} - className="mt-4 m-2"/> - }) - } + <> + <Button + variant="secondary" + className="w-full h-8 cursor-pointer" + onClick={() => setOpen(true)} + > + Check Eligibility + </Button> + + <Sheet open={open} onOpenChange={setOpen}> + <SheetContent + side="right" + className="md:w-[900px], lg:w-[1000px] p-5"> + + <SheetHeader> + <SheetTitle>Bucket Questions</SheetTitle> + <SheetDescription> + This survey has some unanswered questions. Answer these to determine if you're + eligible for the Survey Bucket + </SheetDescription> + </SheetHeader> - </SheetContent> - </Sheet> + { + questions.map(q => { + return <ProfileQuestionFull + key={q.question_id} + question={q} + submitAnswerEvt={submitEvt} + className="mt-4 m-2"/> + }) + } + + </SheetContent> + </Sheet> + </> ) } @@ -201,22 +210,24 @@ const CallToAction: React.FC<SoftPairBucket> = ({bucket}) => { </div> ) case "conditional": - // return <ConditionalQuestions bucket={bucket}/> + return ( + <div className="absolute bottom-2 left-1/2 transform -translate-x-1/2 w-[90%]"> + <ConditionalQuestions bucket={bucket}/> + </div> + ) + + case "ineligible": 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" + variant="outline" + disabled > - Check Eligibility + Sorry, you're ineligible </Button> </div> ) - - case "ineligible": - return <button type="button"> - Ineligible Survey - </button>; } } |
