Add countdown timer for daily free market (#276)

* Add countdown timer for daily free market

* Reset example numbers

* Remove daily

* Free market reset => 4pm UTC
This commit is contained in:
Ian Philips 2022-05-23 08:43:11 -06:00 committed by GitHub
parent 355b2261a7
commit d0347ff5c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 39 deletions

View File

@ -87,13 +87,17 @@ export const createContract = newEndpoint(['POST'], async (req, _res) => {
) )
throw new APIError(400, 'Invalid initial probability') throw new APIError(400, 'Invalid initial probability')
// uses utc time on server: // Uses utc time on server:
const today = new Date().setHours(0, 0, 0, 0) const yesterday = new Date()
yesterday.setUTCDate(yesterday.getUTCDate() - 1)
const freeMarketResetTime = yesterday.setUTCHours(16, 0, 0, 0)
const userContractsCreatedTodaySnapshot = await firestore const userContractsCreatedTodaySnapshot = await firestore
.collection(`contracts`) .collection(`contracts`)
.where('creatorId', '==', creator.id) .where('creatorId', '==', creator.id)
.where('createdTime', '>=', today) .where('createdTime', '>=', freeMarketResetTime)
.get() .get()
console.log('free market reset time: ', freeMarketResetTime)
const isFree = userContractsCreatedTodaySnapshot.size === 0 const isFree = userContractsCreatedTodaySnapshot.size === 0
const ante = FIXED_ANTE const ante = FIXED_ANTE

View File

@ -20,8 +20,12 @@ import { firebaseLogin, firebaseLogout } from 'web/lib/firebase/users'
import { ManifoldLogo } from './manifold-logo' import { ManifoldLogo } from './manifold-logo'
import { MenuButton } from './menu' import { MenuButton } from './menu'
import { getNavigationOptions, ProfileSummary } from './profile-menu' import { getNavigationOptions, ProfileSummary } from './profile-menu'
import { useHasCreatedContractToday } from 'web/hooks/use-has-created-contract-today' import {
getUtcFreeMarketResetTimeToday,
useHasCreatedContractToday,
} from 'web/hooks/use-has-created-contract-today'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { useEffect, useState } from 'react'
// Create an icon from the url of an image // Create an icon from the url of an image
function IconFromUrl(url: string): React.ComponentType<{ className?: string }> { function IconFromUrl(url: string): React.ComponentType<{ className?: string }> {
@ -121,12 +125,30 @@ export default function Sidebar(props: { className?: string }) {
const { className } = props const { className } = props
const router = useRouter() const router = useRouter()
const currentPage = router.pathname const currentPage = router.pathname
const [countdown, setCountdown] = useState('...')
useEffect(() => {
const utcMidnightToLocalDate = new Date(getUtcFreeMarketResetTimeToday())
const interval = setInterval(() => {
const timeUntil = utcMidnightToLocalDate.getTime() - new Date().getTime()
const hoursUntil = 24 + timeUntil / 1000 / 60 / 60
const minutesUntil = Math.floor((hoursUntil * 60) % 60)
const secondsUntil = Math.floor((hoursUntil * 60 * 60) % 60)
const hoursUntilFloor = Math.floor(hoursUntil)
const timeString =
minutesUntil < 1
? `${secondsUntil}s`
: hoursUntilFloor < 1
? `${minutesUntil}m`
: `${hoursUntilFloor}h`
setCountdown(timeString)
}, 1000)
return () => clearInterval(interval)
}, [])
const user = useUser() const user = useUser()
let folds = useFollowedFolds(user) || [] let folds = useFollowedFolds(user) || []
folds = sortBy(folds, 'followCount').reverse() folds = sortBy(folds, 'followCount').reverse()
const deservesDailyFreeMarket = !useHasCreatedContractToday(user) const mustWaitForFreeMarketStatus = useHasCreatedContractToday(user)
const navigationOptions = const navigationOptions =
user === null user === null
? signedOutNavigation ? signedOutNavigation
@ -186,13 +208,25 @@ export default function Sidebar(props: { className?: string }) {
)} )}
</div> </div>
{user && deservesDailyFreeMarket && ( {user &&
mustWaitForFreeMarketStatus != 'loading' &&
mustWaitForFreeMarketStatus ? (
<Row className="mt-2 justify-center"> <Row className="mt-2 justify-center">
<Row className="gap-1 text-sm text-indigo-400"> <Row className="gap-1 text-sm text-gray-400">
Daily free market Next free market in {countdown}
<SparklesIcon className="mt-0.5 h-4 w-4" aria-hidden="true" />
</Row> </Row>
</Row> </Row>
) : (
user &&
mustWaitForFreeMarketStatus != 'loading' &&
!mustWaitForFreeMarketStatus && (
<Row className="mt-2 justify-center">
<Row className="gap-1 text-sm text-indigo-400">
Daily free market
<SparklesIcon className="mt-0.5 h-4 w-4" aria-hidden="true" />
</Row>
</Row>
)
)} )}
</nav> </nav>
) )

View File

@ -4,16 +4,22 @@ import { User } from 'common/user'
let sessionCreatedContractToday = true let sessionCreatedContractToday = true
export function getUtcFreeMarketResetTimeToday() {
// Uses utc time like the server.
const utcFreeMarketResetTime = new Date()
utcFreeMarketResetTime.setUTCDate(utcFreeMarketResetTime.getUTCDate() - 1)
const utcFreeMarketMS = utcFreeMarketResetTime.setUTCHours(16, 0, 0, 0)
return utcFreeMarketMS
}
export const useHasCreatedContractToday = (user: User | null | undefined) => { export const useHasCreatedContractToday = (user: User | null | undefined) => {
const [hasCreatedContractToday, setHasCreatedContractToday] = useState( const [hasCreatedContractToday, setHasCreatedContractToday] = useState<
sessionCreatedContractToday boolean | 'loading'
) >('loading')
useEffect(() => { useEffect(() => {
// Uses utc time like the server. setHasCreatedContractToday('loading')
const utcTimeString = new Date().toISOString() const todayAtMidnight = getUtcFreeMarketResetTimeToday()
const todayAtMidnight = new Date(utcTimeString).setUTCHours(0, 0, 0, 0)
async function listUserContractsForToday() { async function listUserContractsForToday() {
if (!user) return if (!user) return

View File

@ -77,7 +77,7 @@ export function NewContract(props: { question: string; tag?: string }) {
const [ante, setAnte] = useState(FIXED_ANTE) const [ante, setAnte] = useState(FIXED_ANTE)
const deservesDailyFreeMarket = !useHasCreatedContractToday(creator) const mustWaitForDailyFreeMarketStatus = useHasCreatedContractToday(creator)
// useEffect(() => { // useEffect(() => {
// if (ante === null && creator) { // if (ante === null && creator) {
@ -107,7 +107,9 @@ export function NewContract(props: { question: string; tag?: string }) {
ante !== undefined && ante !== undefined &&
ante !== null && ante !== null &&
ante >= MINIMUM_ANTE && ante >= MINIMUM_ANTE &&
(ante <= balance || deservesDailyFreeMarket) && (ante <= balance ||
(mustWaitForDailyFreeMarketStatus != 'loading' &&
!mustWaitForDailyFreeMarketStatus)) &&
// closeTime must be in the future // closeTime must be in the future
closeTime && closeTime &&
closeTime > Date.now() && closeTime > Date.now() &&
@ -368,13 +370,15 @@ export function NewContract(props: { question: string; tag?: string }) {
<div className="form-control mb-1 items-start"> <div className="form-control mb-1 items-start">
<label className="label mb-1 gap-2"> <label className="label mb-1 gap-2">
<span>Cost</span> <span>Cost</span>
{!deservesDailyFreeMarket && ( {mustWaitForDailyFreeMarketStatus != 'loading' &&
<InfoTooltip mustWaitForDailyFreeMarketStatus && (
text={`Cost to create your market. This amount is used to subsidize trading.`} <InfoTooltip
/> text={`Cost to create your market. This amount is used to subsidize trading.`}
)} />
)}
</label> </label>
{deservesDailyFreeMarket ? ( {mustWaitForDailyFreeMarketStatus != 'loading' &&
!mustWaitForDailyFreeMarketStatus ? (
<div className="label-text text-primary pl-1"> <div className="label-text text-primary pl-1">
<span className={'label-text text-neutral line-through '}> <span className={'label-text text-neutral line-through '}>
{formatMoney(ante)} {formatMoney(ante)}
@ -382,21 +386,25 @@ export function NewContract(props: { question: string; tag?: string }) {
FREE FREE
</div> </div>
) : ( ) : (
<div className="label-text text-neutral pl-1"> mustWaitForDailyFreeMarketStatus != 'loading' && (
{formatMoney(ante)} <div className="label-text text-neutral pl-1">
</div> {formatMoney(ante)}
)} </div>
{!deservesDailyFreeMarket && ante > balance && ( )
<div className="mb-2 mt-2 mr-auto self-center whitespace-nowrap text-xs font-medium tracking-wide">
<span className="mr-2 text-red-500">Insufficient balance</span>
<button
className="btn btn-xs btn-primary"
onClick={() => (window.location.href = '/add-funds')}
>
Add funds
</button>
</div>
)} )}
{mustWaitForDailyFreeMarketStatus != 'loading' &&
mustWaitForDailyFreeMarketStatus &&
ante > balance && (
<div className="mb-2 mt-2 mr-auto self-center whitespace-nowrap text-xs font-medium tracking-wide">
<span className="mr-2 text-red-500">Insufficient balance</span>
<button
className="btn btn-xs btn-primary"
onClick={() => (window.location.href = '/add-funds')}
>
Add funds
</button>
</div>
)}
{/* <BuyAmountInput {/* <BuyAmountInput
amount={ante ?? undefined} amount={ante ?? undefined}