@@ -10,18 +10,20 @@ import {
1010import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
1111import Link from "next/link" ;
1212import { useParams } from "next/navigation" ;
13- import { useState } from "react" ;
13+ import { useState , type ReactNode } from "react" ;
1414import { toast } from "sonner" ;
1515import { PageHeader } from "@/app/(main)/websites/_components/page-header" ;
1616import { EmptyState } from "@/components/empty-state" ;
1717import { ErrorBoundary } from "@/components/error-boundary" ;
18- import { FeatureAccessGate } from "@/components/feature-access-gate" ;
18+ import { FeatureLockedPanel } from "@/components/feature-access-gate" ;
1919import { PageNavigation } from "@/components/layout/page-navigation" ;
2020import { Badge } from "@/components/ui/badge" ;
2121import { Button } from "@/components/ui/button" ;
2222import { List } from "@/components/ui/composables/list" ;
23+ import { Skeleton } from "@/components/ui/skeleton" ;
2324import { DeleteDialog } from "@/components/ui/delete-dialog" ;
2425import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
26+ import { useFeatureAccess } from "@/hooks/use-feature-access" ;
2527import { getStatusPageUrl } from "@/lib/app-url" ;
2628import { orpc } from "@/lib/orpc" ;
2729import { cn } from "@/lib/utils" ;
@@ -56,6 +58,9 @@ export default function StatusPageDetailsPage() {
5658
5759 const statusPage = statusPageQuery . data ;
5860
61+ const { hasAccess, isLoading : isFeatureAccessLoading } =
62+ useFeatureAccess ( "monitors" ) ;
63+
5964 const monitorToRemoveData = statusPage ?. monitors . find (
6065 ( m : StatusPageMonitor ) => m . id === monitorToRemove
6166 ) ;
@@ -70,18 +75,66 @@ export default function StatusPageDetailsPage() {
7075 } ) ;
7176 } ;
7277
78+ let monitorsBody : ReactNode ;
79+ if ( isFeatureAccessLoading ) {
80+ monitorsBody = < List . DefaultLoading /> ;
81+ } else if ( ! hasAccess ) {
82+ monitorsBody = < FeatureLockedPanel flagKey = "monitors" /> ;
83+ } else if ( statusPageQuery . isLoading ) {
84+ monitorsBody = < List . DefaultLoading /> ;
85+ } else if ( statusPageQuery . isError ) {
86+ monitorsBody = (
87+ < div className = "flex flex-1 items-center justify-center py-16" >
88+ < EmptyState
89+ action = { {
90+ label : "Retry" ,
91+ onClick : ( ) => statusPageQuery . refetch ( ) ,
92+ } }
93+ description = "Something went wrong while loading the status page."
94+ icon = { < BrowserIcon weight = "duotone" /> }
95+ title = "Failed to load"
96+ variant = "error"
97+ />
98+ </ div >
99+ ) ;
100+ } else if ( statusPage ?. monitors . length === 0 ) {
101+ monitorsBody = (
102+ < div className = "flex flex-1 items-center justify-center py-16" >
103+ < EmptyState
104+ action = { {
105+ label : "Add Monitor" ,
106+ onClick : ( ) => setIsAddDialogOpen ( true ) ,
107+ } }
108+ description = "Add monitors to this status page to display their uptime and latency."
109+ icon = { < HeartbeatIcon weight = "duotone" /> }
110+ title = "No monitors added"
111+ variant = "minimal"
112+ />
113+ </ div >
114+ ) ;
115+ } else {
116+ monitorsBody = (
117+ < List className = "rounded bg-card" >
118+ { statusPage ?. monitors . map ( ( monitor : StatusPageMonitor ) => (
119+ < StatusPageMonitorRow
120+ key = { monitor . id }
121+ monitor = { monitor }
122+ onRemoveRequestAction = { ( id ) => setMonitorToRemove ( id ) }
123+ statusPageId = { statusPageId }
124+ />
125+ ) ) }
126+ </ List >
127+ ) ;
128+ }
129+
73130 return (
74131 < ErrorBoundary >
75132 < div className = "flex h-full min-h-0 flex-col" >
76133 < PageHeader
77- description = {
78- statusPage
79- ? `Manage monitors for ${ statusPage . name } `
80- : "Manage status page monitors"
81- }
134+ description = "Manage monitors and what appears on your public status page."
82135 icon = { < BrowserIcon /> }
83136 right = {
84- statusPage && (
137+ statusPage ? (
85138 < >
86139 < Button asChild size = "sm" variant = "outline" >
87140 < Link
@@ -114,102 +167,67 @@ export default function StatusPageDetailsPage() {
114167 Add Monitor
115168 </ Button >
116169 </ >
170+ ) : (
171+ < div
172+ aria-hidden = "true"
173+ className = "flex max-w-full shrink-0 flex-wrap items-center justify-end gap-2"
174+ >
175+ < Skeleton className = "h-9 w-22 rounded sm:w-24" />
176+ < Skeleton className = "size-9 rounded" />
177+ < Skeleton className = "h-9 w-29 rounded sm:w-32" />
178+ </ div >
117179 )
118180 }
119- title = { statusPage ?. name ?? "Status Page " }
181+ title = { statusPage ?. name ?? "Status page " }
120182 />
121183
122184 < PageNavigation
123185 breadcrumb = { { label : "Status Pages" , href : "/monitors/status-pages" } }
124- currentPage = { statusPage ?. name ?? "Loading... " }
186+ currentPage = { statusPage ?. name ?? "Status page " }
125187 variant = "breadcrumb"
126188 />
127189
128- < FeatureAccessGate
129- flagKey = "monitors"
130- loadingFallback = { < List . DefaultLoading /> }
190+ < Tabs
191+ className = "flex min-h-0 flex-1 flex-col gap-0"
192+ defaultValue = "monitors"
193+ variant = "navigation"
131194 >
132- < Tabs
133- className = "flex min-h-0 flex-1 flex-col gap-0"
134- defaultValue = "monitors"
135- variant = "navigation"
195+ < TabsList >
196+ < TabsTrigger value = "monitors" >
197+ < HeartbeatIcon size = { 16 } weight = "duotone" />
198+ Monitors
199+ </ TabsTrigger >
200+ < TabsTrigger disabled value = "incidents" >
201+ < SirenIcon size = { 16 } weight = "duotone" />
202+ Incidents
203+ < Badge className = "px-1.5 py-0" variant = "secondary" >
204+ Soon
205+ </ Badge >
206+ </ TabsTrigger >
207+ </ TabsList >
208+
209+ < TabsContent
210+ className = "min-h-0 flex-1 overflow-y-auto"
211+ value = "monitors"
136212 >
137- < TabsList >
138- < TabsTrigger value = "monitors" >
139- < HeartbeatIcon size = { 16 } weight = "duotone" />
140- Monitors
141- </ TabsTrigger >
142- < TabsTrigger disabled value = "incidents" >
143- < SirenIcon size = { 16 } weight = "duotone" />
144- Incidents
145- < Badge className = "px-1.5 py-0" variant = "secondary" >
146- Soon
147- </ Badge >
148- </ TabsTrigger >
149- </ TabsList >
150-
151- < TabsContent
152- className = "min-h-0 flex-1 overflow-y-auto"
153- value = "monitors"
154- >
155- { statusPageQuery . isLoading ? (
156- < List . DefaultLoading />
157- ) : statusPageQuery . isError ? (
158- < div className = "flex flex-1 items-center justify-center py-16" >
159- < EmptyState
160- action = { {
161- label : "Retry" ,
162- onClick : ( ) => statusPageQuery . refetch ( ) ,
163- } }
164- description = "Something went wrong while loading the status page."
165- icon = { < BrowserIcon weight = "duotone" /> }
166- title = "Failed to load"
167- variant = "error"
168- />
169- </ div >
170- ) : statusPage ?. monitors . length === 0 ? (
171- < div className = "flex flex-1 items-center justify-center py-16" >
172- < EmptyState
173- action = { {
174- label : "Add Monitor" ,
175- onClick : ( ) => setIsAddDialogOpen ( true ) ,
176- } }
177- description = "Add monitors to this status page to display their uptime and latency."
178- icon = { < HeartbeatIcon weight = "duotone" /> }
179- title = "No monitors added"
180- variant = "minimal"
181- />
182- </ div >
183- ) : (
184- < List className = "rounded bg-card" >
185- { statusPage ?. monitors . map ( ( monitor : StatusPageMonitor ) => (
186- < StatusPageMonitorRow
187- key = { monitor . id }
188- monitor = { monitor }
189- onRemoveRequestAction = { ( id ) => setMonitorToRemove ( id ) }
190- statusPageId = { statusPageId }
191- />
192- ) ) }
193- </ List >
194- ) }
195- </ TabsContent >
196-
197- < TabsContent
198- className = "min-h-0 flex-1 overflow-y-auto"
199- value = "incidents"
200- >
201- < div className = "flex flex-1 items-center justify-center py-16" >
202- < EmptyState
203- description = "Incident management is coming soon. You'll be able to create and track incidents directly from here."
204- icon = { < SirenIcon weight = "duotone" /> }
205- showPlusBadge = { false }
206- title = "Coming Soon"
207- variant = "minimal"
208- />
209- </ div >
210- </ TabsContent >
211- </ Tabs >
212- </ FeatureAccessGate >
213+ { monitorsBody }
214+ </ TabsContent >
215+
216+ < TabsContent
217+ className = "min-h-0 flex-1 overflow-y-auto"
218+ value = "incidents"
219+ >
220+ < div className = "flex flex-1 items-center justify-center py-16" >
221+ < EmptyState
222+ description = "Incident management is coming soon. You'll be able to create and track incidents directly from here."
223+ icon = { < SirenIcon weight = "duotone" /> }
224+ showPlusBadge = { false }
225+ title = "Coming Soon"
226+ variant = "minimal"
227+ />
228+ </ div >
229+ </ TabsContent >
230+ </ Tabs >
213231
214232 < AddMonitorDialog
215233 existingMonitorIds = {
0 commit comments