1
- import React from "react" ;
2
-
1
+ import React , { useEffect , useRef , useCallback } from "react" ;
2
+ import { useInfiniteHits , useInstantSearch } from "react-instantsearch" ;
3
3
import Card from "./ApiCard" ;
4
-
5
4
import { CardSkeleton } from "@/components/ui/CardSkeleton" ;
6
- import { ApiCard } from "@/types/api " ;
5
+ import { cleanDescription } from "@/utils/textProcessing " ;
7
6
8
7
interface ApiGridProps {
9
- cards : ApiCard [ ] ;
10
8
searchTerm : string ;
11
- loading : boolean ;
12
- loadingMore : boolean ;
13
- hasMore : boolean ;
14
9
gridColumns : number ;
15
10
pageSize : number ;
16
- observerRef : React . RefObject < HTMLDivElement | null > ;
17
11
}
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
+ }
18
83
19
- export function ApiGrid ( {
20
- cards,
21
- searchTerm,
22
- loading,
23
- loadingMore,
24
- hasMore,
25
- gridColumns,
26
- pageSize,
27
- observerRef,
28
- } : ApiGridProps ) {
29
84
return (
30
85
< section id = "apis-list" className = "cards" >
31
- { loading ? (
86
+ { initialLoading ? (
32
87
< div className = "grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mt-4" >
33
88
{ Array . from ( { length : Math . min ( pageSize , gridColumns * 2 ) } ) . map (
34
89
( _ , index ) => (
@@ -38,32 +93,34 @@ export function ApiGrid({
38
93
</ div >
39
94
) : (
40
95
< >
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
+
41
109
{ loadingMore && (
42
110
< 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 (
44
112
( _ , index ) => (
45
113
< CardSkeleton key = { `skeleton-more-${ index } ` } />
46
114
)
47
115
) }
48
116
</ div >
49
117
) }
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 >
61
118
</ >
62
119
) }
63
120
64
- < div ref = { observerRef } className = "h-10 mt-4" />
121
+ < div ref = { observerRef } className = "h-10 mt-4" aria-hidden = "true" />
65
122
66
- { ! hasMore && cards . length > 0 && (
123
+ { ! hasMore && hits . length > 0 && ! hasError && (
67
124
< div className = "text-center py-6 text-gray-500" >
68
125
That's all the APIs! 🎉
69
126
</ div >
0 commit comments