@authdog/react-elements 0.0.39 → 0.0.40
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/.turbo/turbo-build.log +29 -26
- package/CHANGELOG.md +6 -0
- package/dist/components/ui/alert.js.map +1 -1
- package/dist/components/ui/alert.mjs.map +1 -1
- package/dist/components/ui/avatar.js.map +1 -1
- package/dist/components/ui/avatar.mjs.map +1 -1
- package/dist/components/ui/badge.js.map +1 -1
- package/dist/components/ui/badge.mjs.map +1 -1
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/card.mjs.map +1 -1
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/dropdown-menu.mjs.map +1 -1
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/input.mjs.map +1 -1
- package/dist/components/ui/label.js.map +1 -1
- package/dist/components/ui/label.mjs.map +1 -1
- package/dist/components/ui/separator.js.map +1 -1
- package/dist/components/ui/separator.mjs.map +1 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/sheet.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +5 -5
- package/src/components/core/client-only.tsx +10 -15
- package/src/components/core/navbar.tsx +81 -50
- package/src/components/core/placeholder-alert.tsx +7 -9
- package/src/components/core/user-dropdown.tsx +97 -55
- package/src/components/core/user-profile.tsx +180 -86
- package/src/components/flow/login.tsx +42 -29
- package/src/components/flow/totp-validator.tsx +94 -73
- package/src/components/icons.tsx +13 -13
- package/src/components/ui/alert.tsx +11 -11
- package/src/components/ui/avatar.tsx +10 -10
- package/src/components/ui/badge.tsx +9 -9
- package/src/components/ui/card.tsx +13 -13
- package/src/components/ui/dropdown-menu.tsx +39 -37
- package/src/components/ui/input.tsx +5 -5
- package/src/components/ui/label.tsx +7 -7
- package/src/components/ui/separator.tsx +7 -7
- package/src/components/ui/sheet.tsx +21 -21
- package/src/index.ts +6 -6
- package/src/main.tsx +4 -6
- package/src/preview.tsx +4 -8
- package/src/stories/Button._stories.tsx +15 -11
- package/src/stories/LoginForm.stories.tsx +6 -6
- package/src/stories/Navbar._stories.tsx +57 -19
- package/src/stories/PlaceholderAlert._stories.tsx +8 -8
- package/src/stories/TotpValidator.stories.tsx +10 -8
- package/src/stories/UserDropdown.stories.tsx +7 -9
- package/src/stories/UserProfile.stories.tsx +12 -12
- package/tsup.config.ts +6 -9
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import type React from "react"
|
|
4
|
-
import { useState } from "react"
|
|
5
|
-
import { User, LogOut, Menu } from "lucide-react"
|
|
3
|
+
import type React from "react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { User, LogOut, Menu } from "lucide-react";
|
|
6
6
|
|
|
7
|
-
import { cn } from "../../lib/utils"
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
|
+
import {
|
|
9
|
+
Avatar,
|
|
10
|
+
AvatarFallback,
|
|
11
|
+
AvatarImage,
|
|
12
|
+
} from "../../components/ui/avatar";
|
|
13
|
+
import { Button } from "../../components/ui/button";
|
|
10
14
|
import {
|
|
11
15
|
DropdownMenu,
|
|
12
16
|
DropdownMenuContent,
|
|
@@ -15,22 +19,22 @@ import {
|
|
|
15
19
|
DropdownMenuLabel,
|
|
16
20
|
DropdownMenuSeparator,
|
|
17
21
|
DropdownMenuTrigger,
|
|
18
|
-
} from "../../components/ui/dropdown-menu"
|
|
19
|
-
import { Sheet, SheetContent, SheetTrigger } from "../../components/ui/sheet"
|
|
20
|
-
import { IconWrapper } from "../icons"
|
|
22
|
+
} from "../../components/ui/dropdown-menu";
|
|
23
|
+
import { Sheet, SheetContent, SheetTrigger } from "../../components/ui/sheet";
|
|
24
|
+
import { IconWrapper } from "../icons";
|
|
21
25
|
|
|
22
26
|
interface NavItem {
|
|
23
|
-
title: string
|
|
24
|
-
href: string
|
|
25
|
-
disabled?: boolean
|
|
27
|
+
title: string;
|
|
28
|
+
href: string;
|
|
29
|
+
disabled?: boolean;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
interface NavbarProps {
|
|
29
33
|
items?: NavItem[] | undefined;
|
|
30
|
-
children?: React.ReactNode
|
|
31
|
-
className?: string
|
|
32
|
-
logoText?: string
|
|
33
|
-
isLoading?: boolean
|
|
34
|
+
children?: React.ReactNode;
|
|
35
|
+
className?: string;
|
|
36
|
+
logoText?: string;
|
|
37
|
+
isLoading?: boolean;
|
|
34
38
|
user?: any;
|
|
35
39
|
onNavigateHome?: () => void;
|
|
36
40
|
onNavItemClick?: (href: string) => void;
|
|
@@ -61,25 +65,34 @@ export function Navbar({
|
|
|
61
65
|
identityHost = "https://stg-id.authdog.xyz",
|
|
62
66
|
environmentId = "58be35b0-708f-49f6-84f0-6695d307d997",
|
|
63
67
|
}: NavbarProps) {
|
|
64
|
-
const [open, setOpen] = useState(false)
|
|
65
|
-
const isAuthenticated =
|
|
68
|
+
const [open, setOpen] = useState(false);
|
|
69
|
+
const isAuthenticated =
|
|
70
|
+
user !== null &&
|
|
71
|
+
user !== undefined &&
|
|
72
|
+
user.id !== null &&
|
|
73
|
+
user.id !== undefined;
|
|
66
74
|
return (
|
|
67
75
|
<header className={cn("border-b bg-background", className)}>
|
|
68
76
|
<div className="container flex h-16 items-center justify-between px-4 md:px-6">
|
|
69
77
|
<div className="flex items-center gap-4">
|
|
70
|
-
<span
|
|
78
|
+
<span
|
|
79
|
+
className="text-xl font-bold cursor-pointer"
|
|
80
|
+
onClick={onNavigateHome}
|
|
81
|
+
>
|
|
82
|
+
{logoText}
|
|
83
|
+
</span>
|
|
71
84
|
<nav className="hidden md:flex gap-6">
|
|
72
85
|
{items?.map((item, index) => (
|
|
73
86
|
<span
|
|
74
87
|
key={index}
|
|
75
88
|
onClick={() => {
|
|
76
89
|
if (!item.disabled) {
|
|
77
|
-
onNavItemClick(item.href)
|
|
90
|
+
onNavItemClick(item.href);
|
|
78
91
|
}
|
|
79
92
|
}}
|
|
80
93
|
className={cn(
|
|
81
94
|
"text-sm font-medium transition-colors hover:text-primary cursor-pointer",
|
|
82
|
-
item.disabled && "cursor-not-allowed opacity-80"
|
|
95
|
+
item.disabled && "cursor-not-allowed opacity-80",
|
|
83
96
|
)}
|
|
84
97
|
>
|
|
85
98
|
{item.title}
|
|
@@ -90,18 +103,26 @@ export function Navbar({
|
|
|
90
103
|
<div className="flex items-center gap-4">
|
|
91
104
|
{children}
|
|
92
105
|
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
<DropdownMenu>
|
|
106
|
+
{isAuthenticated ? (
|
|
107
|
+
<DropdownMenu>
|
|
96
108
|
<DropdownMenuTrigger asChild>
|
|
97
|
-
<Button
|
|
109
|
+
<Button
|
|
110
|
+
variant="ghost"
|
|
111
|
+
className="relative h-8 w-8 rounded-full"
|
|
112
|
+
disabled={isLoading}
|
|
113
|
+
>
|
|
98
114
|
<Avatar className="h-8 w-8">
|
|
99
115
|
{isLoading ? (
|
|
100
116
|
<div className="h-8 w-8 animate-pulse bg-muted rounded-full" />
|
|
101
117
|
) : (
|
|
102
118
|
<>
|
|
103
|
-
<AvatarImage
|
|
104
|
-
|
|
119
|
+
<AvatarImage
|
|
120
|
+
src={user.photos?.[0]?.value || "/placeholder.svg"}
|
|
121
|
+
alt={user.displayName}
|
|
122
|
+
/>
|
|
123
|
+
<AvatarFallback>
|
|
124
|
+
{user.displayName?.charAt(0)}
|
|
125
|
+
</AvatarFallback>
|
|
105
126
|
</>
|
|
106
127
|
)}
|
|
107
128
|
</Avatar>
|
|
@@ -117,8 +138,12 @@ export function Navbar({
|
|
|
117
138
|
<>
|
|
118
139
|
<DropdownMenuLabel className="font-normal">
|
|
119
140
|
<div className="flex flex-col space-y-1">
|
|
120
|
-
<p className="text-sm font-medium leading-none">
|
|
121
|
-
|
|
141
|
+
<p className="text-sm font-medium leading-none">
|
|
142
|
+
{user.displayName}
|
|
143
|
+
</p>
|
|
144
|
+
<p className="text-xs leading-none text-muted-foreground">
|
|
145
|
+
{user.emails?.[0]?.value}
|
|
146
|
+
</p>
|
|
122
147
|
</div>
|
|
123
148
|
</DropdownMenuLabel>
|
|
124
149
|
<DropdownMenuSeparator />
|
|
@@ -137,29 +162,35 @@ export function Navbar({
|
|
|
137
162
|
)}
|
|
138
163
|
</DropdownMenuContent>
|
|
139
164
|
</DropdownMenu>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
) : (
|
|
166
|
+
<Button
|
|
167
|
+
variant="default"
|
|
168
|
+
aria-label="Sign in"
|
|
169
|
+
onClick={() => {
|
|
170
|
+
if (!environmentId) {
|
|
171
|
+
throw new Error("Environment ID is required");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!identityHost) {
|
|
175
|
+
throw new Error("Identity Host is required");
|
|
176
|
+
}
|
|
147
177
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const signinUrl = `${identityHost}/signin/${environmentId}`;
|
|
153
|
-
window.open(signinUrl, "_blank");
|
|
154
|
-
}}>
|
|
178
|
+
const signinUrl = `${identityHost}/signin/${environmentId}`;
|
|
179
|
+
window.open(signinUrl, "_blank");
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
155
182
|
Sign in
|
|
156
183
|
</Button>
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
|
|
184
|
+
)}
|
|
185
|
+
|
|
160
186
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
161
187
|
<SheetTrigger asChild>
|
|
162
|
-
<Button
|
|
188
|
+
<Button
|
|
189
|
+
variant="ghost"
|
|
190
|
+
size="icon"
|
|
191
|
+
className="md:hidden"
|
|
192
|
+
aria-label="Open Menu"
|
|
193
|
+
>
|
|
163
194
|
<IconWrapper Icon={Menu} />
|
|
164
195
|
</Button>
|
|
165
196
|
</SheetTrigger>
|
|
@@ -171,7 +202,7 @@ export function Navbar({
|
|
|
171
202
|
href={item.href}
|
|
172
203
|
className={cn(
|
|
173
204
|
"flex w-full items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-accent",
|
|
174
|
-
item.disabled && "cursor-not-allowed opacity-80"
|
|
205
|
+
item.disabled && "cursor-not-allowed opacity-80",
|
|
175
206
|
)}
|
|
176
207
|
onClick={() => setOpen(false)}
|
|
177
208
|
>
|
|
@@ -184,5 +215,5 @@ export function Navbar({
|
|
|
184
215
|
</div>
|
|
185
216
|
</div>
|
|
186
217
|
</header>
|
|
187
|
-
)
|
|
218
|
+
);
|
|
188
219
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CheckCircle2Icon } from "lucide-react"
|
|
2
|
-
import { Alert, AlertDescription, AlertTitle } from "../../components/ui/alert"
|
|
3
|
-
import { IconWrapper } from "../icons"
|
|
1
|
+
import { CheckCircle2Icon } from "lucide-react";
|
|
2
|
+
import { Alert, AlertDescription, AlertTitle } from "../../components/ui/alert";
|
|
3
|
+
import { IconWrapper } from "../icons";
|
|
4
4
|
|
|
5
5
|
interface PlaceholderAlertProps {
|
|
6
6
|
title?: string;
|
|
@@ -11,15 +11,13 @@ export const PlaceholderAlert = (props: PlaceholderAlertProps) => {
|
|
|
11
11
|
const title = props.title ?? "Placeholder Alert";
|
|
12
12
|
const description = props.description ?? "This is a placeholder alert.";
|
|
13
13
|
|
|
14
|
-
return (
|
|
14
|
+
return (
|
|
15
15
|
<div className="grid w-full max-w-xl items-start gap-4">
|
|
16
16
|
<Alert>
|
|
17
17
|
<IconWrapper Icon={CheckCircle2Icon} />
|
|
18
18
|
<AlertTitle>{title}</AlertTitle>
|
|
19
|
-
<AlertDescription>
|
|
20
|
-
{description}
|
|
21
|
-
</AlertDescription>
|
|
19
|
+
<AlertDescription>{description}</AlertDescription>
|
|
22
20
|
</Alert>
|
|
23
21
|
</div>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -1,100 +1,142 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuSeparator,
|
|
8
|
+
DropdownMenuTrigger,
|
|
9
|
+
} from "../../components/ui/dropdown-menu";
|
|
10
|
+
import {
|
|
11
|
+
Avatar,
|
|
12
|
+
AvatarFallback,
|
|
13
|
+
AvatarImage,
|
|
14
|
+
} from "../../components/ui/avatar";
|
|
15
|
+
import { cn } from "@authdog/react-elements/lib/utils";
|
|
16
|
+
import { LogOut, Settings, ExternalLink } from "lucide-react";
|
|
7
17
|
|
|
8
18
|
export type UserDropdownLink = {
|
|
9
|
-
label: string
|
|
10
|
-
href?: string
|
|
11
|
-
onClick?: () => void
|
|
12
|
-
icon?: React.ComponentType<any
|
|
13
|
-
}
|
|
19
|
+
label: string;
|
|
20
|
+
href?: string;
|
|
21
|
+
onClick?: () => void;
|
|
22
|
+
icon?: React.ComponentType<any>;
|
|
23
|
+
};
|
|
14
24
|
|
|
15
25
|
export interface UserDropdownProps {
|
|
16
|
-
trigger: React.ReactElement
|
|
26
|
+
trigger: React.ReactElement;
|
|
17
27
|
user: {
|
|
18
|
-
displayName?: string
|
|
19
|
-
name?: string
|
|
20
|
-
email?: string
|
|
21
|
-
emails?: { value: string }[]
|
|
22
|
-
photos?: { value: string }[]
|
|
23
|
-
avatar?: string
|
|
24
|
-
}
|
|
25
|
-
className?: string
|
|
26
|
-
onManageAccount?: () => void
|
|
27
|
-
onSignout?: () => void
|
|
28
|
-
links?: UserDropdownLink[]
|
|
29
|
-
side?: "top" | "right" | "bottom" | "left"
|
|
30
|
-
align?: "start" | "center" | "end"
|
|
31
|
-
sideOffset?: number
|
|
32
|
-
modal?: boolean
|
|
28
|
+
displayName?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
emails?: { value: string }[];
|
|
32
|
+
photos?: { value: string }[];
|
|
33
|
+
avatar?: string;
|
|
34
|
+
};
|
|
35
|
+
className?: string;
|
|
36
|
+
onManageAccount?: () => void;
|
|
37
|
+
onSignout?: () => void;
|
|
38
|
+
links?: UserDropdownLink[];
|
|
39
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
40
|
+
align?: "start" | "center" | "end";
|
|
41
|
+
sideOffset?: number;
|
|
42
|
+
modal?: boolean;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
const getInitials = (name?: string) => {
|
|
36
|
-
if (!name) return "?"
|
|
37
|
-
const parts = String(name).trim().split(/\s+/)
|
|
38
|
-
const initials = parts
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
if (!name) return "?";
|
|
47
|
+
const parts = String(name).trim().split(/\s+/);
|
|
48
|
+
const initials = parts
|
|
49
|
+
.slice(0, 2)
|
|
50
|
+
.map((p) => p[0]?.toUpperCase())
|
|
51
|
+
.join("");
|
|
52
|
+
return initials || "?";
|
|
53
|
+
};
|
|
41
54
|
|
|
42
|
-
export const UserDropdown = ({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
export const UserDropdown = ({
|
|
56
|
+
trigger,
|
|
57
|
+
user,
|
|
58
|
+
className,
|
|
59
|
+
onManageAccount,
|
|
60
|
+
onSignout,
|
|
61
|
+
links = [],
|
|
62
|
+
side = "bottom",
|
|
63
|
+
align = "end",
|
|
64
|
+
sideOffset = 8,
|
|
65
|
+
modal = false,
|
|
66
|
+
}: UserDropdownProps) => {
|
|
67
|
+
const primaryEmail = user?.emails?.[0]?.value || user?.email || "";
|
|
68
|
+
const displayName = user?.displayName || user?.name || "";
|
|
69
|
+
const avatar = user?.photos?.[0]?.value || user?.avatar || "";
|
|
46
70
|
|
|
47
71
|
const handleLink = (item: UserDropdownLink) => {
|
|
48
|
-
if (item.onClick) return item.onClick()
|
|
72
|
+
if (item.onClick) return item.onClick();
|
|
49
73
|
if (item.href) {
|
|
50
74
|
if (item.href.startsWith("http")) {
|
|
51
|
-
window.open(item.href, "_blank")
|
|
75
|
+
window.open(item.href, "_blank");
|
|
52
76
|
} else {
|
|
53
|
-
window.location.assign(item.href)
|
|
77
|
+
window.location.assign(item.href);
|
|
54
78
|
}
|
|
55
79
|
}
|
|
56
|
-
}
|
|
80
|
+
};
|
|
57
81
|
|
|
58
|
-
const IconExternal = ExternalLink as any
|
|
82
|
+
const IconExternal = ExternalLink as any;
|
|
59
83
|
|
|
60
84
|
return (
|
|
61
85
|
<DropdownMenu modal={modal}>
|
|
62
|
-
<DropdownMenuTrigger asChild>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
|
87
|
+
<DropdownMenuContent
|
|
88
|
+
align={align}
|
|
89
|
+
side={side}
|
|
90
|
+
sideOffset={sideOffset}
|
|
91
|
+
className={cn(
|
|
92
|
+
"w-72 p-2 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
>
|
|
66
96
|
<div className="flex items-center gap-3 px-4 pt-4 pb-3">
|
|
67
97
|
<Avatar className="h-9 w-9 rounded-full">
|
|
68
98
|
<AvatarImage src={avatar} alt={displayName} />
|
|
69
|
-
<AvatarFallback className="rounded-full">
|
|
99
|
+
<AvatarFallback className="rounded-full">
|
|
100
|
+
{getInitials(displayName)}
|
|
101
|
+
</AvatarFallback>
|
|
70
102
|
</Avatar>
|
|
71
103
|
<div className="min-w-0">
|
|
72
104
|
<div className="text-sm font-semibold truncate">{displayName}</div>
|
|
73
|
-
<div className="text-xs text-muted-foreground truncate">
|
|
105
|
+
<div className="text-xs text-muted-foreground truncate">
|
|
106
|
+
{primaryEmail}
|
|
107
|
+
</div>
|
|
74
108
|
</div>
|
|
75
109
|
</div>
|
|
76
110
|
<DropdownMenuSeparator />
|
|
77
|
-
<DropdownMenuItem
|
|
111
|
+
<DropdownMenuItem
|
|
112
|
+
className="cursor-pointer py-2"
|
|
113
|
+
onClick={() => onManageAccount?.()}
|
|
114
|
+
>
|
|
78
115
|
<Settings className="mr-2 h-4 w-4" />
|
|
79
116
|
<span>Manage account</span>
|
|
80
117
|
</DropdownMenuItem>
|
|
81
118
|
{links.map((item, idx) => {
|
|
82
|
-
const Icon = (item.icon ?? IconExternal) as any
|
|
119
|
+
const Icon = (item.icon ?? IconExternal) as any;
|
|
83
120
|
return (
|
|
84
|
-
<DropdownMenuItem
|
|
121
|
+
<DropdownMenuItem
|
|
122
|
+
key={`${item.label}-${idx}`}
|
|
123
|
+
className="cursor-pointer py-2"
|
|
124
|
+
onClick={() => handleLink(item)}
|
|
125
|
+
>
|
|
85
126
|
<Icon className="mr-2 h-4 w-4" />
|
|
86
127
|
<span>{item.label}</span>
|
|
87
128
|
</DropdownMenuItem>
|
|
88
|
-
)
|
|
129
|
+
);
|
|
89
130
|
})}
|
|
90
131
|
<DropdownMenuSeparator />
|
|
91
|
-
<DropdownMenuItem
|
|
132
|
+
<DropdownMenuItem
|
|
133
|
+
className="cursor-pointer py-2 rounded-md font-semibold text-red-600 dark:text-red-300 hover:bg-red-50 hover:text-red-700 focus:bg-red-50 focus:text-red-700 dark:hover:bg-red-500/20 dark:focus:bg-red-500/25 dark:hover:text-red-100 dark:focus:text-red-100 border border-transparent dark:border-red-500/30 ring-0 focus-visible:ring-2 focus-visible:ring-red-400/40 dark:focus-visible:ring-red-400/40"
|
|
134
|
+
onClick={() => onSignout?.()}
|
|
135
|
+
>
|
|
92
136
|
<LogOut className="mr-2 h-4 w-4 text-red-600 dark:text-red-300" />
|
|
93
137
|
<span>Sign out</span>
|
|
94
138
|
</DropdownMenuItem>
|
|
95
139
|
</DropdownMenuContent>
|
|
96
140
|
</DropdownMenu>
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
141
|
+
);
|
|
142
|
+
};
|