1
+ import { OrganizationSwitcher , useClerk } from "@clerk/clerk-react" ;
1
2
import {
2
3
faArrowUpRight ,
3
4
faLink ,
@@ -6,7 +7,12 @@ import {
6
7
Icon ,
7
8
} from "@rivet-gg/icons" ;
8
9
import { useQuery } from "@tanstack/react-query" ;
9
- import { Link , useMatchRoute , useNavigate } from "@tanstack/react-router" ;
10
+ import {
11
+ Link ,
12
+ useMatch ,
13
+ useMatchRoute ,
14
+ useNavigate ,
15
+ } from "@tanstack/react-router" ;
10
16
import {
11
17
type ComponentProps ,
12
18
createContext ,
@@ -20,8 +26,10 @@ import {
20
26
useState ,
21
27
} from "react" ;
22
28
import type { ImperativePanelGroupHandle } from "react-resizable-panels" ;
29
+ import { match } from "ts-pattern" ;
23
30
import {
24
31
Button ,
32
+ type ButtonProps ,
25
33
cn ,
26
34
DocsSheet ,
27
35
type ImperativePanelHandle ,
@@ -146,13 +154,25 @@ const Sidebar = ({
146
154
/>
147
155
</ Link >
148
156
< div className = "flex flex-1 flex-col gap-4 px-2 min-h-0" >
149
- { __APP_TYPE__ === "inspector" ? (
150
- < ConnectionStatus />
151
- ) : null }
152
- { __APP_TYPE__ === "engine" ? < Breadcrumbs /> : null }
153
- < ScrollArea >
154
- < Subnav />
155
- </ ScrollArea >
157
+ { match ( __APP_TYPE__ )
158
+ . with ( "engine" , ( ) => (
159
+ < >
160
+ < ConnectionStatus />
161
+ < ScrollArea >
162
+ < Subnav />
163
+ </ ScrollArea >
164
+ </ >
165
+ ) )
166
+ . with ( "inspector" , ( ) => (
167
+ < >
168
+ < Breadcrumbs />
169
+ < ScrollArea >
170
+ < Subnav />
171
+ </ ScrollArea >
172
+ </ >
173
+ ) )
174
+ . with ( "cloud" , ( ) => < CloudSidebar /> )
175
+ . exhaustive ( ) }
156
176
</ div >
157
177
< div >
158
178
< div className = "border-t p-2 flex flex-col gap-[1px] text-sm" >
@@ -241,7 +261,7 @@ const Footer = () => {
241
261
242
262
export { Root , Main , Header , Footer , VisibleInFull , Sidebar } ;
243
263
244
- const Breadcrumbs = ( ) => {
264
+ const Breadcrumbs = ( ) : ReactNode => {
245
265
const matchRoute = useMatchRoute ( ) ;
246
266
const nsMatch = matchRoute ( {
247
267
to : "/ns/$namespace" ,
@@ -341,26 +361,37 @@ const Subnav = () => {
341
361
342
362
function HeaderLink ( { icon, children, className, ...props } : HeaderLinkProps ) {
343
363
return (
344
- < Button
364
+ < HeaderButton
345
365
asChild
346
366
variant = "ghost"
347
367
{ ...props }
348
- className = { cn (
349
- "text-muted-foreground px-2 aria-current-page:text-foreground relative h-auto py-1 justify-start" ,
350
- className ,
351
- ) }
352
368
startIcon = {
353
369
icon ? (
354
370
< Icon className = { cn ( "size-5 opacity-80" ) } icon = { icon } />
355
371
) : undefined
356
372
}
357
373
>
358
374
< Link to = { props . to } > { children } </ Link >
375
+ </ HeaderButton >
376
+ ) ;
377
+ }
378
+
379
+ function HeaderButton ( { children, className, ...props } : ButtonProps ) {
380
+ return (
381
+ < Button
382
+ variant = "ghost"
383
+ { ...props }
384
+ className = { cn (
385
+ "text-muted-foreground px-2 aria-current-page:text-foreground relative h-auto py-1 justify-start" ,
386
+ className ,
387
+ ) }
388
+ >
389
+ { children }
359
390
</ Button >
360
391
) ;
361
392
}
362
393
363
- function ConnectionStatus ( ) {
394
+ function ConnectionStatus ( ) : ReactNode {
364
395
const { endpoint, ...queries } = useManager ( ) ;
365
396
const { setCredentials } = useInspectorCredentials ( ) ;
366
397
const { isLoading, isError, isSuccess } = useQuery (
@@ -414,4 +445,48 @@ function ConnectionStatus() {
414
445
</ div >
415
446
) ;
416
447
}
448
+
449
+ return null ;
450
+ }
451
+
452
+ function CloudSidebar ( ) : ReactNode {
453
+ const match = useMatch ( {
454
+ from : "/_layout/orgs/$organization/" ,
455
+ shouldThrow : false ,
456
+ } ) ;
457
+
458
+ const clerk = useClerk ( ) ;
459
+ return (
460
+ < >
461
+ < OrganizationSwitcher />
462
+
463
+ < ScrollArea >
464
+ < div className = "flex gap-1.5 flex-col" >
465
+ < HeaderLink
466
+ to = "/orgs/$organization"
467
+ className = "font-normal"
468
+ params = { match ?. params }
469
+ >
470
+ Projects
471
+ </ HeaderLink >
472
+ < HeaderButton
473
+ onClick = { ( ) => {
474
+ clerk . openUserProfile ( {
475
+ __experimental_startPath : "/billing" ,
476
+ } ) ;
477
+ } }
478
+ >
479
+ Billing
480
+ </ HeaderButton >
481
+ < HeaderButton
482
+ onClick = { ( ) => {
483
+ clerk . openUserProfile ( ) ;
484
+ } }
485
+ >
486
+ Settings
487
+ </ HeaderButton >
488
+ </ div >
489
+ </ ScrollArea >
490
+ </ >
491
+ ) ;
417
492
}
0 commit comments