diff --git a/common/categories.ts b/common/categories.ts new file mode 100644 index 00000000..4ab1afe9 --- /dev/null +++ b/common/categories.ts @@ -0,0 +1,25 @@ +export const CATEGORIES = { + politics: 'Politics', + technology: 'Technology', + sports: 'Sports', + gaming: 'Gaming / Esports', + manifold: 'Manifold Markets', + science: 'Science', + world: 'World', + fun: 'Fun stuff', + personal: 'Personal', + economics: 'Economics', + crypto: 'Crypto', + health: 'Health', + // entertainment: 'Entertainment', + // society: 'Society', + // friends: 'Friends / Community', + // business: 'Business', + // charity: 'Charities / Non-profits', +} as { [category: string]: string } + +export const TO_CATEGORY = Object.fromEntries( + Object.entries(CATEGORIES).map(([k, v]) => [v, k]) +) + +export const CATEGORY_LIST = Object.keys(CATEGORIES) diff --git a/common/feed.ts b/common/feed.ts new file mode 100644 index 00000000..a8783c7f --- /dev/null +++ b/common/feed.ts @@ -0,0 +1,9 @@ +import { Bet } from './bet' +import { Comment } from './comment' +import { Contract } from './contract' + +export type feed = { + contract: Contract + recentBets: Bet[] + recentComments: Comment[] +}[] diff --git a/common/user.ts b/common/user.ts index dcbe28e9..8713717d 100644 --- a/common/user.ts +++ b/common/user.ts @@ -17,6 +17,8 @@ export type User = { totalDeposits: number totalPnLCached: number creatorVolumeCached: number + + followedCategories?: string[] } export const STARTING_BALANCE = 1000 diff --git a/firestore.rules b/firestore.rules index 28e03e64..24ab0941 100644 --- a/firestore.rules +++ b/firestore.rules @@ -16,7 +16,7 @@ service cloud.firestore { allow read; allow update: if resource.data.id == request.auth.uid && request.resource.data.diff(resource.data).affectedKeys() - .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle']); + .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories']); } match /private-users/{userId} { @@ -35,7 +35,7 @@ service cloud.firestore { allow create: if userId == request.auth.uid; } - match /private-users/{userId}/cache/feed { + match /private-users/{userId}/cache/{docId} { allow read: if userId == request.auth.uid || isAdmin(); } diff --git a/functions/package.json b/functions/package.json index 2eeec6f2..d51a3481 100644 --- a/functions/package.json +++ b/functions/package.json @@ -19,11 +19,13 @@ }, "main": "lib/functions/src/index.js", "dependencies": { + "@react-query-firebase/firestore": "0.4.2", "fetch": "1.1.0", "firebase-admin": "10.0.0", "firebase-functions": "3.16.0", "lodash": "4.17.21", "mailgun-js": "0.22.0", + "react-query": "3.39.0", "module-alias": "2.2.2", "stripe": "8.194.0" }, diff --git a/functions/src/create-answer.ts b/functions/src/create-answer.ts index 21eceba8..45f42291 100644 --- a/functions/src/create-answer.ts +++ b/functions/src/create-answer.ts @@ -7,8 +7,8 @@ import { getNewMultiBetInfo } from 'common/new-bet' import { Answer, MAX_ANSWER_LENGTH } from 'common/answer' import { getContract, getValues } from './utils' import { sendNewAnswerEmail } from './emails' -import { Bet } from 'common/bet' -import { hasUserHitManaLimit } from 'common/calculate' +import { Bet } from '../../common/bet' +import { hasUserHitManaLimit } from '../../common/calculate' export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall( async ( diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index a2a1a93d..5fa0c036 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -1,6 +1,7 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' import * as _ from 'lodash' + import { chargeUser, getUser } from './utils' import { Binary, diff --git a/functions/src/get-feed-data.ts b/functions/src/get-feed-data.ts new file mode 100644 index 00000000..75782fed --- /dev/null +++ b/functions/src/get-feed-data.ts @@ -0,0 +1,71 @@ +import * as admin from 'firebase-admin' +import { Bet } from '../../common/bet' +import { Contract } from '../../common/contract' +import { DAY_MS } from '../../common/util/time' +import { getValues } from './utils' + +const firestore = admin.firestore() + +export async function getFeedContracts() { + // Get contracts bet on or created in last week. + const [activeContracts, inactiveContracts] = await Promise.all([ + getValues( + firestore + .collection('contracts') + .where('isResolved', '==', false) + .where('volume7Days', '>', 0) + ), + + getValues( + firestore + .collection('contracts') + .where('isResolved', '==', false) + .where('createdTime', '>', Date.now() - DAY_MS * 7) + .where('volume7Days', '==', 0) + ), + ]) + + const combined = [...activeContracts, ...inactiveContracts] + // Remove closed contracts. + return combined.filter((c) => (c.closeTime ?? Infinity) > Date.now()) +} + +export async function getTaggedContracts(tag: string) { + const taggedContracts = await getValues( + firestore + .collection('contracts') + .where('isResolved', '==', false) + .where('lowercaseTags', 'array-contains', tag.toLowerCase()) + ) + + // Remove closed contracts. + return taggedContracts.filter((c) => (c.closeTime ?? Infinity) > Date.now()) +} + +export async function getRecentBetsAndComments(contract: Contract) { + const contractDoc = firestore.collection('contracts').doc(contract.id) + + const [recentBets, recentComments] = await Promise.all([ + getValues( + contractDoc + .collection('bets') + .where('createdTime', '>', Date.now() - DAY_MS) + .orderBy('createdTime', 'desc') + .limit(1) + ), + + getValues( + contractDoc + .collection('comments') + .where('createdTime', '>', Date.now() - 3 * DAY_MS) + .orderBy('createdTime', 'desc') + .limit(3) + ), + ]) + + return { + contract, + recentBets, + recentComments, + } +} diff --git a/functions/src/scripts/update-feed.ts b/functions/src/scripts/update-feed.ts index f98631dd..d698a529 100644 --- a/functions/src/scripts/update-feed.ts +++ b/functions/src/scripts/update-feed.ts @@ -9,7 +9,9 @@ import { User } from 'common/user' import { batchedWaitAll } from 'common/util/promise' import { Contract } from 'common/contract' import { updateWordScores } from '../update-recommendations' -import { getFeedContracts, doUserFeedUpdate } from '../update-feed' +import { computeFeed } from '../update-feed' +import { getFeedContracts, getTaggedContracts } from '../get-feed-data' +import { CATEGORY_LIST } from '../../../common/categories' const firestore = admin.firestore() @@ -19,8 +21,7 @@ async function updateFeed() { const contracts = await getValues(firestore.collection('contracts')) const feedContracts = await getFeedContracts() const users = await getValues( - firestore.collection('users') - // .where('username', '==', 'JamesGrugett') + firestore.collection('users').where('username', '==', 'JamesGrugett') ) await batchedWaitAll( @@ -28,7 +29,22 @@ async function updateFeed() { console.log('Updating recs for', user.username) await updateWordScores(user, contracts) console.log('Updating feed for', user.username) - await doUserFeedUpdate(user, feedContracts) + await computeFeed(user, feedContracts) + }) + ) + + console.log('Updating feed categories!') + + await batchedWaitAll( + users.map((user) => async () => { + for (const category of CATEGORY_LIST) { + const contracts = await getTaggedContracts(category) + const feed = await computeFeed(user, contracts) + await firestore + .collection(`private-users/${user.id}/cache`) + .doc(`feed-${category}`) + .set({ feed }) + } }) ) } diff --git a/functions/src/update-feed.ts b/functions/src/update-feed.ts index 3bfa7949..c9d2def8 100644 --- a/functions/src/update-feed.ts +++ b/functions/src/update-feed.ts @@ -10,15 +10,19 @@ import { getProbability, getOutcomeProbability, getTopAnswer, -} from 'common/calculate' -import { Bet } from 'common/bet' -import { Comment } from 'common/comment' -import { User } from 'common/user' +} from '../../common/calculate' +import { User } from '../../common/user' import { getContractScore, MAX_FEED_CONTRACTS, } from 'common/recommended-contracts' import { callCloudFunction } from './call-cloud-function' +import { + getFeedContracts, + getRecentBetsAndComments, + getTaggedContracts, +} from './get-feed-data' +import { CATEGORY_LIST } from '../../common/categories' const firestore = admin.firestore() @@ -28,16 +32,28 @@ export const updateFeed = functions.pubsub const users = await getValues(firestore.collection('users')) const batchSize = 100 - const userBatches: User[][] = [] + let userBatches: User[][] = [] for (let i = 0; i < users.length; i += batchSize) { userBatches.push(users.slice(i, i + batchSize)) } + console.log('updating feed batch') + await Promise.all( - userBatches.map(async (users) => + userBatches.map((users) => callCloudFunction('updateFeedBatch', { users }) ) ) + + console.log('updating category feed') + + await Promise.all( + CATEGORY_LIST.map((category) => + callCloudFunction('updateCategoryFeed', { + category, + }) + ) + ) }) export const updateFeedBatch = functions.https.onCall( @@ -45,40 +61,56 @@ export const updateFeedBatch = functions.https.onCall( const { users } = data const contracts = await getFeedContracts() - await Promise.all(users.map((user) => doUserFeedUpdate(user, contracts))) + await Promise.all( + users.map(async (user) => { + const feed = await computeFeed(user, contracts) + await getUserCacheCollection(user).doc('feed').set({ feed }) + }) + ) + } +) +export const updateCategoryFeed = functions.https.onCall( + async (data: { category: string }) => { + const { category } = data + const users = await getValues(firestore.collection('users')) + + const batchSize = 100 + const userBatches: User[][] = [] + for (let i = 0; i < users.length; i += batchSize) { + userBatches.push(users.slice(i, i + batchSize)) + } + + await Promise.all( + userBatches.map(async (users) => { + await callCloudFunction('updateCategoryFeedBatch', { + users, + category, + }) + }) + ) } ) -export async function getFeedContracts() { - // Get contracts bet on or created in last week. - const contracts = await Promise.all([ - getValues( - firestore - .collection('contracts') - .where('isResolved', '==', false) - .where('volume7Days', '>', 0) - ), +export const updateCategoryFeedBatch = functions.https.onCall( + async (data: { users: User[]; category: string }) => { + const { users, category } = data + const contracts = await getTaggedContracts(category) - getValues( - firestore - .collection('contracts') - .where('isResolved', '==', false) - .where('createdTime', '>', Date.now() - DAY_MS * 7) - .where('volume7Days', '==', 0) - ), - ]).then(([activeContracts, inactiveContracts]) => { - const combined = [...activeContracts, ...inactiveContracts] - // Remove closed contracts. - return combined.filter((c) => (c.closeTime ?? Infinity) > Date.now()) - }) + await Promise.all( + users.map(async (user) => { + const feed = await computeFeed(user, contracts) + await getUserCacheCollection(user).doc(`feed-${category}`).set({ feed }) + }) + ) + } +) - return contracts -} +const getUserCacheCollection = (user: User) => + firestore.collection(`private-users/${user.id}/cache`) + +export const computeFeed = async (user: User, contracts: Contract[]) => { + const userCacheCollection = getUserCacheCollection(user) -export const doUserFeedUpdate = async (user: User, contracts: Contract[]) => { - const userCacheCollection = firestore.collection( - `private-users/${user.id}/cache` - ) const [wordScores, lastViewedTime] = await Promise.all([ getValue<{ [word: string]: number }>(userCacheCollection.doc('wordScores')), getValue<{ [contractId: string]: number }>( @@ -109,8 +141,7 @@ export const doUserFeedUpdate = async (user: User, contracts: Contract[]) => { const feed = await Promise.all( feedContracts.map((contract) => getRecentBetsAndComments(contract)) ) - - await userCacheCollection.doc('feed').set({ feed }) + return feed } function scoreContract( @@ -180,31 +211,3 @@ function getLastViewedScore(viewTime: number | undefined) { const frac = logInterpolation(0.5, 14, daysAgo) return 0.75 + 0.25 * frac } - -async function getRecentBetsAndComments(contract: Contract) { - const contractDoc = firestore.collection('contracts').doc(contract.id) - - const [recentBets, recentComments] = await Promise.all([ - getValues( - contractDoc - .collection('bets') - .where('createdTime', '>', Date.now() - DAY_MS) - .orderBy('createdTime', 'desc') - .limit(1) - ), - - getValues( - contractDoc - .collection('comments') - .where('createdTime', '>', Date.now() - 3 * DAY_MS) - .orderBy('createdTime', 'desc') - .limit(3) - ), - ]) - - return { - contract, - recentBets, - recentComments, - } -} diff --git a/web/components/contract/contract-description.tsx b/web/components/contract/contract-description.tsx index 86331601..dd748f50 100644 --- a/web/components/contract/contract-description.tsx +++ b/web/components/contract/contract-description.tsx @@ -2,6 +2,7 @@ import clsx from 'clsx' import dayjs from 'dayjs' import { useState } from 'react' import Textarea from 'react-expanding-textarea' +import { CATEGORY_LIST } from '../../../common/categories' import { Contract } from 'common/contract' import { parseTags } from 'common/util/parse' @@ -9,6 +10,7 @@ import { useAdmin } from 'web/hooks/use-admin' import { updateContract } from 'web/lib/firebase/contracts' import { Row } from '../layout/row' import { Linkify } from '../linkify' +import { TagsList } from '../tags-list' export function ContractDescription(props: { contract: Contract @@ -26,6 +28,7 @@ export function ContractDescription(props: { `${newDescription} ${contract.tags.map((tag) => `#${tag}`).join(' ')}` ) const lowercaseTags = tags.map((tag) => tag.toLowerCase()) + await updateContract(contract.id, { description: newDescription, tags, @@ -35,6 +38,9 @@ export function ContractDescription(props: { if (!isCreator && !contract.description.trim()) return null + const { tags } = contract + const category = tags.find((tag) => CATEGORY_LIST.includes(tag.toLowerCase())) + return (
+ + {category && ( +
+ +
+ )} +
+ {isCreator && ( +
+ { + if (!user?.id) return + + await updateUser(user.id, { + followedCategories: [], + }) + }} + /> + + {CATEGORY_LIST.map((cat) => ( + { + if (!user?.id) return + + await updateUser(user.id, { + followedCategories: [cat], + }) + }} + /> + ))} + + ) +} + +function CategoryButton(props: { + category: string + isFollowed: boolean + toggle: () => void +}) { + const { toggle, category, isFollowed } = props + + return ( +
+ {category} +
+ ) +} diff --git a/web/components/feed/contract-activity.tsx b/web/components/feed/contract-activity.tsx index 40b2dd09..e061f475 100644 --- a/web/components/feed/contract-activity.tsx +++ b/web/components/feed/contract-activity.tsx @@ -10,6 +10,7 @@ import { } from './activity-items' import { FeedItems } from './feed-items' import { User } from 'common/user' +import { useContract } from 'web/hooks/use-contract' export function ContractActivity(props: { contract: Contract @@ -27,8 +28,9 @@ export function ContractActivity(props: { className?: string betRowClassName?: string }) { - const { contract, user, mode, contractPath, className, betRowClassName } = - props + const { user, mode, contractPath, className, betRowClassName } = props + + const contract = useContract(props.contract.id) ?? props.contract const updatedComments = // eslint-disable-next-line react-hooks/rules-of-hooks diff --git a/web/components/tags-list.tsx b/web/components/tags-list.tsx index 1f22859a..4a55051e 100644 --- a/web/components/tags-list.tsx +++ b/web/components/tags-list.tsx @@ -1,4 +1,5 @@ import clsx from 'clsx' +import { CATEGORIES } from '../../common/categories' import { Col } from './layout/col' import { Row } from './layout/row' @@ -6,6 +7,9 @@ import { SiteLink } from './site-link' function Hashtag(props: { tag: string; noLink?: boolean }) { const { tag, noLink } = props + const category = CATEGORIES[tag.replace('#', '').toLowerCase()] + console.log(tag, category) + const body = (
- {tag} + {category ?? tag}
) @@ -30,11 +34,12 @@ export function TagsList(props: { className?: string noLink?: boolean noLabel?: boolean + label?: string }) { - const { tags, className, noLink, noLabel } = props + const { tags, className, noLink, noLabel, label } = props return ( - {!noLabel &&
Tags
} + {!noLabel &&
{label || 'Tags'}
} {tags.map((tag) => ( { const [feed, setFeed] = useState() + const [categoryFeeds, setCategoryFeeds] = useState>() const getTime = useTimeSinceFirstRender() @@ -34,21 +26,21 @@ export const useAlgoFeed = (user: User | null | undefined) => { trackLatency('feed', getTime()) console.log('feed load time', getTime()) }) + + getCategoryFeeds(user.id).then((feeds) => { + setCategoryFeeds(feeds) + console.log('category feeds load time', getTime()) + }) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [user?.id]) - return useUpdateFeed(feed) -} + const followedCategory = user?.followedCategories?.[0] ?? 'all' -const useUpdateFeed = (feed: feed | undefined) => { - const contracts = useUpdatedContracts(feed?.map((item) => item.contract)) + const followedFeed = + followedCategory === 'all' ? feed : categoryFeeds?.[followedCategory] - return feed && contracts - ? feed.map(({ contract, ...other }, i) => ({ - ...other, - contract: contracts[i], - })) - : undefined + return followedFeed } const getDefaultFeed = async () => { diff --git a/web/hooks/use-contract.ts b/web/hooks/use-contract.ts index ad2f12a2..c0a32eb8 100644 --- a/web/hooks/use-contract.ts +++ b/web/hooks/use-contract.ts @@ -1,17 +1,21 @@ -import { useEffect, useState } from 'react' -import { Contract, listenForContract } from 'web/lib/firebase/contracts' +import { useEffect } from 'react' +import { useFirestoreDocumentData } from '@react-query-firebase/firestore' +import { + Contract, + contractDocRef, + listenForContract, +} from 'web/lib/firebase/contracts' import { useStateCheckEquality } from './use-state-check-equality' +import { DocumentData } from 'firebase/firestore' export const useContract = (contractId: string) => { - const [contract, setContract] = useState( - 'loading' + const result = useFirestoreDocumentData( + ['contracts', contractId], + contractDocRef(contractId), + { subscribe: true, includeMetadataChanges: true } ) - useEffect(() => { - if (contractId) return listenForContract(contractId, setContract) - }, [contractId]) - - return contract + return result.isLoading ? undefined : result.data } export const useContractWithPreload = (initial: Contract | null) => { diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index 72264b72..bd8bfc9b 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -79,6 +79,8 @@ export function tradingAllowed(contract: Contract) { const db = getFirestore(app) export const contractCollection = collection(db, 'contracts') +export const contractDocRef = (contractId: string) => + doc(db, 'contracts', contractId) // Push contract to Firestore export async function setContract(contract: Contract) { diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index 594786ab..6867b2d0 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -25,9 +25,8 @@ import { PrivateUser, User } from 'common/user' import { createUser } from './api-call' import { getValue, getValues, listenForValue, listenForValues } from './utils' import { DAY_MS } from 'common/util/time' -import { Contract } from './contracts' -import { Bet } from './bets' -import { Comment } from './comments' +import { feed } from 'common/feed' +import { CATEGORY_LIST } from 'common/categories' export type { User } @@ -216,11 +215,18 @@ export async function getDailyNewUsers( export async function getUserFeed(userId: string) { const feedDoc = doc(db, 'private-users', userId, 'cache', 'feed') const userFeed = await getValue<{ - feed: { - contract: Contract - recentBets: Bet[] - recentComments: Comment[] - }[] + feed: feed }>(feedDoc) return userFeed?.feed ?? [] } + +export async function getCategoryFeeds(userId: string) { + const cacheCollection = collection(db, 'private-users', userId, 'cache') + const feedData = await Promise.all( + CATEGORY_LIST.map((category) => + getValue<{ feed: feed }>(doc(cacheCollection, `feed-${category}`)) + ) + ) + const feeds = feedData.map((data) => data?.feed ?? []) + return _.fromPairs(_.zip(CATEGORY_LIST, feeds) as [string, feed][]) +} diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index cf06b683..11bedb83 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -4,6 +4,7 @@ import { useEffect } from 'react' import Head from 'next/head' import Script from 'next/script' import { usePreserveScroll } from 'web/hooks/use-preserve-scroll' +import { QueryClient, QueryClientProvider } from 'react-query' function firstLine(msg: string) { return msg.replace(/\r?\n.*/s, '') @@ -76,9 +77,14 @@ function MyApp({ Component, pageProps }: AppProps) { content="width=device-width, initial-scale=1, maximum-scale=1" /> - + + + + ) } +const queryClient = new QueryClient() + export default MyApp diff --git a/web/pages/create.tsx b/web/pages/create.tsx index a4898524..7205f9c3 100644 --- a/web/pages/create.tsx +++ b/web/pages/create.tsx @@ -19,6 +19,8 @@ import { Row } from 'web/components/layout/row' import { MAX_DESCRIPTION_LENGTH, outcomeType } from 'common/contract' import { formatMoney } from 'common/util/format' import { useHasCreatedContractToday } from 'web/hooks/use-has-created-contract-today' +import { removeUndefinedProps } from '../../common/util/object' +import { CATEGORIES, CATEGORY_LIST, TO_CATEGORY } from 'common/categories' export default function Create() { const [question, setQuestion] = useState('') @@ -67,8 +69,10 @@ export function NewContract(props: { question: string; tag?: string }) { const [outcomeType, setOutcomeType] = useState('BINARY') const [initialProb, setInitialProb] = useState(50) const [description, setDescription] = useState('') - const [tagText, setTagText] = useState(tag ?? '') - const tags = parseWordsAsTags(tagText) + + const [category, setCategory] = useState('') + // const [tagText, setTagText] = useState(tag ?? '') + // const tags = parseWordsAsTags(tagText) const [ante, setAnte] = useState(FIXED_ANTE) @@ -110,15 +114,17 @@ export function NewContract(props: { question: string; tag?: string }) { setIsSubmitting(true) - const result: any = await createContract({ - question, - outcomeType, - description, - initialProb, - ante, - closeTime, - tags, - }).then((r) => r.data || {}) + const result: any = await createContract( + removeUndefinedProps({ + question, + outcomeType, + description, + initialProb, + ante, + closeTime, + tags: category ? [category] : undefined, + }) + ).then((r) => r.data || {}) if (result.status !== 'success') { console.log('error creating contract', result) @@ -199,25 +205,29 @@ export function NewContract(props: { question: string; tag?: string }) { />
- {/* +
- setTagText(e.target.value || '')} - /> -
*/} + +
- -
diff --git a/web/pages/home.tsx b/web/pages/home.tsx index f4dbc319..db94f456 100644 --- a/web/pages/home.tsx +++ b/web/pages/home.tsx @@ -11,6 +11,7 @@ import { useUser } from 'web/hooks/use-user' import { LoadingIndicator } from 'web/components/loading-indicator' import { useAlgoFeed } from 'web/hooks/use-algo-feed' import { ContractPageContent } from './[username]/[contractSlug]' +import { CategorySelector } from '../components/feed/category-selector' const Home = () => { const user = useUser() @@ -42,7 +43,12 @@ const Home = () => { - + + + + + + {feed ? ( = 1.43.0 < 2": version "1.51.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" @@ -3957,6 +4004,13 @@ multimatch@^4.0.0: arrify "^2.0.1" minimatch "^3.0.4" +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= + dependencies: + big-integer "^1.6.16" + nanoid@^3.1.23, nanoid@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" @@ -4139,6 +4193,11 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -4695,6 +4754,15 @@ react-motion@^0.5.2: prop-types "^15.5.8" raf "^3.1.0" +react-query@^3.39.0: + version "3.39.0" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.0.tgz#0caca7b0da98e65008bbcd4df0d25618c2100050" + integrity sha512-Od0IkSuS79WJOhzWBx/ys0x13+7wFqgnn64vBqqAAnZ9whocVhl/y1padD5uuZ6EIkXbFbInax0qvY7zGM0thA== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + react-with-forwarded-ref@^0.3.3: version "0.3.4" resolved "https://registry.yarnpkg.com/react-with-forwarded-ref/-/react-with-forwarded-ref-0.3.4.tgz#b1e884ea081ec3c5dd578f37889159797454c0a5" @@ -4767,6 +4835,11 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -4830,7 +4903,7 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -5421,6 +5494,14 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" +unload@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"