diff --git a/functions/src/place-bet.ts b/functions/src/place-bet.ts index 41ad8141..4a1e04f8 100644 --- a/functions/src/place-bet.ts +++ b/functions/src/place-bet.ts @@ -40,9 +40,6 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( return { status: 'error', message: 'User not found' } const user = userSnap.data() as User - if (user.balance < amount) - return { status: 'error', message: 'Insufficient balance' } - const contractDoc = firestore.doc(`contracts/${contractId}`) const contractSnap = await transaction.get(contractDoc) if (!contractSnap.exists) @@ -58,6 +55,10 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( ) const yourBets = yourBetsSnap.docs.map((doc) => doc.data() as Bet) + const loanAmount = getLoanAmount(yourBets, amount) + if (user.balance < amount - loanAmount) + return { status: 'error', message: 'Insufficient balance' } + if (outcomeType === 'FREE_RESPONSE') { const answerSnap = await transaction.get( contractDoc.collection('answers').doc(outcome) @@ -70,8 +71,6 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall( .collection(`contracts/${contractId}/bets`) .doc() - const loanAmount = getLoanAmount(yourBets, amount) - const { newBet, newPool, newTotalShares, newTotalBets, newBalance } = outcomeType === 'BINARY' ? mechanism === 'dpm-2' diff --git a/functions/src/utils.ts b/functions/src/utils.ts index 0e87538a..f34db1c8 100644 --- a/functions/src/utils.ts +++ b/functions/src/utils.ts @@ -68,8 +68,7 @@ const updateUserBalance = ( } export const payUser = (userId: string, payout: number, isDeposit = false) => { - if (!isFinite(payout) || payout <= 0) - throw new Error('Payout is not positive: ' + payout) + if (!isFinite(payout)) throw new Error('Payout is not finite: ' + payout) return updateUserBalance(userId, payout, isDeposit) } diff --git a/web/pages/activity.tsx b/web/components/activity-feed.tsx similarity index 74% rename from web/pages/activity.tsx rename to web/components/activity-feed.tsx index bab58328..bfd4cc1c 100644 --- a/web/pages/activity.tsx +++ b/web/components/activity-feed.tsx @@ -1,9 +1,12 @@ import _ from 'lodash' -import { ContractFeed, ContractSummaryFeed } from '../components/contract-feed' -import { Page } from '../components/page' +import { + ContractActivityFeed, + ContractFeed, + ContractSummaryFeed, +} from './contract-feed' import { Contract } from '../lib/firebase/contracts' import { Comment } from '../lib/firebase/comments' -import { Col } from '../components/layout/col' +import { Col } from './layout/col' import { Bet } from '../../common/bet' const MAX_ACTIVE_CONTRACTS = 75 @@ -72,30 +75,44 @@ export function findActiveContracts( export function ActivityFeed(props: { contracts: Contract[] - contractBets: Bet[][] - contractComments: Comment[][] + recentBets: Bet[] + recentComments: Comment[] + loadBetAndCommentHistory?: boolean }) { - const { contracts, contractBets, contractComments } = props + const { contracts, recentBets, recentComments, loadBetAndCommentHistory } = + props - return contracts.length > 0 ? ( + const groupedBets = _.groupBy(recentBets, (bet) => bet.contractId) + const groupedComments = _.groupBy( + recentComments, + (comment) => comment.contractId + ) + + return ( - + - {contracts.map((contract, i) => ( + {contracts.map((contract) => (
- + {loadBetAndCommentHistory ? ( + + ) : ( + + )}
))} - ) : ( - <> ) } @@ -116,11 +133,3 @@ export function SummaryActivityFeed(props: { contracts: Contract[] }) { ) } - -export default function ActivityPage() { - return ( - - - - ) -} diff --git a/web/components/amount-input.tsx b/web/components/amount-input.tsx index e36962a2..8f20c0ab 100644 --- a/web/components/amount-input.tsx +++ b/web/components/amount-input.tsx @@ -14,7 +14,7 @@ export function AmountInput(props: { onChange: (newAmount: number | undefined) => void error: string | undefined setError: (error: string | undefined) => void - contractId: string | undefined + contractIdForLoan: string | undefined minimumAmount?: number disabled?: boolean className?: string @@ -27,7 +27,7 @@ export function AmountInput(props: { onChange, error, setError, - contractId, + contractIdForLoan, disabled, className, inputClassName, @@ -37,14 +37,13 @@ export function AmountInput(props: { const user = useUser() - const userBets = useUserContractBets(user?.id, contractId) ?? [] + const userBets = useUserContractBets(user?.id, contractIdForLoan) ?? [] const openUserBets = userBets.filter((bet) => !bet.isSold && !bet.sale) const prevLoanAmount = _.sumBy(openUserBets, (bet) => bet.loanAmount ?? 0) - const loanAmount = Math.min( - amount ?? 0, - MAX_LOAN_PER_CONTRACT - prevLoanAmount - ) + const loanAmount = contractIdForLoan + ? Math.min(amount ?? 0, MAX_LOAN_PER_CONTRACT - prevLoanAmount) + : 0 const onAmountChange = (str: string) => { if (str.includes('-')) { @@ -58,7 +57,12 @@ export function AmountInput(props: { onChange(str ? amount : undefined) - if (user && user.balance < amount) { + const loanAmount = contractIdForLoan + ? Math.min(amount, MAX_LOAN_PER_CONTRACT - prevLoanAmount) + : 0 + const amountNetLoan = amount - loanAmount + + if (user && user.balance < amountNetLoan) { setError('Insufficient balance') } else if (minimumAmount && amount < minimumAmount) { setError('Minimum amount: ' + formatMoney(minimumAmount)) @@ -99,7 +103,7 @@ export function AmountInput(props: { )} {user && ( - {contractId && ( + {contractIdForLoan && ( Amount loaned{' '} diff --git a/web/components/analytics/charts.tsx b/web/components/analytics/charts.tsx new file mode 100644 index 00000000..3a315d38 --- /dev/null +++ b/web/components/analytics/charts.tsx @@ -0,0 +1,49 @@ +import { ResponsiveLine } from '@nivo/line' +import dayjs from 'dayjs' +import _ from 'lodash' +import { useWindowSize } from '../../hooks/use-window-size' + +export function DailyCountChart(props: { + startDate: number + dailyCounts: number[] + small?: boolean +}) { + const { dailyCounts, startDate, small } = props + const { width } = useWindowSize() + + const dates = dailyCounts.map((_, i) => + dayjs(startDate).add(i, 'day').toDate() + ) + + const points = _.zip(dates, dailyCounts).map(([date, betCount]) => ({ + x: date, + y: betCount, + })) + const data = [{ id: 'Count', data: points, color: '#11b981' }] + + return ( +
= 800) ? 400 : 250 }} + > + dayjs(date).format('MMM DD'), + }} + colors={{ datum: 'color' }} + pointSize={width && width >= 800 ? 10 : 0} + pointBorderWidth={1} + pointBorderColor="#fff" + enableSlices="x" + enableGridX={!!width && width >= 800} + enableArea + margin={{ top: 20, right: 28, bottom: 22, left: 40 }} + /> +
+ ) +} diff --git a/web/components/answers/answer-bet-panel.tsx b/web/components/answers/answer-bet-panel.tsx index bad7efc0..e426c370 100644 --- a/web/components/answers/answer-bet-panel.tsx +++ b/web/components/answers/answer-bet-panel.tsx @@ -49,11 +49,6 @@ export function AnswerBetPanel(props: { async function submitBet() { if (!user || !betAmount) return - if (user.balance < betAmount) { - setError('Insufficient balance') - return - } - setError(undefined) setIsSubmitting(true) @@ -103,11 +98,11 @@ export function AnswerBetPanel(props: { return ( - +
Buy this answer
Amount
@@ -119,10 +114,10 @@ export function AnswerBetPanel(props: { setError={setError} disabled={isSubmitting} inputRef={inputRef} - contractId={contract.id} + contractIdForLoan={contract.id} /> - - + +
Probability
{formatPercent(initialProb)}
@@ -131,8 +126,8 @@ export function AnswerBetPanel(props: {
- - + +
Payout if chosen
- + {formatMoney(currentPayout)} diff --git a/web/components/answers/answer-resolve-panel.tsx b/web/components/answers/answer-resolve-panel.tsx index a1f8b1d1..ae79cdf3 100644 --- a/web/components/answers/answer-resolve-panel.tsx +++ b/web/components/answers/answer-resolve-panel.tsx @@ -71,9 +71,9 @@ export function AnswerResolvePanel(props: { : 'btn-disabled' return ( - +
Resolve your market
- + No answers yet... +
No answers yet...
) : ( -
+
None of the above:{' '} {formatPercent(getDpmOutcomeProbability(contract.totalShares, '0'))}
diff --git a/web/components/answers/create-answer-panel.tsx b/web/components/answers/create-answer-panel.tsx index af7f3cf2..35e04db2 100644 --- a/web/components/answers/create-answer-panel.tsx +++ b/web/components/answers/create-answer-panel.tsx @@ -38,6 +38,7 @@ export function CreateAnswerPanel(props: { const submitAnswer = async () => { if (canSubmit) { setIsSubmitting(true) + const result = await createAnswer({ contractId: contract.id, text, @@ -50,7 +51,7 @@ export function CreateAnswerPanel(props: { setText('') setBetAmount(10) setAmountError(undefined) - } + } else setAmountError(result.message) } } @@ -74,7 +75,7 @@ export function CreateAnswerPanel(props: { const currentReturnPercent = (currentReturn * 100).toFixed() + '%' return ( - +
Add your answer