Rearrange home sections. Load more in carousel.
This commit is contained in:
parent
7dddff52b8
commit
849402ed70
|
@ -34,6 +34,7 @@ export type User = {
|
||||||
followerCountCached: number
|
followerCountCached: number
|
||||||
|
|
||||||
followedCategories?: string[]
|
followedCategories?: string[]
|
||||||
|
homeSections?: { visible: string[]; hidden: string[] }
|
||||||
|
|
||||||
referredByUserId?: string
|
referredByUserId?: string
|
||||||
referredByContractId?: string
|
referredByContractId?: string
|
||||||
|
|
|
@ -3,9 +3,14 @@ import clsx from 'clsx'
|
||||||
import { throttle } from 'lodash'
|
import { throttle } from 'lodash'
|
||||||
import { ReactNode, useRef, useState, useEffect } from 'react'
|
import { ReactNode, useRef, useState, useEffect } from 'react'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
import { VisibilityObserver } from 'web/components/visibility-observer'
|
||||||
|
|
||||||
export function Carousel(props: { children: ReactNode; className?: string }) {
|
export function Carousel(props: {
|
||||||
const { children, className } = props
|
children: ReactNode
|
||||||
|
loadMore?: () => void
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
const { children, loadMore, className } = props
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
@ -38,6 +43,13 @@ export function Carousel(props: { children: ReactNode; className?: string }) {
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
|
{loadMore && (
|
||||||
|
<VisibilityObserver
|
||||||
|
className="relative -left-96"
|
||||||
|
onVisibilityUpdated={(visible) => visible && loadMore()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
{!atFront && (
|
{!atFront && (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -38,7 +38,7 @@ const searchClient = algoliasearch(
|
||||||
const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
|
const indexPrefix = ENV === 'DEV' ? 'dev-' : ''
|
||||||
const searchIndexName = ENV === 'DEV' ? 'dev-contracts' : 'contractsIndex'
|
const searchIndexName = ENV === 'DEV' ? 'dev-contracts' : 'contractsIndex'
|
||||||
|
|
||||||
const SORTS = [
|
export const SORTS = [
|
||||||
{ label: 'Newest', value: 'newest' },
|
{ label: 'Newest', value: 'newest' },
|
||||||
{ label: 'Trending', value: 'score' },
|
{ label: 'Trending', value: 'score' },
|
||||||
{ label: 'Most traded', value: 'most-traded' },
|
{ label: 'Most traded', value: 'most-traded' },
|
||||||
|
@ -83,9 +83,11 @@ export function ContractSearch(props: {
|
||||||
persistPrefix?: string
|
persistPrefix?: string
|
||||||
useQueryUrlParam?: boolean
|
useQueryUrlParam?: boolean
|
||||||
isWholePage?: boolean
|
isWholePage?: boolean
|
||||||
maxItems?: number
|
|
||||||
noControls?: boolean
|
noControls?: boolean
|
||||||
renderContracts?: (contracts: Contract[] | undefined) => ReactNode
|
renderContracts?: (
|
||||||
|
contracts: Contract[] | undefined,
|
||||||
|
loadMore: () => void
|
||||||
|
) => ReactNode
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
|
@ -100,7 +102,6 @@ export function ContractSearch(props: {
|
||||||
persistPrefix,
|
persistPrefix,
|
||||||
useQueryUrlParam,
|
useQueryUrlParam,
|
||||||
isWholePage,
|
isWholePage,
|
||||||
maxItems,
|
|
||||||
noControls,
|
noControls,
|
||||||
renderContracts,
|
renderContracts,
|
||||||
} = props
|
} = props
|
||||||
|
@ -184,8 +185,7 @@ export function ContractSearch(props: {
|
||||||
const contracts = state.pages
|
const contracts = state.pages
|
||||||
.flat()
|
.flat()
|
||||||
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
|
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
|
||||||
const renderedContracts =
|
const renderedContracts = state.pages.length === 0 ? undefined : contracts
|
||||||
state.pages.length === 0 ? undefined : contracts.slice(0, maxItems)
|
|
||||||
|
|
||||||
if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
|
if (IS_PRIVATE_MANIFOLD || process.env.NEXT_PUBLIC_FIREBASE_EMULATE) {
|
||||||
return <ContractSearchFirestore additionalFilter={additionalFilter} />
|
return <ContractSearchFirestore additionalFilter={additionalFilter} />
|
||||||
|
@ -206,7 +206,7 @@ export function ContractSearch(props: {
|
||||||
noControls={noControls}
|
noControls={noControls}
|
||||||
/>
|
/>
|
||||||
{renderContracts ? (
|
{renderContracts ? (
|
||||||
renderContracts(renderedContracts)
|
renderContracts(renderedContracts, performQuery)
|
||||||
) : (
|
) : (
|
||||||
<ContractsGrid
|
<ContractsGrid
|
||||||
contracts={renderedContracts}
|
contracts={renderedContracts}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"next": "12.2.5",
|
"next": "12.2.5",
|
||||||
"node-fetch": "3.2.4",
|
"node-fetch": "3.2.4",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
|
"react-beautiful-dnd": "13.1.1",
|
||||||
"react-confetti": "6.0.1",
|
"react-confetti": "6.0.1",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-expanding-textarea": "2.3.5",
|
"react-expanding-textarea": "2.3.5",
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
"@types/lodash": "4.14.178",
|
"@types/lodash": "4.14.178",
|
||||||
"@types/node": "16.11.11",
|
"@types/node": "16.11.11",
|
||||||
"@types/react": "17.0.43",
|
"@types/react": "17.0.43",
|
||||||
|
"@types/react-beautiful-dnd": "13.1.2",
|
||||||
"@types/react-dom": "17.0.2",
|
"@types/react-dom": "17.0.2",
|
||||||
"@types/string-similarity": "^4.0.0",
|
"@types/string-similarity": "^4.0.0",
|
||||||
"autoprefixer": "10.2.6",
|
"autoprefixer": "10.2.6",
|
||||||
|
|
|
@ -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
|
|
127
web/pages/experimental/home/_arrange-home.tsx
Normal file
127
web/pages/experimental/home/_arrange-home.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
52
web/pages/experimental/home/_double-carousel.tsx
Normal file
52
web/pages/experimental/home/_double-carousel.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
204
web/pages/experimental/home/index.tsx
Normal file
204
web/pages/experimental/home/index.tsx
Normal 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
|
95
yarn.lock
95
yarn.lock
|
@ -1310,6 +1310,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
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":
|
"@babel/template@^7.12.7", "@babel/template@^7.16.7":
|
||||||
version "7.16.7"
|
version "7.16.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.1.tgz#64c54407b30da359763e14877f5702b8ae85d61c"
|
||||||
integrity sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==
|
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":
|
"@types/html-minifier-terser@^6.0.0":
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
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":
|
"@types/react-dom@17.0.2":
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.2.tgz#35654cf6c49ae162d5bc90843d5437dc38008d43"
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.2.tgz#35654cf6c49ae162d5bc90843d5437dc38008d43"
|
||||||
|
@ -3435,6 +3457,16 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@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@*":
|
"@types/react-router-config@*":
|
||||||
version "5.0.6"
|
version "5.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451"
|
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"
|
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
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:
|
css-declaration-sorter@^6.2.2:
|
||||||
version "6.2.2"
|
version "6.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02"
|
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"
|
mkdirp "0.3.0"
|
||||||
nopt "1.0.10"
|
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"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
|
@ -8377,6 +8416,11 @@ memfs@^3.1.2, memfs@^3.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
fs-monkey "1.0.3"
|
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:
|
merge-descriptors@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
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"
|
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
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:
|
raf@^3.1.0:
|
||||||
version "3.4.1"
|
version "3.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
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"
|
lodash.flow "^3.3.0"
|
||||||
pure-color "^1.2.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:
|
react-confetti@6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.0.1.tgz#d4f57b5a021dd908a6243b8f63b6009b00818d10"
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
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:
|
react-json-view@^1.21.3:
|
||||||
version "1.21.3"
|
version "1.21.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475"
|
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"
|
broadcast-channel "^3.4.1"
|
||||||
match-sorter "^6.0.2"
|
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:
|
react-router-config@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988"
|
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:
|
dependencies:
|
||||||
minimatch "3.0.4"
|
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:
|
regenerate-unicode-properties@^10.0.1:
|
||||||
version "10.0.1"
|
version "10.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
|
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"
|
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
|
||||||
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
|
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"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
|
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
|
||||||
integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
|
integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
|
||||||
|
@ -11740,6 +11826,11 @@ use-latest@^1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
use-isomorphic-layout-effect "^1.1.1"
|
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:
|
use-sync-external-store@1.2.0:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user