SWR, improved vendors list
This commit is contained in:
219
frontend/pages/vendors/index.js
vendored
219
frontend/pages/vendors/index.js
vendored
@@ -1,7 +1,32 @@
|
||||
import { Link } from '@chakra-ui/next-js'
|
||||
import { Card, CardBody, Heading, Image, LinkBox, LinkOverlay, SimpleGrid, Skeleton } from '@chakra-ui/react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Card,
|
||||
CardBody,
|
||||
Flex,
|
||||
HStack,
|
||||
Heading,
|
||||
IconButton,
|
||||
Image,
|
||||
LinkBox,
|
||||
LinkOverlay,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
Spinner,
|
||||
Table,
|
||||
Tbody,
|
||||
Td,
|
||||
Text,
|
||||
Tr,
|
||||
} from '@chakra-ui/react'
|
||||
import { useLocalStorageValue } from '@react-hookz/web'
|
||||
import { useRouter } from 'next/router'
|
||||
import { TbLayoutGrid, TbLayoutList } from 'react-icons/tb'
|
||||
import useSWR from 'swr'
|
||||
import Pagination from '~/components/pagination'
|
||||
|
||||
const PER_PAGE = 12
|
||||
@@ -12,65 +37,157 @@ export default function VendorsPage() {
|
||||
query: { page = 1 },
|
||||
} = router
|
||||
|
||||
const { data, isFetching } = useQuery({
|
||||
queryKey: ['vendors', page],
|
||||
queryFn: async (...props2) => {
|
||||
const url = new URL(`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/items/vendors`)
|
||||
url.searchParams.append('fields[]', '*')
|
||||
url.searchParams.append('limit', PER_PAGE)
|
||||
url.searchParams.append('page', page)
|
||||
url.searchParams.append('sort', 'name')
|
||||
url.searchParams.append('meta[]', 'filter_count')
|
||||
const { value: perPage, set: setPerPage } = useLocalStorageValue('perPage', {
|
||||
defaultValue: PER_PAGE,
|
||||
initializeWithValue: false,
|
||||
})
|
||||
|
||||
const res = await fetch(url.toString())
|
||||
if (!res.ok) {
|
||||
throw new Error('Oops')
|
||||
}
|
||||
const { value: layout, set: setLayout } = useLocalStorageValue('layout', {
|
||||
defaultValue: 'GRID',
|
||||
initializeWithValue: false,
|
||||
})
|
||||
|
||||
return res.json()
|
||||
},
|
||||
keepPreviousData: true,
|
||||
cacheTime: 1000 * 60 * 30,
|
||||
staleTime: 1000 * 60 * 30,
|
||||
const { data, error, isLoading, isValidating } = useSWR(['vendors', page, perPage], async () => {
|
||||
// await new Promise((r) => setTimeout(r, 2000))
|
||||
const url = new URL(`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/items/vendors`)
|
||||
url.searchParams.append('fields[]', '*')
|
||||
url.searchParams.append('limit', perPage)
|
||||
url.searchParams.append('page', page)
|
||||
url.searchParams.append('sort', 'name')
|
||||
url.searchParams.append('meta[]', 'filter_count')
|
||||
const res = await fetch(url.toString())
|
||||
if (!res.ok) {
|
||||
throw new Error('Oops')
|
||||
}
|
||||
return res.json()
|
||||
})
|
||||
|
||||
const { meta, data: vendors } = data || { meta: {}, data: [] }
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Heading size="lg" my={8}>
|
||||
Vendors
|
||||
</Heading>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<Heading size="lg" my={8}>
|
||||
Vendors
|
||||
</Heading>
|
||||
<Box>{isValidating && <Spinner />}</Box>
|
||||
<ButtonGroup isAttached>
|
||||
<IconButton icon={<TbLayoutGrid />} onClick={() => setLayout('GRID')} isDisabled={layout === 'GRID'} />
|
||||
<IconButton icon={<TbLayoutList />} onClick={() => setLayout('LIST')} isDisabled={layout === 'LIST'} />
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
|
||||
<SimpleGrid columns={[1, 2, 3, 4]} spacing={3}>
|
||||
{isFetching &&
|
||||
new Array(PER_PAGE).fill(true).map((v, k) => (
|
||||
<Card key={k}>
|
||||
<Skeleton h="40" />
|
||||
<CardBody>
|
||||
<Skeleton h="4" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
))}
|
||||
{!isFetching &&
|
||||
vendors.map((v) => (
|
||||
<LinkBox as={Card} rounded={4} key={v.id}>
|
||||
<Image
|
||||
roundedTop={4}
|
||||
objectFit="cover"
|
||||
src="https://images.unsplash.com/photo-1531403009284-440f080d1e12?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80"
|
||||
alt={v.name}
|
||||
/>
|
||||
<CardBody>
|
||||
<LinkOverlay as={Link} href={`/vendors/${v.slug}`} prefetch={false}>
|
||||
{v.name}
|
||||
</LinkOverlay>
|
||||
</CardBody>
|
||||
</LinkBox>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
{error && (
|
||||
<Alert status="error" marginY={3}>
|
||||
<AlertIcon />
|
||||
There was an error processing your request
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Pagination page={Number(page)} itemsPerPage={PER_PAGE} totalItems={meta.filter_count} />
|
||||
{layout === 'GRID' && (
|
||||
<SimpleGrid columns={[1, 2, 3, 4]} spacing={3}>
|
||||
{isLoading &&
|
||||
new Array(perPage).fill(true).map((v, k) => (
|
||||
<Card key={k}>
|
||||
<Skeleton h="40" />
|
||||
<CardBody>
|
||||
<Skeleton h="6" mb="2" />
|
||||
<Skeleton h="4" mb="1" />
|
||||
<Skeleton h="4" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
))}
|
||||
{!isLoading &&
|
||||
vendors.map((v) => (
|
||||
<LinkBox as={Card} rounded={4} key={v.id}>
|
||||
<Image
|
||||
roundedTop={4}
|
||||
objectFit="cover"
|
||||
src={`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/assets/${v.logo}?key=logo-card`}
|
||||
alt={v.name}
|
||||
width="250"
|
||||
height="150"
|
||||
style={{ objectFit: 'contain', background: '#fff' }}
|
||||
/>
|
||||
<CardBody>
|
||||
<LinkOverlay as={Link} href={`/vendors/${v.slug}`} prefetch={false}>
|
||||
<Text mb="2" fontWeight="900">
|
||||
{v.name}
|
||||
</Text>
|
||||
<Text fontSize="sm">
|
||||
{v?.description?.substring(0, v?.description?.substring(0, 80).lastIndexOf(' '))} …
|
||||
</Text>
|
||||
</LinkOverlay>
|
||||
</CardBody>
|
||||
</LinkBox>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
||||
{layout === 'LIST' && (
|
||||
<>
|
||||
{isLoading && (
|
||||
<Table>
|
||||
<Tbody>
|
||||
{new Array(perPage).fill(true).map((v, k) => (
|
||||
<Tr key={k}>
|
||||
<Td w="10%">
|
||||
<Skeleton h="12" w="12" />
|
||||
</Td>
|
||||
<Td w="30%">
|
||||
<Skeleton h="6" />
|
||||
</Td>
|
||||
<Td w="60%">
|
||||
<Skeleton h="6" />
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
)}
|
||||
{!isLoading && (
|
||||
<Table>
|
||||
<Tbody>
|
||||
{vendors.map((v) => (
|
||||
<Link key={v.id} href={`/vendors/${v.slug}`} prefetch={false} display="contents">
|
||||
<Tr verticalAlign="middle">
|
||||
<Td>
|
||||
<Image
|
||||
rounded="md"
|
||||
src={`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/assets/${v.logo}?key=logo-card`}
|
||||
alt={v.name}
|
||||
width="12"
|
||||
height="12"
|
||||
style={{ objectFit: 'contain', background: '#fff' }}
|
||||
/>
|
||||
</Td>
|
||||
<Td>{v.name}</Td>
|
||||
<Td>
|
||||
<Text fontSize="sm">
|
||||
{v?.description?.substring(0, v?.description?.substring(0, 80).lastIndexOf(' '))} …
|
||||
</Text>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Link>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Pagination page={Number(page)} itemsPerPage={perPage} totalItems={meta.filter_count} />
|
||||
|
||||
<HStack>
|
||||
<Text>Results per page:</Text>
|
||||
<ButtonGroup isAttached>
|
||||
{[12, 24, 48].map((n) => (
|
||||
<Button key={n} onClick={() => setPerPage(n)} isDisabled={perPage === n}>
|
||||
{n}
|
||||
</Button>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
</HStack>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user