Refactor useSaveShares to actually read from localStorage, use less bug-prone interface.

This commit is contained in:
James Grugett 2022-07-09 18:54:48 -05:00
parent 600203a675
commit 9d9dcea9b5
7 changed files with 86 additions and 114 deletions

View File

@ -37,7 +37,7 @@ import {
getPseudoProbability,
} from 'common/pseudo-numeric'
import { SellRow } from './sell-row'
import { useSaveShares } from './use-save-shares'
import { useSaveBinaryShares } from './use-save-binary-shares'
import { SignUpPrompt } from './sign-up-prompt'
import { isIOS } from 'web/lib/util/device'
import { ProbabilityInput } from './probability-input'
@ -56,12 +56,7 @@ export function BetPanel(props: {
const userBets = useUserContractBets(user?.id, contract.id)
const unfilledBets = useUnfilledBets(contract.id) ?? []
const yourUnfilledBets = unfilledBets.filter((bet) => bet.userId === user?.id)
const { yesFloorShares, noFloorShares } = useSaveShares(contract, userBets)
const sharesOutcome = yesFloorShares
? 'YES'
: noFloorShares
? 'NO'
: undefined
const { sharesOutcome } = useSaveBinaryShares(contract, userBets)
const [isLimitOrder, setIsLimitOrder] = useState(false)

View File

@ -8,7 +8,7 @@ import { Modal } from './layout/modal'
import { SellButton } from './sell-button'
import { useUser } from 'web/hooks/use-user'
import { useUserContractBets } from 'web/hooks/use-user-bets'
import { useSaveShares } from './use-save-shares'
import { useSaveBinaryShares } from './use-save-binary-shares'
// Inline version of a bet panel. Opens BetPanel in a new modal.
export default function BetRow(props: {
@ -24,10 +24,8 @@ export default function BetRow(props: {
)
const user = useUser()
const userBets = useUserContractBets(user?.id, contract.id)
const { yesFloorShares, noFloorShares, yesShares, noShares } = useSaveShares(
contract,
userBets
)
const { yesShares, noShares, hasYesShares, hasNoShares } =
useSaveBinaryShares(contract, userBets)
return (
<>
@ -40,7 +38,7 @@ export default function BetRow(props: {
setBetChoice(choice)
}}
replaceNoButton={
yesFloorShares > 0 ? (
hasYesShares ? (
<SellButton
panelClassName={betPanelClassName}
contract={contract}
@ -51,7 +49,7 @@ export default function BetRow(props: {
) : undefined
}
replaceYesButton={
noFloorShares > 0 ? (
hasNoShares ? (
<SellButton
panelClassName={betPanelClassName}
contract={contract}

View File

@ -52,10 +52,7 @@ export function ContractCard(props: {
const showQuickBet =
user &&
!marketClosed &&
!(
outcomeType === 'FREE_RESPONSE' && getTopAnswer(contract) === undefined
) &&
outcomeType !== 'NUMERIC' &&
(outcomeType === 'BINARY' || outcomeType === 'PSEUDO_NUMERIC') &&
!hideQuickBet
return (

View File

@ -7,7 +7,13 @@ import {
} from 'common/calculate'
import { getExpectedValue } from 'common/calculate-dpm'
import { User } from 'common/user'
import { Contract, NumericContract, resolution } from 'common/contract'
import {
BinaryContract,
Contract,
NumericContract,
PseudoNumericContract,
resolution,
} from 'common/contract'
import {
formatLargeNumber,
formatMoney,
@ -22,7 +28,7 @@ import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon'
import TriangleFillIcon from 'web/lib/icons/triangle-fill-icon'
import { Col } from '../layout/col'
import { OUTCOME_TO_COLOR } from '../outcome-label'
import { useSaveShares } from '../use-save-shares'
import { useSaveBinaryShares } from '../use-save-binary-shares'
import { sellShares } from 'web/lib/firebase/api-call'
import { calculateCpmmSale, getCpmmProbability } from 'common/calculate-cpmm'
import { track } from 'web/lib/service/analytics'
@ -31,26 +37,21 @@ import { useUnfilledBets } from 'web/hooks/use-bets'
const BET_SIZE = 10
export function QuickBet(props: { contract: Contract; user: User }) {
export function QuickBet(props: {
contract: BinaryContract | PseudoNumericContract
user: User
}) {
const { contract, user } = props
const { mechanism, outcomeType } = contract
const isCpmm = mechanism === 'cpmm-1'
const userBets = useUserContractBets(user.id, contract.id)
const unfilledBets = useUnfilledBets(contract.id) ?? []
const topAnswer =
outcomeType === 'FREE_RESPONSE' ? getTopAnswer(contract) : undefined
// TODO: yes/no from useSaveShares doesn't work on numeric contracts
const { yesFloorShares, noFloorShares, yesShares, noShares } = useSaveShares(
contract,
userBets,
topAnswer?.number.toString() || undefined
)
const hasUpShares =
yesFloorShares || (noFloorShares && outcomeType === 'NUMERIC')
const hasDownShares =
noFloorShares && yesFloorShares <= 0 && outcomeType !== 'NUMERIC'
const { hasYesShares, hasNoShares, yesShares, noShares } =
useSaveBinaryShares(contract, userBets)
const hasUpShares = hasYesShares
const hasDownShares = hasNoShares && !hasUpShares
const [upHover, setUpHover] = useState(false)
const [downHover, setDownHover] = useState(false)
@ -134,13 +135,6 @@ export function QuickBet(props: { contract: Contract; user: User }) {
})
}
if (outcomeType === 'FREE_RESPONSE')
return (
<Col className="relative -my-4 -mr-5 min-w-[5.5rem] justify-center gap-2 pr-5 pl-1 align-middle">
<QuickOutcomeView contract={contract} previewProb={previewProb} />
</Col>
)
return (
<Col
className={clsx(
@ -161,7 +155,7 @@ export function QuickBet(props: { contract: Contract; user: User }) {
{formatMoney(10)}
</div>
{hasUpShares > 0 ? (
{hasUpShares ? (
<TriangleFillIcon
className={clsx(
'mx-auto h-5 w-5',
@ -196,7 +190,7 @@ export function QuickBet(props: { contract: Contract; user: User }) {
onMouseLeave={() => setDownHover(false)}
onClick={() => placeQuickBet('DOWN')}
></div>
{hasDownShares > 0 ? (
{hasDownShares ? (
<TriangleDownFillIcon
className={clsx(
'mx-auto h-5 w-5',

View File

@ -6,7 +6,7 @@ import { Row } from './layout/row'
import { formatWithCommas } from 'common/util/format'
import { OutcomeLabel } from './outcome-label'
import { useUserContractBets } from 'web/hooks/use-user-bets'
import { useSaveShares } from './use-save-shares'
import { useSaveBinaryShares } from './use-save-binary-shares'
import { SellSharesModal } from './sell-modal'
export function SellRow(props: {
@ -20,16 +20,7 @@ export function SellRow(props: {
const [showSellModal, setShowSellModal] = useState(false)
const { mechanism } = contract
const { yesFloorShares, noFloorShares, yesShares, noShares } = useSaveShares(
contract,
userBets
)
const floorShares = yesFloorShares || noFloorShares
const sharesOutcome = yesFloorShares
? 'YES'
: noFloorShares
? 'NO'
: undefined
const { sharesOutcome, shares } = useSaveBinaryShares(contract, userBets)
if (sharesOutcome && user && mechanism === 'cpmm-1') {
return (
@ -37,7 +28,7 @@ export function SellRow(props: {
<Col className={className}>
<Row className="items-center justify-between gap-2 ">
<div>
You have {formatWithCommas(floorShares)}{' '}
You have {formatWithCommas(shares)}{' '}
<OutcomeLabel
outcome={sharesOutcome}
contract={contract}
@ -64,7 +55,7 @@ export function SellRow(props: {
contract={contract}
user={user}
userBets={userBets ?? []}
shares={sharesOutcome === 'YES' ? yesShares : noShares}
shares={shares}
sharesOutcome={sharesOutcome}
setOpen={setShowSellModal}
/>

View File

@ -0,0 +1,56 @@
import { BinaryContract, PseudoNumericContract } from 'common/contract'
import { Bet } from 'common/bet'
import { useEffect, useState } from 'react'
import { partition, sumBy } from 'lodash'
import { safeLocalStorage } from 'web/lib/util/local'
export const useSaveBinaryShares = (
contract: BinaryContract | PseudoNumericContract,
userBets: Bet[] | undefined
) => {
const [savedShares, setSavedShares] = useState({ yesShares: 0, noShares: 0 })
const [yesBets, noBets] = partition(
userBets ?? [],
(bet) => bet.outcome === 'YES'
)
const [yesShares, noShares] = userBets
? [sumBy(yesBets, (bet) => bet.shares), sumBy(noBets, (bet) => bet.shares)]
: [savedShares.yesShares, savedShares.noShares]
useEffect(() => {
const local = safeLocalStorage()
// Read shares from local storage.
const savedShares = local?.getItem(`${contract.id}-shares`)
if (savedShares) {
setSavedShares(JSON.parse(savedShares))
}
if (userBets) {
// Save shares to local storage.
const sharesData = JSON.stringify({ yesShares, noShares })
local?.setItem(`${contract.id}-shares`, sharesData)
}
}, [contract.id, userBets, noShares, yesShares])
const hasYesShares = yesShares >= 1
const hasNoShares = noShares >= 1
const sharesOutcome = hasYesShares
? ('YES' as const)
: hasNoShares
? ('NO' as const)
: undefined
const shares =
sharesOutcome === 'YES' ? yesShares : sharesOutcome === 'NO' ? noShares : 0
return {
yesShares,
noShares,
shares,
sharesOutcome,
hasYesShares,
hasNoShares,
}
}

View File

@ -1,59 +0,0 @@
import { Contract } from 'common/contract'
import { Bet } from 'common/bet'
import { useEffect, useState } from 'react'
import { partition, sumBy } from 'lodash'
import { safeLocalStorage } from 'web/lib/util/local'
export const useSaveShares = (
contract: Contract,
userBets: Bet[] | undefined,
freeResponseAnswerOutcome?: string
) => {
const [savedShares, setSavedShares] = useState<
| {
yesShares: number
noShares: number
yesFloorShares: number
noFloorShares: number
}
| undefined
>()
// TODO: How do we handle numeric yes / no bets? - maybe bet amounts above vs below the highest peak
const [yesBets, noBets] = partition(userBets ?? [], (bet) =>
freeResponseAnswerOutcome
? bet.outcome === freeResponseAnswerOutcome
: bet.outcome === 'YES'
)
const [yesShares, noShares] = [
sumBy(yesBets, (bet) => bet.shares),
sumBy(noBets, (bet) => bet.shares),
]
const yesFloorShares = Math.round(yesShares) === 0 ? 0 : Math.floor(yesShares)
const noFloorShares = Math.round(noShares) === 0 ? 0 : Math.floor(noShares)
useEffect(() => {
const local = safeLocalStorage()
// Save yes and no shares to local storage.
const savedShares = local?.getItem(`${contract.id}-shares`)
if (!userBets && savedShares) {
setSavedShares(JSON.parse(savedShares))
}
if (userBets) {
const updatedShares = { yesShares, noShares }
local?.setItem(`${contract.id}-shares`, JSON.stringify(updatedShares))
}
}, [contract.id, userBets, noShares, yesShares])
if (userBets) return { yesShares, noShares, yesFloorShares, noFloorShares }
return (
savedShares ?? {
yesShares: 0,
noShares: 0,
yesFloorShares: 0,
noFloorShares: 0,
}
)
}