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 && ( - - - +
+ + + +
)} ) 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