@nimbleflux/fluxbase-sdk-react 2026.3.6-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.
- package/.nvmrc +1 -0
- package/README-ADMIN.md +1076 -0
- package/README.md +195 -0
- package/examples/AdminDashboard.tsx +513 -0
- package/examples/README.md +163 -0
- package/package.json +66 -0
- package/src/context.test.tsx +147 -0
- package/src/context.tsx +33 -0
- package/src/index.test.ts +255 -0
- package/src/index.ts +175 -0
- package/src/test-setup.ts +22 -0
- package/src/test-utils.tsx +215 -0
- package/src/use-admin-auth.test.ts +175 -0
- package/src/use-admin-auth.ts +187 -0
- package/src/use-admin-hooks.test.ts +457 -0
- package/src/use-admin-hooks.ts +309 -0
- package/src/use-auth-config.test.ts +145 -0
- package/src/use-auth-config.ts +101 -0
- package/src/use-auth.test.ts +313 -0
- package/src/use-auth.ts +164 -0
- package/src/use-captcha.test.ts +273 -0
- package/src/use-captcha.ts +250 -0
- package/src/use-client-keys.test.ts +286 -0
- package/src/use-client-keys.ts +185 -0
- package/src/use-graphql.test.ts +424 -0
- package/src/use-graphql.ts +392 -0
- package/src/use-query.test.ts +348 -0
- package/src/use-query.ts +211 -0
- package/src/use-realtime.test.ts +359 -0
- package/src/use-realtime.ts +180 -0
- package/src/use-saml.test.ts +269 -0
- package/src/use-saml.ts +221 -0
- package/src/use-storage.test.ts +549 -0
- package/src/use-storage.ts +508 -0
- package/src/use-table-export.ts +481 -0
- package/src/use-users.test.ts +264 -0
- package/src/use-users.ts +198 -0
- package/tsconfig.json +28 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +11 -0
- package/typedoc.json +33 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Settings and Management Hooks
|
|
3
|
+
*
|
|
4
|
+
* Hooks for managing application settings, system settings, and webhooks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
8
|
+
import { useFluxbaseClient } from './context'
|
|
9
|
+
import type {
|
|
10
|
+
AppSettings,
|
|
11
|
+
UpdateAppSettingsRequest,
|
|
12
|
+
SystemSetting,
|
|
13
|
+
UpdateSystemSettingRequest,
|
|
14
|
+
Webhook,
|
|
15
|
+
CreateWebhookRequest,
|
|
16
|
+
UpdateWebhookRequest
|
|
17
|
+
} from '@fluxbase/sdk'
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// useAppSettings Hook
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export interface UseAppSettingsOptions {
|
|
24
|
+
autoFetch?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UseAppSettingsReturn {
|
|
28
|
+
settings: AppSettings | null
|
|
29
|
+
isLoading: boolean
|
|
30
|
+
error: Error | null
|
|
31
|
+
refetch: () => Promise<void>
|
|
32
|
+
updateSettings: (update: UpdateAppSettingsRequest) => Promise<void>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Hook for managing application settings
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* function SettingsPanel() {
|
|
41
|
+
* const { settings, isLoading, updateSettings } = useAppSettings({ autoFetch: true })
|
|
42
|
+
*
|
|
43
|
+
* const handleToggleFeature = async (feature: string, enabled: boolean) => {
|
|
44
|
+
* await updateSettings({
|
|
45
|
+
* features: { ...settings?.features, [feature]: enabled }
|
|
46
|
+
* })
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* return <div>...</div>
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function useAppSettings(options: UseAppSettingsOptions = {}): UseAppSettingsReturn {
|
|
54
|
+
const { autoFetch = true } = options
|
|
55
|
+
const client = useFluxbaseClient()
|
|
56
|
+
|
|
57
|
+
const [settings, setSettings] = useState<AppSettings | null>(null)
|
|
58
|
+
const [isLoading, setIsLoading] = useState(autoFetch)
|
|
59
|
+
const [error, setError] = useState<Error | null>(null)
|
|
60
|
+
|
|
61
|
+
const fetchSettings = useCallback(async () => {
|
|
62
|
+
try {
|
|
63
|
+
setIsLoading(true)
|
|
64
|
+
setError(null)
|
|
65
|
+
const appSettings = await client.admin.settings.app.get()
|
|
66
|
+
setSettings(appSettings)
|
|
67
|
+
} catch (err) {
|
|
68
|
+
setError(err as Error)
|
|
69
|
+
} finally {
|
|
70
|
+
setIsLoading(false)
|
|
71
|
+
}
|
|
72
|
+
}, [client])
|
|
73
|
+
|
|
74
|
+
const updateSettings = useCallback(
|
|
75
|
+
async (update: UpdateAppSettingsRequest): Promise<void> => {
|
|
76
|
+
await client.admin.settings.app.update(update)
|
|
77
|
+
await fetchSettings()
|
|
78
|
+
},
|
|
79
|
+
[client, fetchSettings]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (autoFetch) {
|
|
84
|
+
fetchSettings()
|
|
85
|
+
}
|
|
86
|
+
}, [autoFetch, fetchSettings])
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
settings,
|
|
90
|
+
isLoading,
|
|
91
|
+
error,
|
|
92
|
+
refetch: fetchSettings,
|
|
93
|
+
updateSettings
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// useSystemSettings Hook
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
export interface UseSystemSettingsOptions {
|
|
102
|
+
autoFetch?: boolean
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface UseSystemSettingsReturn {
|
|
106
|
+
settings: SystemSetting[]
|
|
107
|
+
isLoading: boolean
|
|
108
|
+
error: Error | null
|
|
109
|
+
refetch: () => Promise<void>
|
|
110
|
+
getSetting: (key: string) => SystemSetting | undefined
|
|
111
|
+
updateSetting: (key: string, update: UpdateSystemSettingRequest) => Promise<void>
|
|
112
|
+
deleteSetting: (key: string) => Promise<void>
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Hook for managing system settings (key-value storage)
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```tsx
|
|
120
|
+
* function SystemSettings() {
|
|
121
|
+
* const { settings, isLoading, updateSetting } = useSystemSettings({ autoFetch: true })
|
|
122
|
+
*
|
|
123
|
+
* const handleUpdateSetting = async (key: string, value: any) => {
|
|
124
|
+
* await updateSetting(key, { value })
|
|
125
|
+
* }
|
|
126
|
+
*
|
|
127
|
+
* return <div>...</div>
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function useSystemSettings(options: UseSystemSettingsOptions = {}): UseSystemSettingsReturn {
|
|
132
|
+
const { autoFetch = true } = options
|
|
133
|
+
const client = useFluxbaseClient()
|
|
134
|
+
|
|
135
|
+
const [settings, setSettings] = useState<SystemSetting[]>([])
|
|
136
|
+
const [isLoading, setIsLoading] = useState(autoFetch)
|
|
137
|
+
const [error, setError] = useState<Error | null>(null)
|
|
138
|
+
|
|
139
|
+
const fetchSettings = useCallback(async () => {
|
|
140
|
+
try {
|
|
141
|
+
setIsLoading(true)
|
|
142
|
+
setError(null)
|
|
143
|
+
const response = await client.admin.settings.system.list()
|
|
144
|
+
setSettings(response.settings)
|
|
145
|
+
} catch (err) {
|
|
146
|
+
setError(err as Error)
|
|
147
|
+
} finally {
|
|
148
|
+
setIsLoading(false)
|
|
149
|
+
}
|
|
150
|
+
}, [client])
|
|
151
|
+
|
|
152
|
+
const getSetting = useCallback(
|
|
153
|
+
(key: string): SystemSetting | undefined => {
|
|
154
|
+
return settings.find((s) => s.key === key)
|
|
155
|
+
},
|
|
156
|
+
[settings]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
const updateSetting = useCallback(
|
|
160
|
+
async (key: string, update: UpdateSystemSettingRequest): Promise<void> => {
|
|
161
|
+
await client.admin.settings.system.update(key, update)
|
|
162
|
+
await fetchSettings()
|
|
163
|
+
},
|
|
164
|
+
[client, fetchSettings]
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
const deleteSetting = useCallback(
|
|
168
|
+
async (key: string): Promise<void> => {
|
|
169
|
+
await client.admin.settings.system.delete(key)
|
|
170
|
+
await fetchSettings()
|
|
171
|
+
},
|
|
172
|
+
[client, fetchSettings]
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (autoFetch) {
|
|
177
|
+
fetchSettings()
|
|
178
|
+
}
|
|
179
|
+
}, [autoFetch, fetchSettings])
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
settings,
|
|
183
|
+
isLoading,
|
|
184
|
+
error,
|
|
185
|
+
refetch: fetchSettings,
|
|
186
|
+
getSetting,
|
|
187
|
+
updateSetting,
|
|
188
|
+
deleteSetting
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// useWebhooks Hook
|
|
194
|
+
// ============================================================================
|
|
195
|
+
|
|
196
|
+
export interface UseWebhooksOptions {
|
|
197
|
+
autoFetch?: boolean
|
|
198
|
+
refetchInterval?: number
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface UseWebhooksReturn {
|
|
202
|
+
webhooks: Webhook[]
|
|
203
|
+
isLoading: boolean
|
|
204
|
+
error: Error | null
|
|
205
|
+
refetch: () => Promise<void>
|
|
206
|
+
createWebhook: (webhook: CreateWebhookRequest) => Promise<Webhook>
|
|
207
|
+
updateWebhook: (id: string, update: UpdateWebhookRequest) => Promise<Webhook>
|
|
208
|
+
deleteWebhook: (id: string) => Promise<void>
|
|
209
|
+
testWebhook: (id: string) => Promise<void>
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Hook for managing webhooks
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```tsx
|
|
217
|
+
* function WebhooksManager() {
|
|
218
|
+
* const { webhooks, isLoading, createWebhook, deleteWebhook } = useWebhooks({
|
|
219
|
+
* autoFetch: true
|
|
220
|
+
* })
|
|
221
|
+
*
|
|
222
|
+
* const handleCreate = async () => {
|
|
223
|
+
* await createWebhook({
|
|
224
|
+
* url: 'https://example.com/webhook',
|
|
225
|
+
* events: ['user.created', 'user.updated'],
|
|
226
|
+
* enabled: true
|
|
227
|
+
* })
|
|
228
|
+
* }
|
|
229
|
+
*
|
|
230
|
+
* return <div>...</div>
|
|
231
|
+
* }
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
export function useWebhooks(options: UseWebhooksOptions = {}): UseWebhooksReturn {
|
|
235
|
+
const { autoFetch = true, refetchInterval = 0 } = options
|
|
236
|
+
const client = useFluxbaseClient()
|
|
237
|
+
|
|
238
|
+
const [webhooks, setWebhooks] = useState<Webhook[]>([])
|
|
239
|
+
const [isLoading, setIsLoading] = useState(autoFetch)
|
|
240
|
+
const [error, setError] = useState<Error | null>(null)
|
|
241
|
+
|
|
242
|
+
const fetchWebhooks = useCallback(async () => {
|
|
243
|
+
try {
|
|
244
|
+
setIsLoading(true)
|
|
245
|
+
setError(null)
|
|
246
|
+
const response = await client.admin.management.webhooks.list()
|
|
247
|
+
setWebhooks(response.webhooks)
|
|
248
|
+
} catch (err) {
|
|
249
|
+
setError(err as Error)
|
|
250
|
+
} finally {
|
|
251
|
+
setIsLoading(false)
|
|
252
|
+
}
|
|
253
|
+
}, [client])
|
|
254
|
+
|
|
255
|
+
const createWebhook = useCallback(
|
|
256
|
+
async (webhook: CreateWebhookRequest): Promise<Webhook> => {
|
|
257
|
+
const created = await client.admin.management.webhooks.create(webhook)
|
|
258
|
+
await fetchWebhooks()
|
|
259
|
+
return created
|
|
260
|
+
},
|
|
261
|
+
[client, fetchWebhooks]
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
const updateWebhook = useCallback(
|
|
265
|
+
async (id: string, update: UpdateWebhookRequest): Promise<Webhook> => {
|
|
266
|
+
const updated = await client.admin.management.webhooks.update(id, update)
|
|
267
|
+
await fetchWebhooks()
|
|
268
|
+
return updated
|
|
269
|
+
},
|
|
270
|
+
[client, fetchWebhooks]
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
const deleteWebhook = useCallback(
|
|
274
|
+
async (id: string): Promise<void> => {
|
|
275
|
+
await client.admin.management.webhooks.delete(id)
|
|
276
|
+
await fetchWebhooks()
|
|
277
|
+
},
|
|
278
|
+
[client, fetchWebhooks]
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
const testWebhook = useCallback(
|
|
282
|
+
async (id: string): Promise<void> => {
|
|
283
|
+
await client.admin.management.webhooks.test(id)
|
|
284
|
+
},
|
|
285
|
+
[client]
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (autoFetch) {
|
|
290
|
+
fetchWebhooks()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (refetchInterval > 0) {
|
|
294
|
+
const interval = setInterval(fetchWebhooks, refetchInterval)
|
|
295
|
+
return () => clearInterval(interval)
|
|
296
|
+
}
|
|
297
|
+
}, [autoFetch, refetchInterval, fetchWebhooks])
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
webhooks,
|
|
301
|
+
isLoading,
|
|
302
|
+
error,
|
|
303
|
+
refetch: fetchWebhooks,
|
|
304
|
+
createWebhook,
|
|
305
|
+
updateWebhook,
|
|
306
|
+
deleteWebhook,
|
|
307
|
+
testWebhook
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for auth configuration hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
7
|
+
import { useAuthConfig } from './use-auth-config';
|
|
8
|
+
import { createMockClient, createWrapper } from './test-utils';
|
|
9
|
+
|
|
10
|
+
describe('useAuthConfig', () => {
|
|
11
|
+
it('should fetch auth configuration', async () => {
|
|
12
|
+
const mockConfig = {
|
|
13
|
+
signup_enabled: true,
|
|
14
|
+
email_verification_required: true,
|
|
15
|
+
magic_link_enabled: true,
|
|
16
|
+
mfa_enabled: true,
|
|
17
|
+
password_min_length: 8,
|
|
18
|
+
password_require_uppercase: true,
|
|
19
|
+
password_require_lowercase: true,
|
|
20
|
+
password_require_number: true,
|
|
21
|
+
password_require_special: false,
|
|
22
|
+
oauth_providers: [
|
|
23
|
+
{ provider: 'google', display_name: 'Google', authorize_url: 'https://google.com/oauth' },
|
|
24
|
+
],
|
|
25
|
+
saml_providers: [],
|
|
26
|
+
captcha: { enabled: false },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getAuthConfigMock = vi.fn().mockResolvedValue({ data: mockConfig, error: null });
|
|
30
|
+
|
|
31
|
+
const client = createMockClient({
|
|
32
|
+
auth: { getAuthConfig: getAuthConfigMock },
|
|
33
|
+
} as any);
|
|
34
|
+
|
|
35
|
+
const { result } = renderHook(
|
|
36
|
+
() => useAuthConfig(),
|
|
37
|
+
{ wrapper: createWrapper(client) }
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
41
|
+
expect(result.current.data).toEqual(mockConfig);
|
|
42
|
+
expect(getAuthConfigMock).toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw error on fetch failure', async () => {
|
|
46
|
+
const error = new Error('Failed to fetch config');
|
|
47
|
+
const getAuthConfigMock = vi.fn().mockResolvedValue({ data: null, error });
|
|
48
|
+
|
|
49
|
+
const client = createMockClient({
|
|
50
|
+
auth: { getAuthConfig: getAuthConfigMock },
|
|
51
|
+
} as any);
|
|
52
|
+
|
|
53
|
+
const { result } = renderHook(
|
|
54
|
+
() => useAuthConfig(),
|
|
55
|
+
{ wrapper: createWrapper(client) }
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
59
|
+
expect(result.current.error).toBe(error);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should have appropriate stale time', async () => {
|
|
63
|
+
const mockConfig = { signup_enabled: true };
|
|
64
|
+
const getAuthConfigMock = vi.fn().mockResolvedValue({ data: mockConfig, error: null });
|
|
65
|
+
|
|
66
|
+
const client = createMockClient({
|
|
67
|
+
auth: { getAuthConfig: getAuthConfigMock },
|
|
68
|
+
} as any);
|
|
69
|
+
|
|
70
|
+
const { result } = renderHook(
|
|
71
|
+
() => useAuthConfig(),
|
|
72
|
+
{ wrapper: createWrapper(client) }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
76
|
+
|
|
77
|
+
// The config should be cached with a stale time of 5 minutes
|
|
78
|
+
// Verify the data is available
|
|
79
|
+
expect(result.current.data).toEqual(mockConfig);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should return signup enabled status', async () => {
|
|
83
|
+
const mockConfig = { signup_enabled: false };
|
|
84
|
+
const getAuthConfigMock = vi.fn().mockResolvedValue({ data: mockConfig, error: null });
|
|
85
|
+
|
|
86
|
+
const client = createMockClient({
|
|
87
|
+
auth: { getAuthConfig: getAuthConfigMock },
|
|
88
|
+
} as any);
|
|
89
|
+
|
|
90
|
+
const { result } = renderHook(
|
|
91
|
+
() => useAuthConfig(),
|
|
92
|
+
{ wrapper: createWrapper(client) }
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
96
|
+
expect(result.current.data?.signup_enabled).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should return OAuth providers', async () => {
|
|
100
|
+
const mockConfig = {
|
|
101
|
+
oauth_providers: [
|
|
102
|
+
{ provider: 'google', display_name: 'Google', authorize_url: 'https://google.com' },
|
|
103
|
+
{ provider: 'github', display_name: 'GitHub', authorize_url: 'https://github.com' },
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
const getAuthConfigMock = vi.fn().mockResolvedValue({ data: mockConfig, error: null });
|
|
107
|
+
|
|
108
|
+
const client = createMockClient({
|
|
109
|
+
auth: { getAuthConfig: getAuthConfigMock },
|
|
110
|
+
} as any);
|
|
111
|
+
|
|
112
|
+
const { result } = renderHook(
|
|
113
|
+
() => useAuthConfig(),
|
|
114
|
+
{ wrapper: createWrapper(client) }
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
118
|
+
expect(result.current.data?.oauth_providers).toHaveLength(2);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return password requirements', async () => {
|
|
122
|
+
const mockConfig = {
|
|
123
|
+
password_min_length: 12,
|
|
124
|
+
password_require_uppercase: true,
|
|
125
|
+
password_require_lowercase: true,
|
|
126
|
+
password_require_number: true,
|
|
127
|
+
password_require_special: true,
|
|
128
|
+
};
|
|
129
|
+
const getAuthConfigMock = vi.fn().mockResolvedValue({ data: mockConfig, error: null });
|
|
130
|
+
|
|
131
|
+
const client = createMockClient({
|
|
132
|
+
auth: { getAuthConfig: getAuthConfigMock },
|
|
133
|
+
} as any);
|
|
134
|
+
|
|
135
|
+
const { result } = renderHook(
|
|
136
|
+
() => useAuthConfig(),
|
|
137
|
+
{ wrapper: createWrapper(client) }
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
141
|
+
expect(result.current.data?.password_min_length).toBe(12);
|
|
142
|
+
expect(result.current.data?.password_require_uppercase).toBe(true);
|
|
143
|
+
expect(result.current.data?.password_require_special).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth configuration hooks for Fluxbase SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides hooks to fetch comprehensive authentication configuration
|
|
5
|
+
* from the server including signup status, OAuth/SAML providers,
|
|
6
|
+
* password requirements, and CAPTCHA settings.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useQuery } from "@tanstack/react-query";
|
|
10
|
+
import { useFluxbaseClient } from "./context";
|
|
11
|
+
import type { AuthConfig } from "@fluxbase/sdk";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hook to get the complete authentication configuration from the server
|
|
15
|
+
*
|
|
16
|
+
* Returns all public auth settings in a single request including:
|
|
17
|
+
* - Signup enabled status
|
|
18
|
+
* - Email verification requirements
|
|
19
|
+
* - Magic link availability
|
|
20
|
+
* - MFA availability
|
|
21
|
+
* - Password requirements (length, complexity)
|
|
22
|
+
* - Available OAuth providers (Google, GitHub, etc.)
|
|
23
|
+
* - Available SAML providers (enterprise SSO)
|
|
24
|
+
* - CAPTCHA configuration
|
|
25
|
+
*
|
|
26
|
+
* Use this to conditionally render UI elements based on server configuration,
|
|
27
|
+
* such as hiding signup forms when signup is disabled or displaying available
|
|
28
|
+
* OAuth provider buttons.
|
|
29
|
+
*
|
|
30
|
+
* @returns Query result with authentication configuration
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* function AuthPage() {
|
|
35
|
+
* const { data: config, isLoading } = useAuthConfig();
|
|
36
|
+
*
|
|
37
|
+
* if (isLoading) return <Loading />;
|
|
38
|
+
*
|
|
39
|
+
* return (
|
|
40
|
+
* <div>
|
|
41
|
+
* {config?.signup_enabled && (
|
|
42
|
+
* <SignupForm passwordMinLength={config.password_min_length} />
|
|
43
|
+
* )}
|
|
44
|
+
*
|
|
45
|
+
* {config?.oauth_providers.map(provider => (
|
|
46
|
+
* <OAuthButton
|
|
47
|
+
* key={provider.provider}
|
|
48
|
+
* provider={provider.provider}
|
|
49
|
+
* displayName={provider.display_name}
|
|
50
|
+
* authorizeUrl={provider.authorize_url}
|
|
51
|
+
* />
|
|
52
|
+
* ))}
|
|
53
|
+
*
|
|
54
|
+
* {config?.saml_providers.map(provider => (
|
|
55
|
+
* <SAMLButton
|
|
56
|
+
* key={provider.provider}
|
|
57
|
+
* provider={provider.provider}
|
|
58
|
+
* displayName={provider.display_name}
|
|
59
|
+
* />
|
|
60
|
+
* ))}
|
|
61
|
+
* </div>
|
|
62
|
+
* );
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @example Showing password requirements
|
|
67
|
+
* ```tsx
|
|
68
|
+
* function PasswordInput() {
|
|
69
|
+
* const { data: config } = useAuthConfig();
|
|
70
|
+
*
|
|
71
|
+
* return (
|
|
72
|
+
* <div>
|
|
73
|
+
* <input type="password" minLength={config?.password_min_length} />
|
|
74
|
+
* <ul>
|
|
75
|
+
* <li>Minimum {config?.password_min_length || 8} characters</li>
|
|
76
|
+
* {config?.password_require_uppercase && <li>One uppercase letter</li>}
|
|
77
|
+
* {config?.password_require_lowercase && <li>One lowercase letter</li>}
|
|
78
|
+
* {config?.password_require_number && <li>One number</li>}
|
|
79
|
+
* {config?.password_require_special && <li>One special character</li>}
|
|
80
|
+
* </ul>
|
|
81
|
+
* </div>
|
|
82
|
+
* );
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export function useAuthConfig() {
|
|
87
|
+
const client = useFluxbaseClient();
|
|
88
|
+
|
|
89
|
+
return useQuery<AuthConfig>({
|
|
90
|
+
queryKey: ["fluxbase", "auth", "config"],
|
|
91
|
+
queryFn: async () => {
|
|
92
|
+
const { data, error } = await client.auth.getAuthConfig();
|
|
93
|
+
if (error) {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
return data!;
|
|
97
|
+
},
|
|
98
|
+
staleTime: 1000 * 60 * 5, // Cache for 5 minutes (config changes infrequently)
|
|
99
|
+
gcTime: 1000 * 60 * 60, // Keep in cache for 1 hour
|
|
100
|
+
});
|
|
101
|
+
}
|