@pubflow/react 0.4.4 → 0.4.6

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
@@ -254,6 +254,7 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
254
254
  });
255
255
  const apiClient = new core.ApiClient(fullConfig, storage);
256
256
  const authService = new core.AuthService(apiClient, storage, fullConfig);
257
+ const twoFactorService = new core.TwoFactorService(apiClient, fullConfig);
257
258
  // Get current user (non-blocking)
258
259
  let user = null;
259
260
  let isAuthenticated = false;
@@ -261,12 +262,35 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
261
262
  config: fullConfig,
262
263
  apiClient,
263
264
  authService,
265
+ twoFactorService,
264
266
  user,
265
267
  isAuthenticated,
266
268
  isLoading: true,
269
+ twoFactorPending: false,
270
+ twoFactorMethods: [],
267
271
  login: async (credentials) => {
268
272
  const result = await authService.login(credentials);
269
- 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) {
270
294
  setContextValue(prev => ({
271
295
  ...prev,
272
296
  instances: {
@@ -275,7 +299,9 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
275
299
  ...prev.instances[instanceConfig.id],
276
300
  user: result.user,
277
301
  isAuthenticated: true,
278
- isLoading: false
302
+ isLoading: false,
303
+ twoFactorPending: false,
304
+ twoFactorMethods: [],
279
305
  }
280
306
  }
281
307
  }));
@@ -293,7 +319,9 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
293
319
  ...prev.instances[instanceConfig.id],
294
320
  user: null,
295
321
  isAuthenticated: false,
296
- isLoading: false
322
+ isLoading: false,
323
+ twoFactorPending: false,
324
+ twoFactorMethods: [],
297
325
  }
298
326
  }
299
327
  }));
@@ -316,7 +344,31 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
316
344
  }));
317
345
  }
318
346
  return result;
319
- }
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
+ },
320
372
  };
321
373
  // Load user asynchronously
322
374
  authService.getCurrentUser().then(currentUser => {
@@ -365,16 +417,39 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
365
417
  });
366
418
  const apiClient = new core.ApiClient(fullConfig, storage);
367
419
  const authService = new core.AuthService(apiClient, storage, fullConfig);
420
+ const twoFactorService = new core.TwoFactorService(apiClient, fullConfig);
368
421
  instancesMap[defaultInstance] = {
369
422
  config: fullConfig,
370
423
  apiClient,
371
424
  authService,
425
+ twoFactorService,
372
426
  user: null,
373
427
  isAuthenticated: false,
374
428
  isLoading: true,
429
+ twoFactorPending: false,
430
+ twoFactorMethods: [],
375
431
  login: async (credentials) => {
376
432
  const result = await authService.login(credentials);
377
- 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) {
378
453
  setContextValue(prev => ({
379
454
  ...prev,
380
455
  instances: {
@@ -383,7 +458,9 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
383
458
  ...prev.instances[defaultInstance],
384
459
  user: result.user,
385
460
  isAuthenticated: true,
386
- isLoading: false
461
+ isLoading: false,
462
+ twoFactorPending: false,
463
+ twoFactorMethods: [],
387
464
  }
388
465
  }
389
466
  }));
@@ -401,7 +478,9 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
401
478
  ...prev.instances[defaultInstance],
402
479
  user: null,
403
480
  isAuthenticated: false,
404
- isLoading: false
481
+ isLoading: false,
482
+ twoFactorPending: false,
483
+ twoFactorMethods: [],
405
484
  }
406
485
  }
407
486
  }));
@@ -424,7 +503,30 @@ function PubflowProvider({ children, config, instances, defaultInstance = 'defau
424
503
  }));
425
504
  }
426
505
  return result;
427
- }
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
+ },
428
530
  };
429
531
  // Load user asynchronously
430
532
  authService.getCurrentUser().then(currentUser => {
@@ -526,9 +628,13 @@ function useAuth(instanceId) {
526
628
  user: null,
527
629
  isAuthenticated: false,
528
630
  isLoading: true, // Crucial: tell the UI we are still loading the provider
631
+ twoFactorPending: false,
632
+ twoFactorMethods: [],
529
633
  login: async () => { throw new Error('Pubflow not initialized yet'); },
530
634
  logout: async () => { throw new Error('Pubflow not initialized yet'); },
531
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' }),
532
638
  };
533
639
  }
534
640
  // After provider is ready, if the instance is STILL missing, it's a real config error.
@@ -540,9 +646,13 @@ function useAuth(instanceId) {
540
646
  user: pubflowInstance.user || null,
541
647
  isAuthenticated: pubflowInstance.isAuthenticated,
542
648
  isLoading: pubflowInstance.isLoading,
649
+ twoFactorPending: pubflowInstance.twoFactorPending,
650
+ twoFactorMethods: pubflowInstance.twoFactorMethods,
543
651
  login: pubflowInstance.login,
544
652
  logout: pubflowInstance.logout,
545
- validateSession: pubflowInstance.validateSession
653
+ validateSession: pubflowInstance.validateSession,
654
+ verifyTwoFactor: pubflowInstance.verifyTwoFactor,
655
+ startTwoFactor: pubflowInstance.startTwoFactor,
546
656
  };
547
657
  }
548
658
 
@@ -8647,17 +8757,25 @@ function processLogoUrl(logoUrl) {
8647
8757
  * Professional login form component
8648
8758
  */
8649
8759
  function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccountCreation, instanceId }) {
8650
- const { login, isLoading } = useAuth(instanceId);
8760
+ const { login, verifyTwoFactor, startTwoFactor, isLoading } = useAuth(instanceId);
8651
8761
  // Form state
8652
8762
  const [email, setEmail] = React.useState('');
8653
8763
  const [password, setPassword] = React.useState('');
8654
8764
  const [showPassword, setShowPassword] = React.useState(false);
8655
8765
  const [error, setError] = React.useState('');
8656
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);
8657
8774
  // Configuration with defaults
8658
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;
8659
8776
  // Handle form submission
8660
8777
  const handleSubmit = async (e) => {
8778
+ var _a;
8661
8779
  e.preventDefault();
8662
8780
  setError('');
8663
8781
  // Basic validation
@@ -8671,6 +8789,17 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8671
8789
  }
8672
8790
  try {
8673
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
+ }
8674
8803
  if (result.success) {
8675
8804
  if (onSuccess) {
8676
8805
  onSuccess(result.user);
@@ -8696,6 +8825,56 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8696
8825
  }
8697
8826
  }
8698
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
+ };
8699
8878
  // Dynamic styles
8700
8879
  const styles = {
8701
8880
  container: {
@@ -8711,6 +8890,7 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8711
8890
  },
8712
8891
  logoSection: {
8713
8892
  display: 'flex',
8893
+ flexDirection: 'column',
8714
8894
  justifyContent: 'center',
8715
8895
  alignItems: 'center',
8716
8896
  textAlign: 'center',
@@ -8833,11 +9013,17 @@ function LoginForm({ config = {}, onSuccess, onError, onPasswordReset, onAccount
8833
9013
  textAlign: 'center'
8834
9014
  }
8835
9015
  };
8836
- 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: {
8837
9023
  ...styles.loginButton,
8838
9024
  opacity: isLoading ? 0.8 : 1,
8839
9025
  cursor: isLoading ? 'not-allowed' : 'pointer'
8840
- }, 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: `
8841
9027
  @keyframes spin {
8842
9028
  from { transform: rotate(0deg); }
8843
9029
  to { transform: rotate(360deg); }
@@ -9850,6 +10036,125 @@ function useBridgeApiRaw(instanceId) {
9850
10036
  };
9851
10037
  }
9852
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
+
9853
10158
  /**
9854
10159
  * Search Query Builder Hook for React
9855
10160
  *
@@ -10102,6 +10407,7 @@ exports.useRequireAuth = useRequireAuth;
10102
10407
  exports.useSearchQueryBuilder = useSearchQueryBuilder;
10103
10408
  exports.useSimpleAuthGuard = useSimpleAuthGuard;
10104
10409
  exports.useTheme = useTheme;
10410
+ exports.useTwoFactor = useTwoFactor;
10105
10411
  Object.keys(core).forEach(function (k) {
10106
10412
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
10107
10413
  enumerable: true,