better-auth-ui 3.2.5

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.
Files changed (226) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/auth-hooks-IOEvlYYv.d.cts +6966 -0
  4. package/dist/auth-hooks-IOEvlYYv.d.ts +6966 -0
  5. package/dist/auth-mutators-DdqOmQ32.d.cts +29 -0
  6. package/dist/auth-mutators-DdqOmQ32.d.ts +29 -0
  7. package/dist/auth-ui-provider-BsH3xJDw.d.ts +697 -0
  8. package/dist/auth-ui-provider-DhZfncd3.d.cts +697 -0
  9. package/dist/chunk-BDFQSFBU.js +750 -0
  10. package/dist/chunk-CRAHKL2C.cjs +801 -0
  11. package/dist/chunk-MJPOA6PK.js +801 -0
  12. package/dist/chunk-SV64DXGW.cjs +750 -0
  13. package/dist/index.cjs +12618 -0
  14. package/dist/index.d.cts +771 -0
  15. package/dist/index.d.ts +771 -0
  16. package/dist/index.js +12618 -0
  17. package/dist/instantdb.cjs +189 -0
  18. package/dist/instantdb.d.cts +36 -0
  19. package/dist/instantdb.d.ts +36 -0
  20. package/dist/instantdb.js +189 -0
  21. package/dist/metafile-cjs.json +1 -0
  22. package/dist/metafile-esm.json +1 -0
  23. package/dist/server.cjs +194 -0
  24. package/dist/server.d.cts +35 -0
  25. package/dist/server.d.ts +35 -0
  26. package/dist/server.js +194 -0
  27. package/dist/style.css +1 -0
  28. package/dist/tanstack.cjs +153 -0
  29. package/dist/tanstack.d.cts +18 -0
  30. package/dist/tanstack.d.ts +18 -0
  31. package/dist/tanstack.js +153 -0
  32. package/dist/triplit.cjs +201 -0
  33. package/dist/triplit.d.cts +31 -0
  34. package/dist/triplit.d.ts +31 -0
  35. package/dist/triplit.js +201 -0
  36. package/dist/utils-C5R37WDe.d.cts +3 -0
  37. package/dist/utils-C5R37WDe.d.ts +3 -0
  38. package/dist/view-paths-CHSJf5dv.d.cts +645 -0
  39. package/dist/view-paths-CHSJf5dv.d.ts +645 -0
  40. package/package.json +156 -0
  41. package/src/components/account/account-view.tsx +220 -0
  42. package/src/components/auth/auth-callback.tsx +36 -0
  43. package/src/components/auth/auth-form.tsx +277 -0
  44. package/src/components/auth/auth-view.tsx +389 -0
  45. package/src/components/auth/email-otp-button.tsx +53 -0
  46. package/src/components/auth/forms/email-otp-form.tsx +288 -0
  47. package/src/components/auth/forms/forgot-password-form.tsx +168 -0
  48. package/src/components/auth/forms/magic-link-form.tsx +191 -0
  49. package/src/components/auth/forms/recover-account-form.tsx +138 -0
  50. package/src/components/auth/forms/reset-password-form.tsx +215 -0
  51. package/src/components/auth/forms/sign-in-form.tsx +289 -0
  52. package/src/components/auth/forms/sign-up-form.tsx +788 -0
  53. package/src/components/auth/forms/two-factor-form.tsx +372 -0
  54. package/src/components/auth/magic-link-button.tsx +54 -0
  55. package/src/components/auth/one-tap.tsx +48 -0
  56. package/src/components/auth/otp-input-group.tsx +65 -0
  57. package/src/components/auth/passkey-button.tsx +85 -0
  58. package/src/components/auth/provider-button.tsx +141 -0
  59. package/src/components/auth/sign-out.tsx +25 -0
  60. package/src/components/auth-loading.tsx +21 -0
  61. package/src/components/captcha/captcha.tsx +79 -0
  62. package/src/components/captcha/recaptcha-badge.tsx +61 -0
  63. package/src/components/captcha/recaptcha-v2.tsx +58 -0
  64. package/src/components/captcha/recaptcha-v3.tsx +73 -0
  65. package/src/components/email/email-template.tsx +216 -0
  66. package/src/components/form-error.tsx +27 -0
  67. package/src/components/organization/accept-invitation-card.tsx +362 -0
  68. package/src/components/organization/create-organization-dialog.tsx +395 -0
  69. package/src/components/organization/delete-organization-card.tsx +101 -0
  70. package/src/components/organization/delete-organization-dialog.tsx +209 -0
  71. package/src/components/organization/invitation-cell.tsx +156 -0
  72. package/src/components/organization/invite-member-dialog.tsx +258 -0
  73. package/src/components/organization/leave-organization-dialog.tsx +150 -0
  74. package/src/components/organization/member-cell.tsx +187 -0
  75. package/src/components/organization/organization-cell-view.tsx +122 -0
  76. package/src/components/organization/organization-cell.tsx +154 -0
  77. package/src/components/organization/organization-invitations-card.tsx +94 -0
  78. package/src/components/organization/organization-logo-card.tsx +308 -0
  79. package/src/components/organization/organization-logo.tsx +120 -0
  80. package/src/components/organization/organization-members-card.tsx +155 -0
  81. package/src/components/organization/organization-name-card.tsx +204 -0
  82. package/src/components/organization/organization-settings-cards.tsx +67 -0
  83. package/src/components/organization/organization-slug-card.tsx +223 -0
  84. package/src/components/organization/organization-switcher.tsx +512 -0
  85. package/src/components/organization/organization-view.tsx +228 -0
  86. package/src/components/organization/organizations-card.tsx +72 -0
  87. package/src/components/organization/personal-account-view.tsx +115 -0
  88. package/src/components/organization/remove-member-dialog.tsx +144 -0
  89. package/src/components/organization/update-member-role-dialog.tsx +213 -0
  90. package/src/components/organization/user-invitations-card.tsx +238 -0
  91. package/src/components/password-input.tsx +56 -0
  92. package/src/components/provider-icons.tsx +385 -0
  93. package/src/components/redirect-to-sign-in.tsx +16 -0
  94. package/src/components/redirect-to-sign-up.tsx +16 -0
  95. package/src/components/settings/account/account-cell.tsx +158 -0
  96. package/src/components/settings/account/accounts-card.tsx +75 -0
  97. package/src/components/settings/account/delete-account-card.tsx +65 -0
  98. package/src/components/settings/account/delete-account-dialog.tsx +231 -0
  99. package/src/components/settings/account/update-avatar-card.tsx +198 -0
  100. package/src/components/settings/account/update-field-card.tsx +282 -0
  101. package/src/components/settings/account/update-name-card.tsx +39 -0
  102. package/src/components/settings/account/update-username-card.tsx +42 -0
  103. package/src/components/settings/account-settings-cards.tsx +123 -0
  104. package/src/components/settings/api-key/api-key-cell.tsx +108 -0
  105. package/src/components/settings/api-key/api-key-delete-dialog.tsx +162 -0
  106. package/src/components/settings/api-key/api-key-display-dialog.tsx +110 -0
  107. package/src/components/settings/api-key/api-keys-card.tsx +98 -0
  108. package/src/components/settings/api-key/create-api-key-dialog.tsx +376 -0
  109. package/src/components/settings/passkey/passkey-cell.tsx +113 -0
  110. package/src/components/settings/passkey/passkeys-card.tsx +111 -0
  111. package/src/components/settings/providers/provider-cell.tsx +152 -0
  112. package/src/components/settings/providers/providers-card.tsx +108 -0
  113. package/src/components/settings/security/change-email-card.tsx +200 -0
  114. package/src/components/settings/security/change-password-card.tsx +326 -0
  115. package/src/components/settings/security/session-cell.tsx +120 -0
  116. package/src/components/settings/security/sessions-card.tsx +58 -0
  117. package/src/components/settings/security-settings-cards.tsx +111 -0
  118. package/src/components/settings/shared/session-freshness-dialog.tsx +97 -0
  119. package/src/components/settings/shared/settings-action-button.tsx +51 -0
  120. package/src/components/settings/shared/settings-card-footer.tsx +94 -0
  121. package/src/components/settings/shared/settings-card-header.tsx +67 -0
  122. package/src/components/settings/shared/settings-card.tsx +106 -0
  123. package/src/components/settings/skeletons/input-field-skeleton.tsx +18 -0
  124. package/src/components/settings/skeletons/settings-cell-skeleton.tsx +37 -0
  125. package/src/components/settings/two-factor/backup-codes-dialog.tsx +113 -0
  126. package/src/components/settings/two-factor/two-factor-card.tsx +63 -0
  127. package/src/components/settings/two-factor/two-factor-password-dialog.tsx +226 -0
  128. package/src/components/signed-in.tsx +20 -0
  129. package/src/components/signed-out.tsx +20 -0
  130. package/src/components/ui/alert.tsx +66 -0
  131. package/src/components/ui/avatar.tsx +53 -0
  132. package/src/components/ui/button.tsx +59 -0
  133. package/src/components/ui/card.tsx +92 -0
  134. package/src/components/ui/checkbox.tsx +32 -0
  135. package/src/components/ui/dialog.tsx +143 -0
  136. package/src/components/ui/drawer.tsx +135 -0
  137. package/src/components/ui/dropdown-menu.tsx +257 -0
  138. package/src/components/ui/form.tsx +167 -0
  139. package/src/components/ui/input-otp.tsx +77 -0
  140. package/src/components/ui/input.tsx +21 -0
  141. package/src/components/ui/label.tsx +24 -0
  142. package/src/components/ui/select.tsx +185 -0
  143. package/src/components/ui/separator.tsx +28 -0
  144. package/src/components/ui/skeleton.tsx +13 -0
  145. package/src/components/ui/tabs.tsx +66 -0
  146. package/src/components/ui/textarea.tsx +18 -0
  147. package/src/components/user-avatar.tsx +147 -0
  148. package/src/components/user-button.tsx +409 -0
  149. package/src/components/user-view.tsx +138 -0
  150. package/src/hooks/use-auth-data.ts +184 -0
  151. package/src/hooks/use-authenticate.ts +62 -0
  152. package/src/hooks/use-captcha.tsx +138 -0
  153. package/src/hooks/use-current-organization.ts +59 -0
  154. package/src/hooks/use-hydrated.ts +13 -0
  155. package/src/hooks/use-lang.ts +32 -0
  156. package/src/hooks/use-success-transition.ts +51 -0
  157. package/src/hooks/use-theme.ts +39 -0
  158. package/src/index.ts +65 -0
  159. package/src/instantdb.ts +1 -0
  160. package/src/lib/auth-data-cache.ts +90 -0
  161. package/src/lib/auth-ui-provider.tsx +658 -0
  162. package/src/lib/gravatar-utils.ts +58 -0
  163. package/src/lib/image-utils.ts +55 -0
  164. package/src/lib/instantdb/model-names.ts +24 -0
  165. package/src/lib/instantdb/use-instant-options.ts +98 -0
  166. package/src/lib/instantdb/use-list-accounts.ts +38 -0
  167. package/src/lib/instantdb/use-list-sessions.ts +53 -0
  168. package/src/lib/instantdb/use-session.ts +55 -0
  169. package/src/lib/organization-refetcher.tsx +56 -0
  170. package/src/lib/social-providers.ts +144 -0
  171. package/src/lib/tanstack/auth-ui-provider-tanstack.tsx +49 -0
  172. package/src/lib/tanstack/use-tanstack-options.ts +112 -0
  173. package/src/lib/triplit/model-names.ts +24 -0
  174. package/src/lib/triplit/use-conditional-query.ts +82 -0
  175. package/src/lib/triplit/use-list-accounts.ts +31 -0
  176. package/src/lib/triplit/use-list-sessions.ts +33 -0
  177. package/src/lib/triplit/use-session.ts +42 -0
  178. package/src/lib/triplit/use-triplit-hooks.ts +68 -0
  179. package/src/lib/triplit/use-triplit-token.ts +44 -0
  180. package/src/lib/utils.ts +105 -0
  181. package/src/lib/view-paths.ts +55 -0
  182. package/src/localization/admin-error-codes.ts +20 -0
  183. package/src/localization/anonymous-error-codes.ts +6 -0
  184. package/src/localization/api-key-error-codes.ts +32 -0
  185. package/src/localization/auth-localization.ts +740 -0
  186. package/src/localization/base-error-codes.ts +27 -0
  187. package/src/localization/captcha-error-codes.ts +17 -0
  188. package/src/localization/email-otp-error-codes.ts +7 -0
  189. package/src/localization/generic-oauth-error-codes.ts +3 -0
  190. package/src/localization/haveibeenpwned-error-codes.ts +4 -0
  191. package/src/localization/multi-session-error-codes.ts +3 -0
  192. package/src/localization/organization-error-codes.ts +57 -0
  193. package/src/localization/passkey-error-codes.ts +10 -0
  194. package/src/localization/phone-number-error-codes.ts +10 -0
  195. package/src/localization/stripe-localization.ts +12 -0
  196. package/src/localization/two-factor-error-codes.ts +12 -0
  197. package/src/localization/username-error-codes.ts +9 -0
  198. package/src/server.ts +4 -0
  199. package/src/style.css +1 -0
  200. package/src/tanstack.ts +1 -0
  201. package/src/triplit.ts +1 -0
  202. package/src/types/account-options.ts +35 -0
  203. package/src/types/additional-fields.ts +21 -0
  204. package/src/types/any-auth-client.ts +6 -0
  205. package/src/types/api-key.ts +9 -0
  206. package/src/types/auth-client.ts +37 -0
  207. package/src/types/auth-hooks.ts +61 -0
  208. package/src/types/auth-mutators.ts +17 -0
  209. package/src/types/avatar-options.ts +29 -0
  210. package/src/types/captcha-options.ts +32 -0
  211. package/src/types/captcha-provider.ts +6 -0
  212. package/src/types/credentials-options.ts +32 -0
  213. package/src/types/delete-user-options.ts +7 -0
  214. package/src/types/fetch-error.ts +6 -0
  215. package/src/types/generic-oauth-options.ts +16 -0
  216. package/src/types/gravatar-options.ts +21 -0
  217. package/src/types/image.ts +7 -0
  218. package/src/types/invitation.ts +10 -0
  219. package/src/types/link.ts +7 -0
  220. package/src/types/organization-options.ts +106 -0
  221. package/src/types/password-validation.ts +16 -0
  222. package/src/types/profile.ts +15 -0
  223. package/src/types/refetch.ts +1 -0
  224. package/src/types/render-toast.ts +9 -0
  225. package/src/types/sign-up-options.ts +7 -0
  226. package/src/types/social-options.ts +16 -0
@@ -0,0 +1,150 @@
1
+ "use client"
2
+
3
+ import type { Organization } from "better-auth/plugins/organization"
4
+ import { Loader2 } from "lucide-react"
5
+ import { type ComponentProps, useContext, useMemo, useState } from "react"
6
+
7
+ import { AuthUIContext } from "../../lib/auth-ui-provider"
8
+ import { cn, getLocalizedError } from "../../lib/utils"
9
+ import type { AuthLocalization } from "../../localization/auth-localization"
10
+ import type { SettingsCardClassNames } from "../settings/shared/settings-card"
11
+ import { Button } from "../ui/button"
12
+ import { Card } from "../ui/card"
13
+ import {
14
+ Dialog,
15
+ DialogContent,
16
+ DialogDescription,
17
+ DialogFooter,
18
+ DialogHeader,
19
+ DialogTitle
20
+ } from "../ui/dialog"
21
+ import { OrganizationCellView } from "./organization-cell-view"
22
+
23
+ export interface LeaveOrganizationDialogProps
24
+ extends ComponentProps<typeof Dialog> {
25
+ className?: string
26
+ classNames?: SettingsCardClassNames
27
+ localization?: AuthLocalization
28
+ organization: Organization
29
+ }
30
+
31
+ export function LeaveOrganizationDialog({
32
+ organization,
33
+ className,
34
+ classNames,
35
+ localization: localizationProp,
36
+ onOpenChange,
37
+ ...props
38
+ }: LeaveOrganizationDialogProps) {
39
+ const {
40
+ authClient,
41
+ hooks: { useListOrganizations },
42
+ localization: contextLocalization,
43
+ toast
44
+ } = useContext(AuthUIContext)
45
+
46
+ const localization = useMemo(
47
+ () => ({ ...contextLocalization, ...localizationProp }),
48
+ [contextLocalization, localizationProp]
49
+ )
50
+
51
+ const { refetch: refetchOrganizations } = useListOrganizations()
52
+
53
+ const [isLeaving, setIsLeaving] = useState(false)
54
+
55
+ const handleLeaveOrganization = async () => {
56
+ setIsLeaving(true)
57
+
58
+ try {
59
+ await authClient.organization.leave({
60
+ organizationId: organization.id,
61
+ fetchOptions: { throw: true }
62
+ })
63
+
64
+ await refetchOrganizations?.()
65
+
66
+ toast({
67
+ variant: "success",
68
+ message: localization.LEAVE_ORGANIZATION_SUCCESS
69
+ })
70
+
71
+ onOpenChange?.(false)
72
+ } catch (error) {
73
+ toast({
74
+ variant: "error",
75
+ message: getLocalizedError({ error, localization })
76
+ })
77
+ }
78
+
79
+ setIsLeaving(false)
80
+ }
81
+
82
+ return (
83
+ <Dialog onOpenChange={onOpenChange} {...props}>
84
+ <DialogContent
85
+ className={classNames?.dialog?.content}
86
+ onOpenAutoFocus={(e) => e.preventDefault()}
87
+ >
88
+ <DialogHeader className={classNames?.dialog?.header}>
89
+ <DialogTitle
90
+ className={cn("text-lg md:text-xl", classNames?.title)}
91
+ >
92
+ {localization.LEAVE_ORGANIZATION}
93
+ </DialogTitle>
94
+
95
+ <DialogDescription
96
+ className={cn(
97
+ "text-xs md:text-sm",
98
+ classNames?.description
99
+ )}
100
+ >
101
+ {localization.LEAVE_ORGANIZATION_CONFIRM}
102
+ </DialogDescription>
103
+ </DialogHeader>
104
+
105
+ <Card
106
+ className={cn(
107
+ "my-2 flex-row p-4",
108
+ className,
109
+ classNames?.cell
110
+ )}
111
+ >
112
+ <OrganizationCellView
113
+ organization={organization}
114
+ localization={localization}
115
+ />
116
+ </Card>
117
+
118
+ <DialogFooter className={classNames?.dialog?.footer}>
119
+ <Button
120
+ type="button"
121
+ variant="outline"
122
+ onClick={() => onOpenChange?.(false)}
123
+ className={cn(
124
+ classNames?.button,
125
+ classNames?.outlineButton
126
+ )}
127
+ disabled={isLeaving}
128
+ >
129
+ {localization.CANCEL}
130
+ </Button>
131
+
132
+ <Button
133
+ type="button"
134
+ variant="destructive"
135
+ onClick={handleLeaveOrganization}
136
+ className={cn(
137
+ classNames?.button,
138
+ classNames?.destructiveButton
139
+ )}
140
+ disabled={isLeaving}
141
+ >
142
+ {isLeaving && <Loader2 className="animate-spin" />}
143
+
144
+ {localization.LEAVE_ORGANIZATION}
145
+ </Button>
146
+ </DialogFooter>
147
+ </DialogContent>
148
+ </Dialog>
149
+ )
150
+ }
@@ -0,0 +1,187 @@
1
+ "use client"
2
+
3
+ import type { User } from "better-auth"
4
+ import type { Member } from "better-auth/plugins/organization"
5
+ import { EllipsisIcon, UserCogIcon, UserXIcon } from "lucide-react"
6
+ import { useContext, useState } from "react"
7
+
8
+ import { AuthUIContext } from "../../lib/auth-ui-provider"
9
+ import { cn } from "../../lib/utils"
10
+ import type { AuthLocalization } from "../../localization/auth-localization"
11
+ import type { SettingsCardClassNames } from "../settings/shared/settings-card"
12
+ import { Button } from "../ui/button"
13
+ import { Card } from "../ui/card"
14
+ import {
15
+ DropdownMenu,
16
+ DropdownMenuContent,
17
+ DropdownMenuItem,
18
+ DropdownMenuTrigger
19
+ } from "../ui/dropdown-menu"
20
+ import { UserView } from "../user-view"
21
+ import { LeaveOrganizationDialog } from "./leave-organization-dialog"
22
+ import { RemoveMemberDialog } from "./remove-member-dialog"
23
+ import { UpdateMemberRoleDialog } from "./update-member-role-dialog"
24
+
25
+ export interface MemberCellProps {
26
+ className?: string
27
+ classNames?: SettingsCardClassNames
28
+ member: Member & { user?: Partial<User> | null }
29
+ localization?: AuthLocalization
30
+ hideActions?: boolean
31
+ }
32
+
33
+ export function MemberCell({
34
+ className,
35
+ classNames,
36
+ member,
37
+ localization: localizationProp,
38
+ hideActions
39
+ }: MemberCellProps) {
40
+ const {
41
+ organization: organizationOptions,
42
+ hooks: {
43
+ useListMembers,
44
+ useSession,
45
+ useListOrganizations,
46
+ useHasPermission
47
+ },
48
+ localization: contextLocalization
49
+ } = useContext(AuthUIContext)
50
+ const localization = { ...contextLocalization, ...localizationProp }
51
+
52
+ const { data: sessionData } = useSession()
53
+ const [removeDialogOpen, setRemoveDialogOpen] = useState(false)
54
+ const [leaveDialogOpen, setLeaveDialogOpen] = useState(false)
55
+ const [updateRoleDialogOpen, setUpdateRoleDialogOpen] = useState(false)
56
+
57
+ const builtInRoles = [
58
+ { role: "owner", label: localization.OWNER },
59
+ { role: "admin", label: localization.ADMIN },
60
+ { role: "member", label: localization.MEMBER }
61
+ ]
62
+
63
+ const { data } = useListMembers({
64
+ query: { organizationId: member.organizationId }
65
+ })
66
+
67
+ const members = data?.members
68
+
69
+ const myRole = members?.find(
70
+ (m) => m.user?.id === sessionData?.user.id
71
+ )?.role
72
+ const roles = [...builtInRoles, ...(organizationOptions?.customRoles || [])]
73
+ const role = roles.find((r) => r.role === member.role)
74
+
75
+ const isSelf = sessionData?.user.id === member?.userId
76
+
77
+ const { data: organizations } = useListOrganizations()
78
+ const organization = organizations?.find(
79
+ (org) => org.id === member.organizationId
80
+ )
81
+
82
+ const { data: hasPermissionToUpdateMember } = useHasPermission({
83
+ organizationId: member.organizationId,
84
+ permission: { member: ["update"] }
85
+ })
86
+
87
+ return (
88
+ <>
89
+ <Card
90
+ className={cn(
91
+ "flex-row items-center p-4",
92
+ className,
93
+ classNames?.cell
94
+ )}
95
+ >
96
+ <UserView
97
+ user={member.user}
98
+ localization={localization}
99
+ className="flex-1"
100
+ />
101
+
102
+ <span className="text-xs opacity-70">{role?.label}</span>
103
+
104
+ {!hideActions &&
105
+ (isSelf ||
106
+ member.role !== "owner" ||
107
+ myRole === "owner") && (
108
+ <DropdownMenu>
109
+ <DropdownMenuTrigger asChild>
110
+ <Button
111
+ className={cn(
112
+ "relative ms-auto",
113
+ classNames?.button,
114
+ classNames?.outlineButton
115
+ )}
116
+ size="icon"
117
+ type="button"
118
+ variant="outline"
119
+ >
120
+ <EllipsisIcon
121
+ className={classNames?.icon}
122
+ />
123
+ </Button>
124
+ </DropdownMenuTrigger>
125
+
126
+ <DropdownMenuContent
127
+ onCloseAutoFocus={(e) => e.preventDefault()}
128
+ >
129
+ {hasPermissionToUpdateMember?.success && (
130
+ <DropdownMenuItem
131
+ onClick={() =>
132
+ setUpdateRoleDialogOpen(true)
133
+ }
134
+ >
135
+ <UserCogIcon
136
+ className={classNames?.icon}
137
+ />
138
+ {localization?.UPDATE_ROLE}
139
+ </DropdownMenuItem>
140
+ )}
141
+
142
+ <DropdownMenuItem
143
+ onClick={() =>
144
+ isSelf
145
+ ? setLeaveDialogOpen(true)
146
+ : setRemoveDialogOpen(true)
147
+ }
148
+ variant="destructive"
149
+ >
150
+ <UserXIcon className={classNames?.icon} />
151
+ {isSelf
152
+ ? localization?.LEAVE_ORGANIZATION
153
+ : localization?.REMOVE_MEMBER}
154
+ </DropdownMenuItem>
155
+ </DropdownMenuContent>
156
+ </DropdownMenu>
157
+ )}
158
+ </Card>
159
+
160
+ <RemoveMemberDialog
161
+ open={removeDialogOpen}
162
+ onOpenChange={setRemoveDialogOpen}
163
+ member={member}
164
+ classNames={classNames}
165
+ localization={localization}
166
+ />
167
+
168
+ {organization && (
169
+ <LeaveOrganizationDialog
170
+ open={leaveDialogOpen}
171
+ onOpenChange={setLeaveDialogOpen}
172
+ organization={organization}
173
+ classNames={classNames}
174
+ localization={localization}
175
+ />
176
+ )}
177
+
178
+ <UpdateMemberRoleDialog
179
+ open={updateRoleDialogOpen}
180
+ onOpenChange={setUpdateRoleDialogOpen}
181
+ member={member}
182
+ classNames={classNames}
183
+ localization={localization}
184
+ />
185
+ </>
186
+ )
187
+ }
@@ -0,0 +1,122 @@
1
+ "use client"
2
+
3
+ import type { Organization } from "better-auth/plugins/organization"
4
+ import { useContext } from "react"
5
+
6
+ import { AuthUIContext } from "../../lib/auth-ui-provider"
7
+ import { cn } from "../../lib/utils"
8
+ import type { AuthLocalization } from "../../localization/auth-localization"
9
+ import { Skeleton } from "../ui/skeleton"
10
+ import {
11
+ OrganizationLogo,
12
+ type OrganizationLogoClassNames
13
+ } from "./organization-logo"
14
+
15
+ export interface OrganizationViewClassNames {
16
+ base?: string
17
+ avatar?: OrganizationLogoClassNames
18
+ content?: string
19
+ title?: string
20
+ subtitle?: string
21
+ skeleton?: string
22
+ }
23
+
24
+ export interface OrganizationViewProps {
25
+ className?: string
26
+ classNames?: OrganizationViewClassNames
27
+ isPending?: boolean
28
+ size?: "sm" | "default" | "lg" | null
29
+ organization?: Organization | null
30
+ /**
31
+ * @default authLocalization
32
+ * @remarks `AuthLocalization`
33
+ */
34
+ localization?: AuthLocalization
35
+ }
36
+
37
+ export function OrganizationCellView({
38
+ className,
39
+ classNames,
40
+ isPending,
41
+ size,
42
+ organization,
43
+ localization: propLocalization
44
+ }: OrganizationViewProps) {
45
+ const { localization: contextLocalization } = useContext(AuthUIContext)
46
+
47
+ const localization = { ...contextLocalization, ...propLocalization }
48
+
49
+ return (
50
+ <div
51
+ className={cn(
52
+ "flex items-center gap-2 truncate",
53
+ className,
54
+ classNames?.base
55
+ )}
56
+ >
57
+ <OrganizationLogo
58
+ className={cn(size !== "sm" && "my-0.5")}
59
+ classNames={classNames?.avatar}
60
+ isPending={isPending}
61
+ localization={localization}
62
+ organization={organization}
63
+ size={size}
64
+ />
65
+
66
+ <div
67
+ className={cn(
68
+ "flex flex-col truncate text-left leading-tight",
69
+ classNames?.content
70
+ )}
71
+ >
72
+ {isPending ? (
73
+ <>
74
+ <Skeleton
75
+ className={cn(
76
+ "max-w-full",
77
+ size === "lg" ? "h-4.5 w-32" : "h-3.5 w-24",
78
+ classNames?.title,
79
+ classNames?.skeleton
80
+ )}
81
+ />
82
+
83
+ {size !== "sm" && (
84
+ <Skeleton
85
+ className={cn(
86
+ "mt-1.5 max-w-full",
87
+ size === "lg" ? "h-3.5 w-24" : "h-3 w-16",
88
+ classNames?.subtitle,
89
+ classNames?.skeleton
90
+ )}
91
+ />
92
+ )}
93
+ </>
94
+ ) : (
95
+ <>
96
+ <span
97
+ className={cn(
98
+ "truncate font-semibold",
99
+ size === "lg" ? "text-base" : "text-sm",
100
+ classNames?.title
101
+ )}
102
+ >
103
+ {organization?.name || localization?.ORGANIZATION}
104
+ </span>
105
+
106
+ {size !== "sm" && organization?.slug && (
107
+ <span
108
+ className={cn(
109
+ "truncate opacity-70",
110
+ size === "lg" ? "text-sm" : "text-xs",
111
+ classNames?.subtitle
112
+ )}
113
+ >
114
+ {organization.slug}
115
+ </span>
116
+ )}
117
+ </>
118
+ )}
119
+ </div>
120
+ </div>
121
+ )
122
+ }
@@ -0,0 +1,154 @@
1
+ "use client"
2
+
3
+ import type { Organization } from "better-auth/plugins/organization"
4
+ import { EllipsisIcon, Loader2, LogOutIcon, SettingsIcon } from "lucide-react"
5
+ import { useCallback, useContext, useMemo, useState } from "react"
6
+
7
+ import { AuthUIContext } from "../../lib/auth-ui-provider"
8
+ import { cn, getLocalizedError } from "../../lib/utils"
9
+ import type { AuthLocalization } from "../../localization/auth-localization"
10
+ import type { SettingsCardClassNames } from "../settings/shared/settings-card"
11
+ import { Button } from "../ui/button"
12
+ import { Card } from "../ui/card"
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuContent,
16
+ DropdownMenuItem,
17
+ DropdownMenuTrigger
18
+ } from "../ui/dropdown-menu"
19
+ import { LeaveOrganizationDialog } from "./leave-organization-dialog"
20
+ import { OrganizationCellView } from "./organization-cell-view"
21
+
22
+ export interface OrganizationCellProps {
23
+ className?: string
24
+ classNames?: SettingsCardClassNames
25
+ organization: Organization
26
+ localization?: AuthLocalization
27
+ }
28
+
29
+ export function OrganizationCell({
30
+ className,
31
+ classNames,
32
+ organization,
33
+ localization: localizationProp
34
+ }: OrganizationCellProps) {
35
+ const {
36
+ authClient,
37
+ localization: contextLocalization,
38
+ organization: organizationOptions,
39
+ navigate,
40
+ toast
41
+ } = useContext(AuthUIContext)
42
+
43
+ const localization = useMemo(
44
+ () => ({ ...contextLocalization, ...localizationProp }),
45
+ [contextLocalization, localizationProp]
46
+ )
47
+
48
+ const { pathMode } = organizationOptions || {}
49
+
50
+ const [isLeaveDialogOpen, setIsLeaveDialogOpen] = useState(false)
51
+ const [isManagingOrganization, setIsManagingOrganization] = useState(false)
52
+
53
+ const handleManageOrganization = useCallback(async () => {
54
+ setIsManagingOrganization(true)
55
+
56
+ if (pathMode === "slug") {
57
+ navigate(
58
+ `${organizationOptions?.basePath}/${organization.slug}/${organizationOptions?.viewPaths.SETTINGS}`
59
+ )
60
+
61
+ return
62
+ }
63
+
64
+ try {
65
+ await authClient.organization.setActive({
66
+ organizationId: organization.id,
67
+ fetchOptions: {
68
+ throw: true
69
+ }
70
+ })
71
+
72
+ navigate(
73
+ `${organizationOptions?.basePath}/${organizationOptions?.viewPaths?.SETTINGS}`
74
+ )
75
+ } catch (error) {
76
+ toast({
77
+ variant: "error",
78
+ message: getLocalizedError({ error, localization })
79
+ })
80
+
81
+ setIsManagingOrganization(false)
82
+ }
83
+ }, [
84
+ authClient,
85
+ organization.id,
86
+ organizationOptions?.basePath,
87
+ organizationOptions?.viewPaths?.SETTINGS,
88
+ organization.slug,
89
+ pathMode,
90
+ navigate,
91
+ toast,
92
+ localization
93
+ ])
94
+
95
+ return (
96
+ <>
97
+ <Card className={cn("flex-row p-4", className, classNames?.cell)}>
98
+ <OrganizationCellView
99
+ organization={organization}
100
+ localization={localization}
101
+ />
102
+
103
+ <DropdownMenu>
104
+ <DropdownMenuTrigger asChild>
105
+ <Button
106
+ className={cn(
107
+ "relative ms-auto",
108
+ classNames?.button,
109
+ classNames?.outlineButton
110
+ )}
111
+ disabled={isManagingOrganization}
112
+ size="icon"
113
+ type="button"
114
+ variant="outline"
115
+ >
116
+ {isManagingOrganization ? (
117
+ <Loader2 className="animate-spin" />
118
+ ) : (
119
+ <EllipsisIcon className={classNames?.icon} />
120
+ )}
121
+ </Button>
122
+ </DropdownMenuTrigger>
123
+
124
+ <DropdownMenuContent>
125
+ <DropdownMenuItem
126
+ onClick={handleManageOrganization}
127
+ disabled={isManagingOrganization}
128
+ >
129
+ <SettingsIcon className={classNames?.icon} />
130
+
131
+ {localization.MANAGE_ORGANIZATION}
132
+ </DropdownMenuItem>
133
+
134
+ <DropdownMenuItem
135
+ onClick={() => setIsLeaveDialogOpen(true)}
136
+ variant="destructive"
137
+ >
138
+ <LogOutIcon className={classNames?.icon} />
139
+
140
+ {localization.LEAVE_ORGANIZATION}
141
+ </DropdownMenuItem>
142
+ </DropdownMenuContent>
143
+ </DropdownMenu>
144
+ </Card>
145
+
146
+ <LeaveOrganizationDialog
147
+ open={isLeaveDialogOpen}
148
+ onOpenChange={setIsLeaveDialogOpen}
149
+ organization={organization}
150
+ localization={localization}
151
+ />
152
+ </>
153
+ )
154
+ }