Rearrange home sections. Load more in carousel.

This commit is contained in:
James Grugett 2022-08-30 23:45:55 -05:00
parent 7dddff52b8
commit 849402ed70
9 changed files with 500 additions and 200 deletions

View File

@ -34,6 +34,7 @@ export type User = {
followerCountCached: number
followedCategories?: string[]
homeSections?: { visible: string[]; hidden: string[] }
referredByUserId?: string
referredByContractId?: string

View File

@ -3,9 +3,14 @@ import clsx from 'clsx'
import { throttle } from 'lodash'
import { ReactNode, useRef, useState, useEffect } from 'react'
import { Row } from './layout/row'
import { VisibilityObserver } from 'web/components/visibility-observer'
export function Carousel(props: { children: ReactNode; className?: string }) {
const { children, className } = props
export function Carousel(props: {
children: ReactNode
loadMore?: () => void
className?: string
}) {
const { children, loadMore, className } = props
const ref = useRef<HTMLDivElement>(null)
@ -38,6 +43,13 @@ export function Carousel(props: { children: ReactNode; className?: string }) {
onScroll={onScroll}
>
{children}
{loadMore && (
<VisibilityObserver
className="relative -left-96"
onVisibilityUpdated={(visible) => visible && loadMore()}
/>
)}
</Row>
{!atFront && (
<div

View File

@ -38,7 +38,7 @@ const searchClient = algoliasearch(
const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
const searchIndexName = ENV === 'DEV' ? 'dev-contracts' : 'contractsIndex'
const SORTS = [
export const SORTS = [
{ label: 'Newest', value: 'newest' },
{ label: 'Trending', value: 'score' },
{ label: 'Most traded', value: 'most-traded' },
@ -83,9 +83,11 @@ export function ContractSearch(props: {
persistPrefix?: string
useQueryUrlParam?: boolean
isWholePage?: boolean
maxItems?: number
noControls?: boolean
renderContracts?: (contracts: Contract[] | undefined) => ReactNode
renderContracts?: (
contracts: Contract[] | undefined,
loadMore: () => void
) => ReactNode
}) {
const {
user,
@ -100,7 +102,6 @@ export function ContractSearch(props: {
persistPrefix,
useQueryUrlParam,
isWholePage,
maxItems,
noControls,
renderContracts,
} = props
@ -184,8 +185,7 @@ export function ContractSearch(props: {
const contracts = state.pages
.flat()
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
const renderedContracts =
state.pages.length === 0 ? undefined : contracts.slice(0, maxItems)
const renderedContracts = state.pages.length === 0 ? undefined : contracts
if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
return <ContractSearchFirestore additionalFilter={additionalFilter} />
@ -206,7 +206,7 @@ export function ContractSearch(props: {
noControls={noControls}
/>
{renderContracts ? (
renderContracts(renderedContracts)
renderContracts(renderedContracts, performQuery)
) : (
<ContractsGrid
contracts={renderedContracts}

View File

@ -49,6 +49,7 @@
"next": "12.2.5",
"node-fetch": "3.2.4",
"react": "17.0.2",
"react-beautiful-dnd": "13.1.1",
"react-confetti": "6.0.1",
"react-dom": "17.0.2",
"react-expanding-textarea": "2.3.5",
@ -66,6 +67,7 @@
"@types/lodash": "4.14.178",
"@types/node": "16.11.11",
"@types/react": "17.0.43",
"@types/react-beautiful-dnd": "13.1.2",
"@types/react-dom": "17.0.2",
"@types/string-similarity": "^4.0.0",
"autoprefixer": "10.2.6",

View File

@ -1,189 +0,0 @@
import React from 'react'
import Router from 'next/router'
import { PlusSmIcon } from '@heroicons/react/solid'
import { Page } from 'web/components/page'
import { Col } from 'web/components/layout/col'
import { ContractSearch } from 'web/components/contract-search'
import { User } from 'common/user'
import { getUserAndPrivateUser } from 'web/lib/firebase/users'
import { useTracking } from 'web/hooks/use-tracking'
import { track } from 'web/lib/service/analytics'
import { authenticateOnServer } from 'web/lib/firebase/server-auth'
import { useSaveReferral } from 'web/hooks/use-save-referral'
import { GetServerSideProps } from 'next'
import { Sort } from 'web/components/contract-search'
import { Button } from 'web/components/button'
import { useMemberGroups } from 'web/hooks/use-group'
import { Group } from 'common/group'
import { Carousel } from 'web/components/carousel'
import { LoadingIndicator } from 'web/components/loading-indicator'
import { ContractCard } from 'web/components/contract/contract-card'
import { range } from 'lodash'
import { Contract } from 'common/contract'
import { ShowTime } from 'web/components/contract/contract-details'
import { GroupLinkItem } from '../groups'
import { SiteLink } from 'web/components/site-link'
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const creds = await authenticateOnServer(ctx)
const auth = creds ? await getUserAndPrivateUser(creds.user.uid) : null
return { props: { auth } }
}
const Home = (props: { auth: { user: User } | null }) => {
const user = props.auth ? props.auth.user : null
useTracking('view home')
useSaveReferral()
const memberGroups = (useMemberGroups(user?.id) ?? []).filter(
(group) => group.contractIds.length > 0
)
return (
<Page>
<Col className="mx-4 mt-4 gap-4 sm:mx-10 xl:w-[125%]">
<SearchSection label="Trending" sort="score" user={user} />
<SearchSection label="Newest" sort="newest" user={user} />
<SearchSection label="Closing soon" sort="close-date" user={user} />
{memberGroups.map((group) => (
<GroupSection key={group.id} group={group} user={user} />
))}
</Col>
<button
type="button"
className="fixed bottom-[70px] right-3 inline-flex items-center rounded-full border border-transparent bg-indigo-600 p-3 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 lg:hidden"
onClick={() => {
Router.push('/create')
track('mobile create button')
}}
>
<PlusSmIcon className="h-8 w-8" aria-hidden="true" />
</button>
</Page>
)
}
function SearchSection(props: {
label: string
user: User | null
sort: Sort
}) {
const { label, user, sort } = props
const href = `/home?s=${sort}`
return (
<Col>
<SiteLink className="mb-2 text-xl" href={href}>
{label}
</SiteLink>
<ContractSearch
user={user}
defaultSort={sort}
maxItems={12}
noControls
renderContracts={(contracts) =>
contracts ? (
<DoubleCarousel
contracts={contracts}
seeMoreUrl={href}
showTime={
sort === 'close-date' || sort === 'resolve-date'
? sort
: undefined
}
/>
) : (
<LoadingIndicator />
)
}
/>
</Col>
)
}
function GroupSection(props: { group: Group; user: User | null }) {
const { group, user } = props
return (
<Col>
<GroupLinkItem className="mb-2 text-xl" group={group} />
<ContractSearch
user={user}
defaultSort={'score'}
additionalFilter={{ groupSlug: group.slug }}
maxItems={12}
noControls
renderContracts={(contracts) =>
contracts ? (
<DoubleCarousel
contracts={contracts}
seeMoreUrl={`/group/${group.slug}`}
/>
) : (
<LoadingIndicator />
)
}
/>
</Col>
)
}
function DoubleCarousel(props: {
contracts: Contract[]
seeMoreUrl?: string
showTime?: ShowTime
}) {
const { contracts, seeMoreUrl, showTime } = props
return (
<Carousel className="-mx-4 mt-2 sm:-mx-10">
<div className="shrink-0 sm:w-6" />
{contracts.length >= 6
? range(0, Math.floor(contracts.length / 2)).map((col) => {
const i = col * 2
return (
<Col>
<ContractCard
key={contracts[i].id}
contract={contracts[i]}
className="mb-2 max-h-[200px] w-96 shrink-0"
questionClass="line-clamp-3"
trackingPostfix=" tournament"
showTime={showTime}
/>
<ContractCard
key={contracts[i + 1].id}
contract={contracts[i + 1]}
className="mb-2 max-h-[200px] w-96 shrink-0"
questionClass="line-clamp-3"
trackingPostfix=" tournament"
showTime={showTime}
/>
</Col>
)
})
: contracts.map((c) => (
<ContractCard
key={c.id}
contract={c}
className="mb-2 max-h-[200px] w-96 shrink-0"
questionClass="line-clamp-3"
trackingPostfix=" tournament"
showTime={showTime}
/>
))}
<Button
className="self-center whitespace-nowrap"
color="blue"
size="sm"
onClick={() => seeMoreUrl && Router.push(seeMoreUrl)}
>
See more
</Button>
</Carousel>
)
}
export default Home

View File

@ -0,0 +1,127 @@
import clsx from 'clsx'
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
import { MenuIcon } from '@heroicons/react/solid'
import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
import { Subtitle } from 'web/components/subtitle'
import { useMemberGroups } from 'web/hooks/use-group'
import { filterDefined } from 'common/util/array'
import { keyBy } from 'lodash'
import { User } from 'common/user'
export function ArrangeHome(props: {
user: User | null
homeSections: { visible: string[]; hidden: string[] }
setHomeSections: (homeSections: {
visible: string[]
hidden: string[]
}) => void
}) {
const {
user,
homeSections: { visible, hidden },
setHomeSections,
} = props
const memberGroups = useMemberGroups(user?.id) ?? []
const items = [
{ label: 'Trending', id: 'score' },
{ label: 'Newest', id: 'newest' },
{ label: 'Close date', id: 'close-date' },
...memberGroups.map((g) => ({
label: g.name,
id: g.id,
})),
]
const itemsById = keyBy(items, 'id')
const [visibleItems, hiddenItems] = [
filterDefined(visible.map((id) => itemsById[id])),
filterDefined(hidden.map((id) => itemsById[id])),
]
// Add unmentioned items to the visible list.
visibleItems.push(
...items.filter(
(item) => !visibleItems.includes(item) && !hiddenItems.includes(item)
)
)
return (
<DragDropContext
onDragEnd={(e) => {
console.log('drag end', e)
const { destination, source, draggableId } = e
if (!destination) return
const item = itemsById[draggableId]
const newHomeSections = {
visible: visibleItems.map((item) => item.id),
hidden: hiddenItems.map((item) => item.id),
}
const sourceSection = source.droppableId as 'visible' | 'hidden'
newHomeSections[sourceSection].splice(source.index, 1)
const destSection = destination.droppableId as 'visible' | 'hidden'
newHomeSections[destSection].splice(destination.index, 0, item.id)
setHomeSections(newHomeSections)
}}
>
<Row className="relative max-w-lg gap-4">
<DraggableList items={visibleItems} title="Visible" />
<DraggableList items={hiddenItems} title="Hidden" />
</Row>
</DragDropContext>
)
}
function DraggableList(props: {
title: string
items: { id: string; label: string }[]
}) {
const { title, items } = props
return (
<Droppable droppableId={title.toLowerCase()}>
{(provided, snapshot) => (
<Col
{...provided.droppableProps}
ref={provided.innerRef}
className={clsx(
'width-[220px] flex-1 items-start rounded bg-gray-50 p-2',
snapshot.isDraggingOver && 'bg-gray-100'
)}
>
<Subtitle text={title} className="mx-2 !my-2" />
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={provided.draggableProps.style}
className={clsx(
'flex flex-row items-center gap-4 rounded bg-gray-50 p-2',
snapshot.isDragging && 'z-[9000] bg-gray-300'
)}
>
<MenuIcon
className="h-5 w-5 flex-shrink-0 text-gray-500"
aria-hidden="true"
/>{' '}
{item.label}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</Col>
)}
</Droppable>
)
}

View File

@ -0,0 +1,52 @@
import { Contract } from 'common/contract'
import { range } from 'lodash'
import { Carousel } from 'web/components/carousel'
import { ContractCard } from 'web/components/contract/contract-card'
import { ShowTime } from 'web/components/contract/contract-details'
import { Col } from 'web/components/layout/col'
export function DoubleCarousel(props: {
contracts: Contract[]
seeMoreUrl?: string
showTime?: ShowTime
loadMore?: () => void
}) {
const { contracts, showTime, loadMore } = props
return (
<Carousel className="-mx-4 mt-2 sm:-mx-10" loadMore={loadMore}>
<div className="shrink-0 sm:w-6" />
{contracts.length >= 6
? range(0, Math.floor(contracts.length / 2)).map((col) => {
const i = col * 2
return (
<Col key={contracts[i].id}>
<ContractCard
contract={contracts[i]}
className="mb-2 w-96 shrink-0"
questionClass="line-clamp-3"
trackingPostfix=" tournament"
showTime={showTime}
/>
<ContractCard
contract={contracts[i + 1]}
className="mb-2 w-96 shrink-0"
questionClass="line-clamp-3"
trackingPostfix=" tournament"
showTime={showTime}
/>
</Col>
)
})
: contracts.map((c) => (
<ContractCard
key={c.id}
contract={c}
className="mb-2 max-h-[220px] w-96 shrink-0"
questionClass="line-clamp-3"
trackingPostfix=" tournament"
showTime={showTime}
/>
))}
</Carousel>
)
}

View File

@ -0,0 +1,204 @@
import React, { useState } from 'react'
import Router from 'next/router'
import { PencilIcon, PlusSmIcon } from '@heroicons/react/solid'
import { Page } from 'web/components/page'
import { Col } from 'web/components/layout/col'
import { ContractSearch, SORTS } from 'web/components/contract-search'
import { User } from 'common/user'
import { getUserAndPrivateUser, updateUser } from 'web/lib/firebase/users'
import { useTracking } from 'web/hooks/use-tracking'
import { track } from 'web/lib/service/analytics'
import { authenticateOnServer } from 'web/lib/firebase/server-auth'
import { useSaveReferral } from 'web/hooks/use-save-referral'
import { GetServerSideProps } from 'next'
import { Sort } from 'web/components/contract-search'
import { Group } from 'common/group'
import { LoadingIndicator } from 'web/components/loading-indicator'
import { GroupLinkItem } from '../../groups'
import { SiteLink } from 'web/components/site-link'
import { useUser } from 'web/hooks/use-user'
import { useMemberGroups } from 'web/hooks/use-group'
import { DoubleCarousel } from './_double-carousel'
import clsx from 'clsx'
import { Button } from 'web/components/button'
import { ArrangeHome } from './_arrange-home'
import { Title } from 'web/components/title'
import { Row } from 'web/components/layout/row'
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const creds = await authenticateOnServer(ctx)
const auth = creds ? await getUserAndPrivateUser(creds.user.uid) : null
return { props: { auth } }
}
const Home = (props: { auth: { user: User } | null }) => {
const user = useUser() ?? props.auth?.user ?? null
useTracking('view home')
useSaveReferral()
const memberGroups = useMemberGroups(user?.id) ?? []
const [homeSections, setHomeSections] = useState(
user?.homeSections ?? { visible: [], hidden: [] }
)
const updateHomeSections = (newHomeSections: {
visible: string[]
hidden: string[]
}) => {
if (!user) return
updateUser(user.id, { homeSections: newHomeSections })
setHomeSections(newHomeSections)
}
const [isEditing, setIsEditing] = useState(false)
return (
<Page>
<Col className="pm:mx-10 gap-4 px-4 pb-12 xl:w-[125%]">
<Row className={'w-full items-center justify-between'}>
<Title text={isEditing ? 'Edit your home page' : 'Home'} />
<EditDoneButton isEditing={isEditing} setIsEditing={setIsEditing} />
</Row>
{isEditing ? (
<>
<ArrangeHome
user={user}
homeSections={homeSections}
setHomeSections={updateHomeSections}
/>
</>
) : (
homeSections.visible.map((id) => {
const sort = SORTS.find((sort) => sort.value === id)
if (sort)
return (
<SearchSection
label={sort.label}
sort={sort.value}
user={user}
/>
)
const group = memberGroups.find((g) => g.id === id)
if (group) return <GroupSection group={group} user={user} />
return null
})
)}
</Col>
<button
type="button"
className="fixed bottom-[70px] right-3 z-20 inline-flex items-center rounded-full border border-transparent bg-indigo-600 p-3 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 lg:hidden"
onClick={() => {
Router.push('/create')
track('mobile create button')
}}
>
<PlusSmIcon className="h-8 w-8" aria-hidden="true" />
</button>
</Page>
)
}
function SearchSection(props: {
label: string
user: User | null
sort: Sort
}) {
const { label, user, sort } = props
const href = `/home?s=${sort}`
return (
<Col>
<SiteLink className="mb-2 text-xl" href={href}>
{label}
</SiteLink>
<ContractSearch
user={user}
defaultSort={sort}
noControls
// persistPrefix={`experimental-home-${sort}`}
renderContracts={(contracts, loadMore) =>
contracts ? (
<DoubleCarousel
contracts={contracts}
seeMoreUrl={href}
showTime={
sort === 'close-date' || sort === 'resolve-date'
? sort
: undefined
}
loadMore={loadMore}
/>
) : (
<LoadingIndicator />
)
}
/>
</Col>
)
}
function GroupSection(props: { group: Group; user: User | null }) {
const { group, user } = props
return (
<Col>
<GroupLinkItem className="mb-2 text-xl" group={group} />
<ContractSearch
user={user}
defaultSort={'score'}
additionalFilter={{ groupSlug: group.slug }}
noControls
// persistPrefix={`experimental-home-${group.slug}`}
renderContracts={(contracts, loadMore) =>
contracts ? (
contracts.length == 0 ? (
<div className="m-2 text-gray-500">No open markets</div>
) : (
<DoubleCarousel
contracts={contracts}
seeMoreUrl={`/group/${group.slug}`}
loadMore={loadMore}
/>
)
) : (
<LoadingIndicator />
)
}
/>
</Col>
)
}
function EditDoneButton(props: {
isEditing: boolean
setIsEditing: (isEditing: boolean) => void
className?: string
}) {
const { isEditing, setIsEditing, className } = props
return (
<Button
size="lg"
color={isEditing ? 'blue' : 'gray-white'}
className={clsx(className, 'flex')}
onClick={() => {
setIsEditing(!isEditing)
}}
>
{!isEditing && (
<PencilIcon className={clsx('mr-2 h-[24px] w-5')} aria-hidden="true" />
)}
{isEditing ? 'Done' : 'Edit'}
</Button>
)
}
export default Home

View File

@ -1310,6 +1310,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.12.7", "@babel/template@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
@ -3316,6 +3323,14 @@
resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.1.tgz#64c54407b30da359763e14877f5702b8ae85d61c"
integrity sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/html-minifier-terser@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
@ -3428,6 +3443,13 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react-beautiful-dnd@13.1.2":
version "13.1.2"
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.2.tgz#510405abb09f493afdfd898bf83995dc6385c130"
integrity sha512-+OvPkB8CdE/bGdXKyIhc/Lm2U7UAYCCJgsqmopFmh9gbAudmslkI8eOrPDjg4JhwSE6wytz4a3/wRjKtovHVJg==
dependencies:
"@types/react" "*"
"@types/react-dom@17.0.2":
version "17.0.2"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.2.tgz#35654cf6c49ae162d5bc90843d5437dc38008d43"
@ -3435,6 +3457,16 @@
dependencies:
"@types/react" "*"
"@types/react-redux@^7.1.20":
version "7.1.24"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0"
integrity sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-router-config@*":
version "5.0.6"
resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451"
@ -4992,6 +5024,13 @@ crypto-random-string@^2.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
css-box-model@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
dependencies:
tiny-invariant "^1.0.6"
css-declaration-sorter@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02"
@ -7071,7 +7110,7 @@ hogan.js@^3.0.2:
mkdirp "0.3.0"
nopt "1.0.10"
hoist-non-react-statics@^3.1.0:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -8377,6 +8416,11 @@ memfs@^3.1.2, memfs@^3.4.3:
dependencies:
fs-monkey "1.0.3"
memoize-one@^5.1.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@ -9851,6 +9895,11 @@ quick-lru@^5.1.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
raf-schd@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
raf@^3.1.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@ -9905,6 +9954,19 @@ react-base16-styling@^0.6.0:
lodash.flow "^3.3.0"
pure-color "^1.2.0"
react-beautiful-dnd@13.1.1:
version "13.1.1"
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==
dependencies:
"@babel/runtime" "^7.9.2"
css-box-model "^1.2.0"
memoize-one "^5.1.1"
raf-schd "^4.0.2"
react-redux "^7.2.0"
redux "^4.0.4"
use-memo-one "^1.1.1"
react-confetti@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.0.1.tgz#d4f57b5a021dd908a6243b8f63b6009b00818d10"
@ -10012,6 +10074,11 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-json-view@^1.21.3:
version "1.21.3"
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475"
@ -10057,6 +10124,18 @@ react-query@3.39.0:
broadcast-channel "^3.4.1"
match-sorter "^6.0.2"
react-redux@^7.2.0:
version "7.2.8"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
integrity sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==
dependencies:
"@babel/runtime" "^7.15.4"
"@types/react-redux" "^7.1.20"
hoist-non-react-statics "^3.3.2"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^17.0.2"
react-router-config@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988"
@ -10208,6 +10287,13 @@ recursive-readdir@^2.2.2:
dependencies:
minimatch "3.0.4"
redux@^4.0.0, redux@^4.0.4:
version "4.2.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==
dependencies:
"@babel/runtime" "^7.9.2"
regenerate-unicode-properties@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
@ -11306,7 +11392,7 @@ thunky@^1.0.2:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
tiny-invariant@^1.0.2:
tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
@ -11740,6 +11826,11 @@ use-latest@^1.2.1:
dependencies:
use-isomorphic-layout-effect "^1.1.1"
use-memo-one@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
use-sync-external-store@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"