@stoker-platform/web-app 0.5.118 → 0.5.120
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/App.css +7 -0
- package/src/Collection.tsx +17 -7
- package/src/Tenant.tsx +267 -260
- package/src/index.css +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @stoker-platform/web-app
|
|
2
2
|
|
|
3
|
+
## 0.5.120
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat: improve mobile collection transitions
|
|
8
|
+
|
|
9
|
+
## 0.5.119
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fix: fix UI flicker when changing collection on mobile
|
|
14
|
+
|
|
3
15
|
## 0.5.118
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
package/src/App.css
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
html,
|
|
2
|
+
body {
|
|
3
|
+
background-color: #000000 !important;
|
|
4
|
+
background-image: none !important;
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
@media (min-width: 1280px) {
|
|
2
8
|
html,
|
|
3
9
|
body {
|
|
@@ -8,6 +14,7 @@
|
|
|
8
14
|
/* Print */
|
|
9
15
|
|
|
10
16
|
@media print {
|
|
17
|
+
html,
|
|
11
18
|
body {
|
|
12
19
|
background-color: white !important;
|
|
13
20
|
background-image: none !important;
|
package/src/Collection.tsx
CHANGED
|
@@ -1687,10 +1687,20 @@ function Collection({
|
|
|
1687
1687
|
[recordTitle],
|
|
1688
1688
|
)
|
|
1689
1689
|
|
|
1690
|
+
const [showCollection, setShowCollection] = useState(window.innerWidth > 1280)
|
|
1691
|
+
useEffect(() => {
|
|
1692
|
+
if (isInitialized) {
|
|
1693
|
+
setTimeout(() => {
|
|
1694
|
+
setShowCollection(true)
|
|
1695
|
+
}, 150)
|
|
1696
|
+
}
|
|
1697
|
+
}, [isInitialized])
|
|
1698
|
+
|
|
1690
1699
|
if (!permissions.collections?.[labels.collection]) return null
|
|
1691
1700
|
|
|
1692
1701
|
return (
|
|
1693
|
-
!collection.singleton &&
|
|
1702
|
+
!collection.singleton &&
|
|
1703
|
+
showCollection && (
|
|
1694
1704
|
<>
|
|
1695
1705
|
<div
|
|
1696
1706
|
ref={mainContentRef}
|
|
@@ -2535,17 +2545,17 @@ function Collection({
|
|
|
2535
2545
|
) : (
|
|
2536
2546
|
!relationList &&
|
|
2537
2547
|
(tab === "list" ? (
|
|
2538
|
-
<div className="py-2">
|
|
2539
|
-
<Card className="h-[calc(100vh-250px)]"></Card>
|
|
2548
|
+
<div className="pb-2 pt-[88px] lg:py-2">
|
|
2549
|
+
<Card className="min-h-[calc(100vh-88px)] lg:min-h-full lg:h-[calc(100vh-250px)]"></Card>
|
|
2540
2550
|
</div>
|
|
2541
2551
|
) : tab === "map" ? (
|
|
2542
|
-
<div className="py-2">
|
|
2543
|
-
<Card className="h-[calc(100vh-204px)]"></Card>
|
|
2552
|
+
<div className="pb-2 pt-[88px] lg:py-2">
|
|
2553
|
+
<Card className="min-h-[calc(100vh-88px)] lg:min-h-full lg:h-[calc(100vh-204px)]"></Card>
|
|
2544
2554
|
</div>
|
|
2545
2555
|
) : (
|
|
2546
2556
|
tab === "calendar" && (
|
|
2547
|
-
<div className="py-2">
|
|
2548
|
-
<Card className="h-[calc(100vh-
|
|
2557
|
+
<div className="pb-2 pt-[44px] lg:py-2">
|
|
2558
|
+
<Card className="min-h-[calc(100vh-44px)] lg:min-h-full lg:h-[calc(100vh-204px)]"></Card>
|
|
2549
2559
|
</div>
|
|
2550
2560
|
)
|
|
2551
2561
|
))
|
package/src/Tenant.tsx
CHANGED
|
@@ -60,7 +60,7 @@ import { getFunctions, httpsCallable } from "firebase/functions"
|
|
|
60
60
|
import { getApp } from "firebase/app"
|
|
61
61
|
import { useTheme } from "./components/theme-provider"
|
|
62
62
|
|
|
63
|
-
const MOBILE_SHEET_CLOSE_MS =
|
|
63
|
+
const MOBILE_SHEET_CLOSE_MS = 400
|
|
64
64
|
|
|
65
65
|
function Tenant() {
|
|
66
66
|
const [dialogContent, setDialogContent] = useDialog()
|
|
@@ -104,6 +104,10 @@ function Tenant() {
|
|
|
104
104
|
const [search, setSearch] = useState<string>("")
|
|
105
105
|
const [searchFocused, setSearchFocused] = useState(false)
|
|
106
106
|
const [background, setBackground] = useState<Background | undefined>(undefined)
|
|
107
|
+
const [backgroundStyle, setBackgroundStyle] = useState<{
|
|
108
|
+
backgroundColor: string
|
|
109
|
+
backgroundImage: string
|
|
110
|
+
}>({ backgroundColor: "transparent", backgroundImage: "none" })
|
|
107
111
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
108
112
|
|
|
109
113
|
const { unsubscribe } = useCache()
|
|
@@ -242,29 +246,18 @@ function Tenant() {
|
|
|
242
246
|
|
|
243
247
|
useEffect(() => {
|
|
244
248
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
249
|
+
const resolveTheme = theme === "system" || theme === undefined ? (prefersDark ? "dark" : "light") : theme
|
|
245
250
|
if (background) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
document.body.style.backgroundImage = background?.dark?.image || "none"
|
|
252
|
-
} else if (prefersDark) {
|
|
253
|
-
document.body.style.backgroundColor = background?.dark?.color || "transparent"
|
|
254
|
-
document.body.style.backgroundImage = background?.dark?.image || "none"
|
|
255
|
-
} else {
|
|
256
|
-
document.body.style.backgroundColor = background?.light?.color || "transparent"
|
|
257
|
-
document.body.style.backgroundImage = background?.light?.image || "none"
|
|
258
|
-
}
|
|
251
|
+
const variant = resolveTheme === "dark" ? background.dark : background.light
|
|
252
|
+
setBackgroundStyle({
|
|
253
|
+
backgroundColor: variant?.color || "transparent",
|
|
254
|
+
backgroundImage: variant?.image || "none",
|
|
255
|
+
})
|
|
259
256
|
} else {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
} else {
|
|
265
|
-
document.body.style.backgroundColor = prefersDark ? "#000000" : "#ffffff"
|
|
266
|
-
}
|
|
267
|
-
document.body.style.backgroundImage = "none"
|
|
257
|
+
setBackgroundStyle({
|
|
258
|
+
backgroundColor: resolveTheme === "dark" ? "#000000" : "#ffffff",
|
|
259
|
+
backgroundImage: "none",
|
|
260
|
+
})
|
|
268
261
|
}
|
|
269
262
|
}, [background, theme])
|
|
270
263
|
|
|
@@ -297,9 +290,9 @@ function Tenant() {
|
|
|
297
290
|
|
|
298
291
|
const navigateFromSidebar = useCallback(
|
|
299
292
|
(path: string) => {
|
|
300
|
-
|
|
293
|
+
runViewTransition(() => navigate(path))
|
|
301
294
|
window.setTimeout(() => {
|
|
302
|
-
|
|
295
|
+
setSidebarOpen(false)
|
|
303
296
|
}, MOBILE_SHEET_CLOSE_MS)
|
|
304
297
|
},
|
|
305
298
|
[navigate],
|
|
@@ -504,269 +497,283 @@ function Tenant() {
|
|
|
504
497
|
return (
|
|
505
498
|
appName &&
|
|
506
499
|
collectionTitles && (
|
|
507
|
-
|
|
508
|
-
<
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
e.preventDefault()
|
|
500
|
+
<div className="relative isolate min-h-dvh">
|
|
501
|
+
<div
|
|
502
|
+
aria-hidden="true"
|
|
503
|
+
className="absolute inset-0 z-0 pointer-events-none print:hidden"
|
|
504
|
+
style={backgroundStyle}
|
|
505
|
+
/>
|
|
506
|
+
<div className="relative z-10 flex min-h-dvh flex-col">
|
|
507
|
+
<Helmet>
|
|
508
|
+
{meta?.description && <meta name="description" content={meta.description} />}
|
|
509
|
+
{meta?.icons ? (
|
|
510
|
+
meta.icons.map((icon) => (
|
|
511
|
+
<link key={icon.rel} rel={icon.rel} type={icon.type} href={icon.url} />
|
|
512
|
+
))
|
|
513
|
+
) : (
|
|
514
|
+
<link key="favicon" rel="icon" type="image/png" href="./favicon.ico" />
|
|
515
|
+
)}
|
|
516
|
+
</Helmet>
|
|
517
|
+
|
|
518
|
+
<Dialog
|
|
519
|
+
open={dialogContent !== null}
|
|
520
|
+
onOpenChange={() => {
|
|
521
|
+
if (!dialogContent?.disableClose) {
|
|
522
|
+
setDialogContent(null)
|
|
531
523
|
}
|
|
532
524
|
}}
|
|
533
|
-
hideCloseButton={dialogContent?.disableClose}
|
|
534
525
|
>
|
|
535
|
-
<
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
{mfaDialog && (
|
|
541
|
-
<div className="flex flex-col gap-4 items-center">
|
|
542
|
-
<QRCodeSVG value={mfaDialog.totpUri} size={180} />
|
|
543
|
-
<div className="w-full flex flex-col items-center">
|
|
544
|
-
<label htmlFor="mfa-code" className="mb-1">
|
|
545
|
-
Enter 6-digit code
|
|
546
|
-
</label>
|
|
547
|
-
<Input
|
|
548
|
-
id="mfa-code"
|
|
549
|
-
type="text"
|
|
550
|
-
inputMode="numeric"
|
|
551
|
-
pattern="[0-9]{6}"
|
|
552
|
-
maxLength={6}
|
|
553
|
-
value={mfaCode}
|
|
554
|
-
onChange={(e) => {
|
|
555
|
-
const value = e.target.value.replace(/[^0-9]/g, "")
|
|
556
|
-
setMfaCode(value)
|
|
557
|
-
mfaCodeRef.current = value
|
|
558
|
-
setMfaError("")
|
|
559
|
-
}}
|
|
560
|
-
className="text-center w-32"
|
|
561
|
-
/>
|
|
562
|
-
{mfaError && <div className="text-destructive text-xs mt-1">{mfaError}</div>}
|
|
563
|
-
</div>
|
|
564
|
-
</div>
|
|
565
|
-
)}
|
|
566
|
-
|
|
567
|
-
{(!dialogContent?.disableClose || dialogContent?.buttons) && (
|
|
568
|
-
<DialogFooter>
|
|
569
|
-
<div className="flex gap-2 justify-end">
|
|
570
|
-
{dialogContent?.buttons?.map((button) => (
|
|
571
|
-
<Button key={button.label} onClick={button.onClick}>
|
|
572
|
-
{button.label}
|
|
573
|
-
</Button>
|
|
574
|
-
))}
|
|
575
|
-
{!dialogContent?.disableClose && (
|
|
576
|
-
<Button onClick={() => setDialogContent(null)} variant="outline">
|
|
577
|
-
Close
|
|
578
|
-
</Button>
|
|
579
|
-
)}
|
|
580
|
-
</div>
|
|
581
|
-
</DialogFooter>
|
|
582
|
-
)}
|
|
583
|
-
</DialogContent>
|
|
584
|
-
</Dialog>
|
|
585
|
-
|
|
586
|
-
<header className="sticky top-0 z-50 flex h-16 items-center gap-4 border-b border-[rgb(39,39,42)] bg-black px-4 lg:px-6 print:hidden select-none">
|
|
587
|
-
<nav className="hidden h-full flex-col gap-6 text-lg font-medium lg:flex lg:flex-row lg:items-center lg:text-sm lg:gap-6 dark">
|
|
588
|
-
<button
|
|
589
|
-
className="flex h-full items-center gap-2"
|
|
590
|
-
key="home"
|
|
591
|
-
onClick={() => {
|
|
592
|
-
runViewTransition(() => navigate("/"))
|
|
526
|
+
<DialogContent
|
|
527
|
+
onEscapeKeyDown={(e) => {
|
|
528
|
+
if (dialogContent?.disableClose) {
|
|
529
|
+
e.preventDefault()
|
|
530
|
+
}
|
|
593
531
|
}}
|
|
532
|
+
hideCloseButton={dialogContent?.disableClose}
|
|
594
533
|
>
|
|
595
|
-
<
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
onBlur={() => setSearchFocused(false)}
|
|
620
|
-
onKeyDown={(e) => {
|
|
621
|
-
if (e.key === "Escape") {
|
|
622
|
-
setSearchFocused(false)
|
|
623
|
-
} else {
|
|
624
|
-
setSearchFocused(true)
|
|
625
|
-
}
|
|
626
|
-
}}
|
|
627
|
-
/>
|
|
628
|
-
</search>
|
|
629
|
-
</PopoverTrigger>
|
|
630
|
-
{search && showSearchAll && <SearchAll query={search} />}
|
|
631
|
-
</Popover>
|
|
632
|
-
</div>
|
|
633
|
-
)}
|
|
634
|
-
<ModeToggle />
|
|
635
|
-
{enableMfa && (mfaActive || mfaEnabled) && !mfaRevoked && (
|
|
636
|
-
<DropdownMenu>
|
|
637
|
-
<DropdownMenuTrigger asChild>
|
|
638
|
-
<Button variant="outline" size="icon">
|
|
639
|
-
<User />
|
|
640
|
-
</Button>
|
|
641
|
-
</DropdownMenuTrigger>
|
|
642
|
-
<DropdownMenuContent className="w-56 dark print:hidden">
|
|
643
|
-
<DropdownMenuGroup>
|
|
644
|
-
<DropdownMenuItem
|
|
645
|
-
onClick={() => {
|
|
646
|
-
revokeMfa()
|
|
534
|
+
<DialogHeader>
|
|
535
|
+
<DialogTitle>{dialogContent?.title}</DialogTitle>
|
|
536
|
+
<DialogDescription>{dialogContent?.description}</DialogDescription>
|
|
537
|
+
</DialogHeader>
|
|
538
|
+
|
|
539
|
+
{mfaDialog && (
|
|
540
|
+
<div className="flex flex-col gap-4 items-center">
|
|
541
|
+
<QRCodeSVG value={mfaDialog.totpUri} size={180} />
|
|
542
|
+
<div className="w-full flex flex-col items-center">
|
|
543
|
+
<label htmlFor="mfa-code" className="mb-1">
|
|
544
|
+
Enter 6-digit code
|
|
545
|
+
</label>
|
|
546
|
+
<Input
|
|
547
|
+
id="mfa-code"
|
|
548
|
+
type="text"
|
|
549
|
+
inputMode="numeric"
|
|
550
|
+
pattern="[0-9]{6}"
|
|
551
|
+
maxLength={6}
|
|
552
|
+
value={mfaCode}
|
|
553
|
+
onChange={(e) => {
|
|
554
|
+
const value = e.target.value.replace(/[^0-9]/g, "")
|
|
555
|
+
setMfaCode(value)
|
|
556
|
+
mfaCodeRef.current = value
|
|
557
|
+
setMfaError("")
|
|
647
558
|
}}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
</
|
|
651
|
-
</
|
|
652
|
-
</
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
559
|
+
className="text-center w-32"
|
|
560
|
+
/>
|
|
561
|
+
{mfaError && <div className="text-destructive text-xs mt-1">{mfaError}</div>}
|
|
562
|
+
</div>
|
|
563
|
+
</div>
|
|
564
|
+
)}
|
|
565
|
+
|
|
566
|
+
{(!dialogContent?.disableClose || dialogContent?.buttons) && (
|
|
567
|
+
<DialogFooter>
|
|
568
|
+
<div className="flex gap-2 justify-end">
|
|
569
|
+
{dialogContent?.buttons?.map((button) => (
|
|
570
|
+
<Button key={button.label} onClick={button.onClick}>
|
|
571
|
+
{button.label}
|
|
572
|
+
</Button>
|
|
573
|
+
))}
|
|
574
|
+
{!dialogContent?.disableClose && (
|
|
575
|
+
<Button onClick={() => setDialogContent(null)} variant="outline">
|
|
576
|
+
Close
|
|
577
|
+
</Button>
|
|
578
|
+
)}
|
|
579
|
+
</div>
|
|
580
|
+
</DialogFooter>
|
|
581
|
+
)}
|
|
582
|
+
</DialogContent>
|
|
583
|
+
</Dialog>
|
|
584
|
+
|
|
585
|
+
<header className="sticky top-0 z-50 flex h-16 items-center gap-4 border-b border-[rgb(39,39,42)] bg-black px-4 lg:px-6 print:hidden select-none">
|
|
586
|
+
<nav className="hidden h-full flex-col gap-6 text-lg font-medium lg:flex lg:flex-row lg:items-center lg:text-sm lg:gap-6 dark">
|
|
587
|
+
<button
|
|
588
|
+
className="flex h-full items-center gap-2"
|
|
589
|
+
key="home"
|
|
590
|
+
onClick={() => {
|
|
591
|
+
runViewTransition(() => navigate("/"))
|
|
592
|
+
}}
|
|
593
|
+
>
|
|
594
|
+
<img src={logo || defaultLogo} alt="Logo" className="h-8 mr-2" />
|
|
595
|
+
</button>
|
|
596
|
+
{links}
|
|
597
|
+
</nav>
|
|
598
|
+
<div className="hidden items-center text-sm font-medium gap-4 lg:ml-auto lg:flex lg:gap-4">
|
|
599
|
+
{isLoading && connectionStatus === "online" && <LoadingSpinner size={7} dark />}
|
|
600
|
+
{connectionStatus === "offline" && <Badge variant="destructive">Offline</Badge>}
|
|
601
|
+
{showSearchAll && (
|
|
602
|
+
<div className="ml-auto flex-1 lg:flex-initial text-primary dark">
|
|
603
|
+
<Popover open={searchFocused && Boolean(search)}>
|
|
604
|
+
<PopoverTrigger asChild>
|
|
605
|
+
<search className="relative">
|
|
606
|
+
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
607
|
+
<Input
|
|
608
|
+
type="search"
|
|
609
|
+
value={search}
|
|
610
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
611
|
+
placeholder="Search all..."
|
|
612
|
+
className="pl-8 lg:w-[195px] xl:w-[275px]"
|
|
613
|
+
onFocus={() => {
|
|
614
|
+
setTimeout(() => {
|
|
615
|
+
setSearchFocused(true)
|
|
616
|
+
}, 100)
|
|
617
|
+
}}
|
|
618
|
+
onBlur={() => setSearchFocused(false)}
|
|
619
|
+
onKeyDown={(e) => {
|
|
620
|
+
if (e.key === "Escape") {
|
|
621
|
+
setSearchFocused(false)
|
|
622
|
+
} else {
|
|
623
|
+
setSearchFocused(true)
|
|
624
|
+
}
|
|
625
|
+
}}
|
|
626
|
+
/>
|
|
627
|
+
</search>
|
|
628
|
+
</PopoverTrigger>
|
|
629
|
+
{search && showSearchAll && <SearchAll query={search} />}
|
|
630
|
+
</Popover>
|
|
631
|
+
</div>
|
|
632
|
+
)}
|
|
633
|
+
<ModeToggle />
|
|
634
|
+
{enableMfa && (mfaActive || mfaEnabled) && !mfaRevoked && (
|
|
635
|
+
<DropdownMenu>
|
|
636
|
+
<DropdownMenuTrigger asChild>
|
|
637
|
+
<Button variant="outline" size="icon">
|
|
638
|
+
<User />
|
|
639
|
+
</Button>
|
|
640
|
+
</DropdownMenuTrigger>
|
|
641
|
+
<DropdownMenuContent className="w-56 dark print:hidden">
|
|
642
|
+
<DropdownMenuGroup>
|
|
643
|
+
<DropdownMenuItem
|
|
644
|
+
onClick={() => {
|
|
645
|
+
revokeMfa()
|
|
646
|
+
}}
|
|
696
647
|
>
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
648
|
+
Revoke MFA
|
|
649
|
+
</DropdownMenuItem>
|
|
650
|
+
</DropdownMenuGroup>
|
|
651
|
+
</DropdownMenuContent>
|
|
652
|
+
</DropdownMenu>
|
|
653
|
+
)}
|
|
654
|
+
<button className="text-muted-foreground hover:text-foreground dark" onClick={signOutUser}>
|
|
655
|
+
Sign Out
|
|
656
|
+
</button>
|
|
657
|
+
</div>
|
|
658
|
+
|
|
659
|
+
<Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>
|
|
660
|
+
<SheetTrigger asChild>
|
|
661
|
+
<Button size="icon" variant="outline" className="absolute left-4 lg:hidden dark">
|
|
662
|
+
<PanelLeft className="h-5 w-5 text-primary" />
|
|
663
|
+
<span className="sr-only">Toggle Menu</span>
|
|
664
|
+
</Button>
|
|
665
|
+
</SheetTrigger>
|
|
666
|
+
<SheetContent side="left" className="sm:max-w-xs overflow-y-auto">
|
|
667
|
+
<nav className="grid gap-6 text-lg font-medium pt-12">
|
|
668
|
+
{hasDashboard && (
|
|
669
|
+
<button
|
|
670
|
+
className={
|
|
671
|
+
window.location.pathname === "/"
|
|
672
|
+
? "flex items-center gap-4 px-2.5 text-foreground"
|
|
673
|
+
: "flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
|
674
|
+
}
|
|
675
|
+
onClick={() => navigateFromSidebar("/")}
|
|
676
|
+
>
|
|
677
|
+
<ChartBar className="h-5 w-5" />
|
|
678
|
+
Dashboard
|
|
679
|
+
</button>
|
|
680
|
+
)}
|
|
681
|
+
{sidebarMenu.map((group) => {
|
|
682
|
+
if (group.collections.length === 1) {
|
|
709
683
|
const className = "flex items-center gap-4 px-2.5 text-primary"
|
|
710
684
|
return (
|
|
711
685
|
<button
|
|
712
|
-
key={
|
|
686
|
+
key={group.collections[0]}
|
|
713
687
|
className={
|
|
714
|
-
isMatch(
|
|
688
|
+
isMatch(group.collections[0])
|
|
715
689
|
? cn(className, "text-foreground")
|
|
716
690
|
: cn(
|
|
717
691
|
className,
|
|
718
692
|
"text-muted-foreground hover:text-foreground",
|
|
719
693
|
)
|
|
720
694
|
}
|
|
721
|
-
onClick={() =>
|
|
695
|
+
onClick={() =>
|
|
696
|
+
navigateFromSidebar(`/${group.collections[0].toLowerCase()}`)
|
|
697
|
+
}
|
|
722
698
|
>
|
|
723
699
|
{/* eslint-disable security/detect-object-injection */}
|
|
724
|
-
{iconNames[
|
|
725
|
-
? createElement(iconNames[
|
|
700
|
+
{iconNames[group.collections[0]]
|
|
701
|
+
? createElement(iconNames[group.collections[0]], {
|
|
702
|
+
className: "h-5 w-5",
|
|
703
|
+
})
|
|
726
704
|
: null}
|
|
727
|
-
{collectionTitles[
|
|
705
|
+
{collectionTitles[group.collections[0]]}
|
|
728
706
|
{/* eslint-enable security/detect-object-injection */}
|
|
729
707
|
</button>
|
|
730
708
|
)
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
709
|
+
} else {
|
|
710
|
+
return group.collections.map((collection) => {
|
|
711
|
+
const className = "flex items-center gap-4 px-2.5 text-primary"
|
|
712
|
+
return (
|
|
713
|
+
<button
|
|
714
|
+
key={collection}
|
|
715
|
+
className={
|
|
716
|
+
isMatch(collection)
|
|
717
|
+
? cn(className, "text-foreground")
|
|
718
|
+
: cn(
|
|
719
|
+
className,
|
|
720
|
+
"text-muted-foreground hover:text-foreground",
|
|
721
|
+
)
|
|
722
|
+
}
|
|
723
|
+
onClick={() =>
|
|
724
|
+
navigateFromSidebar(`/${collection.toLowerCase()}`)
|
|
725
|
+
}
|
|
726
|
+
>
|
|
727
|
+
{/* eslint-disable security/detect-object-injection */}
|
|
728
|
+
{iconNames[collection]
|
|
729
|
+
? createElement(iconNames[collection], {
|
|
730
|
+
className: "h-5 w-5",
|
|
731
|
+
})
|
|
732
|
+
: null}
|
|
733
|
+
{collectionTitles[collection]}
|
|
734
|
+
{/* eslint-enable security/detect-object-injection */}
|
|
735
|
+
</button>
|
|
736
|
+
)
|
|
737
|
+
})
|
|
738
|
+
}
|
|
739
|
+
})}
|
|
740
|
+
{enableMfa && ((!mfaActive && !mfaEnabled) || mfaRevoked) && (
|
|
741
|
+
<button
|
|
742
|
+
key="mfa-enroll-mobile"
|
|
743
|
+
className="flex items-center gap-4 px-2.5 bg-destructive p-4 rounded-md text-white"
|
|
744
|
+
onClick={() => multiFactorEnroll(user, getMultiFactorCode)}
|
|
745
|
+
>
|
|
746
|
+
Enable MFA
|
|
747
|
+
</button>
|
|
748
|
+
)}
|
|
749
|
+
</nav>
|
|
750
|
+
<div className="grid gap-6 text-lg mt-6 font-medium">
|
|
735
751
|
<button
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
onClick={() => multiFactorEnroll(user, getMultiFactorCode)}
|
|
752
|
+
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
|
753
|
+
onClick={signOutUser}
|
|
739
754
|
>
|
|
740
|
-
|
|
755
|
+
Sign Out
|
|
741
756
|
</button>
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
<button
|
|
746
|
-
className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
|
|
747
|
-
onClick={signOutUser}
|
|
748
|
-
>
|
|
749
|
-
Sign Out
|
|
750
|
-
</button>
|
|
751
|
-
<div>
|
|
752
|
-
<ModeToggle />
|
|
757
|
+
<div>
|
|
758
|
+
<ModeToggle />
|
|
759
|
+
</div>
|
|
753
760
|
</div>
|
|
754
|
-
</
|
|
755
|
-
</
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
</
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
761
|
+
</SheetContent>
|
|
762
|
+
</Sheet>
|
|
763
|
+
<div className="flex justify-center items-center w-full lg:hidden">
|
|
764
|
+
<img src={logo || defaultLogo} alt="Logo" className="h-8" />
|
|
765
|
+
</div>
|
|
766
|
+
<div className="absolute right-4 flex justify-center items-center gap-4 ml-auto lg:hidden">
|
|
767
|
+
{isLoading && connectionStatus === "online" && <LoadingSpinner size={7} dark />}
|
|
768
|
+
{connectionStatus === "offline" && <Badge variant="destructive">Offline</Badge>}
|
|
769
|
+
</div>
|
|
770
|
+
</header>
|
|
771
|
+
<main className="flex w-full flex-col">
|
|
772
|
+
<Outlet />
|
|
773
|
+
</main>
|
|
774
|
+
<Toaster />
|
|
775
|
+
</div>
|
|
776
|
+
</div>
|
|
770
777
|
)
|
|
771
778
|
)
|
|
772
779
|
}
|