@pubflow/react 0.2.1 → 0.3.3

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/dist/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createStorageKey, initConfig, ApiClient, AuthService, BridgeApiService, validateWithSchema, FilterOperator } from '@pubflow/core';
2
2
  export * from '@pubflow/core';
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
- import React, { createContext, useState, useEffect, useCallback, useContext } from 'react';
4
+ import React, { createContext, useState, useCallback, useEffect, useContext } from 'react';
5
5
  import useSWR, { SWRConfig, useSWRConfig } from 'swr';
6
6
 
7
7
  /**
@@ -208,67 +208,36 @@ const PubflowContext = createContext({
208
208
  defaultInstance: 'default'
209
209
  });
210
210
  /**
211
- * Pubflow provider for React
211
+ * Pubflow Provider Component
212
212
  */
213
- function PubflowProvider({ children, config, instances, defaultInstance = 'default', onSessionExpired, onSessionRefreshed, showSessionAlerts = false, persistentCache = { enabled: false }, loginRedirectPath = '/login', publicPaths = ['/login', '/register', '/forgot-password'], enableDebugTools = false, theme = {} }) {
214
- const [isInitialized, setIsInitialized] = useState(false);
213
+ function PubflowProvider({ children, config, instances, defaultInstance = 'default', enableDebugTools = false, showSessionAlerts = false, persistentCache = { enabled: false }, theme = {}, loginRedirectPath = '/login', publicPaths = [] }) {
215
214
  const [contextValue, setContextValue] = useState({
216
215
  instances: {},
217
216
  defaultInstance
218
217
  });
219
- // Configure debug tools
220
- useEffect(() => {
221
- if (enableDebugTools) {
222
- console.log('Pubflow debug tools enabled');
223
- // Add debug utilities to window object
224
- if (typeof window !== 'undefined') {
225
- window.pubflowDebug = {
226
- getInstances: () => contextValue.instances,
227
- getDefaultInstance: () => contextValue.defaultInstance,
228
- clearStorage: async () => {
229
- const storage = new BrowserStorageAdapter();
230
- await storage.removeItem('pubflow_session_id');
231
- await storage.removeItem('pubflow_user_data');
232
- console.log('Pubflow storage cleared');
233
- },
234
- loginRedirectPath,
235
- publicPaths,
236
- theme
237
- };
238
- }
239
- }
240
- }, [enableDebugTools]); // ✅ OPTIMIZED: Only enableDebugTools as dependency
241
- // Handle session expiration
218
+ // Session handlers
242
219
  const handleSessionExpired = useCallback(() => {
243
- if (showSessionAlerts) {
244
- alert('Your session has expired. Please log in again.');
245
- }
246
- if (onSessionExpired) {
247
- onSessionExpired();
220
+ if (showSessionAlerts && typeof window !== 'undefined') {
221
+ console.warn('Session expired');
248
222
  }
249
- }, [onSessionExpired, showSessionAlerts]);
250
- // Handle session refresh
223
+ }, [showSessionAlerts]);
251
224
  const handleSessionRefreshed = useCallback(() => {
252
- if (showSessionAlerts) {
253
- alert('Your session has been refreshed.');
225
+ if (showSessionAlerts && typeof window !== 'undefined') {
226
+ console.log('Session refreshed');
254
227
  }
255
- if (onSessionRefreshed) {
256
- onSessionRefreshed();
257
- }
258
- }, [onSessionRefreshed, showSessionAlerts]);
228
+ }, [showSessionAlerts]);
229
+ // Initialize instances
259
230
  useEffect(() => {
260
231
  let isMounted = true;
261
232
  const initialize = async () => {
262
233
  var _a, _b;
263
234
  try {
264
- // Initialize instances
265
235
  const instancesMap = {};
266
236
  if (instances && instances.length > 0) {
267
237
  // Initialize multiple instances
268
238
  for (const instanceConfig of instances) {
269
239
  if (!isMounted)
270
- return; // Prevent state updates if unmounted
271
- // Initialize configuration
240
+ return;
272
241
  const fullConfig = initConfig({
273
242
  ...instanceConfig,
274
243
  sessionConfig: {
@@ -277,38 +246,21 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
277
246
  onSessionRefreshed: handleSessionRefreshed
278
247
  }
279
248
  }, instanceConfig.id);
280
- // Create storage adapter
281
249
  const storage = new BrowserStorageAdapter({
282
250
  prefix: (_a = fullConfig.storageConfig) === null || _a === void 0 ? void 0 : _a.prefix
283
251
  });
284
- // Create API client
285
252
  const apiClient = new ApiClient(fullConfig, storage);
286
- // Create auth service
287
253
  const authService = new AuthService(apiClient, storage, fullConfig);
288
- // Get current user (optimized - no await in initialization)
254
+ // Get current user (non-blocking)
289
255
  let user = null;
290
256
  let isAuthenticated = false;
291
- // Don't block initialization with API calls
292
- authService.getCurrentUser().then(currentUser => {
293
- if (isMounted && currentUser) {
294
- setContextValue(prev => ({
295
- ...prev,
296
- instances: {
297
- ...prev.instances,
298
- [instanceConfig.id]: {
299
- ...prev.instances[instanceConfig.id],
300
- user: currentUser,
301
- isAuthenticated: true,
302
- isLoading: false
303
- }
304
- }
305
- }));
306
- }
307
- }).catch(error => {
308
- console.error(`Error getting current user for instance ${instanceConfig.id}:`, error);
309
- });
310
- // Create instance with optimized functions
311
- const createInstanceFunctions = (id) => ({
257
+ instancesMap[instanceConfig.id] = {
258
+ config: fullConfig,
259
+ apiClient,
260
+ authService,
261
+ user,
262
+ isAuthenticated,
263
+ isLoading: true,
312
264
  login: async (credentials) => {
313
265
  const result = await authService.login(credentials);
314
266
  if (result.success && result.user && isMounted) {
@@ -316,10 +268,11 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
316
268
  ...prev,
317
269
  instances: {
318
270
  ...prev.instances,
319
- [id]: {
320
- ...prev.instances[id],
271
+ [instanceConfig.id]: {
272
+ ...prev.instances[instanceConfig.id],
321
273
  user: result.user,
322
- isAuthenticated: true
274
+ isAuthenticated: true,
275
+ isLoading: false
323
276
  }
324
277
  }
325
278
  }));
@@ -333,10 +286,11 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
333
286
  ...prev,
334
287
  instances: {
335
288
  ...prev.instances,
336
- [id]: {
337
- ...prev.instances[id],
289
+ [instanceConfig.id]: {
290
+ ...prev.instances[instanceConfig.id],
338
291
  user: null,
339
- isAuthenticated: false
292
+ isAuthenticated: false,
293
+ isLoading: false
340
294
  }
341
295
  }
342
296
  }));
@@ -349,31 +303,52 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
349
303
  ...prev,
350
304
  instances: {
351
305
  ...prev.instances,
352
- [id]: {
353
- ...prev.instances[id],
306
+ [instanceConfig.id]: {
307
+ ...prev.instances[instanceConfig.id],
354
308
  user: result.user || null,
355
- isAuthenticated: result.isValid
309
+ isAuthenticated: result.isValid,
310
+ isLoading: false
356
311
  }
357
312
  }
358
313
  }));
359
314
  }
360
315
  return result;
361
316
  }
362
- });
363
- instancesMap[instanceConfig.id] = {
364
- config: fullConfig,
365
- apiClient,
366
- authService,
367
- user,
368
- isAuthenticated,
369
- isLoading: true, // Start as loading
370
- ...createInstanceFunctions(instanceConfig.id)
371
317
  };
318
+ // Load user asynchronously
319
+ authService.getCurrentUser().then(currentUser => {
320
+ if (isMounted) {
321
+ setContextValue(prev => ({
322
+ ...prev,
323
+ instances: {
324
+ ...prev.instances,
325
+ [instanceConfig.id]: {
326
+ ...prev.instances[instanceConfig.id],
327
+ user: currentUser,
328
+ isAuthenticated: !!currentUser,
329
+ isLoading: false
330
+ }
331
+ }
332
+ }));
333
+ }
334
+ }).catch(() => {
335
+ if (isMounted) {
336
+ setContextValue(prev => ({
337
+ ...prev,
338
+ instances: {
339
+ ...prev.instances,
340
+ [instanceConfig.id]: {
341
+ ...prev.instances[instanceConfig.id],
342
+ isLoading: false
343
+ }
344
+ }
345
+ }));
346
+ }
347
+ });
372
348
  }
373
349
  }
374
350
  else if (config) {
375
- // Initialize single instance
376
- // Initialize configuration
351
+ // Single instance mode (simplified)
377
352
  const fullConfig = initConfig({
378
353
  ...config,
379
354
  sessionConfig: {
@@ -381,37 +356,22 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
381
356
  onSessionExpired: handleSessionExpired,
382
357
  onSessionRefreshed: handleSessionRefreshed
383
358
  }
384
- }, defaultInstance);
385
- // Create storage adapter
359
+ }, config.id || defaultInstance);
386
360
  const storage = new BrowserStorageAdapter({
387
361
  prefix: (_b = fullConfig.storageConfig) === null || _b === void 0 ? void 0 : _b.prefix
388
362
  });
389
- // Create API client
390
363
  const apiClient = new ApiClient(fullConfig, storage);
391
- // Create auth service
392
364
  const authService = new AuthService(apiClient, storage, fullConfig);
393
- // Get current user
394
- let user = null;
395
- let isAuthenticated = false;
396
- try {
397
- user = await authService.getCurrentUser();
398
- isAuthenticated = !!user;
399
- }
400
- catch (error) {
401
- console.error('Error getting current user:', error);
402
- }
403
- // Create instance
404
365
  instancesMap[defaultInstance] = {
405
366
  config: fullConfig,
406
367
  apiClient,
407
368
  authService,
408
- user,
409
- isAuthenticated,
410
- isLoading: false,
369
+ user: null,
370
+ isAuthenticated: false,
371
+ isLoading: true,
411
372
  login: async (credentials) => {
412
373
  const result = await authService.login(credentials);
413
- if (result.success && result.user) {
414
- // Update instance
374
+ if (result.success && result.user && isMounted) {
415
375
  setContextValue(prev => ({
416
376
  ...prev,
417
377
  instances: {
@@ -419,7 +379,8 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
419
379
  [defaultInstance]: {
420
380
  ...prev.instances[defaultInstance],
421
381
  user: result.user,
422
- isAuthenticated: true
382
+ isAuthenticated: true,
383
+ isLoading: false
423
384
  }
424
385
  }
425
386
  }));
@@ -428,65 +389,107 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
428
389
  },
429
390
  logout: async () => {
430
391
  await authService.logout();
431
- // Update instance
392
+ if (isMounted) {
393
+ setContextValue(prev => ({
394
+ ...prev,
395
+ instances: {
396
+ ...prev.instances,
397
+ [defaultInstance]: {
398
+ ...prev.instances[defaultInstance],
399
+ user: null,
400
+ isAuthenticated: false,
401
+ isLoading: false
402
+ }
403
+ }
404
+ }));
405
+ }
406
+ },
407
+ validateSession: async () => {
408
+ const result = await authService.validateSession();
409
+ if (isMounted) {
410
+ setContextValue(prev => ({
411
+ ...prev,
412
+ instances: {
413
+ ...prev.instances,
414
+ [defaultInstance]: {
415
+ ...prev.instances[defaultInstance],
416
+ user: result.user || null,
417
+ isAuthenticated: result.isValid,
418
+ isLoading: false
419
+ }
420
+ }
421
+ }));
422
+ }
423
+ return result;
424
+ }
425
+ };
426
+ // Load user asynchronously
427
+ authService.getCurrentUser().then(currentUser => {
428
+ if (isMounted) {
432
429
  setContextValue(prev => ({
433
430
  ...prev,
434
431
  instances: {
435
432
  ...prev.instances,
436
433
  [defaultInstance]: {
437
434
  ...prev.instances[defaultInstance],
438
- user: null,
439
- isAuthenticated: false
435
+ user: currentUser,
436
+ isAuthenticated: !!currentUser,
437
+ isLoading: false
440
438
  }
441
439
  }
442
440
  }));
443
- },
444
- validateSession: async () => {
445
- const result = await authService.validateSession();
446
- // Update instance
441
+ }
442
+ }).catch(() => {
443
+ if (isMounted) {
447
444
  setContextValue(prev => ({
448
445
  ...prev,
449
446
  instances: {
450
447
  ...prev.instances,
451
448
  [defaultInstance]: {
452
449
  ...prev.instances[defaultInstance],
453
- user: result.user || null,
454
- isAuthenticated: result.isValid
450
+ isLoading: false
455
451
  }
456
452
  }
457
453
  }));
458
- return result;
459
454
  }
460
- };
455
+ });
456
+ }
457
+ if (isMounted) {
458
+ setContextValue({
459
+ instances: instancesMap,
460
+ defaultInstance
461
+ });
461
462
  }
462
- // Set context value
463
- setContextValue({
464
- instances: instancesMap,
465
- defaultInstance
466
- });
467
- // Set initialized
468
- setIsInitialized(true);
469
463
  }
470
464
  catch (error) {
471
465
  console.error('Error initializing Pubflow:', error);
472
466
  }
473
467
  };
474
468
  initialize();
475
- // Cleanup function
476
469
  return () => {
477
470
  isMounted = false;
478
471
  };
479
472
  }, [config, instances, defaultInstance, handleSessionExpired, handleSessionRefreshed]);
480
- // Show loading state
481
- if (!isInitialized) {
482
- return null;
483
- }
484
- // If persistent cache is enabled, wrap with SWRConfig
485
- if (persistentCache.enabled && persistentCache.provider) {
486
- return (jsx(SWRConfig, { value: { provider: persistentCache.provider }, children: jsx(PubflowContext.Provider, { value: contextValue, children: children }) }));
487
- }
488
- // Otherwise, just use the regular provider
489
- return (jsx(PubflowContext.Provider, { value: contextValue, children: children }));
473
+ // Debug tools (simplified)
474
+ useEffect(() => {
475
+ if (enableDebugTools && typeof window !== 'undefined') {
476
+ window.pubflowDebug = {
477
+ getInstances: () => contextValue.instances,
478
+ getDefaultInstance: () => contextValue.defaultInstance
479
+ };
480
+ }
481
+ }, [enableDebugTools]);
482
+ // SWR configuration
483
+ const swrConfig = {
484
+ revalidateOnFocus: false,
485
+ revalidateOnReconnect: true,
486
+ ...(persistentCache.enabled && {
487
+ provider: () => new Map(),
488
+ isOnline: () => true,
489
+ isVisible: () => true
490
+ })
491
+ };
492
+ return (jsx(PubflowContext.Provider, { value: contextValue, children: jsx(SWRConfig, { value: swrConfig, children: children }) }));
490
493
  }
491
494
 
492
495
  /**
@@ -502,7 +505,6 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
502
505
  */
503
506
  function useAuth(instanceId) {
504
507
  const context = useContext(PubflowContext);
505
- const [isLoading, setIsLoading] = useState(false);
506
508
  if (!context) {
507
509
  throw new Error('useAuth must be used within a PubflowProvider');
508
510
  }
@@ -511,68 +513,14 @@ function useAuth(instanceId) {
511
513
  if (!pubflowInstance) {
512
514
  throw new Error(`Pubflow instance '${instance}' not found`);
513
515
  }
514
- // Wrap login to handle loading state
515
- const login = useCallback(async (credentials) => {
516
- setIsLoading(true);
517
- try {
518
- return await pubflowInstance.login(credentials);
519
- }
520
- finally {
521
- setIsLoading(false);
522
- }
523
- }, [pubflowInstance]);
524
- // Wrap logout to handle loading state
525
- const logout = useCallback(async () => {
526
- setIsLoading(true);
527
- try {
528
- await pubflowInstance.logout();
529
- }
530
- finally {
531
- setIsLoading(false);
532
- }
533
- }, [pubflowInstance]);
534
- // Wrap validateSession to handle loading state
535
- const validateSession = useCallback(async () => {
536
- setIsLoading(true);
537
- try {
538
- return await pubflowInstance.validateSession();
539
- }
540
- finally {
541
- setIsLoading(false);
542
- }
543
- }, [pubflowInstance]);
544
- // Add refreshUser function to manually refresh user data
545
- const refreshUser = useCallback(async () => {
546
- setIsLoading(true);
547
- try {
548
- const result = await pubflowInstance.validateSession();
549
- return result.user || null;
550
- }
551
- finally {
552
- setIsLoading(false);
553
- }
554
- }, [pubflowInstance]);
555
- // Validate session on mount
556
- useEffect(() => {
557
- const validate = async () => {
558
- try {
559
- await validateSession();
560
- }
561
- catch (error) {
562
- console.error('Error validating session:', error);
563
- }
564
- };
565
- validate();
566
- }, [validateSession]);
567
516
  return {
568
517
  // Ensure user is never undefined
569
518
  user: pubflowInstance.user || null,
570
519
  isAuthenticated: pubflowInstance.isAuthenticated,
571
- isLoading,
572
- login,
573
- logout,
574
- validateSession,
575
- refreshUser
520
+ isLoading: pubflowInstance.isLoading,
521
+ login: pubflowInstance.login,
522
+ logout: pubflowInstance.logout,
523
+ validateSession: pubflowInstance.validateSession
576
524
  };
577
525
  }
578
526
 
@@ -8611,6 +8559,68 @@ function BridgeList({ renderItem, showPagination = true, showSearch = true, show
8611
8559
  }) })), renderPagination()] })] })] }));
8612
8560
  }
8613
8561
 
8562
+ /**
8563
+ * Asset Utilities for Pubflow React
8564
+ *
8565
+ * Utilities for handling assets like logos, images, and other media files
8566
+ * Supports both external URLs and internal assets with proper Vite/bundler integration
8567
+ */
8568
+ /**
8569
+ * Process asset URL - supports both external URLs and internal assets
8570
+ *
8571
+ * @param assetUrl - The asset URL to process
8572
+ * @returns Processed URL ready for use in src attributes
8573
+ */
8574
+ function processAssetUrl(assetUrl) {
8575
+ // Return non-string values as-is (React components, null, undefined)
8576
+ if (!assetUrl || typeof assetUrl !== 'string') {
8577
+ return assetUrl;
8578
+ }
8579
+ // Return empty string as-is (no logo case)
8580
+ if (assetUrl.trim() === '') {
8581
+ return '';
8582
+ }
8583
+ // Check if it's an external URL (starts with http:// or https://)
8584
+ if (assetUrl.startsWith('http://') || assetUrl.startsWith('https://')) {
8585
+ return assetUrl;
8586
+ }
8587
+ // Check if it's a data URL (base64 encoded image)
8588
+ if (assetUrl.startsWith('data:')) {
8589
+ return assetUrl;
8590
+ }
8591
+ // Check if it's already a processed Vite asset (starts with /)
8592
+ if (assetUrl.startsWith('/')) {
8593
+ return assetUrl;
8594
+ }
8595
+ // Check if it's a blob URL
8596
+ if (assetUrl.startsWith('blob:')) {
8597
+ return assetUrl;
8598
+ }
8599
+ // For relative paths or simple filenames, assume they're in the public directory
8600
+ // This handles cases like 'logo.svg', './logo.png', '../assets/logo.jpg', etc.
8601
+ if (assetUrl.includes('.')) {
8602
+ // Remove leading ./ if present
8603
+ const cleanPath = assetUrl.replace(/^\.\//, '');
8604
+ // If it starts with ../, keep it as is (relative to current directory)
8605
+ if (assetUrl.startsWith('../')) {
8606
+ return assetUrl;
8607
+ }
8608
+ // For simple filenames or paths, prepend with /
8609
+ return `/${cleanPath}`;
8610
+ }
8611
+ // Return as-is for any other cases
8612
+ return assetUrl;
8613
+ }
8614
+ /**
8615
+ * Process logo URL specifically - alias for processAssetUrl with better naming
8616
+ *
8617
+ * @param logoUrl - The logo URL to process
8618
+ * @returns Processed URL ready for use in img src
8619
+ */
8620
+ function processLogoUrl(logoUrl) {
8621
+ return processAssetUrl(logoUrl);
8622
+ }
8623
+
8614
8624
  /**
8615
8625
  * Professional login form component
8616
8626
  */
@@ -8791,7 +8801,7 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8791
8801
  textAlign: 'center'
8792
8802
  }
8793
8803
  };
8794
- return (jsxs("div", { className: className, style: styles.container, children: [jsxs("div", { style: styles.logoSection, children: [logo && (typeof logo === 'string' ? (jsx("img", { src: logo, alt: `${appName} Logo`, style: styles.logo })) : (logo)), jsxs("h1", { style: styles.welcomeText, children: ["Welcome to ", appName] }), jsx("p", { style: styles.subtitleText, children: "Sign in to your account" })] }), jsxs("form", { onSubmit: handleSubmit, children: [error && (jsxs("div", { style: styles.errorContainer, children: [jsx("span", { style: { color: '#ff4757', fontSize: '20px' }, children: "\u26A0" }), jsx("span", { style: styles.errorText, children: error })] })), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Email Address" }), jsxs("div", { style: styles.inputContainer, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\u2709" }), jsx("input", { type: "email", style: styles.input, placeholder: "Enter your email", value: email, onChange: (e) => setEmail(e.target.value), autoCapitalize: "none", autoComplete: "email", disabled: isLoading, required: true })] })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Password" }), jsxs("div", { style: styles.inputContainer, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD12" }), jsx("input", { type: showPassword ? 'text' : 'password', style: styles.input, placeholder: "Enter your password", value: password, onChange: (e) => setPassword(e.target.value), autoComplete: "current-password", disabled: isLoading, required: true }), jsx("button", { type: "button", style: styles.eyeButton, onClick: () => setShowPassword(!showPassword), disabled: isLoading, children: showPassword ? '👁' : '👁‍🗨' })] })] }), jsxs("div", { style: styles.checkboxContainer, children: [jsxs("label", { style: { display: 'flex', alignItems: 'center', fontSize: '14px', color: '#666' }, children: [jsx("input", { type: "checkbox", checked: rememberMe, onChange: (e) => setRememberMe(e.target.checked), style: { marginRight: '8px' } }), "Remember me"] }), showPasswordReset && (jsx("button", { type: "button", style: { ...styles.linkButton, fontSize: '14px' }, onClick: onPasswordReset, disabled: isLoading, children: "Forgot password?" }))] }), jsx("button", { type: "submit", style: {
8804
+ return (jsxs("div", { className: className, style: styles.container, children: [jsxs("div", { style: styles.logoSection, children: [logo && (typeof logo === 'string' ? (jsx("img", { src: processLogoUrl(logo), alt: `${appName} Logo`, style: styles.logo })) : (logo)), jsxs("h1", { style: styles.welcomeText, children: ["Welcome to ", appName] }), jsx("p", { style: styles.subtitleText, children: "Sign in to your account" })] }), jsxs("form", { onSubmit: handleSubmit, children: [error && (jsxs("div", { style: styles.errorContainer, children: [jsx("span", { style: { color: '#ff4757', fontSize: '20px' }, children: "\u26A0" }), jsx("span", { style: styles.errorText, children: error })] })), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Email Address" }), jsxs("div", { style: styles.inputContainer, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\u2709" }), jsx("input", { type: "email", style: styles.input, placeholder: "Enter your email", value: email, onChange: (e) => setEmail(e.target.value), autoCapitalize: "none", autoComplete: "email", disabled: isLoading, required: true })] })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Password" }), jsxs("div", { style: styles.inputContainer, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD12" }), jsx("input", { type: showPassword ? 'text' : 'password', style: styles.input, placeholder: "Enter your password", value: password, onChange: (e) => setPassword(e.target.value), autoComplete: "current-password", disabled: isLoading, required: true }), jsx("button", { type: "button", style: styles.eyeButton, onClick: () => setShowPassword(!showPassword), disabled: isLoading, children: showPassword ? '👁' : '👁‍🗨' })] })] }), jsxs("div", { style: styles.checkboxContainer, children: [jsxs("label", { style: { display: 'flex', alignItems: 'center', fontSize: '14px', color: '#666' }, children: [jsx("input", { type: "checkbox", checked: rememberMe, onChange: (e) => setRememberMe(e.target.checked), style: { marginRight: '8px' } }), "Remember me"] }), showPasswordReset && (jsx("button", { type: "button", style: { ...styles.linkButton, fontSize: '14px' }, onClick: onPasswordReset, disabled: isLoading, children: "Forgot password?" }))] }), jsx("button", { type: "submit", style: {
8795
8805
  ...styles.loginButton,
8796
8806
  opacity: isLoading ? 0.8 : 1,
8797
8807
  cursor: isLoading ? 'not-allowed' : 'pointer'
@@ -9041,7 +9051,7 @@ function PasswordResetForm({ config = {}, onSuccess, onError, onBackToLogin, res
9041
9051
  const renderStep = () => {
9042
9052
  switch (step) {
9043
9053
  case 'request':
9044
- return (jsxs(Fragment, { children: [jsxs("div", { style: styles.logoSection, children: [logo && (typeof logo === 'string' ? (jsx("img", { src: logo, alt: `${appName} Logo`, style: styles.logo })) : (logo)), jsx("h1", { style: styles.title, children: "Reset Password" }), jsx("p", { style: styles.subtitle, children: "Enter your email to receive reset instructions" })] }), jsxs("form", { onSubmit: handleRequestReset, children: [error && (jsxs("div", { style: styles.errorContainer, children: [jsx("span", { style: { color: '#ff4757', fontSize: '20px' }, children: "\u26A0" }), jsx("span", { style: styles.errorText, children: error })] })), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Email Address" }), jsxs("div", { style: styles.inputContainer, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\u2709" }), jsx("input", { type: "email", style: styles.input, placeholder: "Enter your email", value: email, onChange: (e) => setEmail(e.target.value), autoCapitalize: "none", autoComplete: "email", disabled: isLoading, required: true })] })] }), jsx("button", { type: "submit", style: {
9054
+ return (jsxs(Fragment, { children: [jsxs("div", { style: styles.logoSection, children: [logo && (typeof logo === 'string' ? (jsx("img", { src: processLogoUrl(logo), alt: `${appName} Logo`, style: styles.logo })) : (logo)), jsx("h1", { style: styles.title, children: "Reset Password" }), jsx("p", { style: styles.subtitle, children: "Enter your email to receive reset instructions" })] }), jsxs("form", { onSubmit: handleRequestReset, children: [error && (jsxs("div", { style: styles.errorContainer, children: [jsx("span", { style: { color: '#ff4757', fontSize: '20px' }, children: "\u26A0" }), jsx("span", { style: styles.errorText, children: error })] })), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Email Address" }), jsxs("div", { style: styles.inputContainer, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\u2709" }), jsx("input", { type: "email", style: styles.input, placeholder: "Enter your email", value: email, onChange: (e) => setEmail(e.target.value), autoCapitalize: "none", autoComplete: "email", disabled: isLoading, required: true })] })] }), jsx("button", { type: "submit", style: {
9045
9055
  ...styles.button,
9046
9056
  opacity: isLoading ? 0.8 : 1,
9047
9057
  cursor: isLoading ? 'not-allowed' : 'pointer'
@@ -9305,7 +9315,7 @@ function AccountCreationForm({ config = {}, onSuccess, onError, onBackToLogin })
9305
9315
  const renderStep = () => {
9306
9316
  switch (step) {
9307
9317
  case 'form':
9308
- return (jsxs(Fragment, { children: [jsxs("div", { style: styles.logoSection, children: [logo && (typeof logo === 'string' ? (jsx("img", { src: logo, alt: `${appName} Logo`, style: styles.logo })) : (logo)), jsx("h1", { style: styles.title, children: "Create Account" }), jsxs("p", { style: styles.subtitle, children: ["Join ", appName, " today"] })] }), jsxs("form", { onSubmit: handleSubmit, children: [errors.general && (jsxs("div", { style: { ...styles.successContainer, backgroundColor: '#fef2f2', borderColor: '#fecaca' }, children: [jsx("span", { style: { color: '#ff4757', fontSize: '20px' }, children: "\u26A0" }), jsx("span", { style: { ...styles.successText, color: '#dc2626' }, children: errors.general })] })), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "First Name *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.name ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDC64" }), jsx("input", { type: "text", style: styles.input, placeholder: "Enter your first name", value: formData.name, onChange: (e) => updateField('name', e.target.value), autoComplete: "given-name", disabled: isLoading, required: true })] }), errors.name && jsx("div", { style: styles.errorText, children: errors.name })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Last Name *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.lastName ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDC64" }), jsx("input", { type: "text", style: styles.input, placeholder: "Enter your last name", value: formData.lastName, onChange: (e) => updateField('lastName', e.target.value), autoComplete: "family-name", disabled: isLoading, required: true })] }), errors.lastName && jsx("div", { style: styles.errorText, children: errors.lastName })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Email Address *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.email ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\u2709" }), jsx("input", { type: "email", style: styles.input, placeholder: "Enter your email", value: formData.email, onChange: (e) => updateField('email', e.target.value), autoCapitalize: "none", autoComplete: "email", disabled: isLoading, required: true })] }), errors.email && jsx("div", { style: styles.errorText, children: errors.email })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Password *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.password ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD12" }), jsx("input", { type: showPassword ? 'text' : 'password', style: styles.input, placeholder: "Create a password", value: formData.password, onChange: (e) => updateField('password', e.target.value), autoComplete: "new-password", disabled: isLoading, required: true }), jsx("button", { type: "button", style: styles.eyeButton, onClick: () => setShowPassword(!showPassword), disabled: isLoading, children: showPassword ? '👁' : '👁‍🗨' })] }), errors.password && jsx("div", { style: styles.errorText, children: errors.password })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Confirm Password *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.confirmPassword ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD12" }), jsx("input", { type: showConfirmPassword ? 'text' : 'password', style: styles.input, placeholder: "Confirm your password", value: formData.confirmPassword, onChange: (e) => updateField('confirmPassword', e.target.value), autoComplete: "new-password", disabled: isLoading, required: true }), jsx("button", { type: "button", style: styles.eyeButton, onClick: () => setShowConfirmPassword(!showConfirmPassword), disabled: isLoading, children: showConfirmPassword ? '👁' : '👁‍🗨' })] }), errors.confirmPassword && jsx("div", { style: styles.errorText, children: errors.confirmPassword })] }), jsx("button", { type: "submit", style: {
9318
+ return (jsxs(Fragment, { children: [jsxs("div", { style: styles.logoSection, children: [logo && (typeof logo === 'string' ? (jsx("img", { src: processLogoUrl(logo), alt: `${appName} Logo`, style: styles.logo })) : (logo)), jsx("h1", { style: styles.title, children: "Create Account" }), jsxs("p", { style: styles.subtitle, children: ["Join ", appName, " today"] })] }), jsxs("form", { onSubmit: handleSubmit, children: [errors.general && (jsxs("div", { style: { ...styles.successContainer, backgroundColor: '#fef2f2', borderColor: '#fecaca' }, children: [jsx("span", { style: { color: '#ff4757', fontSize: '20px' }, children: "\u26A0" }), jsx("span", { style: { ...styles.successText, color: '#dc2626' }, children: errors.general })] })), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "First Name *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.name ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDC64" }), jsx("input", { type: "text", style: styles.input, placeholder: "Enter your first name", value: formData.name, onChange: (e) => updateField('name', e.target.value), autoComplete: "given-name", disabled: isLoading, required: true })] }), errors.name && jsx("div", { style: styles.errorText, children: errors.name })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Last Name *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.lastName ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDC64" }), jsx("input", { type: "text", style: styles.input, placeholder: "Enter your last name", value: formData.lastName, onChange: (e) => updateField('lastName', e.target.value), autoComplete: "family-name", disabled: isLoading, required: true })] }), errors.lastName && jsx("div", { style: styles.errorText, children: errors.lastName })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Email Address *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.email ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\u2709" }), jsx("input", { type: "email", style: styles.input, placeholder: "Enter your email", value: formData.email, onChange: (e) => updateField('email', e.target.value), autoCapitalize: "none", autoComplete: "email", disabled: isLoading, required: true })] }), errors.email && jsx("div", { style: styles.errorText, children: errors.email })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Password *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.password ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD12" }), jsx("input", { type: showPassword ? 'text' : 'password', style: styles.input, placeholder: "Create a password", value: formData.password, onChange: (e) => updateField('password', e.target.value), autoComplete: "new-password", disabled: isLoading, required: true }), jsx("button", { type: "button", style: styles.eyeButton, onClick: () => setShowPassword(!showPassword), disabled: isLoading, children: showPassword ? '👁' : '👁‍🗨' })] }), errors.password && jsx("div", { style: styles.errorText, children: errors.password })] }), jsxs("div", { style: styles.inputGroup, children: [jsx("label", { style: styles.inputLabel, children: "Confirm Password *" }), jsxs("div", { style: { ...styles.inputContainer, ...(errors.confirmPassword ? styles.inputError : {}) }, children: [jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD12" }), jsx("input", { type: showConfirmPassword ? 'text' : 'password', style: styles.input, placeholder: "Confirm your password", value: formData.confirmPassword, onChange: (e) => updateField('confirmPassword', e.target.value), autoComplete: "new-password", disabled: isLoading, required: true }), jsx("button", { type: "button", style: styles.eyeButton, onClick: () => setShowConfirmPassword(!showConfirmPassword), disabled: isLoading, children: showConfirmPassword ? '👁' : '👁‍🗨' })] }), errors.confirmPassword && jsx("div", { style: styles.errorText, children: errors.confirmPassword })] }), jsx("button", { type: "submit", style: {
9309
9319
  ...styles.button,
9310
9320
  opacity: isLoading ? 0.8 : 1,
9311
9321
  cursor: isLoading ? 'not-allowed' : 'pointer'
@@ -9582,6 +9592,131 @@ function createStyles(theme) {
9582
9592
  };
9583
9593
  }
9584
9594
 
9595
+ /**
9596
+ * Authentication Guard Hook for React
9597
+ *
9598
+ * Framework-agnostic hook for handling authentication validation and redirects
9599
+ */
9600
+ /**
9601
+ * Hook for handling authentication validation and redirects
9602
+ *
9603
+ * This hook is framework-agnostic and requires the user to provide
9604
+ * their own redirect function (e.g., Next.js router, React Router, etc.)
9605
+ *
9606
+ * @param options Hook options
9607
+ * @returns Authentication guard result
9608
+ */
9609
+ function useAuthGuard({ validateOnMount = true, allowedTypes = ['authenticated'], instanceId, onRedirect, onSessionExpired, loginRedirectPath = '/login', accessDeniedPath = '/access-denied', enableLogging = false } = {}) {
9610
+ const { user, isAuthenticated, isLoading, validateSession } = useAuth(instanceId);
9611
+ // Check if user is authorized based on user type
9612
+ const isAuthorized = useCallback(() => {
9613
+ if (!isAuthenticated || !user) {
9614
+ return false;
9615
+ }
9616
+ // If allowedTypes includes 'authenticated', any authenticated user is allowed
9617
+ if (allowedTypes.includes('authenticated')) {
9618
+ return true;
9619
+ }
9620
+ // Check specific user types
9621
+ const userType = user.userType || user.user_type || '';
9622
+ return allowedTypes.some(type => type.toLowerCase() === userType.toLowerCase());
9623
+ }, [isAuthenticated, user, allowedTypes]);
9624
+ const authorized = isAuthorized();
9625
+ // Determine redirect reason
9626
+ const getRedirectReason = useCallback(() => {
9627
+ if (isLoading) {
9628
+ return null;
9629
+ }
9630
+ if (!isAuthenticated) {
9631
+ return 'unauthenticated';
9632
+ }
9633
+ if (!authorized) {
9634
+ return 'unauthorized';
9635
+ }
9636
+ return null;
9637
+ }, [isLoading, isAuthenticated, authorized]);
9638
+ const redirectReason = getRedirectReason();
9639
+ // Validate session on mount
9640
+ useEffect(() => {
9641
+ if (validateOnMount && isAuthenticated) {
9642
+ validateSession().then(({ isValid }) => {
9643
+ if (!isValid) {
9644
+ if (enableLogging) {
9645
+ console.warn('useAuthGuard: Session validation failed');
9646
+ }
9647
+ // Call session expired handler
9648
+ if (onSessionExpired) {
9649
+ onSessionExpired();
9650
+ }
9651
+ // Redirect to login
9652
+ if (onRedirect) {
9653
+ onRedirect(loginRedirectPath, 'session-expired');
9654
+ }
9655
+ }
9656
+ }).catch(error => {
9657
+ if (enableLogging) {
9658
+ console.error('useAuthGuard: Session validation error:', error);
9659
+ }
9660
+ });
9661
+ }
9662
+ }, [validateOnMount, isAuthenticated, validateSession, onSessionExpired, onRedirect, loginRedirectPath, enableLogging]);
9663
+ // Handle authentication redirects
9664
+ useEffect(() => {
9665
+ if (isLoading || !onRedirect) {
9666
+ return;
9667
+ }
9668
+ if (!isAuthenticated) {
9669
+ if (enableLogging) {
9670
+ console.warn('useAuthGuard: User not authenticated, redirecting to login');
9671
+ }
9672
+ onRedirect(loginRedirectPath, 'unauthenticated');
9673
+ return;
9674
+ }
9675
+ if (!authorized) {
9676
+ if (enableLogging) {
9677
+ console.warn('useAuthGuard: User not authorized for this page, redirecting to access denied');
9678
+ }
9679
+ onRedirect(accessDeniedPath, 'unauthorized');
9680
+ return;
9681
+ }
9682
+ }, [isLoading, isAuthenticated, authorized, onRedirect, loginRedirectPath, accessDeniedPath, enableLogging]);
9683
+ return {
9684
+ user,
9685
+ isAuthenticated,
9686
+ isLoading,
9687
+ isAuthorized: authorized,
9688
+ validateSession,
9689
+ redirectReason
9690
+ };
9691
+ }
9692
+ /**
9693
+ * Simple authentication guard hook with automatic redirects
9694
+ *
9695
+ * This is a simplified version that uses window.location for redirects
9696
+ * Suitable for simple React apps without complex routing
9697
+ *
9698
+ * @param options Hook options
9699
+ * @returns Authentication guard result
9700
+ */
9701
+ function useSimpleAuthGuard(options = {}) {
9702
+ var _a;
9703
+ const handleRedirect = useCallback((path, reason) => {
9704
+ if (typeof window !== 'undefined') {
9705
+ // Add current path as redirect parameter
9706
+ const currentPath = window.location.pathname + window.location.search;
9707
+ const redirectUrl = reason === 'unauthenticated'
9708
+ ? `${path}?redirect=${encodeURIComponent(currentPath)}`
9709
+ : path;
9710
+ window.location.href = redirectUrl;
9711
+ }
9712
+ }, []);
9713
+ return useAuthGuard({
9714
+ ...options,
9715
+ onRedirect: handleRedirect,
9716
+ enableLogging: (_a = options.enableLogging) !== null && _a !== void 0 ? _a : true
9717
+ });
9718
+ }
9719
+
9585
9720
  /**
9586
9721
  * Bridge API Raw Hook for React
9587
9722
  *
@@ -9661,65 +9796,6 @@ function useBridgeApiRaw(instanceId) {
9661
9796
  };
9662
9797
  }
9663
9798
 
9664
- /**
9665
- * Server Authentication Hook for React
9666
- *
9667
- * Provides a hook for handling authentication with automatic redirects
9668
- */
9669
- /**
9670
- * Hook for handling authentication with automatic redirects
9671
- *
9672
- * @param options Hook options
9673
- * @returns Authentication hook result
9674
- */
9675
- function useServerAuth({ loginRedirectPath = '/login', allowedTypes = ['authenticated'], validateOnMount = true, instanceId, onRedirect } = {}) {
9676
- const { user, isAuthenticated, isLoading, validateSession } = useAuth(instanceId);
9677
- // Default redirect function
9678
- const defaultRedirect = (path) => {
9679
- if (typeof window !== 'undefined') {
9680
- const currentPath = window.location.pathname;
9681
- const redirectUrl = `${path}?redirect=${encodeURIComponent(currentPath)}`;
9682
- window.location.href = redirectUrl;
9683
- }
9684
- };
9685
- const redirect = onRedirect || defaultRedirect;
9686
- useEffect(() => {
9687
- // Validate session on mount if configured
9688
- if (validateOnMount) {
9689
- validateSession().then(({ isValid }) => {
9690
- if (!isValid) {
9691
- redirect(loginRedirectPath);
9692
- }
9693
- });
9694
- }
9695
- }, [validateOnMount, loginRedirectPath, validateSession, redirect]);
9696
- useEffect(() => {
9697
- // Skip if still loading
9698
- if (isLoading) {
9699
- return;
9700
- }
9701
- // Redirect if not authenticated
9702
- if (!isAuthenticated) {
9703
- redirect(loginRedirectPath);
9704
- return;
9705
- }
9706
- // Check user type if allowedTypes doesn't include 'authenticated'
9707
- if (!allowedTypes.includes('authenticated') && user) {
9708
- const userType = user.userType || '';
9709
- const isAllowed = allowedTypes.some(type => type.toLowerCase() === userType.toLowerCase());
9710
- if (!isAllowed) {
9711
- redirect('/access-denied');
9712
- }
9713
- }
9714
- }, [isLoading, isAuthenticated, user, allowedTypes, loginRedirectPath, redirect]);
9715
- return {
9716
- user,
9717
- isAuthenticated,
9718
- isLoading,
9719
- validateSession
9720
- };
9721
- }
9722
-
9723
9799
  /**
9724
9800
  * Search Query Builder Hook for React
9725
9801
  *
@@ -9941,5 +10017,5 @@ function useRequireAuth(options = {}) {
9941
10017
  };
9942
10018
  }
9943
10019
 
9944
- export { AccountCreationForm, AdvancedFilter, BridgeForm, BridgeList, BridgeTable, BridgeView, BrowserStorageAdapter, LoginForm, OfflineIndicator, PasswordResetForm, PubflowContext, PubflowProvider, ThemeProvider, createStyles, generateCSSVariables, useAuth, useBridgeApi, useBridgeApiRaw, useBridgeCrud, useBridgeMutation, useBridgeQuery, useNetworkStatus, useRequireAuth, useSearchQueryBuilder, useServerAuth, useTheme };
10020
+ export { AccountCreationForm, AdvancedFilter, BridgeForm, BridgeList, BridgeTable, BridgeView, BrowserStorageAdapter, LoginForm, OfflineIndicator, PasswordResetForm, PubflowContext, PubflowProvider, ThemeProvider, createStyles, generateCSSVariables, useAuth, useAuthGuard, useBridgeApi, useBridgeApiRaw, useBridgeCrud, useBridgeMutation, useBridgeQuery, useNetworkStatus, useRequireAuth, useSearchQueryBuilder, useSimpleAuthGuard, useTheme };
9945
10021
  //# sourceMappingURL=index.esm.js.map