From e5dac8b9d61b175b09d859643a94ea91ee6ef48d Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Tue, 10 Jun 2025 02:51:37 +0700 Subject: Demographics page. userMarketplaceAnswer & userUpkAnswer redux stores. --- src/Widget.tsx | 19 ++++- src/components/nav-main.tsx | 18 ++++- src/lib/utils.ts | 7 ++ src/models/app.ts | 2 +- src/models/marketplaceAnswerSlice.ts | 24 ------ src/models/upkAnswerSlice.ts | 23 ------ src/models/userMarketplaceAnswerSlice.ts | 27 +++++++ src/models/userUpkAnswerSlice.ts | 54 ++++++++++++++ src/pages/Demographics.tsx | 122 +++++++++++++++++++++++++++++++ src/store.ts | 9 ++- 10 files changed, 249 insertions(+), 56 deletions(-) delete mode 100644 src/models/marketplaceAnswerSlice.ts delete mode 100644 src/models/upkAnswerSlice.ts create mode 100644 src/models/userMarketplaceAnswerSlice.ts create mode 100644 src/models/userUpkAnswerSlice.ts create mode 100644 src/pages/Demographics.tsx diff --git a/src/Widget.tsx b/src/Widget.tsx index dc24bea..e1522c0 100644 --- a/src/Widget.tsx +++ b/src/Widget.tsx @@ -4,9 +4,19 @@ import {SiteHeader} from "@/components/site-header" import {SidebarInset, SidebarProvider} from "@/components/ui/sidebar" import {Offerwall} from "@/pages/Offerwall.tsx" import {QuestionsPage} from "@/pages/Questions.tsx"; +import {Demographics} from "@/pages/Demographics.tsx" import {useAppDispatch, useAppSelector} from "@/hooks.ts"; -import {CashoutMethodOut, OfferwallApi, ProfilingQuestionsApi, QuestionInfo, UserWalletBalance, WalletApi} from "@/api"; +import { + CashoutMethodOut, + MarketProfileKnowledge, + OfferwallApi, + ProfilingQuestionsApi, + QuestionInfo, + UserProfileKnowledge, + UserWalletBalance, + WalletApi +} from "@/api"; import {ProfileQuestion, setQuestions} from "@/models/questionSlice.ts"; import {setBuckets} from "@/models/bucketSlice.ts"; import {setCashoutMethods} from "@/models/cashoutMethodSlice.ts"; @@ -14,6 +24,8 @@ import {setWallet} from "@/models/walletSlice.ts" import {CashoutMethodsPage} from "@/pages/CashoutMethods.tsx"; import {setUpkQuestions} from "@/models/upkQuestionSlice.ts" import {setAvailabilityCount, setOfferwallId} from "@/models/appSlice.ts" +import {setUpkAnswers} from "@/models/userUpkAnswerSlice.ts"; +import {setMarketplaceAnswers} from "@/models/userMarketplaceAnswerSlice.ts"; import './index.css'; @@ -46,8 +58,8 @@ const Widget = () => { new ProfilingQuestionsApi().userProfileProductIdUserProfileGet(app.bpid, app.bpuid, "us") .then(res => { - console.log("Marketplace Profile", res.data["user-profile"].marketplace_profile_knowledge) - console.log("UPK Profile", res.data["user-profile"].user_profile_knowledge) + dispatch(setMarketplaceAnswers(res.data["user-profile"].marketplace_profile_knowledge as MarketProfileKnowledge[])) + dispatch(setUpkAnswers(res.data["user-profile"].user_profile_knowledge as UserProfileKnowledge[])) }).catch(err => console.log(err)) new ProfilingQuestionsApi().profilingInfoProductIdProfilingInfoGet(app.bpid, "us") @@ -86,6 +98,7 @@ const Widget = () => { {app.currentPage === 'offerwall' && } {app.currentPage === 'questions' && } {app.currentPage === 'cashout_methods' && } + {app.currentPage === 'demographics' && } diff --git a/src/components/nav-main.tsx b/src/components/nav-main.tsx index 1f276b1..fbd0ff8 100644 --- a/src/components/nav-main.tsx +++ b/src/components/nav-main.tsx @@ -1,6 +1,6 @@ "use client" -import {ListIcon, NotebookText, Users} from "lucide-react" +import {ListIcon, NotebookText, Users, User} from "lucide-react" import { SidebarGroup, SidebarGroupContent, @@ -13,12 +13,14 @@ import {useAppDispatch, useAppSelector} from "@/hooks.ts"; import {useSelector} from "react-redux"; import {selectQuestions} from "@/models/questionSlice.ts"; import {Badge} from "@/components/ui/badge" +import {selectUserUpkAnswers} from "@/models/userUpkAnswerSlice.ts"; export function NavMain() { const dispatch = useAppDispatch() const app = useAppSelector(state => state.app) const questions = useSelector(selectQuestions) + const upkAnswers = useSelector(selectUserUpkAnswers) return ( @@ -56,6 +58,20 @@ export function NavMain() { + dispatch(setPage("demographics"))} + > + + + + Demographics {upkAnswers.length.toLocaleString()} + + + + diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b1cd120..ed75d2f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -34,3 +34,10 @@ export function formatCentsToUSD(cents: number): string { currency: 'USD', }).format(cents / 100) } + +export function titleCase(str: string): string { + return str + .split(" ") + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} \ No newline at end of file diff --git a/src/models/app.ts b/src/models/app.ts index 25b2412..27923db 100644 --- a/src/models/app.ts +++ b/src/models/app.ts @@ -1,4 +1,4 @@ -export type Page = 'offerwall' | 'questions' | 'cashout_methods'; +export type Page = 'offerwall' | 'questions' | 'demographics' | 'cashout_methods'; export interface App { targetId: string, diff --git a/src/models/marketplaceAnswerSlice.ts b/src/models/marketplaceAnswerSlice.ts deleted file mode 100644 index a1db7f0..0000000 --- a/src/models/marketplaceAnswerSlice.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {createSlice, PayloadAction} from "@reduxjs/toolkit"; -import {MarketProfileKnowledge} from "@/api"; - -const marketplaceInitialState: MarketProfileKnowledge[] = [] - - -const marketplaceAnswerSlice = createSlice({ - name: 'marketplaceAnswers', - marketplaceInitialState, - reducers: { - setMarketplaceAnswers(state, action: PayloadAction) { - // TODO: Does this need question_id + source uniqueness? - const existingIds = new Set(state.map(q => q.question_id)); - const newQuestions = action.payload.filter(q => !existingIds.has(q.question_id)); - state.push(...newQuestions); - } - } -}) - -export const { - setMarketplaceAnswers, -} = marketplaceAnswerSlice.actions; - -export default marketplaceAnswerSlice.reducer; diff --git a/src/models/upkAnswerSlice.ts b/src/models/upkAnswerSlice.ts deleted file mode 100644 index 7bd57f1..0000000 --- a/src/models/upkAnswerSlice.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {createSlice, PayloadAction} from '@reduxjs/toolkit' -import {UserProfileKnowledge} from "@/api"; - -const upkInitialState: UserProfileKnowledge[] = [] - -const upkAnswerSlice = createSlice({ - name: 'upkAnswers', - upkInitialState, - reducers: { - setUpkAnswers(state, action: PayloadAction) { - const existingIds = new Set(state.map(q => q.property_id)); - const newQuestions = action.payload.filter(q => !existingIds.has(q.property_id)); - state.push(...newQuestions); - } - } -}) - - -export const { - setUpkAnswers, -} = upkAnswerSlice.actions; - -export default upkAnswerSlice.reducer; \ No newline at end of file diff --git a/src/models/userMarketplaceAnswerSlice.ts b/src/models/userMarketplaceAnswerSlice.ts new file mode 100644 index 0000000..7cfe95b --- /dev/null +++ b/src/models/userMarketplaceAnswerSlice.ts @@ -0,0 +1,27 @@ +import {createSlice, PayloadAction} from "@reduxjs/toolkit"; +import {MarketProfileKnowledge} from "@/api"; +import type {RootState} from '@/store' + +const initialState: MarketProfileKnowledge[] = [] + + +const userMarketplaceAnswerSlice = createSlice({ + name: 'userMarketplaceAnswers', + initialState, + reducers: { + setMarketplaceAnswers(state, action: PayloadAction) { + // TODO: Does this need question_id + source uniqueness? + const existingIds = new Set(state.map(q => q.question_id)); + const newQuestions = action.payload.filter(q => !existingIds.has(q.question_id)); + state.push(...newQuestions); + } + } +}) + +export const { + setMarketplaceAnswers, +} = userMarketplaceAnswerSlice.actions; + +export default userMarketplaceAnswerSlice.reducer; + +export const selectUserMarketplaceAnswers = (state: RootState) => state.userMarketplaceAnswers \ No newline at end of file diff --git a/src/models/userUpkAnswerSlice.ts b/src/models/userUpkAnswerSlice.ts new file mode 100644 index 0000000..9cb0e9b --- /dev/null +++ b/src/models/userUpkAnswerSlice.ts @@ -0,0 +1,54 @@ +import {createSlice, PayloadAction} from '@reduxjs/toolkit' +import {UserProfileKnowledge} from "@/api"; +import type {RootState} from "@/store.ts"; + +const initialState: UserProfileKnowledge[] = [] + +const userUpkAnswerSlice = createSlice({ + name: 'upkAnswers', + initialState, + reducers: { + setUpkAnswers(state, action: PayloadAction) { + const existingIds = new Set(state.map(q => q.property_id)); + const newQuestions = action.payload.filter(q => !existingIds.has(q.property_id)); + state.push(...newQuestions); + } + } +}) + + +export const { + setUpkAnswers, +} = userUpkAnswerSlice.actions; + +export default userUpkAnswerSlice.reducer; + +export const selectUserUpkAnswers = (state: RootState) => state.userUpkAnswers + + +// educational_attainment +export const selectUserAge = (state: RootState): number | null => { + let upk_a = state.userUpkAnswers.find(a => a.property_label === "age_in_years") + if (upk_a) { + return Number(upk_a.answer[0].value) + } else { + return null + } +} +export const selectUserZip = (state: RootState): string | null => { + let upk_a = state.userUpkAnswers.find(a => a.property_label === "home_postal_code") + if (upk_a) { + return upk_a.answer[0].value + } else { + return null + } +} + +export const selectUserGender = (state: RootState): string | null => { + let upk_a = state.userUpkAnswers.find(a => a.property_label === "gender") + if (upk_a) { + return upk_a.answer[0].label + } else { + return null + } +} \ No newline at end of file diff --git a/src/pages/Demographics.tsx b/src/pages/Demographics.tsx new file mode 100644 index 0000000..1130ef4 --- /dev/null +++ b/src/pages/Demographics.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import {useSelector} from "react-redux"; +import {selectUserAge, selectUserGender, selectUserUpkAnswers, selectUserZip} from "@/models/userUpkAnswerSlice.ts"; +import {titleCase} from "@/lib/utils.ts"; + +import {Card, CardContent, CardHeader} from "@/components/ui/card"; +import {Calendar, MapPin, User} from "lucide-react"; +import {BucketTask} from "@/api"; +import {ColumnDef, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; +import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table.tsx"; + +export const UpkGrid = () => { + + const columns: ColumnDef[] = [ + { + accessorKey: "property_label", + header: "Label", + }, + { + accessorKey: "answer", + header: "Answer", + cell: ({getValue}) => getValue()[0].value ?? getValue()[0].label + }, + ] + + const data = useSelector(selectUserUpkAnswers) + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + return ( + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + + { + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + } + + + + ) +} + +export const ContactCard = () => { + + const age = useSelector(selectUserAge) + const zip = useSelector(selectUserZip) + const gender = useSelector(selectUserGender) + + return ( + + + + + + + + + + + {titleCase(gender as string) ?? " - "} + + + + {age ?? " - "} years old + + + + {zip ?? " - "} + + + + ); +}; + + +const Demographics = () => { + + return ( + <> + + + > + ) +} + +export { + Demographics +} \ No newline at end of file diff --git a/src/store.ts b/src/store.ts index 9600704..c4844b5 100644 --- a/src/store.ts +++ b/src/store.ts @@ -7,8 +7,8 @@ import answerReducers from "@/models/answerSlice.ts" import cashoutMethodReducers from "@/models/cashoutMethodSlice.ts" import walletReducers from "@/models/walletSlice.ts" import upkQuestionReducers from "@/models/upkQuestionSlice" -import upkAnswerReducers from "@/models/upkAnswerSlice" -import marketplaceReducers from "@/models/marketplaceAnswerSlice" +import userUpkAnswerReducers from "@/models/userUpkAnswerSlice.ts" +import userMarketplaceReducers from "@/models/userMarketplaceAnswerSlice.ts" export const store = configureStore({ reducer: { @@ -20,8 +20,9 @@ export const store = configureStore({ questions: questionReducers, upkQuestions: upkQuestionReducers, - upkAnswers: upkAnswerReducers, - marketplaceAnswers: marketplaceReducers, + + userUpkAnswers: userUpkAnswerReducers, + userMarketplaceAnswers: userMarketplaceReducers, // - Read Write // -- This stores user engagement (eg: answering any questions) -- cgit v1.2.3