Openstatus www.openstatus.dev

feat: coming soon (#1)

* wip: typo and add todo

* fix: error

* feat: add open graph api route

* chore: extract hook and update og image

* feat: add metadata to layout

authored by

Maximilian Kaske and committed by
GitHub
ab408d0e 91998a13

+133 -34
+4 -25
apps/web/src/app/_components/background.tsx
··· 1 1 "use client"; 2 + import useMouseMove from "@/hooks/use-mouse-move"; 2 3 import React from "react"; 3 4 4 5 export default function Background({ ··· 6 7 }: { 7 8 children: React.ReactNode; 8 9 }) { 9 - React.useEffect(() => { 10 - function mouseMoveEvent(e: MouseEvent) { 11 - const scale = window.visualViewport.scale; 12 - // disable mouse movement on viewport zoom - causes page to slow down 13 - if (scale === 1) { 14 - const body = document.body; 15 - 16 - const targetX = e.clientX; 17 - const targetY = e.clientY; 18 - 19 - body.style.setProperty("--x", `${targetX}px`); 20 - body.style.setProperty("--y", `${targetY}px`); 21 - } 22 - } 23 - 24 - document.addEventListener("mousemove", mouseMoveEvent); 25 - return () => { 26 - document.removeEventListener("mousemove", mouseMoveEvent); 27 - }; 28 - }, []); 29 - 10 + // --x and --y will be updated based on mouse position 11 + useMouseMove(); 30 12 return ( 31 13 <> 32 14 <div className="absolute inset-0 z-[-1]"> 33 15 <div className="absolute inset-0 z-[-1] bg-muted-foreground/20" /> 34 - <div 35 - className="absolute z-[-1] h-56 w-56 rounded-full -translate-y-1/2 -translate-x-1/2 bg-gradient-radial from-muted-foreground/70 from-0% to-transparent to-90% blur-md" 36 - style={{ left: "var(--x)", top: "var(--y)" }} 37 - /> 16 + <div className="absolute left-[--x] top-[--y] z-[-1] h-56 w-56 rounded-full -translate-y-1/2 -translate-x-1/2 bg-gradient-radial from-muted-foreground/80 from-0% to-transparent to-90% blur-md" /> 38 17 <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> 39 18 <defs> 40 19 <pattern
+70
apps/web/src/app/api/og/route.tsx
··· 1 + import { ImageResponse } from "next/server"; 2 + 3 + export const runtime = "edge"; 4 + 5 + const size = { 6 + width: 1200, 7 + height: 630, 8 + }; 9 + 10 + const TITLE = "Open Status"; 11 + const DESCRIPTION = "An Open Source Alternative for your next Status Page"; 12 + 13 + const interRegular = fetch( 14 + new URL("../../../public/fonts/Inter-Regular.ttf", import.meta.url) 15 + ).then((res) => res.arrayBuffer()); 16 + 17 + const calSemiBold = fetch( 18 + new URL("../../../public/fonts/CalSans-SemiBold.ttf", import.meta.url) 19 + ).then((res) => res.arrayBuffer()); 20 + 21 + export async function GET(req: Request) { 22 + const interRegularData = await interRegular; 23 + const calSemiBoldData = await calSemiBold; 24 + 25 + const { searchParams } = new URL(req.url); 26 + const title = searchParams.has("title") ? searchParams.get("title") : TITLE; 27 + const description = searchParams.has("description") 28 + ? searchParams.get("description") 29 + : DESCRIPTION; 30 + 31 + return new ImageResponse( 32 + ( 33 + <div tw="relative flex flex-col bg-white items-center justify-center w-full h-full"> 34 + <div 35 + tw="flex w-full h-full absolute inset-0" 36 + // not every css variable is supported 37 + style={{ 38 + backgroundImage: "radial-gradient(#94a3b8 10%, transparent 10%)", 39 + backgroundPosition: "0px 0px, 6px 6px", 40 + backgroundSize: "12px 12px", 41 + filter: "blur(1px)", // to be discussed... couldn't put it inside the content container 42 + }} 43 + ></div> 44 + <div tw="max-w-2xl relative flex flex-col items-center rounded-lg border border-slate-200 p-6 overflow-hidden bg-white bg-opacity-80"> 45 + <h1 style={{ fontFamily: "Cal" }} tw="text-6xl text-center"> 46 + {title} 47 + </h1> 48 + <p tw="text-slate-600 text-3xl text-center">{description}</p> 49 + </div> 50 + </div> 51 + ), 52 + { 53 + ...size, 54 + fonts: [ 55 + { 56 + name: "Inter", 57 + data: interRegularData, 58 + style: "normal", 59 + weight: 400, 60 + }, 61 + { 62 + name: "Cal", 63 + data: calSemiBoldData, 64 + style: "normal", 65 + weight: 600, 66 + }, 67 + ], 68 + } 69 + ); 70 + }
+3
apps/web/src/app/api/send/route.ts
··· 4 4 5 5 const resend = new Resend(process.env.RESEND_API_KEY); 6 6 7 + // TODO: move it into /api/send/[slug]/route.ts 8 + // where slug is the name of the template 9 + 7 10 export async function POST() { 8 11 try { 9 12 const data = await resend.emails.send({
+19
apps/web/src/app/layout.tsx
··· 5 5 import PlausibleProvider from "next-plausible"; 6 6 import Background from "./_components/background"; 7 7 import { Toaster } from "@/components/ui/toaster"; 8 + import { Metadata } from "next"; 8 9 9 10 const inter = Inter({ subsets: ["latin"] }); 10 11 ··· 12 13 src: "../public/fonts/CalSans-SemiBold.ttf", 13 14 variable: "--font-calsans", 14 15 }); 16 + 17 + export const metadata: Metadata = { 18 + title: "openstatus.dev", 19 + description: "An Open Source Alternative for your next Status Page.", 20 + metadataBase: new URL("https://openstatus.dev"), 21 + twitter: { 22 + images: [`/api/og`], 23 + card: "summary_large_image", 24 + title: "openstatus.dev", 25 + description: "An Open Source Alternative for your next Status Page.", 26 + }, 27 + openGraph: { 28 + type: "website", 29 + images: [`/api/og`], 30 + title: "openstatus.dev", 31 + description: "An Open Source Alternative for your next Status Page.", 32 + }, 33 + }; 15 34 16 35 export default function RootLayout({ 17 36 children,
+10 -8
apps/web/src/app/page.tsx
··· 11 11 <main className="min-h-screen w-full flex flex-col p-4 md:p-8 space-y-6"> 12 12 <div className="flex-1 flex flex-col justify-center"> 13 13 <div className="mx-auto max-w-xl text-center"> 14 - <div className="rounded-lg border border-border backdrop-blur-[2px] p-8"> 15 - <Badge>Announcing Post</Badge> 16 - <h1 className="text-3xl text-foreground font-cal mb-6 mt-2"> 14 + <div className="rounded-lg border border-border backdrop-blur-[2px] p-8 md:p-16"> 15 + <Badge>Coming Soon</Badge> 16 + <h1 className="text-5xl text-foreground font-cal mb-6 mt-2"> 17 17 OpenStatus 18 18 </h1> 19 - <p className="text-muted-foreground mb-4"> 20 - Your Open Source Status Page. 19 + <p className="text-muted-foreground text-lg mb-4"> 20 + {"Let's"} build a Saas together. Open for everyone. <br /> 21 + Managed or self-hosted. Pay-as-you go or plans. <br /> 22 + Choose your solution. 21 23 </p> 22 24 <form 23 25 action={async (data) => { ··· 48 50 </div> 49 51 </div> 50 52 <footer className="text-center text-sm text-muted-foreground mx-auto rounded-full px-4 py-2 border border-border backdrop-blur-[2px]"> 51 - Creating by{" "} 53 + A collaboration between{" "} 52 54 <a 53 55 href="https://twitter.com/mxkaske" 54 56 target="_blank" 55 57 rel="noreferrer" 56 - className="underline underline-offset-4 hover:no-underline" 58 + className="underline underline-offset-4 hover:no-underline text-foreground" 57 59 > 58 60 @mxkaske 59 61 </a>{" "} ··· 62 64 href="https://twitter.com/thibaultleouay" 63 65 target="_blank" 64 66 rel="noreferrer" 65 - className="underline underline-offset-4 hover:no-underline" 67 + className="underline underline-offset-4 hover:no-underline text-foreground" 66 68 > 67 69 @thibaultleouay 68 70 </a>
+26
apps/web/src/hooks/use-mouse-move.tsx
··· 1 + "use client"; 2 + 3 + import React from "react"; 4 + 5 + export default function useMouseMove() { 6 + React.useEffect(() => { 7 + function mouseMoveEvent(e: MouseEvent) { 8 + const scale = window.visualViewport.scale; 9 + // disable mouse movement on viewport zoom - causes page to slow down 10 + if (scale === 1) { 11 + const body = document.body; 12 + 13 + const targetX = e.clientX; 14 + const targetY = e.clientY; 15 + 16 + body.style.setProperty("--x", `${targetX}px`); 17 + body.style.setProperty("--y", `${targetY}px`); 18 + } 19 + } 20 + 21 + document.addEventListener("mousemove", mouseMoveEvent); 22 + return () => { 23 + document.removeEventListener("mousemove", mouseMoveEvent); 24 + }; 25 + }, []); 26 + }
+1 -1
apps/web/src/middleware.ts
··· 1 1 import { authMiddleware } from "@clerk/nextjs"; 2 2 3 3 export default authMiddleware({ 4 - publicRoutes: ["/"], 4 + publicRoutes: ["/", "/api/og"], 5 5 }); 6 6 7 7 export const config = {
apps/web/src/public/fonts/Inter-Regular.ttf

This is a binary file and will not be displayed.