diff --git a/backend/api/src/convert-cash-to-mana.ts b/backend/api/src/convert-cash-to-mana.ts index 8112c62fb4..d15478b51c 100644 --- a/backend/api/src/convert-cash-to-mana.ts +++ b/backend/api/src/convert-cash-to-mana.ts @@ -1,10 +1,10 @@ import { APIError, APIHandler } from './helpers/endpoint' import { type TxnData, insertTxns } from 'shared/txn/run-txn' import { createSupabaseDirectClient } from 'shared/supabase/init' -import { getUser } from 'shared/utils' import { incrementBalance } from 'shared/supabase/users' import { betsQueue } from 'shared/helpers/fn-queue' import { CASH_TO_MANA_CONVERSION_RATE } from 'common/envs/constants' +import { calculateRedeemablePrizeCash } from 'shared/calculate-redeemable-prize-cash' export const convertCashToMana: APIHandler<'convert-cash-to-mana'> = async ( { amount }, @@ -15,11 +15,9 @@ export const convertCashToMana: APIHandler<'convert-cash-to-mana'> = async ( await betsQueue.enqueueFn(async () => { // check if user has enough cash await pg.tx(async (tx) => { - const user = await getUser(auth.uid, tx) - if (!user) throw new APIError(401, 'Your account was not found') - - if (user.cashBalance < amount) { - throw new APIError(403, 'Not enough balance') + const redeemable = await calculateRedeemablePrizeCash(auth.uid, tx) + if (redeemable < amount) { + throw new APIError(403, 'Not enough redeemable balance') } await incrementBalance(tx, auth.uid, { diff --git a/backend/api/src/place-bet.ts b/backend/api/src/place-bet.ts index 5d0e2c4ed3..01115c13d1 100644 --- a/backend/api/src/place-bet.ts +++ b/backend/api/src/place-bet.ts @@ -24,7 +24,7 @@ import { Answer } from 'common/answer' import { CpmmState, getCpmmProbability } from 'common/calculate-cpmm' import { ValidatedAPIParams } from 'common/api/schema' import { onCreateBets } from 'api/on-create-bet' -import { BANNED_TRADING_USER_IDS } from 'common/envs/constants' +import { BANNED_TRADING_USER_IDS, TWOMBA_ENABLED } from 'common/envs/constants' import * as crypto from 'crypto' import { formatMoneyWithDecimals } from 'common/util/format' import { @@ -263,7 +263,7 @@ export const fetchContractBetDataAndValidate = async ( const queries = ` select * from users where id = $1; select ${contractColumnsToSelect} from contracts where id = $2; - select * from answers + select * from answers where contract_id = $2 and ( ($3 is null or id in ($3:list)) or (select (data->'shouldAnswersSumToOne')::boolean from contracts where id = $2) @@ -561,22 +561,24 @@ export const executeNewBetResult = async ( ) log(`Updated user ${user.username} balance - auth ${user.id}.`) - const totalCreatorFee = - newBet.fees.creatorFee + - sumBy(otherBetResults, (r) => r.bet.fees.creatorFee) - if (totalCreatorFee !== 0) { - await incrementBalance(pgTrans, contract.creatorId, { - balance: totalCreatorFee, - totalDeposits: totalCreatorFee, - }) + if (!TWOMBA_ENABLED) { + const totalCreatorFee = + newBet.fees.creatorFee + + sumBy(otherBetResults, (r) => r.bet.fees.creatorFee) + if (totalCreatorFee !== 0) { + await incrementBalance(pgTrans, contract.creatorId, { + balance: totalCreatorFee, + totalDeposits: totalCreatorFee, + }) - log( - `Updated creator ${ - contract.creatorUsername - } with fee gain ${formatMoneyWithDecimals(totalCreatorFee)} - ${ - contract.creatorId - }.` - ) + log( + `Updated creator ${ + contract.creatorUsername + } with fee gain ${formatMoneyWithDecimals(totalCreatorFee)} - ${ + contract.creatorId + }.` + ) + } } const answerUpdates: { diff --git a/common/src/contract.ts b/common/src/contract.ts index 7cf2ce626b..7dd98c8805 100644 --- a/common/src/contract.ts +++ b/common/src/contract.ts @@ -423,6 +423,7 @@ export type ContractParams = { betReplies: Bet[] cash?: { contract: Contract + lastBetTime?: number pointsString: string multiPointsString: { [answerId: string]: string } userPositionsByOutcome: ContractMetricsByOutcome diff --git a/common/src/fees.ts b/common/src/fees.ts index f7002b2ca3..ad59b0dc1f 100644 --- a/common/src/fees.ts +++ b/common/src/fees.ts @@ -1,4 +1,5 @@ import { addObjects } from 'common/util/object' +import { TWOMBA_ENABLED } from './envs/constants' export const FEE_START_TIME = 1713292320000 @@ -12,6 +13,14 @@ export const getFeesSplit = ( totalFees: number, previouslyCollectedFees: Fees ) => { + if (TWOMBA_ENABLED) { + return { + creatorFee: 0, + platformFee: totalFees, + liquidityFee: 0, + } + } + const before1k = Math.max( 0, CREATORS_EARN_WHOLE_FEE_UP_TO - previouslyCollectedFees.creatorFee diff --git a/web/components/answers/answer-components.tsx b/web/components/answers/answer-components.tsx index dd99f9b7a9..adf68415fc 100644 --- a/web/components/answers/answer-components.tsx +++ b/web/components/answers/answer-components.tsx @@ -545,6 +545,10 @@ export function AnswerPosition(props: { const noWinnings = totalShares.NO ?? 0 const position = yesWinnings - noWinnings const isCashContract = contract.token === 'CASH' + const canSell = tradingAllowed(contract, answer) + const won = + (position > 1e-7 && answer.resolution === 'YES') || + (position < -1e-7 && answer.resolution === 'NO') return ( - Payout + {canSell ? 'Payout' : won ? 'Paid out' : 'Held out for'} {position > 1e-7 ? ( <> @@ -586,18 +590,17 @@ export function AnswerPosition(props: { - {(!contract.closeTime || contract.closeTime > Date.now()) && - !answer.resolutionTime && ( - <> - · - - - )} + {canSell && ( + <> + · + + + )} ) } diff --git a/web/components/answers/answers-panel.tsx b/web/components/answers/answers-panel.tsx index 8368c84aa3..08427599fc 100644 --- a/web/components/answers/answers-panel.tsx +++ b/web/components/answers/answers-panel.tsx @@ -193,7 +193,7 @@ export function AnswersPanel(props: { enabled: isAdvancedTrader && shouldShowLimitOrderChart, }) - const [shouldShowPositions, setShouldShowPositions] = useState(true) + const [shouldShowPositions, setShouldShowPositions] = useState(!allResolved) const moreCount = answers.length - answersToShow.length // Note: Hide answers if there is just one "Other" answer. @@ -624,12 +624,7 @@ export function Answer(props: { resolvedProb === 0 ? 'text-ink-700' : 'text-ink-900' ) - const showSellButton = - !resolution && - hasBets && - user && - (!contract.closeTime || contract.closeTime > Date.now()) && - !answer.resolutionTime + const showPosition = hasBets && user const userHasLimitOrders = shouldShowLimitOrderChart && (yourUnfilledBets ?? []).length > 0 @@ -778,7 +773,7 @@ export function Answer(props: { } > - {showSellButton && ( + {showPosition && ( )} - {userHasLimitOrders && showSellButton && <>·} + {userHasLimitOrders && showPosition && <>·} {userHasLimitOrders && ( - - Verify your info to start trading on sweepstakes markets and earn a - bonus of{' '} - - ! - - - Verify - - + +
+ Must be verified to {TRADE_TERM} +
+

+ Verify your info to start trading on sweepstakes markets! +

+ + ) } else if (contract.token === 'CASH' && blockFromSweepstakes(user)) { return ( diff --git a/web/components/bet/fees.tsx b/web/components/bet/fees.tsx index 4ccc1b838f..a596938a93 100644 --- a/web/components/bet/fees.tsx +++ b/web/components/bet/fees.tsx @@ -1,4 +1,4 @@ -import { TRADE_TERM } from 'common/envs/constants' +import { TRADE_TERM, TWOMBA_ENABLED } from 'common/envs/constants' import { InfoTooltip } from '../widgets/info-tooltip' import { MoneyDisplay } from './money-display' @@ -20,7 +20,11 @@ export const FeeDisplay = (props: { diff --git a/web/components/charts/generic-charts.tsx b/web/components/charts/generic-charts.tsx index 33c8476dce..888bcc46e2 100644 --- a/web/components/charts/generic-charts.tsx +++ b/web/components/charts/generic-charts.tsx @@ -728,6 +728,7 @@ export const SingleValueHistoryChart =

(props: { hideXAxis?: boolean onGraphClick?: () => void areaClassName?: string + noWatermark?: boolean className?: string }) => { const { @@ -749,6 +750,7 @@ export const SingleValueHistoryChart =

(props: { hideXAxis, onGraphClick, areaClassName, + noWatermark, } = props useLayoutEffect(() => { @@ -873,6 +875,7 @@ export const SingleValueHistoryChart =

(props: { pointerMode={pointerMode} hideXAxis={hideXAxis} yKind={yKind} + noWatermark={noWatermark} > {typeof color !== 'string' && ( diff --git a/web/components/charts/stats.tsx b/web/components/charts/stats.tsx index a35c24abb6..05f0e8b862 100644 --- a/web/components/charts/stats.tsx +++ b/web/components/charts/stats.tsx @@ -81,6 +81,7 @@ export function DailyChart(props: { values: Point[]; pct?: boolean }) { curve={curveLinear} zoomParams={zoomParams} showZoomer + noWatermark /> )} diff --git a/web/components/contract/contract-overview.tsx b/web/components/contract/contract-overview.tsx index e9476e69fa..2c174b3e63 100644 --- a/web/components/contract/contract-overview.tsx +++ b/web/components/contract/contract-overview.tsx @@ -1,6 +1,5 @@ import { ReactNode, memo, useState, useEffect } from 'react' import clsx from 'clsx' -import { KYC_VERIFICATION_BONUS_CASH } from 'common/economy' import { Bet } from 'common/bet' import { HistoryPoint, MultiPoints } from 'common/chart' @@ -49,7 +48,7 @@ import { getAnswerProbability } from 'common/calculate' import { useAnnotateChartTools } from 'web/hooks/use-chart-annotations' import { type ChartAnnotation } from 'common/supabase/chart-annotations' import { formatMoney, formatPercent } from 'common/util/format' -import { isAdminId, isModId } from 'common/envs/constants' +import { isAdminId, isModId, TRADE_TERM } from 'common/envs/constants' import { LoadingIndicator } from '../widgets/loading-indicator' import { useDataZoomFetcher } from '../charts/contract/zoom-utils' import { AlertBox } from '../widgets/alert-box' @@ -73,9 +72,7 @@ import { EditChartAnnotationsButton, } from '../charts/chart-annotations' import { useLiveContractWithAnswers } from 'web/hooks/use-contract' -import Link from 'next/link' -import { buttonClass } from 'web/components/buttons/button' -import { CoinNumber } from 'web/components/widgets/coin-number' +import { VerifyButton } from '../twomba/toggle-verify-callout' export const ContractOverview = memo( (props: { @@ -839,21 +836,15 @@ export function BinaryBetPanel(props: { You can't trade on sweepstakes markets while your status is pending. ) : contract.token === 'CASH' && user && !user.idVerified ? ( - - - Verify your info to start trading on sweepstakes markets and earn a - bonus of{' '} - - ! - - - Verify - - + +

+ Must be verified to {TRADE_TERM} +
+

+ Verify your info to start trading on sweepstakes markets! +

+ + ) : contract.token === 'CASH' && blockFromSweepstakes(user) ? ( You are not eligible to trade on sweepstakes markets. diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx index 9232bf86d4..a43110d6cd 100644 --- a/web/components/contract/contract-tabs.tsx +++ b/web/components/contract/contract-tabs.tsx @@ -134,6 +134,7 @@ export function ContractTabs(props: { title: positionsTitle, content: ( 0 && @@ -151,6 +152,7 @@ export function ContractTabs(props: { content: ( {formatWithToken({ amount: collectedFees.creatorFee, - token: isCashContract ? 'CASH' : 'M$', - toDecimal: isCashContract ? 4 : 2, + token: 'M$', + toDecimal: 2, })}{' '} earned diff --git a/web/components/contract/twomba-contract-page.tsx b/web/components/contract/twomba-contract-page.tsx index fe7afd841c..1c306afaba 100644 --- a/web/components/contract/twomba-contract-page.tsx +++ b/web/components/contract/twomba-contract-page.tsx @@ -75,6 +75,7 @@ import { YourTrades } from 'web/pages/[username]/[contractSlug]' import { useSweepstakes } from '../sweestakes-context' import { useMonitorStatus } from 'web/hooks/use-monitor-status' import { ToggleVerifyCallout } from '../twomba/toggle-verify-callout' +import { useRouter } from 'next/router' export function TwombaContractPageContent(props: ContractParams) { const { @@ -90,7 +91,14 @@ export function TwombaContractPageContent(props: ContractParams) { cash, } = props - const { isPlay } = useSweepstakes() + const { isPlay, setIsPlay } = useSweepstakes() + const router = useRouter() + useEffect(() => { + if (router.isReady) { + setIsPlay(router.query.play !== 'false') + } + }, [router.isReady]) + const livePlayContract = useLiveContractWithAnswers(props.contract) const liveCashContract = props.cash ? // eslint-disable-next-line react-hooks/rules-of-hooks @@ -144,7 +152,7 @@ export function TwombaContractPageContent(props: ContractParams) { contractId: cash?.contract.id ?? '', outcomeType: cash?.contract.outcomeType, userId: user?.id, - lastBetTime: props.lastBetTime, + lastBetTime: cash?.lastBetTime, totalBets: cash?.totalBets ?? 0, pointsString: cash?.pointsString, multiPointsString: cash?.multiPointsString, diff --git a/web/components/gidx/location-panel.tsx b/web/components/gidx/location-panel.tsx index 0575152b96..45eaaf3336 100644 --- a/web/components/gidx/location-panel.tsx +++ b/web/components/gidx/location-panel.tsx @@ -4,17 +4,13 @@ import { GPSData, } from 'common/gidx/gidx' import { useEffect, useState } from 'react' +import { Button } from 'web/components/buttons/button' +import { LoadingIndicator } from 'web/components/widgets/loading-indicator' import { useNativeMessages } from 'web/hooks/use-native-messages' import { getIsNative } from 'web/lib/native/is-native' import { postMessageToNative } from 'web/lib/native/post-message' -import { Col } from 'web/components/layout/col' -import { LoadingIndicator } from 'web/components/widgets/loading-indicator' -import { Row } from 'web/components/layout/row' -import { Button } from 'web/components/buttons/button' -import { - registrationBottomRowClass, - registrationColClass, -} from 'web/components/gidx/register-user-form' +import { BottomRow } from './register-component-helpers' +import { LocationBlockedIcon } from 'web/public/custom-components/locationBlockedIcon' export const LocationPanel = (props: { setLocation: (data: GPSData) => void @@ -128,21 +124,18 @@ export const LocationPanel = (props: { } if (!checkedPermissions) { - return ( - - - - ) + return } return ( - - Location required - + <> + + Location required + You must allow location sharing to verify that you're in a participating municipality. - + @@ -153,7 +146,7 @@ export const LocationPanel = (props: { > Share location - + {locationError && ( {locationError} @@ -162,6 +155,6 @@ export const LocationPanel = (props: { : ''} )} - + ) } diff --git a/web/components/gidx/register-component-helpers.tsx b/web/components/gidx/register-component-helpers.tsx new file mode 100644 index 0000000000..54d0d6e9d0 --- /dev/null +++ b/web/components/gidx/register-component-helpers.tsx @@ -0,0 +1,16 @@ +import { Row } from 'web/components/layout/row' + +export function InputTitle(props: { + className?: string + children: React.ReactNode +}) { + return ( + + {props.children} + + ) +} + +export function BottomRow(props: { children: React.ReactNode }) { + return {props.children} +} diff --git a/web/components/gidx/register-user-form.tsx b/web/components/gidx/register-user-form.tsx index 778a169a47..a91963a97d 100644 --- a/web/components/gidx/register-user-form.tsx +++ b/web/components/gidx/register-user-form.tsx @@ -29,9 +29,15 @@ import { import { LocationPanel } from 'web/components/gidx/location-panel' import { KYC_VERIFICATION_BONUS_CASH } from 'common/economy' import { CoinNumber } from 'web/components/widgets/coin-number' - -export const registrationColClass = 'gap-3 p-4' -export const registrationBottomRowClass = 'mb-4 mt-4 w-full gap-16' +import { RegisterIcon } from 'web/public/custom-components/registerIcon' +import { + BottomRow, + InputTitle, +} from 'web/components/gidx/register-component-helpers' +import { DocumentUploadIcon } from 'web/public/custom-components/documentUploadIcon' +import { LocationBlockedIcon } from 'web/public/custom-components/locationBlockedIcon' +import { RiUserForbidLine } from 'react-icons/ri' +import { PiClockCountdown } from 'react-icons/pi' export const RegisterUserForm = (props: { user: User @@ -131,20 +137,21 @@ export const RegisterUserForm = (props: { if (page === 'intro') { return ( - - - Identity Verification + <> + +
Identity Verification
+ + To use sweepstakes coins, you must verify your identity. - To use sweepstakes coins, you must verify your identity. - + - - + + ) } @@ -177,38 +184,40 @@ export const RegisterUserForm = (props: { } if (page === 'form') { - const sectionClass = 'gap-2 w-full sm:w-96' + const sectionClass = 'gap-0.5 w-full' return ( - - - Identity Verification - - - First Name - - setUserInfo({ ...userInfo, FirstName: e.target.value }) - } - /> - - - Last Name - - setUserInfo({ ...userInfo, LastName: e.target.value }) - } - /> - + <> + Identity Verification + +
+ + First Name + + setUserInfo({ ...userInfo, FirstName: e.target.value }) + } + /> + + + + Last Name + + setUserInfo({ ...userInfo, LastName: e.target.value }) + } + /> + +
- Date of Birth + Date of Birth - Citizenship Country + Email Address + + setUserInfo({ ...userInfo, EmailAddress: e.target.value }) + } + /> + + +
+ + + Citizenship Country @@ -229,8 +252,9 @@ export const RegisterUserForm = (props: { } /> + - Address Line 1 + Address Line 1 - Address Line 2 + Address Line 2 - Email Address + City - setUserInfo({ ...userInfo, EmailAddress: e.target.value }) - } + onChange={(e) => setUserInfo({ ...userInfo, City: e.target.value })} /> - - - City + + + State - setUserInfo({ ...userInfo, City: e.target.value }) + setUserInfo({ ...userInfo, StateCode: e.target.value }) } /> - - State + + Postal Code - setUserInfo({ ...userInfo, StateCode: e.target.value }) + setUserInfo({ ...userInfo, PostalCode: e.target.value }) } /> - - Postal Code - - setUserInfo({ ...userInfo, PostalCode: e.target.value }) - } - /> - - {error && ( {error} @@ -315,7 +325,7 @@ export const RegisterUserForm = (props: { )} - + - - + + ) } if (page === 'documents') { return ( - - + <> + Identity Document Verification router.back()} next={() => setPage('final')} /> - + ) } if (user.kycDocumentStatus === 'pending') { return ( - - - Verification pending + <> + + Verification pending + + Thank you for submitting your identification information! Your + identity verification is pending. Check back later to see if you're + verified. - Thank you for submitting your identification information! Your identity - verification is pending. Check back later to see if you're verified. - + - + ) } return ( - - - Identity Verification Complete + <> +
🎉
+ + Identity Verification Complete! - + Hooray! Now you can participate in sweepstakes markets. We sent you{' '} {' '} to get started. - +
{/*// TODO: auto-redirect rather than make them click this button*/} {redirect === 'checkout' ? ( @@ -445,7 +475,7 @@ export const RegisterUserForm = (props: { Done )} - - +
+ ) } diff --git a/web/components/registration-verify-phone.tsx b/web/components/registration-verify-phone.tsx index c1efd08391..9e225cd18e 100644 --- a/web/components/registration-verify-phone.tsx +++ b/web/components/registration-verify-phone.tsx @@ -1,14 +1,14 @@ -import { Col } from 'web/components/layout/col' -import { Button } from 'web/components/buttons/button' -import { api } from 'web/lib/api/api' import { useEffect, useState } from 'react' -import { Input } from 'web/components/widgets/input' -import { PhoneInput } from 'react-international-phone' import { toast } from 'react-hot-toast' +import { PhoneInput } from 'react-international-phone' import 'react-international-phone/style.css' -import { Row } from 'web/components/layout/row' -import { track } from 'web/lib/service/analytics' +import { Button } from 'web/components/buttons/button' +import { Input } from 'web/components/widgets/input' import { useUser } from 'web/hooks/use-user' +import { api } from 'web/lib/api/api' +import { track } from 'web/lib/service/analytics' +import { PhoneIcon } from 'web/public/custom-components/phoneIcon' +import { BottomRow } from './gidx/register-component-helpers' export function RegistrationVerifyPhone(props: { cancel: () => void @@ -63,20 +63,19 @@ export function RegistrationVerifyPhone(props: { }, [user?.verifiedPhone]) return ( - + <> {page === 0 && ( - - - Verify your phone number - + <> + + Verify your phone number setPhoneNumber(phone)} placeholder={'Phone Number'} - className={'ml-3'} + className={'mx-auto'} /> - + @@ -87,23 +86,20 @@ export function RegistrationVerifyPhone(props: { > Request code - - + + )} {page === 1 && ( - - - - Enter verification code - - setOtp(e.target.value)} - placeholder="123456" - /> - - + <> + + Enter verification code + setOtp(e.target.value)} + placeholder="123456" + /> + @@ -114,9 +110,9 @@ export function RegistrationVerifyPhone(props: { > Verify - - + + )} - + ) } diff --git a/web/components/stats/bonus-summary.tsx b/web/components/stats/bonus-summary.tsx index c6e28c1391..2f4da97958 100644 --- a/web/components/stats/bonus-summary.tsx +++ b/web/components/stats/bonus-summary.tsx @@ -161,6 +161,7 @@ export const BonusSummary = (props: { background: 'black', borderRadius: '8px', pointerEvents: 'none', + opacity: 0, }} >
diff --git a/web/components/stats/mana-summary.tsx b/web/components/stats/mana-summary.tsx index f97936e9df..b34569f956 100644 --- a/web/components/stats/mana-summary.tsx +++ b/web/components/stats/mana-summary.tsx @@ -5,34 +5,68 @@ import { scaleBand, scaleLinear, scaleOrdinal } from 'd3-scale' import { stack } from 'd3-shape' import { axisBottom, axisRight } from 'd3-axis' import { max } from 'd3-array' -import { uniq } from 'lodash' +import { unzip, zip, pick } from 'lodash' import { renderToString } from 'react-dom/server' import { Col } from 'web/components/layout/col' import { formatWithCommas } from 'common/util/format' - -const colors = [ - '#60d775', - '#FFDFBA', - '#BAFFC9', - '#BAE1FF', - '#D4A5A5', - '#A5D4D4', - '#D4D4A5', - '#D4A5C2', - '#FFB7B7', - '#B7FFB7', - '#FFD700', -] +import { Title } from 'web/components/widgets/title' type DateAndCategoriesToTotals = { date: string } & { [key: string]: number } -const categoryToColor = new Map() + +const categoryToLabel = { + total_value: 'total mana (-loans)', + balance: 'mana balance', + spice_balance: 'spice balance', + investment_value: 'invested', + loan_total: 'loans', + amm_liquidity: 'amm liquidity', + total_cash_value: 'total prize cash', + cash_balance: 'prize cash balance', + cash_investment_value: 'invested', + amm_cash_liquidity: 'amm liquidity', +} + +const categoryToColor = { + total_value: '#FFF0FF', + balance: '#B690D6', + spice_balance: '#FFA620', + investment_value: '#30A0C6', + loan_total: '#FFB7B7', + amm_liquidity: '#B7FFB7', + total_cash_value: '#FFFFF0', + cash_balance: '#FFD700', + cash_investment_value: '#60D0C6', + amm_cash_liquidity: '#20D020', +} + +const [categories, colors] = zip(...Object.entries(categoryToColor)) as [ + string[], + string[] +] +const colorScale = scaleOrdinal().domain(categories).range(colors) export const ManaSupplySummary = (props: { manaSupplyStats: rowFor<'mana_supply_stats'>[] }) => { const { manaSupplyStats } = props + const [manaData, cashData] = orderAndGroupData(manaSupplyStats) + + return ( + <> + Mana supply over time + + Prize cash supply supply over time + + + ) +} + +const StackedChart = (props: { + data: ({ date: string } & Partial>)[] +}) => { + const { data } = props const svgRef = useRef(null) const tooltipRef = useRef(null) const xAxisRef = useRef(null) @@ -42,15 +76,7 @@ export const ManaSupplySummary = (props: { const height = 500 const innerWidth = width - margin.left - margin.right const innerHeight = height - margin.top - margin.bottom - const { data, xScale, stackGen, colorScale, yScale, keys } = useMemo(() => { - const data = orderAndGroupData(manaSupplyStats) - const uniqueCategories = uniq(Object.keys(manaSupplyStats[0])) - for (let i = 0; i < uniqueCategories.length; i++) { - categoryToColor.set(uniqueCategories[i], colors[i]) - } - const colorScale = scaleOrdinal() - .domain(uniqueCategories) - .range(colors) + const { xScale, layers, yScale, keys } = useMemo(() => { const xScale = scaleBand() .domain(data.map((d) => d.date)) .range([0, innerWidth]) @@ -59,15 +85,18 @@ export const ManaSupplySummary = (props: { const yScale = scaleLinear().range([innerHeight, 0]) const keys = Array.from( - new Set(data.flatMap((d) => Object.keys(d)).filter((k) => k !== 'date')) + new Set(data.flatMap((d) => Object.keys(d))) + ).filter( + (key) => !['date', 'total_value', 'total_cash_value'].includes(key) ) + const stackGen = stack<{ [key: string]: number }>().keys(keys) - const layers = stackGen(data) + const layers = stackGen(data as any) const maxY = max(layers, (layer) => max(layer, (d) => d[1] as number)) || 0 xScale.domain(data.map((d) => d.date)) yScale.domain([0, maxY]).nice() - return { data, xScale, yScale, keys, colorScale, stackGen } - }, [manaSupplyStats.length]) + return { data, xScale, yScale, keys, colorScale, layers } + }, [data.length]) useEffect(() => { if (xScale && xAxisRef.current) { @@ -90,7 +119,7 @@ export const ManaSupplySummary = (props: { {data.length > 0 && keys.length > 0 && - stackGen?.(data).map((layer, i) => ( + layers.map((layer, i) => ( {layer.map((d, j) => ( ms.start_time === datum.date - )!.total_value as number - } - categoryToColor={categoryToColor} - data={datum} - /> - ) + renderToString() ) }} onMouseMove={(event) => { @@ -149,6 +168,7 @@ export const ManaSupplySummary = (props: { background: 'black', borderRadius: '8px', pointerEvents: 'none', + opacity: 0, }} > @@ -156,30 +176,34 @@ export const ManaSupplySummary = (props: { } const orderAndGroupData = (data: rowFor<'mana_supply_stats'>[]) => { - return data.map((datum) => { - const { - id: _, - created_time: __, - end_time: ___, - start_time, - total_value: ____, - total_cash_value: _____, - ...rest - } = datum - - return { - ...rest, - date: start_time, - } as any as DateAndCategoriesToTotals - }) + return unzip( + data.map((datum) => [ + { + date: datum.start_time, + ...pick(datum, [ + 'total_value', + 'balance', + 'spice_balance', + 'investment_value', + 'loan_total', + 'amm_liquidity', + ]), + }, + { + date: datum.start_time, + ...pick(datum, [ + 'total_cash_value', + 'cash_balance', + 'cash_investment_value', + 'amm_cash_liquidity', + ]), + }, + ]) + ) } -const StackedChartTooltip = (props: { - data: DateAndCategoriesToTotals - totalValue: number - categoryToColor: Map -}) => { - const { data, totalValue, categoryToColor } = props +const StackedChartTooltip = (props: { data: DateAndCategoriesToTotals }) => { + const { data } = props return ( {new Date(Date.parse(data.date)).toLocaleString('en-us', { @@ -189,15 +213,17 @@ const StackedChartTooltip = (props: { hour: 'numeric', })}
- total (-loans): {formatWithCommas(totalValue)} {Object.keys(data) .filter((k) => k !== 'date') .map((key) => ( - {key}: {formatWithCommas(data[key])} + {categoryToLabel[key as keyof typeof categoryToLabel]}:{' '} + {formatWithCommas(data[key])} ))} diff --git a/web/components/sweestakes-context.tsx b/web/components/sweestakes-context.tsx index 24f5acb1ff..c486dc6a9c 100644 --- a/web/components/sweestakes-context.tsx +++ b/web/components/sweestakes-context.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext } from 'react' -import { usePersistentQueryState } from 'web/hooks/use-persistent-query-state' +import { usePersistentLocalState } from 'web/hooks/use-persistent-local-state' type SweepstakesContextType = { isPlay: boolean @@ -13,7 +13,7 @@ const SweepstakesContext = createContext( export const SweepstakesProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { - const [queryPlay, setQueryPlay] = usePersistentQueryState('play', 'true') + const [queryPlay, setQueryPlay] = usePersistentLocalState('play', 'true') const isPlay = !queryPlay || queryPlay === 'true' const setIsPlay = (isPlay: boolean) => { diff --git a/web/components/twomba/toggle-verify-callout.tsx b/web/components/twomba/toggle-verify-callout.tsx index 8740032845..5b0bab205a 100644 --- a/web/components/twomba/toggle-verify-callout.tsx +++ b/web/components/twomba/toggle-verify-callout.tsx @@ -49,21 +49,28 @@ export function ToggleVerifyCallout(props: {
- - Verify and claim - - + ) } + +export function VerifyButton(props: { className?: string }) { + const { className } = props + return ( + + Verify and claim + + + ) +} diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index f2371277bb..b8e4947d1c 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -94,7 +94,7 @@ import { pick } from 'lodash' export async function getStaticProps(ctx: { params: { username: string; contractSlug: string } }) { - const { contractSlug } = ctx.params + const { username, contractSlug } = ctx.params const adminDb = await initSupabaseAdmin() const contract = await getContractFromSlug(adminDb, contractSlug) @@ -122,7 +122,7 @@ export async function getStaticProps(ctx: { return { redirect: { - destination: `/username/${slug}?play=false`, + destination: `/${username}/${slug}?play=false`, permanent: false, }, } @@ -138,6 +138,7 @@ export async function getStaticProps(ctx: { const params = await getContractParams(cashContract, adminDb) cash = pick(params, [ 'contract', + 'lastBetTime', 'pointsString', 'multiPointsString', 'userPositionsByOutcome', diff --git a/web/pages/gidx/register.tsx b/web/pages/gidx/register.tsx index 8a0fe3c952..db717cc24c 100644 --- a/web/pages/gidx/register.tsx +++ b/web/pages/gidx/register.tsx @@ -4,6 +4,7 @@ import { LoadingIndicator } from 'web/components/widgets/loading-indicator' import { RegisterUserForm } from 'web/components/gidx/register-user-form' import { TWOMBA_ENABLED } from 'common/envs/constants' +import { Col } from 'web/components/layout/col' const HomePage = () => { const user = useUser() @@ -11,11 +12,13 @@ const HomePage = () => { if (!TWOMBA_ENABLED) return null return ( - {!user || !privateUser ? ( - - ) : ( - - )} + + {!user || !privateUser ? ( + + ) : ( + + )} + ) } diff --git a/web/pages/stats.tsx b/web/pages/stats.tsx index 09d60fed3e..284cfeb91a 100644 --- a/web/pages/stats.tsx +++ b/web/pages/stats.tsx @@ -20,7 +20,6 @@ import { api } from '../lib/api/api' import { Column, Row as rowfor } from 'common/supabase/utils' import { BonusSummary } from 'web/components/stats/bonus-summary' import { ManaSupplySummary } from 'web/components/stats/mana-summary' -import { Row } from 'web/components/layout/row' import { average } from 'common/util/math' import { useCallback, useState } from 'react' import { Button } from 'web/components/buttons/button' @@ -105,13 +104,13 @@ export function CustomAnalytics(props: { const differenceInCashSinceYesterday = currentSupply.total_cash_value - yesterdaySupply.total_cash_value - const [fromBankSummaryMana, fromBankSummaryCash] = partition( + const [fromBankSummaryCash, fromBankSummaryMana] = partition( fromBankSummary, - (f) => f.token === 'MANA' + (f) => f.token === 'CASH' ) - const [toBankSummaryMana, toBankSummaryCash] = partition( + const [toBankSummaryCash, toBankSummaryMana] = partition( toBankSummary, - (f) => f.token === 'MANA' + (f) => f.token === 'CASH' ) const latestRecordingTime = orderBy(fromBankSummary, 'start_time', 'desc')[0] @@ -214,64 +213,64 @@ export function CustomAnalytics(props: { Mana supply - - Supply Today - -
Balances
-
- {formatMoney(currentSupply.balance)} -
-
- -
Prize point balances
-
- ₽{formatWithCommas(currentSupply.spice_balance)} -
-
- -
Cash balances
-
- {formatSweepies(currentSupply.cash_balance)} -
-
- -
Investment
-
- {formatMoney(currentSupply.investment_value)} -
-
- {/* -
Loans
-
- {formatMoney(manaSupply.loanTotal)} -
-
*/} - -
AMM liquidity
-
- {formatMoney(currentSupply.amm_liquidity)} -
-
- -
Unaccounted for since yesterday
-
- mana: {formatMoney(unaccountedDifference)} -
-
- cash: {formatSweepies(cashUnaccountedDifference)} -
-
- -
Total
-
- {formatMoney(currentSupply.total_value)} -
-
- {formatSweepies(currentSupply.total_cash_value)} -
-
- - Mana supply over time +
+
Supply Today
+
Mana
+
Prize Cash
+ +
Balances
+
+ {formatMoney(currentSupply.balance)} +
+
+ {formatSweepies(currentSupply.cash_balance)} +
+ +
Prize point balances
+
+ ₽{formatWithCommas(currentSupply.spice_balance)} +
+ +
Investment
+
+ {formatMoney(currentSupply.investment_value)} +
+
+ {formatSweepies(currentSupply.cash_investment_value)} +
+ + {/* +
Loans
+
+ {formatMoney(manaSupply.loanTotal)} +
+ */} + +
AMM liquidity
+
+ {formatMoney(currentSupply.amm_liquidity)} +
+
+ {formatSweepies(currentSupply.amm_cash_liquidity)} +
+ +
+
Unaccounted since yesterday
+
+ {formatMoney(unaccountedDifference)} +
+
+ {formatSweepies(cashUnaccountedDifference)} +
+ +
Total
+
+ {formatMoney(currentSupply.total_value)} +
+
+ {formatSweepies(currentSupply.total_cash_value)} +
+
Transactions from Manifold @@ -279,7 +278,7 @@ export function CustomAnalytics(props: { Transactions to Manifold - (Ignores mana purchases) + (Ignores mana purchases) diff --git a/web/public/custom-components/documentUploadIcon.tsx b/web/public/custom-components/documentUploadIcon.tsx new file mode 100644 index 0000000000..87bab6c1c5 --- /dev/null +++ b/web/public/custom-components/documentUploadIcon.tsx @@ -0,0 +1,30 @@ +export function DocumentUploadIcon(props: { + height?: number + className?: string +}) { + const { className } = props + + const height = props.height ? props.height * 4 : 48 + const width = height * 1.25 + + return ( + + + + + + ) +} diff --git a/web/public/custom-components/phoneIcon.tsx b/web/public/custom-components/phoneIcon.tsx new file mode 100644 index 0000000000..13ee6ee41c --- /dev/null +++ b/web/public/custom-components/phoneIcon.tsx @@ -0,0 +1,22 @@ +export function PhoneIcon(props: { height?: number; className?: string }) { + const { className } = props + + const height = props.height ? props.height * 4 : 48 + const width = height * 1.25 + + return ( + + + + + + + ) +}