@@ -36,6 +36,8 @@ const ReferencesSection = dynamic(() => import('@/components/ReferencesSection')
3636 ssr : false
3737} ) ;
3838
39+ import FilterGroup from '../components/FilterGroup' ;
40+
3941// --- Constants and Helper Functions (keep as is) ---
4042const parseUrlList = ( param ) => param ? param . split ( ',' ) : [ ] ;
4143// Helper functions for macro categories (which contain commas, so we use pipe delimiter)
@@ -215,10 +217,14 @@ function HomePageContent({ scrollY }) {
215217 const searchParams = useSearchParams ( ) ; // This causes the component to suspend
216218
217219 // ... state declarations (filters, showMore, isMounted, etc.) ...
218- const [ filters , setFilters ] = useState ( { dataTypes : [ ] , countries : [ ] , compensationTypes : [ ] , searchTerm : '' , macroCategories : [ ] } ) ;
219- const [ showMoreDataTypes , setShowMoreDataTypes ] = useState ( false ) ;
220- const [ showMoreCountries , setShowMoreCountries ] = useState ( false ) ;
221- const [ showMoreMacroCategories , setShowMoreMacroCategories ] = useState ( false ) ;
220+
221+ // --- Filter State ---
222+ const [ filters , setFilters ] = useState ( {
223+ macroCategories : [ ] ,
224+ countries : [ ] ,
225+ dataTypes : [ ] ,
226+ compensationTypes : [ ]
227+ } ) ;
222228 const [ isMounted , setIsMounted ] = useState ( false ) ;
223229 const [ isFilterDrawerOpen , setIsFilterDrawerOpen ] = useState ( false ) ;
224230 const [ openFilterPanel , setOpenFilterPanel ] = useState ( null ) ;
@@ -348,7 +354,7 @@ function HomePageContent({ scrollY }) {
348354 } , [ ] ) ;
349355
350356 // Show-more state for desktop compensation filter panel (mirrors other filters)
351- const [ showMoreCompensationTypes , setShowMoreCompensationTypes ] = useState ( false ) ;
357+
352358
353359 const processedResources = useMemo ( ( ) => {
354360 let filteredData = [ ...allResources ] ;
@@ -535,21 +541,12 @@ function HomePageContent({ scrollY }) {
535541 } ) ;
536542 } , [ ] ) ; // Empty dependency array
537543
538- const handleFilterChange = ( filterKey , value ) => {
539- setFilters ( prev => ( {
540- ...prev ,
541- [ filterKey ] : prev [ filterKey ] . includes ( value )
542- ? prev [ filterKey ] . filter ( item => item !== value )
543- : [ ...prev [ filterKey ] , value ]
544- } ) ) ;
545- } ;
546-
547544 const handleWearableFilterToggle = ( ) => {
548545 const isWearableActive = filters . dataTypes . includes ( 'Wearable data' ) ;
549546 handleCheckboxChange ( 'dataTypes' , 'Wearable data' , ! isWearableActive ) ;
550547 } ;
551548
552- const handleFilterChangeWrapper = useCallback ( ( filterKey , value , isChecked ) => {
549+ const handleFilterChange = useCallback ( ( filterKey , value , isChecked ) => {
553550 if ( filterKey === 'compensationTypes' ) {
554551 const paymentOption = PAYMENT_TYPES . find ( p => p . value === value ) ;
555552 if ( paymentOption ) {
@@ -562,118 +559,7 @@ function HomePageContent({ scrollY }) {
562559 }
563560 } , [ handlePaymentCheckboxChange , handleMacroCategoryFilterChange , handleCheckboxChange ] ) ;
564561
565- // --- Render Functions (keep as is) ---
566- const renderFilterGroup = ( title , options , filterKey , showMore , setShowMore , config = { } ) => {
567- const { alwaysExpanded = false , columns = 1 } = config ;
568- const selectedValues = filters [ filterKey ] ;
569- const visibleOptions = alwaysExpanded || showMore ? options : options . slice ( 0 , 3 ) ;
570- // --- START: Modify condition for showing Clear all ---
571- // Show "Clear all" if more than one item is selected
572- const showClearAll = selectedValues . length > 1 ;
573- // --- END: Modify condition for showing Clear all ---
574-
575-
576- const optionLayoutClass = columns > 1
577- ? 'grid grid-cols-1 sm:grid-cols-2 gap-3'
578- : 'space-y-2' ;
579-
580- return (
581- < div className = "mb-6" >
582- < h3 className = "font-semibold text-base text-slate-900 mb-1" > { title } </ h3 >
583- < button
584- // --- START: Update onClick and text based on showClear all ---
585- onClick = { ( ) => handleSelectAll ( filterKey , options , ! showClearAll ) } // If showing "Clear all", pass false to selectAll; otherwise pass true
586- className = "text-xs font-semibold text-google-blue hover:underline mb-3 block"
587- aria-label = { showClearAll ? `Clear all ${ title } ` : `Select all ${ title } ` }
588- >
589- { showClearAll ? 'Clear all' : 'Select all' }
590- { /* --- END: Update onClick and text based on showClear all --- */ }
591- </ button >
592-
593- < div className = { optionLayoutClass } >
594- { visibleOptions . map ( option => {
595- const value = typeof option === 'string' ? option : option . value ;
596- const label = typeof option === 'string' ? option : option . label ;
597- const code = typeof option === 'object' ? option . code : null ;
598- const emoji = typeof option === 'object' ? option . emoji : null ;
599- const idSuffix = alwaysExpanded ? 'desktop' : 'mobile' ;
600- let isChecked = false ;
601- if ( filterKey === 'countries' ) {
602- isChecked = selectedValues . includes ( value ) ;
603- } else if ( filterKey === 'compensationTypes' ) {
604- isChecked = selectedValues . some ( p => p . value === value ) ;
605- } else {
606- isChecked = selectedValues . includes ( value ) ;
607- }
608-
609- // Define category styles locally to match ResourceCard
610- const categoryStyles = {
611- 'Organ, Body & Tissue Donation' : 'bg-rose-100 text-rose-800 border-rose-200' ,
612- 'Biological Samples' : 'bg-blue-100 text-blue-800 border-blue-200' ,
613- 'Clinical Trials' : 'bg-green-100 text-green-800 border-green-200' ,
614- 'Health & Digital Data' : 'bg-yellow-100 text-yellow-800 border-yellow-200' ,
615- } ;
616-
617- let itemClass = "flex items-center gap-3 rounded-2xl border px-3 py-2 shadow-[0_5px_20px_rgba(15,23,42,0.06)] transition-colors cursor-pointer " ;
618-
619- if ( filterKey === 'macroCategories' && categoryStyles [ value ] ) {
620- // Apply specific category style if it's a macro category
621- itemClass += categoryStyles [ value ] + ( isChecked ? " ring-2 ring-offset-1 ring-slate-400" : " hover:opacity-80" ) ;
622- } else {
623- // Default style for other filters
624- itemClass += "border-white/60 bg-white/70 hover:bg-white/90 hover:border-white/80" ;
625- }
626-
627-
628- return (
629- < label
630- key = { value }
631- htmlFor = { `${ filterKey } -${ value } -${ idSuffix } ` }
632- className = { itemClass }
633- >
634- < input
635- type = "checkbox"
636- id = { `${ filterKey } -${ value } -${ idSuffix } ` }
637- value = { value }
638- checked = { isChecked }
639- onChange = { ( e ) => handleFilterChangeWrapper ( filterKey , value , e . target . checked ) }
640- className = "h-4 w-4 text-slate-900 border-slate-300 rounded focus:ring-slate-900 focus:ring-offset-0 focus:ring-1"
641- />
642- < span className = { `flex items-center gap-2 text-sm sm:text-base font-medium ${ filterKey === 'macroCategories' ? 'text-inherit' : 'text-slate-800' } ` } >
643- { emoji && < span className = "text-base sm:text-lg leading-none" > { emoji } </ span > }
644- { label }
645- { code && (
646- < CountryFlag
647- countryCode = { code }
648- svg
649- aria-label = { label }
650- style = { { width : '1.1em' , height : '0.9em' , display : 'inline-block' , verticalAlign : 'middle' } }
651- />
652- ) }
653- </ span >
654- </ label >
655- ) ;
656- } ) }
657- </ div >
658-
659- { options . length > 3 && ! alwaysExpanded && typeof setShowMore === 'function' && (
660- < button onClick = { ( ) => setShowMore ( ! showMore ) } className = "text-sm font-medium text-google-blue hover:underline mt-1 flex items-center" >
661- < svg className = { `w-3 h-3 mr-1 transform transition-transform ${ showMore ? 'rotate-180' : '' } ` } fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" xmlns = "http://www.w3.org/2000/svg" > < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M19 9l-7 7-7-7" > </ path > </ svg >
662- { showMore ? 'Less' : `More (${ options . length - 3 } )` }
663- </ button >
664- ) }
665- </ div >
666- ) ;
667- } ;
668562
669- const renderFilterContent = ( ) => (
670- < >
671- { isMounted && renderFilterGroup ( 'Category' , macroCategoryOptions , 'macroCategories' , showMoreMacroCategories , setShowMoreMacroCategories , { alwaysExpanded : true } ) }
672- { isMounted && renderFilterGroup ( 'Available In' , countryOptions , 'countries' , showMoreCountries , setShowMoreCountries ) }
673- { isMounted && renderFilterGroup ( 'Data Type' , dataTypeOptions , 'dataTypes' , showMoreDataTypes , setShowMoreDataTypes ) }
674- { isMounted && renderFilterGroup ( 'Compensation' , PAYMENT_TYPES , 'compensationTypes' , showMoreCompensationTypes , setShowMoreCompensationTypes ) }
675- </ >
676- ) ;
677563
678564
679565
@@ -697,6 +583,7 @@ function HomePageContent({ scrollY }) {
697583
698584 return (
699585 < div className = "flex-grow w-full max-w-screen-xl mx-auto px-4 pb-8 pt-3" >
586+
700587 { /* Intro Text */ }
701588 < p className = "text-base text-google-text-secondary max-w-5xl mb-6" >
702589 A comprehensive open-source list of services allowing individuals to contribute to scientific research.
@@ -828,10 +715,50 @@ function HomePageContent({ scrollY }) {
828715 < div className = "absolute -bottom-16 right-12 w-48 h-48 bg-blue-100/40 blur-[90px] rounded-full" />
829716 </ div >
830717 < div className = "relative border-t border-slate-100 pt-4" >
831- { openFilterPanel === 'macroCategories' && renderFilterGroup ( 'Category' , macroCategoryOptions , 'macroCategories' , true , null , { alwaysExpanded : true , columns : 2 } ) }
832- { openFilterPanel === 'countries' && renderFilterGroup ( 'Available in' , countryOptions , 'countries' , true , null , { alwaysExpanded : true , columns : 2 } ) }
833- { openFilterPanel === 'dataTypes' && renderFilterGroup ( 'Data type' , dataTypeOptions , 'dataTypes' , true , null , { alwaysExpanded : true , columns : 2 } ) }
834- { openFilterPanel === 'compensationTypes' && renderFilterGroup ( 'Compensation' , PAYMENT_TYPES , 'compensationTypes' , true , null , { alwaysExpanded : true , columns : 2 } ) }
718+ { openFilterPanel === 'macroCategories' && (
719+ < FilterGroup
720+ title = "Category"
721+ options = { macroCategoryOptions }
722+ filterKey = "macroCategories"
723+ selectedValues = { filters . macroCategories }
724+ onFilterChange = { ( k , v , c ) => handleFilterChange ( k , v , c ) }
725+ onSelectAll = { ( shouldSelect ) => handleSelectAll ( 'macroCategories' , macroCategoryOptions , shouldSelect ) }
726+ config = { { alwaysExpanded : true , columns : 2 , HeadingTag : 'h2' } }
727+ />
728+ ) }
729+ { openFilterPanel === 'countries' && (
730+ < FilterGroup
731+ title = "Available in"
732+ options = { countryOptions }
733+ filterKey = "countries"
734+ selectedValues = { filters . countries }
735+ onFilterChange = { ( k , v , c ) => handleFilterChange ( k , v , c ) }
736+ onSelectAll = { ( shouldSelect ) => handleSelectAll ( 'countries' , countryOptions , shouldSelect ) }
737+ config = { { alwaysExpanded : true , columns : 2 , HeadingTag : 'h2' } }
738+ />
739+ ) }
740+ { openFilterPanel === 'dataTypes' && (
741+ < FilterGroup
742+ title = "Data type"
743+ options = { dataTypeOptions }
744+ filterKey = "dataTypes"
745+ selectedValues = { filters . dataTypes }
746+ onFilterChange = { ( k , v , c ) => handleFilterChange ( k , v , c ) }
747+ onSelectAll = { ( shouldSelect ) => handleSelectAll ( 'dataTypes' , dataTypeOptions , shouldSelect ) }
748+ config = { { alwaysExpanded : true , columns : 2 , HeadingTag : 'h2' } }
749+ />
750+ ) }
751+ { openFilterPanel === 'compensationTypes' && (
752+ < FilterGroup
753+ title = "Compensation"
754+ options = { PAYMENT_TYPES }
755+ filterKey = "compensationTypes"
756+ selectedValues = { filters . compensationTypes }
757+ onFilterChange = { ( k , v , c ) => handleFilterChange ( k , v , c ) }
758+ onSelectAll = { ( shouldSelect ) => handleSelectAll ( 'compensationTypes' , PAYMENT_TYPES , shouldSelect ) }
759+ config = { { alwaysExpanded : true , columns : 2 , HeadingTag : 'h2' } }
760+ />
761+ ) }
835762
836763 < div className = "mt-3 flex justify-between items-center text-xs" >
837764 < button
@@ -971,10 +898,53 @@ function HomePageContent({ scrollY }) {
971898 countryOptions = { countryOptions }
972899 dataTypeOptions = { dataTypeOptions }
973900 paymentTypes = { PAYMENT_TYPES }
974- handleCheckboxChange = { ( filterKey , value ) => handleFilterChange ( filterKey , value ) }
975- handlePaymentCheckboxChange = { ( option ) => handleFilterChange ( 'compensationType' , option . value ) }
901+ handleCheckboxChange = { handleCheckboxChange }
902+ handlePaymentCheckboxChange = { handlePaymentCheckboxChange }
976903 handleResetFilters = { handleResetFilters }
977- renderFilterContent = { renderFilterContent } // Pass the existing render function
904+ renderFilterContent = { ( ) => (
905+ < >
906+ { isMounted && (
907+ < >
908+ < FilterGroup
909+ title = "Category"
910+ options = { macroCategoryOptions }
911+ filterKey = "macroCategories"
912+ selectedValues = { filters . macroCategories }
913+ onFilterChange = { ( k , v , c ) => handleFilterChange ( k , v , c ) }
914+ onSelectAll = { ( shouldSelect ) => handleSelectAll ( 'macroCategories' , macroCategoryOptions , shouldSelect ) }
915+ config = { { alwaysExpanded : true , HeadingTag : 'h2' } }
916+ />
917+ < FilterGroup
918+ title = "Available In"
919+ options = { countryOptions }
920+ filterKey = "countries"
921+ selectedValues = { filters . countries }
922+ onFilterChange = { ( k , v , c ) => handleFilterChange ( k , v , c ) }
923+ onSelectAll = { ( shouldSelect ) => handleSelectAll ( 'countries' , countryOptions , shouldSelect ) }
924+ config = { { HeadingTag : 'h2' } }
925+ />
926+ < FilterGroup
927+ title = "Data Type"
928+ options = { dataTypeOptions }
929+ filterKey = "dataTypes"
930+ selectedValues = { filters . dataTypes }
931+ onFilterChange = { ( k , v , c ) => handleFilterChange ( k , v , c ) }
932+ onSelectAll = { ( shouldSelect ) => handleSelectAll ( 'dataTypes' , dataTypeOptions , shouldSelect ) }
933+ config = { { HeadingTag : 'h2' } }
934+ />
935+ < FilterGroup
936+ title = "Compensation"
937+ options = { PAYMENT_TYPES }
938+ filterKey = "compensationTypes"
939+ selectedValues = { filters . compensationTypes }
940+ onFilterChange = { ( k , v , c ) => handleFilterChange ( k , v , c ) }
941+ onSelectAll = { ( shouldSelect ) => handleSelectAll ( 'compensationTypes' , PAYMENT_TYPES , shouldSelect ) }
942+ config = { { HeadingTag : 'h2' } }
943+ />
944+ </ >
945+ ) }
946+ </ >
947+ ) }
978948 />
979949 )
980950 }
0 commit comments