From 65998c9a10700568bdf5aef0fe0e78ef0fe8e28b Mon Sep 17 00:00:00 2001 From: michaelvic123 Date: Mon, 1 Jun 2026 11:09:01 +0100 Subject: [PATCH] feat: Persist creator list filters and sort in URL params - Add URL search params to persist search query (q) and sort option (sort) - Sync filter state to URL without page reload - Restore state from URL on navigation or shared links - Fix ESLint exhaustive dependencies warning using refs --- src/pages/LandingPage.tsx | 68 +++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 80d8066..dc89a37 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -1,5 +1,6 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { LayoutGroup, motion } from 'framer-motion'; +import { useSearchParams } from 'react-router'; import { courseService, type Course } from '@/services/course.service'; import SkipToContent from '@/components/common/SkipToContent'; import { cn } from '@/lib/utils'; @@ -253,7 +254,13 @@ function LandingPage() { const { isMismatch: isNetworkMismatch } = useNetworkMismatch(); const [isLoading, setIsLoading] = useState(true); const [isFilterLoading, setIsFilterLoading] = useState(false); - const [searchQuery, setSearchQuery] = useState(''); + const [searchParams, setSearchParams] = useSearchParams(); + const [searchQuery, setSearchQuery] = useState(() => { + const q = searchParams.get('q'); + return q ?? ''; + }); + const searchQueryRef = useRef(searchQuery); + const sortOptionRef = useRef('featured'); const [activeProfileTab, setActiveProfileTab] = useState(() => { if (typeof window === 'undefined') return 'overview'; const PROFILE_TABS = ['overview', 'creations', 'collectors', 'activity']; @@ -268,11 +275,21 @@ function LandingPage() { const tradeFeeEstimateProvider = useEthersProvider(); const prefersReducedMotion = usePrefersReducedMotion(); const [sortOption, setSortOption] = useState(() => { - if (typeof window === 'undefined') return 'featured'; - const saved = window.localStorage.getItem( - CREATOR_SORT_KEY - ) as SortOption | null; - return saved ?? 'featured'; + const sort = searchParams.get('sort') as SortOption | null; + if (sort && ['featured', 'price-asc', 'price-desc', 'supply-desc'].includes(sort)) { + sortOptionRef.current = sort; + return sort; + } + if (typeof window !== 'undefined') { + const saved = window.localStorage.getItem( + CREATOR_SORT_KEY + ) as SortOption | null; + if (saved) { + sortOptionRef.current = saved; + return saved; + } + } + return 'featured'; }); const [fetchRetryAttempt, setFetchRetryAttempt] = useState(0); const [fetchRequestId, setFetchRequestId] = useState(0); @@ -292,6 +309,15 @@ function LandingPage() { }); const pendingScrollRestoreRef = useRef(null); + // Keep refs in sync with state + useEffect(() => { + searchQueryRef.current = searchQuery; + }, [searchQuery]); + + useEffect(() => { + sortOptionRef.current = sortOption; + }, [sortOption]); + // Use scroll preservation for profile tabs useScrollPreservation(activeProfileTab, { storageKey: 'accesslayer.profile-tab-scroll', @@ -311,6 +337,36 @@ function LandingPage() { } }, [sortOption]); + useEffect(() => { + const newParams = new URLSearchParams(searchParams); + if (searchQuery.trim()) { + newParams.set('q', searchQuery.trim()); + } else { + newParams.delete('q'); + } + if (sortOption !== 'featured') { + newParams.set('sort', sortOption); + } else { + newParams.delete('sort'); + } + setSearchParams(newParams, { replace: true }); + }, [searchQuery, sortOption, searchParams, setSearchParams]); + + useEffect(() => { + const q = searchParams.get('q'); + if (q !== null && q !== searchQueryRef.current) { + setSearchQuery(q); + } else if (q === null && searchQueryRef.current !== '') { + setSearchQuery(''); + } + const sort = searchParams.get('sort') as SortOption | null; + if (sort && ['featured', 'price-asc', 'price-desc', 'supply-desc'].includes(sort) && sort !== sortOptionRef.current) { + setSortOption(sort); + } else if (sort === null && sortOptionRef.current !== 'featured') { + setSortOption('featured'); + } + }, [searchParams]); + useEffect(() => { if (typeof window === 'undefined') return; window.sessionStorage.setItem(CREATOR_PAGE_KEY, String(page));