Skip to content

Commit

Permalink
V2 Maintenance banner
Browse files Browse the repository at this point in the history
  • Loading branch information
quietbits committed Feb 28, 2024
1 parent 32b4b34 commit 0137725
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 27 deletions.
2 changes: 1 addition & 1 deletion next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
// output: "export",
distDir: "build",
};

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
},
"dependencies": {
"@stellar/design-system": "^2.0.0-beta.4",
"@tanstack/react-query": "^5.24.1",
"@tanstack/react-query-devtools": "^5.24.1",
"dompurify": "^3.0.9",
"html-react-parser": "^5.1.8",
"immer": "^10.0.3",
"next": "14.0.4",
"react": "^18",
Expand All @@ -23,6 +27,7 @@
"zustand-querystring": "^0.0.19"
},
"devDependencies": {
"@types/dompurify": "^3.0.5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
7 changes: 5 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";

import { LayoutMain } from "@/components/layout/LayoutMain";
import { QueryProvider } from "@/query/QueryProvider";
import { StoreProvider } from "@/store/StoreProvider";

import "@stellar/design-system/build/styles.min.css";
Expand All @@ -20,9 +21,11 @@ export default function RootLayout({
return (
<html lang="en">
<body>
<div id="root" className="LabLayout">
<div id="root">
<StoreProvider>
<LayoutMain>{children}</LayoutMain>
<QueryProvider>
<LayoutMain>{children}</LayoutMain>
</QueryProvider>
</StoreProvider>
</div>
</body>
Expand Down
53 changes: 53 additions & 0 deletions src/components/MaintenanceBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Banner, Link } from "@stellar/design-system";

import { getRelevantMaintenanceMsg } from "@/helpers/getRelevantMaintenanceMsg";
import { sanitizeHtml } from "@/helpers/sanitizeHtml";
import { useMaintenanceData } from "@/query/useMaintenanceData";
import { useStore } from "@/store/useStore";

export const MaintenanceBanner = () => {
const { network } = useStore();
const { data, error } = useMaintenanceData();

const relevantMaintenance = getRelevantMaintenanceMsg(network.id, data);

const renderBanner = (message: React.ReactNode) => (
<Banner variant="primary">{message}</Banner>
);

if (error) {
return renderBanner(error.message);
}

if (!relevantMaintenance) {
return network.id === "testnet"
? renderBanner(
<>
Failed to fetch testnet reset date. Check status{" "}
<Link href="https://9sl3dhr1twv1.statuspage.io/">here</Link>.
</>,
)
: null;
}

if (relevantMaintenance.length === 0) {
return network.id === "testnet"
? renderBanner("The next testnet reset has not yet been scheduled.")
: null;
}

const nextMaintenance = relevantMaintenance[0];
const date = new Date(nextMaintenance.scheduled_for);

return renderBanner(
<>
<Link href={`https://status.stellar.org/incidents/${nextMaintenance.id}`}>
{nextMaintenance.name}
</Link>{" "}
on {date.toDateString()} at {date.toTimeString()}
{nextMaintenance.incident_updates.map((update) => (
<div key={update.id}>{sanitizeHtml(update.body)}</div>
))}
</>,
);
};
34 changes: 19 additions & 15 deletions src/components/layout/LayoutMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,31 @@ import { ProjectLogo, ThemeSwitch } from "@stellar/design-system";

import { MainNav } from "@/components/MainNav";
import { NetworkSelector } from "@/components/NetworkSelector";
import { MaintenanceBanner } from "@/components/MaintenanceBanner";

export const LayoutMain = ({ children }: { children: ReactNode }) => {
return (
<>
<div className="LabLayout__header">
<header className="LabLayout__header__main">
<ProjectLogo
title="Laboratory"
link="/"
customAnchor={<Link href="/" />}
/>
<div className="LabLayout">
<div>
<MaintenanceBanner />
<div className="LabLayout__header">
<header className="LabLayout__header__main">
<ProjectLogo
title="Laboratory"
link="/"
customAnchor={<Link href="/" />}
/>

<div className="LabLayout__header__settings">
<ThemeSwitch storageKeyId="stellarTheme:Laboratory" />
<NetworkSelector />
</div>
</header>
<MainNav />
<div className="LabLayout__header__settings">
<ThemeSwitch storageKeyId="stellarTheme:Laboratory" />
<NetworkSelector />
</div>
</header>
<MainNav />
</div>
</div>

{children}
</>
</div>
);
};
20 changes: 20 additions & 0 deletions src/helpers/getRelevantMaintenanceMsg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NetworkType, StatusPageScheduled } from "@/types/types";

export const getRelevantMaintenanceMsg = (
currentNetwork: NetworkType,
allMaintenance?: StatusPageScheduled[],
) => {
if (!allMaintenance) {
return false;
}

// If we’re on the test network, we care about all scheduled maintenance. If
// we’re on the public network, we only care about public network maintenance.
return allMaintenance.filter((maintenance) =>
maintenance.components.some((component) =>
currentNetwork === "testnet"
? true
: component.name === "Stellar Public Network",
),
);
};
6 changes: 6 additions & 0 deletions src/helpers/sanitizeHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import parse from "html-react-parser";
import DOMPurify from "dompurify";

export const sanitizeHtml = (html: string | Node) => {
return parse(DOMPurify.sanitize(html, { USE_PROFILES: { html: true } }));
};
22 changes: 22 additions & 0 deletions src/query/QueryProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: false,
},
},
});

export const QueryProvider = ({ children }: { children: React.ReactNode }) => {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
};
22 changes: 22 additions & 0 deletions src/query/useMaintenanceData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useQuery } from "@tanstack/react-query";
import { StatusPageScheduled } from "@/types/types";

export const useMaintenanceData = () => {
const query = useQuery<StatusPageScheduled[]>({
queryKey: ["maintenanceData"],
queryFn: async () => {
try {
const scheduleResponse = await fetch(
"https://9sl3dhr1twv1.statuspage.io/api/v2/summary.json",
);
const scheduleResponseJson = await scheduleResponse.json();

return scheduleResponseJson.scheduled_maintenances;
} catch (e) {
throw Error("Failed to fetch testnet reset date.");
}
},
});

return query;
};
19 changes: 10 additions & 9 deletions src/styles/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@

// Layout
#root {
&.LabLayout {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
height: 100vh;
overflow: hidden;
min-width: 0;
min-height: 0;
}
min-width: auto !important;
min-height: auto !important;
display: block !important;
}

.LabLayout {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
height: 100vh;
overflow: hidden;
min-width: 0;
min-height: 0;
color: var(--sds-clr-gray-11);
background-color: var(--sds-clr-gray-02);

Expand Down
22 changes: 22 additions & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,25 @@ export type Network = {
rpcUrl: string;
passphrase: string;
};

export type StatusPageComponent = {
[key: string]: any;
id: string;
name: string;
};

export type StatusPageIncident = {
[key: string]: any;
id: string;
name: string;
body: string;
};

export type StatusPageScheduled = {
[key: string]: any;
id: string;
name: string;
scheduled_for: string;
components: StatusPageComponent[];
incident_updates: StatusPageIncident[];
};
Loading

0 comments on commit 0137725

Please sign in to comment.