1- import React from "react" ;
2-
1+ import React , { useEffect , useRef , useCallback } from "react" ;
2+ import { useInfiniteHits , useInstantSearch } from "react-instantsearch" ;
33import Card from "./ApiCard" ;
4-
54import { CardSkeleton } from "@/components/ui/CardSkeleton" ;
6- import { ApiCard } from "@/types/api " ;
5+ import { cleanDescription } from "@/utils/textProcessing " ;
76
87interface ApiGridProps {
9- cards : ApiCard [ ] ;
108 searchTerm : string ;
11- loading : boolean ;
12- loadingMore : boolean ;
13- hasMore : boolean ;
149 gridColumns : number ;
1510 pageSize : number ;
16- observerRef : React . RefObject < HTMLDivElement | null > ;
1711}
12+ const transformItems = ( items : any [ ] ) => {
13+ console . log ( "Transforming items:" , items ) ;
14+ return items . map ( ( item ) => ( {
15+ ...item ,
16+ name : item . name || item . objectID ,
17+ description : cleanDescription ( item . description || "" ) ,
18+ title : item . title || item . name || "" ,
19+ categories : item . categories ? item . categories . split ( "," ) : [ ] ,
20+ tags : item . tags ? item . tags . split ( "," ) : [ ] ,
21+ contact : item . contact || "" ,
22+ license : item . license || "" ,
23+ logoUrl : item . logoUrl || "" ,
24+ swaggerUrl : item . swaggerUrl || "" ,
25+ swaggerYamlUrl : item . swaggerYamlUrl || "" ,
26+ externalUrl : item . externalUrl || "" ,
27+ version : item . version || "" ,
28+ added : item . added || "" ,
29+ updated : item . updated || "" ,
30+ } ) ) ;
31+ } ;
32+
33+ export function ApiGrid ( { searchTerm, gridColumns, pageSize } : ApiGridProps ) {
34+ const { status, error } = useInstantSearch ( { catchError : true } ) ;
35+ const { hits, isLastPage, showMore, results } = useInfiniteHits ( {
36+ transformItems,
37+ showPrevious : false ,
38+ } ) ;
39+
40+ const loading = status === "loading" ;
41+ const stalled = status === "stalled" ;
42+ const hasError = status === "error" ;
43+ const initialLoading = ( loading || stalled ) && hits . length === 0 ;
44+ const loadingMore = ( loading || stalled ) && hits . length > 0 ;
45+ const hasMore = ! isLastPage && ! hasError ;
46+
47+ const observerRef = useRef < HTMLDivElement | null > ( null ) ;
48+
49+ const handleIntersection = useCallback (
50+ ( entries : IntersectionObserverEntry [ ] ) => {
51+ if ( entries [ 0 ] . isIntersecting && hasMore && ! loading && ! stalled ) {
52+ showMore ( ) ;
53+ }
54+ } ,
55+ [ hasMore , loading , stalled , showMore ]
56+ ) ;
57+
58+ useEffect ( ( ) => {
59+ const observer = observerRef . current ;
60+ if ( ! observer ) return ;
61+
62+ const intersectionObserver = new IntersectionObserver ( handleIntersection , {
63+ threshold : 0.1 ,
64+ rootMargin : "100px" ,
65+ } ) ;
66+
67+ intersectionObserver . observe ( observer ) ;
68+
69+ return ( ) => {
70+ intersectionObserver . disconnect ( ) ;
71+ } ;
72+ } , [ handleIntersection ] ) ;
73+
74+ if ( hasError && error ) {
75+ return (
76+ < section id = "apis-list" className = "cards" >
77+ < div className = "col-span-full text-center py-6 bg-red-50 rounded-lg border border-red-100" >
78+ < p className = "text-red-600" > Error loading APIs: { error . message } </ p >
79+ </ div >
80+ </ section >
81+ ) ;
82+ }
1883
19- export function ApiGrid ( {
20- cards,
21- searchTerm,
22- loading,
23- loadingMore,
24- hasMore,
25- gridColumns,
26- pageSize,
27- observerRef,
28- } : ApiGridProps ) {
2984 return (
3085 < section id = "apis-list" className = "cards" >
31- { loading ? (
86+ { initialLoading ? (
3287 < div className = "grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mt-4" >
3388 { Array . from ( { length : Math . min ( pageSize , gridColumns * 2 ) } ) . map (
3489 ( _ , index ) => (
@@ -38,32 +93,34 @@ export function ApiGrid({
3893 </ div >
3994 ) : (
4095 < >
96+ < div className = "grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mt-4" >
97+ { hits . length > 0 ? (
98+ hits . map ( ( hit , index ) => {
99+ console . log ( "Rendering hit:" , hit ) ;
100+ return < Card key = { `${ hit . objectID } -${ index } ` } model = { hit } /> ;
101+ } )
102+ ) : (
103+ < div className = "col-span-full text-center py-6 bg-gray-50 rounded-lg border border-gray-100" >
104+ No APIs found matching "{ searchTerm } "
105+ </ div >
106+ ) }
107+ </ div >
108+
41109 { loadingMore && (
42110 < div className = "grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mt-4" >
43- { Array . from ( { length : Math . min ( pageSize , gridColumns * 2 ) } ) . map (
111+ { Array . from ( { length : Math . min ( pageSize , gridColumns ) } ) . map (
44112 ( _ , index ) => (
45113 < CardSkeleton key = { `skeleton-more-${ index } ` } />
46114 )
47115 ) }
48116 </ div >
49117 ) }
50- < div className = "grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4" >
51- { cards . length > 0 ? (
52- cards . map ( ( card , index ) => (
53- < Card key = { `${ card . name } -${ index } ` } model = { card } />
54- ) )
55- ) : (
56- < div className = "col-span-full text-center py-6 bg-gray-50 rounded-lg border border-gray-100" >
57- No APIs found matching "{ searchTerm } "
58- </ div >
59- ) }
60- </ div >
61118 </ >
62119 ) }
63120
64- < div ref = { observerRef } className = "h-10 mt-4" />
121+ < div ref = { observerRef } className = "h-10 mt-4" aria-hidden = "true" />
65122
66- { ! hasMore && cards . length > 0 && (
123+ { ! hasMore && hits . length > 0 && ! hasError && (
67124 < div className = "text-center py-6 text-gray-500" >
68125 That's all the APIs! 🎉
69126 </ div >
0 commit comments