diff --git a/src/app/(public)/_components/search-form.tsx b/src/app/(public)/_components/search-form.tsx index cb52e32..71e5d06 100644 --- a/src/app/(public)/_components/search-form.tsx +++ b/src/app/(public)/_components/search-form.tsx @@ -1,25 +1,29 @@ 'use client'; -import { usePathname, useSearchParams, useRouter } from 'next/navigation'; +import { usePathname } from 'next/navigation'; import { useForm } from 'react-hook-form'; import { GoX } from 'react-icons/go'; +import { useQueryState } from 'nuqs'; interface FormValues { searchQuery: string; } export function SearchForm() { - const router = useRouter(); const pathname = usePathname(); - const searchParams = useSearchParams(); + const [searchQuery, setSearchQuery] = useQueryState('q', { + defaultValue: '', + parse: (value: string) => value, + serialize: (value: string) => value || '' + }); const { register, handleSubmit, reset, watch } = useForm({ defaultValues: { - searchQuery: searchParams.get('q') as string + searchQuery: searchQuery } }); - const searchQuery = watch('searchQuery'); + const queryValue = watch('searchQuery'); if (!pathname.startsWith('/repos')) { return null; @@ -28,12 +32,9 @@ export function SearchForm() { function onSubmit({ searchQuery }: FormValues) { if (!pathname.startsWith('/repos')) return; - const reposPathname = pathname as `/repos/${string}`; const trimmedQuery = searchQuery.trim(); if (trimmedQuery !== '') { - const sp = new URLSearchParams(searchParams); - sp.set('q', trimmedQuery); - router.push(`${reposPathname}?${sp.toString()}`); + void setSearchQuery(trimmedQuery); } } @@ -47,11 +48,14 @@ export function SearchForm() { type="text" {...register('searchQuery', { required: true })} /> - {searchQuery && searchQuery.trim() !== '' && ( + {queryValue && queryValue.trim() !== '' && ( diff --git a/src/app/(public)/repos/_components/pagination.tsx b/src/app/(public)/repos/_components/pagination.tsx index 5d105a0..07f3faa 100644 --- a/src/app/(public)/repos/_components/pagination.tsx +++ b/src/app/(public)/repos/_components/pagination.tsx @@ -2,33 +2,30 @@ import { Button } from '@/app/(public)/_components/button'; import { ArrowLeft, ArrowRight, Loader2 } from 'lucide-react'; -import { useRouter } from 'next/navigation'; import { useTransition } from 'react'; -import type { SearchParams } from '@/types'; +import { useQueryState } from 'nuqs'; const MAX_PER_PAGE = 21; interface PaginationProps { page: number; totalCount: number; - searchParams: SearchParams; } export function Pagination({ page, - totalCount, - searchParams + totalCount }: PaginationProps) { - const router = useRouter(); + const [, setPageParam] = useQueryState('p', { + defaultValue: '1', + parse: (value: string) => value, + serialize: (value: string) => value + }); const [isPending, startTransition] = useTransition(); function changePage(delta: number) { - const params = new URLSearchParams( - Object.entries(searchParams).map(([k, v]) => [k, String(v)]) - ); - params.set('p', String(page + delta)); - + const newPage = page + delta; startTransition(() => { - router.push(`?${params.toString()}`); + void setPageParam(String(newPage)); }); } diff --git a/src/app/(public)/repos/_components/sorter.tsx b/src/app/(public)/repos/_components/sorter.tsx index 545c35c..31f4718 100644 --- a/src/app/(public)/repos/_components/sorter.tsx +++ b/src/app/(public)/repos/_components/sorter.tsx @@ -4,8 +4,9 @@ import { Button } from '@/app/(public)/_components/button'; import { ArrowUpAZ, Code } from 'lucide-react'; import Link from 'next/link'; import languages from '@/assets/languages.json'; -import { usePathname, useSearchParams } from 'next/navigation'; +import { usePathname } from 'next/navigation'; import { sortByName } from '@/lib/utils'; +import { useQueryStates, useQueryState } from 'nuqs'; const { mainLanguages } = languages; @@ -23,88 +24,74 @@ enum SortTypes { type Pathname = '/repos' | `/repos/${string}`; export function Sorter() { - const searchParams = useSearchParams(); const pathname = usePathname() as Pathname; + const [sortField] = useQueryState('s', { defaultValue: '' }); + const [sortOrder] = useQueryState('o', { defaultValue: 'desc' }); + const [languages_] = useQueryState('l', { parse: (value: string) => value.split(',').filter(Boolean), serialize: (value: string[]) => value.join(',') }); const navigationItems = [ { name: 'Best match', - onSelect(sp: URLSearchParams) { - sp.delete('o'); - sp.delete('s'); - return sp; + onSelect(): { s: string | null; o: string | null } { + return { s: null, o: null }; } }, { name: 'Most stars', - onSelect(sp: URLSearchParams) { - sp.set('s', 'stars'); - sp.set('o', 'desc'); - return sp; + onSelect() { + return { s: 'stars', o: 'desc' }; } }, { name: 'Fewest stars', - onSelect(sp: URLSearchParams) { - sp.set('s', 'stars'); - sp.set('o', 'asc'); - return sp; + onSelect() { + return { s: 'stars', o: 'asc' }; } }, { name: 'Most forks', - onSelect(sp: URLSearchParams) { - sp.set('s', 'forks'); - sp.set('o', 'desc'); - return sp; + onSelect() { + return { s: 'forks', o: 'desc' }; } }, { name: 'Fewest forks', - onSelect(sp: URLSearchParams) { - sp.set('s', 'forks'); - sp.set('o', 'asc'); - return sp; + onSelect() { + return { s: 'forks', o: 'asc' }; } }, { name: 'Most help wanted issues', - onSelect(sp: URLSearchParams) { - sp.set('s', 'help-wanted-issues'); - sp.set('o', 'desc'); - return sp; + onSelect() { + return { s: 'help-wanted-issues', o: 'desc' }; } }, { name: 'Recently updated', - onSelect(sp: URLSearchParams) { - sp.set('s', 'updated'); - sp.set('o', 'desc'); - return sp; + onSelect() { + return { s: 'updated', o: 'desc' }; } }, { name: 'Least recently updated', - onSelect(sp: URLSearchParams) { - sp.set('s', 'updated'); - sp.set('o', 'asc'); - return sp; + onSelect() { + return { s: 'updated', o: 'asc' }; } } ]; function selectedSort(): SortTypes { - if (searchParams.get('o') === 'asc') { - if (searchParams.get('s') === 'stars') return SortTypes.FewestStars; - if (searchParams.get('s') === 'forks') return SortTypes.FewestForks; - if (searchParams.get('s') === 'updated') + if (sortOrder === 'asc') { + if (sortField === 'stars') return SortTypes.FewestStars; + if (sortField === 'forks') return SortTypes.FewestForks; + if (sortField === 'updated') return SortTypes.LeastRecentlyUpdated; return SortTypes.BestMatch; - } else if (searchParams.get('o') === 'desc') { - if (searchParams.get('s') === 'stars') return SortTypes.MostStars; - if (searchParams.get('s') === 'forks') return SortTypes.MostForks; - if (searchParams.get('s') === 'updated') return SortTypes.RecentlyUpdated; - if (searchParams.get('s') === 'help-wanted-issues') + } else if (sortOrder === 'desc') { + if (sortField === 'stars') return SortTypes.MostStars; + if (sortField === 'forks') return SortTypes.MostForks; + if (sortField === 'updated') return SortTypes.RecentlyUpdated; + if (sortField === 'help-wanted-issues') return SortTypes.MostHelpWantedIssues; return SortTypes.BestMatch; } else { @@ -127,12 +114,17 @@ export function Sorter() {