Allow adding multiple contracts to group in modal
This commit is contained in:
parent
ec0e25e5ed
commit
af25a6c795
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user