Skip to content

set debounce to 1000ms #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ next-env.d.ts

public/robots.txt
public/sitemap-0.xml
public/sitemap.xml
public/sitemap.xml
# Sentry Config File
.env.sentry-build-plugin
162 changes: 82 additions & 80 deletions app/apis/[providerSlug]/[serviceSlug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@ import { Badge } from "@/components/ui/badge";
import JsonTreeContainer, { JsonTree } from "@/components/JsonTree";
import ApiButtons from "@/components/ApiButtons";
import VisitCounter from "@/components/VisitCounter";

interface ApiVersion {
version: string;
swaggerUrl: string;
swaggerYamlUrl: string;
}
import type { ApiVersion } from "@/types/api";

function stripMarkdown(markdown: string): string {
return markdown
Expand All @@ -37,85 +32,95 @@ export function getData(
? `${providerSlug}:${serviceSlug}`
: providerSlug;

if (apiList[targetKey]) {
return processApiData(targetKey, apiList[targetKey]);
}

for (const key in apiList) {
if (apiList.hasOwnProperty(key) && key === targetKey) {
try {
const api = apiList[key];
const versions = api.versions || {};
const preferred = api.preferred || Object.keys(versions)[0] || "";
const preferredVersion = versions[preferred] || {};
const info = preferredVersion.info || {};
const externalDocs = preferredVersion.externalDocs || {};
const contact = info.contact || {};

const logo = {
url: info["x-logo"]?.url || "/assets/images/no-logo.svg",
backgroundColor: info["x-logo"]?.backgroundColor || null,
};

const externalUrl =
externalDocs.url ||
contact.url ||
(key.indexOf(".local") < 0 ? `https://${key.split(":")[0]}` : "");

let origUrl = "";
if (
info["x-origin"] &&
Array.isArray(info["x-origin"]) &&
info["x-origin"].length > 0
) {
origUrl =
info["x-origin"][0]?.url || preferredVersion.swaggerUrl || "";
} else {
origUrl = preferredVersion.swaggerUrl || "";
}

const categories = info["x-apisguru-categories"] || [];
const tags = info["x-tags"] || [];

const versionsArray = Object.entries(versions).map(
([version, details]: [string, any]) => ({
version,
swaggerUrl: details?.swaggerUrl || "",
swaggerYamlUrl: details?.swaggerYamlUrl || "",
})
);

const description = info.description || "No description available";
const cardDescription = marked(description);
const cardDescriptionPlain = stripMarkdown(description);

return {
name: key,
preferred: api.preferred || "",
info,
api: {
swaggerUrl: preferredVersion.swaggerUrl || "",
swaggerYamlUrl: preferredVersion.swaggerYamlUrl || "",
},
logo,
externalUrl,
origUrl,
versions: versionsArray,
cardDescription,
cardDescriptionPlain,
categories,
tags,
integrations: api.integrations || [],
updated: preferredVersion.updated || "", // Include the updated field
};
} catch (error) {
console.error(`Error processing API ${key}:`, error);
return null;
}
if (
apiList.hasOwnProperty(key) &&
key.toLowerCase() === targetKey.toLowerCase()
) {
return processApiData(key, apiList[key]);
}
}

console.warn(
`No API found for provider: ${providerSlug}, service: ${serviceSlug}`
);
return null;
}

function processApiData(key: string, api: any) {
try {
const versions = api.versions || {};
const preferred = api.preferred || Object.keys(versions)[0] || "";
const preferredVersion = versions[preferred] || {};
const info = preferredVersion.info || {};
const externalDocs = preferredVersion.externalDocs || {};
const contact = info.contact || {};

const logo = {
url: info["x-logo"]?.url || "/assets/images/no-logo.svg",
backgroundColor: info["x-logo"]?.backgroundColor || null,
};

const externalUrl =
externalDocs.url ||
contact.url ||
(key.indexOf(".local") < 0 ? `https://${key.split(":")[0]}` : "");

let origUrl = "";
if (
info["x-origin"] &&
Array.isArray(info["x-origin"]) &&
info["x-origin"].length > 0
) {
origUrl = info["x-origin"][0]?.url || preferredVersion.swaggerUrl || "";
} else {
origUrl = preferredVersion.swaggerUrl || "";
}

const categories = info["x-apisguru-categories"] || [];
const tags = info["x-tags"] || [];

const versionsArray = Object.entries(versions).map(
([version, details]: [string, any]) => ({
version,
swaggerUrl: details?.swaggerUrl || "",
swaggerYamlUrl: details?.swaggerYamlUrl || "",
})
);

const description = info.description || "No description available";
const cardDescription = marked(description);
const cardDescriptionPlain = stripMarkdown(description);

return {
name: key,
preferred: api.preferred || "",
info,
api: {
swaggerUrl: preferredVersion.swaggerUrl || "",
swaggerYamlUrl: preferredVersion.swaggerYamlUrl || "",
},
logo,
externalUrl,
origUrl,
versions: versionsArray,
cardDescription,
cardDescriptionPlain,
categories,
tags,
integrations: api.integrations || [],
updated: preferredVersion.updated || "",
};
} catch (error) {
console.error(`Error processing API ${key}:`, error);
return null;
}
}

export async function generateStaticParams() {
const apiList = list as Record<string, any>;
const params: { providerSlug: string; serviceSlug: string }[] = [];
Expand Down Expand Up @@ -213,10 +218,7 @@ export default async function ApiPage({

return (
<div className="container mx-auto p-6 max-w-4xl">
<VisitCounter
providerSlug={providerSlug}
serviceSlug={serviceSlug}
/>
<VisitCounter providerSlug={providerSlug} serviceSlug={serviceSlug} />

<div className="flex flex-col md:flex-row gap-6 mb-8">
<div className="flex-shrink-0">
Expand Down
27 changes: 27 additions & 0 deletions app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import * as Sentry from "@sentry/nextjs";
import NextError from "next/error";
import { useEffect } from "react";

export default function GlobalError({
error,
}: {
error: Error & { digest?: string };
}) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);

return (
<html>
<body>
{/* `NextError` is the default Next.js error page component. Its type
definition requires a `statusCode` prop. However, since the App Router
does not expose status codes for errors, we simply pass 0 to render a
generic error message. */}
<NextError statusCode={0} />
</body>
</html>
);
}
13 changes: 0 additions & 13 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,6 @@ export default function RootLayout({

<Footer />

{/* Google Analytics - Replace with your GA ID */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
`}
</Script>
</body>
</html>
);
Expand Down
36 changes: 9 additions & 27 deletions components/ApiCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { ApiCardModel } from "../models/ApiCardModel";
import { Card, CardContent, CardHeader } from "./ui/card";
import Link from "next/link";
import Image from "next/image";
import type { ApiCard } from "@/types/api";

export default function ApiCard({ model }: { model: ApiCardModel }) {
export default function ApiCard({ model }: { model: ApiCard }) {
const [provider, service] = model.name.split(":");

const providerSlug = provider.toLowerCase();
Expand All @@ -19,45 +20,26 @@ export default function ApiCard({ model }: { model: ApiCardModel }) {
className="block hover:scale-105 transition-transform duration-200"
>
<Card className="flex flex-col text-center border border-[#388c9a] rounded-md bg-[#eee] overflow-hidden h-full cursor-pointer hover:shadow-lg transition-shadow duration-200">
{model.classes ? (
<span
className={`block w-[125px] py-1 px-5 relative text-center text-black top-[15px] -left-[28px] rotate-[-45deg] opacity-75 ${
model.classes.includes("flash-green")
? "bg-[#2c7]"
: model.classes.includes("flash-yellow")
? "bg-[#fed16e]"
: model.classes.includes("flash-red")
? "bg-red-500"
: ""
}`}
title={model.flashTitle}
>
<strong>{model.flashText}</strong>
</span>
) : (
<span className=""></span>
)}

<span className=""></span>
<CardHeader className="text-[#388c9a] pb-2">
<h2
className="font-normal mb-0.5 whitespace-nowrap text-ellipsis overflow-hidden"
title={model.info.title}
title={model.title}
>
{model.info.title}
{model.title}
</h2>
<p className="text-sm text-gray-600">v{model.preferred}</p>
<p className="text-sm text-gray-600">v{model.version}</p>
</CardHeader>

<CardContent className="p-[15px] bg-white flex-grow flex flex-col justify-between">
<div>
<Image
src={model.logo.url || "/assets/images/no-logo.svg"}
alt={`${model.info.title} API logo`}
src={model.logoUrl || "/assets/images/no-logo.svg"}
alt={`${model.title} API logo`}
width={80}
height={80}
className="max-w-full max-h-[80px] p-[5px] mx-auto mb-4"
style={{
backgroundColor: model.logo.backgroundColor || "transparent",
backgroundColor: "transparent",
}}
/>
<p className="leading-[1.2] overflow-hidden text-ellipsis h-[calc(1em*1.2*3)] text-sm text-gray-700">
Expand Down
12 changes: 5 additions & 7 deletions components/ApiGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from "react";
import { ApiCardModel } from "@/models/ApiCardModel";
import Card from "@/components/Card";

import Card from "./ApiCard";

import { CardSkeleton } from "@/components/ui/CardSkeleton";
import { ApiCard } from "@/types/api";

interface ApiGridProps {
cards: ApiCardModel[];
cards: ApiCard[];
searchTerm: string;
loading: boolean;
loadingMore: boolean;
Expand All @@ -26,7 +28,6 @@ export function ApiGrid({
}: ApiGridProps) {
return (
<section id="apis-list" className="cards">
{/* Show skeletons when loading initial data */}
{loading ? (
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mt-4">
{Array.from({ length: Math.min(pageSize, gridColumns * 2) }).map(
Expand All @@ -37,7 +38,6 @@ export function ApiGrid({
</div>
) : (
<>
{/* Show skeletons when loading more data */}
{loadingMore && (
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4 mt-4">
{Array.from({ length: Math.min(pageSize, gridColumns * 2) }).map(
Expand All @@ -61,10 +61,8 @@ export function ApiGrid({
</>
)}

{/* Intersection observer target */}
<div ref={observerRef} className="h-10 mt-4" />

{/* End of results indicator */}
{!hasMore && cards.length > 0 && (
<div className="text-center py-6 text-gray-500">
That's all the APIs! 🎉
Expand Down
7 changes: 0 additions & 7 deletions components/Card.tsx

This file was deleted.

Loading