@erikey/react 0.4.26 → 0.4.27

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 (143) hide show
  1. package/package.json +2 -1
  2. package/src/__tests__/auth-client.test.ts +105 -0
  3. package/src/__tests__/security/localStorage-encryption.test.ts +171 -0
  4. package/src/auth-client.ts +158 -0
  5. package/src/dashboard-client.ts +60 -0
  6. package/src/index.ts +88 -0
  7. package/src/kv-client.ts +316 -0
  8. package/src/lib/cross-origin-auth.ts +99 -0
  9. package/src/stubs/captcha.ts +24 -0
  10. package/src/stubs/hashes.ts +16 -0
  11. package/src/stubs/index.ts +17 -0
  12. package/src/stubs/passkey.ts +12 -0
  13. package/src/stubs/qr-code.ts +10 -0
  14. package/src/stubs/query.ts +16 -0
  15. package/src/stubs/realtime.ts +17 -0
  16. package/src/stubs/use-sync-external-store.ts +12 -0
  17. package/src/styles.css +141 -0
  18. package/src/types.ts +14 -0
  19. package/src/ui/components/auth/auth-callback.tsx +36 -0
  20. package/src/ui/components/auth/auth-form.tsx +310 -0
  21. package/src/ui/components/auth/auth-view.tsx +435 -0
  22. package/src/ui/components/auth/email-otp-button.tsx +53 -0
  23. package/src/ui/components/auth/forms/email-otp-form.tsx +312 -0
  24. package/src/ui/components/auth/forms/email-verification-form.tsx +271 -0
  25. package/src/ui/components/auth/forms/forgot-password-form.tsx +173 -0
  26. package/src/ui/components/auth/forms/magic-link-form.tsx +196 -0
  27. package/src/ui/components/auth/forms/recover-account-form.tsx +143 -0
  28. package/src/ui/components/auth/forms/reset-password-form.tsx +220 -0
  29. package/src/ui/components/auth/forms/sign-in-form.tsx +323 -0
  30. package/src/ui/components/auth/forms/sign-up-form.tsx +820 -0
  31. package/src/ui/components/auth/forms/two-factor-form.tsx +381 -0
  32. package/src/ui/components/auth/magic-link-button.tsx +54 -0
  33. package/src/ui/components/auth/one-tap.tsx +53 -0
  34. package/src/ui/components/auth/otp-input-group.tsx +65 -0
  35. package/src/ui/components/auth/passkey-button.tsx +91 -0
  36. package/src/ui/components/auth/provider-button.tsx +155 -0
  37. package/src/ui/components/auth/sign-out.tsx +25 -0
  38. package/src/ui/components/auth/wallet-button.tsx +192 -0
  39. package/src/ui/components/auth-loading.tsx +21 -0
  40. package/src/ui/components/captcha/captcha.tsx +91 -0
  41. package/src/ui/components/captcha/recaptcha-badge.tsx +61 -0
  42. package/src/ui/components/captcha/recaptcha-v2.tsx +58 -0
  43. package/src/ui/components/captcha/recaptcha-v3.tsx +73 -0
  44. package/src/ui/components/email/email-template.tsx +216 -0
  45. package/src/ui/components/form-error.tsx +27 -0
  46. package/src/ui/components/password-input.tsx +56 -0
  47. package/src/ui/components/provider-icons.tsx +404 -0
  48. package/src/ui/components/redirect-to-sign-in.tsx +16 -0
  49. package/src/ui/components/redirect-to-sign-up.tsx +16 -0
  50. package/src/ui/components/signed-in.tsx +20 -0
  51. package/src/ui/components/signed-out.tsx +20 -0
  52. package/src/ui/components/ui/alert.tsx +66 -0
  53. package/src/ui/components/ui/button.tsx +70 -0
  54. package/src/ui/components/ui/card.tsx +92 -0
  55. package/src/ui/components/ui/checkbox.tsx +66 -0
  56. package/src/ui/components/ui/field.tsx +248 -0
  57. package/src/ui/components/ui/form.tsx +165 -0
  58. package/src/ui/components/ui/input-otp.tsx +77 -0
  59. package/src/ui/components/ui/input.tsx +21 -0
  60. package/src/ui/components/ui/label.tsx +23 -0
  61. package/src/ui/components/ui/separator.tsx +34 -0
  62. package/src/ui/components/ui/skeleton.tsx +13 -0
  63. package/src/ui/components/ui/textarea.tsx +18 -0
  64. package/src/ui/components/user-avatar.tsx +151 -0
  65. package/src/ui/hooks/use-auth-data.ts +193 -0
  66. package/src/ui/hooks/use-authenticate.ts +64 -0
  67. package/src/ui/hooks/use-captcha.tsx +151 -0
  68. package/src/ui/hooks/use-hydrated.ts +13 -0
  69. package/src/ui/hooks/use-lang.ts +32 -0
  70. package/src/ui/hooks/use-success-transition.ts +41 -0
  71. package/src/ui/hooks/use-theme.ts +39 -0
  72. package/src/ui/index.ts +46 -0
  73. package/src/ui/instantdb.ts +1 -0
  74. package/src/ui/lib/auth-data-cache.ts +90 -0
  75. package/src/ui/lib/auth-ui-provider.tsx +769 -0
  76. package/src/ui/lib/gravatar-utils.ts +58 -0
  77. package/src/ui/lib/image-utils.ts +55 -0
  78. package/src/ui/lib/instantdb/model-names.ts +24 -0
  79. package/src/ui/lib/instantdb/use-instant-options.ts +98 -0
  80. package/src/ui/lib/instantdb/use-list-accounts.ts +38 -0
  81. package/src/ui/lib/instantdb/use-list-sessions.ts +53 -0
  82. package/src/ui/lib/instantdb/use-session.ts +55 -0
  83. package/src/ui/lib/social-providers.ts +150 -0
  84. package/src/ui/lib/tanstack/auth-ui-provider-tanstack.tsx +49 -0
  85. package/src/ui/lib/tanstack/use-tanstack-options.ts +112 -0
  86. package/src/ui/lib/triplit/model-names.ts +24 -0
  87. package/src/ui/lib/triplit/use-conditional-query.ts +82 -0
  88. package/src/ui/lib/triplit/use-list-accounts.ts +31 -0
  89. package/src/ui/lib/triplit/use-list-sessions.ts +33 -0
  90. package/src/ui/lib/triplit/use-session.ts +42 -0
  91. package/src/ui/lib/triplit/use-triplit-hooks.ts +68 -0
  92. package/src/ui/lib/triplit/use-triplit-token.ts +44 -0
  93. package/src/ui/lib/utils.ts +119 -0
  94. package/src/ui/lib/view-paths.ts +61 -0
  95. package/src/ui/lib/wallet.ts +129 -0
  96. package/src/ui/localization/admin-error-codes.ts +20 -0
  97. package/src/ui/localization/anonymous-error-codes.ts +6 -0
  98. package/src/ui/localization/api-key-error-codes.ts +32 -0
  99. package/src/ui/localization/auth-localization.ts +865 -0
  100. package/src/ui/localization/base-error-codes.ts +27 -0
  101. package/src/ui/localization/captcha-error-codes.ts +17 -0
  102. package/src/ui/localization/email-otp-error-codes.ts +7 -0
  103. package/src/ui/localization/generic-oauth-error-codes.ts +3 -0
  104. package/src/ui/localization/haveibeenpwned-error-codes.ts +4 -0
  105. package/src/ui/localization/multi-session-error-codes.ts +3 -0
  106. package/src/ui/localization/organization-error-codes.ts +57 -0
  107. package/src/ui/localization/passkey-error-codes.ts +10 -0
  108. package/src/ui/localization/phone-number-error-codes.ts +10 -0
  109. package/src/ui/localization/stripe-localization.ts +12 -0
  110. package/src/ui/localization/team-error-codes.ts +12 -0
  111. package/src/ui/localization/two-factor-error-codes.ts +12 -0
  112. package/src/ui/localization/username-error-codes.ts +9 -0
  113. package/src/ui/server.ts +4 -0
  114. package/src/ui/style.css +146 -0
  115. package/src/ui/tanstack.ts +1 -0
  116. package/src/ui/triplit.ts +1 -0
  117. package/src/ui/types/account-options.ts +35 -0
  118. package/src/ui/types/additional-fields.ts +21 -0
  119. package/src/ui/types/any-auth-client.ts +6 -0
  120. package/src/ui/types/api-key.ts +9 -0
  121. package/src/ui/types/auth-client.ts +41 -0
  122. package/src/ui/types/auth-hooks.ts +81 -0
  123. package/src/ui/types/auth-mutators.ts +21 -0
  124. package/src/ui/types/avatar-options.ts +29 -0
  125. package/src/ui/types/captcha-options.ts +32 -0
  126. package/src/ui/types/captcha-provider.ts +7 -0
  127. package/src/ui/types/credentials-options.ts +38 -0
  128. package/src/ui/types/delete-user-options.ts +7 -0
  129. package/src/ui/types/email-verification-options.ts +7 -0
  130. package/src/ui/types/fetch-error.ts +6 -0
  131. package/src/ui/types/generic-oauth-options.ts +16 -0
  132. package/src/ui/types/gravatar-options.ts +21 -0
  133. package/src/ui/types/image.ts +7 -0
  134. package/src/ui/types/invitation.ts +10 -0
  135. package/src/ui/types/link.ts +7 -0
  136. package/src/ui/types/organization-options.ts +106 -0
  137. package/src/ui/types/password-validation.ts +16 -0
  138. package/src/ui/types/profile.ts +15 -0
  139. package/src/ui/types/refetch.ts +1 -0
  140. package/src/ui/types/render-toast.ts +9 -0
  141. package/src/ui/types/sign-up-options.ts +7 -0
  142. package/src/ui/types/social-options.ts +16 -0
  143. package/src/ui/types/team-options.ts +47 -0
@@ -0,0 +1,316 @@
1
+ /**
2
+ * @erikey/react - KV Store Client
3
+ *
4
+ * Erikey-specific key-value storage for end-users.
5
+ * Stores data per-user, scoped to the project.
6
+ *
7
+ * This is NOT part of better-auth - it's an Erikey-specific feature.
8
+ */
9
+
10
+ import {
11
+ shouldUseBearerAuth,
12
+ getStoredToken,
13
+ } from './lib/cross-origin-auth';
14
+
15
+ // ============================================================================
16
+ // Types
17
+ // ============================================================================
18
+
19
+ export interface KvClientConfig {
20
+ /**
21
+ * Your Erikey project ID (pk_live_xxx or pk_test_xxx)
22
+ */
23
+ projectId: string;
24
+
25
+ /**
26
+ * Base URL for the auth API
27
+ * @default 'https://auth.erikey.com'
28
+ */
29
+ baseUrl?: string;
30
+ }
31
+
32
+ export interface KvPair {
33
+ key: string;
34
+ value: any;
35
+ createdAt: string;
36
+ updatedAt: string;
37
+ }
38
+
39
+ export interface KvBulkSetInput {
40
+ key: string;
41
+ value: any;
42
+ }
43
+
44
+ export interface SetValueResponse {
45
+ success: boolean;
46
+ data?: { key: string; value: any };
47
+ error?: string;
48
+ }
49
+
50
+ export interface GetValueResponse {
51
+ success: boolean;
52
+ data?: KvPair;
53
+ error?: string;
54
+ }
55
+
56
+ export interface GetValuesResponse {
57
+ success: boolean;
58
+ data?: { kvPairs: KvPair[]; total: number };
59
+ error?: string;
60
+ }
61
+
62
+ export interface DeleteValueResponse {
63
+ success: boolean;
64
+ data?: { message: string };
65
+ error?: string;
66
+ }
67
+
68
+ export interface DeleteValuesResponse {
69
+ success: boolean;
70
+ data?: { deleted: string[]; failed: Array<{ key: string; error: string }> };
71
+ error?: string;
72
+ }
73
+
74
+ export interface SetValuesResponse {
75
+ success: boolean;
76
+ data?: { results: Array<{ key: string; success: boolean }>; total: number };
77
+ error?: string;
78
+ }
79
+
80
+ // ============================================================================
81
+ // KV Client
82
+ // ============================================================================
83
+
84
+ /**
85
+ * Create a KV store client for per-user data storage
86
+ *
87
+ * @example
88
+ * ```tsx
89
+ * import { createKvClient } from '@erikey/react';
90
+ *
91
+ * const kv = createKvClient({
92
+ * projectId: 'pk_live_xxx',
93
+ * });
94
+ *
95
+ * // Store a value
96
+ * await kv.setValue('preferences', { theme: 'dark' });
97
+ *
98
+ * // Get a value
99
+ * const { data } = await kv.getValue('preferences');
100
+ *
101
+ * // Get all values
102
+ * const { data: all } = await kv.getValues();
103
+ *
104
+ * // Delete a value
105
+ * await kv.deleteValue('preferences');
106
+ * ```
107
+ */
108
+ export function createKvClient(config: KvClientConfig) {
109
+ const { projectId, baseUrl = 'https://auth.erikey.com' } = config;
110
+ const useBearerAuth = shouldUseBearerAuth(baseUrl);
111
+
112
+ /**
113
+ * Internal helper for making authenticated requests
114
+ */
115
+ const fetchWithAuth = async <T>(
116
+ endpoint: string,
117
+ options?: RequestInit
118
+ ): Promise<T> => {
119
+ const headers: Record<string, string> = {
120
+ 'Content-Type': 'application/json',
121
+ 'X-Project-Id': projectId,
122
+ ...(options?.headers as Record<string, string> || {}),
123
+ };
124
+
125
+ // Add Bearer token for cross-origin contexts
126
+ if (useBearerAuth) {
127
+ const token = getStoredToken(projectId);
128
+ if (token) {
129
+ headers['Authorization'] = `Bearer ${token}`;
130
+ }
131
+ }
132
+
133
+ const response = await fetch(`${baseUrl}/api/auth${endpoint}`, {
134
+ ...options,
135
+ credentials: useBearerAuth ? 'omit' : 'include',
136
+ headers,
137
+ });
138
+
139
+ const data = await response.json() as any;
140
+
141
+ if (!response.ok) {
142
+ return {
143
+ success: false,
144
+ error: data?.error?.message || data?.message || 'Request failed',
145
+ } as T;
146
+ }
147
+
148
+ return {
149
+ success: true,
150
+ data,
151
+ } as T;
152
+ };
153
+
154
+ return {
155
+ /**
156
+ * Set a single key-value pair
157
+ */
158
+ setValue: async (key: string, value: any): Promise<SetValueResponse> => {
159
+ return fetchWithAuth<SetValueResponse>(`/key-value/${encodeURIComponent(key)}`, {
160
+ method: 'PUT',
161
+ body: JSON.stringify({ value }),
162
+ });
163
+ },
164
+
165
+ /**
166
+ * Get a single key-value pair
167
+ */
168
+ getValue: async (key: string): Promise<GetValueResponse> => {
169
+ const result = await fetchWithAuth<{ success: boolean; data?: any; error?: string }>(
170
+ `/key-value/${encodeURIComponent(key)}`,
171
+ { method: 'GET' }
172
+ );
173
+
174
+ if (!result.success) {
175
+ return { success: false, error: result.error };
176
+ }
177
+
178
+ return {
179
+ success: true,
180
+ data: {
181
+ key: result.data.key,
182
+ value: result.data.value,
183
+ createdAt: result.data.createdAt,
184
+ updatedAt: result.data.updatedAt,
185
+ },
186
+ };
187
+ },
188
+
189
+ /**
190
+ * Get all key-value pairs for the authenticated user
191
+ */
192
+ getValues: async (): Promise<GetValuesResponse> => {
193
+ const result = await fetchWithAuth<{ success: boolean; data?: any; error?: string }>(
194
+ '/key-value',
195
+ { method: 'GET' }
196
+ );
197
+
198
+ if (!result.success) {
199
+ return { success: false, error: result.error };
200
+ }
201
+
202
+ return {
203
+ success: true,
204
+ data: {
205
+ kvPairs: result.data.kvPairs,
206
+ total: result.data.total,
207
+ },
208
+ };
209
+ },
210
+
211
+ /**
212
+ * Delete a single key-value pair
213
+ */
214
+ deleteValue: async (key: string): Promise<DeleteValueResponse> => {
215
+ const result = await fetchWithAuth<{ success: boolean; data?: any; error?: string }>(
216
+ `/key-value/${encodeURIComponent(key)}`,
217
+ { method: 'DELETE' }
218
+ );
219
+
220
+ if (!result.success) {
221
+ return { success: false, error: result.error };
222
+ }
223
+
224
+ return {
225
+ success: true,
226
+ data: {
227
+ message: result.data.message || 'KV pair deleted',
228
+ },
229
+ };
230
+ },
231
+
232
+ /**
233
+ * Delete multiple key-value pairs
234
+ */
235
+ deleteValues: async (keys: string[]): Promise<DeleteValuesResponse> => {
236
+ if (keys.length === 0) {
237
+ return {
238
+ success: true,
239
+ data: {
240
+ deleted: [],
241
+ failed: [],
242
+ },
243
+ };
244
+ }
245
+
246
+ const results = await Promise.allSettled(
247
+ keys.map((key) =>
248
+ fetchWithAuth<{ success: boolean; error?: string }>(
249
+ `/key-value/${encodeURIComponent(key)}`,
250
+ { method: 'DELETE' }
251
+ ).then((result) => ({ key, result }))
252
+ )
253
+ );
254
+
255
+ const deleted: string[] = [];
256
+ const failed: Array<{ key: string; error: string }> = [];
257
+
258
+ results.forEach((result, index) => {
259
+ if (result.status === 'fulfilled') {
260
+ const { key, result: deleteResult } = result.value;
261
+ if (deleteResult.success) {
262
+ deleted.push(key);
263
+ } else {
264
+ failed.push({
265
+ key,
266
+ error: deleteResult.error || 'Delete failed',
267
+ });
268
+ }
269
+ } else {
270
+ failed.push({
271
+ key: keys[index],
272
+ error: result.reason?.message || 'Request failed',
273
+ });
274
+ }
275
+ });
276
+
277
+ return {
278
+ success: failed.length === 0,
279
+ data: {
280
+ deleted,
281
+ failed,
282
+ },
283
+ };
284
+ },
285
+
286
+ /**
287
+ * Set multiple key-value pairs in bulk (max 100 pairs)
288
+ */
289
+ setValues: async (kvPairs: KvBulkSetInput[]): Promise<SetValuesResponse> => {
290
+ const result = await fetchWithAuth<{ success: boolean; data?: any; error?: string }>(
291
+ '/key-value/bulk',
292
+ {
293
+ method: 'POST',
294
+ body: JSON.stringify({ kvPairs }),
295
+ }
296
+ );
297
+
298
+ if (!result.success) {
299
+ return { success: false, error: result.error };
300
+ }
301
+
302
+ return {
303
+ success: true,
304
+ data: {
305
+ results: result.data.results,
306
+ total: result.data.total,
307
+ },
308
+ };
309
+ },
310
+ };
311
+ }
312
+
313
+ /**
314
+ * Type helper for inferring the KV client type
315
+ */
316
+ export type KvClient = ReturnType<typeof createKvClient>;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Cross-origin authentication utilities
3
+ *
4
+ * Handles authentication in cross-origin contexts where cookies don't work:
5
+ * - Sandpack preview iframes
6
+ * - Deployed customer sites (e.g., mysite.com -> auth.erikey.com)
7
+ *
8
+ * Uses localStorage to store bearer tokens as a fallback.
9
+ */
10
+
11
+ // ============================================================================
12
+ // Types
13
+ // ============================================================================
14
+
15
+ export interface StoredSession {
16
+ id: string;
17
+ token: string;
18
+ expiresAt: string;
19
+ }
20
+
21
+ // ============================================================================
22
+ // Detection
23
+ // ============================================================================
24
+
25
+ /**
26
+ * Detect if we should use Bearer token authentication
27
+ *
28
+ * Returns true when the current origin differs from the auth API origin,
29
+ * meaning cookies won't work and we need Bearer tokens.
30
+ */
31
+ export function shouldUseBearerAuth(authApiUrl: string): boolean {
32
+ if (typeof window === 'undefined') {
33
+ return false;
34
+ }
35
+
36
+ try {
37
+ const currentOrigin = window.location.origin;
38
+ const authOrigin = new URL(authApiUrl).origin;
39
+ return currentOrigin !== authOrigin;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ // ============================================================================
46
+ // Storage
47
+ // ============================================================================
48
+
49
+ function getStorageKey(projectId: string): string {
50
+ return `erikey.session.${projectId}`;
51
+ }
52
+
53
+ /**
54
+ * Store session token in localStorage for cross-origin contexts
55
+ */
56
+ export function storeToken(projectId: string, session: StoredSession): void {
57
+ if (typeof window === 'undefined') return;
58
+
59
+ const key = getStorageKey(projectId);
60
+ localStorage.setItem(key, JSON.stringify(session));
61
+ }
62
+
63
+ /**
64
+ * Get stored token from localStorage
65
+ * Returns null if not found or expired
66
+ */
67
+ export function getStoredToken(projectId: string): string | null {
68
+ if (typeof window === 'undefined') return null;
69
+
70
+ const key = getStorageKey(projectId);
71
+ const stored = localStorage.getItem(key);
72
+
73
+ if (!stored) return null;
74
+
75
+ try {
76
+ const session: StoredSession = JSON.parse(stored);
77
+
78
+ // Check if expired
79
+ if (new Date(session.expiresAt) < new Date()) {
80
+ localStorage.removeItem(key);
81
+ return null;
82
+ }
83
+
84
+ return session.token;
85
+ } catch {
86
+ localStorage.removeItem(key);
87
+ return null;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Clear stored token from localStorage
93
+ */
94
+ export function clearToken(projectId: string): void {
95
+ if (typeof window === 'undefined') return;
96
+
97
+ const key = getStorageKey(projectId);
98
+ localStorage.removeItem(key);
99
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Captcha Provider Stubs
3
+ *
4
+ * No-op implementations for captcha providers. These are optional features
5
+ * that most users won't need. If real captcha is required, the user should
6
+ * configure it separately in their application.
7
+ */
8
+
9
+ // @wojtekmaj/react-recaptcha-v3
10
+ export const GoogleReCaptchaProvider = ({ children }: { children?: React.ReactNode }) => children;
11
+ export const useGoogleReCaptcha = () => ({ executeRecaptcha: null });
12
+
13
+ // @captchafox/react
14
+ export const CaptchaFox = () => null;
15
+
16
+ // @hcaptcha/react-hcaptcha
17
+ export const HCaptcha = () => null;
18
+ export default HCaptcha;
19
+
20
+ // @marsidev/react-turnstile
21
+ export const Turnstile = () => null;
22
+
23
+ // react-google-recaptcha
24
+ export const ReCAPTCHA = () => null;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Noble Hashes Stubs
3
+ *
4
+ * No-op implementations for @noble/hashes.
5
+ * Used for cryptographic operations in passkey/WebAuthn flows.
6
+ */
7
+
8
+ // @noble/hashes
9
+ export const sha256 = () => new Uint8Array(32);
10
+
11
+ // @noble/hashes/sha2
12
+ export { sha256 as sha256Hash };
13
+
14
+ // @noble/hashes/utils
15
+ export const bytesToHex = (_bytes: Uint8Array) => '';
16
+ export const hexToBytes = (_hex: string) => new Uint8Array(0);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Stubs for Optional Dependencies
3
+ *
4
+ * These provide no-op implementations for optional features like captcha providers,
5
+ * passkey support, QR codes, etc. This allows the SDK to be fully self-contained
6
+ * without requiring consumers to install packages they don't use.
7
+ *
8
+ * If a user wants to use a real implementation, they can configure it in their app.
9
+ */
10
+
11
+ // Re-export all stubs
12
+ export * from './captcha';
13
+ export * from './passkey';
14
+ export * from './qr-code';
15
+ export * from './hashes';
16
+ export * from './query';
17
+ export * from './realtime';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Passkey Stubs
3
+ *
4
+ * No-op implementations for @better-auth/passkey.
5
+ * Passkey support is optional and requires WebAuthn-capable browsers.
6
+ */
7
+
8
+ // @better-auth/passkey
9
+ export const passkey = () => ({});
10
+
11
+ // @better-auth/passkey/client
12
+ export const passkeyClient = () => ({});
@@ -0,0 +1,10 @@
1
+ /**
2
+ * QR Code Stub
3
+ *
4
+ * No-op implementation for react-qr-code.
5
+ * Used for 2FA TOTP setup. If not needed, this stub prevents import errors.
6
+ */
7
+
8
+ // react-qr-code
9
+ const QRCode = () => null;
10
+ export default QRCode;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Query Library Stubs
3
+ *
4
+ * No-op implementations for optional query/state management libraries.
5
+ */
6
+
7
+ // @tanstack/react-query
8
+ export const QueryClient = class {
9
+ constructor() {}
10
+ };
11
+ export const QueryClientProvider = ({ children }: { children?: React.ReactNode }) => children;
12
+ export const useQuery = () => ({ data: undefined, isLoading: false, error: null });
13
+ export const useMutation = () => ({ mutate: () => {}, isLoading: false, error: null });
14
+
15
+ // @daveyplate/better-auth-tanstack
16
+ export const createAuthTanstack = () => ({});
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Realtime Database Stubs
3
+ *
4
+ * No-op implementations for optional realtime database libraries.
5
+ */
6
+
7
+ // @instantdb/react
8
+ export const init = () => ({});
9
+ export const useQuery = () => ({ data: undefined, isLoading: false, error: null });
10
+
11
+ // @triplit/client
12
+ export const TriplitClient = class {
13
+ constructor() {}
14
+ };
15
+
16
+ // @triplit/react
17
+ export const useTriplit = () => ({ client: null });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * use-sync-external-store stub
3
+ *
4
+ * Redirects to React 18's built-in useSyncExternalStore.
5
+ * The npm package is a CJS polyfill that fails in browser ESM.
6
+ * Since we require React 18+, we use the native implementation.
7
+ */
8
+ import { useSyncExternalStore } from 'react';
9
+
10
+ export { useSyncExternalStore };
11
+ export { useSyncExternalStore as useSyncExternalStoreWithSelector };
12
+ export default useSyncExternalStore;
package/src/styles.css ADDED
@@ -0,0 +1,141 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /*
6
+ * Animation CSS for SDK components
7
+ *
8
+ * These styles are bundled with the SDK to ensure they work regardless of
9
+ * consumer's Tailwind configuration. Placed AFTER @tailwind utilities so
10
+ * these styles take precedence over purged Tailwind output.
11
+ *
12
+ * This solves the problem where consumer's Tailwind purges animation classes
13
+ * because they only exist in the SDK's minified bundle.
14
+ */
15
+
16
+ /* Keyframes for enter/exit animations (from tailwindcss-animate) */
17
+ @keyframes enter {
18
+ from {
19
+ opacity: var(--tw-enter-opacity, 1);
20
+ transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0)
21
+ scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1))
22
+ rotate(var(--tw-enter-rotate, 0));
23
+ }
24
+ }
25
+
26
+ @keyframes exit {
27
+ to {
28
+ opacity: var(--tw-exit-opacity, 1);
29
+ transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0)
30
+ scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1))
31
+ rotate(var(--tw-exit-rotate, 0));
32
+ }
33
+ }
34
+
35
+ /* Animation base classes */
36
+ .animate-in {
37
+ animation-name: enter;
38
+ animation-duration: 150ms;
39
+ --tw-enter-opacity: initial;
40
+ --tw-enter-scale: initial;
41
+ --tw-enter-rotate: initial;
42
+ --tw-enter-translate-x: initial;
43
+ --tw-enter-translate-y: initial;
44
+ }
45
+
46
+ .animate-out {
47
+ animation-name: exit;
48
+ animation-duration: 150ms;
49
+ --tw-exit-opacity: initial;
50
+ --tw-exit-scale: initial;
51
+ --tw-exit-rotate: initial;
52
+ --tw-exit-translate-x: initial;
53
+ --tw-exit-translate-y: initial;
54
+ }
55
+
56
+ /* Opacity variants */
57
+ .fade-in-0 { --tw-enter-opacity: 0; }
58
+ .fade-out-0 { --tw-exit-opacity: 0; }
59
+
60
+ /* Scale variants */
61
+ .zoom-in-95 { --tw-enter-scale: 0.95; }
62
+ .zoom-out-95 { --tw-exit-scale: 0.95; }
63
+
64
+ /* Slide variants */
65
+ .slide-in-from-top-2 { --tw-enter-translate-y: -0.5rem; }
66
+ .slide-in-from-bottom-2 { --tw-enter-translate-y: 0.5rem; }
67
+ .slide-in-from-left-2 { --tw-enter-translate-x: -0.5rem; }
68
+ .slide-in-from-right-2 { --tw-enter-translate-x: 0.5rem; }
69
+
70
+ /* Data attribute variants for Radix UI components */
71
+ [data-state="open"].animate-in {
72
+ animation-name: enter;
73
+ animation-duration: 150ms;
74
+ }
75
+
76
+ [data-state="closed"].animate-out {
77
+ animation-name: exit;
78
+ animation-duration: 150ms;
79
+ }
80
+
81
+ [data-state="open"].fade-in-0,
82
+ .data-\[state\=open\]\:fade-in-0[data-state="open"] {
83
+ --tw-enter-opacity: 0;
84
+ }
85
+
86
+ [data-state="closed"].fade-out-0,
87
+ .data-\[state\=closed\]\:fade-out-0[data-state="closed"] {
88
+ --tw-exit-opacity: 0;
89
+ }
90
+
91
+ [data-state="open"].zoom-in-95,
92
+ .data-\[state\=open\]\:zoom-in-95[data-state="open"] {
93
+ --tw-enter-scale: 0.95;
94
+ }
95
+
96
+ [data-state="closed"].zoom-out-95,
97
+ .data-\[state\=closed\]\:zoom-out-95[data-state="closed"] {
98
+ --tw-exit-scale: 0.95;
99
+ }
100
+
101
+ /* Slide variants with data-side for dropdown positioning */
102
+ [data-side="bottom"].slide-in-from-top-2,
103
+ .data-\[side\=bottom\]\:slide-in-from-top-2[data-side="bottom"] {
104
+ --tw-enter-translate-y: -0.5rem;
105
+ }
106
+
107
+ [data-side="top"].slide-in-from-bottom-2,
108
+ .data-\[side\=top\]\:slide-in-from-bottom-2[data-side="top"] {
109
+ --tw-enter-translate-y: 0.5rem;
110
+ }
111
+
112
+ [data-side="left"].slide-in-from-right-2,
113
+ .data-\[side\=left\]\:slide-in-from-right-2[data-side="left"] {
114
+ --tw-enter-translate-x: 0.5rem;
115
+ }
116
+
117
+ [data-side="right"].slide-in-from-left-2,
118
+ .data-\[side\=right\]\:slide-in-from-left-2[data-side="right"] {
119
+ --tw-enter-translate-x: -0.5rem;
120
+ }
121
+
122
+ /* Tailwind's data attribute variant syntax support */
123
+ .data-\[state\=open\]\:animate-in[data-state="open"] {
124
+ animation-name: enter;
125
+ animation-duration: 150ms;
126
+ --tw-enter-opacity: initial;
127
+ --tw-enter-scale: initial;
128
+ --tw-enter-rotate: initial;
129
+ --tw-enter-translate-x: initial;
130
+ --tw-enter-translate-y: initial;
131
+ }
132
+
133
+ .data-\[state\=closed\]\:animate-out[data-state="closed"] {
134
+ animation-name: exit;
135
+ animation-duration: 150ms;
136
+ --tw-exit-opacity: initial;
137
+ --tw-exit-scale: initial;
138
+ --tw-exit-rotate: initial;
139
+ --tw-exit-translate-x: initial;
140
+ --tw-exit-translate-y: initial;
141
+ }
package/src/types.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @erikey/react types
3
+ *
4
+ * Re-exports shared types from @erikey/core for dashboard client compatibility.
5
+ * For end-user auth types, see auth-client.ts.
6
+ */
7
+
8
+ // Re-export dashboard-related types from core
9
+ export type {
10
+ SessionData,
11
+ Organization,
12
+ APIKey,
13
+ Permission,
14
+ } from '@erikey/core/types';