From 05b0ca5cdb9538ad47b2ffb7da0ea67ad460698c Mon Sep 17 00:00:00 2001 From: Ian Philips Date: Thu, 28 Jul 2022 11:16:48 -0700 Subject: [PATCH 01/44] I want to see others' referrals --- web/components/user-page.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/components/user-page.tsx b/web/components/user-page.tsx index 035536b5..d628e92d 100644 --- a/web/components/user-page.tsx +++ b/web/components/user-page.tsx @@ -214,6 +214,10 @@ export function UserPage(props: { user: User; currentUser?: User }) { + {currentUser && + ['ian', 'Austin', 'SG', 'JamesGrugett'].includes( + currentUser.username + ) && } From aa6d0d175002134b69094389f9e7c91280caed27 Mon Sep 17 00:00:00 2001 From: marsteralex Date: Thu, 28 Jul 2022 11:31:58 -0700 Subject: [PATCH 02/44] add beasts (#700) * fix https * add beasts * Remove extra file * Prettier-ify code * Prettier-ify Co-authored-by: Austin Chen --- web/public/mtg/app.js | 3 +++ web/public/mtg/index.html | 10 ++++++++++ web/public/mtg/jsons/beast1.json | 1 + web/public/mtg/jsons/beast2.json | 1 + web/public/mtg/jsons/beast3.json | 1 + web/public/mtg/jsons/counterspell3.json | 2 +- 6 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 web/public/mtg/jsons/beast1.json create mode 100644 web/public/mtg/jsons/beast2.json create mode 100644 web/public/mtg/jsons/beast3.json diff --git a/web/public/mtg/app.js b/web/public/mtg/app.js index fc7711d0..d4c98f70 100644 --- a/web/public/mtg/app.js +++ b/web/public/mtg/app.js @@ -59,6 +59,9 @@ function putIntoMapAndFetch(data) { document.getElementById('guess-type').innerText = 'Counterspell Guesser' } else if (whichGuesser === 'burn') { document.getElementById('guess-type').innerText = 'Match With Hot Singles' + } else if (whichGuesser === 'beast') { + document.getElementById('guess-type').innerText = + 'Finding Fantastic Beasts' } setUpNewGame() } diff --git a/web/public/mtg/index.html b/web/public/mtg/index.html index 62849462..dde69c64 100644 --- a/web/public/mtg/index.html +++ b/web/public/mtg/index.html @@ -149,6 +149,16 @@

Match With Hot Singles


+ + +
+
Date: Thu, 28 Jul 2022 11:37:26 -0700 Subject: [PATCH 03/44] Groups default open --- web/components/groups/create-group-button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/groups/create-group-button.tsx b/web/components/groups/create-group-button.tsx index 0685d8e4..360c4ea8 100644 --- a/web/components/groups/create-group-button.tsx +++ b/web/components/groups/create-group-button.tsx @@ -46,7 +46,7 @@ export function CreateGroupButton(props: { const newGroup = { name: groupName, memberIds: memberUsers.map((user) => user.id), - anyoneCanJoin: false, + anyoneCanJoin: true, } const result = await createGroup(newGroup).catch((e) => { const errorDetails = e.details[0] From ada3f0787c06d0f2f85355f2e29557d9b80e242f Mon Sep 17 00:00:00 2001 From: mantikoros Date: Thu, 28 Jul 2022 11:44:07 -0700 Subject: [PATCH 04/44] create: add bottom margin --- web/pages/create.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/pages/create.tsx b/web/pages/create.tsx index 84ac82da..ca29cba9 100644 --- a/web/pages/create.tsx +++ b/web/pages/create.tsx @@ -477,6 +477,8 @@ export function NewContract(props: { {isSubmitting ? 'Creating...' : 'Create question'} + + ) } From 693eb96d23da49fc1f28e16bcb7b02a5be3e128f Mon Sep 17 00:00:00 2001 From: Austin Chen Date: Wed, 27 Jul 2022 19:47:25 -0700 Subject: [PATCH 05/44] Include groupId when duplicating markets --- web/components/copy-contract-button.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/components/copy-contract-button.tsx b/web/components/copy-contract-button.tsx index fcb3a347..8536df71 100644 --- a/web/components/copy-contract-button.tsx +++ b/web/components/copy-contract-button.tsx @@ -49,6 +49,10 @@ function duplicateContractHref(contract: Contract) { params.initValue = getMappedValue(contract)(contract.initialProbability) } + if (contract.groupLinks && contract.groupLinks.length > 0) { + params.groupId = contract.groupLinks[0].groupId + } + return ( `/create?` + Object.entries(params) From bdea739c5516778037b11cd1dc5b5d3c1a0ad5ed Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 29 Jul 2022 09:20:21 -0700 Subject: [PATCH 06/44] multiple choice contract card --- web/components/contract/contract-card.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/components/contract/contract-card.tsx b/web/components/contract/contract-card.tsx index 164f3f27..e418178c 100644 --- a/web/components/contract/contract-card.tsx +++ b/web/components/contract/contract-card.tsx @@ -115,7 +115,8 @@ export function ContractCard(props: { {question}

- {outcomeType === 'FREE_RESPONSE' && + {(outcomeType === 'FREE_RESPONSE' || + outcomeType === 'MULTIPLE_CHOICE') && (resolution ? ( )} - {outcomeType === 'FREE_RESPONSE' && ( + {(outcomeType === 'FREE_RESPONSE' || + outcomeType === 'MULTIPLE_CHOICE') && ( Date: Fri, 29 Jul 2022 15:09:48 -0700 Subject: [PATCH 07/44] manalink referrals --- web/pages/link/[slug].tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/web/pages/link/[slug].tsx b/web/pages/link/[slug].tsx index 119fec77..fa728c85 100644 --- a/web/pages/link/[slug].tsx +++ b/web/pages/link/[slug].tsx @@ -1,14 +1,17 @@ import { useRouter } from 'next/router' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { SEO } from 'web/components/SEO' import { Title } from 'web/components/title' import { claimManalink } from 'web/lib/firebase/api' import { useManalink } from 'web/lib/firebase/manalinks' import { ManalinkCard } from 'web/components/manalink-card' import { useUser } from 'web/hooks/use-user' -import { firebaseLogin } from 'web/lib/firebase/users' +import { firebaseLogin, getUser } from 'web/lib/firebase/users' import { Row } from 'web/components/layout/row' import { Button } from 'web/components/button' +import { useSaveReferral } from 'web/hooks/use-save-referral' +import { User } from 'common/lib/user' +import { Manalink } from 'common/manalink' export default function ClaimPage() { const user = useUser() @@ -18,6 +21,8 @@ export default function ClaimPage() { const [claiming, setClaiming] = useState(false) const [error, setError] = useState(undefined) + useReferral(user, manalink) + if (!manalink) { return <> } @@ -76,3 +81,13 @@ export default function ClaimPage() { ) } + +const useReferral = (user: User | undefined | null, manalink?: Manalink) => { + const [creator, setCreator] = useState(undefined) + + useEffect(() => { + if (manalink?.fromId) getUser(manalink.fromId).then(setCreator) + }, [manalink]) + + useSaveReferral(user, { defaultReferrer: creator?.username }) +} From 5812d8ed2ebde8557f8e09e1b9c038cfabdd3222 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 29 Jul 2022 16:02:18 -0700 Subject: [PATCH 08/44] manalink qr code --- web/components/manalink-card.tsx | 18 +++++++++++++++++- .../manalinks/create-links-button.tsx | 11 +++++++---- web/components/qr-code.tsx | 16 ++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 web/components/qr-code.tsx diff --git a/web/components/manalink-card.tsx b/web/components/manalink-card.tsx index 51880f5d..c8529609 100644 --- a/web/components/manalink-card.tsx +++ b/web/components/manalink-card.tsx @@ -10,6 +10,7 @@ import { DotsHorizontalIcon } from '@heroicons/react/solid' import { contractDetailsButtonClassName } from './contract/contract-info-dialog' import { useUserById } from 'web/hooks/use-user' import getManalinkUrl from 'web/get-manalink-url' +import { QrcodeIcon } from '@heroicons/react/outline' export type ManalinkInfo = { expiresTime: number | null maxUses: number | null @@ -78,7 +79,9 @@ export function ManalinkCardFromView(props: { const { className, link, highlightedSlug } = props const { message, amount, expiresTime, maxUses, claims } = link const [showDetails, setShowDetails] = useState(false) - + const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${200}x${200}&data=${getManalinkUrl( + link.slug + )}` return ( {formatMoney(amount)} + + + {!finishedCreating && ( @@ -199,17 +202,17 @@ function CreateManalinkForm(props: { copyPressed ? 'bg-indigo-50 text-indigo-500 transition-none' : '' )} > -
- {getManalinkUrl(highlightedSlug)} -
+
{url}
{ - navigator.clipboard.writeText(getManalinkUrl(highlightedSlug)) + navigator.clipboard.writeText(url) setCopyPressed(true) }} className="my-auto ml-2 h-5 w-5 cursor-pointer transition hover:opacity-50" /> + + )} diff --git a/web/components/qr-code.tsx b/web/components/qr-code.tsx new file mode 100644 index 00000000..a10f8886 --- /dev/null +++ b/web/components/qr-code.tsx @@ -0,0 +1,16 @@ +export function QRCode(props: { + url: string + className?: string + width?: number + height?: number +}) { + const { url, className, width, height } = { + width: 200, + height: 200, + ...props, + } + + const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${width}x${height}&data=${url}` + + return +} From 079a2a3936426edc70c1319dd0ddeddc79261ea6 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 29 Jul 2022 16:06:22 -0700 Subject: [PATCH 09/44] eslint --- web/pages/link/[slug].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/pages/link/[slug].tsx b/web/pages/link/[slug].tsx index fa728c85..af3f01a8 100644 --- a/web/pages/link/[slug].tsx +++ b/web/pages/link/[slug].tsx @@ -10,7 +10,7 @@ import { firebaseLogin, getUser } from 'web/lib/firebase/users' import { Row } from 'web/components/layout/row' import { Button } from 'web/components/button' import { useSaveReferral } from 'web/hooks/use-save-referral' -import { User } from 'common/lib/user' +import { User } from 'common/user' import { Manalink } from 'common/manalink' export default function ClaimPage() { From be01a152305a769c5b2859d1e2ca01c25e872524 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Fri, 29 Jul 2022 19:08:51 -0500 Subject: [PATCH 10/44] Refactor search to not use Algolia components (#705) * In progress refactor to not use Algolia components * Cleanup * Only query when necessary * Read and update url params for query and sort * Don't push router * Don't update url if using default sort * Add popstate listener to improve home navigation * Remove console.logs --- web/components/contract-search.tsx | 366 +++++++++++------------- web/hooks/use-sort-and-query-params.tsx | 64 +++-- web/pages/home.tsx | 10 +- 3 files changed, 213 insertions(+), 227 deletions(-) diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx index c7660138..8596aa2e 100644 --- a/web/components/contract-search.tsx +++ b/web/components/contract-search.tsx @@ -1,26 +1,14 @@ /* eslint-disable react-hooks/exhaustive-deps */ import algoliasearch from 'algoliasearch/lite' -import { - Configure, - InstantSearch, - SearchBox, - SortBy, - useInfiniteHits, - useSortBy, -} from 'react-instantsearch-hooks-web' import { Contract } from 'common/contract' -import { - Sort, - useInitialQueryAndSort, - useUpdateQueryAndSort, -} from '../hooks/use-sort-and-query-params' +import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params' import { ContractHighlightOptions, ContractsGrid, } from './contract/contracts-list' import { Row } from './layout/row' -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Spacer } from './layout/spacer' import { ENV, IS_PRIVATE_MANIFOLD } from 'common/envs/constants' import { useUser } from 'web/hooks/use-user' @@ -30,8 +18,9 @@ import ContractSearchFirestore from 'web/pages/contract-search-firestore' import { useMemberGroups } from 'web/hooks/use-group' import { Group, NEW_USER_GROUP_SLUGS } from 'common/group' import { PillButton } from './buttons/pill-button' -import { sortBy } from 'lodash' +import { range, sortBy } from 'lodash' import { DEFAULT_CATEGORY_GROUPS } from 'common/categories' +import { Col } from './layout/col' const searchClient = algoliasearch( 'GJQPAYENIF', @@ -40,16 +29,15 @@ const searchClient = algoliasearch( const indexPrefix = ENV === 'DEV' ? 'dev-' : '' -const sortIndexes = [ - { label: 'Newest', value: indexPrefix + 'contracts-newest' }, - // { label: 'Oldest', value: indexPrefix + 'contracts-oldest' }, - { label: 'Most popular', value: indexPrefix + 'contracts-score' }, - { label: 'Most traded', value: indexPrefix + 'contracts-most-traded' }, - { label: '24h volume', value: indexPrefix + 'contracts-24-hour-vol' }, - { label: 'Last updated', value: indexPrefix + 'contracts-last-updated' }, - { label: 'Subsidy', value: indexPrefix + 'contracts-liquidity' }, - { label: 'Close date', value: indexPrefix + 'contracts-close-date' }, - { label: 'Resolve date', value: indexPrefix + 'contracts-resolve-date' }, +const sortOptions = [ + { label: 'Newest', value: 'newest' }, + { label: 'Most popular', value: 'score' }, + { label: 'Most traded', value: 'most-traded' }, + { label: '24h volume', value: '24-hour-vol' }, + { label: 'Last updated', value: 'last-updated' }, + { label: 'Subsidy', value: 'liquidity' }, + { label: 'Close date', value: 'close-date' }, + { label: 'Resolve date', value: 'resolve-date' }, ] export const DEFAULT_SORT = 'score' @@ -108,13 +96,12 @@ export function ContractSearch(props: { memberPillGroups.length > 0 ? memberPillGroups : defaultPillGroups const follows = useFollows(user?.id) - const { initialSort } = useInitialQueryAndSort(querySortOptions) - const sort = sortIndexes - .map(({ value }) => value) - .includes(`${indexPrefix}contracts-${initialSort ?? ''}`) - ? initialSort - : querySortOptions?.defaultSort ?? DEFAULT_SORT + const { shouldLoadFromStorage, defaultSort } = querySortOptions ?? {} + const { query, setQuery, sort, setSort } = useQueryAndSortParams({ + defaultSort, + shouldLoadFromStorage, + }) const [filter, setFilter] = useState( querySortOptions?.defaultFilter ?? 'open' @@ -123,62 +110,129 @@ export function ContractSearch(props: { const [pillFilter, setPillFilter] = useState(undefined) - const selectFilter = (pill: string | undefined) => () => { + const selectPill = (pill: string | undefined) => () => { setPillFilter(pill) + setPage(0) track('select search category', { category: pill ?? 'all' }) } - const { filters, numericFilters } = useMemo(() => { - let filters = [ - filter === 'open' ? 'isResolved:false' : '', - filter === 'closed' ? 'isResolved:false' : '', - filter === 'resolved' ? 'isResolved:true' : '', - additionalFilter?.creatorId - ? `creatorId:${additionalFilter.creatorId}` - : '', - additionalFilter?.tag ? `lowercaseTags:${additionalFilter.tag}` : '', - additionalFilter?.groupSlug - ? `groupLinks.slug:${additionalFilter.groupSlug}` - : '', - pillFilter && pillFilter !== 'personal' && pillFilter !== 'your-bets' - ? `groupLinks.slug:${pillFilter}` - : '', - 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}`) ?? []) - // Show contracts bet on by users the user follows - .concat( - follows?.map((followId) => `uniqueBettorIds:${followId}`) ?? [] - ) - : '', - // Subtract contracts you bet on from For you. - pillFilter === 'personal' && user ? `uniqueBettorIds:-${user.id}` : '', - pillFilter === 'your-bets' && user - ? // Show contracts bet on by the user - `uniqueBettorIds:${user.id}` - : '', - ].filter((f) => f) - // Hack to make Algolia work. - filters = ['', ...filters] + let facetFilters = [ + filter === 'open' ? 'isResolved:false' : '', + filter === 'closed' ? 'isResolved:false' : '', + filter === 'resolved' ? 'isResolved:true' : '', + additionalFilter?.creatorId + ? `creatorId:${additionalFilter.creatorId}` + : '', + additionalFilter?.tag ? `lowercaseTags:${additionalFilter.tag}` : '', + additionalFilter?.groupSlug + ? `groupLinks.slug:${additionalFilter.groupSlug}` + : '', + pillFilter && pillFilter !== 'personal' && pillFilter !== 'your-bets' + ? `groupLinks.slug:${pillFilter}` + : '', + 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}`) ?? []) + // Show contracts bet on by users the user follows + .concat( + follows?.map((followId) => `uniqueBettorIds:${followId}`) ?? [] + ) + : '', + // Subtract contracts you bet on from For you. + pillFilter === 'personal' && user ? `uniqueBettorIds:-${user.id}` : '', + pillFilter === 'your-bets' && user + ? // Show contracts bet on by the user + `uniqueBettorIds:${user.id}` + : '', + ].filter((f) => f) + // Hack to make Algolia work. + facetFilters = ['', ...facetFilters] - const numericFilters = [ - filter === 'open' ? `closeTime > ${Date.now()}` : '', - filter === 'closed' ? `closeTime <= ${Date.now()}` : '', - ].filter((f) => f) - - return { filters, numericFilters } - }, [ - filter, - Object.values(additionalFilter ?? {}).join(','), - memberGroupSlugs.join(','), - (follows ?? []).join(','), - pillFilter, - ]) + const numericFilters = [ + filter === 'open' ? `closeTime > ${Date.now()}` : '', + filter === 'closed' ? `closeTime <= ${Date.now()}` : '', + ].filter((f) => f) const indexName = `${indexPrefix}contracts-${sort}` + const index = useMemo(() => searchClient.initIndex(indexName), [indexName]) + + const [page, setPage] = useState(0) + const [numPages, setNumPages] = useState(1) + const [hitsByPage, setHitsByPage] = useState<{ [page: string]: Contract[] }>( + {} + ) + + useEffect(() => { + let wasMostRecentQuery = true + index + .search(query, { + facetFilters, + numericFilters, + page, + hitsPerPage: 20, + }) + .then((results) => { + if (!wasMostRecentQuery) return + + if (page === 0) { + setHitsByPage({ + [0]: results.hits as any as Contract[], + }) + } else { + setHitsByPage((hitsByPage) => ({ + ...hitsByPage, + [page]: results.hits, + })) + } + setNumPages(results.nbPages) + }) + return () => { + wasMostRecentQuery = false + } + // Note numeric filters are unique based on current time, so can't compare + // them by value. + }, [query, page, index, JSON.stringify(facetFilters), filter]) + + const loadMore = () => { + if (page >= numPages - 1) return + + const haveLoadedCurrentPage = hitsByPage[page] + if (haveLoadedCurrentPage) setPage(page + 1) + } + + const hits = range(0, page + 1) + .map((p) => hitsByPage[p] ?? []) + .flat() + + const contracts = hits.filter( + (c) => !additionalFilter?.excludeContractIds?.includes(c.id) + ) + + const showTime = + sort === 'close-date' || sort === 'resolve-date' ? sort : undefined + + const updateQuery = (newQuery: string) => { + setQuery(newQuery) + setPage(0) + } + + const selectFilter = (newFilter: filter) => { + if (newFilter === filter) return + setFilter(newFilter) + setPage(0) + trackCallback('select search filter', { filter: newFilter }) + } + + const selectSort = (newSort: Sort) => { + if (newSort === sort) return + + setPage(0) + setSort(newSort) + track('select sort', { sort: newSort }) + } if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) { return ( @@ -190,23 +244,19 @@ export function ContractSearch(props: { } return ( - + - @@ -237,14 +285,14 @@ export function ContractSearch(props: { All {user ? 'For you' : 'Featured'} @@ -253,7 +301,7 @@ export function ContractSearch(props: { Your bets @@ -264,7 +312,7 @@ export function ContractSearch(props: { {name} @@ -280,103 +328,17 @@ export function ContractSearch(props: { memberGroupSlugs.length === 0 ? ( <>You're not following anyone, nor in any of your own groups yet. ) : ( - )} - - ) -} - -export function ContractSearchInner(props: { - querySortOptions?: { - defaultSort: Sort - shouldLoadFromStorage?: boolean - } - onContractClick?: (contract: Contract) => void - overrideGridClassName?: string - hideQuickBet?: boolean - excludeContractIds?: string[] - highlightOptions?: ContractHighlightOptions - cardHideOptions?: { - hideQuickBet?: boolean - hideGroupLink?: boolean - } -}) { - const { - querySortOptions, - onContractClick, - overrideGridClassName, - cardHideOptions, - excludeContractIds, - highlightOptions, - } = props - const { initialQuery } = useInitialQueryAndSort(querySortOptions) - - const { query, setQuery, setSort } = useUpdateQueryAndSort({ - shouldLoadFromStorage: true, - }) - - useEffect(() => { - setQuery(initialQuery) - }, [initialQuery]) - - const { currentRefinement: index } = useSortBy({ - items: [], - }) - - useEffect(() => { - setQuery(query) - }, [query]) - - const isFirstRender = useRef(true) - useEffect(() => { - if (isFirstRender.current) { - isFirstRender.current = false - return - } - - const sort = index.split('contracts-')[1] as Sort - if (sort) { - setSort(sort) - } - }, [index]) - - const [isInitialLoad, setIsInitialLoad] = useState(true) - useEffect(() => { - const id = setTimeout(() => setIsInitialLoad(false), 1000) - return () => clearTimeout(id) - }, []) - - const { showMore, hits, isLastPage } = useInfiniteHits() - let contracts = hits as any as Contract[] - - if (isInitialLoad && contracts.length === 0) return <> - - const showTime = index.endsWith('close-date') - ? 'close-date' - : index.endsWith('resolve-date') - ? 'resolve-date' - : undefined - - if (excludeContractIds) - contracts = contracts.filter((c) => !excludeContractIds.includes(c.id)) - - return ( - + ) } diff --git a/web/hooks/use-sort-and-query-params.tsx b/web/hooks/use-sort-and-query-params.tsx index 9023dc1a..fb5bf29b 100644 --- a/web/hooks/use-sort-and-query-params.tsx +++ b/web/hooks/use-sort-and-query-params.tsx @@ -1,8 +1,6 @@ import { defaults, debounce } from 'lodash' import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' -import { useSearchBox } from 'react-instantsearch-hooks-web' -import { track } from 'web/lib/service/analytics' import { DEFAULT_SORT } from 'web/components/contract-search' const MARKETS_SORT = 'markets_sort' @@ -74,51 +72,69 @@ export function useInitialQueryAndSort(options?: { } } -export function useUpdateQueryAndSort(props: { - shouldLoadFromStorage: boolean +export function useQueryAndSortParams(options?: { + defaultSort?: Sort + shouldLoadFromStorage?: boolean }) { - const { shouldLoadFromStorage } = props + const { defaultSort = DEFAULT_SORT, shouldLoadFromStorage = true } = + options ?? {} const router = useRouter() + const { s: sort, q: query } = router.query as { + q?: string + s?: Sort + } + const setSort = (sort: Sort | undefined) => { - if (sort !== router.query.s) { - router.query.s = sort - router.replace({ query: { ...router.query, s: sort } }, undefined, { - shallow: true, - }) - if (shouldLoadFromStorage) { - localStorage.setItem(MARKETS_SORT, sort || '') - } + router.replace({ query: { ...router.query, s: sort } }, undefined, { + shallow: true, + }) + if (shouldLoadFromStorage) { + localStorage.setItem(MARKETS_SORT, sort || '') } } - const { query, refine } = useSearchBox() + const [queryState, setQueryState] = useState(query) + + useEffect(() => { + setQueryState(query) + }, [query]) // Debounce router query update. const pushQuery = useMemo( () => debounce((query: string | undefined) => { - if (query) { - router.query.q = query - } else { - delete router.query.q - } - router.replace({ query: router.query }, undefined, { + router.replace({ query: { ...router.query, q: query } }, undefined, { shallow: true, }) - track('search', { query }) - }, 500), + }, 100), [router] ) const setQuery = (query: string | undefined) => { - refine(query ?? '') + setQueryState(query) pushQuery(query) } + useEffect(() => { + // If there's no sort option, then set the one from localstorage + if (router.isReady && !sort && shouldLoadFromStorage) { + const localSort = localStorage.getItem(MARKETS_SORT) as Sort + if (localSort && localSort !== defaultSort) { + // Use replace to not break navigating back. + router.replace( + { query: { ...router.query, s: localSort } }, + undefined, + { shallow: true } + ) + } + } + }) + return { + sort: sort ?? defaultSort, + query: queryState ?? '', setSort, setQuery, - query, } } diff --git a/web/pages/home.tsx b/web/pages/home.tsx index 61003895..ab915ae3 100644 --- a/web/pages/home.tsx +++ b/web/pages/home.tsx @@ -81,11 +81,18 @@ const useContractPage = () => { if (!username || !contractSlug) setContract(undefined) else { // Show contract if route is to a contract: '/[username]/[contractSlug]'. - getContractFromSlug(contractSlug).then(setContract) + getContractFromSlug(contractSlug).then((contract) => { + const path = location.pathname.split('/').slice(1) + const [_username, contractSlug] = path + // Make sure we're still on the same contract. + if (contract?.slug === contractSlug) setContract(contract) + }) } } } + addEventListener('popstate', updateContract) + const { pushState, replaceState } = window.history window.history.pushState = function () { @@ -101,6 +108,7 @@ const useContractPage = () => { } return () => { + removeEventListener('popstate', updateContract) window.history.pushState = pushState window.history.replaceState = replaceState } From d6cf4332da7a58dbd10c3d571711b2c779e27aa1 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Fri, 29 Jul 2022 17:37:34 -0700 Subject: [PATCH 11/44] Delete query param when empty --- web/hooks/use-sort-and-query-params.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/hooks/use-sort-and-query-params.tsx b/web/hooks/use-sort-and-query-params.tsx index fb5bf29b..ae226e87 100644 --- a/web/hooks/use-sort-and-query-params.tsx +++ b/web/hooks/use-sort-and-query-params.tsx @@ -104,7 +104,9 @@ export function useQueryAndSortParams(options?: { const pushQuery = useMemo( () => debounce((query: string | undefined) => { - router.replace({ query: { ...router.query, q: query } }, undefined, { + const queryObj = { ...router.query, q: query || undefined } + if (!query) delete queryObj.q + router.replace({ query: queryObj }, undefined, { shallow: true, }) }, 100), From 003301762c884f3e1abca501cfac49873f556ac5 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Fri, 29 Jul 2022 17:37:53 -0700 Subject: [PATCH 12/44] Ignore filter on contract status when searching --- web/components/contract-search.tsx | 98 ++++++++++++++++-------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx index 8596aa2e..4202618f 100644 --- a/web/components/contract-search.tsx +++ b/web/components/contract-search.tsx @@ -116,45 +116,49 @@ export function ContractSearch(props: { track('select search category', { category: pill ?? 'all' }) } - let facetFilters = [ - filter === 'open' ? 'isResolved:false' : '', - filter === 'closed' ? 'isResolved:false' : '', - filter === 'resolved' ? 'isResolved:true' : '', - additionalFilter?.creatorId - ? `creatorId:${additionalFilter.creatorId}` - : '', - additionalFilter?.tag ? `lowercaseTags:${additionalFilter.tag}` : '', - additionalFilter?.groupSlug - ? `groupLinks.slug:${additionalFilter.groupSlug}` - : '', - pillFilter && pillFilter !== 'personal' && pillFilter !== 'your-bets' - ? `groupLinks.slug:${pillFilter}` - : '', - 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}`) ?? []) - // Show contracts bet on by users the user follows - .concat( - follows?.map((followId) => `uniqueBettorIds:${followId}`) ?? [] - ) - : '', - // Subtract contracts you bet on from For you. - pillFilter === 'personal' && user ? `uniqueBettorIds:-${user.id}` : '', - pillFilter === 'your-bets' && user - ? // Show contracts bet on by the user - `uniqueBettorIds:${user.id}` - : '', - ].filter((f) => f) + let facetFilters = query + ? [] + : [ + filter === 'open' ? 'isResolved:false' : '', + filter === 'closed' ? 'isResolved:false' : '', + filter === 'resolved' ? 'isResolved:true' : '', + additionalFilter?.creatorId + ? `creatorId:${additionalFilter.creatorId}` + : '', + additionalFilter?.tag ? `lowercaseTags:${additionalFilter.tag}` : '', + additionalFilter?.groupSlug + ? `groupLinks.slug:${additionalFilter.groupSlug}` + : '', + pillFilter && pillFilter !== 'personal' && pillFilter !== 'your-bets' + ? `groupLinks.slug:${pillFilter}` + : '', + 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}`) ?? []) + // Show contracts bet on by users the user follows + .concat( + follows?.map((followId) => `uniqueBettorIds:${followId}`) ?? [] + ) + : '', + // Subtract contracts you bet on from For you. + pillFilter === 'personal' && user ? `uniqueBettorIds:-${user.id}` : '', + pillFilter === 'your-bets' && user + ? // Show contracts bet on by the user + `uniqueBettorIds:${user.id}` + : '', + ].filter((f) => f) // Hack to make Algolia work. facetFilters = ['', ...facetFilters] - const numericFilters = [ - filter === 'open' ? `closeTime > ${Date.now()}` : '', - filter === 'closed' ? `closeTime <= ${Date.now()}` : '', - ].filter((f) => f) + const numericFilters = query + ? [] + : [ + filter === 'open' ? `closeTime > ${Date.now()}` : '', + filter === 'closed' ? `closeTime <= ${Date.now()}` : '', + ].filter((f) => f) const indexName = `${indexPrefix}contracts-${sort}` const index = useMemo(() => searchClient.initIndex(indexName), [indexName]) @@ -253,16 +257,18 @@ export function ContractSearch(props: { placeholder={showPlaceHolder ? `Search ${filter} markets` : ''} className="input input-bordered w-full" /> - + {!query && ( + + )} {!hideOrderSelector && ( )} - {!hideOrderSelector && ( + {!hideOrderSelector && !query && (