Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: @openstatus/react (#366)

* feat: create first npm package

* feat: docs and package update

* chore: update readme

authored by

Maximilian Kaske and committed by
GitHub
2c4562a8 619fc468

+281 -78
+1
apps/docs/pages/_meta.json
··· 4 4 "integrations": "Integrations", 5 5 "developer-guide": "Developer guide", 6 6 "api-server": "API", 7 + "packages": "Packages", 7 8 "contact": { 8 9 "title": "🌐 Website", 9 10 "type": "page",
+8
apps/docs/pages/api-server/status-widget.mdx
··· 1 + import { Callout } from "nextra/components"; 2 + 1 3 # Public Status Widget 2 4 3 5 We have added a public endpoint where you can access the status of your status ··· 7 9 ```bash 8 10 curl https://api.openstatus.dev/public/status/:slug 9 11 ``` 12 + 13 + <Callout type="info"> 14 + We have released an `@openstatus/react` npm package that allows you to easily 15 + integrate the pre-typed status into your React projects or use the default 16 + widget. Read more [here](/packages/react). 17 + </Callout> 10 18 11 19 The response is a JSON object with the following structure: 12 20
+3
apps/docs/pages/packages/_meta.json
··· 1 + { 2 + "react": "@openstatus/react" 3 + }
+64
apps/docs/pages/packages/react.mdx
··· 1 + ## React Server Component 2 + 3 + ```tsx 4 + import { StatusWidget } from "@openstatus/react"; 5 + 6 + export function Page() { 7 + return <StatusWidget slug="status" />; 8 + } 9 + ``` 10 + 11 + It will automatically attach the slug to the href to allow the user to open a 12 + new tab on click to `https://slug.openstatus.dev`. If you want to redirect him 13 + to a specific page, use the `href` property, like so: 14 + 15 + ```tsx 16 + <StatusWidget slug="documenso" href="https://status.documenso.com" /> 17 + ``` 18 + 19 + > `StatusWidget` is an **async function** and will only work with RSC. Using it 20 + > within a dead simple React App will not work. 21 + 22 + ### Styling 23 + 24 + #### With tailwindcss 25 + 26 + ```ts 27 + // tailwind.config.js 28 + module.exports = { 29 + content: [ 30 + "./app/**/*.{tsx,ts,mdx,md}", 31 + "./node_modules/@openstatus/react/**/*.{js,ts,jsx,tsx}", 32 + ], 33 + theme: { 34 + extend: {}, 35 + }, 36 + plugins: [], 37 + }; 38 + ``` 39 + 40 + #### Without tailwindcss 41 + 42 + ```tsx 43 + // app/layout.tsx 44 + import "@openstatus/react/dist/styles.css"; 45 + ``` 46 + 47 + ## Typed fetch function 48 + 49 + ```tsx 50 + import { getStatus } from "@openstatus/react"; 51 + 52 + // React Server Component 53 + async function CustomStatusWidget() { 54 + const res = await getStatus("slug"); 55 + // ^StatusResponse = { status: Status } 56 + 57 + const { status } = res; 58 + // ^Status = "unknown" | "operational" | "degraded_performance" | "partial_outage" | "major_outage" | "under_maintenance" 59 + 60 + return <div>{/* customize */}</div>; 61 + } 62 + ``` 63 + 64 + [Link to npm](https://www.npmjs.com/package/@openstatus/react)
+1
apps/web/package.json
··· 19 19 "@openstatus/emails": "workspace:*", 20 20 "@openstatus/notification-emails": "workspace:*", 21 21 "@openstatus/plans": "workspace:*", 22 + "@openstatus/react": "workspace:*", 22 23 "@openstatus/tinybird": "workspace:*", 23 24 "@openstatus/ui": "workspace:*", 24 25 "@openstatus/upstash": "workspace:*",
+2 -1
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 "@/components/status-widget"; 4 + import { StatusWidget } from "@openstatus/react"; 5 + 5 6 import { cn } from "@/lib/utils"; 6 7 import { Shell } from "../dashboard/shell"; 7 8
-75
apps/web/src/components/status-widget.tsx
··· 1 - import * as z from "zod"; 2 - 3 - const statusEnum = z.enum([ 4 - "operational", 5 - "degraded_performance", 6 - "partial_outage", 7 - "major_outage", 8 - "under_maintenance", 9 - "unknown", 10 - ]); 11 - 12 - const statusSchema = z.object({ status: statusEnum }); 13 - 14 - const dictionary = { 15 - operational: { 16 - label: "Operational", 17 - color: "bg-green-500", 18 - }, 19 - degraded_performance: { 20 - label: "Degraded Performance", 21 - color: "bg-yellow-500", 22 - }, 23 - partial_outage: { 24 - label: "Partial Outage", 25 - color: "bg-yellow-500", 26 - }, 27 - major_outage: { 28 - label: "Major Outage", 29 - color: "bg-red-500", 30 - }, 31 - unknown: { 32 - label: "Unknown", 33 - color: "bg-gray-500", 34 - }, 35 - under_maintenance: { 36 - label: "Under Maintenance", 37 - color: "bg-gray-500", 38 - }, 39 - } as const; 40 - 41 - export async function StatusWidget({ slug }: { slug: string }) { 42 - const res = await fetch(`https://api.openstatus.dev/public/status/${slug}`, { 43 - next: { revalidate: 60 }, // cache request for 60 seconds 44 - }); 45 - const data = await res.json(); 46 - const parsed = statusSchema.safeParse(data); 47 - 48 - if (!parsed.success) { 49 - return null; 50 - } 51 - 52 - const key = parsed.data.status; 53 - const { label, color } = dictionary[key]; 54 - 55 - return ( 56 - <a 57 - className="border-border text-foreground/70 hover:bg-muted hover:text-foreground inline-flex max-w-fit items-center gap-2 rounded-md border px-3 py-1 text-sm" 58 - href={`https://${slug}.openstatus.dev`} 59 - target="_blank" 60 - rel="noreferrer" 61 - > 62 - {label} 63 - <span className="relative flex h-2 w-2"> 64 - {parsed.data.status === "operational" ? ( 65 - <span 66 - className={`absolute inline-flex h-full w-full animate-ping rounded-full ${color} opacity-75 duration-1000`} 67 - /> 68 - ) : null} 69 - <span 70 - className={`relative inline-flex h-2 w-2 rounded-full ${color}`} 71 - /> 72 - </span> 73 - </a> 74 - ); 75 - }
+1
apps/web/tailwind.config.ts
··· 14 14 // our vercel integration 15 15 "../../packages/integrations/**/*.{ts,tsx}", 16 16 "../../packages/ui/**/*.{ts,tsx}", 17 + "./node_modules/@openstatus/react/**/*.{js,ts,jsx,tsx}", 17 18 ], 18 19 theme: { 19 20 /* Tremor */
+24
packages/react/README.md
··· 1 + ## Status Widget 2 + 3 + Create an account on [openstatus.dev](https://openstatus.dev) and integrate a 4 + simple wiget into your React Application. 5 + 6 + ### Install 7 + 8 + ```bash 9 + npm install @openstatus/react 10 + ``` 11 + 12 + ```bash 13 + pnpm add @openstatus/react 14 + ``` 15 + 16 + ```bash 17 + yarn add @openstatus/react 18 + ``` 19 + 20 + ```bash 21 + bun add @openstatus/react 22 + ``` 23 + 24 + Learn more [here](https://docs.openstatus.dev/packages/react).
+26
packages/react/package.json
··· 1 + { 2 + "name": "@openstatus/react", 3 + "version": "0.0.1", 4 + "main": "./dist/index.js", 5 + "types": "./dist/index.d.ts", 6 + "module": "./dist/index.mjs", 7 + "files": [ 8 + "./dist/**" 9 + ], 10 + "license": "MIT", 11 + "scripts": { 12 + "build:tailwind": "tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify", 13 + "build": "tsup && pnpm run build:tailwind" 14 + }, 15 + "devDependencies": { 16 + "@openstatus/tsconfig": "workspace:*", 17 + "@types/node": "20.8.0", 18 + "@types/react": "18.2.24", 19 + "tailwindcss": "3.3.2", 20 + "tsup": "7.2.0", 21 + "typescript": "5.2.2" 22 + }, 23 + "peerDependencies": { 24 + "react": "^18.0.0" 25 + } 26 + }
+1
packages/react/src/index.ts
··· 1 + export * from "./widget";
+3
packages/react/src/styles.css
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities;
+26
packages/react/src/utils.ts
··· 1 + export const statusDictionary = { 2 + operational: { 3 + label: "Operational", 4 + color: "bg-green-500", 5 + }, 6 + degraded_performance: { 7 + label: "Degraded Performance", 8 + color: "bg-yellow-500", 9 + }, 10 + partial_outage: { 11 + label: "Partial Outage", 12 + color: "bg-yellow-500", 13 + }, 14 + major_outage: { 15 + label: "Major Outage", 16 + color: "bg-red-500", 17 + }, 18 + unknown: { 19 + label: "Unknown", 20 + color: "bg-gray-500", 21 + }, 22 + under_maintenance: { 23 + label: "Under Maintenance", 24 + color: "bg-gray-500", 25 + }, 26 + } as const;
+57
packages/react/src/widget.tsx
··· 1 + import { statusDictionary } from "./utils"; 2 + 3 + export type Status = 4 + | "operational" 5 + | "degraded_performance" 6 + | "partial_outage" 7 + | "major_outage" 8 + | "under_maintenance" 9 + | "unknown"; 10 + 11 + export type StatusResponse = { status: Status }; 12 + 13 + export async function getStatus(slug: string): Promise<StatusResponse> { 14 + const res = await fetch(`https://api.openstatus.dev/public/status/${slug}`, { 15 + cache: "no-cache", 16 + }); 17 + 18 + if (res.ok) { 19 + const data = (await res.json()) as StatusResponse; 20 + return data; 21 + } 22 + 23 + return { status: "unknown" }; 24 + } 25 + 26 + type StatusWidgetProps = { 27 + slug: string; 28 + href?: string; 29 + }; 30 + 31 + export async function StatusWidget({ slug, href }: StatusWidgetProps) { 32 + const data = await getStatus(slug); 33 + 34 + const key = data.status; 35 + const { label, color } = statusDictionary[key]; 36 + 37 + return ( 38 + <a 39 + className="inline-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" 40 + href={href || `https://${slug}.openstatus.dev`} 41 + target="_blank" 42 + rel="noreferrer" 43 + > 44 + {label} 45 + <span className="relative flex h-2 w-2"> 46 + {data.status === "operational" ? ( 47 + <span 48 + className={`absolute inline-flex h-full w-full animate-ping rounded-full ${color} opacity-75 duration-1000`} 49 + /> 50 + ) : null} 51 + <span 52 + className={`relative inline-flex h-2 w-2 rounded-full ${color}`} 53 + /> 54 + </span> 55 + </a> 56 + ); 57 + }
+8
packages/react/tailwind.config.js
··· 1 + /** @type {import('tailwindcss').Config} */ 2 + module.exports = { 3 + content: ["./src/**/*.{ts,tsx}"], 4 + theme: { 5 + extend: {}, 6 + }, 7 + plugins: [], 8 + };
+14
packages/react/tsconfig.json
··· 1 + { 2 + "extends": "@openstatus/tsconfig/react-library.json", 3 + "exclude": ["node_modules"], 4 + "compilerOptions": { 5 + "esModuleInterop": true, 6 + "forceConsistentCasingInFileNames": true, 7 + "isolatedModules": true, 8 + "moduleResolution": "node", 9 + "preserveWatchOutput": true, 10 + "skipLibCheck": true, 11 + "noEmit": true, 12 + "strict": true 13 + } 14 + }
+11
packages/react/tsup.config.js
··· 1 + import { defineConfig } from "tsup"; 2 + 3 + export default defineConfig({ 4 + entry: ["src/index.ts"], 5 + format: ["cjs", "esm"], 6 + splitting: false, 7 + sourcemap: true, 8 + clean: true, 9 + bundle: true, 10 + dts: true, 11 + });
+31 -2
pnpm-lock.yaml
··· 136 136 '@openstatus/plans': 137 137 specifier: workspace:* 138 138 version: link:../../packages/plans 139 + '@openstatus/react': 140 + specifier: workspace:* 141 + version: link:../../packages/react 139 142 '@openstatus/tinybird': 140 143 specifier: workspace:* 141 144 version: link:../../packages/tinybird ··· 623 626 specifier: 5.2.2 624 627 version: 5.2.2 625 628 629 + packages/react: 630 + dependencies: 631 + react: 632 + specifier: ^18.0.0 633 + version: 18.2.0 634 + devDependencies: 635 + '@openstatus/tsconfig': 636 + specifier: workspace:* 637 + version: link:../tsconfig 638 + '@types/node': 639 + specifier: 20.8.0 640 + version: 20.8.0 641 + '@types/react': 642 + specifier: 18.2.24 643 + version: 18.2.24 644 + tailwindcss: 645 + specifier: 3.3.2 646 + version: 3.3.2 647 + tsup: 648 + specifier: 7.2.0 649 + version: 7.2.0(postcss@8.4.31)(typescript@5.2.2) 650 + typescript: 651 + specifier: 5.2.2 652 + version: 5.2.2 653 + 626 654 packages/tinybird: 627 655 dependencies: 628 656 '@chronark/zod-bird': ··· 785 813 version: 20.8.0 786 814 tsup: 787 815 specifier: 7.2.0 788 - version: 7.2.0(typescript@5.2.2) 816 + version: 7.2.0(postcss@8.4.31)(typescript@5.2.2) 789 817 typescript: 790 818 specifier: 5.2.2 791 819 version: 5.2.2 ··· 14462 14490 /tslib@2.6.2: 14463 14491 resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 14464 14492 14465 - /tsup@7.2.0(typescript@5.2.2): 14493 + /tsup@7.2.0(postcss@8.4.31)(typescript@5.2.2): 14466 14494 resolution: {integrity: sha512-vDHlczXbgUvY3rWvqFEbSqmC1L7woozbzngMqTtL2PGBODTtWlRwGDDawhvWzr5c1QjKe4OAKqJGfE1xeXUvtQ==} 14467 14495 engines: {node: '>=16.14'} 14468 14496 hasBin: true ··· 14486 14514 execa: 5.1.1 14487 14515 globby: 11.1.0 14488 14516 joycon: 3.1.1 14517 + postcss: 8.4.31 14489 14518 postcss-load-config: 4.0.1(postcss@8.4.31) 14490 14519 resolve-from: 5.0.0 14491 14520 rollup: 3.29.4