diff options
| author | Max Nanis | 2025-05-28 04:41:37 +0100 |
|---|---|---|
| committer | Max Nanis | 2025-05-28 04:41:37 +0100 |
| commit | 8caa77413ea372e5cbd2980a9922d701af359c04 (patch) | |
| tree | 9341e2f70fab6b2678fdff53c002954ef69c7b3e /src/pages | |
| download | panel-ui-8caa77413ea372e5cbd2980a9922d701af359c04.tar.gz panel-ui-8caa77413ea372e5cbd2980a9922d701af359c04.zip | |
initial commit
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/CashoutMethods.tsx | 48 | ||||
| -rw-r--r-- | src/pages/Community.tsx | 50 | ||||
| -rw-r--r-- | src/pages/Offerwall.tsx | 101 | ||||
| -rw-r--r-- | src/pages/Questions.tsx | 105 | ||||
| -rw-r--r-- | src/pages/Support.tsx | 38 |
5 files changed, 342 insertions, 0 deletions
diff --git a/src/pages/CashoutMethods.tsx b/src/pages/CashoutMethods.tsx new file mode 100644 index 0000000..86727ee --- /dev/null +++ b/src/pages/CashoutMethods.tsx @@ -0,0 +1,48 @@ +import React, {useEffect, useState} from 'react' + +import {Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle} from "@/components/ui/card.tsx"; +import {GRLWidgetSettings} from "@/Widget.tsx" +import {CashoutMethodsResponse, WalletApi} from "@/api" +import {CashoutMethod} from "@/models/CashoutMethod.ts"; + +const CashoutMethodPreview: React.FC<{ cashout_method: CashoutMethod }> = ({cashout_method}) => { + + return ( + <Card key={cashout_method.id}> + <CardHeader> + {cashout_method.name} + </CardHeader> + + <CardContent> + <img className="blur-xs grayscale" src={cashout_method.imageUrl}/> + </CardContent> + </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" + + return ( + <div className="grid grid-cols-3 gap-1 p-1"> + { + cashoutMethods.map((m, index) => { + const cm = new CashoutMethod(m); + return <CashoutMethodPreview key={index} cashout_method={cm} />; + }) + } + </div> + ); +} + +export {CashoutMethodsPage}
\ No newline at end of file diff --git a/src/pages/Community.tsx b/src/pages/Community.tsx new file mode 100644 index 0000000..88cb74d --- /dev/null +++ b/src/pages/Community.tsx @@ -0,0 +1,50 @@ +import React, {useEffect, useState} from 'react' + +import {Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle} from "@/components/ui/card.tsx"; +import {GRLWidgetSettings} from "@/Widget.tsx" +import {CashoutMethodsResponse, WalletApi} from "@/api" +import {CashoutMethod} from "@/models/CashoutMethod.ts"; + +const CashoutMethodPreview: React.FC<{ cashout_method: CashoutMethod }> = ({cashout_method}) => { + + console.log("CashoutMethodPreview", cashout_method) + + return ( + <Card key={cashout_method.id}> + <CardHeader> + {cashout_method.name} + </CardHeader> + + <CardContent> + <img className="blur-xs grayscale" src={cashout_method.imageUrl}/> + </CardContent> + </Card> + ) +} + +const CommunityPage: 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" + + return ( + <div className="grid grid-cols-3 gap-1 p-1"> + { + cashoutMethods.map((m, index) => { + const cm = new CashoutMethod(m); + return <CashoutMethodPreview key={index} cashout_method={cm} />; + }) + } + </div> + ); +} + +export {CommunityPage}
\ No newline at end of file diff --git a/src/pages/Offerwall.tsx b/src/pages/Offerwall.tsx new file mode 100644 index 0000000..acce696 --- /dev/null +++ b/src/pages/Offerwall.tsx @@ -0,0 +1,101 @@ +import React from 'react' +import {Separator} from "@/components/ui/separator" +import {Link} from '@mui/material'; + +import {Card, CardContent, CardFooter, CardHeader, CardTitle} from "@/components/ui/card.tsx"; +import {ScrollArea} from "@/components/ui/scroll-area.tsx"; +import {CheckIcon, MessageCircleQuestionIcon, XIcon} from "lucide-react" +import {SoftPairBucket} from "@/api/models/soft-pair-bucket.ts" +import {useAppSelector} from '@/hooks' + +const BucketStatus: React.FC<SoftPairBucket> = ({bucket}) => { + switch (bucket.eligibility) { + case "eligible": + return <CheckIcon/>; + case "conditional": + return <MessageCircleQuestionIcon/>; + case "ineligible": + return <XIcon/>; + } +} + +const CallToAction: React.FC<SoftPairBucket> = ({bucket}) => { + switch (bucket.eligibility) { + case "eligible": + return <Link href={bucket.uri}> + <button type="button"> + Start Survey + </button> + </Link>; + case "conditional": + return <button type="button"> + Unlock Survey + </button>; + + case "ineligible": + return <button type="button"> + Ineligible Survey + </button>; + } +} + +const Offerwall = () => { + const buckets = useAppSelector(state => state.buckets) + + return ( + + <div className="grid grid-cols-2 gap-2 p-2"> + + {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> + + </CardFooter> + + </Card> + ))} + + </div> + + ) +} + +export {Offerwall}
\ No newline at end of file diff --git a/src/pages/Questions.tsx b/src/pages/Questions.tsx new file mode 100644 index 0000000..13d31c4 --- /dev/null +++ b/src/pages/Questions.tsx @@ -0,0 +1,105 @@ +import React, {useEffect, useState} from 'react' + +import {ProfilingQuestionsApi, UpkQuestionResponse} from "@/api" +import {ProfilingQuestion} from "@/models/question.ts"; +import {setAnswer} from "@/models/questionSlice.ts" +import {UpkQuestion} from "@/api/models/upk-question.ts" +import {Card, CardContent, CardHeader} from "@/components/ui/card.tsx"; +import {useAppDispatch, useAppSelector} from "@/hooks.ts"; + + +const TextEntry: React.FC<{ question: UpkQuestion }> = ({question}) => { + const dispatch = useAppDispatch() + // const buckets = useAppSelector(state => state.buckets) + + const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { + // Don't allow any input changes after they triggered submission... + if (question._complete || question._processing) { + return + } + + // Assign the input value as an answer to the question + const newValue = event.target.value; + dispatch(setAnswer({questionId: question.questionId, val: newValue})) + }; + + return ( + <Card className="@container/card"> + <CardHeader> + {question.questionText} + </CardHeader> + + <CardContent> + <input type="text" + id="text-entry-input" + aria-describedby="" + placeholder="" + onInput={handleInputChange} + /> + <small id="text-entry-msg">{question.error_msg}</small> + </CardContent> + </Card> + ) +} + +const MultipleChoice: React.FC<{ question: UpkQuestion }> = ({question}) => { + + return ( + <Card> + <CardHeader> + {question.questionText} + </CardHeader> + + <CardContent> + <p>MultipleChoice ... </p> + </CardContent> + </Card> + ) +} + + +const ProfileQuestionFull: React.FC<{ question: UpkQuestion }> = ({question}) => { + console.log("ProfileQuestionFull", question, question) + + switch (question.questionType) { + case "TE": + return <TextEntry question={question}/> + + case "MC": + return <MultipleChoice question={question}/> + + default: + throw new Error("Incorrect Question Type provided"); + } +} + +const ProfileQuestionPreview: React.FC<{ question: UpkQuestion }> = ({question}) => { + + return ( + <div> + {question.questionText} + </div> + ) +}; + +const QuestionsPage = () => { + const questions = useAppSelector(state => state.questions) + + return ( + <div> + <p> + A total of {questions.length} questions are available. + </p> + + { + questions.forEach(q => { + return <ProfileQuestionFull key={q.questionId} question={q} className="mt-4 mb-4"/>; + }) + } + </div> + ); +} + +export { + QuestionsPage +}
\ No newline at end of file diff --git a/src/pages/Support.tsx b/src/pages/Support.tsx new file mode 100644 index 0000000..4746b77 --- /dev/null +++ b/src/pages/Support.tsx @@ -0,0 +1,38 @@ +import React, { useEffect } from 'react'; + +const ChatwootLoader = () => { + useEffect(() => { + const loadChatwoot = () => { + const BASE_URL = "https://chat.g-r-l.com"; + const script = document.createElement('script'); + script.src = `${BASE_URL}/packs/js/sdk.js`; + script.defer = true; + script.async = true; + + script.onload = () => { + if (window.chatwootSDK) { + window.chatwootSDK.run({ + websiteToken: 'dEEw3fcexQvnQ5tesJPKFjSb', + baseUrl: BASE_URL, + }); + } + }; + + document.body.appendChild(script); + }; + + loadChatwoot(); + + // Clean up script when the component unmounts + return () => { + const existingScript = document.querySelector(`script[src="https://app.chatwoot.com/packs/js/sdk.js"]`); + if (existingScript) { + document.body.removeChild(existingScript); + } + }; + }, []); + + return null; +}; + +export {ChatwootLoader};
\ No newline at end of file |
