Openstatus www.openstatus.dev

⌛ add suspense (#447)

* ⌛

* ⌛

* ⌛

* chore: suspense

* feat: widget docs and suspense

* chore: docs

* chore: bump version

* feat: add props to package.json

---------

Co-authored-by: mxkaske <maximilian@kaske.org>

authored by

Thibault Le Ouay
mxkaske
and committed by
GitHub
f9f404a1 7fbeba1d

+246 -88
+4 -71
apps/web/src/app/page.tsx
··· 1 - import Link from "next/link"; 2 - import { ClerkProvider } from "@clerk/nextjs"; 3 - import { ChevronRight } from "lucide-react"; 4 - 5 - import { Badge, Button } from "@openstatus/ui"; 6 - 7 1 import { Shell } from "@/components/dashboard/shell"; 8 2 import { MarketingLayout } from "@/components/layout/marketing-layout"; 9 3 import { Cards, SpecialCard } from "@/components/marketing/cards"; 4 + import { Example } from "@/components/marketing/example"; 10 5 import { FAQs } from "@/components/marketing/faqs"; 6 + import { Hero } from "@/components/marketing/hero"; 11 7 import { Partners } from "@/components/marketing/partners"; 12 8 import { Plans } from "@/components/marketing/plans"; 13 9 import { Stats } from "@/components/marketing/stats"; 14 - import { Tracker } from "@/components/tracker"; 15 10 import { cardConfig, specialCardConfig } from "@/config/features"; 16 - import { getGitHubStars } from "@/lib/github"; 17 - import { getHomeMonitorListData } from "@/lib/tb"; 18 - import { numberFormatter } from "@/lib/utils"; 19 11 20 12 export const revalidate = 600; 21 13 22 14 export default async function Page() { 23 - const data = await getHomeMonitorListData(); 24 - const stars = await getGitHubStars(); 25 15 return ( 26 16 <MarketingLayout> 27 17 <div className="grid gap-8"> 28 - <Shell className="text-center"> 29 - <Link 30 - href="https://twitter.com/mxkaske/status/1685666982786404352?s=20" 31 - target="_blank" 32 - rel="noreferrer" 33 - > 34 - <Badge variant="outline"> 35 - Announcement Post <ChevronRight className="ml-1 h-3 w-3" /> 36 - </Badge> 37 - </Link> 38 - <h1 className="text-foreground font-cal mb-6 mt-2 text-3xl"> 39 - Open-source monitoring service 40 - </h1> 41 - <p className="text-muted-foreground mx-auto mb-6 max-w-lg text-lg"> 42 - OpenStatus is an open source monitoring services with incident 43 - managements. 44 - </p> 45 - {/* much better than using flex without text alignment, text stays center even thought not same length */} 46 - <div className="my-4 grid gap-2 sm:grid-cols-2"> 47 - <div className="text-center sm:block sm:text-right"> 48 - <Button className="w-48 rounded-full sm:w-auto" asChild> 49 - <Link href="/app/sign-up">Get Started</Link> 50 - </Button> 51 - </div> 52 - <div className="text-center sm:block sm:text-left"> 53 - <Button 54 - variant="outline" 55 - className="w-48 rounded-full sm:w-auto" 56 - asChild 57 - > 58 - <Link href="/github" target="_blank"> 59 - Star on GitHub{" "} 60 - <Badge variant="secondary" className="ml-1 hidden sm:block"> 61 - {numberFormatter(stars)} 62 - </Badge> 63 - <Badge variant="secondary" className="ml-1 block sm:hidden"> 64 - {stars} 65 - </Badge> 66 - </Link> 67 - </Button> 68 - </div> 69 - </div> 70 - </Shell> 71 - <Shell className="text-center"> 72 - <h2 className="font-cal mb-3 text-2xl">Status</h2> 73 - <Button asChild variant="outline" className="rounded-full"> 74 - <Link href="/play">Playground</Link> 75 - </Button> 76 - <div className="mx-auto max-w-md"> 77 - {data && ( 78 - <Tracker 79 - data={data} 80 - id="openstatusPing" 81 - name="Ping" 82 - url="https://www.openstatus.dev/api/ping" 83 - /> 84 - )} 85 - </div> 86 - </Shell> 18 + <Hero /> 19 + <Example /> 87 20 <Cards {...cardConfig.monitors} /> 88 21 <Stats /> 89 22 <Cards {...cardConfig.incidents} />
+1 -1
apps/web/src/app/play/page.tsx
··· 13 13 {data && ( 14 14 <Tracker 15 15 data={data} 16 - id="1" 16 + id={1} 17 17 name="Ping" 18 18 url="https://www.openstatus.dev/api/ping" 19 19 />
+2 -3
apps/web/src/components/layout/marketing-footer.tsx
··· 1 1 import Link from "next/link"; 2 2 import { ArrowUpRight } from "lucide-react"; 3 3 4 - import { StatusWidget } from "@openstatus/react"; 5 - 6 4 import { cn } from "@/lib/utils"; 7 5 import { Shell } from "../dashboard/shell"; 6 + import { StatusWidgetContainer } from "./status-widget-suspense"; 8 7 9 8 interface Props { 10 9 className?: string; ··· 16 15 <Shell className="grid gap-6"> 17 16 <div className="grid grid-cols-2 gap-6 md:grid-cols-4"> 18 17 <div className="order-4 md:order-1"> 19 - <StatusWidget slug="status" /> 18 + <StatusWidgetContainer slug="status" /> 20 19 </div> 21 20 <div className="order-1 flex flex-col gap-3 text-sm md:order-2"> 22 21 <p className="text-foreground font-semibold">Community</p>
+21
apps/web/src/components/layout/status-widget-suspense.tsx
··· 1 + import { Suspense } from "react"; 2 + 3 + import type { StatusWidgetProps } from "@openstatus/react"; 4 + import { StatusWidget } from "@openstatus/react"; 5 + 6 + export function StatusWidgetFallback() { 7 + return ( 8 + <div className="flex max-w-fit items-center gap-2 rounded-md border border-gray-200 px-3 py-1 text-sm text-gray-700 hover:bg-gray-100 hover:text-black"> 9 + <span className="h-5 w-20 animate-pulse rounded-md bg-gray-600/10" /> 10 + <span className="relative inline-flex h-2 w-2 rounded-full bg-black/10" /> 11 + </div> 12 + ); 13 + } 14 + 15 + export function StatusWidgetContainer(props: StatusWidgetProps) { 16 + return ( 17 + <Suspense fallback={<StatusWidgetFallback />}> 18 + <StatusWidget {...props} /> 19 + </Suspense> 20 + ); 21 + }
+48
apps/web/src/components/marketing/example.tsx
··· 1 + import { Suspense } from "react"; 2 + import Link from "next/link"; 3 + 4 + import { Button } from "@openstatus/ui"; 5 + 6 + import { Shell } from "@/components/dashboard/shell"; 7 + import { Tracker } from "@/components/tracker"; 8 + import { getHomeMonitorListData } from "@/lib/tb"; 9 + 10 + export async function Example() { 11 + return ( 12 + <Shell className="text-center"> 13 + <h2 className="font-cal mb-3 text-2xl">Status</h2> 14 + <Button asChild variant="outline" className="rounded-full"> 15 + <Link href="/play">Playground</Link> 16 + </Button> 17 + <div className="mx-auto max-w-md"> 18 + <Suspense fallback={<ExampleTrackerFallback />}> 19 + <ExampleTracker /> 20 + </Suspense> 21 + </div> 22 + </Shell> 23 + ); 24 + } 25 + 26 + function ExampleTrackerFallback() { 27 + return ( 28 + <Tracker 29 + data={[]} 30 + id={1} 31 + name="Ping" 32 + url="https://www.openstatus.dev/api/ping" 33 + /> 34 + ); 35 + } 36 + 37 + async function ExampleTracker() { 38 + const data = await getHomeMonitorListData(); 39 + if (!data) return null; 40 + return ( 41 + <Tracker 42 + data={data} 43 + id={1} 44 + name="Ping" 45 + url="https://www.openstatus.dev/api/ping" 46 + /> 47 + ); 48 + }
+76
apps/web/src/components/marketing/hero.tsx
··· 1 + import { Suspense } from "react"; 2 + import Link from "next/link"; 3 + import { ChevronRight } from "lucide-react"; 4 + 5 + import { Badge, Button } from "@openstatus/ui"; 6 + 7 + import { Shell } from "@/components/dashboard/shell"; 8 + import { getGitHubStars } from "@/lib/github"; 9 + import { numberFormatter } from "@/lib/utils"; 10 + 11 + export function Hero() { 12 + return ( 13 + <Shell className="text-center"> 14 + <Link 15 + href="https://twitter.com/mxkaske/status/1685666982786404352?s=20" 16 + target="_blank" 17 + rel="noreferrer" 18 + > 19 + <Badge variant="outline"> 20 + Announcement Post <ChevronRight className="ml-1 h-3 w-3" /> 21 + </Badge> 22 + </Link> 23 + <h1 className="text-foreground font-cal mb-6 mt-2 text-3xl"> 24 + Open-source monitoring service 25 + </h1> 26 + <p className="text-muted-foreground mx-auto mb-6 max-w-lg text-lg"> 27 + OpenStatus is an open source monitoring services with incident 28 + managements. 29 + </p> 30 + {/* much better than using flex without text alignment, text stays center even thought not same length */} 31 + <div className="my-4 grid gap-2 sm:grid-cols-2"> 32 + <div className="text-center sm:block sm:text-right"> 33 + <Button className="w-48 rounded-full sm:w-auto" asChild> 34 + <Link href="/app/sign-up">Get Started</Link> 35 + </Button> 36 + </div> 37 + <div className="text-center sm:block sm:text-left"> 38 + <Button 39 + variant="outline" 40 + className="w-48 rounded-full sm:w-auto" 41 + asChild 42 + > 43 + <Link href="/github" target="_blank"> 44 + Star on GitHub{" "} 45 + <Suspense fallback={<StarsBadgeFallback />}> 46 + <StarsBadge /> 47 + </Suspense> 48 + </Link> 49 + </Button> 50 + </div> 51 + </div> 52 + </Shell> 53 + ); 54 + } 55 + 56 + function StarsBadgeFallback() { 57 + return ( 58 + <Badge variant="secondary" className="ml-1"> 59 + ~ 60 + </Badge> 61 + ); 62 + } 63 + 64 + async function StarsBadge() { 65 + const stars = await getGitHubStars(); 66 + return ( 67 + <> 68 + <Badge variant="secondary" className="ml-1 hidden sm:block"> 69 + {numberFormatter(stars)} 70 + </Badge> 71 + <Badge variant="secondary" className="ml-1 block sm:hidden"> 72 + {stars} 73 + </Badge> 74 + </> 75 + ); 76 + }
+1 -1
apps/web/src/components/tracker.tsx
··· 157 157 <p className="text-sm font-semibold">{getStatus(ratio).label}</p> 158 158 {context === "play" ? ( 159 159 <Link 160 - href={`/monitor/openstatusPing?fromDate=${cronTimestamp}&toDate=${toDate}`} 160 + href={`/monitor/1?fromDate=${cronTimestamp}&toDate=${toDate}`} 161 161 className="text-muted-foreground hover:text-foreground" 162 162 > 163 163 <Eye className="h-4 w-4" />
+1
apps/web/src/lib/github.ts
··· 7 7 export async function getGitHubStars() { 8 8 const res = await fetch( 9 9 "https://api.github.com/repos/openstatusHQ/openstatus", 10 + { next: { revalidate: 600 } }, // 10min 10 11 ); 11 12 const json = await res.json(); 12 13 const github = schema.safeParse(json);
+82 -9
packages/react/README.md
··· 1 1 ## Status Widget 2 2 3 - Create an account on [openstatus.dev](https://openstatus.dev) and integrate a 4 - simple wiget into your React Application. 3 + Create an account on [openstatus.dev](https://openstatus.dev), start monitoring 4 + your endpoints and include your own StatusWidget into your React Application. 5 + 6 + ![Image of StatusWidget on openstatus.dev](https://openstatus.dev/assets/changelog/status-widget.png) 5 7 6 8 ### Install 7 9 8 10 ```bash 9 11 npm install @openstatus/react 12 + pnpm add @openstatus/react 13 + yarn add @openstatus/react 14 + bun add @openstatus/react 10 15 ``` 11 16 12 - ```bash 13 - pnpm add @openstatus/react 17 + ### How to use the StatusWidget in your Next.js App Router 18 + 19 + #### Include the styles.css 20 + 21 + If you are using tailwind, extend your config with: 22 + 23 + ```js 24 + // tailwind.config.js 25 + module.exports = { 26 + content: [ 27 + "./app/**/*.{tsx,ts,mdx,md}", 28 + // OpenStatus Widget 29 + "./node_modules/@openstatus/react/**/*.{js,ts,jsx,tsx}", 30 + ], 31 + theme: { 32 + extend: {}, 33 + }, 34 + plugins: [], 35 + }; 36 + ``` 37 + 38 + Otherwise, include the styles in your App: 39 + 40 + ```tsx 41 + // app/layout.tsx 42 + import "@openstatus/react/dist/styles.css"; 43 + ``` 44 + 45 + The `StatusWidget` is a React Server Component. Include the `slug` to your 46 + status-page. 47 + 48 + ```tsx 49 + import { StatusWidget } from "@openstatus/react"; 50 + 51 + export function Page() { 52 + return <StatusWidget slug="status" />; 53 + } 14 54 ``` 15 55 16 - ```bash 17 - yarn add @openstatus/react 56 + ### Headless getStatus utility function 57 + 58 + If you would like to style it yourself, you can use the `getStatus` function to 59 + access the type response of the api call to: 60 + 61 + `https://api.openstatus.dev/public/status/:slug` 62 + 63 + Learn more about our supported 64 + [API endpoints](https://docs.openstatus.dev/api-reference/auth). 65 + 66 + ```ts 67 + import { getStatus } from "@openstatus/react"; 68 + 69 + // React Server Component 70 + async function CustomStatusWidget() { 71 + const res = await getStatus("slug"); 72 + // ^StatusResponse = { status: Status } 73 + 74 + const { status } = res; 75 + // ^Status = "unknown" | "operational" | "degraded_performance" | "partial_outage" | "major_outage" | "under_maintenance" 76 + 77 + return <div>{/* customize */}</div>; 78 + } 18 79 ``` 19 80 20 - ```bash 21 - bun add @openstatus/react 81 + ```ts 82 + export type Status = 83 + | "operational" 84 + | "degraded_performance" 85 + | "partial_outage" 86 + | "major_outage" 87 + | "under_maintenance" 88 + | "unknown"; 22 89 ``` 23 90 24 - Learn more [here](https://docs.openstatus.dev/packages/react). 91 + Learn more in the [docs](https://docs.openstatus.dev/packages/react). 92 + 93 + #### About OpenStatus 94 + 95 + OpenStatus is an open source monitoring services with incident managements. 96 + 97 + Follow our journey [@openstatusHQ](https://x.com/openstatusHQ).
+9 -2
packages/react/package.json
··· 1 1 { 2 2 "name": "@openstatus/react", 3 - "version": "0.0.1", 3 + "version": "0.0.2", 4 + "repository": { 5 + "type": "git", 6 + "url": "https://github.com/openstatusHQ/openstatus.git" 7 + }, 8 + "keywords": ["openstatus", "react", "monitoring"], 9 + "homepage": "https://github.com/openstatusHQ/openstatus#readme", 4 10 "main": "./dist/index.js", 5 11 "types": "./dist/index.d.ts", 6 12 "module": "./dist/index.mjs", ··· 9 15 ], 10 16 "license": "MIT", 11 17 "scripts": { 12 - "dev":"tsup && pnpm run build:tailwind", 18 + "dev":"tsup --watch && pnpm run dev:tailwind", 19 + "dev:tailwind": "tailwindcss -i ./src/styles.css -o ./dist/styles.css --watch", 13 20 "build:tailwind": "tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify", 14 21 "build": "tsup && pnpm run build:tailwind" 15 22 },
+1 -1
packages/react/src/widget.tsx
··· 23 23 return { status: "unknown" }; 24 24 } 25 25 26 - type StatusWidgetProps = { 26 + export type StatusWidgetProps = { 27 27 slug: string; 28 28 href?: string; 29 29 };