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.
-
+
-
+
+ >
)
}
if (identityBlocked(user, privateUser) || ageBlocked(user, privateUser)) {
return (
-
- Blocked identity
-
+ <>
+
+ Blocked identity
+
We verified your identity! But, you're blocked. Unfortunately, this
means you can't use our sweepstakes markets.
-
+
+
+ Go home
+
+
+ >
)
} else if (locationBlocked(user, privateUser)) {
return (
-
- Blocked location
-
+ <>
+
+ Blocked location
+
We verified your identity! But, you're currently in a blocked
- location. Please try again later ({'>'}3 hrs) in an allowed location.
+ location. Please try again later (more than 3 hrs) in an allowed
+ location.
-
+
+
+ Go home
+
+
+ >
)
}
if (user.sweepstakesVerified === false || user.kycDocumentStatus === 'fail') {
return (
-
- Document upload
-
+ <>
+
+ Document upload
+
{user.kycDocumentStatus === 'fail' &&
'There were errors with your documents. '}
Please upload identity documents to continue.
-
+
-
+ >
)
}
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 (
+
+ )
+}