From 54c227cf6c57a68bf0f0f1afeb2e1bdae42fdf36 Mon Sep 17 00:00:00 2001
From: James Grugett <jahooma@gmail.com>
Date: Thu, 8 Sep 2022 01:36:34 -0500
Subject: [PATCH] Updates to experimental home (#858)

* Line clamp question in prob change table

* Tweaks

* Expand option for daily movers

* Snap scrolling for carousel

* Add arrows to section headers

* Remove carousel from experimental/home

* React querify fetching your groups

* Edit home is its own page

* Add daily profit and balance

* Merge branch 'main' into new-home

* Make experimental search by your followed groups/creators

* Just submit, allow xs on pills

* Weigh in

* Use next/future/image component to optimize avatar images

* Inga/challenge icon (#857)

* changed challenge icon to custom icon
* fixed tip button alignment

* weighing in and trading "weigh in" for "trade"

Co-authored-by: Ian Philips <iansphilips@gmail.com>
Co-authored-by: Austin Chen <akrolsmir@gmail.com>
Co-authored-by: ingawei <46611122+ingawei@users.noreply.github.com>
Co-authored-by: mantikoros <sgrugett@gmail.com>
---
 common/calculate-metrics.ts                   |   6 +-
 web/components/arrange-home.tsx               |   3 +-
 web/components/carousel.tsx                   |   2 +-
 web/components/contract-search.tsx            |  32 ++-
 web/components/contract/prob-change-table.tsx |  86 ++++---
 web/components/double-carousel.tsx            |   3 +-
 web/hooks/use-group.ts                        |  13 +-
 web/lib/firebase/groups.ts                    |  11 +-
 web/pages/experimental/home/edit.tsx          |  60 +++++
 web/pages/experimental/home/index.tsx         | 239 ++++++++----------
 web/pages/tournaments/index.tsx               |   2 +-
 11 files changed, 265 insertions(+), 192 deletions(-)
 create mode 100644 web/pages/experimental/home/edit.tsx

diff --git a/common/calculate-metrics.ts b/common/calculate-metrics.ts
index 3aad1a9c..b27ac977 100644
--- a/common/calculate-metrics.ts
+++ b/common/calculate-metrics.ts
@@ -116,12 +116,12 @@ const calculateProfitForPeriod = (
     return currentProfit
   }
 
-  const startingProfit = calculateTotalProfit(startingPortfolio)
+  const startingProfit = calculatePortfolioProfit(startingPortfolio)
 
   return currentProfit - startingProfit
 }
 
-const calculateTotalProfit = (portfolio: PortfolioMetrics) => {
+export const calculatePortfolioProfit = (portfolio: PortfolioMetrics) => {
   return portfolio.investmentValue + portfolio.balance - portfolio.totalDeposits
 }
 
@@ -129,7 +129,7 @@ export const calculateNewProfit = (
   portfolioHistory: PortfolioMetrics[],
   newPortfolio: PortfolioMetrics
 ) => {
-  const allTimeProfit = calculateTotalProfit(newPortfolio)
+  const allTimeProfit = calculatePortfolioProfit(newPortfolio)
   const descendingPortfolio = sortBy(
     portfolioHistory,
     (p) => p.timestamp
diff --git a/web/components/arrange-home.tsx b/web/components/arrange-home.tsx
index 2f49d144..ae02e3ea 100644
--- a/web/components/arrange-home.tsx
+++ b/web/components/arrange-home.tsx
@@ -12,7 +12,7 @@ import { User } from 'common/user'
 import { Group } from 'common/group'
 
 export function ArrangeHome(props: {
-  user: User | null
+  user: User | null | undefined
   homeSections: { visible: string[]; hidden: string[] }
   setHomeSections: (homeSections: {
     visible: string[]
@@ -30,7 +30,6 @@ export function ArrangeHome(props: {
   return (
     <DragDropContext
       onDragEnd={(e) => {
-        console.log('drag end', e)
         const { destination, source, draggableId } = e
         if (!destination) return
 
diff --git a/web/components/carousel.tsx b/web/components/carousel.tsx
index 79baa451..030c256c 100644
--- a/web/components/carousel.tsx
+++ b/web/components/carousel.tsx
@@ -38,7 +38,7 @@ export function Carousel(props: {
   return (
     <div className={clsx('relative', className)}>
       <Row
-        className="scrollbar-hide w-full gap-4 overflow-x-auto scroll-smooth"
+        className="scrollbar-hide w-full snap-x gap-4 overflow-x-auto scroll-smooth"
         ref={ref}
         onScroll={onScroll}
       >
diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx
index 8e3b18e0..e4b7f9cf 100644
--- a/web/components/contract-search.tsx
+++ b/web/components/contract-search.tsx
@@ -69,6 +69,7 @@ type AdditionalFilter = {
   excludeContractIds?: string[]
   groupSlug?: string
   yourBets?: boolean
+  followed?: boolean
 }
 
 export function ContractSearch(props: {
@@ -88,6 +89,7 @@ export function ContractSearch(props: {
   useQueryUrlParam?: boolean
   isWholePage?: boolean
   noControls?: boolean
+  maxResults?: number
   renderContracts?: (
     contracts: Contract[] | undefined,
     loadMore: () => void
@@ -107,6 +109,7 @@ export function ContractSearch(props: {
     useQueryUrlParam,
     isWholePage,
     noControls,
+    maxResults,
     renderContracts,
   } = props
 
@@ -189,7 +192,8 @@ export function ContractSearch(props: {
   const contracts = state.pages
     .flat()
     .filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
-  const renderedContracts = state.pages.length === 0 ? undefined : contracts
+  const renderedContracts =
+    state.pages.length === 0 ? undefined : contracts.slice(0, maxResults)
 
   if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
     return <ContractSearchFirestore additionalFilter={additionalFilter} />
@@ -292,6 +296,19 @@ function ContractSearchControls(props: {
   const pillGroups: { name: string; slug: string }[] =
     memberPillGroups.length > 0 ? memberPillGroups : DEFAULT_CATEGORY_GROUPS
 
+  const personalFilters = user
+    ? [
+        // Show contracts in groups that the user is a member of.
+        memberGroupSlugs
+          .map((slug) => `groupLinks.slug:${slug}`)
+          // Or, show contracts created by users the user follows
+          .concat(follows?.map((followId) => `creatorId:${followId}`) ?? []),
+
+        // Subtract contracts you bet on, to show new ones.
+        `uniqueBettorIds:-${user.id}`,
+      ]
+    : []
+
   const additionalFilters = [
     additionalFilter?.creatorId
       ? `creatorId:${additionalFilter.creatorId}`
@@ -304,6 +321,7 @@ function ContractSearchControls(props: {
       ? // Show contracts bet on by the user
         `uniqueBettorIds:${user.id}`
       : '',
+    ...(additionalFilter?.followed ? personalFilters : []),
   ]
   const facetFilters = query
     ? additionalFilters
@@ -320,17 +338,7 @@ function ContractSearchControls(props: {
         state.pillFilter !== 'your-bets'
           ? `groupLinks.slug:${state.pillFilter}`
           : '',
-        state.pillFilter === 'personal'
-          ? // Show contracts in groups that the user is a member of
-            memberGroupSlugs
-              .map((slug) => `groupLinks.slug:${slug}`)
-              // Show contracts created by users the user follows
-              .concat(follows?.map((followId) => `creatorId:${followId}`) ?? [])
-          : '',
-        // Subtract contracts you bet on from For you.
-        state.pillFilter === 'personal' && user
-          ? `uniqueBettorIds:-${user.id}`
-          : '',
+        ...(state.pillFilter === 'personal' ? personalFilters : []),
         state.pillFilter === 'your-bets' && user
           ? // Show contracts bet on by the user
             `uniqueBettorIds:${user.id}`
diff --git a/web/components/contract/prob-change-table.tsx b/web/components/contract/prob-change-table.tsx
index f6e5d892..f973d260 100644
--- a/web/components/contract/prob-change-table.tsx
+++ b/web/components/contract/prob-change-table.tsx
@@ -3,52 +3,74 @@ import { contractPath } from 'web/lib/firebase/contracts'
 import { CPMMContract } from 'common/contract'
 import { formatPercent } from 'common/util/format'
 import { useProbChanges } from 'web/hooks/use-prob-changes'
-import { SiteLink } from '../site-link'
+import { linkClass, SiteLink } from '../site-link'
 import { Col } from '../layout/col'
 import { Row } from '../layout/row'
+import { useState } from 'react'
 
 export function ProbChangeTable(props: { userId: string | undefined }) {
   const { userId } = props
 
   const changes = useProbChanges(userId ?? '')
+  const [expanded, setExpanded] = useState(false)
 
   if (!changes) {
     return null
   }
 
-  const { positiveChanges, negativeChanges } = changes
+  const count = expanded ? 16 : 4
 
-  const count = 3
+  const { positiveChanges, negativeChanges } = changes
+  const filteredPositiveChanges = positiveChanges.slice(0, count / 2)
+  const filteredNegativeChanges = negativeChanges.slice(0, count / 2)
+  const filteredChanges = [
+    ...filteredPositiveChanges,
+    ...filteredNegativeChanges,
+  ]
 
   return (
-    <Row className="w-full flex-wrap divide-x-2 rounded bg-white shadow-md">
-      <Col className="min-w-[300px] flex-1 divide-y">
-        {positiveChanges.slice(0, count).map((contract) => (
-          <Row className="hover:bg-gray-100">
-            <ProbChange className="p-4 text-right" contract={contract} />
-            <SiteLink
-              className="p-4 font-semibold text-indigo-700"
-              href={contractPath(contract)}
-            >
-              {contract.question}
-            </SiteLink>
-          </Row>
-        ))}
+    <Col>
+      <Col className="mb-4 w-full divide-x-2 divide-y rounded-lg bg-white shadow-md md:flex-row md:divide-y-0">
+        <Col className="flex-1 divide-y">
+          {filteredChanges.slice(0, count / 2).map((contract) => (
+            <Row className="items-center hover:bg-gray-100">
+              <ProbChange
+                className="p-4 text-right text-xl"
+                contract={contract}
+              />
+              <SiteLink
+                className="p-4 pl-2 font-semibold text-indigo-700"
+                href={contractPath(contract)}
+              >
+                <span className="line-clamp-2">{contract.question}</span>
+              </SiteLink>
+            </Row>
+          ))}
+        </Col>
+        <Col className="flex-1 divide-y">
+          {filteredChanges.slice(count / 2).map((contract) => (
+            <Row className="items-center hover:bg-gray-100">
+              <ProbChange
+                className="p-4 text-right text-xl"
+                contract={contract}
+              />
+              <SiteLink
+                className="p-4 pl-2 font-semibold text-indigo-700"
+                href={contractPath(contract)}
+              >
+                <span className="line-clamp-2">{contract.question}</span>
+              </SiteLink>
+            </Row>
+          ))}
+        </Col>
       </Col>
-      <Col className="justify-content-stretch min-w-[300px] flex-1 divide-y">
-        {negativeChanges.slice(0, count).map((contract) => (
-          <Row className="hover:bg-gray-100">
-            <ProbChange className="p-4 text-right" contract={contract} />
-            <SiteLink
-              className="p-4 font-semibold text-indigo-700"
-              href={contractPath(contract)}
-            >
-              {contract.question}
-            </SiteLink>
-          </Row>
-        ))}
-      </Col>
-    </Row>
+      <div
+        className={clsx(linkClass, 'cursor-pointer self-end')}
+        onClick={() => setExpanded(!expanded)}
+      >
+        {expanded ? 'Show less' : 'Show more'}
+      </div>
+    </Col>
   )
 }
 
@@ -63,9 +85,9 @@ export function ProbChange(props: {
 
   const color =
     change > 0
-      ? 'text-green-600'
+      ? 'text-green-500'
       : change < 0
-      ? 'text-red-600'
+      ? 'text-red-500'
       : 'text-gray-600'
 
   const str =
diff --git a/web/components/double-carousel.tsx b/web/components/double-carousel.tsx
index da01eb5a..12538cf7 100644
--- a/web/components/double-carousel.tsx
+++ b/web/components/double-carousel.tsx
@@ -7,7 +7,6 @@ import { Col } from 'web/components/layout/col'
 
 export function DoubleCarousel(props: {
   contracts: Contract[]
-  seeMoreUrl?: string
   showTime?: ShowTime
   loadMore?: () => void
 }) {
@@ -19,7 +18,7 @@ export function DoubleCarousel(props: {
         ? range(0, Math.floor(contracts.length / 2)).map((col) => {
             const i = col * 2
             return (
-              <Col key={contracts[i].id}>
+              <Col className="snap-start scroll-m-4" key={contracts[i].id}>
                 <ContractCard
                   contract={contracts[i]}
                   className="mb-2 w-96 shrink-0"
diff --git a/web/hooks/use-group.ts b/web/hooks/use-group.ts
index 001c29c3..781da9cb 100644
--- a/web/hooks/use-group.ts
+++ b/web/hooks/use-group.ts
@@ -2,13 +2,13 @@ import { useEffect, useState } from 'react'
 import { Group } from 'common/group'
 import { User } from 'common/user'
 import {
+  getMemberGroups,
   GroupMemberDoc,
   groupMembers,
   listenForGroup,
   listenForGroupContractDocs,
   listenForGroups,
   listenForMemberGroupIds,
-  listenForMemberGroups,
   listenForOpenGroups,
   listGroups,
 } from 'web/lib/firebase/groups'
@@ -17,6 +17,7 @@ import { filterDefined } from 'common/util/array'
 import { Contract } from 'common/contract'
 import { uniq } from 'lodash'
 import { listenForValues } from 'web/lib/firebase/utils'
+import { useQuery } from 'react-query'
 
 export const useGroup = (groupId: string | undefined) => {
   const [group, setGroup] = useState<Group | null | undefined>()
@@ -49,12 +50,10 @@ export const useOpenGroups = () => {
 }
 
 export const useMemberGroups = (userId: string | null | undefined) => {
-  const [memberGroups, setMemberGroups] = useState<Group[] | undefined>()
-  useEffect(() => {
-    if (userId)
-      return listenForMemberGroups(userId, (groups) => setMemberGroups(groups))
-  }, [userId])
-  return memberGroups
+  const result = useQuery(['member-groups', userId ?? ''], () =>
+    getMemberGroups(userId ?? '')
+  )
+  return result.data
 }
 
 // Note: We cache member group ids in localstorage to speed up the initial load
diff --git a/web/lib/firebase/groups.ts b/web/lib/firebase/groups.ts
index 0366fe0b..7a372d9a 100644
--- a/web/lib/firebase/groups.ts
+++ b/web/lib/firebase/groups.ts
@@ -32,7 +32,7 @@ export const groupMembers = (groupId: string) =>
 export const groupContracts = (groupId: string) =>
   collection(groups, groupId, 'groupContracts')
 const openGroupsQuery = query(groups, where('anyoneCanJoin', '==', true))
-const memberGroupsQuery = (userId: string) =>
+export const memberGroupsQuery = (userId: string) =>
   query(collectionGroup(db, 'groupMembers'), where('userId', '==', userId))
 
 export function groupPath(
@@ -113,6 +113,15 @@ export function listenForGroup(
   return listenForValue(doc(groups, groupId), setGroup)
 }
 
+export async function getMemberGroups(userId: string) {
+  const snapshot = await getDocs(memberGroupsQuery(userId))
+  const groupIds = filterDefined(
+    snapshot.docs.map((doc) => doc.ref.parent.parent?.id)
+  )
+  const groups = await Promise.all(groupIds.map(getGroup))
+  return filterDefined(groups)
+}
+
 export function listenForMemberGroupIds(
   userId: string,
   setGroupIds: (groupIds: string[]) => void
diff --git a/web/pages/experimental/home/edit.tsx b/web/pages/experimental/home/edit.tsx
new file mode 100644
index 00000000..2cba3f19
--- /dev/null
+++ b/web/pages/experimental/home/edit.tsx
@@ -0,0 +1,60 @@
+import clsx from 'clsx'
+import { useState } from 'react'
+import { ArrangeHome } from 'web/components/arrange-home'
+import { Button } from 'web/components/button'
+import { Col } from 'web/components/layout/col'
+import { Row } from 'web/components/layout/row'
+import { Page } from 'web/components/page'
+import { SiteLink } from 'web/components/site-link'
+import { Title } from 'web/components/title'
+import { useTracking } from 'web/hooks/use-tracking'
+import { useUser } from 'web/hooks/use-user'
+import { updateUser } from 'web/lib/firebase/users'
+
+export default function Home() {
+  const user = useUser()
+
+  useTracking('edit home')
+
+  const [homeSections, setHomeSections] = useState(
+    user?.homeSections ?? { visible: [], hidden: [] }
+  )
+
+  const updateHomeSections = (newHomeSections: {
+    visible: string[]
+    hidden: string[]
+  }) => {
+    if (!user) return
+    updateUser(user.id, { homeSections: newHomeSections })
+    setHomeSections(newHomeSections)
+  }
+
+  return (
+    <Page>
+      <Col className="pm:mx-10 gap-4 px-4 pb-12">
+        <Row className={'w-full items-center justify-between'}>
+          <Title text="Edit your home page" />
+          <DoneButton />
+        </Row>
+
+        <ArrangeHome
+          user={user}
+          homeSections={homeSections}
+          setHomeSections={updateHomeSections}
+        />
+      </Col>
+    </Page>
+  )
+}
+
+function DoneButton(props: { className?: string }) {
+  const { className } = props
+
+  return (
+    <SiteLink href="/experimental/home">
+      <Button size="lg" color="blue" className={clsx(className, 'flex')}>
+        Done
+      </Button>
+    </SiteLink>
+  )
+}
diff --git a/web/pages/experimental/home/index.tsx b/web/pages/experimental/home/index.tsx
index fb0b488d..90b4f888 100644
--- a/web/pages/experimental/home/index.tsx
+++ b/web/pages/experimental/home/index.tsx
@@ -1,40 +1,36 @@
 import React, { useState } from 'react'
 import Router from 'next/router'
-import { PencilIcon, PlusSmIcon } from '@heroicons/react/solid'
+import {
+  PencilIcon,
+  PlusSmIcon,
+  ArrowSmRightIcon,
+} from '@heroicons/react/solid'
+import clsx from 'clsx'
 
 import { Page } from 'web/components/page'
 import { Col } from 'web/components/layout/col'
 import { ContractSearch, SORTS } from 'web/components/contract-search'
 import { User } from 'common/user'
-import { getUserAndPrivateUser, updateUser } from 'web/lib/firebase/users'
 import { useTracking } from 'web/hooks/use-tracking'
 import { track } from 'web/lib/service/analytics'
-import { authenticateOnServer } from 'web/lib/firebase/server-auth'
 import { useSaveReferral } from 'web/hooks/use-save-referral'
-import { GetServerSideProps } from 'next'
 import { Sort } from 'web/components/contract-search'
 import { Group } from 'common/group'
-import { LoadingIndicator } from 'web/components/loading-indicator'
-import { GroupLinkItem } from '../../groups'
 import { SiteLink } from 'web/components/site-link'
 import { useUser } from 'web/hooks/use-user'
 import { useMemberGroups } from 'web/hooks/use-group'
-import { DoubleCarousel } from '../../../components/double-carousel'
-import clsx from 'clsx'
 import { Button } from 'web/components/button'
-import { ArrangeHome, getHomeItems } from '../../../components/arrange-home'
+import { getHomeItems } from '../../../components/arrange-home'
 import { Title } from 'web/components/title'
 import { Row } from 'web/components/layout/row'
 import { ProbChangeTable } from 'web/components/contract/prob-change-table'
+import { groupPath } from 'web/lib/firebase/groups'
+import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
+import { calculatePortfolioProfit } from 'common/calculate-metrics'
+import { formatMoney } from 'common/util/format'
 
-export const getServerSideProps: GetServerSideProps = async (ctx) => {
-  const creds = await authenticateOnServer(ctx)
-  const auth = creds ? await getUserAndPrivateUser(creds.uid) : null
-  return { props: { auth } }
-}
-
-const Home = (props: { auth: { user: User } | null }) => {
-  const user = useUser() ?? props.auth?.user ?? null
+const Home = () => {
+  const user = useUser()
 
   useTracking('view home')
 
@@ -42,76 +38,54 @@ const Home = (props: { auth: { user: User } | null }) => {
 
   const groups = useMemberGroups(user?.id) ?? []
 
-  const [homeSections, setHomeSections] = useState(
+  const [homeSections] = useState(
     user?.homeSections ?? { visible: [], hidden: [] }
   )
   const { visibleItems } = getHomeItems(groups, homeSections)
 
-  const updateHomeSections = (newHomeSections: {
-    visible: string[]
-    hidden: string[]
-  }) => {
-    if (!user) return
-    updateUser(user.id, { homeSections: newHomeSections })
-    setHomeSections(newHomeSections)
-  }
-
-  const [isEditing, setIsEditing] = useState(false)
-
   return (
     <Page>
-      <Col className="pm:mx-10 gap-4 px-4 pb-12 xl:w-[125%]">
+      <Col className="pm:mx-10 gap-4 px-4 pb-12">
         <Row className={'w-full items-center justify-between'}>
-          <Title text={isEditing ? 'Edit your home page' : 'Home'} />
+          <Title className="!mb-0" text="Home" />
 
-          <EditDoneButton isEditing={isEditing} setIsEditing={setIsEditing} />
+          <EditButton />
         </Row>
 
-        {isEditing ? (
-          <>
-            <ArrangeHome
-              user={user}
-              homeSections={homeSections}
-              setHomeSections={updateHomeSections}
-            />
-          </>
-        ) : (
-          <>
-            <div className="text-xl text-gray-800">Daily movers</div>
-            <ProbChangeTable userId={user?.id} />
+        <DailyProfitAndBalance userId={user?.id} />
 
-            {visibleItems.map((item) => {
-              const { id } = item
-              if (id === 'your-bets') {
-                return (
-                  <SearchSection
-                    key={id}
-                    label={'Your trades'}
-                    sort={'prob-change-day'}
-                    user={user}
-                    yourBets
-                  />
-                )
-              }
-              const sort = SORTS.find((sort) => sort.value === id)
-              if (sort)
-                return (
-                  <SearchSection
-                    key={id}
-                    label={sort.label}
-                    sort={sort.value}
-                    user={user}
-                  />
-                )
+        <div className="text-xl text-gray-800">Daily movers</div>
+        <ProbChangeTable userId={user?.id} />
 
-              const group = groups.find((g) => g.id === id)
-              if (group)
-                return <GroupSection key={id} group={group} user={user} />
+        {visibleItems.map((item) => {
+          const { id } = item
+          if (id === 'your-bets') {
+            return (
+              <SearchSection
+                key={id}
+                label={'Your trades'}
+                sort={'newest'}
+                user={user}
+                yourBets
+              />
+            )
+          }
+          const sort = SORTS.find((sort) => sort.value === id)
+          if (sort)
+            return (
+              <SearchSection
+                key={id}
+                label={sort.label}
+                sort={sort.value}
+                user={user}
+              />
+            )
 
-              return null
-            })}
-          </>
-        )}
+          const group = groups.find((g) => g.id === id)
+          if (group) return <GroupSection key={id} group={group} user={user} />
+
+          return null
+        })}
       </Col>
       <button
         type="button"
@@ -129,7 +103,7 @@ const Home = (props: { auth: { user: User } | null }) => {
 
 function SearchSection(props: {
   label: string
-  user: User | null
+  user: User | null | undefined
   sort: Sort
   yourBets?: boolean
 }) {
@@ -139,88 +113,91 @@ function SearchSection(props: {
   return (
     <Col>
       <SiteLink className="mb-2 text-xl" href={href}>
-        {label}
+        {label}{' '}
+        <ArrowSmRightIcon
+          className="mb-0.5 inline h-6 w-6 text-gray-500"
+          aria-hidden="true"
+        />
       </SiteLink>
       <ContractSearch
         user={user}
         defaultSort={sort}
-        additionalFilter={yourBets ? { yourBets: true } : undefined}
+        additionalFilter={yourBets ? { yourBets: true } : { followed: true }}
         noControls
-        // persistPrefix={`experimental-home-${sort}`}
-        renderContracts={(contracts, loadMore) =>
-          contracts ? (
-            <DoubleCarousel
-              contracts={contracts}
-              seeMoreUrl={href}
-              showTime={
-                sort === 'close-date' || sort === 'resolve-date'
-                  ? sort
-                  : undefined
-              }
-              loadMore={loadMore}
-            />
-          ) : (
-            <LoadingIndicator />
-          )
-        }
+        maxResults={6}
+        persistPrefix={`experimental-home-${sort}`}
       />
     </Col>
   )
 }
 
-function GroupSection(props: { group: Group; user: User | null }) {
+function GroupSection(props: { group: Group; user: User | null | undefined }) {
   const { group, user } = props
 
   return (
     <Col>
-      <GroupLinkItem className="mb-2 text-xl" group={group} />
+      <SiteLink className="mb-2 text-xl" href={groupPath(group.slug)}>
+        {group.name}{' '}
+        <ArrowSmRightIcon
+          className="mb-0.5 inline h-6 w-6 text-gray-500"
+          aria-hidden="true"
+        />
+      </SiteLink>
       <ContractSearch
         user={user}
         defaultSort={'score'}
         additionalFilter={{ groupSlug: group.slug }}
         noControls
-        // persistPrefix={`experimental-home-${group.slug}`}
-        renderContracts={(contracts, loadMore) =>
-          contracts ? (
-            contracts.length == 0 ? (
-              <div className="m-2 text-gray-500">No open markets</div>
-            ) : (
-              <DoubleCarousel
-                contracts={contracts}
-                seeMoreUrl={`/group/${group.slug}`}
-                loadMore={loadMore}
-              />
-            )
-          ) : (
-            <LoadingIndicator />
-          )
-        }
+        maxResults={6}
+        persistPrefix={`experimental-home-${group.slug}`}
       />
     </Col>
   )
 }
 
-function EditDoneButton(props: {
-  isEditing: boolean
-  setIsEditing: (isEditing: boolean) => void
-  className?: string
-}) {
-  const { isEditing, setIsEditing, className } = props
+function EditButton(props: { className?: string }) {
+  const { className } = props
 
   return (
-    <Button
-      size="lg"
-      color={isEditing ? 'blue' : 'gray-white'}
-      className={clsx(className, 'flex')}
-      onClick={() => {
-        setIsEditing(!isEditing)
-      }}
-    >
-      {!isEditing && (
-        <PencilIcon className={clsx('mr-2 h-[24px] w-5')} aria-hidden="true" />
-      )}
-      {isEditing ? 'Done' : 'Edit'}
-    </Button>
+    <SiteLink href="/experimental/home/edit">
+      <Button size="lg" color="gray-white" className={clsx(className, 'flex')}>
+        <PencilIcon className={clsx('mr-2 h-[24px] w-5')} aria-hidden="true" />{' '}
+        Edit
+      </Button>
+    </SiteLink>
+  )
+}
+
+function DailyProfitAndBalance(props: {
+  userId: string | null | undefined
+  className?: string
+}) {
+  const { userId, className } = props
+  const metrics = usePortfolioHistory(userId ?? '', 'daily') ?? []
+  const [first, last] = [metrics[0], metrics[metrics.length - 1]]
+
+  if (first === undefined || last === undefined) return null
+
+  const profit =
+    calculatePortfolioProfit(last) - calculatePortfolioProfit(first)
+
+  const balanceChange = last.balance - first.balance
+
+  return (
+    <div className={clsx(className, 'text-lg')}>
+      <span className={clsx(profit >= 0 ? 'text-green-500' : 'text-red-500')}>
+        {profit >= 0 ? '+' : '-'}
+        {formatMoney(profit)}
+      </span>{' '}
+      profit and{' '}
+      <span
+        className={clsx(balanceChange >= 0 ? 'text-green-500' : 'text-red-500')}
+      >
+        {balanceChange >= 0 ? '+' : '-'}
+        {formatMoney(balanceChange)}
+      </span>{' '}
+      balance today
+    </div>
   )
 }
 
diff --git a/web/pages/tournaments/index.tsx b/web/pages/tournaments/index.tsx
index 4b573e3f..b308ee7f 100644
--- a/web/pages/tournaments/index.tsx
+++ b/web/pages/tournaments/index.tsx
@@ -237,7 +237,7 @@ const MarketCarousel = (props: { slug: string }) => {
           key={m.id}
           contract={m}
           hideGroupLink
-          className="mb-2 max-h-[200px] w-96 shrink-0"
+          className="mb-2 max-h-[200px] w-96 shrink-0 snap-start scroll-m-4 md:snap-align-none"
           questionClass="line-clamp-3"
           trackingPostfix=" tournament"
         />