diff --git a/frontend/package.json b/frontend/package.json
index bfee0ab..f653162 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,13 +12,14 @@
"@chakra-ui/icons": "2.0.19",
"@chakra-ui/next-js": "2.1.4",
"@chakra-ui/react": "2.7.1",
- "@directus/sdk": "10.3.3",
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
- "@tanstack/react-query": "4.29.19",
+ "@react-hookz/web": "23.1.0",
"clsx": "1.2.1",
"next": "13.4.8",
"react": "18.2.0",
- "react-dom": "18.2.0"
+ "react-dom": "18.2.0",
+ "react-icons": "4.10.1",
+ "swr": "2.2.0"
}
}
diff --git a/frontend/pages/[slug]/index.js b/frontend/pages/[slug]/index.js
index ec2c8e4..6809f7b 100644
--- a/frontend/pages/[slug]/index.js
+++ b/frontend/pages/[slug]/index.js
@@ -1,6 +1,7 @@
export const getStaticPaths = async () => {
const url = new URL(`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/items/pages`)
url.searchParams.append('fields[]', 'slug')
+ url.searchParams.append('limit', -1)
const res = await fetch(url.toString())
const { data: pages } = await res.json()
diff --git a/frontend/pages/_app.js b/frontend/pages/_app.js
index a037570..4dcb81b 100644
--- a/frontend/pages/_app.js
+++ b/frontend/pages/_app.js
@@ -1,18 +1,13 @@
import { ChakraProvider } from '@chakra-ui/react'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import Layout from '~/components/layout'
import theme from '~/src/theme'
-const queryClient = new QueryClient()
-
export default function MyApp({ Component, pageProps }) {
return (
-
-
-
-
-
-
-
+
+
+
+
+
)
}
diff --git a/frontend/pages/vendors/index.js b/frontend/pages/vendors/index.js
index 758568c..f195ca6 100644
--- a/frontend/pages/vendors/index.js
+++ b/frontend/pages/vendors/index.js
@@ -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 (
-
- Vendors
-
+
+
+ Vendors
+
+ {isValidating && }
+
+ } onClick={() => setLayout('GRID')} isDisabled={layout === 'GRID'} />
+ } onClick={() => setLayout('LIST')} isDisabled={layout === 'LIST'} />
+
+
-
- {isFetching &&
- new Array(PER_PAGE).fill(true).map((v, k) => (
-
-
-
-
-
-
- ))}
- {!isFetching &&
- vendors.map((v) => (
-
-
-
-
- {v.name}
-
-
-
- ))}
-
+ {error && (
+
+
+ There was an error processing your request
+
+ )}
-
+ {layout === 'GRID' && (
+
+ {isLoading &&
+ new Array(perPage).fill(true).map((v, k) => (
+
+
+
+
+
+
+
+
+ ))}
+ {!isLoading &&
+ vendors.map((v) => (
+
+
+
+
+
+ {v.name}
+
+
+ {v?.description?.substring(0, v?.description?.substring(0, 80).lastIndexOf(' '))} …
+
+
+
+
+ ))}
+
+ )}
+
+ {layout === 'LIST' && (
+ <>
+ {isLoading && (
+
+
+ {new Array(perPage).fill(true).map((v, k) => (
+
+ |
+
+ |
+
+
+ |
+
+
+ |
+
+ ))}
+
+
+ )}
+ {!isLoading && (
+
+
+ {vendors.map((v) => (
+
+
+ |
+
+ |
+ {v.name} |
+
+
+ {v?.description?.substring(0, v?.description?.substring(0, 80).lastIndexOf(' '))} …
+
+ |
+
+
+ ))}
+
+
+ )}
+ >
+ )}
+
+
+
+
+ Results per page:
+
+ {[12, 24, 48].map((n) => (
+
+ ))}
+
+
)
}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 4667115..0865876 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -14,18 +14,15 @@ dependencies:
'@chakra-ui/react':
specifier: 2.7.1
version: 2.7.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(framer-motion@6.5.1)(react-dom@18.2.0)(react@18.2.0)
- '@directus/sdk':
- specifier: 10.3.3
- version: 10.3.3
'@emotion/react':
specifier: 11.11.1
version: 11.11.1(react@18.2.0)
'@emotion/styled':
specifier: 11.11.0
version: 11.11.0(@emotion/react@11.11.1)(react@18.2.0)
- '@tanstack/react-query':
- specifier: 4.29.19
- version: 4.29.19(react-dom@18.2.0)(react@18.2.0)
+ '@react-hookz/web':
+ specifier: 23.1.0
+ version: 23.1.0(react-dom@18.2.0)(react@18.2.0)
clsx:
specifier: 1.2.1
version: 1.2.1
@@ -38,6 +35,12 @@ dependencies:
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
+ react-icons:
+ specifier: 4.10.1
+ version: 4.10.1(react@18.2.0)
+ swr:
+ specifier: 2.2.0
+ version: 2.2.0(react@18.2.0)
packages:
@@ -1203,14 +1206,6 @@ packages:
react: 18.2.0
dev: false
- /@directus/sdk@10.3.3:
- resolution: {integrity: sha512-58gw+QjkuIr0lJFRx5HwSp1ewAf7rjfV++eJqAmC13p7vif9wfJEcWcJwqXstYdvtJVUg+nB4O/CE0OBEtp5HQ==}
- dependencies:
- axios: 0.27.2
- transitivePeerDependencies:
- - debug
- dev: false
-
/@emotion/babel-plugin@11.11.0:
resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
dependencies:
@@ -1474,34 +1469,31 @@ packages:
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
dev: false
+ /@react-hookz/deep-equal@1.0.4:
+ resolution: {integrity: sha512-N56fTrAPUDz/R423pag+n6TXWbvlBZDtTehaGFjK0InmN+V2OFWLE/WmORhmn6Ce7dlwH5+tQN1LJFw3ngTJVg==}
+ dev: false
+
+ /@react-hookz/web@23.1.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-fvbURdsa1ukttbLR1ASE/XmqXP09vZ1PiCYppYeR1sNMzCrdkG0iBnjxniFSVjJ8gIw2fRs6nqMTbeBz2uAkuA==}
+ peerDependencies:
+ js-cookie: ^3.0.5
+ react: ^16.8 || ^17 || ^18
+ react-dom: ^16.8 || ^17 || ^18
+ peerDependenciesMeta:
+ js-cookie:
+ optional: true
+ dependencies:
+ '@react-hookz/deep-equal': 1.0.4
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@swc/helpers@0.5.1:
resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==}
dependencies:
tslib: 2.6.0
dev: false
- /@tanstack/query-core@4.29.19:
- resolution: {integrity: sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==}
- dev: false
-
- /@tanstack/react-query@4.29.19(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0
- react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
- react-native: '*'
- peerDependenciesMeta:
- react-dom:
- optional: true
- react-native:
- optional: true
- dependencies:
- '@tanstack/query-core': 4.29.19
- react: 18.2.0
- react-dom: 18.2.0(react@18.2.0)
- use-sync-external-store: 1.2.0(react@18.2.0)
- dev: false
-
/@types/lodash.mergewith@4.6.7:
resolution: {integrity: sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==}
dependencies:
@@ -1538,19 +1530,6 @@ packages:
tslib: 2.5.3
dev: false
- /asynckit@0.4.0:
- resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
- dev: false
-
- /axios@0.27.2:
- resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
- dependencies:
- follow-redirects: 1.15.2
- form-data: 4.0.0
- transitivePeerDependencies:
- - debug
- dev: false
-
/babel-plugin-macros@3.1.0:
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
engines: {node: '>=10', npm: '>=6'}
@@ -1608,13 +1587,6 @@ packages:
resolution: {integrity: sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==}
dev: false
- /combined-stream@1.0.8:
- resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
- engines: {node: '>= 0.8'}
- dependencies:
- delayed-stream: 1.0.0
- dev: false
-
/compute-scroll-into-view@1.0.20:
resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
dev: false
@@ -1650,11 +1622,6 @@ packages:
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
dev: false
- /delayed-stream@1.0.0:
- resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
- engines: {node: '>=0.4.0'}
- dev: false
-
/detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
dev: false
@@ -1686,25 +1653,6 @@ packages:
tslib: 2.5.3
dev: false
- /follow-redirects@1.15.2:
- resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
- engines: {node: '>=4.0'}
- peerDependencies:
- debug: '*'
- peerDependenciesMeta:
- debug:
- optional: true
- dev: false
-
- /form-data@4.0.0:
- resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
- engines: {node: '>= 6'}
- dependencies:
- asynckit: 0.4.0
- combined-stream: 1.0.8
- mime-types: 2.1.35
- dev: false
-
/framer-motion@6.5.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==}
peerDependencies:
@@ -1821,18 +1769,6 @@ packages:
js-tokens: 4.0.0
dev: false
- /mime-db@1.52.0:
- resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
- engines: {node: '>= 0.6'}
- dev: false
-
- /mime-types@2.1.35:
- resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
- engines: {node: '>= 0.6'}
- dependencies:
- mime-db: 1.52.0
- dev: false
-
/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -1984,6 +1920,14 @@ packages:
use-sidecar: 1.1.2(react@18.2.0)
dev: false
+ /react-icons@4.10.1(react@18.2.0):
+ resolution: {integrity: sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==}
+ peerDependencies:
+ react: '*'
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: false
@@ -2123,6 +2067,15 @@ packages:
engines: {node: '>= 0.4'}
dev: false
+ /swr@2.2.0(react@18.2.0):
+ resolution: {integrity: sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ use-sync-external-store: 1.2.0(react@18.2.0)
+ dev: false
+
/tiny-invariant@1.3.1:
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
dev: false
diff --git a/frontend/src/import/index.js b/frontend/src/import/index.js
deleted file mode 100644
index f4372db..0000000
--- a/frontend/src/import/index.js
+++ /dev/null
@@ -1,166 +0,0 @@
-import directus from '~/lib/directus'
-import all from '~/public/data.json'
-
-function sluggify(str = '') {
- return (
- str
- .toLocaleLowerCase()
- .normalize('NFKC')
- .trim()
- //
- .split('.')
- .join(' ')
- .split('(')
- .join('')
- .split(')')
- .join('')
- .split('|')
- .join(' ')
- .trim()
- .split(',')
- .join('')
- .split(' ')
- .join('-')
- .split('-/-')
- .join('-')
- .split(' &')
- .join('')
- .split('&')
- .join('and')
- .split('/')
- .join('-')
- .replace(/\-\-+/, '-')
- .trim()
- )
-}
-
-function sanitize(str = '') {
- return (
- str
- //
- .split(' &')
- .join('')
- .split(' / ')
- .join('/')
- .split('/')
- .join(' / ')
- .trim()
- )
-}
-
-async function importCategories() {
- all.results.map((one) => {
- one.raw.marketplaceexhibitorcategory?.map(async (cat) => {
- const name = sanitize(cat)
- const slug = sluggify(name)
- try {
- await directus.items('categories').createOne({
- slug,
- name,
- })
- } catch (error) {}
- })
- })
-}
-
-async function importSubcategories() {
- all.results.map((one) => {
- one.raw.marketplaceexhibitorsubcategory?.map(async (cat) => {
- const name = sanitize(cat)
- const slug = sluggify(name)
- try {
- await directus.items('categories').createOne({
- slug,
- name,
- })
- } catch (error) {}
- })
- })
-}
-
-async function importCategoryStructure() {
- const { data: categories } = await directus.items('categories').readByQuery({ limit: -1 })
-
- all.results.map((one) => {
- one.raw.marketplaceexhibitorcategorysubcategory?.map(async (cat) => {
- const [parentTitle, catTitle] = cat.split('|')
- const catSlug = sluggify(catTitle)
- const parentSlug = sluggify(parentTitle)
- if (parentSlug) {
- try {
- const category = categories.find((c) => catSlug === c.slug)
- const parent = categories.find((c) => parentSlug === c.slug)
- await directus.items('categories').updateOne(category.id, {
- parent_id: parent.id,
- })
- } catch (error) {}
- }
- })
- })
-}
-
-async function importVendors() {
- const { data: categories } = await directus.items('categories').readByQuery({ limit: -1 })
-
- return Promise.all(
- all.results.map(async (one) => {
- const name = sanitize(one.raw.name)
- let slug = sluggify(name)
- const {
- data: [existingVendor],
- } = await directus.items('vendors').readByQuery({
- fields: ['slug'],
- limit: -1,
- filter: {
- slug: {
- _eq: slug,
- },
- },
- })
- let i = 1
- while (existingVendor?.slug === slug) {
- slug = sluggify(name) + '-' + i++
- }
-
- const categoryIds = new Set(
- one.raw.marketplaceexhibitorcategory
- ?.map((t) => sluggify(sanitize(t)))
- .map((s) => categories.filter((t) => !t.parent_id).find((t) => s === t.slug)?.id)
- .filter((s) => !!s)
- )
-
- const vendor = {
- slug,
- name,
- status: 'published',
- description: one.raw?.description,
- address_line_1: one.raw?.addressline1,
- address_line_2: one.raw?.addressline2,
- city: one.raw?.city,
- state: one.raw?.state,
- country: one.raw?.country,
- website: one.raw?.website,
- }
- try {
- const res = await directus.items('vendors').createOne(vendor)
- categoryIds.forEach(async (cid) => {
- await directus.items('vendors_categories').createOne({ vendors_id: res.id, categories_id: cid })
- })
- } catch (error) {}
- })
- )
-}
-
-export default function Home() {
- // importCategories()
- // importSubcategories()
- // importCategoryStructure()
- // importVendors()
-
- return (
-
- Import {new Date().toISOString()}
- {/* {JSON.stringify(res, null, 2)} */}
-
- )
-}