import { InfoCircleOutlined } from '@ant-design/icons' ; import { t , Trans } from '@lingui/macro' ; import { Button , Checkbox , Select , Space , Tooltip } from 'antd' ; import Search from 'antd/lib/input/Search' ; import FeedbackFormLink from 'components/shared/FeedbackFormLink' ; import Loading from 'components/shared/Loading' ; import ProjectsGrid from 'components/shared/ProjectsGrid' ; import { ThemeContext } from 'contexts/themeContext' ; import { useInfiniteProjectsQuery , useProjectsSearch } from 'hooks/Projects' ; import { ProjectState } from 'models/project-visibility' ; import { TerminalVersion } from 'models/terminal-version' ; import { useContext , useEffect , useMemo , useRef , useState } from 'react' ; import { layouts } from 'constants/styles/layouts' ; type OrderByOption = 'createdAt' | 'totalPaid' ; const pageSize = 20 ; export default function Projects ( ) { const [ searchText , setSearchText ] = useState < string > ( ) ; const [ selectedTab , setSelectedTab ] = useState < ProjectState > ( 'active' ) ; const [ orderBy , setOrderBy ] = useState < OrderByOption > ( 'totalPaid' ) ; const [ includeV1 , setIncludeV1 ] = useState < boolean > ( true ) ; const [ includeV1_1 , setIncludeV1_1 ] = useState < boolean > ( true ) ; const loadMoreContainerRef = useRef < HTMLDivElement > ( null ) ; const { theme : { colors } , } = useContext ( ThemeContext ) ; const terminalVersion = useMemo ( ( ) => { if ( includeV1 && ! includeV1_1 ) return '1' ; if ( ! includeV1 && includeV1_1 ) return '1.1' ; } , [ includeV1 , includeV1_1 ] ) ; const { data : pages , isLoading : isLoadingProjects , isFetchingNextPage , hasNextPage , fetchNextPage , } = useInfiniteProjectsQuery ( { orderBy , pageSize , orderDirection : 'desc' , filter : selectedTab , terminalVersion , } ) ; const { data : searchPages , isLoading : isLoadingSearch } = useProjectsSearch ( searchText ) ; useEffect ( ( ) => { if ( loadMoreContainerRef . current ) { const observer = new IntersectionObserver ( entries => { if ( entries . find ( e => e . isIntersecting ) && hasNextPage ) { fetchNextPage ( ) ; } } , { rootMargin : '200px' , } ) ; observer . observe ( loadMoreContainerRef . current ) ; return ( ) => observer . disconnect ( ) ; } } , [ fetchNextPage , hasNextPage ] ) ; const isLoading = isLoadingProjects || isLoadingSearch ; const concatenatedPages = searchText ?. length ? searchPages : pages ?. pages ?. reduce ( ( prev , group ) => [ ... prev , ... group ] , [ ] ) ; const tab = ( tab ) => ( < div style = { { textTransform : 'uppercase' , cursor : 'pointer' , borderBottom : '2px solid transparent' , paddingBottom : 6 , ... ( tab === selectedTab ? { color : colors . text . primary , fontWeight : 500 , borderColor : colors . text . primary , } : { color : colors . text . secondary , borderColor : 'transparent' , } ) , } } onClick = { ( ) => setSelectedTab ( tab ) } > { tab } < /div>); return ( < div style = { { ... layouts . maxWidth } } > < div style = { { marginBottom : 40 } } > < div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'baseline' , } } > < h1 > < Trans > Projects on Juicebox < /Trans> < /h1> < a href = "/#/create" > < Button > < Trans > New project < /Trans> < /Button> < /a> < /div> < div > < p style = { { maxWidth : 800 , marginBottom : 20 } } > < Trans > < InfoCircleOutlined /> The Juicebox protocol is open to anyone, and project configurations can vary widely . There are risks associated with interacting with all projects on the protocol . Projects built on the protocol are not endorsed or vetted by JuiceboxDAO , so you should do your own research and understand the risks before committing your funds . < /Trans> < /p> < Search autoFocus style = { { flex : 1 , marginBottom : 20 , marginRight : 20 } } prefix = "@" placeholder = { t `Search projects by handle` } onSearch = { val => setSearchText ( val ) } allowClear / > < /div> < div hidden = { ! ! searchText } style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'baseline' , flexWrap : 'wrap' , maxWidth : '100vw' , } } > < div style = { { height : 40 , marginTop : 10 , marginBottom : 10 } } > < Space direction = "horizontal" size = "large" > { tab ( 'active' ) } { tab ( 'archived' ) } < /Space> < /div> < div style = { { alignItems : 'baseline' , whiteSpace : 'pre' , flexWrap : 'wrap' , maxWidth : '100vw' , } } > < Space direction = "horizontal" size = "middle" style = { { marginRight : 20 , marginTop : 10 , marginBottom : 10 } } > < div > < Checkbox checked = { includeV1 } onChange = { ( ) => setIncludeV1 ( ! includeV1 ) } />{' '} < Trans > V1 < /Trans> < /div> < div > < Checkbox checked = { includeV1_1 } onChange = { ( ) => setIncludeV1_1 ( ! includeV1_1 ) } />{' '} < Trans > V1 .1 < /Trans> < /div> < /Space> < Select value = { orderBy } onChange = { setOrderBy } style = { { width : 180 , marginTop : 10 , marginBottom : 10 , } } > < Select . Option value = "totalPaid" > < Trans > Volume < /Trans> < /Select.Option> < Select . Option value = "createdAt" > < Trans > Created < /Trans> < /Select.Option> < /Select> < /div> < /div> { selectedTab === 'archived' && ( < p style = { { marginBottom : 40 , maxWidth : 800 } } > < Trans > < InfoCircleOutlined /> Archived projects have not been modified or deleted on the blockchain , and can still be interacted with directly through the Juicebox contracts . < /Trans> < Tooltip title = { t `If you have a project you'd like to archive, let the Juicebox team know in Discord.` } > < span style = { { color : colors . text . action . primary , fontWeight : 500 , cursor : 'default' , } } > { ' ' } < Trans > How do I archive a project ? < /Trans> < /span> < /Tooltip> < /p>)} < /div> { concatenatedPages && < ProjectsGrid projects = { concatenatedPages } />} { ( isLoading || isFetchingNextPage ) && < Loading />} { } < div ref = { loadMoreContainerRef } /> { hasNextPage && ! isFetchingNextPage && ( concatenatedPages ?. length || 0 ) > pageSize ? ( < div role = "button" style = { { textAlign : 'center' , color : colors . text . secondary , cursor : 'pointer' , padding : 20 , } } onClick = { ( ) => fetchNextPage ( ) } > < Trans > Load more < /Trans> < /div>) : (!isLoadingSearch && ! isLoadingProjects && ( < div style = { { textAlign : 'center' , color : colors . text . disabled , padding : 20 , } } > { concatenatedPages ?. length } { ' ' } { concatenatedPages ?. length === 1 ? 'project' : 'projects' } { searchText ? ` matching "${ searchText }"` : '' } < /div>))} < FeedbackFormLink / > < /div>); }