11'use client' ;
22
33import { useState , useEffect , useCallback , useRef } from 'react' ;
4- import { useSearchParams } from 'next/navigation' ;
4+ import { useSearchParams , useParams } from 'next/navigation' ;
55import { useRouter } from '@/i18n/routing' ;
66import { useTranslations } from 'next-intl' ;
77import { Search , X } from 'lucide-react' ;
8+
89import AccordionList from '@/components/q&a/AccordionList' ;
910import { Pagination } from '@/components/q&a/Pagination' ;
1011import { Tabs , TabsList , TabsTrigger , TabsContent } from '@/components/ui/tabs' ;
@@ -24,8 +25,11 @@ export default function TabsSection() {
2425 const t = useTranslations ( 'qa' ) ;
2526 const router = useRouter ( ) ;
2627 const searchParams = useSearchParams ( ) ;
28+ const params = useParams ( ) ;
29+
30+ const locale = params . locale as string ;
2731
28- const pageFromUrl = parseInt ( searchParams . get ( 'page' ) || '1' , 10 ) ;
32+ const pageFromUrl = Number ( searchParams . get ( 'page' ) || 1 ) ;
2933 const categoryFromUrl = searchParams . get ( 'category' ) || DEFAULT_CATEGORY ;
3034 const searchFromUrl = searchParams . get ( 'search' ) || '' ;
3135
@@ -40,56 +44,59 @@ export default function TabsSection() {
4044 const [ isLoading , setIsLoading ] = useState ( true ) ;
4145
4246 const debounceRef = useRef < NodeJS . Timeout | null > ( null ) ;
47+ const mountedRef = useRef ( false ) ;
4348
4449 const updateUrl = useCallback (
4550 ( category : string , page : number , search : string ) => {
4651 const params = new URLSearchParams ( ) ;
47- if ( category !== DEFAULT_CATEGORY ) {
48- params . set ( 'category' , category ) ;
49- }
50- if ( page > 1 ) {
51- params . set ( 'page' , String ( page ) ) ;
52- }
53- if ( search ) {
54- params . set ( 'search' , search ) ;
55- }
52+
53+ if ( category !== DEFAULT_CATEGORY ) params . set ( 'category' , category ) ;
54+ if ( page > 1 ) params . set ( 'page' , String ( page ) ) ;
55+ if ( search ) params . set ( 'search' , search ) ;
56+
5657 const queryString = params . toString ( ) ;
57- router . push ( `/q&a${ queryString ? `?${ queryString } ` : '' } ` , {
58+
59+ router . replace ( `/q&a${ queryString ? `?${ queryString } ` : '' } ` , {
5860 scroll : false ,
5961 } ) ;
6062 } ,
6163 [ router ]
6264 ) ;
6365
6466 useEffect ( ( ) => {
65- if ( debounceRef . current ) {
66- clearTimeout ( debounceRef . current ) ;
67+ if ( ! mountedRef . current ) {
68+ mountedRef . current = true ;
69+ return ;
6770 }
6871
72+ if ( debounceRef . current ) clearTimeout ( debounceRef . current ) ;
73+
6974 debounceRef . current = setTimeout ( ( ) => {
7075 setDebouncedSearch ( searchQuery ) ;
7176 setCurrentPage ( 1 ) ;
7277 updateUrl ( active , 1 , searchQuery ) ;
7378 } , DEBOUNCE_MS ) ;
7479
7580 return ( ) => {
76- if ( debounceRef . current ) {
77- clearTimeout ( debounceRef . current ) ;
78- }
81+ if ( debounceRef . current ) clearTimeout ( debounceRef . current ) ;
7982 } ;
80- } , [ searchQuery ] ) ;
83+ } , [ searchQuery , active , updateUrl ] ) ;
8184
8285 useEffect ( ( ) => {
8386 async function load ( ) {
8487 setIsLoading ( true ) ;
88+
8589 try {
8690 const searchParam = debouncedSearch
8791 ? `&search=${ encodeURIComponent ( debouncedSearch ) } `
8892 : '' ;
93+
8994 const res = await fetch (
90- `/api/questions/${ active } ?page=${ currentPage } &limit=10${ searchParam } `
95+ `/api/questions/${ active } ?page=${ currentPage } &limit=10&locale= ${ locale } ${ searchParam } `
9196 ) ;
97+
9298 const data : PaginatedResponse = await res . json ( ) ;
99+
93100 setItems ( data . items ) ;
94101 setTotalPages ( data . totalPages ) ;
95102 } catch ( error ) {
@@ -100,8 +107,9 @@ export default function TabsSection() {
100107 setIsLoading ( false ) ;
101108 }
102109 }
110+
103111 load ( ) ;
104- } , [ active , currentPage , debouncedSearch ] ) ;
112+ } , [ active , currentPage , debouncedSearch , locale ] ) ;
105113
106114 const handleCategoryChange = ( category : string ) => {
107115 setActive ( category ) ;
@@ -117,46 +125,35 @@ export default function TabsSection() {
117125 window . scrollTo ( { top : 0 , behavior : 'smooth' } ) ;
118126 } ;
119127
120- const handleSearchChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
121- setSearchQuery ( e . target . value ) ;
122- } ;
123-
124- const handleClearSearch = ( ) => {
125- setSearchQuery ( '' ) ;
126- setDebouncedSearch ( '' ) ;
127- setCurrentPage ( 1 ) ;
128- updateUrl ( active , 1 , '' ) ;
129- } ;
130-
131128 return (
132129 < div className = "w-full" >
133130 < div className = "relative mb-6" >
134- < div className = "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none" >
135- < Search className = "h-5 w-5 text-gray-400" />
136- </ div >
131+ < Search className = "absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
132+
137133 < input
138134 type = "text"
139135 value = { searchQuery }
140- onChange = { handleSearchChange }
136+ onChange = { e => setSearchQuery ( e . target . value ) }
141137 placeholder = { t ( 'searchPlaceholder' ) }
142- className = "block w-full pl-10 pr-10 py-3 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors "
138+ className = "w-full pl-10 pr-10 py-3 border rounded-lg"
143139 />
140+
144141 { searchQuery && (
145142 < button
146- onClick = { handleClearSearch }
147- className = "absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
148- aria-label = { t ( 'clearSearch' ) }
143+ onClick = { ( ) => {
144+ setSearchQuery ( '' ) ;
145+ setDebouncedSearch ( '' ) ;
146+ setCurrentPage ( 1 ) ;
147+ updateUrl ( active , 1 , '' ) ;
148+ } }
149+ className = "absolute right-3 top-1/2 -translate-y-1/2"
149150 >
150151 < X className = "h-5 w-5" />
151152 </ button >
152153 ) }
153154 </ div >
154155
155- < Tabs
156- value = { active }
157- onValueChange = { handleCategoryChange }
158- className = "w-full"
159- >
156+ < Tabs value = { active } onValueChange = { handleCategoryChange } >
160157 < TabsList className = "grid grid-cols-7 mb-6" >
161158 { categoryNames . map ( c => (
162159 < TabsTrigger key = { c } value = { c } >
@@ -169,12 +166,12 @@ export default function TabsSection() {
169166 < TabsContent key = { c } value = { c } >
170167 { isLoading ? (
171168 < div className = "flex justify-center py-12" >
172- < div className = "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 " />
169+ < div className = "animate-spin h-8 w-8 border-b-2" />
173170 </ div >
174- ) : items . length > 0 ? (
171+ ) : items . length ? (
175172 < AccordionList items = { items } />
176173 ) : (
177- < p className = "text-center py-12 text-gray-500 dark:text-gray-400 " >
174+ < p className = "text-center py-12" >
178175 { debouncedSearch
179176 ? t ( 'noResults' , { query : debouncedSearch } )
180177 : t ( 'noQuestions' ) }
0 commit comments