Replace native selects with NovaSelect
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { startTransition, useDeferredValue, useEffect, useRef, useState }
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import CategoryCard from '../components/category/CategoryCard'
|
||||
import Pagination from '../components/forum/Pagination'
|
||||
import NovaSelect from '../components/ui/NovaSelect'
|
||||
|
||||
const SORT_OPTIONS = [
|
||||
{ value: 'popular', label: 'Popular' },
|
||||
@@ -13,6 +14,26 @@ const PAGE_SIZE = 24
|
||||
|
||||
const numberFormatter = new Intl.NumberFormat()
|
||||
|
||||
function normalizeInitialData(initialData) {
|
||||
if (!initialData || typeof initialData !== 'object') {
|
||||
return {
|
||||
data: [],
|
||||
meta: { current_page: 1, last_page: 1, per_page: PAGE_SIZE, total: 0 },
|
||||
summary: { total_categories: 0, total_artworks: 0 },
|
||||
popular_categories: [],
|
||||
request: { query: '', sort: 'popular', page: 1 },
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: Array.isArray(initialData.data) ? initialData.data : [],
|
||||
meta: initialData.meta || { current_page: 1, last_page: 1, per_page: PAGE_SIZE, total: 0 },
|
||||
summary: initialData.summary || { total_categories: 0, total_artworks: 0 },
|
||||
popular_categories: Array.isArray(initialData.popular_categories) ? initialData.popular_categories : [],
|
||||
request: initialData.request || { query: '', sort: 'popular', page: 1 },
|
||||
}
|
||||
}
|
||||
|
||||
function LoadingGrid() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
||||
@@ -113,17 +134,21 @@ function syncQueryState({ page, sort, query }) {
|
||||
window.history.replaceState({}, '', url.toString())
|
||||
}
|
||||
|
||||
function CategoriesPage({ apiUrl = '/api/categories', pageTitle = 'Categories', pageDescription = '' }) {
|
||||
const [categories, setCategories] = useState([])
|
||||
const [popularCategories, setPopularCategories] = useState([])
|
||||
const [meta, setMeta] = useState({ current_page: 1, last_page: 1, per_page: PAGE_SIZE, total: 0 })
|
||||
const [summary, setSummary] = useState({ total_categories: 0, total_artworks: 0 })
|
||||
const [loading, setLoading] = useState(true)
|
||||
function CategoriesPage({ apiUrl = '/api/categories', pageTitle = 'Categories', pageDescription = '', initialData = null }) {
|
||||
const bootstrap = normalizeInitialData(initialData)
|
||||
const hasInitialData = initialData !== null
|
||||
const initialRequestRef = useRef(hasInitialData ? bootstrap.request : null)
|
||||
|
||||
const [categories, setCategories] = useState(() => (hasInitialData ? bootstrap.data : []))
|
||||
const [popularCategories, setPopularCategories] = useState(() => (hasInitialData ? bootstrap.popular_categories : []))
|
||||
const [meta, setMeta] = useState(() => (hasInitialData ? bootstrap.meta : { current_page: 1, last_page: 1, per_page: PAGE_SIZE, total: 0 }))
|
||||
const [summary, setSummary] = useState(() => (hasInitialData ? bootstrap.summary : { total_categories: 0, total_artworks: 0 }))
|
||||
const [loading, setLoading] = useState(() => !hasInitialData)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [error, setError] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState(() => getInitialSearchQuery())
|
||||
const [sort, setSort] = useState(() => getInitialSort())
|
||||
const [currentPage, setCurrentPage] = useState(() => getInitialPage())
|
||||
const [searchQuery, setSearchQuery] = useState(() => (hasInitialData ? (bootstrap.request.query || '') : getInitialSearchQuery()))
|
||||
const [sort, setSort] = useState(() => (hasInitialData ? (bootstrap.request.sort || 'popular') : getInitialSort()))
|
||||
const [currentPage, setCurrentPage] = useState(() => (hasInitialData ? (bootstrap.request.page || 1) : getInitialPage()))
|
||||
const deferredQuery = useDeferredValue(searchQuery)
|
||||
const sentinelRef = useRef(null)
|
||||
|
||||
@@ -199,6 +224,20 @@ function CategoriesPage({ apiUrl = '/api/categories', pageTitle = 'Categories',
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const initialRequest = initialRequestRef.current
|
||||
|
||||
if (initialRequest) {
|
||||
const sameQuery = (initialRequest.query || '') === deferredQuery
|
||||
const sameSort = (initialRequest.sort || 'popular') === sort
|
||||
const samePage = Number(initialRequest.page || 1) === currentPage
|
||||
|
||||
initialRequestRef.current = null
|
||||
|
||||
if (sameQuery && sameSort && samePage) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
|
||||
void loadCategories({
|
||||
@@ -334,24 +373,19 @@ function CategoriesPage({ apiUrl = '/api/categories', pageTitle = 'Categories',
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="block">
|
||||
<div className="block">
|
||||
<span className="mb-2 block text-xs font-semibold uppercase tracking-[0.2em] text-white/38">Sort by</span>
|
||||
<select
|
||||
<NovaSelect
|
||||
value={sort}
|
||||
onChange={(event) => {
|
||||
setSort(event.target.value)
|
||||
onChange={(value) => {
|
||||
setSort(value)
|
||||
setCurrentPage(1)
|
||||
}}
|
||||
aria-label="Sort categories"
|
||||
className="h-14 w-full rounded-2xl border border-white/10 bg-white/[0.04] px-4 text-sm text-white focus:border-orange-300/45 focus:outline-none focus:ring-2 focus:ring-orange-300/12"
|
||||
>
|
||||
{SORT_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value} className="bg-slate-950 text-white">
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
id="categories-sort"
|
||||
options={SORT_OPTIONS.map((option) => ({ value: option.value, label: option.label }))}
|
||||
searchable={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -438,6 +472,7 @@ function CategoriesPage({ apiUrl = '/api/categories', pageTitle = 'Categories',
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
const mountElement = document.getElementById('categories-page-root')
|
||||
|
||||
if (mountElement) {
|
||||
@@ -452,5 +487,6 @@ if (mountElement) {
|
||||
|
||||
createRoot(mountElement).render(<CategoriesPage {...props} />)
|
||||
}
|
||||
}
|
||||
|
||||
export default CategoriesPage
|
||||
|
||||
Reference in New Issue
Block a user