Skip to content

Commit

Permalink
refactor: Status component rebuilt with SWR
Browse files Browse the repository at this point in the history
Main reason is to have only a single ajax request for status data on the
page, no matter how many status icons are embedded.

Also extract the icon portion into its own StatusIcon component that can
be better used in other components, especially inside other anchor tags
  • Loading branch information
kennethkalmer committed Nov 25, 2024
1 parent 76a0d49 commit 2a1bbc2
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 42 deletions.
68 changes: 34 additions & 34 deletions src/core/Status.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import React, { useEffect, useState } from "react";
import React from "react";
import useSWR from "swr";
import clsx from "clsx";

// Our SWR fetcher function
const fetcher = (url: string) => fetch(url).then((res) => res.json());

const indicatorClass = (indicator?: string) => {
switch (indicator) {
Expand All @@ -17,53 +22,48 @@ const indicatorClass = (indicator?: string) => {
}
};

export const StatusIcon = ({
statusUrl,
refreshInterval = 1000 * 60,
}: {
statusUrl: string;
refreshInterval?: number;
}) => {
const { data, error, isLoading } = useSWR(statusUrl, fetcher, {
refreshInterval,
});

return (
<span
className={clsx(
"inline-flex h-[1rem] aspect-square m-[0.25rem] rounded-full",
indicatorClass(data?.status?.indicator),
{ "animate-pulse": isLoading || error },
)}
></span>
);
};

const Status = ({
statusUrl,
additionalCSS,
refreshInterval = 1000 * 60,
}: {
statusUrl: string;
additionalCSS?: string;
refreshInterval?: number;
}) => {
const [data, setData] = useState<{ status: { indicator: string } } | null>(
null,
);

useEffect(() => {
let interval: NodeJS.Timeout;

if (statusUrl !== "") {
const fetchData = async () => {
try {
const response = await fetch(statusUrl);
const jsonData = await response.json();
setData(jsonData);
} catch (error) {
console.error("Error fetching status data:", error);
}
};

fetchData();

interval = setInterval(fetchData, 60000); // Fetch data every minute
}

return () => {
clearInterval(interval);
};
}, [statusUrl]);

return (
<a
href="https://status.ably.com"
className={`inline-block ${additionalCSS}`}
target="_blank"
rel="noreferrer"
>
<span className="flex items-center h-[1.5rem] p-[0.25rem]">
<span
className={`w-[1rem] h-[1rem] leading-[1rem] rounded-full ${!data ? "animate-pulse" : ""} ${indicatorClass(data?.status?.indicator)}`}
></span>
</span>
<StatusIcon
statusUrl={statusUrl}
refreshInterval={refreshInterval ?? 1000 * 60}
/>
</a>
);
};
Expand Down
42 changes: 34 additions & 8 deletions src/core/Status/Status.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { delay, http, HttpResponse } from "msw";
import { SWRConfig } from "swr";
import Status from "../Status";

const statusUrl = "https://ntqy1wz94gjv.statuspage.io/api/v2/status.json";
Expand All @@ -13,6 +14,10 @@ export default {
tags: ["!autodocs"],
};

const withEmptySWRCache = (component: JSX.Element) => (
<SWRConfig value={{ provider: () => new Map() }}>{component}</SWRConfig>
);

export const Loading = {
parameters: {
msw: {
Expand All @@ -29,10 +34,25 @@ export const Loading = {
},
},
},
render: () => <Status statusUrl={statusUrl} />,
render: () =>
withEmptySWRCache(<Status statusUrl={statusUrl} refreshInterval={0} />),
};

export const Error = {
parameters: {
msw: {
handlers: {
statusError: http.get(statusUrl, () => {
return HttpResponse.error();
}),
},
},
},
render: () =>
withEmptySWRCache(<Status statusUrl={statusUrl} refreshInterval={0} />),
};

const mockParametersWithStatus = (indicator) => {
const mockParametersWithStatus = (indicator: string) => {
return {
msw: {
handlers: {
Expand All @@ -52,30 +72,36 @@ const mockParametersWithStatus = (indicator) => {

export const None = {
parameters: mockParametersWithStatus("none"),
render: () => <Status statusUrl={statusUrl} />,
render: () =>
withEmptySWRCache(<Status statusUrl={statusUrl} refreshInterval={0} />),
};

export const Operational = {
parameters: mockParametersWithStatus("operational"),
render: () => <Status statusUrl={statusUrl} />,
render: () =>
withEmptySWRCache(<Status statusUrl={statusUrl} refreshInterval={0} />),
};

export const Minor = {
parameters: mockParametersWithStatus("minor"),
render: () => <Status statusUrl={statusUrl} />,
render: () =>
withEmptySWRCache(<Status statusUrl={statusUrl} refreshInterval={0} />),
};

export const Major = {
parameters: mockParametersWithStatus("major"),
render: () => <Status statusUrl={statusUrl} />,
render: () =>
withEmptySWRCache(<Status statusUrl={statusUrl} refreshInterval={0} />),
};

export const Critical = {
parameters: mockParametersWithStatus("critical"),
render: () => <Status statusUrl={statusUrl} />,
render: () =>
withEmptySWRCache(<Status statusUrl={statusUrl} refreshInterval={0} />),
};

export const Unknown = {
parameters: mockParametersWithStatus("unknown"),
render: () => <Status statusUrl={statusUrl} />,
render: () =>
withEmptySWRCache(<Status statusUrl={statusUrl} refreshInterval={0} />),
};

0 comments on commit 2a1bbc2

Please sign in to comment.