From af25a6c7951f8046ae2bf9e5fc9c3d1fc2cd99b0 Mon Sep 17 00:00:00 2001 From: Ian Philips Date: Mon, 25 Jul 2022 18:27:43 -0700 Subject: [PATCH] Allow adding multiple contracts to group in modal --- web/components/contract-search.tsx | 28 +++++-- web/components/contract/contract-card.tsx | 15 +++- web/components/contract/contract-details.tsx | 5 +- web/components/contract/contracts-list.tsx | 26 +++++- web/components/layout/modal.tsx | 2 +- web/lib/firebase/contracts.ts | 1 - web/lib/firebase/groups.ts | 87 +++++++++++--------- web/pages/group/[...slugs]/index.tsx | 84 ++++++++++++++----- 8 files changed, 171 insertions(+), 77 deletions(-) diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx index 45145c54..c7660138 100644 --- a/web/components/contract-search.tsx +++ b/web/components/contract-search.tsx @@ -15,7 +15,10 @@ import { useInitialQueryAndSort, useUpdateQueryAndSort, } from '../hooks/use-sort-and-query-params' -import { ContractsGrid } from './contract/contracts-list' +import { + ContractHighlightOptions, + ContractsGrid, +} from './contract/contracts-list' import { Row } from './layout/row' import { useEffect, useMemo, useRef, useState } from 'react' import { Spacer } from './layout/spacer' @@ -64,11 +67,15 @@ export function ContractSearch(props: { excludeContractIds?: string[] groupSlug?: string } + highlightOptions?: ContractHighlightOptions onContractClick?: (contract: Contract) => void showPlaceHolder?: boolean hideOrderSelector?: boolean overrideGridClassName?: string - hideQuickBet?: boolean + cardHideOptions?: { + hideGroupLink?: boolean + hideQuickBet?: boolean + } }) { const { querySortOptions, @@ -77,7 +84,8 @@ export function ContractSearch(props: { overrideGridClassName, hideOrderSelector, showPlaceHolder, - hideQuickBet, + cardHideOptions, + highlightOptions, } = props const user = useUser() @@ -276,8 +284,9 @@ export function ContractSearch(props: { querySortOptions={querySortOptions} onContractClick={onContractClick} overrideGridClassName={overrideGridClassName} - hideQuickBet={hideQuickBet} excludeContractIds={additionalFilter?.excludeContractIds} + highlightOptions={highlightOptions} + cardHideOptions={cardHideOptions} /> )} @@ -293,13 +302,19 @@ export function ContractSearchInner(props: { overrideGridClassName?: string hideQuickBet?: boolean excludeContractIds?: string[] + highlightOptions?: ContractHighlightOptions + cardHideOptions?: { + hideQuickBet?: boolean + hideGroupLink?: boolean + } }) { const { querySortOptions, onContractClick, overrideGridClassName, - hideQuickBet, + cardHideOptions, excludeContractIds, + highlightOptions, } = props const { initialQuery } = useInitialQueryAndSort(querySortOptions) @@ -360,7 +375,8 @@ export function ContractSearchInner(props: { showTime={showTime} onContractClick={onContractClick} overrideGridClassName={overrideGridClassName} - hideQuickBet={hideQuickBet} + highlightOptions={highlightOptions} + cardHideOptions={cardHideOptions} /> ) } diff --git a/web/components/contract/contract-card.tsx b/web/components/contract/contract-card.tsx index 30c54363..f3f9807c 100644 --- a/web/components/contract/contract-card.tsx +++ b/web/components/contract/contract-card.tsx @@ -5,8 +5,8 @@ import { formatLargeNumber, formatPercent } from 'common/util/format' import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts' import { Col } from '../layout/col' import { - Contract, BinaryContract, + Contract, FreeResponseContract, NumericContract, PseudoNumericContract, @@ -24,7 +24,7 @@ import { } from 'common/calculate' import { AvatarDetails, MiscDetails, ShowTime } from './contract-details' import { getExpectedValue, getValueFromBucket } from 'common/calculate-dpm' -import { QuickBet, ProbBar, getColor } from './quick-bet' +import { getColor, ProbBar, QuickBet } from './quick-bet' import { useContractWithPreload } from 'web/hooks/use-contract' import { useUser } from 'web/hooks/use-user' import { track } from '@amplitude/analytics-browser' @@ -38,8 +38,16 @@ export function ContractCard(props: { className?: string onClick?: () => void hideQuickBet?: boolean + hideGroupLink?: boolean }) { - const { showHotVolume, showTime, className, onClick, hideQuickBet } = props + const { + showHotVolume, + showTime, + className, + onClick, + hideQuickBet, + hideGroupLink, + } = props const contract = useContractWithPreload(props.contract) ?? props.contract const { question, outcomeType } = contract const { resolution } = contract @@ -121,6 +129,7 @@ export function ContractCard(props: { contract={contract} showHotVolume={showHotVolume} showTime={showTime} + hideGroupLink={hideGroupLink} /> {showQuickBet ? ( diff --git a/web/components/contract/contract-details.tsx b/web/components/contract/contract-details.tsx index 83c291c7..7a7242a0 100644 --- a/web/components/contract/contract-details.tsx +++ b/web/components/contract/contract-details.tsx @@ -42,8 +42,9 @@ export function MiscDetails(props: { contract: Contract showHotVolume?: boolean showTime?: ShowTime + hideGroupLink?: boolean }) { - const { contract, showHotVolume, showTime } = props + const { contract, showHotVolume, showTime, hideGroupLink } = props const { volume, volume24Hours, @@ -80,7 +81,7 @@ export function MiscDetails(props: { )} - {groupLinks && groupLinks.length > 0 && ( + {!hideGroupLink && groupLinks && groupLinks.length > 0 && ( void @@ -16,7 +21,11 @@ export function ContractsGrid(props: { showTime?: ShowTime onContractClick?: (contract: Contract) => void overrideGridClassName?: string - hideQuickBet?: boolean + cardHideOptions?: { + hideQuickBet?: boolean + hideGroupLink?: boolean + } + highlightOptions?: ContractHighlightOptions }) { const { contracts, @@ -25,9 +34,12 @@ export function ContractsGrid(props: { loadMore, onContractClick, overrideGridClassName, - hideQuickBet, + cardHideOptions, + highlightOptions, } = props + const { hideQuickBet, hideGroupLink } = cardHideOptions || {} + const { contractIds, highlightClassName } = highlightOptions || {} const [elem, setElem] = useState(null) const isBottomVisible = useIsVisible(elem) @@ -66,6 +78,12 @@ export function ContractsGrid(props: { onContractClick ? () => onContractClick(contract) : undefined } hideQuickBet={hideQuickBet} + hideGroupLink={hideGroupLink} + className={ + contractIds?.includes(contract.id) + ? highlightClassName + : undefined + } /> ))} diff --git a/web/components/layout/modal.tsx b/web/components/layout/modal.tsx index af2b66de..aac9e4c8 100644 --- a/web/components/layout/modal.tsx +++ b/web/components/layout/modal.tsx @@ -26,7 +26,7 @@ export function Modal(props: { className="fixed inset-0 z-50 overflow-y-auto" onClose={setOpen} > -
+
{ const q = query(contracts, where('groupSlugs', 'array-contains', slug)) const snapshot = await getDocs(q) - console.log(snapshot.docs.map((doc) => doc.data())) return snapshot.docs.map((doc) => doc.data()) } diff --git a/web/lib/firebase/groups.ts b/web/lib/firebase/groups.ts index f782f6a8..debc9a97 100644 --- a/web/lib/firebase/groups.ts +++ b/web/lib/firebase/groups.ts @@ -129,56 +129,61 @@ export async function addContractToGroup( contract: Contract, userId: string ) { - if (contract.groupLinks?.map((l) => l.groupId).includes(group.id)) return // already in that group + if (!contract.groupLinks?.map((l) => l.groupId).includes(group.id)) { + const newGroupLinks = [ + ...(contract.groupLinks ?? []), + { + groupId: group.id, + createdTime: Date.now(), + slug: group.slug, + userId, + name: group.name, + } as GroupLink, + ] - const newGroupLinks = [ - ...(contract.groupLinks ?? []), - { - groupId: group.id, - createdTime: Date.now(), - slug: group.slug, - userId, - name: group.name, - } as GroupLink, - ] - - await updateContract(contract.id, { - groupSlugs: uniq([...(contract.groupSlugs ?? []), group.slug]), - groupLinks: newGroupLinks, - }) - return await updateGroup(group, { - contractIds: uniq([...group.contractIds, contract.id]), - }) - .then(() => group) - .catch((err) => { - console.error('error adding contract to group', err) - return err + await updateContract(contract.id, { + groupSlugs: uniq([...(contract.groupSlugs ?? []), group.slug]), + groupLinks: newGroupLinks, }) + } + if (!group.contractIds.includes(contract.id)) { + return await updateGroup(group, { + contractIds: uniq([...group.contractIds, contract.id]), + }) + .then(() => group) + .catch((err) => { + console.error('error adding contract to group', err) + return err + }) + } } export async function removeContractFromGroup( group: Group, contract: Contract ) { - if (!contract.groupLinks?.map((l) => l.groupId).includes(group.id)) return // not in that group - - const newGroupLinks = contract.groupLinks?.filter( - (link) => link.slug !== group.slug - ) - await updateContract(contract.id, { - groupSlugs: - contract.groupSlugs?.filter((slug) => slug !== group.slug) ?? [], - groupLinks: newGroupLinks ?? [], - }) - const newContractIds = group.contractIds.filter((id) => id !== contract.id) - return await updateGroup(group, { - contractIds: uniq(newContractIds), - }) - .then(() => group) - .catch((err) => { - console.error('error removing contract from group', err) - return err + if (contract.groupLinks?.map((l) => l.groupId).includes(group.id)) { + const newGroupLinks = contract.groupLinks?.filter( + (link) => link.slug !== group.slug + ) + await updateContract(contract.id, { + groupSlugs: + contract.groupSlugs?.filter((slug) => slug !== group.slug) ?? [], + groupLinks: newGroupLinks ?? [], }) + } + + if (group.contractIds.includes(contract.id)) { + const newContractIds = group.contractIds.filter((id) => id !== contract.id) + return await updateGroup(group, { + contractIds: uniq(newContractIds), + }) + .then(() => group) + .catch((err) => { + console.error('error removing contract from group', err) + return err + }) + } } export async function setContractGroupLinks( diff --git a/web/pages/group/[...slugs]/index.tsx b/web/pages/group/[...slugs]/index.tsx index eebf0619..dd712a36 100644 --- a/web/pages/group/[...slugs]/index.tsx +++ b/web/pages/group/[...slugs]/index.tsx @@ -49,6 +49,7 @@ import { useWindowSize } from 'web/hooks/use-window-size' import { CopyLinkButton } from 'web/components/copy-link-button' import { ENV_CONFIG } from 'common/envs/constants' import { useSaveReferral } from 'web/hooks/use-save-referral' +import { Button } from 'web/components/button' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { params: { slugs: string[] } }) { @@ -541,10 +542,26 @@ function GroupLeaderboards(props: { function AddContractButton(props: { group: Group; user: User }) { const { group, user } = props const [open, setOpen] = useState(false) + const [contracts, setContracts] = useState([]) + const [loading, setLoading] = useState(false) async function addContractToCurrentGroup(contract: Contract) { - await addContractToGroup(group, contract, user.id) - setOpen(false) + if (contracts.map((c) => c.id).includes(contract.id)) { + setContracts(contracts.filter((c) => c.id !== contract.id)) + } else setContracts([...contracts, contract]) + } + + async function doneAddingContracts() { + Promise.all( + contracts.map(async (contract) => { + setLoading(true) + await addContractToGroup(group, contract, user.id) + }) + ).then(() => { + setLoading(false) + setOpen(false) + setContracts([]) + }) } return ( @@ -558,37 +575,66 @@ function AddContractButton(props: { group: Group; user: User }) {
- - + +
Add a question to your group
- - + {contracts.length === 0 ? ( + + -
or
- +
+ (or select old questions) +
+ + ) : ( + + {!loading ? ( + + + + + ) : ( + + + + )} + + )}
c.id), + highlightClassName: '!bg-indigo-100 border-indigo-100 border-2', + }} />