Allow adding multiple contracts to group in modal

This commit is contained in:
Ian Philips 2022-07-25 18:27:43 -07:00
parent ec0e25e5ed
commit af25a6c795
8 changed files with 171 additions and 77 deletions

View File

@ -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}
/>
)}
</InstantSearch>
@ -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}
/>
)
}

View File

@ -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}
/>
</Col>
{showQuickBet ? (

View File

@ -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: {
<NewContractBadge />
)}
{groupLinks && groupLinks.length > 0 && (
{!hideGroupLink && groupLinks && groupLinks.length > 0 && (
<SiteLink
href={groupPath(groupLinks[0].slug)}
className="text-sm text-gray-400"

View File

@ -1,5 +1,5 @@
import { Contract } from '../../lib/firebase/contracts'
import { User } from '../../lib/firebase/users'
import { Contract } from 'web/lib/firebase/contracts'
import { User } from 'web/lib/firebase/users'
import { Col } from '../layout/col'
import { SiteLink } from '../site-link'
import { ContractCard } from './contract-card'
@ -9,6 +9,11 @@ import { useIsVisible } from 'web/hooks/use-is-visible'
import { useEffect, useState } from 'react'
import clsx from 'clsx'
export type ContractHighlightOptions = {
contractIds?: string[]
highlightClassName?: string
}
export function ContractsGrid(props: {
contracts: Contract[]
loadMore: () => 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<HTMLElement | null>(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
}
/>
))}
</ul>

View File

@ -26,7 +26,7 @@ export function Modal(props: {
className="fixed inset-0 z-50 overflow-y-auto"
onClose={setOpen}
>
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"

View File

@ -129,7 +129,6 @@ export async function listContractsByGroupSlug(
): Promise<Contract[]> {
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())
}

View File

@ -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(

View File

@ -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<Contract[]>([])
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 }) {
</button>
</div>
<Modal open={open} setOpen={setOpen} className={'sm:p-0'}>
<Col
className={
'max-h-[60vh] min-h-[60vh] w-full gap-4 rounded-md bg-white'
}
>
<Modal open={open} setOpen={setOpen} className={'sm:p-0'} size={'lg'}>
<Col className={' w-full gap-4 rounded-md bg-white'}>
<Col className="p-8 pb-0">
<div className={'text-xl text-indigo-700'}>
Add a question to your group
</div>
<Col className="items-center">
<CreateQuestionButton
user={user}
overrideText={'New question'}
className={'w-48 flex-shrink-0 '}
query={`?groupId=${group.id}`}
/>
{contracts.length === 0 ? (
<Col className="items-center justify-center">
<CreateQuestionButton
user={user}
overrideText={'New question'}
className={'w-48 flex-shrink-0 '}
query={`?groupId=${group.id}`}
/>
<div className={'mt-2 text-lg text-indigo-700'}>or</div>
</Col>
<div className={'mt-1 text-lg text-gray-600'}>
(or select old questions)
</div>
</Col>
) : (
<Col className={'w-full '}>
{!loading ? (
<Row className={'justify-end gap-4'}>
<Button onClick={doneAddingContracts} color={'indigo'}>
Add {contracts.length} question
{contracts.length > 1 && 's'}
</Button>
<Button
onClick={() => {
setContracts([])
}}
color={'gray'}
>
Cancel
</Button>
</Row>
) : (
<Row className={'justify-center'}>
<LoadingIndicator />
</Row>
)}
</Col>
)}
</Col>
<div className={'overflow-y-scroll sm:px-8'}>
<ContractSearch
hideOrderSelector={true}
onContractClick={addContractToCurrentGroup}
overrideGridClassName={'flex grid-cols-1 flex-col gap-3 p-1'}
overrideGridClassName={
'flex grid grid-cols-1 sm:grid-cols-2 flex-col gap-3 p-1'
}
showPlaceHolder={true}
hideQuickBet={true}
cardHideOptions={{ hideGroupLink: true, hideQuickBet: true }}
additionalFilter={{ excludeContractIds: group.contractIds }}
highlightOptions={{
contractIds: contracts.map((c) => c.id),
highlightClassName: '!bg-indigo-100 border-indigo-100 border-2',
}}
/>
</div>
</Col>