@pubflow/react 0.4.3 → 0.4.5

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.cjs CHANGED
@@ -206,7 +206,8 @@ class BrowserStorageAdapter {
206
206
  */
207
207
  const PubflowContext = React.createContext({
208
208
  instances: {},
209
- defaultInstance: 'default'
209
+ defaultInstance: 'default',
210
+ isReady: false
210
211
  });
211
212
  /**
212
213
  * Pubflow Provider Component
@@ -214,7 +215,8 @@ const PubflowContext = React.createContext({
214
215
  function PubflowProvider({ children, config, instances, defaultInstance = 'default', enableDebugTools = false, showSessionAlerts = false, persistentCache = { enabled: false }, theme = {}, loginRedirectPath = '/login', publicPaths = [] }) {
215
216
  const [contextValue, setContextValue] = React.useState({
216
217
  instances: {},
217
- defaultInstance
218
+ defaultInstance,
219
+ isReady: false
218
220
  });
219
221
  // Session handlers
220
222
  const handleSessionExpired = React.useCallback(() => {
@@ -252,6 +254,7 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
252
254
  });
253
255
  const apiClient = new core.ApiClient(fullConfig, storage);
254
256
  const authService = new core.AuthService(apiClient, storage, fullConfig);
257
+ const twoFactorService = new core.TwoFactorService(apiClient, fullConfig);
255
258
  // Get current user (non-blocking)
256
259
  let user = null;
257
260
  let isAuthenticated = false;
@@ -259,12 +262,35 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
259
262
  config: fullConfig,
260
263
  apiClient,
261
264
  authService,
265
+ twoFactorService,
262
266
  user,
263
267
  isAuthenticated,
264
268
  isLoading: true,
269
+ twoFactorPending: false,
270
+ twoFactorMethods: [],
265
271
  login: async (credentials) => {
266
272
  const result = await authService.login(credentials);
267
- if (result.success && result.user && isMounted) {
273
+ if (!isMounted)
274
+ return result;
275
+ if (result.success && result.requires2fa) {
276
+ // 2FA required — keep session pending, expose methods
277
+ setContextValue(prev => {
278
+ var _a;
279
+ return ({
280
+ ...prev,
281
+ instances: {
282
+ ...prev.instances,
283
+ [instanceConfig.id]: {
284
+ ...prev.instances[instanceConfig.id],
285
+ isLoading: false,
286
+ twoFactorPending: true,
287
+ twoFactorMethods: (_a = result.availableMethods) !== null && _a !== void 0 ? _a : [],
288
+ }
289
+ }
290
+ });
291
+ });
292
+ }
293
+ else if (result.success && result.user) {
268
294
  setContextValue(prev => ({
269
295
  ...prev,
270
296
  instances: {
@@ -273,7 +299,9 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
273
299
  ...prev.instances[instanceConfig.id],
274
300
  user: result.user,
275
301
  isAuthenticated: true,
276
- isLoading: false
302
+ isLoading: false,
303
+ twoFactorPending: false,
304
+ twoFactorMethods: [],
277
305
  }
278
306
  }
279
307
  }));
@@ -291,7 +319,9 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
291
319
  ...prev.instances[instanceConfig.id],
292
320
  user: null,
293
321
  isAuthenticated: false,
294
- isLoading: false
322
+ isLoading: false,
323
+ twoFactorPending: false,
324
+ twoFactorMethods: [],
295
325
  }
296
326
  }
297
327
  }));
@@ -314,7 +344,31 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
314
344
  }));
315
345
  }
316
346
  return result;
317
- }
347
+ },
348
+ verifyTwoFactor: async (methodId, code) => {
349
+ const result = await twoFactorService.verify(methodId, code, 'login');
350
+ if (result.verified && isMounted) {
351
+ // Fetch the now-active user from the server
352
+ const userData = await authService.getCurrentUser();
353
+ setContextValue(prev => ({
354
+ ...prev,
355
+ instances: {
356
+ ...prev.instances,
357
+ [instanceConfig.id]: {
358
+ ...prev.instances[instanceConfig.id],
359
+ user: userData,
360
+ isAuthenticated: true,
361
+ twoFactorPending: false,
362
+ twoFactorMethods: [],
363
+ }
364
+ }
365
+ }));
366
+ }
367
+ return result;
368
+ },
369
+ startTwoFactor: async (methodId, method) => {
370
+ return twoFactorService.start(methodId, method, 'login');
371
+ },
318
372
  };
319
373
  // Load user asynchronously
320
374
  authService.getCurrentUser().then(currentUser => {
@@ -363,16 +417,39 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
363
417
  });
364
418
  const apiClient = new core.ApiClient(fullConfig, storage);
365
419
  const authService = new core.AuthService(apiClient, storage, fullConfig);
420
+ const twoFactorService = new core.TwoFactorService(apiClient, fullConfig);
366
421
  instancesMap[defaultInstance] = {
367
422
  config: fullConfig,
368
423
  apiClient,
369
424
  authService,
425
+ twoFactorService,
370
426
  user: null,
371
427
  isAuthenticated: false,
372
428
  isLoading: true,
429
+ twoFactorPending: false,
430
+ twoFactorMethods: [],
373
431
  login: async (credentials) => {
374
432
  const result = await authService.login(credentials);
375
- if (result.success && result.user && isMounted) {
433
+ if (!isMounted)
434
+ return result;
435
+ if (result.success && result.requires2fa) {
436
+ setContextValue(prev => {
437
+ var _a;
438
+ return ({
439
+ ...prev,
440
+ instances: {
441
+ ...prev.instances,
442
+ [defaultInstance]: {
443
+ ...prev.instances[defaultInstance],
444
+ isLoading: false,
445
+ twoFactorPending: true,
446
+ twoFactorMethods: (_a = result.availableMethods) !== null && _a !== void 0 ? _a : [],
447
+ }
448
+ }
449
+ });
450
+ });
451
+ }
452
+ else if (result.success && result.user) {
376
453
  setContextValue(prev => ({
377
454
  ...prev,
378
455
  instances: {
@@ -381,7 +458,9 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
381
458
  ...prev.instances[defaultInstance],
382
459
  user: result.user,
383
460
  isAuthenticated: true,
384
- isLoading: false
461
+ isLoading: false,
462
+ twoFactorPending: false,
463
+ twoFactorMethods: [],
385
464
  }
386
465
  }
387
466
  }));
@@ -399,7 +478,9 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
399
478
  ...prev.instances[defaultInstance],
400
479
  user: null,
401
480
  isAuthenticated: false,
402
- isLoading: false
481
+ isLoading: false,
482
+ twoFactorPending: false,
483
+ twoFactorMethods: [],
403
484
  }
404
485
  }
405
486
  }));
@@ -422,7 +503,30 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
422
503
  }));
423
504
  }
424
505
  return result;
425
- }
506
+ },
507
+ verifyTwoFactor: async (methodId, code) => {
508
+ const result = await twoFactorService.verify(methodId, code, 'login');
509
+ if (result.verified && isMounted) {
510
+ const userData = await authService.getCurrentUser();
511
+ setContextValue(prev => ({
512
+ ...prev,
513
+ instances: {
514
+ ...prev.instances,
515
+ [defaultInstance]: {
516
+ ...prev.instances[defaultInstance],
517
+ user: userData,
518
+ isAuthenticated: true,
519
+ twoFactorPending: false,
520
+ twoFactorMethods: [],
521
+ }
522
+ }
523
+ }));
524
+ }
525
+ return result;
526
+ },
527
+ startTwoFactor: async (methodId, method) => {
528
+ return twoFactorService.start(methodId, method, 'login');
529
+ },
426
530
  };
427
531
  // Load user asynchronously
428
532
  authService.getCurrentUser().then(currentUser => {
@@ -459,9 +563,14 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
459
563
  if (isMounted && Object.keys(instancesMap).length > 0) {
460
564
  setContextValue({
461
565
  instances: instancesMap,
462
- defaultInstance
566
+ defaultInstance,
567
+ isReady: true
463
568
  });
464
569
  }
570
+ else if (isMounted) {
571
+ // Even if no instances configured correctly, mark ready so useAuth can react
572
+ setContextValue(prev => ({ ...prev, isReady: true }));
573
+ }
465
574
  }
466
575
  catch (error) {
467
576
  console.error('Error initializing Pubflow:', error);
@@ -512,6 +621,23 @@ function useAuth(instanceId) {
512
621
  }
513
622
  const instance = instanceId || context.defaultInstance;
514
623
  const pubflowInstance = context.instances[instance];
624
+ // If the provider hasn't finished its initial useEffect setup,
625
+ // instances won't exist yet. Return a loading state instead of crashing.
626
+ if (!context.isReady) {
627
+ return {
628
+ user: null,
629
+ isAuthenticated: false,
630
+ isLoading: true, // Crucial: tell the UI we are still loading the provider
631
+ twoFactorPending: false,
632
+ twoFactorMethods: [],
633
+ login: async () => { throw new Error('Pubflow not initialized yet'); },
634
+ logout: async () => { throw new Error('Pubflow not initialized yet'); },
635
+ validateSession: async () => ({ isValid: false }),
636
+ verifyTwoFactor: async () => ({ success: false, error: 'Pubflow not initialized yet' }),
637
+ startTwoFactor: async () => ({ success: false, error: 'Pubflow not initialized yet' }),
638
+ };
639
+ }
640
+ // After provider is ready, if the instance is STILL missing, it's a real config error.
515
641
  if (!pubflowInstance) {
516
642
  throw new Error(`Pubflow instance '${instance}' not found`);
517
643
  }
@@ -520,9 +646,13 @@ function useAuth(instanceId) {
520
646
  user: pubflowInstance.user || null,
521
647
  isAuthenticated: pubflowInstance.isAuthenticated,
522
648
  isLoading: pubflowInstance.isLoading,
649
+ twoFactorPending: pubflowInstance.twoFactorPending,
650
+ twoFactorMethods: pubflowInstance.twoFactorMethods,
523
651
  login: pubflowInstance.login,
524
652
  logout: pubflowInstance.logout,
525
- validateSession: pubflowInstance.validateSession
653
+ validateSession: pubflowInstance.validateSession,
654
+ verifyTwoFactor: pubflowInstance.verifyTwoFactor,
655
+ startTwoFactor: pubflowInstance.startTwoFactor,
526
656
  };
527
657
  }
528
658
 
@@ -8627,17 +8757,25 @@ function processLogoUrl(logoUrl) {
8627
8757
  * Professional login form component
8628
8758
  */
8629
8759
  function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccountCreation, instanceId }) {
8630
- const { login, isLoading } = useAuth(instanceId);
8760
+ const { login, verifyTwoFactor, startTwoFactor, isLoading } = useAuth(instanceId);
8631
8761
  // Form state
8632
8762
  const [email, setEmail] = React.useState('');
8633
8763
  const [password, setPassword] = React.useState('');
8634
8764
  const [showPassword, setShowPassword] = React.useState(false);
8635
8765
  const [error, setError] = React.useState('');
8636
8766
  const [rememberMe, setRememberMe] = React.useState(false);
8767
+ // 2FA state
8768
+ const [step, setStep] = React.useState('credentials');
8769
+ const [otpCode, setOtpCode] = React.useState('');
8770
+ const [pendingMethods, setPendingMethods] = React.useState([]);
8771
+ const [activeMethodId, setActiveMethodId] = React.useState(null);
8772
+ const [activeMethodName, setActiveMethodName] = React.useState('email');
8773
+ const [otpLoading, setOtpLoading] = React.useState(false);
8637
8774
  // Configuration with defaults
8638
8775
  const { primaryColor = process.env.REACT_APP_PRIMARY_COLOR || '#006aff', secondaryColor = process.env.REACT_APP_SECONDARY_COLOR || '#4a90e2', appName = process.env.REACT_APP_NAME || 'Pubflow', logo = process.env.REACT_APP_LOGO, showPasswordReset = true, showAccountCreation = true, className = '', redirectPath } = config;
8639
8776
  // Handle form submission
8640
8777
  const handleSubmit = async (e) => {
8778
+ var _a;
8641
8779
  e.preventDefault();
8642
8780
  setError('');
8643
8781
  // Basic validation
@@ -8651,6 +8789,17 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8651
8789
  }
8652
8790
  try {
8653
8791
  const result = await login({ email: email.toLowerCase().trim(), password });
8792
+ if (result.requires2fa) {
8793
+ // Switch to OTP step
8794
+ const methods = (_a = result.availableMethods) !== null && _a !== void 0 ? _a : [];
8795
+ setPendingMethods(methods);
8796
+ if (methods.length > 0) {
8797
+ setActiveMethodId(methods[0].id);
8798
+ setActiveMethodName(methods[0].method);
8799
+ }
8800
+ setStep('otp');
8801
+ return;
8802
+ }
8654
8803
  if (result.success) {
8655
8804
  if (onSuccess) {
8656
8805
  onSuccess(result.user);
@@ -8676,6 +8825,56 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8676
8825
  }
8677
8826
  }
8678
8827
  };
8828
+ // Handle OTP verification
8829
+ const handleOtpSubmit = async (e) => {
8830
+ e.preventDefault();
8831
+ if (!activeMethodId)
8832
+ return;
8833
+ setError('');
8834
+ setOtpLoading(true);
8835
+ try {
8836
+ const result = await verifyTwoFactor(activeMethodId, otpCode.trim());
8837
+ if (result.verified) {
8838
+ if (onSuccess)
8839
+ onSuccess(null);
8840
+ if (redirectPath && typeof window !== 'undefined') {
8841
+ window.location.href = redirectPath;
8842
+ }
8843
+ }
8844
+ else {
8845
+ const msg = result.error ||
8846
+ (result.attempts_remaining !== undefined
8847
+ ? `Incorrect code. ${result.attempts_remaining} attempt(s) remaining.`
8848
+ : 'Incorrect code.');
8849
+ setError(msg);
8850
+ if (onError)
8851
+ onError(msg);
8852
+ }
8853
+ }
8854
+ catch (err) {
8855
+ const msg = err instanceof Error ? err.message : 'Verification failed';
8856
+ setError(msg);
8857
+ if (onError)
8858
+ onError(msg);
8859
+ }
8860
+ finally {
8861
+ setOtpLoading(false);
8862
+ }
8863
+ };
8864
+ // Handle resend code
8865
+ const handleResend = async () => {
8866
+ if (!activeMethodId)
8867
+ return;
8868
+ setError('');
8869
+ setOtpLoading(true);
8870
+ try {
8871
+ await startTwoFactor(activeMethodId, activeMethodName);
8872
+ }
8873
+ catch ( /* ignore */_a) { /* ignore */ }
8874
+ finally {
8875
+ setOtpLoading(false);
8876
+ }
8877
+ };
8679
8878
  // Dynamic styles
8680
8879
  const styles = {
8681
8880
  container: {
@@ -8691,6 +8890,7 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8691
8890
  },
8692
8891
  logoSection: {
8693
8892
  display: 'flex',
8893
+ flexDirection: 'column',
8694
8894
  justifyContent: 'center',
8695
8895
  alignItems: 'center',
8696
8896
  textAlign: 'center',
@@ -8813,11 +9013,17 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8813
9013
  textAlign: 'center'
8814
9014
  }
8815
9015
  };
8816
- return (jsxRuntime.jsxs("div", { className: className, style: styles.container, children: [jsxRuntime.jsxs("div", { style: styles.logoSection, children: [logo && (typeof logo === 'string' ? (jsxRuntime.jsx("img", { src: processLogoUrl(logo), alt: `${appName} Logo`, style: styles.logo })) : (logo)), jsxRuntime.jsxs("h1", { style: styles.welcomeText, children: ["Welcome to ", appName] }), jsxRuntime.jsx("p", { style: styles.subtitleText, children: "Sign in to your account" })] }), jsxRuntime.jsxs("form", { onSubmit: handleSubmit, children: [error && (jsxRuntime.jsxs("div", { style: styles.errorContainer, children: [jsxRuntime.jsx("span", { style: { color: '#ff4757', fontSize: '20px' }, children: "\u26A0" }), jsxRuntime.jsx("span", { style: styles.errorText, children: error })] })), jsxRuntime.jsxs("div", { style: styles.inputGroup, children: [jsxRuntime.jsx("label", { style: styles.inputLabel, children: "Email Address" }), jsxRuntime.jsxs("div", { style: styles.inputContainer, children: [jsxRuntime.jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\u2709" }), jsxRuntime.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 })] })] }), jsxRuntime.jsxs("div", { style: styles.inputGroup, children: [jsxRuntime.jsx("label", { style: styles.inputLabel, children: "Password" }), jsxRuntime.jsxs("div", { style: styles.inputContainer, children: [jsxRuntime.jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD12" }), jsxRuntime.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 }), jsxRuntime.jsx("button", { type: "button", style: styles.eyeButton, onClick: () => setShowPassword(!showPassword), disabled: isLoading, children: showPassword ? '👁' : '👁‍🗨' })] })] }), jsxRuntime.jsxs("div", { style: styles.checkboxContainer, children: [jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', fontSize: '14px', color: '#666' }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: rememberMe, onChange: (e) => setRememberMe(e.target.checked), style: { marginRight: '8px' } }), "Remember me"] }), showPasswordReset && (jsxRuntime.jsx("button", { type: "button", style: { ...styles.linkButton, fontSize: '14px' }, onClick: onPasswordReset, disabled: isLoading, children: "Forgot password?" }))] }), jsxRuntime.jsx("button", { type: "submit", style: {
9016
+ return (jsxRuntime.jsxs("div", { className: className, style: styles.container, children: [jsxRuntime.jsxs("div", { style: styles.logoSection, children: [logo && (typeof logo === 'string' ? (jsxRuntime.jsx("img", { src: processLogoUrl(logo), alt: `${appName} Logo`, style: styles.logo })) : (logo)), jsxRuntime.jsxs("h1", { style: styles.welcomeText, children: ["Welcome to ", appName] }), jsxRuntime.jsx("p", { style: styles.subtitleText, children: step === 'otp' ? 'Enter the verification code we sent you.' : 'Sign in to your account' })] }), error && (jsxRuntime.jsxs("div", { style: styles.errorContainer, children: [jsxRuntime.jsx("span", { style: { color: '#ff4757', fontSize: '20px' }, children: "\u26A0" }), jsxRuntime.jsx("span", { style: styles.errorText, children: error })] })), step === 'otp' ? (jsxRuntime.jsxs("form", { onSubmit: handleOtpSubmit, children: [jsxRuntime.jsxs("div", { style: styles.inputGroup, children: [jsxRuntime.jsx("label", { style: styles.inputLabel, children: "Verification Code" }), jsxRuntime.jsxs("div", { style: styles.inputContainer, children: [jsxRuntime.jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD10" }), jsxRuntime.jsx("input", { type: "text", inputMode: "numeric", pattern: "[0-9]*", maxLength: 6, style: { ...styles.input, letterSpacing: '0.4em', textAlign: 'center' }, placeholder: "000000", value: otpCode, onChange: (e) => setOtpCode(e.target.value.replace(/\D/g, '')), autoFocus: true, autoComplete: "one-time-code", disabled: otpLoading, required: true })] })] }), jsxRuntime.jsx("button", { type: "submit", style: {
9017
+ ...styles.loginButton,
9018
+ opacity: otpLoading || otpCode.length < 6 ? 0.7 : 1,
9019
+ cursor: otpLoading || otpCode.length < 6 ? 'not-allowed' : 'pointer',
9020
+ }, disabled: otpLoading || otpCode.length < 6, children: otpLoading ? '⟳ Verifying…' : 'Verify Code' }), jsxRuntime.jsxs("div", { style: { textAlign: 'center', marginTop: '16px' }, children: [jsxRuntime.jsx("button", { type: "button", style: { ...styles.linkButton, fontSize: '14px' }, onClick: handleResend, disabled: otpLoading, children: "Didn't receive a code? Resend" }), jsxRuntime.jsx("button", { type: "button", style: { ...styles.linkButton, fontSize: '13px', color: '#888' }, onClick: () => { setStep('credentials'); setError(''); setOtpCode(''); }, children: "\u2190 Back to login" })] })] })) : (
9021
+ /* ── Credentials Step ── */
9022
+ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, children: [jsxRuntime.jsxs("div", { style: styles.inputGroup, children: [jsxRuntime.jsx("label", { style: styles.inputLabel, children: "Email Address" }), jsxRuntime.jsxs("div", { style: styles.inputContainer, children: [jsxRuntime.jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\u2709" }), jsxRuntime.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 })] })] }), jsxRuntime.jsxs("div", { style: styles.inputGroup, children: [jsxRuntime.jsx("label", { style: styles.inputLabel, children: "Password" }), jsxRuntime.jsxs("div", { style: styles.inputContainer, children: [jsxRuntime.jsx("span", { style: { marginRight: '12px', opacity: 0.7, color: '#666' }, children: "\uD83D\uDD12" }), jsxRuntime.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 }), jsxRuntime.jsx("button", { type: "button", style: styles.eyeButton, onClick: () => setShowPassword(!showPassword), disabled: isLoading, children: showPassword ? '👁' : '👁‍🗨' })] })] }), jsxRuntime.jsxs("div", { style: styles.checkboxContainer, children: [jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', fontSize: '14px', color: '#666' }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: rememberMe, onChange: (e) => setRememberMe(e.target.checked), style: { marginRight: '8px' } }), "Remember me"] }), showPasswordReset && (jsxRuntime.jsx("button", { type: "button", style: { ...styles.linkButton, fontSize: '14px' }, onClick: onPasswordReset, disabled: isLoading, children: "Forgot password?" }))] }), jsxRuntime.jsx("button", { type: "submit", style: {
8817
9023
  ...styles.loginButton,
8818
9024
  opacity: isLoading ? 0.8 : 1,
8819
9025
  cursor: isLoading ? 'not-allowed' : 'pointer'
8820
- }, disabled: isLoading, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: { animation: 'spin 1s linear infinite' }, children: "\u27F3" }), "Signing in..."] })) : ('Sign In') })] }), showAccountCreation && (jsxRuntime.jsx("div", { style: styles.accountActions, children: jsxRuntime.jsx("button", { type: "button", style: styles.linkButton, onClick: onAccountCreation, disabled: isLoading, children: "Create New Account" }) })), jsxRuntime.jsx("style", { children: `
9026
+ }, disabled: isLoading, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: { animation: 'spin 1s linear infinite' }, children: "\u27F3" }), "Signing in..."] })) : ('Sign In') }), showAccountCreation && (jsxRuntime.jsx("div", { style: styles.accountActions, children: jsxRuntime.jsx("button", { type: "button", style: styles.linkButton, onClick: onAccountCreation, disabled: isLoading, children: "Create New Account" }) }))] })), jsxRuntime.jsx("style", { children: `
8821
9027
  @keyframes spin {
8822
9028
  from { transform: rotate(0deg); }
8823
9029
  to { transform: rotate(360deg); }
@@ -9830,6 +10036,125 @@ function useBridgeApiRaw(instanceId) {
9830
10036
  };
9831
10037
  }
9832
10038
 
10039
+ /**
10040
+ * useTwoFactor Hook for React
10041
+ *
10042
+ * Convenience hook for managing 2FA settings (setup, toggle, remove).
10043
+ * For the LOGIN-TIME 2FA flow use useAuth() which surfaces
10044
+ * twoFactorPending / verifyTwoFactor / startTwoFactor.
10045
+ */
10046
+ /**
10047
+ * Hook for managing 2FA settings.
10048
+ *
10049
+ * Must be used inside a PubflowProvider.
10050
+ *
10051
+ * @param instanceId Optional Pubflow instance ID
10052
+ */
10053
+ function useTwoFactor(instanceId) {
10054
+ const context = React.useContext(PubflowContext);
10055
+ if (!context)
10056
+ throw new Error('useTwoFactor must be used within a PubflowProvider');
10057
+ const id = instanceId || context.defaultInstance;
10058
+ const instance = context.instances[id];
10059
+ const [systemEnabled, setSystemEnabled] = React.useState(false);
10060
+ const [availableMethods, setAvailableMethods] = React.useState([]);
10061
+ const [methods, setMethods] = React.useState([]);
10062
+ const [isLoading, setIsLoading] = React.useState(false);
10063
+ const [error, setError] = React.useState(null);
10064
+ const refresh = React.useCallback(async () => {
10065
+ if (!instance)
10066
+ return;
10067
+ setIsLoading(true);
10068
+ setError(null);
10069
+ try {
10070
+ const [sys, userMethods] = await Promise.all([
10071
+ instance.twoFactorService.getSystem(),
10072
+ instance.twoFactorService.getMethods(),
10073
+ ]);
10074
+ setSystemEnabled(sys.global_two_factor_enabled);
10075
+ setAvailableMethods(sys.available_methods);
10076
+ setMethods(userMethods);
10077
+ }
10078
+ catch (e) {
10079
+ setError((e === null || e === void 0 ? void 0 : e.message) || 'Failed to load 2FA info');
10080
+ }
10081
+ finally {
10082
+ setIsLoading(false);
10083
+ }
10084
+ }, [instance]);
10085
+ const setup = React.useCallback(async (method, identifier) => {
10086
+ if (!instance)
10087
+ return { success: false, error: 'Pubflow not initialized' };
10088
+ setIsLoading(true);
10089
+ setError(null);
10090
+ try {
10091
+ const result = await instance.twoFactorService.setup(method, identifier);
10092
+ if (result.success)
10093
+ await refresh();
10094
+ return result;
10095
+ }
10096
+ catch (e) {
10097
+ const msg = (e === null || e === void 0 ? void 0 : e.message) || 'Setup failed';
10098
+ setError(msg);
10099
+ return { success: false, error: msg };
10100
+ }
10101
+ finally {
10102
+ setIsLoading(false);
10103
+ }
10104
+ }, [instance, refresh]);
10105
+ const toggle = React.useCallback(async (enabled, verificationCode, verificationMethodId) => {
10106
+ if (!instance)
10107
+ return { success: false, error: 'Pubflow not initialized' };
10108
+ setIsLoading(true);
10109
+ setError(null);
10110
+ try {
10111
+ const result = await instance.twoFactorService.toggle(enabled, verificationCode, verificationMethodId);
10112
+ if (result.success)
10113
+ await refresh();
10114
+ return result;
10115
+ }
10116
+ catch (e) {
10117
+ const msg = (e === null || e === void 0 ? void 0 : e.message) || 'Toggle failed';
10118
+ setError(msg);
10119
+ return { success: false, error: msg };
10120
+ }
10121
+ finally {
10122
+ setIsLoading(false);
10123
+ }
10124
+ }, [instance, refresh]);
10125
+ const removeMethod = React.useCallback(async (methodId, verificationCode, verificationMethodId) => {
10126
+ if (!instance)
10127
+ return { success: false, error: 'Pubflow not initialized' };
10128
+ setIsLoading(true);
10129
+ setError(null);
10130
+ try {
10131
+ const result = await instance.twoFactorService.removeMethod(methodId, verificationCode, verificationMethodId);
10132
+ if (result.success)
10133
+ await refresh();
10134
+ return result;
10135
+ }
10136
+ catch (e) {
10137
+ const msg = (e === null || e === void 0 ? void 0 : e.message) || 'Remove failed';
10138
+ setError(msg);
10139
+ return { success: false, error: msg };
10140
+ }
10141
+ finally {
10142
+ setIsLoading(false);
10143
+ }
10144
+ }, [instance, refresh]);
10145
+ return {
10146
+ systemEnabled,
10147
+ availableMethods,
10148
+ methods,
10149
+ isLoading,
10150
+ error,
10151
+ refresh,
10152
+ setup,
10153
+ toggle,
10154
+ removeMethod,
10155
+ };
10156
+ }
10157
+
9833
10158
  /**
9834
10159
  * Search Query Builder Hook for React
9835
10160
  *
@@ -10082,6 +10407,7 @@ exports.useRequireAuth = useRequireAuth;
10082
10407
  exports.useSearchQueryBuilder = useSearchQueryBuilder;
10083
10408
  exports.useSimpleAuthGuard = useSimpleAuthGuard;
10084
10409
  exports.useTheme = useTheme;
10410
+ exports.useTwoFactor = useTwoFactor;
10085
10411
  Object.keys(core).forEach(function (k) {
10086
10412
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
10087
10413
  enumerable: true,