From 54778ec1b1d268e4e871287392367e764990f22b Mon Sep 17 00:00:00 2001 From: mantikoros <95266179+mantikoros@users.noreply.github.com> Date: Tue, 20 Sep 2022 19:23:18 -0500 Subject: [PATCH 001/199] Fix twitch onboarding (#910) * don't show welcome dialog for twitch users * handle sign up race conditions with more hooks * content organization and copy tweaks * lint * fix import --- web/components/onboarding/welcome.tsx | 36 ++++++++--- web/pages/twitch.tsx | 92 +++++++++++++++------------ 2 files changed, 79 insertions(+), 49 deletions(-) diff --git a/web/components/onboarding/welcome.tsx b/web/components/onboarding/welcome.tsx index 654357c5..b18ef83f 100644 --- a/web/components/onboarding/welcome.tsx +++ b/web/components/onboarding/welcome.tsx @@ -1,6 +1,9 @@ -import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid' import clsx from 'clsx' -import { useState } from 'react' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' +import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid' + +import { User } from 'common/user' import { useUser } from 'web/hooks/use-user' import { updateUser } from 'web/lib/firebase/users' import { Col } from '../layout/col' @@ -27,16 +30,12 @@ export default function Welcome() { } } - async function setUserHasSeenWelcome() { - if (user) { - await updateUser(user.id, { ['shouldShowWelcome']: false }) - } + const setUserHasSeenWelcome = async () => { + if (user) await updateUser(user.id, { ['shouldShowWelcome']: false }) } const [groupSelectorOpen, setGroupSelectorOpen] = useState(false) - if (!user || (!user.shouldShowWelcome && !groupSelectorOpen)) return <> - const toggleOpen = (isOpen: boolean) => { setUserHasSeenWelcome() setOpen(isOpen) @@ -45,6 +44,12 @@ export default function Welcome() { setGroupSelectorOpen(true) } } + + const isTwitch = useIsTwitch(user) + + if (isTwitch || !user || (!user.shouldShowWelcome && !groupSelectorOpen)) + return <> + return ( <> { + const router = useRouter() + const isTwitch = router.pathname === '/twitch' + + useEffect(() => { + console.log('twich?', isTwitch) + + if (isTwitch && user?.shouldShowWelcome) { + updateUser(user.id, { ['shouldShowWelcome']: false }) + } + }, [isTwitch, user]) + + return isTwitch +} + function PageIndicator(props: { page: number; totalpages: number }) { const { page, totalpages } = props return ( diff --git a/web/pages/twitch.tsx b/web/pages/twitch.tsx index 46856eaf..22d3152d 100644 --- a/web/pages/twitch.tsx +++ b/web/pages/twitch.tsx @@ -1,8 +1,7 @@ import { LinkIcon } from '@heroicons/react/solid' import clsx from 'clsx' import { PrivateUser, User } from 'common/user' -import Link from 'next/link' -import { MouseEventHandler, ReactNode, useState } from 'react' +import { MouseEventHandler, ReactNode, useEffect, useState } from 'react' import toast from 'react-hot-toast' import { Button } from 'web/components/button' @@ -17,11 +16,7 @@ import { Title } from 'web/components/title' import { useSaveReferral } from 'web/hooks/use-save-referral' import { useTracking } from 'web/hooks/use-tracking' import { usePrivateUser, useUser } from 'web/hooks/use-user' -import { - firebaseLogin, - getUserAndPrivateUser, - updatePrivateUser, -} from 'web/lib/firebase/users' +import { firebaseLogin, updatePrivateUser } from 'web/lib/firebase/users' import { track } from 'web/lib/service/analytics' import { getDockURLForUser, @@ -40,23 +35,32 @@ function ButtonGetStarted(props: { const { user, privateUser, buttonClass, spinnerClass } = props const [isLoading, setLoading] = useState(false) + const needsRelink = privateUser?.twitchInfo?.twitchName && privateUser?.twitchInfo?.needsRelinking + const [waitingForUser, setWaitingForUser] = useState(false) + useEffect(() => { + if (waitingForUser && user && privateUser) { + setWaitingForUser(false) + + if (privateUser.twitchInfo?.twitchName) return // If we've already linked Twitch, no need to do so again + + setLoading(true) + + linkTwitchAccountRedirect(user, privateUser).then(() => { + setLoading(false) + }) + } + }, [user, privateUser, waitingForUser]) + const callback = user && privateUser ? () => linkTwitchAccountRedirect(user, privateUser) : async () => { - const result = await firebaseLogin() - - const userId = result.user.uid - const { user, privateUser } = await getUserAndPrivateUser(userId) - if (!user || !privateUser) return - - if (privateUser.twitchInfo?.twitchName) return // If we've already linked Twitch, no need to do so again - - await linkTwitchAccountRedirect(user, privateUser) + await firebaseLogin() + setWaitingForUser(true) } const getStarted = async () => { @@ -110,20 +114,9 @@ function TwitchPlaysManifoldMarkets(props: { className={'!-my-0 md:block'} /> - -
- Similar to Twitch channel point predictions, Manifold Markets allows - you to create and feature on stream any question you like with users - predicting to earn play money. -
-
- The key difference is that Manifold's questions function more like a - stock market and viewers can buy and sell shares over the course of - the event and not just at the start. The market will eventually - resolve to yes or no at which point the winning shareholders will - receive their profit. -
- Start playing now by logging in with Google and typing commands in chat! + + Start betting on Twitch now by linking your account and typing commands + in chat! {twitchUser && !twitchInfo.needsRelinking ? ( + + + + + ) +} + +export function GraphToggle(props: { + setGraphMode: (mode: 'profit' | 'value') => void + graphMode: string +}) { + const { setGraphMode, graphMode } = props + return ( + + { + setGraphMode('value') + }} + xs={true} + className="z-50" + > + Value + + { + setGraphMode('profit') + }} + xs={true} + className="z-50" + > + Profit + + + ) +} diff --git a/web/components/profile/user-likes-button.tsx b/web/components/profile/user-likes-button.tsx index 3d4fa9ac..666036a8 100644 --- a/web/components/profile/user-likes-button.tsx +++ b/web/components/profile/user-likes-button.tsx @@ -10,15 +10,15 @@ import { XIcon } from '@heroicons/react/outline' import { unLikeContract } from 'web/lib/firebase/likes' import { contractPath } from 'web/lib/firebase/contracts' -export function UserLikesButton(props: { user: User }) { - const { user } = props +export function UserLikesButton(props: { user: User; className?: string }) { + const { user, className } = props const [isOpen, setIsOpen] = useState(false) const likedContracts = useUserLikedContracts(user.id) return ( <> - setIsOpen(true)}> + setIsOpen(true)} className={className}> {likedContracts?.length ?? ''}{' '} Likes diff --git a/web/components/referrals-button.tsx b/web/components/referrals-button.tsx index b164e10c..9a548031 100644 --- a/web/components/referrals-button.tsx +++ b/web/components/referrals-button.tsx @@ -13,14 +13,18 @@ import { getUser, updateUser } from 'web/lib/firebase/users' import { TextButton } from 'web/components/text-button' import { UserLink } from 'web/components/user-link' -export function ReferralsButton(props: { user: User; currentUser?: User }) { - const { user, currentUser } = props +export function ReferralsButton(props: { + user: User + currentUser?: User + className?: string +}) { + const { user, currentUser, className } = props const [isOpen, setIsOpen] = useState(false) const referralIds = useReferrals(user.id) return ( <> - setIsOpen(true)}> + setIsOpen(true)} className={className}> {referralIds?.length ?? ''}{' '} Referrals diff --git a/web/components/user-page.tsx b/web/components/user-page.tsx index f9845fbe..bcbb395e 100644 --- a/web/components/user-page.tsx +++ b/web/components/user-page.tsx @@ -1,8 +1,13 @@ import clsx from 'clsx' import { useEffect, useState } from 'react' -import { useRouter } from 'next/router' +import { NextRouter, useRouter } from 'next/router' import { LinkIcon } from '@heroicons/react/solid' -import { PencilIcon } from '@heroicons/react/outline' +import { + ChatIcon, + FolderIcon, + PencilIcon, + ScaleIcon, +} from '@heroicons/react/outline' import { User } from 'web/lib/firebase/users' import { useUser } from 'web/hooks/use-user' @@ -24,39 +29,23 @@ import { FollowersButton, FollowingButton } from './following-button' import { UserFollowButton } from './follow-button' import { GroupsButton } from 'web/components/groups/groups-button' import { PortfolioValueSection } from './portfolio/portfolio-value-section' -import { ReferralsButton } from 'web/components/referrals-button' import { formatMoney } from 'common/util/format' -import { ShareIconButton } from 'web/components/share-icon-button' -import { ENV_CONFIG } from 'common/envs/constants' import { BettingStreakModal, hasCompletedStreakToday, } from 'web/components/profile/betting-streak-modal' -import { REFERRAL_AMOUNT } from 'common/economy' import { LoansModal } from './profile/loans-modal' -import { UserLikesButton } from 'web/components/profile/user-likes-button' -import { PAST_BETS } from 'common/user' -import { capitalize } from 'lodash' export function UserPage(props: { user: User }) { const { user } = props const router = useRouter() const currentUser = useUser() const isCurrentUser = user.id === currentUser?.id - const bannerUrl = user.bannerUrl ?? defaultBannerUrl(user.id) const [showConfetti, setShowConfetti] = useState(false) - const [showBettingStreakModal, setShowBettingStreakModal] = useState(false) - const [showLoansModal, setShowLoansModal] = useState(false) useEffect(() => { const claimedMana = router.query['claimed-mana'] === 'yes' - const showBettingStreak = router.query['show'] === 'betting-streak' - setShowBettingStreakModal(showBettingStreak) - setShowConfetti(claimedMana || showBettingStreak) - - const showLoansModel = router.query['show'] === 'loans' - setShowLoansModal(showLoansModel) - + setShowConfetti(claimedMana) const query = { ...router.query } if (query.claimedMana || query.show) { delete query['claimed-mana'] @@ -85,218 +74,159 @@ export function UserPage(props: { user: User }) { {showConfetti && ( )} - - {showLoansModal && ( - - )} - {/* Banner image up top, with an circle avatar overlaid */} -
-
-
+ + -
- - {/* Top right buttons (e.g. edit, follow) */} -
- {!isCurrentUser && } {isCurrentUser && ( - - {' '} -
Edit
-
+
+ + {' '} + +
)} -
-
- {/* Profile details: name, username, bio, and link to twitter/discord */} - - - - - {user.name} - - @{user.username} - - - - - = 0 ? 'text-green-600' : 'text-red-400' - )} - > - {formatMoney(profit)} + +
+ + + {user.name} - profit - - setShowBettingStreakModal(true)} - > - 🔥 {user.currentBettingStreak ?? 0} - streak - - setShowLoansModal(true)} - > - - 🏦 {formatMoney(user.nextLoanCached ?? 0)} + + @{user.username} - next loan - + {isCurrentUser && ( + + )} + {!isCurrentUser && } +
+
- - {user.bio && ( - <> -
- -
- - - )} - {(user.website || user.twitterHandle || user.discordHandle) && ( - - {user.website && ( - - - - {user.website} - - - )} - - {user.twitterHandle && ( - - - Twitter - - {user.twitterHandle} - - - - )} - - {user.discordHandle && ( - - - Discord - - {user.discordHandle} - - - - )} - - )} - {currentUser?.id === user.id && REFERRAL_AMOUNT > 0 && ( - - - - Earn {formatMoney(REFERRAL_AMOUNT)} when you refer a friend! - {' '} - You've gotten{' '} - - - - - )} - - ), - }, - { - title: 'Comments', - content: ( - - - - ), - }, - { - title: capitalize(PAST_BETS), - content: ( - <> - - - ), - }, - { - title: 'Stats', - content: ( - - - - - - - + + + + + {user.bio && ( + <> +
+ +
+ + + )} + {(user.website || user.twitterHandle || user.discordHandle) && ( + + {user.website && ( + + + + + {user.website} + - - - ), - }, - ]} - /> + + )} + + {user.twitterHandle && ( + + + Twitter + + {user.twitterHandle} + + + + )} + + {user.discordHandle && ( + + + Discord + + {user.discordHandle} + + + + )} + + )} + , + content: ( + <> + + + + + + ), + }, + { + title: 'Markets', + tabIcon: , + content: ( + <> + + + + ), + }, + { + title: 'Comments', + tabIcon: , + content: ( + <> + + + + + + ), + }, + ]} + /> + ) @@ -314,3 +244,88 @@ export function defaultBannerUrl(userId: string) { ] return defaultBanner[genHash(userId)() % defaultBanner.length] } + +export function ProfilePrivateStats(props: { + currentUser: User | null | undefined + profit: number + user: User + router: NextRouter +}) { + const { currentUser, profit, user, router } = props + const [showBettingStreakModal, setShowBettingStreakModal] = useState(false) + const [showLoansModal, setShowLoansModal] = useState(false) + + useEffect(() => { + const showBettingStreak = router.query['show'] === 'betting-streak' + setShowBettingStreakModal(showBettingStreak) + + const showLoansModel = router.query['show'] === 'loans' + setShowLoansModal(showLoansModel) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + return ( + <> + + + = 0 ? 'text-green-600' : 'text-red-400')} + > + {formatMoney(profit)} + + profit + + setShowBettingStreakModal(true)} + > + + 🔥 {user.currentBettingStreak ?? 0} + + + streak + + + setShowLoansModal(true)} + > + + 🏦 {formatMoney(user.nextLoanCached ?? 0)} + + next loan + + + {BettingStreakModal && ( + + )} + {showLoansModal && ( + + )} + + ) +} + +export function ProfilePublicStats(props: { user: User; className?: string }) { + const { user, className } = props + return ( + + + + {/* */} + + {/* */} + + ) +} diff --git a/web/pages/profile.tsx b/web/pages/profile.tsx index 2c095db6..caa9f47a 100644 --- a/web/pages/profile.tsx +++ b/web/pages/profile.tsx @@ -13,7 +13,6 @@ import { Page } from 'web/components/page' import { SEO } from 'web/components/SEO' import { SiteLink } from 'web/components/site-link' import { Title } from 'web/components/title' -import { defaultBannerUrl } from 'web/components/user-page' import { generateNewApiKey } from 'web/lib/api/api-key' import { changeUserInfo } from 'web/lib/firebase/api' import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' @@ -176,27 +175,6 @@ export default function ProfilePage(props: { onBlur={updateUsername} /> - - {/* TODO: Allow users with M$ 2000 of assets to set custom banners */} - {/* */} - -
- {( [ ['bio', 'Bio'], From e17a59ae233fa3b14d4b3396bc6483f4110452ed Mon Sep 17 00:00:00 2001 From: ingawei <46611122+ingawei@users.noreply.github.com> Date: Mon, 26 Sep 2022 19:28:54 -0500 Subject: [PATCH 065/199] Inga/mobilebetting (#911) * mobile binary betting --- web/components/amount-input.tsx | 129 +++++---- web/components/answers/answer-bet-panel.tsx | 2 +- web/components/bet-button.tsx | 53 +++- web/components/bet-panel.tsx | 251 +++++++++++------- web/components/contract/contract-overview.tsx | 18 +- web/components/contract/quick-bet.tsx | 4 +- web/components/outcome-label.tsx | 2 +- .../warning-confirmation-button.tsx | 16 +- web/components/yes-no-selector.tsx | 14 +- web/tailwind.config.js | 1 + 10 files changed, 305 insertions(+), 185 deletions(-) diff --git a/web/components/amount-input.tsx b/web/components/amount-input.tsx index 76581d9e..fbb49677 100644 --- a/web/components/amount-input.tsx +++ b/web/components/amount-input.tsx @@ -5,7 +5,8 @@ import { formatMoney } from 'common/util/format' import { Col } from './layout/col' import { SiteLink } from './site-link' import { ENV_CONFIG } from 'common/envs/constants' -import { useIsMobile } from 'web/hooks/use-is-mobile' +import { useWindowSize } from 'web/hooks/use-window-size' +import { Row } from './layout/row' export function AmountInput(props: { amount: number | undefined @@ -34,45 +35,53 @@ export function AmountInput(props: { const isInvalid = !str || isNaN(amount) onChange(isInvalid ? undefined : amount) } - const isMobile = useIsMobile(768) - return ( - - - {error && ( -
- {error === 'Insufficient balance' ? ( - <> - Not enough funds. - - Buy more? - - - ) : ( - error - )} -
- )} - + const { width } = useWindowSize() + const isMobile = (width ?? 0) < 768 + + return ( + <> + + + + {error && ( +
+ {error === 'Insufficient balance' ? ( + <> + Not enough funds. + + Buy more? + + + ) : ( + error + )} +
+ )} + + ) } @@ -135,27 +144,29 @@ export function BuyAmountInput(props: { return ( <> - - {showSlider && ( - onAmountChange(parseRaw(parseInt(e.target.value)))} - className="range range-lg only-thumb z-40 mb-2 xl:hidden" - step="5" + + - )} + {showSlider && ( + onAmountChange(parseRaw(parseInt(e.target.value)))} + className="range range-lg only-thumb z-40 my-auto align-middle xl:hidden" + step="5" + /> + )} + ) } diff --git a/web/components/answers/answer-bet-panel.tsx b/web/components/answers/answer-bet-panel.tsx index 3339ded5..ac3a8fa3 100644 --- a/web/components/answers/answer-bet-panel.tsx +++ b/web/components/answers/answer-bet-panel.tsx @@ -182,9 +182,9 @@ export function AnswerBetPanel(props: { - {user ? ( setOpen(false)} hasShares={hasYesShares || hasNoShares} /> @@ -72,3 +78,44 @@ export default function BetButton(props: { ) } + +export function BinaryMobileBetting(props: { contract: BinaryContract }) { + const { contract } = props + const user = useUser() + if (user) { + return + } else { + return + } +} + +export function SignedInBinaryMobileBetting(props: { + contract: BinaryContract + user: User +}) { + const { contract, user } = props + const unfilledBets = useUnfilledBets(contract.id) ?? [] + + return ( + <> + + +