diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts
index 16a416b1..e45e7497 100644
--- a/functions/src/create-contract.ts
+++ b/functions/src/create-contract.ts
@@ -1,7 +1,6 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import * as _ from 'lodash'
-
import { chargeUser, getUser } from './utils'
import {
Binary,
@@ -109,7 +108,16 @@ export const createContract = functions
tags ?? []
)
- if (ante) await chargeUser(creator.id, ante)
+ // uses utc time on server:
+ const today = new Date().setHours(0, 0, 0, 0)
+ const userContractsCreatedTodaySnapshot = await firestore
+ .collection(`contracts`)
+ .where('creatorId', '==', userId)
+ .where('createdTime', '>=', today)
+ .get()
+ const isFree = userContractsCreatedTodaySnapshot.size === 0
+
+ if (!isFree && ante) await chargeUser(creator.id, ante)
await contractRef.create(contract)
diff --git a/web/components/feed-create.tsx b/web/components/feed-create.tsx
index d184e035..0a0c6902 100644
--- a/web/components/feed-create.tsx
+++ b/web/components/feed-create.tsx
@@ -141,7 +141,7 @@ export default function FeedCreate(props: {
{/* Show a fake "Create Market" button, which gets replaced with the NewContract one*/}
{!isExpanded && (
-
diff --git a/web/components/nav/sidebar.tsx b/web/components/nav/sidebar.tsx
index 67e8ae29..576cea87 100644
--- a/web/components/nav/sidebar.tsx
+++ b/web/components/nav/sidebar.tsx
@@ -19,6 +19,7 @@ import { firebaseLogin, firebaseLogout } from '../../lib/firebase/users'
import { ManifoldLogo } from './manifold-logo'
import { MenuButton } from './menu'
import { getNavigationOptions, ProfileSummary } from './profile-menu'
+import { useHasCreatedContractToday } from '../../hooks/use-has-created-contract-today'
const navigation = [
{ name: 'Home', href: '/home', icon: HomeIcon },
@@ -96,6 +97,7 @@ export default function Sidebar() {
const user = useUser()
let folds = useFollowedFolds(user) || []
folds = _.sortBy(folds, 'followCount').reverse()
+ const deservesDailyFreeMarket = !useHasCreatedContractToday(user)
const navigationOptions = user === null ? signedOutNavigation : navigation
const mobileNavigationOptions =
@@ -159,10 +161,22 @@ export default function Sidebar() {
/>
+ {deservesDailyFreeMarket ? (
+
+ Use your daily free market! 🎉
+
+ ) : (
+
+ )}
+
{user && (
-
- Create Market
-
+
+
+
+ Create Market
+
+
+
)}
)
diff --git a/web/hooks/use-has-created-contract-today.ts b/web/hooks/use-has-created-contract-today.ts
new file mode 100644
index 00000000..653049d2
--- /dev/null
+++ b/web/hooks/use-has-created-contract-today.ts
@@ -0,0 +1,27 @@
+import { listContracts } from '../lib/firebase/contracts'
+import { useEffect, useState } from 'react'
+import dayjs from 'dayjs'
+import { User } from '../../common/user'
+
+export const useHasCreatedContractToday = (user: User | null | undefined) => {
+ const [hasCreatedContractToday, setHasCreatedContractToday] = useState(false)
+
+ useEffect(() => {
+ // Uses utc time like the server.
+ const todayAtMidnight = dayjs.utc().startOf('day').valueOf()
+
+ async function listUserContractsForToday() {
+ if (!user) return
+
+ const contracts = await listContracts(user.id)
+ const todayContracts = contracts.filter(
+ (contract) => contract.createdTime > todayAtMidnight
+ )
+ setHasCreatedContractToday(todayContracts.length > 0)
+ }
+
+ listUserContractsForToday()
+ }, [user])
+
+ return hasCreatedContractToday
+}
diff --git a/web/package.json b/web/package.json
index 04f11245..84077a19 100644
--- a/web/package.json
+++ b/web/package.json
@@ -30,6 +30,7 @@
"lodash": "4.17.21",
"next": "12.1.2",
"react": "17.0.2",
+ "react-confetti": "^6.0.1",
"react-dom": "17.0.2",
"react-expanding-textarea": "2.3.5"
},
diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx
index 642de0ca..6e6e4523 100644
--- a/web/pages/[username]/[contractSlug].tsx
+++ b/web/pages/[username]/[contractSlug].tsx
@@ -33,6 +33,8 @@ import { ContractTabs } from '../../components/contract/contract-tabs'
import { FirstArgument } from '../../../common/util/types'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
import { contractTextDetails } from '../../components/contract/contract-details'
+import { useWindowSize } from '../../hooks/use-window-size'
+import Confetti from 'react-confetti'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: {
@@ -86,9 +88,21 @@ export function ContractPageContent(props: FirstArgument) {
const { backToHome } = props
const user = useUser()
+ const { width, height } = useWindowSize()
const contract = useContractWithPreload(props.contract)
const { bets, comments } = props
+ const [showConfetti, setShowConfetti] = useState(false)
+
+ useEffect(() => {
+ const shouldSeeConfetti = !!(
+ user &&
+ contract &&
+ contract.creatorId === user.id &&
+ Date.now() - contract.createdTime < 10 * 1000
+ )
+ setShowConfetti(shouldSeeConfetti)
+ }, [contract, user])
// Sort for now to see if bug is fixed.
comments.sort((c1, c2) => c1.createdTime - c2.createdTime)
@@ -119,6 +133,15 @@ export function ContractPageContent(props: FirstArgument) {
return (
+ {showConfetti && (
+
+ )}
+
{ogCardProps && (
{
// if (ante === null && creator) {
// const initialAnte = creator.balance < 100 ? MINIMUM_ANTE : 100
@@ -246,10 +250,14 @@ export function NewContract(props: { question: string; tag?: string }) {
text={`Cost to create your market. This amount is used to subsidize trading.`}
/>
-
- {formatMoney(ante)}
-
- {ante > balance && (
+ {deservesDailyFreeMarket ? (
+ FREE
+ ) : (
+
+ {formatMoney(ante)}
+
+ )}
+ {!deservesDailyFreeMarket && ante > balance && (
Insufficient balance