@erikey/react 0.4.26 → 0.4.28

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 (145) hide show
  1. package/dist/index.mjs +1 -1
  2. package/dist/index.mjs.map +1 -1
  3. package/package.json +2 -1
  4. package/src/__tests__/auth-client.test.ts +105 -0
  5. package/src/__tests__/security/localStorage-encryption.test.ts +171 -0
  6. package/src/auth-client.ts +158 -0
  7. package/src/dashboard-client.ts +60 -0
  8. package/src/index.ts +88 -0
  9. package/src/kv-client.ts +316 -0
  10. package/src/lib/cross-origin-auth.ts +99 -0
  11. package/src/stubs/captcha.ts +24 -0
  12. package/src/stubs/hashes.ts +16 -0
  13. package/src/stubs/index.ts +17 -0
  14. package/src/stubs/passkey.ts +12 -0
  15. package/src/stubs/qr-code.ts +10 -0
  16. package/src/stubs/query.ts +16 -0
  17. package/src/stubs/realtime.ts +17 -0
  18. package/src/stubs/use-sync-external-store.ts +12 -0
  19. package/src/styles.css +141 -0
  20. package/src/types.ts +14 -0
  21. package/src/ui/components/auth/auth-callback.tsx +36 -0
  22. package/src/ui/components/auth/auth-form.tsx +310 -0
  23. package/src/ui/components/auth/auth-view.tsx +435 -0
  24. package/src/ui/components/auth/email-otp-button.tsx +53 -0
  25. package/src/ui/components/auth/forms/email-otp-form.tsx +312 -0
  26. package/src/ui/components/auth/forms/email-verification-form.tsx +271 -0
  27. package/src/ui/components/auth/forms/forgot-password-form.tsx +173 -0
  28. package/src/ui/components/auth/forms/magic-link-form.tsx +196 -0
  29. package/src/ui/components/auth/forms/recover-account-form.tsx +143 -0
  30. package/src/ui/components/auth/forms/reset-password-form.tsx +220 -0
  31. package/src/ui/components/auth/forms/sign-in-form.tsx +323 -0
  32. package/src/ui/components/auth/forms/sign-up-form.tsx +820 -0
  33. package/src/ui/components/auth/forms/two-factor-form.tsx +381 -0
  34. package/src/ui/components/auth/magic-link-button.tsx +54 -0
  35. package/src/ui/components/auth/one-tap.tsx +53 -0
  36. package/src/ui/components/auth/otp-input-group.tsx +65 -0
  37. package/src/ui/components/auth/passkey-button.tsx +91 -0
  38. package/src/ui/components/auth/provider-button.tsx +155 -0
  39. package/src/ui/components/auth/sign-out.tsx +25 -0
  40. package/src/ui/components/auth/wallet-button.tsx +192 -0
  41. package/src/ui/components/auth-loading.tsx +21 -0
  42. package/src/ui/components/captcha/captcha.tsx +91 -0
  43. package/src/ui/components/captcha/recaptcha-badge.tsx +61 -0
  44. package/src/ui/components/captcha/recaptcha-v2.tsx +58 -0
  45. package/src/ui/components/captcha/recaptcha-v3.tsx +73 -0
  46. package/src/ui/components/email/email-template.tsx +216 -0
  47. package/src/ui/components/form-error.tsx +27 -0
  48. package/src/ui/components/password-input.tsx +56 -0
  49. package/src/ui/components/provider-icons.tsx +404 -0
  50. package/src/ui/components/redirect-to-sign-in.tsx +16 -0
  51. package/src/ui/components/redirect-to-sign-up.tsx +16 -0
  52. package/src/ui/components/signed-in.tsx +20 -0
  53. package/src/ui/components/signed-out.tsx +20 -0
  54. package/src/ui/components/ui/alert.tsx +66 -0
  55. package/src/ui/components/ui/button.tsx +70 -0
  56. package/src/ui/components/ui/card.tsx +92 -0
  57. package/src/ui/components/ui/checkbox.tsx +66 -0
  58. package/src/ui/components/ui/field.tsx +248 -0
  59. package/src/ui/components/ui/form.tsx +165 -0
  60. package/src/ui/components/ui/input-otp.tsx +77 -0
  61. package/src/ui/components/ui/input.tsx +21 -0
  62. package/src/ui/components/ui/label.tsx +23 -0
  63. package/src/ui/components/ui/separator.tsx +34 -0
  64. package/src/ui/components/ui/skeleton.tsx +13 -0
  65. package/src/ui/components/ui/textarea.tsx +18 -0
  66. package/src/ui/components/user-avatar.tsx +151 -0
  67. package/src/ui/hooks/use-auth-data.ts +193 -0
  68. package/src/ui/hooks/use-authenticate.ts +64 -0
  69. package/src/ui/hooks/use-captcha.tsx +151 -0
  70. package/src/ui/hooks/use-hydrated.ts +13 -0
  71. package/src/ui/hooks/use-lang.ts +32 -0
  72. package/src/ui/hooks/use-success-transition.ts +41 -0
  73. package/src/ui/hooks/use-theme.ts +39 -0
  74. package/src/ui/index.ts +46 -0
  75. package/src/ui/instantdb.ts +1 -0
  76. package/src/ui/lib/auth-data-cache.ts +90 -0
  77. package/src/ui/lib/auth-ui-provider.tsx +769 -0
  78. package/src/ui/lib/gravatar-utils.ts +58 -0
  79. package/src/ui/lib/image-utils.ts +55 -0
  80. package/src/ui/lib/instantdb/model-names.ts +24 -0
  81. package/src/ui/lib/instantdb/use-instant-options.ts +98 -0
  82. package/src/ui/lib/instantdb/use-list-accounts.ts +38 -0
  83. package/src/ui/lib/instantdb/use-list-sessions.ts +53 -0
  84. package/src/ui/lib/instantdb/use-session.ts +55 -0
  85. package/src/ui/lib/social-providers.ts +150 -0
  86. package/src/ui/lib/tanstack/auth-ui-provider-tanstack.tsx +49 -0
  87. package/src/ui/lib/tanstack/use-tanstack-options.ts +112 -0
  88. package/src/ui/lib/triplit/model-names.ts +24 -0
  89. package/src/ui/lib/triplit/use-conditional-query.ts +82 -0
  90. package/src/ui/lib/triplit/use-list-accounts.ts +31 -0
  91. package/src/ui/lib/triplit/use-list-sessions.ts +33 -0
  92. package/src/ui/lib/triplit/use-session.ts +42 -0
  93. package/src/ui/lib/triplit/use-triplit-hooks.ts +68 -0
  94. package/src/ui/lib/triplit/use-triplit-token.ts +44 -0
  95. package/src/ui/lib/utils.ts +119 -0
  96. package/src/ui/lib/view-paths.ts +61 -0
  97. package/src/ui/lib/wallet.ts +129 -0
  98. package/src/ui/localization/admin-error-codes.ts +20 -0
  99. package/src/ui/localization/anonymous-error-codes.ts +6 -0
  100. package/src/ui/localization/api-key-error-codes.ts +32 -0
  101. package/src/ui/localization/auth-localization.ts +865 -0
  102. package/src/ui/localization/base-error-codes.ts +27 -0
  103. package/src/ui/localization/captcha-error-codes.ts +17 -0
  104. package/src/ui/localization/email-otp-error-codes.ts +7 -0
  105. package/src/ui/localization/generic-oauth-error-codes.ts +3 -0
  106. package/src/ui/localization/haveibeenpwned-error-codes.ts +4 -0
  107. package/src/ui/localization/multi-session-error-codes.ts +3 -0
  108. package/src/ui/localization/organization-error-codes.ts +57 -0
  109. package/src/ui/localization/passkey-error-codes.ts +10 -0
  110. package/src/ui/localization/phone-number-error-codes.ts +10 -0
  111. package/src/ui/localization/stripe-localization.ts +12 -0
  112. package/src/ui/localization/team-error-codes.ts +12 -0
  113. package/src/ui/localization/two-factor-error-codes.ts +12 -0
  114. package/src/ui/localization/username-error-codes.ts +9 -0
  115. package/src/ui/server.ts +4 -0
  116. package/src/ui/style.css +146 -0
  117. package/src/ui/tanstack.ts +1 -0
  118. package/src/ui/triplit.ts +1 -0
  119. package/src/ui/types/account-options.ts +35 -0
  120. package/src/ui/types/additional-fields.ts +21 -0
  121. package/src/ui/types/any-auth-client.ts +6 -0
  122. package/src/ui/types/api-key.ts +9 -0
  123. package/src/ui/types/auth-client.ts +41 -0
  124. package/src/ui/types/auth-hooks.ts +81 -0
  125. package/src/ui/types/auth-mutators.ts +21 -0
  126. package/src/ui/types/avatar-options.ts +29 -0
  127. package/src/ui/types/captcha-options.ts +32 -0
  128. package/src/ui/types/captcha-provider.ts +7 -0
  129. package/src/ui/types/credentials-options.ts +38 -0
  130. package/src/ui/types/delete-user-options.ts +7 -0
  131. package/src/ui/types/email-verification-options.ts +7 -0
  132. package/src/ui/types/fetch-error.ts +6 -0
  133. package/src/ui/types/generic-oauth-options.ts +16 -0
  134. package/src/ui/types/gravatar-options.ts +21 -0
  135. package/src/ui/types/image.ts +7 -0
  136. package/src/ui/types/invitation.ts +10 -0
  137. package/src/ui/types/link.ts +7 -0
  138. package/src/ui/types/organization-options.ts +106 -0
  139. package/src/ui/types/password-validation.ts +16 -0
  140. package/src/ui/types/profile.ts +15 -0
  141. package/src/ui/types/refetch.ts +1 -0
  142. package/src/ui/types/render-toast.ts +9 -0
  143. package/src/ui/types/sign-up-options.ts +7 -0
  144. package/src/ui/types/social-options.ts +16 -0
  145. package/src/ui/types/team-options.ts +47 -0
@@ -0,0 +1,58 @@
1
+ import { sha256 } from "@noble/hashes/sha2.js"
2
+ import { bytesToHex } from "@noble/hashes/utils.js"
3
+ import type { GravatarOptions } from "../types/gravatar-options"
4
+
5
+ /**
6
+ * Generate a Gravatar URL for an email address
7
+ * @param email - Email address
8
+ * @param options - Gravatar options
9
+ * @returns Gravatar URL or null if email is invalid
10
+ */
11
+ export function getGravatarUrl(
12
+ email?: string | null,
13
+ options?: GravatarOptions
14
+ ): string | null {
15
+ if (!email) return null
16
+
17
+ try {
18
+ // Normalize email: trim and lowercase
19
+ const normalizedEmail = email.trim().toLowerCase()
20
+ // sha256 expects Uint8Array, so encode string to Uint8Array
21
+ const encoder = new TextEncoder()
22
+ const emailBytes = encoder.encode(normalizedEmail)
23
+ const hash = bytesToHex(sha256(emailBytes))
24
+ const extension = options?.jpg ? ".jpg" : ""
25
+ let url = `https://gravatar.com/avatar/${hash}${extension}`
26
+
27
+ const params = new URLSearchParams()
28
+
29
+ // Add size parameter
30
+ if (options?.size) {
31
+ params.append(
32
+ "s",
33
+ Math.min(Math.max(options.size, 1), 2048).toString()
34
+ )
35
+ }
36
+
37
+ // Add default image parameter
38
+ if (options?.d) {
39
+ params.append("d", options.d)
40
+ }
41
+
42
+ // Add force default parameter
43
+ if (options?.forceDefault) {
44
+ params.append("f", "y")
45
+ }
46
+
47
+ // Append parameters if any
48
+ const queryString = params.toString()
49
+ if (queryString) {
50
+ url += `?${queryString}`
51
+ }
52
+
53
+ return url
54
+ } catch (error) {
55
+ console.error("Error generating Gravatar URL:", error)
56
+ return null
57
+ }
58
+ }
@@ -0,0 +1,55 @@
1
+ export async function resizeAndCropImage(
2
+ file: File,
3
+ name: string,
4
+ size: number,
5
+ extension: string
6
+ ): Promise<File> {
7
+ const image = await loadImage(file)
8
+
9
+ const canvas = document.createElement("canvas")
10
+ canvas.width = canvas.height = size
11
+
12
+ const ctx = canvas.getContext("2d")
13
+
14
+ const minEdge = Math.min(image.width, image.height)
15
+
16
+ const sx = (image.width - minEdge) / 2
17
+ const sy = (image.height - minEdge) / 2
18
+ const sWidth = minEdge
19
+ const sHeight = minEdge
20
+
21
+ ctx?.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, size, size)
22
+
23
+ const resizedImageBlob = await new Promise<Blob | null>((resolve) =>
24
+ canvas.toBlob(resolve, `image/${extension}`)
25
+ )
26
+
27
+ return new File([resizedImageBlob as BlobPart], `${name}.${extension}`, {
28
+ type: `image/${extension}`
29
+ })
30
+ }
31
+
32
+ async function loadImage(file: File): Promise<HTMLImageElement> {
33
+ return new Promise((resolve, reject) => {
34
+ const image = new Image()
35
+ const reader = new FileReader()
36
+
37
+ reader.onload = (e) => {
38
+ image.src = e.target?.result as string
39
+ }
40
+
41
+ image.onload = () => resolve(image)
42
+ image.onerror = (err) => reject(err)
43
+
44
+ reader.readAsDataURL(file)
45
+ })
46
+ }
47
+
48
+ export async function fileToBase64(file: File): Promise<string> {
49
+ return new Promise((resolve, reject) => {
50
+ const reader = new FileReader()
51
+ reader.onloadend = () => resolve(reader.result as string)
52
+ reader.onerror = reject
53
+ reader.readAsDataURL(file)
54
+ })
55
+ }
@@ -0,0 +1,24 @@
1
+ const namespaces = [
2
+ "user",
3
+ "session",
4
+ "account",
5
+ "passkey",
6
+ "twoFactor"
7
+ ] as const
8
+ type Namespace = (typeof namespaces)[number]
9
+
10
+ export type ModelNames = {
11
+ [key in Namespace]: string
12
+ }
13
+
14
+ export const getModelName = ({
15
+ namespace,
16
+ modelNames,
17
+ usePlural = false
18
+ }: {
19
+ namespace: Namespace
20
+ modelNames?: Partial<ModelNames>
21
+ usePlural?: boolean
22
+ }) => {
23
+ return modelNames?.[namespace] || `${namespace}${usePlural ? "s" : ""}`
24
+ }
@@ -0,0 +1,98 @@
1
+ import type { InstantReactWebDatabase } from "@instantdb/react"
2
+ import type { User } from "better-auth"
3
+ import { useMemo } from "react"
4
+
5
+ import type { Session } from "../../types/auth-client"
6
+ import type { AuthHooks } from "../../types/auth-hooks"
7
+ import type { AuthMutators } from "../../types/auth-mutators"
8
+ import type { Refetch } from "../../types/refetch"
9
+ import { getModelName } from "./model-names"
10
+ import { useListAccounts } from "./use-list-accounts"
11
+ import { useListSessions } from "./use-list-sessions"
12
+ import { useSession } from "./use-session"
13
+
14
+ const namespaces = ["user", "session", "account", "passkey"] as const
15
+ type Namespace = (typeof namespaces)[number]
16
+
17
+ type ModelNames = {
18
+ [key in Namespace]: string
19
+ }
20
+
21
+ export interface UseInstantOptionsProps {
22
+ // biome-ignore lint/suspicious/noExplicitAny: ignore
23
+ db: InstantReactWebDatabase<any>
24
+ modelNames?: Partial<ModelNames>
25
+ usePlural?: boolean
26
+ sessionData?: { user: User; session: Session }
27
+ refetch?: Refetch
28
+ user?: { id: string } | null
29
+ isPending: boolean
30
+ }
31
+
32
+ export function useInstantOptions({
33
+ db,
34
+ usePlural = true,
35
+ modelNames,
36
+ sessionData,
37
+ isPending,
38
+ user
39
+ }: UseInstantOptionsProps) {
40
+ const userId = user?.id || sessionData?.user.id
41
+
42
+ const hooks = useMemo(() => {
43
+ return {
44
+ useSession: () =>
45
+ useSession({
46
+ db,
47
+ modelNames,
48
+ usePlural,
49
+ sessionData,
50
+ isPending
51
+ }),
52
+ useListAccounts: () =>
53
+ useListAccounts({
54
+ db,
55
+ modelNames,
56
+ usePlural,
57
+ sessionData,
58
+ isPending
59
+ }),
60
+ useListSessions: () =>
61
+ useListSessions({
62
+ db,
63
+ modelNames,
64
+ usePlural,
65
+ sessionData,
66
+ isPending
67
+ })
68
+ } as AuthHooks
69
+ }, [db, modelNames, usePlural, sessionData, isPending])
70
+
71
+ const mutators = useMemo(() => {
72
+ return {
73
+ updateUser: async (data) => {
74
+ if (!userId) {
75
+ throw new Error("Unauthenticated")
76
+ }
77
+
78
+ const modelName = getModelName({
79
+ namespace: "user",
80
+ modelNames,
81
+ usePlural
82
+ })
83
+
84
+ db.transact([
85
+ db.tx[modelName][userId].update({
86
+ ...data,
87
+ updatedAt: Date.now()
88
+ })
89
+ ])
90
+ }
91
+ } as AuthMutators
92
+ }, [db, userId, modelNames, usePlural])
93
+
94
+ return {
95
+ hooks,
96
+ mutators
97
+ }
98
+ }
@@ -0,0 +1,38 @@
1
+ import type { BetterFetchError } from "@better-fetch/fetch"
2
+ import type { Account } from "better-auth"
3
+ import { useMemo } from "react"
4
+ import type { AuthHooks } from "../../types/auth-hooks"
5
+ import { getModelName } from "./model-names"
6
+ import type { UseInstantOptionsProps } from "./use-instant-options"
7
+
8
+ export function useListAccounts({
9
+ db,
10
+ modelNames,
11
+ usePlural,
12
+ isPending
13
+ }: UseInstantOptionsProps): ReturnType<AuthHooks["useListAccounts"]> {
14
+ const { user: authUser, isLoading: authLoading } = db.useAuth()
15
+
16
+ const modelName = getModelName({
17
+ namespace: "account",
18
+ modelNames,
19
+ usePlural
20
+ })
21
+
22
+ const { data, isLoading, error } = db.useQuery(
23
+ authUser
24
+ ? { [modelName]: { $: { where: { userId: authUser?.id } } } }
25
+ : null
26
+ )
27
+
28
+ const accounts = useMemo(
29
+ () => data?.[modelName] as Account[],
30
+ [data, modelName]
31
+ )
32
+
33
+ return {
34
+ data: accounts,
35
+ isPending: !accounts && (isPending || authLoading || isLoading),
36
+ error: (error as BetterFetchError) || null
37
+ }
38
+ }
@@ -0,0 +1,53 @@
1
+ import type { Session } from "better-auth"
2
+ import { useMemo } from "react"
3
+ import type { AuthHooks } from "../../types/auth-hooks"
4
+ import { getModelName } from "./model-names"
5
+ import type { UseInstantOptionsProps } from "./use-instant-options"
6
+
7
+ export function useListSessions({
8
+ db,
9
+ modelNames,
10
+ usePlural,
11
+ isPending
12
+ }: UseInstantOptionsProps): ReturnType<AuthHooks["useListSessions"]> {
13
+ const { user: authUser, isLoading: authLoading } = db.useAuth()
14
+
15
+ const modelName = getModelName({
16
+ namespace: "session",
17
+ modelNames,
18
+ usePlural
19
+ })
20
+
21
+ const now = useMemo(() => Date.now(), [])
22
+
23
+ const { data, isLoading } = db.useQuery(
24
+ authUser
25
+ ? {
26
+ [modelName]: {
27
+ $: {
28
+ where: {
29
+ userId: authUser?.id,
30
+ expiresAt: { $gte: now }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ : null
36
+ )
37
+
38
+ const sessions = useMemo(() => {
39
+ if (data?.[modelName]) {
40
+ return data[modelName].map((session) => ({
41
+ ...session,
42
+ expiresAt: new Date(session.expiresAt as string),
43
+ createdAt: new Date(session.createdAt as string),
44
+ updatedAt: new Date(session.updatedAt as string)
45
+ })) as Session[]
46
+ }
47
+ }, [data, modelName])
48
+
49
+ return {
50
+ data: sessions,
51
+ isPending: !sessions && (isPending || authLoading || isLoading)
52
+ }
53
+ }
@@ -0,0 +1,55 @@
1
+ import type { BetterFetchError } from "better-auth/react"
2
+ import { useMemo } from "react"
3
+ import type { User } from "../../types/auth-client"
4
+ import type { AuthHooks } from "../../types/auth-hooks"
5
+ import { getModelName } from "./model-names"
6
+ import type { UseInstantOptionsProps } from "./use-instant-options"
7
+
8
+ export function useSession({
9
+ db,
10
+ sessionData,
11
+ isPending,
12
+ refetch,
13
+ usePlural,
14
+ modelNames
15
+ }: UseInstantOptionsProps): ReturnType<AuthHooks["useSession"]> {
16
+ const { user: authUser, error } = db.useAuth()
17
+
18
+ const modelName = getModelName({
19
+ namespace: "user",
20
+ modelNames,
21
+ usePlural
22
+ })
23
+
24
+ const { data } = db.useQuery(
25
+ authUser
26
+ ? { [modelName]: { $: { where: { id: authUser?.id } } } }
27
+ : null
28
+ )
29
+
30
+ const user = useMemo(() => {
31
+ if (data?.[modelName]?.length) {
32
+ const user = data[modelName][0]
33
+ return {
34
+ ...user,
35
+ createdAt: new Date(user.createdAt as string),
36
+ updatedAt: new Date(user.updatedAt as string)
37
+ } as User
38
+ }
39
+ return null
40
+ }, [data, modelName])
41
+
42
+ return {
43
+ data: sessionData
44
+ ? {
45
+ session: sessionData.session,
46
+ user: (sessionData?.user.id === user?.id
47
+ ? user
48
+ : sessionData.user) as User
49
+ }
50
+ : null,
51
+ isPending,
52
+ refetch: refetch || (() => {}),
53
+ error: (error as BetterFetchError) || null
54
+ }
55
+ }
@@ -0,0 +1,150 @@
1
+ import {
2
+ AppleIcon,
3
+ DiscordIcon,
4
+ DropboxIcon,
5
+ FacebookIcon,
6
+ GitHubIcon,
7
+ GitLabIcon,
8
+ GoogleIcon,
9
+ HuggingFaceIcon,
10
+ KickIcon,
11
+ LinearIcon,
12
+ LinkedInIcon,
13
+ MicrosoftIcon,
14
+ NotionIcon,
15
+ type ProviderIcon,
16
+ RedditIcon,
17
+ RobloxIcon,
18
+ SlackIcon,
19
+ SpotifyIcon,
20
+ TikTokIcon,
21
+ TwitchIcon,
22
+ VercelIcon,
23
+ VKIcon,
24
+ XIcon,
25
+ ZoomIcon
26
+ } from "../components/provider-icons"
27
+
28
+ export const socialProviders = [
29
+ {
30
+ provider: "apple",
31
+ name: "Apple",
32
+ icon: AppleIcon
33
+ },
34
+ {
35
+ provider: "discord",
36
+ name: "Discord",
37
+ icon: DiscordIcon
38
+ },
39
+ {
40
+ provider: "dropbox",
41
+ name: "Dropbox",
42
+ icon: DropboxIcon
43
+ },
44
+ {
45
+ provider: "facebook",
46
+ name: "Facebook",
47
+ icon: FacebookIcon
48
+ },
49
+ {
50
+ provider: "github",
51
+ name: "GitHub",
52
+ icon: GitHubIcon
53
+ },
54
+ {
55
+ provider: "gitlab",
56
+ name: "GitLab",
57
+ icon: GitLabIcon
58
+ },
59
+ {
60
+ provider: "google",
61
+ name: "Google",
62
+ icon: GoogleIcon
63
+ },
64
+ {
65
+ provider: "huggingface",
66
+ name: "Hugging Face",
67
+ icon: HuggingFaceIcon
68
+ },
69
+ {
70
+ provider: "kick",
71
+ name: "Kick",
72
+ icon: KickIcon
73
+ },
74
+ {
75
+ provider: "linear",
76
+ name: "Linear",
77
+ icon: LinearIcon
78
+ },
79
+ {
80
+ provider: "linkedin",
81
+ name: "LinkedIn",
82
+ icon: LinkedInIcon
83
+ },
84
+ {
85
+ provider: "microsoft",
86
+ name: "Microsoft",
87
+ icon: MicrosoftIcon
88
+ },
89
+ {
90
+ provider: "notion",
91
+ name: "Notion",
92
+ icon: NotionIcon
93
+ },
94
+ {
95
+ provider: "reddit",
96
+ name: "Reddit",
97
+ icon: RedditIcon
98
+ },
99
+ {
100
+ provider: "roblox",
101
+ name: "Roblox",
102
+ icon: RobloxIcon
103
+ },
104
+ {
105
+ provider: "slack",
106
+ name: "Slack",
107
+ icon: SlackIcon
108
+ },
109
+ {
110
+ provider: "spotify",
111
+ name: "Spotify",
112
+ icon: SpotifyIcon
113
+ },
114
+ {
115
+ provider: "tiktok",
116
+ name: "TikTok",
117
+ icon: TikTokIcon
118
+ },
119
+ {
120
+ provider: "twitch",
121
+ name: "Twitch",
122
+ icon: TwitchIcon
123
+ },
124
+ {
125
+ provider: "vercel",
126
+ name: "Vercel",
127
+ icon: VercelIcon
128
+ },
129
+ {
130
+ provider: "vk",
131
+ name: "VK",
132
+ icon: VKIcon
133
+ },
134
+ {
135
+ provider: "twitter",
136
+ name: "X",
137
+ icon: XIcon
138
+ },
139
+ {
140
+ provider: "zoom",
141
+ name: "Zoom",
142
+ icon: ZoomIcon
143
+ }
144
+ ] as const
145
+
146
+ export type Provider = {
147
+ provider: string
148
+ name: string
149
+ icon?: ProviderIcon
150
+ }
@@ -0,0 +1,49 @@
1
+ import { useCallback, useMemo } from "react"
2
+ import {
3
+ AuthUIProvider,
4
+ type AuthUIProviderProps
5
+ } from "../../lib/auth-ui-provider"
6
+ import { useTanstackOptions } from "./use-tanstack-options"
7
+
8
+ export function AuthUIProviderTanstack({
9
+ children,
10
+ authClient,
11
+ hooks: hooksProp,
12
+ mutators: mutatorsProp,
13
+ onSessionChange: onSessionChangeProp,
14
+ ...props
15
+ }: AuthUIProviderProps) {
16
+ const {
17
+ hooks: contextHooks,
18
+ mutators: contextMutators,
19
+ onSessionChange,
20
+ optimistic
21
+ } = useTanstackOptions({ authClient })
22
+
23
+ const hooks = useMemo(
24
+ () => ({ ...contextHooks, ...hooksProp }),
25
+ [contextHooks, hooksProp]
26
+ )
27
+ const mutators = useMemo(
28
+ () => ({ ...contextMutators, ...mutatorsProp }),
29
+ [contextMutators, mutatorsProp]
30
+ )
31
+
32
+ const onSessionChangeCallback = useCallback(async () => {
33
+ await onSessionChange()
34
+ await onSessionChangeProp?.()
35
+ }, [onSessionChangeProp, onSessionChange])
36
+
37
+ return (
38
+ <AuthUIProvider
39
+ authClient={authClient}
40
+ hooks={hooks}
41
+ mutators={mutators}
42
+ onSessionChange={onSessionChangeCallback}
43
+ optimistic={optimistic}
44
+ {...props}
45
+ >
46
+ {children}
47
+ </AuthUIProvider>
48
+ )
49
+ }
@@ -0,0 +1,112 @@
1
+ import {
2
+ AuthQueryContext,
3
+ createAuthHooks
4
+ } from "@daveyplate/better-auth-tanstack"
5
+ import { useIsRestoring, useQueryClient } from "@tanstack/react-query"
6
+ import { useCallback, useContext, useMemo } from "react"
7
+ import type { AuthHooks } from "../../types/auth-hooks"
8
+ import type { AuthMutators } from "../../types/auth-mutators"
9
+
10
+ export function useTanstackOptions({
11
+ authClient
12
+ }: {
13
+ // biome-ignore lint/suspicious/noExplicitAny: ignore
14
+ authClient: any
15
+ }) {
16
+ const {
17
+ useUnlinkAccount,
18
+ useUpdateUser,
19
+ useDeletePasskey,
20
+ useRevokeSession,
21
+ useRevokeDeviceSession,
22
+ useSetActiveSession
23
+ } = createAuthHooks(authClient)
24
+ const queryClient = useQueryClient()
25
+
26
+ const { mutateAsync: updateUserAsync } = useUpdateUser()
27
+ const { mutateAsync: deletePasskeyAsync } = useDeletePasskey()
28
+ const { mutateAsync: unlinkAccountAsync } = useUnlinkAccount()
29
+ const { mutateAsync: revokeSessionAsync } = useRevokeSession()
30
+ const { mutateAsync: revokeDeviceSessionAsync } = useRevokeDeviceSession()
31
+ const { setActiveSessionAsync } = useSetActiveSession()
32
+ const { sessionKey } = useContext(AuthQueryContext)
33
+
34
+ const hooks = useMemo(
35
+ () => ({
36
+ ...(createAuthHooks(authClient) as Partial<AuthHooks>),
37
+ useIsRestoring
38
+ }),
39
+ [authClient]
40
+ )
41
+
42
+ const mutators = useMemo(
43
+ () =>
44
+ ({
45
+ updateUser: async (params) => {
46
+ const { error } = await updateUserAsync({
47
+ ...params,
48
+ fetchOptions: { throw: false }
49
+ })
50
+ if (error) throw error
51
+ },
52
+ unlinkAccount: async (params) => {
53
+ const { error } = await unlinkAccountAsync({
54
+ ...params,
55
+ fetchOptions: { throw: false }
56
+ })
57
+ if (error) throw error
58
+ },
59
+ deletePasskey: async (params) => {
60
+ const { error } = await deletePasskeyAsync({
61
+ ...params,
62
+ fetchOptions: { throw: false }
63
+ })
64
+ if (error) throw error
65
+ },
66
+ revokeSession: async (params) => {
67
+ const { error } = await revokeSessionAsync({
68
+ ...params,
69
+ fetchOptions: { throw: false }
70
+ })
71
+ if (error) throw error
72
+ },
73
+ setActiveSession: async (params) => {
74
+ const { error } = await setActiveSessionAsync({
75
+ ...params,
76
+ fetchOptions: { throw: false }
77
+ })
78
+ if (error) throw error
79
+ },
80
+ revokeDeviceSession: async (params) => {
81
+ const { error } = await revokeDeviceSessionAsync({
82
+ ...params,
83
+ fetchOptions: { throw: false }
84
+ })
85
+ if (error) throw error
86
+ }
87
+ }) as AuthMutators,
88
+ [
89
+ updateUserAsync,
90
+ deletePasskeyAsync,
91
+ unlinkAccountAsync,
92
+ revokeSessionAsync,
93
+ revokeDeviceSessionAsync,
94
+ setActiveSessionAsync
95
+ ]
96
+ )
97
+
98
+ const onSessionChange = useCallback(async () => {
99
+ await queryClient.refetchQueries({ queryKey: sessionKey })
100
+
101
+ queryClient.invalidateQueries({
102
+ predicate: (query) => query.queryKey !== sessionKey
103
+ })
104
+ }, [queryClient, sessionKey])
105
+
106
+ return {
107
+ hooks,
108
+ mutators,
109
+ onSessionChange,
110
+ optimistic: true
111
+ }
112
+ }