Skip to content

Commit aff5b33

Browse files
committed
Add markdown content graqphql apis and remove old HTML redirects
1 parent f6534a8 commit aff5b33

File tree

10 files changed

+716
-72
lines changed

10 files changed

+716
-72
lines changed

app/(with-support)/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React from "react";
22
import SearchClientComponent from "@/components/SearchClientComponent";
33

4+
export const dynamic = "force-dynamic";
5+
46
export default async function Home() {
57
return (
68
<div className="container mx-auto px-4 py-4 relative">

app/globals.css

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,50 @@
212212
.text-sm {
213213
font-size: var(--small-font-size, 14px);
214214
}
215+
216+
/* Custom markdown table styling */
217+
.prose table {
218+
width: 100%;
219+
margin: 2rem 0;
220+
font-size: 0.875rem;
221+
}
222+
223+
.prose table th {
224+
text-align: left;
225+
font-weight: 600;
226+
vertical-align: top;
227+
}
228+
229+
.prose table td {
230+
vertical-align: top;
231+
}
232+
233+
.prose table th:nth-child(3),
234+
.prose table td:nth-child(3) {
235+
text-align: center;
236+
width: 100px;
237+
}
238+
239+
.prose table th:nth-child(4),
240+
.prose table td:nth-child(4) {
241+
text-align: center;
242+
width: 120px;
243+
}
244+
245+
/* Improve link styling in tables */
246+
.prose table a {
247+
font-weight: 500;
248+
text-decoration: none;
249+
border-radius: 4px;
250+
padding: 2px 6px;
251+
transition: all 0.2s ease;
252+
}
253+
254+
/* Emoji support */
255+
.prose img[alt*="smile"] {
256+
display: inline;
257+
margin: 0;
258+
width: 1em;
259+
height: 1em;
260+
}
215261
}

app/graphql-apis/page.tsx

Lines changed: 124 additions & 0 deletions
Large diffs are not rendered by default.

components/ApiGrid.tsx

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import React, { useEffect, useRef, useCallback } from "react";
1+
"use client";
2+
3+
import React, { useEffect, useRef, useCallback, useState } from "react";
24
import {
35
useInfiniteHits,
46
useInstantSearch,
@@ -34,29 +36,64 @@ const transformItems = (items: any[]) => {
3436
};
3537

3638
export function ApiGrid({ gridColumns, pageSize }: ApiGridProps) {
37-
const { query } = useSearchBox(); // <-- Get current search term
39+
const { query } = useSearchBox();
3840
const { status, error } = useInstantSearch({ catchError: true });
39-
const { hits, isLastPage, showMore } = useInfiniteHits({
40-
transformItems,
41-
showPrevious: false,
42-
});
41+
const {
42+
hits,
43+
isLastPage,
44+
showMore: originalShowMore,
45+
} = useInfiniteHits(
46+
{
47+
transformItems,
48+
showPrevious: false,
49+
},
50+
{ skipSuspense: true }
51+
);
52+
53+
const [hasInitiallyLoaded, setHasInitiallyLoaded] = useState(false);
54+
const [isLoadingMore, setIsLoadingMore] = useState(false);
55+
const observerRef = useRef<HTMLDivElement | null>(null);
56+
const prevQueryRef = useRef(query);
4357

4458
const loading = status === "loading";
4559
const stalled = status === "stalled";
4660
const hasError = status === "error";
47-
const initialLoading = (loading || stalled) && hits.length === 0;
48-
const loadingMore = (loading || stalled) && hits.length > 0;
61+
const isSearching = loading || stalled;
4962
const hasMore = !isLastPage && !hasError;
5063

51-
const observerRef = useRef<HTMLDivElement | null>(null);
64+
const showMore = useCallback(() => {
65+
if (hasMore && !isSearching) {
66+
setIsLoadingMore(true);
67+
originalShowMore();
68+
}
69+
}, [hasMore, isSearching, originalShowMore]);
70+
71+
useEffect(() => {
72+
if (isLoadingMore && status === "idle") {
73+
setIsLoadingMore(false);
74+
}
75+
}, [isLoadingMore, status]);
76+
77+
useEffect(() => {
78+
if (query !== prevQueryRef.current) {
79+
setIsLoadingMore(false);
80+
prevQueryRef.current = query;
81+
}
82+
}, [query]);
83+
84+
useEffect(() => {
85+
if (!isSearching && hits.length >= 0) {
86+
setHasInitiallyLoaded(true);
87+
}
88+
}, [isSearching, hits.length]);
5289

5390
const handleIntersection = useCallback(
5491
(entries: IntersectionObserverEntry[]) => {
55-
if (entries[0].isIntersecting && hasMore && !loading && !stalled) {
92+
if (entries[0].isIntersecting && hasMore && !isSearching) {
5693
showMore();
5794
}
5895
},
59-
[hasMore, loading, stalled, showMore],
96+
[hasMore, isSearching, showMore]
6097
);
6198

6299
useEffect(() => {
@@ -69,10 +106,7 @@ export function ApiGrid({ gridColumns, pageSize }: ApiGridProps) {
69106
});
70107

71108
intersectionObserver.observe(observer);
72-
73-
return () => {
74-
intersectionObserver.disconnect();
75-
};
109+
return () => intersectionObserver.disconnect();
76110
}, [handleIntersection]);
77111

78112
if (hasError && error) {
@@ -85,36 +119,45 @@ export function ApiGrid({ gridColumns, pageSize }: ApiGridProps) {
85119
);
86120
}
87121

122+
const shouldShowInitialSkeleton =
123+
isSearching && !hasInitiallyLoaded && hits.length === 0;
124+
const shouldShowNoResults =
125+
hits.length === 0 && hasInitiallyLoaded && query.length > 0;
126+
88127
return (
89128
<section id="apis-list" className="cards">
90-
{initialLoading ? (
91-
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 ">
129+
{stalled && (
130+
<div className="fixed top-0 left-0 w-full h-1 bg-blue-500 animate-pulse z-50" />
131+
)}
132+
133+
{shouldShowInitialSkeleton ? (
134+
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4">
92135
{Array.from({ length: Math.min(pageSize, gridColumns * 2) }).map(
93136
(_, index) => (
94137
<CardSkeleton key={`skeleton-loading-${index}`} />
95-
),
138+
)
96139
)}
97140
</div>
98141
) : (
99142
<>
100-
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 ">
143+
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4">
101144
{hits.length > 0 ? (
102145
hits.map((hit, index) => (
103146
<Card key={`${hit.objectID}-${index}`} model={hit} />
104147
))
105-
) : (
148+
) : shouldShowNoResults ? (
106149
<div className="col-span-full text-center py-6 bg-gray-50 rounded-lg border border-gray-100">
107150
No APIs found matching &quot;{query}&quot;
108151
</div>
109-
)}
152+
) : null}
110153
</div>
111154

112-
{loadingMore && (
113-
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 ">
155+
{isLoadingMore && (
156+
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mt-4">
114157
{Array.from({ length: Math.min(pageSize, gridColumns) }).map(
115158
(_, index) => (
116159
<CardSkeleton key={`skeleton-more-${index}`} />
117-
),
160+
)
118161
)}
119162
</div>
120163
)}

components/SearchClientComponent.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import { useGridLayout } from "@/hooks/useGridLayout";
55
import { LoadingSkeleton } from "@/components/LoadingSkeleton";
66
import { ApiGrid } from "@/components/ApiGrid";
77
import { liteClient as algoliasearch } from "algoliasearch/lite";
8-
import { InstantSearch } from "react-instantsearch";
8+
import { InstantSearchNext } from "react-instantsearch-nextjs";
99
import { SearchSection } from "@/components/SearchSection";
10-
import { history } from "instantsearch.js/es/lib/routers";
1110

1211
const searchClient = algoliasearch(
1312
"D29MLR0AMY",
@@ -17,7 +16,9 @@ const searchClient = algoliasearch(
1716
const indexName = "prod_apis_guru";
1817

1918
const routing = {
20-
router: history(),
19+
router: {
20+
cleanUrlOnDispose: false,
21+
},
2122
stateMapping: {
2223
stateToRoute(uiState: any) {
2324
const indexUiState = uiState[indexName] || {};
@@ -47,15 +48,15 @@ function SearchClientComponentInner({
4748
return (
4849
<div className="container mx-auto px-4 relative">
4950
<div className="relative z-10">
50-
<InstantSearch
51+
<InstantSearchNext
5152
indexName={indexName}
5253
searchClient={searchClient}
5354
routing={routing}
5455
>
5556
<SearchSection />
5657

5758
<ApiGrid gridColumns={gridColumns} pageSize={pageSize} />
58-
</InstantSearch>
59+
</InstantSearchNext>
5960
</div>
6061
</div>
6162
);

components/SearchSection.tsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import {
1212
export function SearchSection() {
1313
const { results } = useInstantSearch();
1414
const { query } = useSearchBox();
15+
const [mounted, setMounted] = React.useState(false);
16+
17+
React.useEffect(() => {
18+
setMounted(true);
19+
}, []);
1520

1621
return (
1722
<div className="max-w-3xl mx-auto">
@@ -22,7 +27,11 @@ export function SearchSection() {
2227
<SearchIcon className="h-5 w-5 text-gray-400" />
2328
</div>
2429
<SearchBox
25-
placeholder={`Search through ${results.nbHits.toLocaleString()} APIs...`}
30+
placeholder={
31+
mounted
32+
? `Search through ${results.nbHits.toLocaleString("en-US")} APIs...`
33+
: "Search APIs..."
34+
}
2635
classNames={{
2736
form: "relative",
2837
input:
@@ -34,13 +43,18 @@ export function SearchSection() {
3443
/>
3544
</div>
3645
<div className="flex items-center pr-4 border-l border-gray-200 ml-2 pl-4">
37-
<PoweredBy
38-
classNames={{
39-
root: "text-sm text-gray-500",
40-
link: "text-blue-600 hover:text-blue-800 no-underline",
41-
logo: "h-4 w-auto ml-1",
42-
}}
43-
/>
46+
{/* Render PoweredBy only after mount to avoid hydration mismatch */}
47+
{mounted ? (
48+
<PoweredBy
49+
classNames={{
50+
root: "text-sm text-gray-500",
51+
link: "text-blue-600 hover:text-blue-800 no-underline",
52+
logo: "h-4 w-auto ml-1",
53+
}}
54+
/>
55+
) : (
56+
<div aria-hidden="true" className="h-4 w-[90px]" />
57+
)}
4458
</div>
4559
</div>
4660
</div>
@@ -49,14 +63,16 @@ export function SearchSection() {
4963
aria-live="polite"
5064
>
5165
<span
66+
suppressHydrationWarning
5267
className={`inline-block transition-opacity duration-200 ${
53-
query ? "opacity-100" : "opacity-0"
68+
mounted && query ? "opacity-100" : "opacity-0"
5469
}`}
5570
>
56-
{results.nbHits.toLocaleString()} APIs found
71+
{mounted && query
72+
? `${results.nbHits.toLocaleString("en-US")} APIs found`
73+
: ""}
5774
</span>
5875
</div>
5976
</div>
6077
);
6178
}
62-

0 commit comments

Comments
 (0)