@fluxbase/sdk-react 0.1.0-rc.1 → 2026.1.1-rc.1

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.
@@ -1,14 +1,14 @@
1
- import { useState, useEffect, useCallback } from 'react'
2
- import { useFluxbaseClient } from './context'
3
- import type { AdminAuthResponse } from '@fluxbase/sdk'
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { useFluxbaseClient } from "./context";
3
+ import type { AdminAuthResponse, DataResponse } from "@fluxbase/sdk";
4
4
 
5
5
  /**
6
6
  * Simplified admin user type returned by authentication
7
7
  */
8
8
  export interface AdminUser {
9
- id: string
10
- email: string
11
- role: string
9
+ id: string;
10
+ email: string;
11
+ role: string;
12
12
  }
13
13
 
14
14
  export interface UseAdminAuthOptions {
@@ -16,44 +16,44 @@ export interface UseAdminAuthOptions {
16
16
  * Automatically check authentication status on mount
17
17
  * @default true
18
18
  */
19
- autoCheck?: boolean
19
+ autoCheck?: boolean;
20
20
  }
21
21
 
22
22
  export interface UseAdminAuthReturn {
23
23
  /**
24
24
  * Current admin user if authenticated
25
25
  */
26
- user: AdminUser | null
26
+ user: AdminUser | null;
27
27
 
28
28
  /**
29
29
  * Whether the admin is authenticated
30
30
  */
31
- isAuthenticated: boolean
31
+ isAuthenticated: boolean;
32
32
 
33
33
  /**
34
34
  * Whether the authentication check is in progress
35
35
  */
36
- isLoading: boolean
36
+ isLoading: boolean;
37
37
 
38
38
  /**
39
39
  * Any error that occurred during authentication
40
40
  */
41
- error: Error | null
41
+ error: Error | null;
42
42
 
43
43
  /**
44
44
  * Login as admin
45
45
  */
46
- login: (email: string, password: string) => Promise<AdminAuthResponse>
46
+ login: (email: string, password: string) => Promise<AdminAuthResponse>;
47
47
 
48
48
  /**
49
49
  * Logout admin
50
50
  */
51
- logout: () => Promise<void>
51
+ logout: () => Promise<void>;
52
52
 
53
53
  /**
54
54
  * Refresh admin user info
55
55
  */
56
- refresh: () => Promise<void>
56
+ refresh: () => Promise<void>;
57
57
  }
58
58
 
59
59
  /**
@@ -78,30 +78,35 @@ export interface UseAdminAuthReturn {
78
78
  * }
79
79
  * ```
80
80
  */
81
- export function useAdminAuth(options: UseAdminAuthOptions = {}): UseAdminAuthReturn {
82
- const { autoCheck = true } = options
83
- const client = useFluxbaseClient()
81
+ export function useAdminAuth(
82
+ options: UseAdminAuthOptions = {},
83
+ ): UseAdminAuthReturn {
84
+ const { autoCheck = true } = options;
85
+ const client = useFluxbaseClient();
84
86
 
85
- const [user, setUser] = useState<AdminUser | null>(null)
86
- const [isLoading, setIsLoading] = useState(autoCheck)
87
- const [error, setError] = useState<Error | null>(null)
87
+ const [user, setUser] = useState<AdminUser | null>(null);
88
+ const [isLoading, setIsLoading] = useState(autoCheck);
89
+ const [error, setError] = useState<Error | null>(null);
88
90
 
89
91
  /**
90
92
  * Check current authentication status
91
93
  */
92
94
  const checkAuth = useCallback(async () => {
93
95
  try {
94
- setIsLoading(true)
95
- setError(null)
96
- const { user } = await client.admin.me()
97
- setUser(user)
96
+ setIsLoading(true);
97
+ setError(null);
98
+ const { data, error: apiError } = await client.admin.me();
99
+ if (apiError) {
100
+ throw apiError;
101
+ }
102
+ setUser(data!.user);
98
103
  } catch (err) {
99
- setUser(null)
100
- setError(err as Error)
104
+ setUser(null);
105
+ setError(err as Error);
101
106
  } finally {
102
- setIsLoading(false)
107
+ setIsLoading(false);
103
108
  }
104
- }, [client])
109
+ }, [client]);
105
110
 
106
111
  /**
107
112
  * Login as admin
@@ -109,52 +114,58 @@ export function useAdminAuth(options: UseAdminAuthOptions = {}): UseAdminAuthRet
109
114
  const login = useCallback(
110
115
  async (email: string, password: string): Promise<AdminAuthResponse> => {
111
116
  try {
112
- setIsLoading(true)
113
- setError(null)
114
- const response = await client.admin.login({ email, password })
115
- setUser(response.user)
116
- return response
117
+ setIsLoading(true);
118
+ setError(null);
119
+ const { data, error: apiError } = await client.admin.login({
120
+ email,
121
+ password,
122
+ });
123
+ if (apiError) {
124
+ throw apiError;
125
+ }
126
+ setUser(data!.user);
127
+ return data!;
117
128
  } catch (err) {
118
- setError(err as Error)
119
- throw err
129
+ setError(err as Error);
130
+ throw err;
120
131
  } finally {
121
- setIsLoading(false)
132
+ setIsLoading(false);
122
133
  }
123
134
  },
124
- [client]
125
- )
135
+ [client],
136
+ );
126
137
 
127
138
  /**
128
139
  * Logout admin
129
140
  */
130
141
  const logout = useCallback(async (): Promise<void> => {
131
142
  try {
132
- setIsLoading(true)
133
- setError(null)
143
+ setIsLoading(true);
144
+ setError(null);
134
145
  // Clear user state
135
- setUser(null)
146
+ setUser(null);
136
147
  // Note: Add logout endpoint call here when available
137
148
  } catch (err) {
138
- setError(err as Error)
139
- throw err
149
+ setError(err as Error);
150
+ throw err;
140
151
  } finally {
141
- setIsLoading(false)
152
+ setIsLoading(false);
142
153
  }
143
- }, [])
154
+ }, []);
144
155
 
145
156
  /**
146
157
  * Refresh admin user info
147
158
  */
148
159
  const refresh = useCallback(async (): Promise<void> => {
149
- await checkAuth()
150
- }, [checkAuth])
160
+ await checkAuth();
161
+ }, [checkAuth]);
151
162
 
152
163
  // Auto-check authentication on mount
153
164
  useEffect(() => {
154
165
  if (autoCheck) {
155
- checkAuth()
166
+ checkAuth();
156
167
  }
157
- }, [autoCheck, checkAuth])
168
+ }, [autoCheck, checkAuth]);
158
169
 
159
170
  return {
160
171
  user,
@@ -163,6 +174,6 @@ export function useAdminAuth(options: UseAdminAuthOptions = {}): UseAdminAuthRet
163
174
  error,
164
175
  login,
165
176
  logout,
166
- refresh
167
- }
177
+ refresh,
178
+ };
168
179
  }
package/src/use-auth.ts CHANGED
@@ -2,132 +2,149 @@
2
2
  * Authentication hooks for Fluxbase SDK
3
3
  */
4
4
 
5
- import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
6
- import { useFluxbaseClient } from './context'
7
- import type { SignInCredentials, SignUpCredentials, User, AuthSession } from '@fluxbase/sdk'
5
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
6
+ import { useFluxbaseClient } from "./context";
7
+ import type {
8
+ SignInCredentials,
9
+ SignUpCredentials,
10
+ User,
11
+ AuthSession,
12
+ } from "@fluxbase/sdk";
8
13
 
9
14
  /**
10
15
  * Hook to get the current user
11
16
  */
12
17
  export function useUser() {
13
- const client = useFluxbaseClient()
18
+ const client = useFluxbaseClient();
14
19
 
15
20
  return useQuery({
16
- queryKey: ['fluxbase', 'auth', 'user'],
21
+ queryKey: ["fluxbase", "auth", "user"],
17
22
  queryFn: async () => {
18
- const session = client.auth.getSession()
19
- if (!session) {
20
- return null
23
+ const { data } = await client.auth.getSession();
24
+ if (!data?.session) {
25
+ return null;
21
26
  }
22
27
 
23
28
  try {
24
- return await client.auth.getCurrentUser()
29
+ const result = await client.auth.getCurrentUser();
30
+ return result.data?.user ?? null;
25
31
  } catch {
26
- return null
32
+ return null;
27
33
  }
28
34
  },
29
35
  staleTime: 1000 * 60 * 5, // 5 minutes
30
- })
36
+ });
31
37
  }
32
38
 
33
39
  /**
34
40
  * Hook to get the current session
35
41
  */
36
42
  export function useSession() {
37
- const client = useFluxbaseClient()
43
+ const client = useFluxbaseClient();
38
44
 
39
45
  return useQuery<AuthSession | null>({
40
- queryKey: ['fluxbase', 'auth', 'session'],
41
- queryFn: () => client.auth.getSession(),
46
+ queryKey: ["fluxbase", "auth", "session"],
47
+ queryFn: async () => {
48
+ const { data } = await client.auth.getSession();
49
+ return data?.session ?? null;
50
+ },
42
51
  staleTime: 1000 * 60 * 5, // 5 minutes
43
- })
52
+ });
44
53
  }
45
54
 
46
55
  /**
47
56
  * Hook for signing in
48
57
  */
49
58
  export function useSignIn() {
50
- const client = useFluxbaseClient()
51
- const queryClient = useQueryClient()
59
+ const client = useFluxbaseClient();
60
+ const queryClient = useQueryClient();
52
61
 
53
62
  return useMutation({
54
63
  mutationFn: async (credentials: SignInCredentials) => {
55
- return await client.auth.signIn(credentials)
64
+ return await client.auth.signIn(credentials);
56
65
  },
57
66
  onSuccess: (session) => {
58
- queryClient.setQueryData(['fluxbase', 'auth', 'session'], session)
67
+ queryClient.setQueryData(["fluxbase", "auth", "session"], session);
59
68
  // Only set user if this is a complete auth session (not 2FA required)
60
- if ('user' in session) {
61
- queryClient.setQueryData(['fluxbase', 'auth', 'user'], session.user)
69
+ if ("user" in session) {
70
+ queryClient.setQueryData(["fluxbase", "auth", "user"], session.user);
62
71
  }
63
72
  },
64
- })
73
+ });
65
74
  }
66
75
 
67
76
  /**
68
77
  * Hook for signing up
69
78
  */
70
79
  export function useSignUp() {
71
- const client = useFluxbaseClient()
72
- const queryClient = useQueryClient()
80
+ const client = useFluxbaseClient();
81
+ const queryClient = useQueryClient();
73
82
 
74
83
  return useMutation({
75
84
  mutationFn: async (credentials: SignUpCredentials) => {
76
- return await client.auth.signUp(credentials)
85
+ return await client.auth.signUp(credentials);
77
86
  },
78
- onSuccess: (session) => {
79
- queryClient.setQueryData(['fluxbase', 'auth', 'session'], session)
80
- queryClient.setQueryData(['fluxbase', 'auth', 'user'], session.user)
87
+ onSuccess: (response) => {
88
+ if (response.data) {
89
+ queryClient.setQueryData(
90
+ ["fluxbase", "auth", "session"],
91
+ response.data.session,
92
+ );
93
+ queryClient.setQueryData(
94
+ ["fluxbase", "auth", "user"],
95
+ response.data.user,
96
+ );
97
+ }
81
98
  },
82
- })
99
+ });
83
100
  }
84
101
 
85
102
  /**
86
103
  * Hook for signing out
87
104
  */
88
105
  export function useSignOut() {
89
- const client = useFluxbaseClient()
90
- const queryClient = useQueryClient()
106
+ const client = useFluxbaseClient();
107
+ const queryClient = useQueryClient();
91
108
 
92
109
  return useMutation({
93
110
  mutationFn: async () => {
94
- await client.auth.signOut()
111
+ await client.auth.signOut();
95
112
  },
96
113
  onSuccess: () => {
97
- queryClient.setQueryData(['fluxbase', 'auth', 'session'], null)
98
- queryClient.setQueryData(['fluxbase', 'auth', 'user'], null)
99
- queryClient.invalidateQueries({ queryKey: ['fluxbase'] })
114
+ queryClient.setQueryData(["fluxbase", "auth", "session"], null);
115
+ queryClient.setQueryData(["fluxbase", "auth", "user"], null);
116
+ queryClient.invalidateQueries({ queryKey: ["fluxbase"] });
100
117
  },
101
- })
118
+ });
102
119
  }
103
120
 
104
121
  /**
105
122
  * Hook for updating the current user
106
123
  */
107
124
  export function useUpdateUser() {
108
- const client = useFluxbaseClient()
109
- const queryClient = useQueryClient()
125
+ const client = useFluxbaseClient();
126
+ const queryClient = useQueryClient();
110
127
 
111
128
  return useMutation({
112
- mutationFn: async (data: Partial<Pick<User, 'email' | 'metadata'>>) => {
113
- return await client.auth.updateUser(data)
129
+ mutationFn: async (data: Partial<Pick<User, "email" | "metadata">>) => {
130
+ return await client.auth.updateUser(data);
114
131
  },
115
132
  onSuccess: (user) => {
116
- queryClient.setQueryData(['fluxbase', 'auth', 'user'], user)
133
+ queryClient.setQueryData(["fluxbase", "auth", "user"], user);
117
134
  },
118
- })
135
+ });
119
136
  }
120
137
 
121
138
  /**
122
139
  * Combined auth hook with all auth state and methods
123
140
  */
124
141
  export function useAuth() {
125
- const { data: user, isLoading: isLoadingUser } = useUser()
126
- const { data: session, isLoading: isLoadingSession } = useSession()
127
- const signIn = useSignIn()
128
- const signUp = useSignUp()
129
- const signOut = useSignOut()
130
- const updateUser = useUpdateUser()
142
+ const { data: user, isLoading: isLoadingUser } = useUser();
143
+ const { data: session, isLoading: isLoadingSession } = useSession();
144
+ const signIn = useSignIn();
145
+ const signUp = useSignUp();
146
+ const signOut = useSignOut();
147
+ const updateUser = useUpdateUser();
131
148
 
132
149
  return {
133
150
  user,
@@ -142,5 +159,5 @@ export function useAuth() {
142
159
  isSigningUp: signUp.isPending,
143
160
  isSigningOut: signOut.isPending,
144
161
  isUpdating: updateUser.isPending,
145
- }
162
+ };
146
163
  }
@@ -0,0 +1,250 @@
1
+ /**
2
+ * CAPTCHA hooks for Fluxbase SDK
3
+ *
4
+ * Provides hooks to:
5
+ * - Fetch CAPTCHA configuration from the server
6
+ * - Manage CAPTCHA widget state
7
+ */
8
+
9
+ import { useQuery } from "@tanstack/react-query";
10
+ import { useFluxbaseClient } from "./context";
11
+ import type { CaptchaConfig, CaptchaProvider } from "@fluxbase/sdk";
12
+ import { useCallback, useEffect, useRef, useState } from "react";
13
+
14
+ /**
15
+ * Hook to get the CAPTCHA configuration from the server
16
+ * Use this to determine which CAPTCHA provider to load
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * function AuthPage() {
21
+ * const { data: captchaConfig, isLoading } = useCaptchaConfig();
22
+ *
23
+ * if (isLoading) return <Loading />;
24
+ *
25
+ * return captchaConfig?.enabled ? (
26
+ * <CaptchaWidget provider={captchaConfig.provider} siteKey={captchaConfig.site_key} />
27
+ * ) : null;
28
+ * }
29
+ * ```
30
+ */
31
+ export function useCaptchaConfig() {
32
+ const client = useFluxbaseClient();
33
+
34
+ return useQuery<CaptchaConfig>({
35
+ queryKey: ["fluxbase", "auth", "captcha", "config"],
36
+ queryFn: async () => {
37
+ const { data, error } = await client.auth.getCaptchaConfig();
38
+ if (error) {
39
+ throw error;
40
+ }
41
+ return data!;
42
+ },
43
+ staleTime: 1000 * 60 * 60, // Cache for 1 hour (config rarely changes)
44
+ gcTime: 1000 * 60 * 60 * 24, // Keep in cache for 24 hours
45
+ });
46
+ }
47
+
48
+ /**
49
+ * CAPTCHA widget state for managing token generation
50
+ */
51
+ export interface CaptchaState {
52
+ /** Current CAPTCHA token (null until solved) */
53
+ token: string | null;
54
+ /** Whether the CAPTCHA widget is ready */
55
+ isReady: boolean;
56
+ /** Whether a token is being generated */
57
+ isLoading: boolean;
58
+ /** Any error that occurred */
59
+ error: Error | null;
60
+ /** Reset the CAPTCHA widget */
61
+ reset: () => void;
62
+ /** Execute/trigger the CAPTCHA (for invisible CAPTCHA like reCAPTCHA v3) */
63
+ execute: () => Promise<string>;
64
+ /** Callback to be called when CAPTCHA is verified */
65
+ onVerify: (token: string) => void;
66
+ /** Callback to be called when CAPTCHA expires */
67
+ onExpire: () => void;
68
+ /** Callback to be called when CAPTCHA errors */
69
+ onError: (error: Error) => void;
70
+ }
71
+
72
+ /**
73
+ * Hook to manage CAPTCHA widget state
74
+ *
75
+ * This hook provides a standardized interface for managing CAPTCHA tokens
76
+ * across different providers (hCaptcha, reCAPTCHA v3, Turnstile, Cap).
77
+ *
78
+ * Supported providers:
79
+ * - hcaptcha: Privacy-focused visual challenge
80
+ * - recaptcha_v3: Google's invisible risk-based CAPTCHA
81
+ * - turnstile: Cloudflare's invisible CAPTCHA
82
+ * - cap: Self-hosted proof-of-work CAPTCHA (https://capjs.js.org/)
83
+ *
84
+ * @param provider - The CAPTCHA provider type
85
+ * @returns CAPTCHA state and callbacks
86
+ *
87
+ * @example
88
+ * ```tsx
89
+ * function LoginForm() {
90
+ * const captcha = useCaptcha('hcaptcha');
91
+ *
92
+ * const handleSubmit = async (e: FormEvent) => {
93
+ * e.preventDefault();
94
+ *
95
+ * // Get CAPTCHA token
96
+ * const captchaToken = captcha.token || await captcha.execute();
97
+ *
98
+ * // Sign in with CAPTCHA token
99
+ * await signIn({
100
+ * email,
101
+ * password,
102
+ * captchaToken
103
+ * });
104
+ * };
105
+ *
106
+ * return (
107
+ * <form onSubmit={handleSubmit}>
108
+ * <input name="email" />
109
+ * <input name="password" type="password" />
110
+ *
111
+ * <HCaptcha
112
+ * sitekey={siteKey}
113
+ * onVerify={captcha.onVerify}
114
+ * onExpire={captcha.onExpire}
115
+ * onError={captcha.onError}
116
+ * />
117
+ *
118
+ * <button type="submit" disabled={!captcha.isReady}>
119
+ * Sign In
120
+ * </button>
121
+ * </form>
122
+ * );
123
+ * }
124
+ * ```
125
+ *
126
+ * @example Cap provider
127
+ * ```tsx
128
+ * function LoginForm() {
129
+ * const { data: config } = useCaptchaConfig();
130
+ * const captcha = useCaptcha(config?.provider);
131
+ *
132
+ * // For Cap, load the widget from cap_server_url
133
+ * // <script src={`${config.cap_server_url}/widget.js`} />
134
+ * // <cap-widget data-cap-url={config.cap_server_url} />
135
+ * }
136
+ * ```
137
+ */
138
+ export function useCaptcha(provider?: CaptchaProvider): CaptchaState {
139
+ const [token, setToken] = useState<string | null>(null);
140
+ const [isReady, setIsReady] = useState(false);
141
+ const [isLoading, setIsLoading] = useState(false);
142
+ const [error, setError] = useState<Error | null>(null);
143
+
144
+ // Promise resolver for execute() method
145
+ const executeResolverRef = useRef<((token: string) => void) | null>(null);
146
+ const executeRejecterRef = useRef<((error: Error) => void) | null>(null);
147
+
148
+ // Callback when CAPTCHA is verified
149
+ const onVerify = useCallback((newToken: string) => {
150
+ setToken(newToken);
151
+ setIsLoading(false);
152
+ setError(null);
153
+ setIsReady(true);
154
+
155
+ // Resolve the execute() promise if waiting
156
+ if (executeResolverRef.current) {
157
+ executeResolverRef.current(newToken);
158
+ executeResolverRef.current = null;
159
+ executeRejecterRef.current = null;
160
+ }
161
+ }, []);
162
+
163
+ // Callback when CAPTCHA expires
164
+ const onExpire = useCallback(() => {
165
+ setToken(null);
166
+ setIsReady(true);
167
+ }, []);
168
+
169
+ // Callback when CAPTCHA errors
170
+ const onError = useCallback((err: Error) => {
171
+ setError(err);
172
+ setIsLoading(false);
173
+ setToken(null);
174
+
175
+ // Reject the execute() promise if waiting
176
+ if (executeRejecterRef.current) {
177
+ executeRejecterRef.current(err);
178
+ executeResolverRef.current = null;
179
+ executeRejecterRef.current = null;
180
+ }
181
+ }, []);
182
+
183
+ // Reset the CAPTCHA
184
+ const reset = useCallback(() => {
185
+ setToken(null);
186
+ setError(null);
187
+ setIsLoading(false);
188
+ }, []);
189
+
190
+ // Execute/trigger the CAPTCHA (for invisible CAPTCHA)
191
+ const execute = useCallback(async (): Promise<string> => {
192
+ // If we already have a token, return it
193
+ if (token) {
194
+ return token;
195
+ }
196
+
197
+ // If CAPTCHA is not configured, return empty string
198
+ if (!provider) {
199
+ return "";
200
+ }
201
+
202
+ setIsLoading(true);
203
+ setError(null);
204
+
205
+ // Return a promise that will be resolved by onVerify
206
+ return new Promise<string>((resolve, reject) => {
207
+ executeResolverRef.current = resolve;
208
+ executeRejecterRef.current = reject;
209
+
210
+ // For invisible CAPTCHAs, the widget should call onVerify when done
211
+ // The actual execution is handled by the CAPTCHA widget component
212
+ });
213
+ }, [token, provider]);
214
+
215
+ // Mark as ready when provider is set
216
+ useEffect(() => {
217
+ if (provider) {
218
+ setIsReady(true);
219
+ }
220
+ }, [provider]);
221
+
222
+ return {
223
+ token,
224
+ isReady,
225
+ isLoading,
226
+ error,
227
+ reset,
228
+ execute,
229
+ onVerify,
230
+ onExpire,
231
+ onError,
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Check if CAPTCHA is required for a specific endpoint
237
+ *
238
+ * @param config - CAPTCHA configuration from useCaptchaConfig
239
+ * @param endpoint - The endpoint to check (e.g., 'signup', 'login', 'password_reset')
240
+ * @returns Whether CAPTCHA is required for this endpoint
241
+ */
242
+ export function isCaptchaRequiredForEndpoint(
243
+ config: CaptchaConfig | undefined,
244
+ endpoint: string
245
+ ): boolean {
246
+ if (!config?.enabled) {
247
+ return false;
248
+ }
249
+ return config.endpoints?.includes(endpoint) ?? false;
250
+ }