Openstatus www.openstatus.dev
6
fork

Configure Feed

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

chore: update dashboard folder and layout (#28)

* chore: update folder and layout

* feat: add simple layout for auth pages

* feat: improve dashboard layout navigation

authored by

Maximilian Kaske and committed by
GitHub
08cff6f9 df3918e0

+422 -4
+7 -4
apps/web/.env.example
··· 3 3 CLERK_SECRET_KEY= 4 4 CLERK_WEBHOOK_SECRET= 5 5 6 - NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in 7 - NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up 8 - NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/ 9 - NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/ 6 + NEXT_PUBLIC_CLERK_SIGN_IN_URL=/app/sign-in 7 + NEXT_PUBLIC_CLERK_SIGN_UP_URL=/app/sign-up 8 + NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/app 9 + NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/app 10 10 11 11 # TinyBird 12 12 TINY_BIRD_API_KEY= ··· 23 23 QSTASH_TOKEN= 24 24 25 25 RESEND_API_KEY='api-key' 26 + 27 + # Solves 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY', see https://github.com/nextauthjs/next-auth/issues/3580 28 + # NODE_TLS_REJECT_UNAUTHORIZED="0"
+43
apps/web/src/app/app/(auth)/layout.tsx
··· 1 + import * as React from "react"; 2 + 3 + // TODO: update description 4 + 5 + export default function AuthLayout({ 6 + children, 7 + }: { 8 + children: React.ReactNode; 9 + }) { 10 + return ( 11 + <div className="grid min-h-screen grid-cols-1 md:grid-cols-2 xl:grid-cols-3"> 12 + <aside className="border-border col-span-1 flex w-full items-center justify-center border p-3 backdrop-blur-[2px] md:p-6"> 13 + <div className="w-full max-w-lg text-left"> 14 + <h1 className="font-cal text-foreground mb-3 text-2xl"> 15 + Open Source Status Page 16 + </h1> 17 + <p className="text-muted-foreground"> 18 + Create your own status page within a couple of minutes. You are 19 + developer and want to know how it works? <br /> 20 + Check out{" "} 21 + <a 22 + href="https://github.com/mxkaske/openstatus" 23 + target="_blank" 24 + rel="noreferrer" 25 + className="text-foreground underline underline-offset-4 hover:no-underline" 26 + > 27 + GitHub 28 + </a>{" "} 29 + and let us know your use case! 30 + </p> 31 + {/* Add Demo tracker here? */} 32 + <div className="h-12" /> 33 + <p className="text-muted-foreground text-right text-sm font-light"> 34 + *your data is safe 35 + </p> 36 + </div> 37 + </aside> 38 + <main className="container col-span-1 mx-auto flex items-center justify-center md:col-span-1 xl:col-span-2"> 39 + {children} 40 + </main> 41 + </div> 42 + ); 43 + }
+11
apps/web/src/app/app/(dashboard)/endpoint/page.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { Header } from "@/components/header"; 4 + 5 + export default function EndpointPage() { 6 + return ( 7 + <div> 8 + <Header title="Endpoint" description="Overview of all your sites." /> 9 + </div> 10 + ); 11 + }
+11
apps/web/src/app/app/(dashboard)/incident/page.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { Header } from "@/components/header"; 4 + 5 + export default function IncidentPage() { 6 + return ( 7 + <div> 8 + <Header title="Monitor" description="Overview of all the responses." /> 9 + </div> 10 + ); 11 + }
+30
apps/web/src/app/app/(dashboard)/layout.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { AppHeader } from "@/components/layout/app-header"; 4 + import { AppMenu } from "@/components/layout/app-menu"; 5 + import { AppSidebar } from "@/components/layout/app-sidebar"; 6 + import { Footer } from "@/components/layout/footer"; 7 + 8 + export default function AppLayout({ children }: { children: React.ReactNode }) { 9 + return ( 10 + <div className="container mx-auto flex min-h-screen w-full flex-col items-center justify-center space-y-6 p-4 md:p-8"> 11 + <AppHeader /> 12 + <div className="flex w-full flex-1 gap-6 md:gap-8"> 13 + <aside className="border-border hidden rounded-lg border p-3 backdrop-blur-[2px] md:block md:p-6"> 14 + <nav> 15 + <AppSidebar /> 16 + </nav> 17 + </aside> 18 + <main className="z-10 flex w-full flex-1 flex-col items-start justify-center"> 19 + <div className="border-border relative w-full flex-1 rounded-lg border p-3 backdrop-blur-[2px] md:p-6"> 20 + <nav className="absolute right-4 top-4 block md:hidden"> 21 + <AppMenu /> 22 + </nav> 23 + {children} 24 + </div> 25 + </main> 26 + </div> 27 + <Footer /> 28 + </div> 29 + ); 30 + }
+11
apps/web/src/app/app/(dashboard)/monitor/page.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { Header } from "@/components/header"; 4 + 5 + export default function MonitorPage() { 6 + return ( 7 + <div> 8 + <Header title="Monitor" description="Overview of all the responses." /> 9 + </div> 10 + ); 11 + }
+14
apps/web/src/app/app/(dashboard)/page.tsx
··· 1 + import * as React from "react"; 2 + import { auth, currentUser } from "@clerk/nextjs"; 3 + 4 + import { Header } from "@/components/header"; 5 + 6 + export default async function AppPage() { 7 + const user = await currentUser(); 8 + console.log({ user }); 9 + return ( 10 + <div> 11 + <Header title="App" description="Overview of all your websites" /> 12 + </div> 13 + ); 14 + }
apps/web/src/app/sign-in/[[...sign-in]]/page.tsx apps/web/src/app/app/(auth)/sign-in/[[...sign-in]]/page.tsx
apps/web/src/app/sign-up/[[...sign-up]]/page.tsx apps/web/src/app/app/(auth)/sign-up/[[...sign-up]]/page.tsx
+17
apps/web/src/components/header.tsx
··· 1 + import { cn } from "@/lib/utils"; 2 + 3 + interface HeaderProps extends React.HTMLAttributes<HTMLDivElement> { 4 + title: string; 5 + description?: string | null; 6 + } 7 + 8 + export function Header({ title, description, className }: HeaderProps) { 9 + return ( 10 + <div className={cn("grid gap-1", className)}> 11 + <h1 className="font-cal text-3xl">{title}</h1> 12 + {description ? ( 13 + <p className="text-muted-foreground">{description}</p> 14 + ) : null} 15 + </div> 16 + ); 17 + }
+12
apps/web/src/components/icons.tsx
··· 1 + import { Activity, LayoutDashboard, Link, Siren } from "lucide-react"; 2 + import type { Icon as LucideIcon, LucideProps } from "lucide-react"; 3 + 4 + export type Icon = LucideIcon; 5 + export type ValidIcon = keyof typeof Icons; 6 + 7 + export const Icons = { 8 + activity: Activity, 9 + "layout-dashboard": LayoutDashboard, 10 + link: Link, 11 + siren: Siren, 12 + } as const;
+16
apps/web/src/components/layout/app-header.tsx
··· 1 + import Link from "next/link"; 2 + import { UserButton } from "@clerk/nextjs"; 3 + 4 + export function AppHeader() { 5 + return ( 6 + <header className="z-10 flex w-full items-center justify-between"> 7 + <Link 8 + href="/" 9 + className="font-cal text-muted-foreground hover:text-foreground text-lg" 10 + > 11 + openstatus 12 + </Link> 13 + <UserButton /> 14 + </header> 15 + ); 16 + }
+32
apps/web/src/components/layout/app-menu.tsx
··· 1 + import { Menu } from "lucide-react"; 2 + 3 + import { Button } from "../ui/button"; 4 + import { 5 + Sheet, 6 + SheetContent, 7 + SheetDescription, 8 + SheetHeader, 9 + SheetTitle, 10 + SheetTrigger, 11 + } from "../ui/sheet"; 12 + import { AppSidebar } from "./app-sidebar"; 13 + 14 + export function AppMenu() { 15 + return ( 16 + <Sheet> 17 + <SheetTrigger asChild> 18 + <Button size="icon" variant="outline"> 19 + <Menu className="h-6 w-6" /> 20 + </Button> 21 + </SheetTrigger> 22 + <SheetContent> 23 + <SheetHeader> 24 + <SheetTitle>Navigation</SheetTitle> 25 + <SheetDescription> 26 + <AppSidebar /> 27 + </SheetDescription> 28 + </SheetHeader> 29 + </SheetContent> 30 + </Sheet> 31 + ); 32 + }
+35
apps/web/src/components/layout/app-sidebar.tsx
··· 1 + "use client"; 2 + 3 + import Link from "next/link"; 4 + import { usePathname } from "next/navigation"; 5 + 6 + import { pagesConfig } from "@/config/pages"; 7 + import { cn } from "@/lib/utils"; 8 + import { Icons } from "../icons"; 9 + 10 + export function AppSidebar() { 11 + const pathname = usePathname(); 12 + return ( 13 + <ul className="grid gap-1"> 14 + {pagesConfig.map(({ title, href, icon, disabled }) => { 15 + const Icon = Icons[icon]; 16 + return ( 17 + <li key={title} className="w-full"> 18 + <Link 19 + href={href} 20 + className={cn( 21 + "hover:bg-muted/50 hover:text-foreground text-muted-foreground group -mx-2 flex w-full min-w-[200px] items-center rounded-md border border-transparent px-3 py-1", 22 + pathname === href && 23 + "bg-muted/50 border-border text-foreground", 24 + disabled && "pointer-events-none opacity-60", 25 + )} 26 + > 27 + <Icon className={cn("mr-2 h-4 w-4")} /> 28 + {title} 29 + </Link> 30 + </li> 31 + ); 32 + })} 33 + </ul> 34 + ); 35 + }
+145
apps/web/src/components/ui/sheet.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import * as SheetPrimitive from "@radix-ui/react-dialog"; 5 + import { cva } from "class-variance-authority"; 6 + import type { VariantProps } from "class-variance-authority"; 7 + import { X } from "lucide-react"; 8 + 9 + import { cn } from "@/lib/utils"; 10 + 11 + const Sheet = SheetPrimitive.Root; 12 + 13 + const SheetTrigger = SheetPrimitive.Trigger; 14 + 15 + const SheetClose = SheetPrimitive.Close; 16 + 17 + const SheetPortal = ({ 18 + className, 19 + ...props 20 + }: SheetPrimitive.DialogPortalProps) => ( 21 + <SheetPrimitive.Portal className={cn(className)} {...props} /> 22 + ); 23 + SheetPortal.displayName = SheetPrimitive.Portal.displayName; 24 + 25 + const SheetOverlay = React.forwardRef< 26 + React.ElementRef<typeof SheetPrimitive.Overlay>, 27 + React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> 28 + >(({ className, ...props }, ref) => ( 29 + <SheetPrimitive.Overlay 30 + className={cn( 31 + "bg-background/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 backdrop-blur-sm", 32 + className, 33 + )} 34 + {...props} 35 + ref={ref} 36 + /> 37 + )); 38 + SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; 39 + 40 + const sheetVariants = cva( 41 + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", 42 + { 43 + variants: { 44 + side: { 45 + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", 46 + bottom: 47 + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", 48 + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", 49 + right: 50 + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", 51 + }, 52 + }, 53 + defaultVariants: { 54 + side: "right", 55 + }, 56 + }, 57 + ); 58 + 59 + interface SheetContentProps 60 + extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, 61 + VariantProps<typeof sheetVariants> {} 62 + 63 + const SheetContent = React.forwardRef< 64 + React.ElementRef<typeof SheetPrimitive.Content>, 65 + SheetContentProps 66 + >(({ side = "right", className, children, ...props }, ref) => ( 67 + <SheetPortal> 68 + <SheetOverlay /> 69 + <SheetPrimitive.Content 70 + ref={ref} 71 + className={cn(sheetVariants({ side }), className)} 72 + {...props} 73 + > 74 + {children} 75 + <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none"> 76 + <X className="h-4 w-4" /> 77 + <span className="sr-only">Close</span> 78 + </SheetPrimitive.Close> 79 + </SheetPrimitive.Content> 80 + </SheetPortal> 81 + )); 82 + SheetContent.displayName = SheetPrimitive.Content.displayName; 83 + 84 + const SheetHeader = ({ 85 + className, 86 + ...props 87 + }: React.HTMLAttributes<HTMLDivElement>) => ( 88 + <div 89 + className={cn( 90 + "flex flex-col space-y-2 text-center sm:text-left", 91 + className, 92 + )} 93 + {...props} 94 + /> 95 + ); 96 + SheetHeader.displayName = "SheetHeader"; 97 + 98 + const SheetFooter = ({ 99 + className, 100 + ...props 101 + }: React.HTMLAttributes<HTMLDivElement>) => ( 102 + <div 103 + className={cn( 104 + "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", 105 + className, 106 + )} 107 + {...props} 108 + /> 109 + ); 110 + SheetFooter.displayName = "SheetFooter"; 111 + 112 + const SheetTitle = React.forwardRef< 113 + React.ElementRef<typeof SheetPrimitive.Title>, 114 + React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> 115 + >(({ className, ...props }, ref) => ( 116 + <SheetPrimitive.Title 117 + ref={ref} 118 + className={cn("text-foreground text-lg font-semibold", className)} 119 + {...props} 120 + /> 121 + )); 122 + SheetTitle.displayName = SheetPrimitive.Title.displayName; 123 + 124 + const SheetDescription = React.forwardRef< 125 + React.ElementRef<typeof SheetPrimitive.Description>, 126 + React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> 127 + >(({ className, ...props }, ref) => ( 128 + <SheetPrimitive.Description 129 + ref={ref} 130 + className={cn("text-muted-foreground text-sm", className)} 131 + {...props} 132 + /> 133 + )); 134 + SheetDescription.displayName = SheetPrimitive.Description.displayName; 135 + 136 + export { 137 + Sheet, 138 + SheetTrigger, 139 + SheetClose, 140 + SheetContent, 141 + SheetHeader, 142 + SheetFooter, 143 + SheetTitle, 144 + SheetDescription, 145 + };
+38
apps/web/src/config/pages.ts
··· 1 + import type { ValidIcon } from "@/components/icons"; 2 + 3 + type Page = { 4 + title: string; 5 + description: string; 6 + href: string; 7 + icon: ValidIcon; 8 + disabled?: boolean; 9 + }; 10 + 11 + export const pagesConfig = [ 12 + { 13 + title: "Dashboard", 14 + description: "Get an overview of what's hot.", 15 + href: "/app", 16 + icon: "layout-dashboard", 17 + }, 18 + { 19 + title: "Endpoint", 20 + description: "Keep track of all your endpoints.", 21 + href: "/app/endpoint", 22 + icon: "link", 23 + }, 24 + { 25 + title: "Monitor", 26 + description: "Check all the responses in one place.", 27 + href: "/app/monitor", 28 + icon: "activity", 29 + }, 30 + { 31 + title: "Incident", 32 + description: "War room where you handle the incidents.", 33 + href: "/app/incident", 34 + icon: "siren", 35 + disabled: true, 36 + }, 37 + // ... 38 + ] satisfies Page[];