From c91f6988432d341cd77a5fc39c628ca50ac713ae Mon Sep 17 00:00:00 2001 From: Ian Philips Date: Wed, 13 Mar 2024 14:52:03 -0700 Subject: [PATCH] Signed out home-page variations --- web/components/home/typewriter.tsx | 48 ++++++++++++ web/hooks/use-ab-test.ts | 10 +-- web/hooks/use-is-feed-test.ts | 8 -- web/pages/index.tsx | 113 +++++++++++++++++++++-------- 4 files changed, 136 insertions(+), 43 deletions(-) create mode 100644 web/components/home/typewriter.tsx delete mode 100644 web/hooks/use-is-feed-test.ts diff --git a/web/components/home/typewriter.tsx b/web/components/home/typewriter.tsx new file mode 100644 index 0000000000..bc7feff1a0 --- /dev/null +++ b/web/components/home/typewriter.tsx @@ -0,0 +1,48 @@ +import { useEffect, useRef } from 'react' + +export const Typewriter = (props: { words: string[] }) => { + const { words } = props + const typingElement = useRef(null) + const wordIndex = useRef(0) + const charIndex = useRef(0) + const deleteTimeout = 2000 + const typeTimeout = 100 + + useEffect(() => { + typeWords() + }, []) + + const typeWords = () => { + if (typingElement.current) { + if (charIndex.current < words[wordIndex.current].length) { + typingElement.current.textContent = + words[wordIndex.current].substring(0, charIndex.current) + '_' + charIndex.current++ + setTimeout(typeWords, typeTimeout) + } else { + typingElement.current.textContent = words[wordIndex.current] + setTimeout(eraseWords, deleteTimeout) + } + } + } + + const eraseWords = () => { + if (typingElement.current) { + if (charIndex.current > 0) { + typingElement.current.textContent = + words[wordIndex.current].substring(0, charIndex.current - 1) + '_' + charIndex.current-- + setTimeout(eraseWords, 50) + } else { + wordIndex.current++ + if (wordIndex.current >= words.length) { + wordIndex.current = 0 + } + charIndex.current = 0 + setTimeout(typeWords, 500) + } + } + } + + return +} diff --git a/web/hooks/use-ab-test.ts b/web/hooks/use-ab-test.ts index 2b9d3a6cd2..a3a7e8b80f 100644 --- a/web/hooks/use-ab-test.ts +++ b/web/hooks/use-ab-test.ts @@ -8,7 +8,7 @@ const AB_TEST_CACHE: { [testName: string]: boolean } = {} export const useABTest = ( testName: string, - variants: { [variantName: string]: T }, + variants: T[], trackingProperties?: any ) => { const [variant, setVariant] = useState(undefined) @@ -18,16 +18,16 @@ export const useABTest = ( if (!deviceId) return const rand = createRNG(testName + deviceId) - const keys = Object.keys(variants).sort() - const key = keys[Math.floor(rand() * keys.length)] + const keys = variants.sort() + const randomVariant = keys[Math.floor(rand() * keys.length)] - setVariant(variants[key]) + setVariant(randomVariant) // only track once per user session if (!AB_TEST_CACHE[testName]) { AB_TEST_CACHE[testName] = true - track(testName, { ...trackingProperties, variant: key }) + track(testName, { ...trackingProperties, variant }) } }, [testName, trackingProperties, variants]) diff --git a/web/hooks/use-is-feed-test.ts b/web/hooks/use-is-feed-test.ts deleted file mode 100644 index 17d4c38720..0000000000 --- a/web/hooks/use-is-feed-test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useABTest } from './use-ab-test' - -export const useIsFeedTest = () => { - return useABTest('test feed homepage 2', { - markets: false, - feed: true, - }) -} diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 24ab293cfd..522c333f42 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -11,9 +11,7 @@ import { firebaseLogin } from 'web/lib/firebase/users' import { Button } from 'web/components/buttons/button' import { redirectIfLoggedIn } from 'web/lib/firebase/server-auth' import { AboutPrivacyTerms } from 'web/components/privacy-terms' -import { formatMoney } from 'common/util/format' import { useRedirectIfSignedIn } from 'web/hooks/use-redirect-if-signed-in' -import { MARKET_VISIT_BONUS_TOTAL, STARTING_BALANCE } from 'common/economy' import { ManifoldLogo } from 'web/components/nav/manifold-logo' import { LogoSEO } from 'web/components/LogoSEO' import { MobileAppsQRCodeDialog } from 'web/components/buttons/mobile-apps-qr-code-button' @@ -26,28 +24,30 @@ import { useUser } from 'web/hooks/use-user' import { some } from 'd3-array' import { getContract } from 'web/lib/supabase/contracts' import { useABTest } from 'web/hooks/use-ab-test' +import { Typewriter } from 'web/components/home/typewriter' +import { filterDefined } from 'common/util/array' const excluded = [...DEEMPHASIZED_GROUP_SLUGS, 'manifold-6748e065087e'] export const getServerSideProps = redirectIfLoggedIn('/home', async (_) => { const { data } = await db - .from('trending_contracts') + .from('contracts') .select('data') - .neq('outcome_type', 'STONK') - .gt('data->uniqueBettorCount', 10) + .not( + 'outcome_type', + 'in', + `(${['STONK', 'BOUNTIED_QUESTION', 'POLL'].join(',')})` + ) + .order('importance_score', { ascending: false }) .limit(50) const contracts = (data ?? []).map((d) => d.data) as Contract[] const prezContract = await getContract('ikSUiiNS8MwAI75RwEJf') - const filteredContracts = [prezContract, ...contracts] - .filter( - (c) => c && !c.groupSlugs?.some((slug) => excluded.includes(slug as any)) - ) - .filter( - (c) => c?.outcomeType !== 'POLL' && c?.outcomeType !== 'BOUNTIED_QUESTION' - ) as Contract[] + const filteredContracts = filterDefined([prezContract, ...contracts]).filter( + (c) => !c.groupSlugs?.some((slug) => excluded.includes(slug as any)) + ) const hasCommonGroupSlug = (contract: Contract, groupSlugsSet: string[]) => some(contract.groupSlugs ?? [], (slug) => groupSlugsSet.includes(slug)) @@ -77,10 +77,13 @@ export default function LandingPage(props: { const [isModalOpen, setIsModalOpen] = useState(false) - const abTestVariant = useABTest('landing-page-bet-vs-predict', { - bet: 'bet', - predict: 'predict', - }) + const abTestVariant = useABTest('signed out home page variations v0', [ + 'know the future', + 'real-world impact', + 'wisdom of the crowds', + 'bet', + 'bet-typewriter', + ]) return ( @@ -148,6 +151,50 @@ export default function LandingPage(props: { + {abTestVariant === 'know the future' && ( + <> +

Know the future

+

+ Harness wisdom of the crowds to predict politics, pandemics, + tech breakthroughs & more with + play money. +

+

+ Forecasting for everything that matters. +

+ + )} + {abTestVariant === 'real-world impact' && ( + <> +

+ Play-money betting. +
+ Real-world impact. +

+

+ Harness humanity's collective intelligence to make smarter + decisions through forecasting. +

+

+ Join a global community betting on a better world. +

+ + )} + {abTestVariant === 'wisdom of the crowds' && ( + <> +

+ Harness the wisdom of the crowds +

+

+ Play-money betting.{' '} + Real-world decisions. +

+

+ Shape the world with accurate forecasts for better + decision-making. +

+ + )} {abTestVariant === 'bet' && ( <>

Bet on politics & more

@@ -160,17 +207,27 @@ export default function LandingPage(props: { )} - {abTestVariant !== 'bet' && ( + {abTestVariant === 'bet-typewriter' && ( <>

- Predict & win internet points + Bet on the next:{' '} + + +

- Trade in markets with real-world accuracy. + Play-money betting. Real-world accuracy.

- Compete with your friends on politics, tech, sports, and - more. + Forecast events in politics, policy, technology, sports & + beyond.

)} @@ -180,16 +237,12 @@ export default function LandingPage(props: { className="mt-8" onClick={firebaseLogin} > - Start predicting + {abTestVariant === 'know the future' + ? 'See the forecasts' + : abTestVariant === 'real-world impact' + ? 'Forecast the future' + : 'Start predicting'} -
- ...and get{' '} - - {formatMoney(STARTING_BALANCE + MARKET_VISIT_BONUS_TOTAL)} - - {' '} - in play money! -