Openstatus www.openstatus.dev

feat: product pages with interactive components (#925)

* chore: upgrade cmdk and improve region preset

* fix: missing type

* wip: example

* fix: assertions

* fix: wrap charts in suspense boundary

* wip: features

* feat: marketing header

* chore: increase font-size, padding, gap

* feat: add demo to subscribe button

* wip:

* feat: navigation menu

* chore: reduce dot opacity

* chore: clean up

* chore: extract into pages

* chore: navigation menu

* chore: password-protection

* chore: mock status report

* wip:

* chore: ci interagration and gradient

* fix: brand name font size

* chore: remove empty string

* feat: improve play page margin

* fix: typo

* fix: pre

* 😁 small update

* 🌐 improve seo

---------

Co-authored-by: Thibault Le Ouay <thibaultleouay@gmail.Com>

authored by

Maximilian Kaske
Thibault Le Ouay
and committed by
GitHub
6645d5f1 f0ed1561

+2567 -166
+1 -1
apps/web/package.json
··· 26 26 "@openstatus/next-monitoring": "0.0.4", 27 27 "@openstatus/notification-discord": "workspace:*", 28 28 "@openstatus/notification-emails": "workspace:*", 29 - "@openstatus/notification-slack": "workspace:*", 30 29 "@openstatus/notification-pagerduty": "workspace:*", 30 + "@openstatus/notification-slack": "workspace:*", 31 31 "@openstatus/plans": "workspace:*", 32 32 "@openstatus/react": "workspace:*", 33 33 "@openstatus/rum": "workspace:*",
+33
apps/web/src/app/(content)/features/_components/assertions-timing-form-example.tsx
··· 1 + "use client"; 2 + 3 + import { SectionAssertions } from "@/components/forms/monitor/section-assertions"; 4 + import { zodResolver } from "@hookform/resolvers/zod"; 5 + import * as assertions from "@openstatus/assertions"; 6 + import { 7 + type InsertMonitor, 8 + insertMonitorSchema, 9 + } from "@openstatus/db/src/schema"; 10 + import { Form } from "@openstatus/ui"; 11 + import { useForm } from "react-hook-form"; 12 + 13 + const _assertions = 14 + '[{"version":"v1","type":"header","compare":"eq","key":"Server","target":"Vercel"}]'; 15 + 16 + export function AssertionsTimingFormExample() { 17 + const form = useForm<InsertMonitor>({ 18 + resolver: zodResolver(insertMonitorSchema), 19 + defaultValues: { 20 + timeout: 45_000, 21 + headerAssertions: assertions 22 + .deserialize(_assertions) 23 + .map((a) => a.schema) 24 + // biome-ignore lint/suspicious/noExplicitAny: <explanation> 25 + .filter((a) => a.type === "header") as any, 26 + }, 27 + }); 28 + return ( 29 + <Form {...form}> 30 + <SectionAssertions form={form} /> 31 + </Form> 32 + ); 33 + }
+22
apps/web/src/app/(content)/features/_components/banner.tsx
··· 1 + import { Shell } from "@/components/dashboard/shell"; 2 + import { Button } from "@openstatus/ui"; 3 + import Link from "next/link"; 4 + 5 + export function Banner() { 6 + return ( 7 + <Shell className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between"> 8 + <p className="max-w-xl font-medium text-lg"> 9 + Learn how your services are performing over time, and notify your users 10 + of any issues. 11 + </p> 12 + <div className="flex items-center gap-2 self-end"> 13 + <Button variant="outline" className="min-w-max rounded-full" asChild> 14 + <Link href="/cal">Book a call</Link> 15 + </Button> 16 + <Button className="min-w-max rounded-full" asChild> 17 + <Link href="/app/login">Get started</Link> 18 + </Button> 19 + </div> 20 + </Shell> 21 + ); 22 + }
+24
apps/web/src/app/(content)/features/_components/hero.tsx
··· 1 + import { cn } from "@/lib/utils"; 2 + import type * as React from "react"; 3 + 4 + interface HeroProps extends React.ComponentPropsWithoutRef<"div"> { 5 + title: string; 6 + subTitle: string; 7 + } 8 + 9 + export function Hero({ title, subTitle, className, ...props }: HeroProps) { 10 + return ( 11 + <div 12 + className={cn( 13 + "mx-auto my-16 flex max-w-xl flex-col items-center gap-4", 14 + className 15 + )} 16 + {...props} 17 + > 18 + <h1 className="text-center font-cal text-5xl">{title}</h1> 19 + <h2 className="mx-auto max-w-md text-center text-lg text-muted-foreground md:max-w-xl md:text-xl"> 20 + {subTitle} 21 + </h2> 22 + </div> 23 + ); 24 + }
+152
apps/web/src/app/(content)/features/_components/interactive-feature.tsx
··· 1 + import { Shell } from "@/components/dashboard/shell"; 2 + import { Icons, type ValidIcon } from "@/components/icons"; 3 + import { cn } from "@/lib/utils"; 4 + import { cva } from "class-variance-authority"; 5 + 6 + const containerVariant = cva( 7 + "flex w-full max-h-72 overflow-hidden flex-col gap-2", 8 + { 9 + variants: { 10 + col: { 11 + 1: "col-span-full md:col-span-1", 12 + 2: "col-span-full md:col-span-2", 13 + 3: "col-span-full md:col-span-3", 14 + }, 15 + position: { 16 + top: "order-2", 17 + right: "order-2", 18 + bottom: "order-1 border-b", 19 + left: "order-1 border-b md:border-b-0 md:border-r", 20 + }, 21 + }, 22 + }, 23 + ); 24 + 25 + interface InteractiveFeatureProps 26 + extends React.ComponentPropsWithoutRef<"div"> { 27 + component: React.ReactNode; 28 + icon: ValidIcon; 29 + iconText: string; 30 + title: string; 31 + subTitle: string; 32 + /** 33 + * Allows to include one or multiple CTA action(s) 34 + * @example <Button>CTA</Button> 35 + */ 36 + action?: React.ReactNode; 37 + // grid aligment 38 + position: "top" | "right" | "bottom" | "left"; 39 + col: 1 | 2; 40 + withGradient?: boolean; 41 + } 42 + 43 + export function InteractiveFeature({ 44 + icon, 45 + iconText, 46 + title, 47 + subTitle, 48 + action, 49 + component, 50 + position, 51 + col, 52 + withGradient = false, 53 + }: InteractiveFeatureProps) { 54 + const Component = component; 55 + const isSingleCol = ["top", "bottom"].includes(position); 56 + return ( 57 + <Shell 58 + className={cn( 59 + "grid px-0 py-0 md:grid-cols-3 md:p-0", 60 + isSingleCol && "md:grid-cols-1", 61 + )} 62 + > 63 + <FeatureCardContentContainer 64 + variant="secondary" 65 + className={cn( 66 + containerVariant({ 67 + col: (3 - col) as 1 | 2, 68 + position, 69 + }), 70 + )} 71 + > 72 + <FeatureSubheader icon={icon} text={iconText} /> 73 + <FeatureTitle strong={title} regular={subTitle} /> 74 + {action} 75 + </FeatureCardContentContainer> 76 + <FeatureCardContentContainer 77 + className={cn( 78 + containerVariant({ 79 + col, 80 + position: isSingleCol 81 + ? position === "top" 82 + ? "bottom" 83 + : "top" 84 + : position === "left" 85 + ? "right" 86 + : "left", 87 + }), 88 + "relative", 89 + )} 90 + > 91 + {Component} 92 + {withGradient ? <Gradient /> : null} 93 + </FeatureCardContentContainer> 94 + </Shell> 95 + ); 96 + } 97 + 98 + function Gradient() { 99 + return ( 100 + <div className="absolute inset-x-0 bottom-0 z-10 h-16 w-full overflow-hidden rounded-b-xl"> 101 + <div className="h-full w-full bg-gradient-to-b from-transparent to-background" /> 102 + </div> 103 + ); 104 + } 105 + 106 + interface FeatureCardContentContainer 107 + extends React.ComponentPropsWithoutRef<"div"> { 108 + variant?: "default" | "secondary"; 109 + } 110 + 111 + function FeatureCardContentContainer({ 112 + children, 113 + variant = "default", 114 + className, 115 + ...props 116 + }: FeatureCardContentContainer) { 117 + return ( 118 + <div 119 + className={cn( 120 + "px-3 py-6 md:p-8", 121 + variant === "secondary" && "bg-accent/50", 122 + className, 123 + )} 124 + {...props} 125 + > 126 + {children} 127 + </div> 128 + ); 129 + } 130 + 131 + function FeatureSubheader({ icon, text }: { icon: ValidIcon; text: string }) { 132 + const Icon = Icons[icon]; 133 + return ( 134 + <h3 className="flex items-center gap-2 text-muted-foreground"> 135 + <Icon className="h-4 w-4 text-foreground" /> 136 + {text} 137 + </h3> 138 + ); 139 + } 140 + 141 + interface FeatureTitleProps 142 + extends React.ComponentPropsWithoutRef<"p">, 143 + Record<"strong" | "regular", string> {} 144 + 145 + function FeatureTitle({ strong, regular }: FeatureTitleProps) { 146 + return ( 147 + <p className="text-muted-foreground text-xl"> 148 + <strong className="font-medium text-foreground">{strong}</strong>{" "} 149 + {regular} 150 + </p> 151 + ); 152 + }
+1073
apps/web/src/app/(content)/features/mock.ts
··· 1 + import * as assertions from "@openstatus/assertions"; 2 + 3 + export const mockResponseData = { 4 + timing: { 5 + dnsStart: 1720297250262, 6 + dnsDone: 1720297250290, 7 + connectStart: 1720297250290, 8 + connectDone: 1720297250291, 9 + tlsHandshakeStart: 1720297250291, 10 + tlsHandshakeDone: 1720297250300, 11 + firstByteStart: 1720297250300, 12 + firstByteDone: 1720297250635, 13 + transferStart: 1720297250635, 14 + transferDone: 1720297250635, 15 + }, 16 + headers: { 17 + Age: "0", 18 + "Cache-Control": "private, no-cache, no-store, max-age=0, must-revalidate", 19 + "Content-Type": "text/html; charset=utf-8", 20 + Date: "Sat, 06 Jul 2024 20:20:50 GMT", 21 + Server: "Vercel", 22 + "Strict-Transport-Security": "max-age=63072000", 23 + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch", 24 + "X-Matched-Path": "/", 25 + "X-Powered-By": "Next.js", 26 + "X-Vercel-Cache": "MISS", 27 + "X-Vercel-Execution-Region": "fra1", 28 + "X-Vercel-Id": "bom1::fra1::nj6b8-1720297250301-a7b4ff53baa9", 29 + }, 30 + status: 200, 31 + message: null, 32 + assertions: assertions.deserialize( 33 + '[{"version":"v1","type":"header","compare":"eq","key":"Server","target":"Vercel"}]', 34 + ), 35 + }; 36 + 37 + export const mockTrackerData = [ 38 + { day: "2024-07-07T00:00:00.000Z", count: 3944, ok: 3944 }, 39 + { day: "2024-07-06T00:00:00.000Z", count: 4692, ok: 4692 }, 40 + { day: "2024-07-05T00:00:00.000Z", count: 4737, ok: 4737 }, 41 + { day: "2024-07-04T00:00:00.000Z", count: 4862, ok: 4862 }, 42 + { day: "2024-07-03T00:00:00.000Z", count: 1958, ok: 1958 }, 43 + { day: "2024-07-02T00:00:00.000Z", count: 1152, ok: 1152 }, 44 + { day: "2024-07-01T00:00:00.000Z", count: 1152, ok: 1152 }, 45 + { day: "2024-06-30T00:00:00.000Z", count: 1152, ok: 1152 }, 46 + { day: "2024-06-29T00:00:00.000Z", count: 1152, ok: 1152 }, 47 + { day: "2024-06-28T00:00:00.000Z", count: 1128, ok: 1128 }, 48 + { day: "2024-06-27T00:00:00.000Z", count: 1144, ok: 1144 }, 49 + { day: "2024-06-26T00:00:00.000Z", count: 1148, ok: 1148 }, 50 + { day: "2024-06-25T00:00:00.000Z", count: 1152, ok: 1152 }, 51 + { day: "2024-06-24T00:00:00.000Z", count: 1151, ok: 1151 }, 52 + { day: "2024-06-23T00:00:00.000Z", count: 1152, ok: 1152 }, 53 + { day: "2024-06-22T00:00:00.000Z", count: 1152, ok: 1152 }, 54 + { day: "2024-06-21T00:00:00.000Z", count: 1152, ok: 1152 }, 55 + { day: "2024-06-20T00:00:00.000Z", count: 1152, ok: 1151 }, 56 + { day: "2024-06-19T00:00:00.000Z", count: 1064, ok: 1064 }, 57 + { day: "2024-06-18T00:00:00.000Z", count: 864, ok: 864 }, 58 + { day: "2024-06-17T00:00:00.000Z", count: 864, ok: 864 }, 59 + { day: "2024-06-16T00:00:00.000Z", count: 864, ok: 864 }, 60 + { day: "2024-06-15T00:00:00.000Z", count: 864, ok: 864 }, 61 + { day: "2024-06-14T00:00:00.000Z", count: 864, ok: 864 }, 62 + { day: "2024-06-13T00:00:00.000Z", count: 864, ok: 864 }, 63 + { day: "2024-06-12T00:00:00.000Z", count: 863, ok: 863 }, 64 + { day: "2024-06-11T00:00:00.000Z", count: 864, ok: 864 }, 65 + { day: "2024-06-10T00:00:00.000Z", count: 864, ok: 864 }, 66 + { day: "2024-06-09T00:00:00.000Z", count: 1320, ok: 1320 }, 67 + { day: "2024-06-08T00:00:00.000Z", count: 864, ok: 864 }, 68 + { day: "2024-06-07T00:00:00.000Z", count: 864, ok: 864 }, 69 + { day: "2024-06-06T00:00:00.000Z", count: 864, ok: 864 }, 70 + { day: "2024-06-05T00:00:00.000Z", count: 864, ok: 864 }, 71 + { day: "2024-06-04T00:00:00.000Z", count: 864, ok: 864 }, 72 + { day: "2024-06-03T00:00:00.000Z", count: 864, ok: 864 }, 73 + { day: "2024-06-02T00:00:00.000Z", count: 864, ok: 864 }, 74 + { day: "2024-06-01T00:00:00.000Z", count: 864, ok: 864 }, 75 + { day: "2024-05-31T00:00:00.000Z", count: 860, ok: 860 }, 76 + { day: "2024-05-30T00:00:00.000Z", count: 858, ok: 858 }, 77 + { day: "2024-05-29T00:00:00.000Z", count: 864, ok: 864 }, 78 + { day: "2024-05-28T00:00:00.000Z", count: 864, ok: 864 }, 79 + { day: "2024-05-27T00:00:00.000Z", count: 864, ok: 864 }, 80 + { day: "2024-05-26T00:00:00.000Z", count: 864, ok: 864 }, 81 + { day: "2024-05-25T00:00:00.000Z", count: 864, ok: 864 }, 82 + { day: "2024-05-24T00:00:00.000Z", count: 864, ok: 864 }, 83 + ]; 84 + 85 + export const mockChartData = { 86 + data: [ 87 + { 88 + timestamp: "Jul 5, 23:00", 89 + lhr: 991, 90 + phx: 1870, 91 + gig: 1088, 92 + sjc: 658, 93 + mia: 808, 94 + dfw: 2403, 95 + gru: 857, 96 + gdl: 1043, 97 + qro: 1183, 98 + syd: 715, 99 + jnb: 496, 100 + yyz: 877, 101 + mad: 886, 102 + ams: 562, 103 + iad: 2311, 104 + scl: 992, 105 + fra: 939, 106 + den: 645, 107 + ord: 2455, 108 + hkg: 2082, 109 + cdg: 984, 110 + arn: 1179, 111 + sea: 914, 112 + sin: 970, 113 + yul: 677, 114 + atl: 1083, 115 + bos: 2061, 116 + bog: 902, 117 + eze: 1031, 118 + ewr: 965, 119 + otp: 1077, 120 + bom: 1609, 121 + waw: 2227, 122 + lax: 743, 123 + }, 124 + { 125 + timestamp: "Jul 6, 00:00", 126 + hkg: 792, 127 + cdg: 741, 128 + den: 744, 129 + ord: 1001, 130 + fra: 917, 131 + sea: 1053, 132 + sin: 845, 133 + arn: 955, 134 + bos: 687, 135 + bog: 562, 136 + atl: 764, 137 + yul: 695, 138 + lax: 651, 139 + otp: 599, 140 + bom: 790, 141 + waw: 669, 142 + eze: 789, 143 + ewr: 614, 144 + sjc: 707, 145 + mia: 1146, 146 + gig: 936, 147 + lhr: 992, 148 + phx: 623, 149 + gdl: 1137, 150 + qro: 925, 151 + dfw: 1153, 152 + gru: 620, 153 + jnb: 537, 154 + syd: 703, 155 + scl: 750, 156 + iad: 310, 157 + ams: 580, 158 + mad: 829, 159 + yyz: 811, 160 + }, 161 + { 162 + timestamp: "Jul 6, 01:00", 163 + lax: 1386, 164 + waw: 494, 165 + bom: 1632, 166 + otp: 687, 167 + ewr: 1053, 168 + eze: 1570, 169 + bog: 1699, 170 + bos: 1144, 171 + atl: 1478, 172 + yul: 1390, 173 + sin: 1820, 174 + sea: 1807, 175 + arn: 1586, 176 + cdg: 667, 177 + hkg: 763, 178 + ord: 1672, 179 + den: 924, 180 + fra: 497, 181 + scl: 888, 182 + iad: 702, 183 + mad: 817, 184 + ams: 1258, 185 + yyz: 1768, 186 + jnb: 496, 187 + syd: 880, 188 + qro: 1148, 189 + gdl: 1568, 190 + gru: 860, 191 + dfw: 1578, 192 + mia: 1564, 193 + sjc: 1680, 194 + gig: 1364, 195 + phx: 788, 196 + lhr: 778, 197 + }, 198 + { 199 + timestamp: "Jul 6, 02:00", 200 + ord: 1854, 201 + den: 1543, 202 + fra: 555, 203 + cdg: 662, 204 + hkg: 1066, 205 + arn: 906, 206 + sin: 944, 207 + sea: 1666, 208 + atl: 950, 209 + yul: 734, 210 + bog: 1057, 211 + bos: 795, 212 + waw: 542, 213 + otp: 621, 214 + bom: 774, 215 + ewr: 662, 216 + eze: 888, 217 + lax: 946, 218 + gig: 854, 219 + phx: 822, 220 + lhr: 780, 221 + mia: 702, 222 + sjc: 604, 223 + gru: 835, 224 + dfw: 1080, 225 + qro: 842, 226 + gdl: 1580, 227 + jnb: 554, 228 + syd: 754, 229 + mad: 756, 230 + ams: 518, 231 + yyz: 1058, 232 + scl: 982, 233 + iad: 946, 234 + }, 235 + { 236 + timestamp: "Jul 6, 03:00", 237 + bos: 1076, 238 + bog: 1106, 239 + atl: 872, 240 + yul: 825, 241 + lax: 958, 242 + otp: 616, 243 + bom: 844, 244 + waw: 586, 245 + eze: 898, 246 + ewr: 1097, 247 + hkg: 678, 248 + cdg: 700, 249 + den: 766, 250 + ord: 784, 251 + fra: 682, 252 + sea: 983, 253 + sin: 824, 254 + arn: 734, 255 + jnb: 467, 256 + syd: 1010, 257 + scl: 780, 258 + iad: 990, 259 + mad: 832, 260 + ams: 547, 261 + yyz: 816, 262 + sjc: 972, 263 + mia: 826, 264 + gig: 966, 265 + lhr: 784, 266 + phx: 554, 267 + gdl: 990, 268 + qro: 1012, 269 + dfw: 1066, 270 + gru: 848, 271 + }, 272 + { 273 + timestamp: "Jul 6, 04:00", 274 + syd: 1142, 275 + jnb: 508, 276 + yyz: 864, 277 + ams: 412, 278 + mad: 642, 279 + iad: 884, 280 + scl: 866, 281 + lhr: 797, 282 + phx: 901, 283 + gig: 794, 284 + sjc: 1398, 285 + mia: 546, 286 + dfw: 1114, 287 + gru: 620, 288 + gdl: 650, 289 + qro: 1684, 290 + yul: 760, 291 + atl: 870, 292 + bos: 686, 293 + bog: 868, 294 + eze: 1065, 295 + ewr: 854, 296 + otp: 770, 297 + bom: 1491, 298 + waw: 678, 299 + lax: 846, 300 + fra: 541, 301 + den: 1314, 302 + ord: 714, 303 + hkg: 1028, 304 + cdg: 556, 305 + arn: 1472, 306 + sea: 1027, 307 + sin: 1592, 308 + }, 309 + { 310 + timestamp: "Jul 6, 05:00", 311 + gru: 660, 312 + dfw: 1060, 313 + qro: 1102, 314 + gdl: 1650, 315 + phx: 1502, 316 + lhr: 706, 317 + gig: 765, 318 + mia: 741, 319 + sjc: 880, 320 + yyz: 928, 321 + mad: 622, 322 + ams: 620, 323 + iad: 928, 324 + scl: 942, 325 + syd: 1692, 326 + jnb: 480, 327 + arn: 784, 328 + sin: 1226, 329 + sea: 914, 330 + fra: 512, 331 + ord: 962, 332 + den: 1683, 333 + cdg: 457, 334 + hkg: 1032, 335 + ewr: 1740, 336 + eze: 790, 337 + waw: 586, 338 + bom: 1131, 339 + otp: 724, 340 + lax: 1774, 341 + yul: 1366, 342 + atl: 692, 343 + bog: 856, 344 + bos: 1666, 345 + }, 346 + { 347 + timestamp: "Jul 6, 06:00", 348 + iad: 762, 349 + scl: 802, 350 + yyz: 1014, 351 + ams: 644, 352 + mad: 650, 353 + syd: 1220, 354 + jnb: 456, 355 + gdl: 1072, 356 + qro: 746, 357 + dfw: 916, 358 + gru: 720, 359 + mia: 597, 360 + sjc: 594, 361 + lhr: 908, 362 + phx: 810, 363 + gig: 674, 364 + lax: 1406, 365 + eze: 918, 366 + ewr: 582, 367 + bom: 862, 368 + otp: 582, 369 + waw: 498, 370 + bos: 840, 371 + bog: 972, 372 + yul: 524, 373 + atl: 788, 374 + sin: 982, 375 + sea: 1035, 376 + arn: 1486, 377 + hkg: 1238, 378 + cdg: 714, 379 + fra: 420, 380 + den: 962, 381 + ord: 828, 382 + }, 383 + { 384 + timestamp: "Jul 6, 07:00", 385 + syd: 1133, 386 + jnb: 515, 387 + yyz: 744, 388 + ams: 750, 389 + mad: 716, 390 + iad: 790, 391 + scl: 781, 392 + phx: 944, 393 + lhr: 746, 394 + gig: 816, 395 + mia: 622, 396 + sjc: 597, 397 + gru: 667, 398 + dfw: 896, 399 + qro: 928, 400 + gdl: 832, 401 + yul: 594, 402 + atl: 668, 403 + bog: 678, 404 + bos: 642, 405 + ewr: 664, 406 + eze: 638, 407 + waw: 711, 408 + otp: 765, 409 + bom: 685, 410 + lax: 865, 411 + fra: 624, 412 + ord: 645, 413 + den: 1426, 414 + cdg: 672, 415 + hkg: 1026, 416 + arn: 971, 417 + sin: 1018, 418 + sea: 976, 419 + }, 420 + { 421 + timestamp: "Jul 6, 08:00", 422 + sjc: 927, 423 + mia: 700, 424 + gig: 1416, 425 + lhr: 754, 426 + phx: 584, 427 + gdl: 1022, 428 + qro: 880, 429 + dfw: 811, 430 + gru: 522, 431 + jnb: 498, 432 + syd: 716, 433 + scl: 826, 434 + iad: 940, 435 + ams: 550, 436 + mad: 621, 437 + yyz: 718, 438 + hkg: 1240, 439 + cdg: 656, 440 + den: 631, 441 + ord: 837, 442 + fra: 554, 443 + sea: 1008, 444 + sin: 1014, 445 + arn: 1148, 446 + bos: 687, 447 + bog: 779, 448 + atl: 832, 449 + yul: 514, 450 + lax: 888, 451 + otp: 748, 452 + bom: 756, 453 + waw: 674, 454 + eze: 576, 455 + ewr: 600, 456 + }, 457 + { 458 + timestamp: "Jul 6, 09:00", 459 + sea: 921, 460 + sin: 1022, 461 + arn: 1199, 462 + hkg: 1161, 463 + cdg: 574, 464 + den: 627, 465 + ord: 767, 466 + fra: 536, 467 + lax: 497, 468 + bom: 1151, 469 + otp: 2027, 470 + waw: 963, 471 + eze: 2208, 472 + ewr: 841, 473 + bos: 623, 474 + bog: 2252, 475 + atl: 660, 476 + yul: 626, 477 + gdl: 753, 478 + qro: 2429, 479 + dfw: 2695, 480 + gru: 504, 481 + sjc: 2272, 482 + mia: 628, 483 + gig: 655, 484 + lhr: 784, 485 + phx: 1087, 486 + scl: 930, 487 + iad: 700, 488 + ams: 602, 489 + mad: 881, 490 + yyz: 898, 491 + jnb: 427, 492 + syd: 1237, 493 + }, 494 + { 495 + timestamp: "Jul 6, 10:00", 496 + cdg: 919, 497 + hkg: 1140, 498 + fra: 648, 499 + ord: 696, 500 + den: 771, 501 + sin: 929, 502 + sea: 978, 503 + arn: 1283, 504 + bog: 693, 505 + bos: 938, 506 + yul: 822, 507 + atl: 741, 508 + lax: 867, 509 + ewr: 667, 510 + eze: 697, 511 + waw: 1059, 512 + bom: 882, 513 + otp: 1144, 514 + mia: 710, 515 + sjc: 834, 516 + phx: 692, 517 + lhr: 1039, 518 + gig: 562, 519 + qro: 1184, 520 + gdl: 763, 521 + gru: 559, 522 + dfw: 1017, 523 + syd: 1211, 524 + jnb: 540, 525 + iad: 903, 526 + scl: 698, 527 + yyz: 2359, 528 + ams: 882, 529 + mad: 652, 530 + }, 531 + { 532 + timestamp: "Jul 6, 11:00", 533 + mia: 478, 534 + sjc: 746, 535 + gig: 695, 536 + lhr: 873, 537 + phx: 654, 538 + gdl: 1040, 539 + qro: 818, 540 + dfw: 771, 541 + gru: 488, 542 + jnb: 621, 543 + syd: 1641, 544 + scl: 832, 545 + iad: 902, 546 + ams: 970, 547 + mad: 651, 548 + yyz: 858, 549 + hkg: 1218, 550 + cdg: 534, 551 + den: 670, 552 + ord: 820, 553 + fra: 582, 554 + sin: 1031, 555 + sea: 953, 556 + arn: 1214, 557 + bos: 670, 558 + bog: 1596, 559 + atl: 572, 560 + yul: 530, 561 + lax: 766, 562 + bom: 730, 563 + otp: 577, 564 + waw: 406, 565 + eze: 888, 566 + ewr: 544, 567 + }, 568 + { 569 + timestamp: "Jul 6, 12:00", 570 + syd: 1056, 571 + jnb: 474, 572 + yyz: 1458, 573 + ams: 946, 574 + mad: 1626, 575 + iad: 942, 576 + scl: 678, 577 + phx: 1514, 578 + lhr: 786, 579 + gig: 610, 580 + sjc: 1358, 581 + mia: 547, 582 + gru: 542, 583 + dfw: 936, 584 + qro: 946, 585 + gdl: 1024, 586 + yul: 1455, 587 + atl: 535, 588 + bog: 794, 589 + bos: 756, 590 + ewr: 877, 591 + eze: 639, 592 + waw: 609, 593 + bom: 1004, 594 + otp: 724, 595 + lax: 713, 596 + fra: 848, 597 + ord: 1636, 598 + den: 856, 599 + cdg: 1002, 600 + hkg: 1106, 601 + arn: 1200, 602 + sea: 952, 603 + sin: 1027, 604 + }, 605 + { 606 + timestamp: "Jul 6, 13:00", 607 + ewr: 1450, 608 + eze: 748, 609 + waw: 900, 610 + otp: 1000, 611 + bom: 878, 612 + lax: 694, 613 + yul: 752, 614 + atl: 672, 615 + bog: 851, 616 + bos: 679, 617 + arn: 1143, 618 + sea: 1073, 619 + sin: 1085, 620 + fra: 940, 621 + ord: 954, 622 + den: 1604, 623 + cdg: 781, 624 + hkg: 1078, 625 + yyz: 719, 626 + ams: 702, 627 + mad: 951, 628 + iad: 1329, 629 + scl: 830, 630 + syd: 1702, 631 + jnb: 794, 632 + gru: 530, 633 + dfw: 931, 634 + qro: 979, 635 + gdl: 1704, 636 + phx: 1432, 637 + lhr: 633, 638 + gig: 635, 639 + sjc: 784, 640 + mia: 1532, 641 + }, 642 + { 643 + timestamp: "Jul 6, 14:00", 644 + cdg: 704, 645 + hkg: 1144, 646 + fra: 392, 647 + ord: 663, 648 + den: 705, 649 + sea: 974, 650 + sin: 1066, 651 + arn: 1201, 652 + bog: 714, 653 + bos: 686, 654 + yul: 682, 655 + atl: 630, 656 + lax: 682, 657 + ewr: 1177, 658 + eze: 636, 659 + waw: 476, 660 + otp: 818, 661 + bom: 1000, 662 + sjc: 740, 663 + mia: 754, 664 + phx: 1424, 665 + lhr: 872, 666 + gig: 578, 667 + qro: 1008, 668 + gdl: 854, 669 + gru: 614, 670 + dfw: 906, 671 + syd: 921, 672 + jnb: 712, 673 + iad: 910, 674 + scl: 718, 675 + yyz: 886, 676 + mad: 693, 677 + ams: 788, 678 + }, 679 + { 680 + timestamp: "Jul 6, 15:00", 681 + arn: 1198, 682 + sea: 1036, 683 + sin: 1271, 684 + fra: 986, 685 + den: 966, 686 + ord: 768, 687 + hkg: 1188, 688 + cdg: 740, 689 + eze: 661, 690 + ewr: 572, 691 + bom: 822, 692 + otp: 796, 693 + waw: 896, 694 + lax: 602, 695 + yul: 824, 696 + atl: 950, 697 + bos: 703, 698 + bog: 1102, 699 + dfw: 1038, 700 + gru: 814, 701 + gdl: 1122, 702 + qro: 1032, 703 + lhr: 897, 704 + phx: 1506, 705 + gig: 836, 706 + sjc: 638, 707 + mia: 958, 708 + yyz: 878, 709 + mad: 588, 710 + ams: 526, 711 + iad: 794, 712 + scl: 760, 713 + syd: 847, 714 + jnb: 754, 715 + }, 716 + { 717 + timestamp: "Jul 6, 16:00", 718 + bos: 876, 719 + bog: 974, 720 + atl: 854, 721 + yul: 623, 722 + lax: 775, 723 + bom: 1265, 724 + otp: 880, 725 + waw: 666, 726 + eze: 614, 727 + ewr: 620, 728 + hkg: 1216, 729 + cdg: 704, 730 + den: 808, 731 + ord: 948, 732 + fra: 682, 733 + sin: 1112, 734 + sea: 1044, 735 + arn: 1179, 736 + jnb: 690, 737 + syd: 812, 738 + scl: 700, 739 + iad: 1136, 740 + mad: 796, 741 + ams: 848, 742 + yyz: 655, 743 + mia: 652, 744 + sjc: 576, 745 + gig: 660, 746 + lhr: 744, 747 + phx: 919, 748 + gdl: 1658, 749 + qro: 1036, 750 + dfw: 781, 751 + gru: 660, 752 + }, 753 + { 754 + timestamp: "Jul 6, 17:00", 755 + sea: 1016, 756 + sin: 1122, 757 + arn: 1180, 758 + cdg: 674, 759 + hkg: 1090, 760 + ord: 950, 761 + den: 610, 762 + fra: 778, 763 + lax: 714, 764 + waw: 713, 765 + otp: 911, 766 + bom: 911, 767 + ewr: 902, 768 + eze: 654, 769 + bog: 935, 770 + bos: 868, 771 + atl: 818, 772 + yul: 625, 773 + qro: 1102, 774 + gdl: 1818, 775 + gru: 610, 776 + dfw: 880, 777 + sjc: 758, 778 + mia: 738, 779 + gig: 724, 780 + phx: 1568, 781 + lhr: 870, 782 + scl: 1643, 783 + iad: 1038, 784 + ams: 312, 785 + mad: 646, 786 + yyz: 978, 787 + jnb: 598, 788 + syd: 700, 789 + }, 790 + { 791 + timestamp: "Jul 6, 18:00", 792 + gru: 827, 793 + dfw: 1083, 794 + qro: 704, 795 + gdl: 740, 796 + phx: 750, 797 + lhr: 916, 798 + gig: 533, 799 + sjc: 738, 800 + mia: 778, 801 + yyz: 1141, 802 + mad: 794, 803 + ams: 730, 804 + iad: 598, 805 + scl: 702, 806 + syd: 714, 807 + jnb: 608, 808 + arn: 1198, 809 + sea: 1006, 810 + sin: 1140, 811 + fra: 644, 812 + ord: 762, 813 + den: 1090, 814 + cdg: 667, 815 + hkg: 1179, 816 + ewr: 442, 817 + eze: 648, 818 + waw: 826, 819 + bom: 906, 820 + otp: 570, 821 + lax: 616, 822 + yul: 760, 823 + atl: 748, 824 + bog: 832, 825 + bos: 577, 826 + }, 827 + { 828 + timestamp: "Jul 6, 19:00", 829 + ams: 596, 830 + mad: 946, 831 + yyz: 1022, 832 + scl: 932, 833 + iad: 948, 834 + jnb: 660, 835 + syd: 512, 836 + gru: 692, 837 + dfw: 930, 838 + qro: 942, 839 + gdl: 1192, 840 + gig: 856, 841 + phx: 732, 842 + lhr: 908, 843 + sjc: 1424, 844 + mia: 784, 845 + waw: 824, 846 + bom: 894, 847 + otp: 925, 848 + ewr: 671, 849 + eze: 614, 850 + lax: 600, 851 + atl: 764, 852 + yul: 692, 853 + bog: 860, 854 + bos: 884, 855 + arn: 1181, 856 + sea: 1051, 857 + sin: 1144, 858 + ord: 704, 859 + den: 963, 860 + fra: 768, 861 + cdg: 812, 862 + hkg: 1148, 863 + }, 864 + { 865 + timestamp: "Jul 6, 20:00", 866 + arn: 1154, 867 + sin: 910, 868 + sea: 996, 869 + fra: 849, 870 + den: 692, 871 + ord: 918, 872 + hkg: 1009, 873 + cdg: 857, 874 + eze: 710, 875 + ewr: 508, 876 + otp: 535, 877 + bom: 939, 878 + waw: 1037, 879 + lax: 838, 880 + yul: 721, 881 + atl: 706, 882 + bos: 644, 883 + bog: 1550, 884 + dfw: 1158, 885 + gru: 504, 886 + gdl: 1247, 887 + qro: 1030, 888 + lhr: 991, 889 + phx: 662, 890 + gig: 1118, 891 + mia: 767, 892 + sjc: 565, 893 + yyz: 831, 894 + mad: 676, 895 + ams: 786, 896 + iad: 674, 897 + scl: 855, 898 + syd: 680, 899 + jnb: 798, 900 + }, 901 + { 902 + timestamp: "Jul 6, 21:00", 903 + bom: 929, 904 + otp: 644, 905 + waw: 789, 906 + eze: 764, 907 + ewr: 846, 908 + lax: 952, 909 + atl: 802, 910 + yul: 656, 911 + bos: 867, 912 + bog: 937, 913 + arn: 1238, 914 + sin: 896, 915 + sea: 978, 916 + den: 902, 917 + ord: 1058, 918 + fra: 890, 919 + hkg: 840, 920 + cdg: 558, 921 + ams: 722, 922 + mad: 1050, 923 + yyz: 810, 924 + scl: 870, 925 + iad: 878, 926 + jnb: 926, 927 + syd: 670, 928 + dfw: 1068, 929 + gru: 856, 930 + gdl: 1814, 931 + qro: 1642, 932 + gig: 856, 933 + lhr: 884, 934 + phx: 810, 935 + mia: 1553, 936 + sjc: 896, 937 + }, 938 + { 939 + timestamp: "Jul 6, 22:00", 940 + iad: 762, 941 + scl: 768, 942 + yyz: 888, 943 + ams: 568, 944 + mad: 772, 945 + syd: 708, 946 + jnb: 520, 947 + gdl: 902, 948 + qro: 935, 949 + dfw: 941, 950 + gru: 924, 951 + mia: 846, 952 + sjc: 1512, 953 + lhr: 906, 954 + phx: 1016, 955 + gig: 805, 956 + lax: 520, 957 + eze: 764, 958 + ewr: 402, 959 + bom: 892, 960 + otp: 766, 961 + waw: 716, 962 + bos: 814, 963 + bog: 1193, 964 + yul: 668, 965 + atl: 1036, 966 + sin: 872, 967 + sea: 1157, 968 + arn: 1104, 969 + hkg: 920, 970 + cdg: 690, 971 + fra: 622, 972 + den: 769, 973 + ord: 1066, 974 + }, 975 + { 976 + timestamp: "Jul 6, 23:00", 977 + gdl: 2533, 978 + qro: 2260, 979 + dfw: 2435, 980 + gru: 1060, 981 + mia: 2234, 982 + sjc: 987, 983 + gig: 832, 984 + lhr: 927, 985 + phx: 2214, 986 + scl: 2293, 987 + iad: 1032, 988 + mad: 872, 989 + ams: 850, 990 + yyz: 1206, 991 + jnb: 682, 992 + syd: 853, 993 + sin: 2429, 994 + sea: 1041, 995 + arn: 1176, 996 + hkg: 1077, 997 + cdg: 740, 998 + den: 1074, 999 + ord: 1074, 1000 + fra: 448, 1001 + lax: 2146, 1002 + otp: 857, 1003 + bom: 615, 1004 + waw: 713, 1005 + eze: 514, 1006 + ewr: 797, 1007 + bos: 924, 1008 + bog: 895, 1009 + atl: 1008, 1010 + yul: 414, 1011 + }, 1012 + ], 1013 + regions: ["ams", "gru", "iad", "jnb", "syd", "hkg"], 1014 + }; 1015 + 1016 + export const maintenanceData = { 1017 + id: 0, 1018 + from: new Date("2024-07-09T21:02:43.000Z"), 1019 + to: new Date("2024-07-09T21:05:43.000Z"), 1020 + title: "Maintenance", 1021 + message: 1022 + "We are performing maintenance on our environment. Services and projects may be unavailable for a few minutes.", 1023 + createdAt: null, 1024 + updatedAt: null, 1025 + workspaceId: null, 1026 + pageId: null, 1027 + monitors: undefined, 1028 + }; 1029 + 1030 + export const statusReportData = { 1031 + report: { 1032 + id: 0, 1033 + status: "resolved" as const, 1034 + title: "Downtime", 1035 + workspaceId: 1, 1036 + createdAt: new Date("2024-07-09T21:22:43.000Z"), 1037 + updatedAt: new Date("2024-07-09T21:23:17.000Z"), 1038 + statusReportUpdates: [ 1039 + { 1040 + id: 3, 1041 + status: "resolved" as const, 1042 + date: new Date("2024-07-09T21:59:23.000Z"), 1043 + message: 1044 + "The database server has been successfully restarted and data integrity has been verified. All affected systems are now operational. We are currently monitoring the systems closely to ensure stability and to confirm that there are no further issues.", 1045 + statusReportId: 1, 1046 + createdAt: new Date("2024-07-09T21:59:23.000Z"), 1047 + updatedAt: new Date("2024-07-09T21:59:23.000Z"), 1048 + }, 1049 + { 1050 + id: 2, 1051 + status: "identified" as const, 1052 + date: new Date("2024-07-09T21:44:03.000Z"), 1053 + message: 1054 + "The issue has been identified as a database server failure. Our team is working to restart the server and check data integrity. We will provide an estimated time for resolution shortly.", 1055 + statusReportId: 1, 1056 + createdAt: new Date("2024-07-09T21:23:12.000Z"), 1057 + updatedAt: new Date("2024-07-09T21:23:12.000Z"), 1058 + }, 1059 + { 1060 + id: 1, 1061 + status: "investigating" as const, 1062 + date: new Date("2024-07-09T21:21:23.000Z"), 1063 + message: 1064 + "An unexpected outage occurred affecting the website, customer portal, and internal email systems. Our team is currently investigating the issue to determine the cause and restore services as quickly as possible. Further updates will be provided as soon as more information is available.", 1065 + statusReportId: 1, 1066 + createdAt: new Date("2024-07-09T21:22:43.000Z"), 1067 + updatedAt: new Date("2024-07-09T21:22:43.000Z"), 1068 + }, 1069 + ], 1070 + monitorsToStatusReports: [], 1071 + }, 1072 + monitors: [], 1073 + };
+140
apps/web/src/app/(content)/features/monitoring/page.tsx
··· 1 + import { Mdx } from "@/components/content/mdx"; 2 + import { Chart } from "@/components/monitor-charts/chart"; 3 + import { RegionsPreset } from "@/components/monitor-dashboard/region-preset"; 4 + import { ResponseDetailTabs } from "@/components/ping-response-analysis/response-detail-tabs"; 5 + import { marketingProductPagesConfig } from "@/config/pages"; 6 + import { flyRegions } from "@openstatus/db/src/schema"; 7 + import type { Region } from "@openstatus/tinybird"; 8 + import { Button } from "@openstatus/ui"; 9 + import { allUnrelateds } from "contentlayer/generated"; 10 + import Link from "next/link"; 11 + import { Suspense } from "react"; 12 + import { AssertionsTimingFormExample } from "../_components/assertions-timing-form-example"; 13 + import { Banner } from "../_components/banner"; 14 + import { Hero } from "../_components/hero"; 15 + import { InteractiveFeature } from "../_components/interactive-feature"; 16 + import { mockChartData, mockResponseData } from "../mock"; 17 + import type { Metadata } from "next"; 18 + import { defaultMetadata, ogMetadata, twitterMetadata } from "@/app/shared-metadata"; 19 + 20 + const { description, subtitle } = marketingProductPagesConfig[0]; 21 + const code = allUnrelateds.find( 22 + (unrelated) => unrelated.slug === "ci-cd-features-block" 23 + ); 24 + 25 + export const metadata: Metadata = { 26 + ...defaultMetadata, 27 + title: "API & Website Monitoring | OpenStatus", 28 + description:'Get insights of the latency of your API and website from all over the world.', 29 + twitter: { 30 + ...twitterMetadata, 31 + title: "API & Website Monitoring | OpenStatus", 32 + description:'Get insights of the latency of your API and website from all over the world.', 33 + }, 34 + openGraph: { 35 + ...ogMetadata, 36 + title: "API & Website Monitoring | OpenStatus", 37 + description:'Get insights of the latency of your API and website from all over the world.', 38 + }, 39 + }; 40 + 41 + 42 + export default function FeaturePage() { 43 + return ( 44 + <div className="grid w-full gap-12"> 45 + <Hero title={description} subTitle={subtitle} /> 46 + <InteractiveFeature 47 + icon="activity" 48 + iconText="Website & API monitoring" 49 + title="Global Monitoring." 50 + subTitle="Get insights of the latency worldwide." 51 + component={ 52 + <div className="m-auto"> 53 + <RegionsPreset 54 + regions={flyRegions as unknown as Region[]} 55 + selectedRegions={flyRegions as unknown as Region[]} 56 + /> 57 + </div> 58 + } 59 + col={1} 60 + position={"left"} 61 + /> 62 + <InteractiveFeature 63 + icon="book-open-check" 64 + iconText="Timing & Assertions" 65 + title="Validate the response." 66 + subTitle="Check the return value, status code, header or maximum response time." 67 + component={<AssertionsTimingFormExample />} 68 + col={2} 69 + position={"left"} 70 + withGradient 71 + /> 72 + <InteractiveFeature 73 + icon="timer" 74 + iconText="Request Metrics Insights" 75 + title="Optimize Web Performance." 76 + subTitle="Analyze DNS, TCP, TLS, and TTFB for every request and inspect Response Headers as needed." 77 + component={ 78 + <ResponseDetailTabs 79 + {...mockResponseData} 80 + defaultOpen="timing" 81 + hideInfo={false} 82 + /> 83 + } 84 + col={2} 85 + position={"left"} 86 + withGradient 87 + /> 88 + <InteractiveFeature 89 + icon="line-chart" 90 + iconText="Charts" 91 + title="Opinionated Dashboard." 92 + subTitle="Keep an overview about Uptime, P50, P75, P90, P95, P99 of your monitors." 93 + action={ 94 + <div className="mt-2"> 95 + <Button variant="outline" className="rounded-full" asChild> 96 + <Link href="/public/monitors/1">Public Dashboard</Link> 97 + </Button> 98 + </div> 99 + } 100 + component={ 101 + <Suspense fallback={"loading..."}> 102 + <Chart {...mockChartData} /> 103 + </Suspense> 104 + } 105 + col={2} 106 + position={"top"} 107 + withGradient 108 + /> 109 + <InteractiveFeature 110 + icon="bot" 111 + iconText="API Monitoring" 112 + title="Synthetic Monitoring." 113 + subTitle="Run your check in your CI/CD pipeline or on demand." 114 + component={ 115 + code ? ( 116 + <Mdx 117 + code={code.body.code} 118 + className="max-w-none prose-pre:overflow-hidden" 119 + /> 120 + ) : ( 121 + <p>Code not found</p> 122 + ) 123 + } 124 + action={ 125 + <div className="mt-2"> 126 + <Button variant="outline" className="rounded-full" asChild> 127 + <Link href="https://docs.openstatus.dev/guides/test-latency-cf-workers-in-github-actions"> 128 + How-to 129 + </Link> 130 + </Button> 131 + </div> 132 + } 133 + col={2} 134 + position={"bottom"} 135 + withGradient 136 + /> 137 + <Banner /> 138 + </div> 139 + ); 140 + }
+145
apps/web/src/app/(content)/features/status-page/page.tsx
··· 1 + import { PasswordFormSuspense } from "@/app/status-page/[domain]/_components/password-form"; 2 + import { SubscribeButton } from "@/app/status-page/[domain]/_components/subscribe-button"; 3 + import { MaintenanceBanner } from "@/components/status-page/maintenance-banner"; 4 + import { StatusCheck } from "@/components/status-page/status-check"; 5 + import { StatusReport } from "@/components/status-page/status-report"; 6 + import { Tracker } from "@/components/tracker/tracker"; 7 + import { marketingProductPagesConfig } from "@/config/pages"; 8 + import { Button, InputWithAddons } from "@openstatus/ui"; 9 + import Link from "next/link"; 10 + import { Banner } from "../_components/banner"; 11 + import { Hero } from "../_components/hero"; 12 + import { InteractiveFeature } from "../_components/interactive-feature"; 13 + import { maintenanceData, mockTrackerData, statusReportData } from "../mock"; 14 + import type { Metadata } from "next"; 15 + import { defaultMetadata, ogMetadata, twitterMetadata } from "@/app/shared-metadata"; 16 + 17 + const { description, subtitle } = marketingProductPagesConfig[1]; 18 + 19 + export const metadata: Metadata = { 20 + ...defaultMetadata, 21 + title: "Status Page | OpenStatus", 22 + description:'Easily report to your users with our public or private status page.', 23 + twitter: { 24 + ...twitterMetadata, 25 + title: "Status Page | OpenStatus", 26 + description:'Easily report to your users with our public or private status page.', 27 + }, 28 + openGraph: { 29 + ...ogMetadata, 30 + title: "Status Page | OpenStatus", 31 + description:'Easily report to your users with our public or private status page.', 32 + }, 33 + }; 34 + 35 + export default function FeaturePage() { 36 + return ( 37 + <div className="grid w-full gap-12"> 38 + <Hero title={description} subTitle={subtitle} /> 39 + <InteractiveFeature 40 + icon="globe" 41 + iconText="Customize" 42 + title="Custom Domain." 43 + subTitle="Bring your own domain, give the status page a personal touch." 44 + component={ 45 + <div className="m-auto"> 46 + <InputWithAddons leading="https://" placeholder="status.acme.com" /> 47 + </div> 48 + } 49 + col={1} 50 + position={"left"} 51 + /> 52 + <InteractiveFeature 53 + icon="panel-top" 54 + iconText="Simple by default" 55 + title="Status page." 56 + subTitle="Connect your monitors and inform your users about the uptime." 57 + component={ 58 + <div className="my-auto"> 59 + <Tracker 60 + data={mockTrackerData} 61 + name="OpenStatus" 62 + description="Website Health" 63 + /> 64 + </div> 65 + } 66 + col={2} 67 + position={"left"} 68 + /> 69 + <InteractiveFeature 70 + icon="users" 71 + iconText="Reach your users" 72 + title="Subscriptions." 73 + subTitle="Let your users subscribe to your status page, to automatically receive updates about the status of your services." 74 + component={ 75 + <div className="m-auto"> 76 + <SubscribeButton slug={""} isDemo /> 77 + </div> 78 + } 79 + col={1} 80 + position={"left"} 81 + /> 82 + <InteractiveFeature 83 + icon="search-check" 84 + iconText="Stay up to date" 85 + title="Status Updates." 86 + subTitle="Down't let your users in the dark and show what's wrong." 87 + component={ 88 + <div className="-translate-y-6 m-auto scale-[0.80]"> 89 + <StatusReport {...statusReportData} /> 90 + </div> 91 + } 92 + col={1} 93 + position={"bottom"} 94 + withGradient 95 + /> 96 + <InteractiveFeature 97 + icon="eye-off" 98 + iconText="Restrict access" 99 + title="Password Protection." 100 + subTitle="Hide your page to unexepected users." 101 + component={ 102 + <div className="m-auto max-w-lg"> 103 + <PasswordFormSuspense slug="" /> 104 + </div> 105 + } 106 + col={2} 107 + position={"left"} 108 + /> 109 + <InteractiveFeature 110 + icon="user" 111 + iconText="Keep it simple" 112 + title="Build trust." 113 + subTitle="Showcase your reliability to your users, and reduce the number of customer service tickets." 114 + component={ 115 + <div className="m-auto max-w-lg"> 116 + <StatusCheck /> 117 + </div> 118 + } 119 + action={ 120 + <div className="mt-2"> 121 + <Button variant="outline" className="rounded-full" asChild> 122 + <Link href="https://status.openstatus.dev">Status Page</Link> 123 + </Button> 124 + </div> 125 + } 126 + col={2} 127 + position={"bottom"} 128 + /> 129 + <InteractiveFeature 130 + icon="hammer" 131 + iconText="Handle migrations" 132 + title="Maintenance." 133 + subTitle="Mute your monitors for a specific period and inform the users about upcoming maintenance." 134 + component={ 135 + <div className="m-auto"> 136 + <MaintenanceBanner {...maintenanceData} /> 137 + </div> 138 + } 139 + col={2} 140 + position={"left"} 141 + /> 142 + <Banner /> 143 + </div> 144 + ); 145 + }
+2 -2
apps/web/src/app/_components/background.tsx
··· 9 9 <> 10 10 <div className="-z-50 fixed top-0 left-0"> 11 11 <div className="sticky top-0 left-0 h-screen w-screen overflow-hidden"> 12 - <div className="absolute inset-0 z-[-1] bg-muted-foreground/20" /> 13 - <div className="-translate-x-1/2 -translate-y-1/2 absolute top-[--y] left-[--x] z-[-1] h-56 w-56 rounded-full bg-gradient-radial from-0% from-muted-foreground/50 to-90% to-transparent blur-md" /> 12 + <div className="absolute inset-0 z-[-1] bg-muted-foreground/15" /> 13 + <div className="-translate-x-1/2 -translate-y-1/2 absolute top-[--y] left-[--x] z-[-1] h-56 w-56 rounded-full bg-gradient-radial from-0% from-muted-foreground/40 to-90% to-transparent blur-md" /> 14 14 <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> 15 15 <defs> 16 16 <pattern
+37 -40
apps/web/src/app/play/checker/[id]/page.tsx
··· 50 50 const { region, headers, timing, status } = check; 51 51 52 52 return ( 53 - <> 54 - <BackButton href="/play/checker" /> 55 - <Shell className="flex flex-col gap-8"> 56 - <div className="flex justify-between gap-4"> 57 - <div className="flex max-w-[calc(100%-50px)] flex-col gap-1"> 58 - <h1 className="truncate text-wrap font-semibold text-lg md:text-3xl sm:text-xl"> 59 - {data.url} 60 - </h1> 61 - <p className="text-muted-foreground text-sm sm:text-base"> 62 - {timestampFormatter(data.time)} 63 - </p> 53 + <Shell className="my-8 flex flex-col gap-8 md:my-16"> 54 + <div className="flex justify-between gap-4"> 55 + <div className="flex max-w-[calc(100%-50px)] flex-col gap-1"> 56 + <h1 className="truncate text-wrap font-semibold text-lg md:text-3xl sm:text-xl"> 57 + {data.url} 58 + </h1> 59 + <p className="text-muted-foreground text-sm sm:text-base"> 60 + {timestampFormatter(data.time)} 61 + </p> 62 + </div> 63 + <div> 64 + <CopyLinkButton /> 65 + </div> 66 + </div> 67 + <MultiRegionTabs regions={data.checks} /> 68 + <Separator /> 69 + <div className="flex flex-col gap-8"> 70 + <div className="grid gap-8 md:grid-cols-2"> 71 + <div> 72 + <SelectRegion defaultValue={region} /> 64 73 </div> 65 74 <div> 66 - <CopyLinkButton /> 75 + <RegionInfo check={check} /> 67 76 </div> 68 77 </div> 69 - <MultiRegionTabs regions={data.checks} /> 70 - <Separator /> 71 - <div className="flex flex-col gap-8"> 72 - <div className="grid gap-8 md:grid-cols-2"> 73 - <div> 74 - <SelectRegion defaultValue={region} /> 75 - </div> 76 - <div> 77 - <RegionInfo check={check} /> 78 - </div> 79 - </div> 80 - <ResponseDetailTabs {...{ timing, headers, status }} /> 81 - </div> 82 - <Separator /> 83 - <p className="text-muted-foreground text-sm"> 84 - The data will be stored for{" "} 85 - <span className="text-foreground">1 day</span>. If you want to persist 86 - the data,{" "} 87 - <Link 88 - href="/app/login" 89 - className="text-foreground underline underline-offset-4 hover:no-underline" 90 - > 91 - login 92 - </Link>{" "} 93 - to your account. 94 - </p> 95 - </Shell> 96 - </> 78 + <ResponseDetailTabs {...{ timing, headers, status }} hideInfo={false} /> 79 + </div> 80 + <Separator /> 81 + <p className="text-muted-foreground text-sm"> 82 + The data will be stored for{" "} 83 + <span className="text-foreground">1 day</span>. If you want to persist 84 + the data,{" "} 85 + <Link 86 + href="/app/login" 87 + className="text-foreground underline underline-offset-4 hover:no-underline" 88 + > 89 + login 90 + </Link>{" "} 91 + to your account. 92 + </p> 93 + </Shell> 97 94 ); 98 95 } 99 96
+7 -12
apps/web/src/app/play/checker/page.tsx
··· 19 19 20 20 export default async function PlayPage() { 21 21 return ( 22 - <> 23 - <div className="mx-auto space-y-12"> 24 - <div className="mt-12"> 25 - <BackButton href="/" /> 26 - <CheckerPlay /> 27 - </div> 28 - <Testimonial /> 29 - <GlobalMonitoring /> 30 - <div className="mx-auto max-w-2xl lg:max-w-4xl"> 31 - <BottomCTA /> 32 - </div> 22 + <div className="my-8 grid h-full w-full gap-12 md:my-16"> 23 + <CheckerPlay /> 24 + <Testimonial /> 25 + <GlobalMonitoring /> 26 + <div className="mx-auto max-w-2xl lg:max-w-4xl"> 27 + <BottomCTA /> 33 28 </div> 34 - </> 29 + </div> 35 30 ); 36 31 }
+1 -2
apps/web/src/app/play/page.tsx
··· 34 34 export default async function PlayPage() { 35 35 return ( 36 36 <> 37 - <BackButton href="/" /> 38 - <div className="grid w-full grid-cols-1 gap-4 md:grid-cols-3 sm:grid-cols-2"> 37 + <div className="my-8 grid w-full grid-cols-1 gap-4 md:my-16 md:grid-cols-3 sm:grid-cols-2"> 39 38 {playgrounds.map((play, i) => { 40 39 const isFirst = i === 0; 41 40 return (
+2 -3
apps/web/src/app/play/status/page.tsx
··· 17 17 18 18 export default async function PlayPage() { 19 19 return ( 20 - <> 21 - <BackButton href="/" /> 20 + <div className="my-8 md:my-16"> 22 21 <StatusPlay /> 23 - </> 22 + </div> 24 23 ); 25 24 }
+10 -3
apps/web/src/app/status-page/[domain]/_components/password-form.tsx
··· 22 22 import { LoadingAnimation } from "@/components/loading-animation"; 23 23 import { useCookieState } from "@/hooks/use-cookie-state"; 24 24 import { toast, toastAction } from "@/lib/toast"; 25 + import { wait } from "@/lib/utils"; 25 26 import { createProtectedCookieKey } from "../utils"; 26 27 import { handleValidatePassword } from "./actions"; 27 28 ··· 30 31 // in the `layout.tsx` because we cannot access the search params there 31 32 32 33 const schema = z.object({ 33 - password: z.string(), 34 + password: z.string().min(1), 34 35 }); 35 36 36 37 type Schema = z.infer<typeof schema>; ··· 69 70 formData.append("password", data.password); 70 71 formData.append("slug", slug); 71 72 73 + // REMINDER: used for the demo on features/status-page 74 + if (slug === "") { 75 + await wait(500); 76 + return; 77 + } 78 + 72 79 const res = await handleValidatePassword(formData); 73 80 74 81 if (res?.error || res.data === undefined) { ··· 101 108 <FormLabel>Password</FormLabel> 102 109 <FormControl> 103 110 <InputWithAddons 104 - placeholder="top-secret" 111 + placeholder="open-source" 105 112 type={inputType} 106 113 disabled={loading} 107 114 trailing={ ··· 109 116 <button 110 117 onClick={() => 111 118 setInputType((type) => 112 - type === "password" ? "text" : "password", 119 + type === "password" ? "text" : "password" 113 120 ) 114 121 } 115 122 >
+18 -9
apps/web/src/app/status-page/[domain]/_components/subscribe-button.tsx
··· 14 14 15 15 import { LoadingAnimation } from "@/components/loading-animation"; 16 16 import { toast } from "@/lib/toast"; 17 + import { wait } from "@/lib/utils"; 17 18 import { handleSubscribe } from "./actions"; 18 19 19 20 interface Props { 20 21 slug: string; 22 + isDemo?: boolean; 21 23 } 22 24 23 - export function SubscribeButton({ slug }: Props) { 25 + export function SubscribeButton({ slug, isDemo = false }: Props) { 24 26 return ( 25 27 <Popover> 26 28 <PopoverTrigger asChild> ··· 42 44 <form 43 45 className="grid gap-2" 44 46 action={async (formData) => { 45 - const res = await handleSubscribe(formData); 46 - if (res?.error) { 47 - toast.error("Something went wrong", { 48 - description: res.error, 47 + if (!isDemo) { 48 + const res = await handleSubscribe(formData); 49 + if (res?.error) { 50 + toast.error("Something went wrong", { 51 + description: res.error, 52 + }); 53 + return; 54 + } 55 + toast.message("Success", { 56 + description: "Please confirm your email.", 57 + }); 58 + } else { 59 + await wait(1000); 60 + toast.message("Success (Demo)", { 61 + description: "Please confirm your email (not).", 49 62 }); 50 - return; 51 63 } 52 - toast.message("Success", { 53 - description: "Please confirm your email.", 54 - }); 55 64 }} 56 65 > 57 66 <Label htmlFor="email">Email</Label>
+2
apps/web/src/components/content/mdx-components.tsx
··· 23 23 24 24 import type { MetricsCardProps } from "../monitor-dashboard/metrics-card"; 25 25 import { MetricsCard } from "../monitor-dashboard/metrics-card"; 26 + import Pre from "./pre"; 26 27 import type { SimpleChartProps } from "./simple-chart"; 27 28 import { SimpleChart } from "./simple-chart"; 28 29 ··· 88 89 td: (props: TdHTMLAttributes<HTMLTableCellElement>) => ( 89 90 <TableCell {...props} /> 90 91 ), 92 + pre: (props: HTMLAttributes<HTMLPreElement>) => <Pre {...props} />, 91 93 };
+2 -2
apps/web/src/components/content/mdx.tsx
··· 15 15 // FIXME: weird behaviour when `prose-headings:font-cal` and on mouse movement font gets bigger 16 16 <div 17 17 className={cn( 18 - "prose prose-slate dark:prose-invert prose-img:rounded-lg prose-pre:rounded-lg prose-img:border prose-pre:border prose-img:border-border prose-pre:border-border prose-headings:font-cal prose-headings:font-normal", 19 - className, 18 + "prose prose-slate dark:prose-invert prose-pre:my-0 prose-img:rounded-lg prose-pre:rounded-lg prose-img:border prose-pre:border prose-img:border-border prose-pre:border-border prose-headings:font-cal prose-headings:font-normal", 19 + className 20 20 )} 21 21 > 22 22 <MDXComponent components={{ ...components }} />
+52
apps/web/src/components/content/pre.tsx
··· 1 + "use client"; 2 + 3 + import { Button } from "@openstatus/ui"; 4 + import { Clipboard, ClipboardCopy } from "lucide-react"; 5 + import React from "react"; 6 + 7 + export interface PreProps extends React.HTMLAttributes<HTMLPreElement> {} 8 + 9 + export default function Pre({ children, ...props }: PreProps) { 10 + const [copied, setCopied] = React.useState(false); 11 + const ref = React.useRef<HTMLPreElement>(null); 12 + 13 + React.useEffect(() => { 14 + let timer: ReturnType<typeof setTimeout>; 15 + if (copied) { 16 + timer = setTimeout(() => { 17 + setCopied(false); 18 + }, 2000); 19 + } 20 + return () => { 21 + clearTimeout(timer); 22 + }; 23 + }, [copied]); 24 + 25 + const onClick = () => { 26 + setCopied(true); 27 + const content = ref.current?.textContent; 28 + if (content) { 29 + navigator.clipboard.writeText(content); 30 + } 31 + }; 32 + 33 + return ( 34 + <div className="relative overflow-hidden"> 35 + <Button 36 + variant="outline" 37 + size="icon" 38 + className="absolute top-4 right-4" 39 + onClick={onClick} 40 + > 41 + {!copied ? ( 42 + <Clipboard className="h-5 w-5 text-black" /> 43 + ) : ( 44 + <ClipboardCopy className="h-5 w-5 text-brand-900" /> 45 + )} 46 + </Button> 47 + <pre ref={ref} {...props}> 48 + {children} 49 + </pre> 50 + </div> 51 + ); 52 + }
+4 -4
apps/web/src/components/dashboard/tabs.tsx
··· 11 11 export function TabsList({ 12 12 className, 13 13 ...props 14 - }: React.ComponentPropsWithoutRef<typeof ShadcnTabsList>) { 14 + }: React.ComponentPropsWithoutRef<typeof ShadcnTabsList> & {}) { 15 15 return ( 16 - <> 16 + // REMINDER: needed for sticky header - ideally, we remove the div 17 + <div className={className}> 17 18 <ShadcnTabsList 18 19 className={cn( 19 20 "w-full justify-start overflow-x-auto overflow-y-hidden rounded-none bg-transparent p-0", 20 - className, 21 21 )} 22 22 {...props} 23 23 /> 24 24 <Separator className="mb-6" /> 25 - </> 25 + </div> 26 26 ); 27 27 } 28 28
+9 -9
apps/web/src/components/forms/monitor/section-assertions.tsx
··· 70 70 name="degradedAfter" 71 71 render={({ field }) => ( 72 72 <FormItem className="col-span-6 sm:col-span-3"> 73 - <FormLabel>Degraded</FormLabel> 73 + <FormLabel> 74 + Degraded <span className="font-normal">(in ms.)</span> 75 + </FormLabel> 74 76 <FormControl> 75 77 <Input 76 - className="bg-muted" 77 78 type="number" 78 79 min={0} 79 80 max={60000} ··· 84 85 /> 85 86 </FormControl> 86 87 <FormDescription> 87 - In milliseconds, the time after which the endpoint is considered 88 - degraded. 88 + Time after which the endpoint is considered degraded. 89 89 </FormDescription> 90 90 </FormItem> 91 91 )} ··· 95 95 name="timeout" 96 96 render={({ field }) => ( 97 97 <FormItem className="col-span-6 sm:col-span-3"> 98 - <FormLabel>Timeout</FormLabel> 98 + <FormLabel> 99 + Timeout <span className="font-normal">(in ms.)</span> 100 + </FormLabel> 99 101 <FormControl> 100 102 <Input 101 - className="bg-muted" 102 103 type="number" 103 104 placeholder="45000" 104 105 min={0} ··· 107 108 /> 108 109 </FormControl> 109 110 <FormDescription> 110 - In milliseconds, the maximum time allowed for the request to 111 - complete. 111 + Max. time allowed for request to complete. 112 112 </FormDescription> 113 113 114 114 {/* <FormMessage /> */} ··· 293 293 </div> 294 294 </div> 295 295 ))} 296 - <div className="flex gap-4"> 296 + <div className="flex flex-wrap gap-4"> 297 297 <Button 298 298 variant="outline" 299 299 type="button"
+10
apps/web/src/components/icons.tsx
··· 3 3 AlertTriangle, 4 4 Bell, 5 5 Book, 6 + BookOpenCheck, 6 7 Bot, 7 8 Calendar, 8 9 Camera, ··· 13 14 Copy, 14 15 CreditCard, 15 16 Eye, 17 + EyeOff, 16 18 FileClock, 17 19 Fingerprint, 20 + Gauge, 18 21 Globe2, 19 22 Hammer, 20 23 Hourglass, ··· 22 25 KeyRound, 23 26 Laptop, 24 27 LayoutDashboard, 28 + Library, 25 29 LineChart, 26 30 Link, 27 31 Linkedin, ··· 30 34 Minus, 31 35 Moon, 32 36 Newspaper, 37 + Package, 33 38 PanelTop, 34 39 Pencil, 35 40 Play, ··· 66 71 "panel-top": PanelTop, 67 72 table: Table, 68 73 "toy-brick": ToyBrick, 74 + gauge: Gauge, 75 + package: Package, 76 + library: Library, 69 77 cog: Cog, 70 78 hammer: Hammer, 71 79 search: Search, ··· 88 96 bell: Bell, 89 97 zap: Zap, 90 98 eye: Eye, 99 + "eye-off": EyeOff, 91 100 users: Users, 92 101 key: KeyRound, 93 102 "credit-card": CreditCard, ··· 112 121 ratio: Ratio, 113 122 user: UserCircle, 114 123 camera: Camera, 124 + "book-open-check": BookOpenCheck, 115 125 discord: ({ ...props }: LucideProps) => ( 116 126 <svg viewBox="0 0 640 512" {...props}> 117 127 <path
+9 -4
apps/web/src/components/layout/brand-name.tsx
··· 10 10 ContextMenuItem, 11 11 ContextMenuTrigger, 12 12 } from "@openstatus/ui"; 13 + import Image from "next/image"; 13 14 14 15 export function BrandName() { 15 16 return ( 16 17 <ContextMenu> 17 18 <ContextMenuTrigger> 18 - <Link 19 - href="/" 20 - className="font-cal text-lg text-muted-foreground hover:text-foreground" 21 - > 19 + <Link href="/" className="flex items-center gap-2 font-cal"> 20 + <Image 21 + src="/icon.png" 22 + alt="OpenStatus" 23 + height={30} 24 + width={30} 25 + className="rounded-full border border-border bg-transparent" 26 + /> 22 27 OpenStatus 23 28 </Link> 24 29 </ContextMenuTrigger>
+1 -1
apps/web/src/components/layout/marketing-footer.tsx
··· 21 21 <div className="col-span-2 flex flex-col gap-3"> 22 22 <div> 23 23 <BrandName /> 24 - <p className="mt-2 font-light text-muted-foreground text-sm"> 24 + <p className="mt-2 max-w-md font-light text-muted-foreground text-sm"> 25 25 We are on a mission to provide a reliable, easy and fast way to 26 26 monitor the performance of your APIs and websites. 27 27 <br />
+113 -25
apps/web/src/components/layout/marketing-header.tsx
··· 1 1 "use client"; 2 2 3 - import Link from "next/link"; 3 + import Link, { type LinkProps } from "next/link"; 4 4 import { usePathname } from "next/navigation"; 5 5 6 - import { Button } from "@openstatus/ui"; 6 + import { 7 + NavigationMenu, 8 + NavigationMenuContent, 9 + NavigationMenuItem, 10 + NavigationMenuLink, 11 + NavigationMenuList, 12 + NavigationMenuTrigger, 13 + navigationMenuTriggerStyle, 14 + } from "@openstatus/ui"; 7 15 8 16 import { marketingPagesConfig } from "@/config/pages"; 9 17 import { cn } from "@/lib/utils"; 18 + import * as React from "react"; 19 + import { Icons, type ValidIcon } from "../icons"; 10 20 import { BrandName } from "./brand-name"; 11 21 import { LoginButton } from "./login-button"; 12 22 import { MarketingMenu } from "./marketing-menu"; ··· 20 30 21 31 return ( 22 32 <header 23 - className={cn("grid w-full grid-cols-2 gap-2 md:grid-cols-5", className)} 33 + className={cn( 34 + "sticky top-3 z-10 flex w-full items-center justify-between gap-8 rounded-full border border-border px-2.5 py-1.5 backdrop-blur-lg md:top-6", 35 + className 36 + )} 24 37 > 25 - <div className="flex items-center md:col-span-1"> 26 - <BrandName /> 27 - </div> 28 - <div className="mx-auto hidden items-center justify-center rounded-full border border-border px-2 backdrop-blur-[2px] md:col-span-3 md:flex md:gap-1"> 29 - {marketingPagesConfig.map(({ href, title, segment }) => { 30 - const isExternal = href.startsWith("http"); 31 - const externalProps = isExternal ? { target: "_blank" } : {}; 32 - const isActive = pathname.startsWith(href); 33 - return ( 34 - <Button 35 - key={segment} 36 - variant="link" 37 - className={isActive ? "font-semibold" : undefined} 38 - asChild 39 - > 40 - <Link href={href} {...externalProps}> 41 - {title} 42 - </Link> 43 - </Button> 44 - ); 45 - })} 38 + <div className="flex items-center gap-6"> 39 + <div className="ml-3 flex items-center gap-3"> 40 + <BrandName /> 41 + </div> 42 + <div 43 + className={cn( 44 + "mx-auto hidden items-center justify-center border border-transparent md:flex md:gap-1" 45 + )} 46 + > 47 + <NavigationMenu> 48 + <NavigationMenuList> 49 + {marketingPagesConfig.map((page) => { 50 + const { href, title, segment, children } = page; 51 + if (!children) { 52 + return ( 53 + <NavigationMenuItem key={title}> 54 + <Link href={href} legacyBehavior passHref> 55 + <NavigationMenuLink 56 + className={cn( 57 + navigationMenuTriggerStyle(), 58 + "h-9 rounded-full bg-transparent text-muted-foreground hover:bg-accent/50", 59 + { "text-foreground": href === pathname } 60 + )} 61 + > 62 + {title} 63 + </NavigationMenuLink> 64 + </Link> 65 + </NavigationMenuItem> 66 + ); 67 + } 68 + 69 + return ( 70 + <NavigationMenuItem key={href}> 71 + <NavigationMenuTrigger className="h-9 rounded-full bg-transparent text-muted-foreground hover:bg-transparent data-[state=open]:text-accent-foreground"> 72 + {title} 73 + </NavigationMenuTrigger> 74 + <NavigationMenuContent> 75 + <ul className="grid w-[400px] gap-3 p-4 lg:w-[600px] md:w-[500px] md:grid-cols-2"> 76 + {children?.map((item) => { 77 + const isExternal = item.href.startsWith("http"); 78 + const _externalProps = isExternal 79 + ? { target: "_blank" } 80 + : {}; 81 + const _isActive = pathname.startsWith(item.href); 82 + return ( 83 + <ListItem 84 + key={item.title} 85 + title={item.title} 86 + href={item.href} 87 + icon={item.icon} 88 + > 89 + {item.description} 90 + </ListItem> 91 + ); 92 + })} 93 + </ul> 94 + </NavigationMenuContent> 95 + </NavigationMenuItem> 96 + ); 97 + })} 98 + </NavigationMenuList> 99 + </NavigationMenu> 100 + </div> 46 101 </div> 47 - <div className="flex items-center justify-end gap-3 md:col-span-1"> 102 + <div className="flex items-center justify-end gap-3"> 48 103 <div className="block md:hidden"> 49 104 <MarketingMenu /> 50 105 </div> ··· 53 108 </header> 54 109 ); 55 110 } 111 + 112 + const ListItem = React.forwardRef< 113 + React.ElementRef<"a">, 114 + React.ComponentPropsWithoutRef<"a"> & LinkProps & { icon: ValidIcon } 115 + >(({ className, title, children, icon, ...props }, ref) => { 116 + // TODO: if external, add Arrow-Right-Up Icon 117 + const Icon = Icons[icon]; 118 + return ( 119 + <li className="group"> 120 + <NavigationMenuLink asChild> 121 + <Link 122 + ref={ref} 123 + className={cn( 124 + "flex select-none gap-3 space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors focus:bg-accent hover:bg-accent focus:text-accent-foreground hover:text-accent-foreground", 125 + className 126 + )} 127 + {...props} 128 + > 129 + <div className="self-start rounded-md border p-2 group-hover:bg-background"> 130 + <Icon className="h-4 w-4" /> 131 + </div> 132 + <div className="grid gap-1"> 133 + <div className="font-medium text-sm leading-none">{title}</div> 134 + <p className="line-clamp-2 text-muted-foreground text-sm leading-snug"> 135 + {children} 136 + </p> 137 + </div> 138 + </Link> 139 + </NavigationMenuLink> 140 + </li> 141 + ); 142 + }); 143 + ListItem.displayName = "ListItem";
+105 -16
apps/web/src/components/layout/marketing-menu.tsx
··· 5 5 import * as React from "react"; 6 6 7 7 import { 8 + Accordion, 9 + AccordionContent, 10 + AccordionItem, 11 + AccordionTrigger, 8 12 Button, 9 13 Sheet, 10 14 SheetContent, ··· 15 19 16 20 import { marketingPagesConfig } from "@/config/pages"; 17 21 import { socialsConfig } from "@/config/socials"; 18 - import { AppLink } from "./app-link"; 22 + import { useWindowScroll } from "@/hooks/use-window-scroll"; 23 + import { cn } from "@/lib/utils"; 24 + import Link, { type LinkProps } from "next/link"; 25 + import { Icons, type ValidIcon } from "../icons"; 19 26 import { LoginButton } from "./login-button"; 20 27 import { SocialIconButton } from "./social-icon-button"; 21 28 22 29 export function MarketingMenu() { 23 30 const [open, setOpen] = React.useState(false); 24 31 const pathname = usePathname(); 32 + const [{ y }] = useWindowScroll(); 33 + const _isScroll = React.useMemo(() => y && y > 0, [y]); 25 34 26 35 React.useEffect(() => { 27 36 setOpen(false); ··· 39 48 <Menu className="h-6 w-6" /> 40 49 </Button> 41 50 </SheetTrigger> 42 - <SheetContent side="top" className="flex flex-col"> 51 + <SheetContent side="top" className={cn("flex flex-col")}> 43 52 <SheetHeader> 44 - <SheetTitle className="ml-2 text-left">Menu</SheetTitle> 53 + <SheetTitle className="text-left">Menu</SheetTitle> 45 54 </SheetHeader> 46 - <div className="flex flex-1 flex-col justify-between gap-4"> 55 + <div className="flex flex-1 flex-col justify-between gap-8"> 47 56 <ul className="grid gap-1"> 48 - {/* biome-ignore lint/correctness/noUnusedVariables: <explanation> */} 49 - {marketingPagesConfig.map(({ href, title, segment }) => { 50 - const isExternal = href.startsWith("http"); 51 - const externalProps = isExternal ? { target: "_blank" } : {}; 52 - const isActive = pathname.startsWith(href); 57 + {marketingPagesConfig.map(({ href, title, icon, children }) => { 58 + if (!children) { 59 + const isExternal = href.startsWith("http"); 60 + const _externalProps = isExternal ? { target: "_blank" } : {}; 61 + const _isActive = pathname.startsWith(href); 62 + return ( 63 + <li key={href} className="w-full"> 64 + <ListItemSingle 65 + title={title} 66 + href={href} 67 + icon={icon} 68 + onClick={() => setOpen(false)} 69 + /> 70 + </li> 71 + ); 72 + } 73 + 53 74 return ( 54 - <li key={href} className="w-full"> 55 - <AppLink 56 - href={href} 57 - label={title} 58 - active={isActive} 59 - {...externalProps} 60 - /> 75 + <li key={href}> 76 + <Accordion type="single" collapsible className="w-full"> 77 + <AccordionItem value={title}> 78 + <AccordionTrigger>{title}</AccordionTrigger> 79 + <AccordionContent> 80 + <ul> 81 + {children.map((page) => { 82 + const { href, title, icon } = page; 83 + const isExternal = href.startsWith("http"); 84 + const _externalProps = isExternal 85 + ? { target: "_blank" } 86 + : {}; 87 + const _isActive = pathname.startsWith(href); 88 + return ( 89 + <li key={href} className="w-full"> 90 + <ListItem 91 + title={title} 92 + href={href} 93 + icon={icon} 94 + onClick={() => setOpen(false)} 95 + /> 96 + </li> 97 + ); 98 + })} 99 + </ul> 100 + </AccordionContent> 101 + </AccordionItem> 102 + </Accordion> 61 103 </li> 62 104 ); 63 105 })} ··· 77 119 </Sheet> 78 120 ); 79 121 } 122 + 123 + const ListItem = React.forwardRef< 124 + React.ElementRef<"a">, 125 + React.ComponentPropsWithoutRef<"a"> & LinkProps & { icon: ValidIcon } 126 + >(({ className, title, children, icon, ...props }, ref) => { 127 + // TODO: if external, add Arrow-Right-Up Icon 128 + const Icon = Icons[icon]; 129 + return ( 130 + <li className="group"> 131 + <Link 132 + ref={ref} 133 + className={cn( 134 + "flex select-none items-center gap-2 space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors focus:bg-accent hover:bg-accent focus:text-accent-foreground hover:text-accent-foreground", 135 + className 136 + )} 137 + {...props} 138 + > 139 + <Icon className="h-4 w-4" /> 140 + <div className="font-medium text-sm leading-none">{title}</div> 141 + </Link> 142 + </li> 143 + ); 144 + }); 145 + ListItem.displayName = "ListItem"; 146 + 147 + const ListItemSingle = React.forwardRef< 148 + React.ElementRef<"a">, 149 + React.ComponentPropsWithoutRef<"a"> & LinkProps & { icon: ValidIcon } 150 + >(({ className, title, children, icon, ...props }, ref) => { 151 + // TODO: if external, add Arrow-Right-Up Icon 152 + const _Icon = Icons[icon]; 153 + return ( 154 + <li className="group"> 155 + <Link 156 + ref={ref} 157 + className={cn( 158 + "flex flex-1 items-center justify-between border-b py-4 font-medium transition-all hover:underline", 159 + className 160 + )} 161 + {...props} 162 + > 163 + {title} 164 + </Link> 165 + </li> 166 + ); 167 + }); 168 + ListItemSingle.displayName = "ListItemSingle";
+1 -1
apps/web/src/components/marketing/status-page/tracker-example.tsx
··· 35 35 }, 36 36 { 37 37 revalidate: 600, // 10 minutes 38 - }, 38 + } 39 39 ); 40 40 41 41 if (!data) return null;
+1 -1
apps/web/src/components/monitor-charts/chart.tsx
··· 10 10 import { dataFormatter, regionFormatter } from "./utils"; 11 11 12 12 interface ChartProps { 13 - data: { timestamp: string; [key: string]: string }[]; 13 + data: { timestamp: string; [key: string]: string | number }[]; 14 14 regions: string[]; 15 15 } 16 16
+13 -5
apps/web/src/components/ping-response-analysis/response-detail-tabs.tsx
··· 17 17 status, 18 18 message, 19 19 assertions, 20 + defaultOpen, 21 + hideInfo = true, 22 + className, 20 23 }: { 21 24 timing: Timing | null; 22 25 headers: Record<string, string> | null; 23 26 status: number | null; 24 27 message?: string | null; 25 28 assertions?: Assertion[] | null; 29 + defaultOpen?: string; 30 + hideInfo?: boolean; 31 + className?: string; 26 32 }) { 27 - const defaultValue = headers ? "headers" : timing ? "timing" : "message"; 33 + const defaultValue = 34 + defaultOpen || headers ? "headers" : timing ? "timing" : "message"; 28 35 return ( 29 - <Tabs defaultValue={defaultValue}> 30 - <TabsList> 36 + <Tabs defaultValue={defaultValue} className={className}> 37 + <TabsList className="sticky top-0 bg-background"> 31 38 <TabsTrigger value="headers" disabled={!headers}> 32 39 Headers 33 40 </TabsTrigger> ··· 50 57 ) : null} 51 58 </TabsContent> 52 59 <TabsContent value="timing"> 53 - {/* TODO: show hideInfo={false} when in /play/checker page */} 54 - {timing ? <ResponseTimingTable timing={timing} hideInfo /> : null} 60 + {timing ? ( 61 + <ResponseTimingTable timing={timing} hideInfo={hideInfo} /> 62 + ) : null} 55 63 </TabsContent> 56 64 <TabsContent value="message"> 57 65 {message ? (
+2 -2
apps/web/src/components/ping-response-analysis/response-timing-table.tsx
··· 46 46 return ( 47 47 <TableRow key={key}> 48 48 <TableCell> 49 - <div className="flex w-[72px] items-center justify-between gap-2"> 49 + <div className="flex items-center justify-between gap-2"> 50 50 <p className="text-muted-foreground">{short}</p> 51 51 {!hideInfo ? ( 52 52 <Popover> 53 53 <PopoverTrigger className="text-muted-foreground data-[state=open]:text-foreground hover:text-foreground"> 54 - <Info className="h-4 w-4" /> 54 + <Info className="mr-2 h-4 w-4" /> 55 55 </PopoverTrigger> 56 56 <PopoverContent> 57 57 <p className="font-medium">{long}</p>
+3 -3
apps/web/src/components/status-page/status-check.tsx
··· 15 15 incidents, 16 16 maintenances, 17 17 }: { 18 - statusReports: StatusReport[]; 19 - incidents: Incident[]; 20 - maintenances: Maintenance[]; 18 + statusReports?: StatusReport[]; 19 + incidents?: Incident[]; 20 + maintenances?: Maintenance[]; 21 21 }) { 22 22 const tracker = new Tracker({ statusReports, incidents, maintenances }); 23 23 const className = tracker.currentClassName;
+12 -12
apps/web/src/components/status-page/status-report.tsx
··· 16 16 import { StatusBadge } from "../status-update/status-badge"; 17 17 import { ProcessMessage } from "./process-message"; 18 18 import { DateTimeTooltip } from "./datetime-tooltip"; 19 - import { format } from "date-fns"; 20 - import { formatInTimeZone } from "date-fns-tz"; 21 19 22 20 function StatusReport({ 23 21 report, ··· 42 40 return ( 43 41 <div className="flex items-center gap-2"> 44 42 <h3 className="font-semibold text-xl">{report.title}</h3> 45 - <Button 46 - variant="ghost" 47 - size="icon" 48 - className="text-muted-foreground/50 group-hover:text-foreground" 49 - asChild 50 - > 51 - <Link href={setPrefixUrl(`/incidents/${report.id}`, params)}> 52 - <ChevronRight className="h-4 w-4" /> 53 - </Link> 54 - </Button> 43 + {report.id ? ( 44 + <Button 45 + variant="ghost" 46 + size="icon" 47 + className="text-muted-foreground/50 group-hover:text-foreground" 48 + asChild 49 + > 50 + <Link href={setPrefixUrl(`/incidents/${report.id}`, params)}> 51 + <ChevronRight className="h-4 w-4" /> 52 + </Link> 53 + </Button> 54 + ) : null} 55 55 </div> 56 56 ); 57 57 }
+1 -1
apps/web/src/components/tracker/tracker.tsx
··· 77 77 const isMissing = tracker.isDataMissing; 78 78 79 79 return ( 80 - <div className="flex flex-col gap-1.5"> 80 + <div className="flex w-full flex-col gap-1.5"> 81 81 <div className="flex justify-between text-sm"> 82 82 <div className="flex items-center gap-2"> 83 83 <p className="line-clamp-1 font-semibold text-foreground">{name}</p>
+56 -8
apps/web/src/config/pages.ts
··· 207 207 }, 208 208 ] as const satisfies readonly Page[]; 209 209 210 - export const marketingPagesConfig = [ 210 + type MarketingPageType = Page & { subtitle: string }; 211 + 212 + export const marketingProductPagesConfig = [ 213 + { 214 + subtitle: "Get insights of the latency of your API and website from all over the world.", 215 + href: "/features/monitoring", 216 + title: "Monitoring", 217 + description: 218 + "Monitor your API and website globablly.", 219 + segment: "features", 220 + icon: "activity", 221 + }, 222 + { 223 + subtitle: "Easily report to your users with our public or private status page.", 224 + href: "/features/status-page", 225 + title: "Status Page", 226 + description: "Create beautiful status pages within seconds.", 227 + segment: "features", 228 + icon: "panel-top", 229 + }, 230 + ] as const satisfies MarketingPageType[]; 231 + 232 + export const marketingResourcePagesConfig = [ 211 233 { 212 234 href: "/blog", 213 235 title: "Blog", ··· 216 238 icon: "book", 217 239 }, 218 240 { 241 + href: "/changelog", 242 + title: "Changelog", 243 + description: "All the latest features, fixes and work to OpenStatus.", 244 + segment: "changelog", 245 + icon: "newspaper", 246 + }, 247 + { 248 + href: "/play/checker", 249 + title: "Speed Checker", 250 + description: "Check your endpoints latency right away.", 251 + segment: "checker", 252 + icon: "gauge", 253 + }, 254 + { 219 255 href: "/play", 220 256 title: "Playground", 221 257 description: "All the latest tools build by OpenStatus.", 222 258 segment: "play", 223 259 icon: "toy-brick", 224 260 }, 261 + ] as const satisfies Page[]; 262 + 263 + export const marketingPagesConfig = [ 225 264 { 226 - href: "/changelog", 227 - title: "Changelog", 228 - description: "All the latest features, fixes and work to OpenStatus.", 229 - segment: "changelog", 230 - icon: "newspaper", 265 + href: "/product", 266 + title: "Product", 267 + description: "All product features for OpenStatus", 268 + segment: "", 269 + icon: "package", 270 + children: marketingProductPagesConfig, 271 + }, 272 + { 273 + href: "/resources", 274 + description: "All resources for OpenStatus", 275 + title: "Resources", 276 + segment: "", 277 + icon: "library", 278 + children: marketingResourcePagesConfig, 231 279 }, 232 280 { 233 281 href: "/pricing", ··· 243 291 segment: "docs", 244 292 icon: "book", 245 293 }, 246 - ] as const satisfies readonly Page[]; 294 + ] satisfies Page[]; 247 295 248 296 export function getPageBySegment( 249 297 segment: string | string[], 250 - currentPage: readonly Page[] = pagesConfig, 298 + currentPage: readonly Page[] = pagesConfig 251 299 ): Page | undefined { 252 300 if (typeof segment === "string") { 253 301 const page = currentPage.find((page) => page.segment === segment);
+18
apps/web/src/content/unrelated/ci-cd-features-block.mdx
··· 1 + ```ts 2 + test("should fail if slow", async () => { 3 + const data = await fetch("https://api.openstatus.dev/v1/check", { 4 + method: "POST", 5 + headers: { 6 + "Content-Type": "application/json", 7 + "x-openstatus-key": process.env.OPENSTATUS_API_KEY, 8 + }, 9 + body: JSON.stringify({ 10 + url: "https://openstat.us", 11 + method: "GET", 12 + regions: ["ams", "iad", "gru"], 13 + runCount: 5, 14 + aggregated: true, 15 + }), 16 + }); 17 + }); 18 + ```
+41
apps/web/src/hooks/use-window-scroll.ts
··· 1 + // Props: https://github.com/uidotdev/usehooks/blob/90fbbb4cc085e74e50c36a62a5759a40c62bb98e/index.js#L1310 2 + 3 + import * as React from "react"; 4 + 5 + export function useWindowScroll() { 6 + const [state, setState] = React.useState<{ 7 + x: number | null; 8 + y: number | null; 9 + }>({ 10 + x: null, 11 + y: null, 12 + }); 13 + 14 + // biome-ignore lint/suspicious/noExplicitAny: <explanation> 15 + const scrollTo = React.useCallback((...args: any[]) => { 16 + if (typeof args[0] === "object") { 17 + window.scrollTo(args[0]); 18 + } else if (typeof args[0] === "number" && typeof args[1] === "number") { 19 + window.scrollTo(args[0], args[1]); 20 + } else { 21 + throw new Error( 22 + "Invalid arguments passed to scrollTo. See here for more info. https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo", 23 + ); 24 + } 25 + }, []); 26 + 27 + React.useLayoutEffect(() => { 28 + const handleScroll = () => { 29 + setState({ x: window.scrollX, y: window.scrollY }); 30 + }; 31 + 32 + handleScroll(); 33 + window.addEventListener("scroll", handleScroll); 34 + 35 + return () => { 36 + window.removeEventListener("scroll", handleScroll); 37 + }; 38 + }, []); 39 + 40 + return [state, scrollTo] as const; 41 + }
+1
packages/ui/package.json
··· 32 32 "@radix-ui/react-dropdown-menu": "2.0.6", 33 33 "@radix-ui/react-hover-card": "1.0.7", 34 34 "@radix-ui/react-label": "2.0.2", 35 + "@radix-ui/react-navigation-menu": "1.2.0", 35 36 "@radix-ui/react-popover": "1.0.7", 36 37 "@radix-ui/react-progress": "1.0.3", 37 38 "@radix-ui/react-radio-group": "1.1.3",
+128
packages/ui/src/components/navigation-menu.tsx
··· 1 + import * as React from "react"; 2 + import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"; 3 + import { cva } from "class-variance-authority"; 4 + import { ChevronDown } from "lucide-react"; 5 + 6 + import { cn } from "@/lib/utils"; 7 + 8 + const NavigationMenu = React.forwardRef< 9 + React.ElementRef<typeof NavigationMenuPrimitive.Root>, 10 + React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root> 11 + >(({ className, children, ...props }, ref) => ( 12 + <NavigationMenuPrimitive.Root 13 + ref={ref} 14 + className={cn( 15 + "relative z-10 flex max-w-max flex-1 items-center justify-center", 16 + className 17 + )} 18 + {...props} 19 + > 20 + {children} 21 + <NavigationMenuViewport /> 22 + </NavigationMenuPrimitive.Root> 23 + )); 24 + NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName; 25 + 26 + const NavigationMenuList = React.forwardRef< 27 + React.ElementRef<typeof NavigationMenuPrimitive.List>, 28 + React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List> 29 + >(({ className, ...props }, ref) => ( 30 + <NavigationMenuPrimitive.List 31 + ref={ref} 32 + className={cn( 33 + "group flex flex-1 list-none items-center justify-center space-x-1", 34 + className 35 + )} 36 + {...props} 37 + /> 38 + )); 39 + NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName; 40 + 41 + const NavigationMenuItem = NavigationMenuPrimitive.Item; 42 + 43 + const navigationMenuTriggerStyle = cva( 44 + "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50" 45 + ); 46 + 47 + const NavigationMenuTrigger = React.forwardRef< 48 + React.ElementRef<typeof NavigationMenuPrimitive.Trigger>, 49 + React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger> 50 + >(({ className, children, ...props }, ref) => ( 51 + <NavigationMenuPrimitive.Trigger 52 + ref={ref} 53 + className={cn(navigationMenuTriggerStyle(), "group", className)} 54 + {...props} 55 + > 56 + {children}{" "} 57 + <ChevronDown 58 + className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180" 59 + aria-hidden="true" 60 + /> 61 + </NavigationMenuPrimitive.Trigger> 62 + )); 63 + NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName; 64 + 65 + const NavigationMenuContent = React.forwardRef< 66 + React.ElementRef<typeof NavigationMenuPrimitive.Content>, 67 + React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content> 68 + >(({ className, ...props }, ref) => ( 69 + <NavigationMenuPrimitive.Content 70 + ref={ref} 71 + className={cn( 72 + "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ", 73 + className 74 + )} 75 + {...props} 76 + /> 77 + )); 78 + NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName; 79 + 80 + const NavigationMenuLink = NavigationMenuPrimitive.Link; 81 + 82 + const NavigationMenuViewport = React.forwardRef< 83 + React.ElementRef<typeof NavigationMenuPrimitive.Viewport>, 84 + React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport> 85 + >(({ className, ...props }, ref) => ( 86 + <div className={cn("absolute left-0 top-full flex justify-center")}> 87 + <NavigationMenuPrimitive.Viewport 88 + className={cn( 89 + "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]", 90 + className 91 + )} 92 + ref={ref} 93 + {...props} 94 + /> 95 + </div> 96 + )); 97 + NavigationMenuViewport.displayName = 98 + NavigationMenuPrimitive.Viewport.displayName; 99 + 100 + const NavigationMenuIndicator = React.forwardRef< 101 + React.ElementRef<typeof NavigationMenuPrimitive.Indicator>, 102 + React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator> 103 + >(({ className, ...props }, ref) => ( 104 + <NavigationMenuPrimitive.Indicator 105 + ref={ref} 106 + className={cn( 107 + "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in", 108 + className 109 + )} 110 + {...props} 111 + > 112 + <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" /> 113 + </NavigationMenuPrimitive.Indicator> 114 + )); 115 + NavigationMenuIndicator.displayName = 116 + NavigationMenuPrimitive.Indicator.displayName; 117 + 118 + export { 119 + navigationMenuTriggerStyle, 120 + NavigationMenu, 121 + NavigationMenuList, 122 + NavigationMenuItem, 123 + NavigationMenuContent, 124 + NavigationMenuTrigger, 125 + NavigationMenuLink, 126 + NavigationMenuIndicator, 127 + NavigationMenuViewport, 128 + };
+1
packages/ui/src/index.tsx
··· 37 37 export * from "./components/collabsible"; 38 38 export * from "./components/sonner"; 39 39 export * from "./components/sortable"; 40 + export * from "./components/navigation-menu";
+315
pnpm-lock.yaml
··· 1052 1052 '@radix-ui/react-label': 1053 1053 specifier: 2.0.2 1054 1054 version: 2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 1055 + '@radix-ui/react-navigation-menu': 1056 + specifier: 1.2.0 1057 + version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 1055 1058 '@radix-ui/react-popover': 1056 1059 specifier: 1.0.7 1057 1060 version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ··· 3593 3596 '@radix-ui/primitive@1.0.1': 3594 3597 resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} 3595 3598 3599 + '@radix-ui/primitive@1.1.0': 3600 + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} 3601 + 3596 3602 '@radix-ui/react-accordion@1.1.2': 3597 3603 resolution: {integrity: sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==} 3598 3604 peerDependencies: ··· 3684 3690 '@types/react-dom': 3685 3691 optional: true 3686 3692 3693 + '@radix-ui/react-collection@1.1.0': 3694 + resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} 3695 + peerDependencies: 3696 + '@types/react': '*' 3697 + '@types/react-dom': '*' 3698 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3699 + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3700 + peerDependenciesMeta: 3701 + '@types/react': 3702 + optional: true 3703 + '@types/react-dom': 3704 + optional: true 3705 + 3687 3706 '@radix-ui/react-compose-refs@1.0.0': 3688 3707 resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} 3689 3708 peerDependencies: ··· 3698 3717 '@types/react': 3699 3718 optional: true 3700 3719 3720 + '@radix-ui/react-compose-refs@1.1.0': 3721 + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} 3722 + peerDependencies: 3723 + '@types/react': '*' 3724 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3725 + peerDependenciesMeta: 3726 + '@types/react': 3727 + optional: true 3728 + 3701 3729 '@radix-ui/react-context-menu@2.1.5': 3702 3730 resolution: {integrity: sha512-R5XaDj06Xul1KGb+WP8qiOh7tKJNz2durpLBXAGZjSVtctcRFCuEvy2gtMwRJGePwQQE5nV77gs4FwRi8T+r2g==} 3703 3731 peerDependencies: ··· 3720 3748 '@types/react': 3721 3749 optional: true 3722 3750 3751 + '@radix-ui/react-context@1.1.0': 3752 + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} 3753 + peerDependencies: 3754 + '@types/react': '*' 3755 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3756 + peerDependenciesMeta: 3757 + '@types/react': 3758 + optional: true 3759 + 3723 3760 '@radix-ui/react-dialog@1.0.4': 3724 3761 resolution: {integrity: sha512-hJtRy/jPULGQZceSAP2Re6/4NpKo8im6V8P2hUqZsdFiSL8l35kYsw3qbRI6Ay5mQd2+wlLqje770eq+RJ3yZg==} 3725 3762 peerDependencies: ··· 3751 3788 peerDependencies: 3752 3789 '@types/react': '*' 3753 3790 react: ^16.8 || ^17.0 || ^18.0 3791 + peerDependenciesMeta: 3792 + '@types/react': 3793 + optional: true 3794 + 3795 + '@radix-ui/react-direction@1.1.0': 3796 + resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} 3797 + peerDependencies: 3798 + '@types/react': '*' 3799 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3754 3800 peerDependenciesMeta: 3755 3801 '@types/react': 3756 3802 optional: true ··· 3781 3827 '@types/react-dom': 3782 3828 optional: true 3783 3829 3830 + '@radix-ui/react-dismissable-layer@1.1.0': 3831 + resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} 3832 + peerDependencies: 3833 + '@types/react': '*' 3834 + '@types/react-dom': '*' 3835 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3836 + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3837 + peerDependenciesMeta: 3838 + '@types/react': 3839 + optional: true 3840 + '@types/react-dom': 3841 + optional: true 3842 + 3784 3843 '@radix-ui/react-dropdown-menu@2.0.6': 3785 3844 resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==} 3786 3845 peerDependencies: ··· 3847 3906 peerDependencies: 3848 3907 '@types/react': '*' 3849 3908 react: ^16.8 || ^17.0 || ^18.0 3909 + peerDependenciesMeta: 3910 + '@types/react': 3911 + optional: true 3912 + 3913 + '@radix-ui/react-id@1.1.0': 3914 + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} 3915 + peerDependencies: 3916 + '@types/react': '*' 3917 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3850 3918 peerDependenciesMeta: 3851 3919 '@types/react': 3852 3920 optional: true ··· 3877 3945 '@types/react-dom': 3878 3946 optional: true 3879 3947 3948 + '@radix-ui/react-navigation-menu@1.2.0': 3949 + resolution: {integrity: sha512-OQ8tcwAOR0DhPlSY3e4VMXeHiol7la4PPdJWhhwJiJA+NLX0SaCaonOkRnI3gCDHoZ7Fo7bb/G6q25fRM2Y+3Q==} 3950 + peerDependencies: 3951 + '@types/react': '*' 3952 + '@types/react-dom': '*' 3953 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3954 + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3955 + peerDependenciesMeta: 3956 + '@types/react': 3957 + optional: true 3958 + '@types/react-dom': 3959 + optional: true 3960 + 3880 3961 '@radix-ui/react-popover@1.0.7': 3881 3962 resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} 3882 3963 peerDependencies: ··· 3942 4023 '@types/react-dom': 3943 4024 optional: true 3944 4025 4026 + '@radix-ui/react-presence@1.1.0': 4027 + resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==} 4028 + peerDependencies: 4029 + '@types/react': '*' 4030 + '@types/react-dom': '*' 4031 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4032 + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4033 + peerDependenciesMeta: 4034 + '@types/react': 4035 + optional: true 4036 + '@types/react-dom': 4037 + optional: true 4038 + 3945 4039 '@radix-ui/react-primitive@1.0.3': 3946 4040 resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} 3947 4041 peerDependencies: ··· 3949 4043 '@types/react-dom': '*' 3950 4044 react: ^16.8 || ^17.0 || ^18.0 3951 4045 react-dom: ^16.8 || ^17.0 || ^18.0 4046 + peerDependenciesMeta: 4047 + '@types/react': 4048 + optional: true 4049 + '@types/react-dom': 4050 + optional: true 4051 + 4052 + '@radix-ui/react-primitive@2.0.0': 4053 + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} 4054 + peerDependencies: 4055 + '@types/react': '*' 4056 + '@types/react-dom': '*' 4057 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4058 + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 3952 4059 peerDependenciesMeta: 3953 4060 '@types/react': 3954 4061 optional: true ··· 4034 4141 '@types/react': 4035 4142 optional: true 4036 4143 4144 + '@radix-ui/react-slot@1.1.0': 4145 + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} 4146 + peerDependencies: 4147 + '@types/react': '*' 4148 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4149 + peerDependenciesMeta: 4150 + '@types/react': 4151 + optional: true 4152 + 4037 4153 '@radix-ui/react-switch@1.0.3': 4038 4154 resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==} 4039 4155 peerDependencies: ··· 4095 4211 '@types/react': 4096 4212 optional: true 4097 4213 4214 + '@radix-ui/react-use-callback-ref@1.1.0': 4215 + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} 4216 + peerDependencies: 4217 + '@types/react': '*' 4218 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4219 + peerDependenciesMeta: 4220 + '@types/react': 4221 + optional: true 4222 + 4098 4223 '@radix-ui/react-use-controllable-state@1.0.1': 4099 4224 resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} 4100 4225 peerDependencies: ··· 4104 4229 '@types/react': 4105 4230 optional: true 4106 4231 4232 + '@radix-ui/react-use-controllable-state@1.1.0': 4233 + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} 4234 + peerDependencies: 4235 + '@types/react': '*' 4236 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4237 + peerDependenciesMeta: 4238 + '@types/react': 4239 + optional: true 4240 + 4107 4241 '@radix-ui/react-use-escape-keydown@1.0.3': 4108 4242 resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} 4109 4243 peerDependencies: 4110 4244 '@types/react': '*' 4111 4245 react: ^16.8 || ^17.0 || ^18.0 4246 + peerDependenciesMeta: 4247 + '@types/react': 4248 + optional: true 4249 + 4250 + '@radix-ui/react-use-escape-keydown@1.1.0': 4251 + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} 4252 + peerDependencies: 4253 + '@types/react': '*' 4254 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4112 4255 peerDependenciesMeta: 4113 4256 '@types/react': 4114 4257 optional: true ··· 4122 4265 '@types/react': 4123 4266 optional: true 4124 4267 4268 + '@radix-ui/react-use-layout-effect@1.1.0': 4269 + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} 4270 + peerDependencies: 4271 + '@types/react': '*' 4272 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4273 + peerDependenciesMeta: 4274 + '@types/react': 4275 + optional: true 4276 + 4125 4277 '@radix-ui/react-use-previous@1.0.1': 4126 4278 resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} 4127 4279 peerDependencies: ··· 4131 4283 '@types/react': 4132 4284 optional: true 4133 4285 4286 + '@radix-ui/react-use-previous@1.1.0': 4287 + resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} 4288 + peerDependencies: 4289 + '@types/react': '*' 4290 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4291 + peerDependenciesMeta: 4292 + '@types/react': 4293 + optional: true 4294 + 4134 4295 '@radix-ui/react-use-rect@1.0.1': 4135 4296 resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==} 4136 4297 peerDependencies: ··· 4156 4317 '@types/react-dom': '*' 4157 4318 react: ^16.8 || ^17.0 || ^18.0 4158 4319 react-dom: ^16.8 || ^17.0 || ^18.0 4320 + peerDependenciesMeta: 4321 + '@types/react': 4322 + optional: true 4323 + '@types/react-dom': 4324 + optional: true 4325 + 4326 + '@radix-ui/react-visually-hidden@1.1.0': 4327 + resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==} 4328 + peerDependencies: 4329 + '@types/react': '*' 4330 + '@types/react-dom': '*' 4331 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4332 + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc 4159 4333 peerDependenciesMeta: 4160 4334 '@types/react': 4161 4335 optional: true ··· 13025 13199 dependencies: 13026 13200 '@babel/runtime': 7.23.2 13027 13201 13202 + '@radix-ui/primitive@1.1.0': {} 13203 + 13028 13204 '@radix-ui/react-accordion@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13029 13205 dependencies: 13030 13206 '@babel/runtime': 7.23.2 ··· 13128 13304 '@types/react': 18.3.3 13129 13305 '@types/react-dom': 18.3.0 13130 13306 13307 + '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13308 + dependencies: 13309 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13310 + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13311 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13312 + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13313 + react: 18.3.1 13314 + react-dom: 18.3.1(react@18.3.1) 13315 + optionalDependencies: 13316 + '@types/react': 18.3.3 13317 + '@types/react-dom': 18.3.0 13318 + 13131 13319 '@radix-ui/react-compose-refs@1.0.0(react@18.2.0)': 13132 13320 dependencies: 13133 13321 '@babel/runtime': 7.23.2 ··· 13140 13328 optionalDependencies: 13141 13329 '@types/react': 18.3.3 13142 13330 13331 + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13332 + dependencies: 13333 + react: 18.3.1 13334 + optionalDependencies: 13335 + '@types/react': 18.3.3 13336 + 13143 13337 '@radix-ui/react-context-menu@2.1.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13144 13338 dependencies: 13145 13339 '@babel/runtime': 7.23.2 ··· 13158 13352 '@radix-ui/react-context@1.0.1(@types/react@18.3.3)(react@18.3.1)': 13159 13353 dependencies: 13160 13354 '@babel/runtime': 7.23.2 13355 + react: 18.3.1 13356 + optionalDependencies: 13357 + '@types/react': 18.3.3 13358 + 13359 + '@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13360 + dependencies: 13161 13361 react: 18.3.1 13162 13362 optionalDependencies: 13163 13363 '@types/react': 18.3.3 ··· 13215 13415 optionalDependencies: 13216 13416 '@types/react': 18.3.3 13217 13417 13418 + '@radix-ui/react-direction@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13419 + dependencies: 13420 + react: 18.3.1 13421 + optionalDependencies: 13422 + '@types/react': 18.3.3 13423 + 13218 13424 '@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13219 13425 dependencies: 13220 13426 '@babel/runtime': 7.23.2 ··· 13243 13449 '@types/react': 18.3.3 13244 13450 '@types/react-dom': 18.3.0 13245 13451 13452 + '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13453 + dependencies: 13454 + '@radix-ui/primitive': 1.1.0 13455 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13456 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13457 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13458 + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13459 + react: 18.3.1 13460 + react-dom: 18.3.1(react@18.3.1) 13461 + optionalDependencies: 13462 + '@types/react': 18.3.3 13463 + '@types/react-dom': 18.3.0 13464 + 13246 13465 '@radix-ui/react-dropdown-menu@2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13247 13466 dependencies: 13248 13467 '@babel/runtime': 7.23.2 ··· 13316 13535 optionalDependencies: 13317 13536 '@types/react': 18.3.3 13318 13537 13538 + '@radix-ui/react-id@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13539 + dependencies: 13540 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13541 + react: 18.3.1 13542 + optionalDependencies: 13543 + '@types/react': 18.3.3 13544 + 13319 13545 '@radix-ui/react-label@2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13320 13546 dependencies: 13321 13547 '@babel/runtime': 7.23.2 ··· 13353 13579 '@types/react': 18.3.3 13354 13580 '@types/react-dom': 18.3.0 13355 13581 13582 + '@radix-ui/react-navigation-menu@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13583 + dependencies: 13584 + '@radix-ui/primitive': 1.1.0 13585 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13586 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13587 + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13588 + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13589 + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13590 + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13591 + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13592 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13593 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13594 + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13595 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13596 + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13597 + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13598 + react: 18.3.1 13599 + react-dom: 18.3.1(react@18.3.1) 13600 + optionalDependencies: 13601 + '@types/react': 18.3.3 13602 + '@types/react-dom': 18.3.0 13603 + 13356 13604 '@radix-ui/react-popover@1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13357 13605 dependencies: 13358 13606 '@babel/runtime': 7.23.2 ··· 13427 13675 '@types/react': 18.3.3 13428 13676 '@types/react-dom': 18.3.0 13429 13677 13678 + '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13679 + dependencies: 13680 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13681 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13682 + react: 18.3.1 13683 + react-dom: 18.3.1(react@18.3.1) 13684 + optionalDependencies: 13685 + '@types/react': 18.3.3 13686 + '@types/react-dom': 18.3.0 13687 + 13430 13688 '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13431 13689 dependencies: 13432 13690 '@babel/runtime': 7.23.2 ··· 13437 13695 '@types/react': 18.3.3 13438 13696 '@types/react-dom': 18.3.0 13439 13697 13698 + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13699 + dependencies: 13700 + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13701 + react: 18.3.1 13702 + react-dom: 18.3.1(react@18.3.1) 13703 + optionalDependencies: 13704 + '@types/react': 18.3.3 13705 + '@types/react-dom': 18.3.0 13706 + 13440 13707 '@radix-ui/react-progress@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13441 13708 dependencies: 13442 13709 '@babel/runtime': 7.23.2 ··· 13539 13806 optionalDependencies: 13540 13807 '@types/react': 18.3.3 13541 13808 13809 + '@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13810 + dependencies: 13811 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13812 + react: 18.3.1 13813 + optionalDependencies: 13814 + '@types/react': 18.3.3 13815 + 13542 13816 '@radix-ui/react-switch@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13543 13817 dependencies: 13544 13818 '@babel/runtime': 7.23.2 ··· 13608 13882 '@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.3)(react@18.3.1)': 13609 13883 dependencies: 13610 13884 '@babel/runtime': 7.23.2 13885 + react: 18.3.1 13886 + optionalDependencies: 13887 + '@types/react': 18.3.3 13888 + 13889 + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13890 + dependencies: 13611 13891 react: 18.3.1 13612 13892 optionalDependencies: 13613 13893 '@types/react': 18.3.3 ··· 13620 13900 optionalDependencies: 13621 13901 '@types/react': 18.3.3 13622 13902 13903 + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13904 + dependencies: 13905 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13906 + react: 18.3.1 13907 + optionalDependencies: 13908 + '@types/react': 18.3.3 13909 + 13623 13910 '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.3)(react@18.3.1)': 13624 13911 dependencies: 13625 13912 '@babel/runtime': 7.23.2 ··· 13628 13915 optionalDependencies: 13629 13916 '@types/react': 18.3.3 13630 13917 13918 + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13919 + dependencies: 13920 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) 13921 + react: 18.3.1 13922 + optionalDependencies: 13923 + '@types/react': 18.3.3 13924 + 13631 13925 '@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.3.3)(react@18.3.1)': 13632 13926 dependencies: 13633 13927 '@babel/runtime': 7.23.2 ··· 13635 13929 optionalDependencies: 13636 13930 '@types/react': 18.3.3 13637 13931 13932 + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13933 + dependencies: 13934 + react: 18.3.1 13935 + optionalDependencies: 13936 + '@types/react': 18.3.3 13937 + 13638 13938 '@radix-ui/react-use-previous@1.0.1(@types/react@18.3.3)(react@18.3.1)': 13639 13939 dependencies: 13640 13940 '@babel/runtime': 7.23.2 13941 + react: 18.3.1 13942 + optionalDependencies: 13943 + '@types/react': 18.3.3 13944 + 13945 + '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.3)(react@18.3.1)': 13946 + dependencies: 13641 13947 react: 18.3.1 13642 13948 optionalDependencies: 13643 13949 '@types/react': 18.3.3 ··· 13662 13968 dependencies: 13663 13969 '@babel/runtime': 7.23.2 13664 13970 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13971 + react: 18.3.1 13972 + react-dom: 18.3.1(react@18.3.1) 13973 + optionalDependencies: 13974 + '@types/react': 18.3.3 13975 + '@types/react-dom': 18.3.0 13976 + 13977 + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': 13978 + dependencies: 13979 + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 13665 13980 react: 18.3.1 13666 13981 react-dom: 18.3.1(react@18.3.1) 13667 13982 optionalDependencies: