@proveanything/smartlinks-auth-ui 0.4.5 → 0.4.7

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
@@ -258,7 +258,7 @@ const AuthModeToggle = ({ mode, onModeChange, disabled = false, }) => {
258
258
  return (jsxs("div", { className: "auth-mode-toggle", role: "tablist", "aria-label": "Authentication mode", children: [jsx("button", { type: "button", role: "tab", "aria-selected": mode === 'login', className: `auth-mode-toggle-button ${mode === 'login' ? 'auth-mode-toggle-button--active' : ''}`, onClick: () => onModeChange('login'), disabled: disabled, children: "Sign In" }), jsx("button", { type: "button", role: "tab", "aria-selected": mode === 'register', className: `auth-mode-toggle-button ${mode === 'register' ? 'auth-mode-toggle-button--active' : ''}`, onClick: () => onModeChange('register'), disabled: disabled, children: "Create Account" })] }));
259
259
  };
260
260
 
261
- const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, signupProminence = 'minimal', schema, registrationFieldsConfig = [], additionalFields = [], }) => {
261
+ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, signupProminence = 'minimal', signupRedirectUrl, schema, registrationFieldsConfig = [], additionalFields = [], }) => {
262
262
  const [formData, setFormData] = useState({
263
263
  email: '',
264
264
  password: '',
@@ -314,7 +314,7 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
314
314
  if (newMode !== mode) {
315
315
  onModeSwitch();
316
316
  }
317
- }, disabled: loading })), jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: title }), jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John Smith" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: formData.email || '', onChange: (e) => handleChange('email', e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: formData.password || '', onChange: (e) => handleChange('password', e.target.value), required: true, disabled: loading, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === 'login' ? 'current-password' : 'new-password', minLength: 6 })] }), mode === 'register' && hasSchemaFields && schemaFields.inline.map(renderSchemaField), mode === 'register' && hasLegacyFields && additionalFields.map(renderLegacyField), mode === 'register' && hasSchemaFields && schemaFields.postCredentials.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsx("div", { className: "auth-form-footer", children: jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && (jsxs("div", { className: "auth-divider", children: [jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] }))] }));
317
+ }, disabled: loading })), jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: title }), jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John Smith" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: formData.email || '', onChange: (e) => handleChange('email', e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: formData.password || '', onChange: (e) => handleChange('password', e.target.value), required: true, disabled: loading, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === 'login' ? 'current-password' : 'new-password', minLength: 6 })] }), mode === 'register' && hasSchemaFields && schemaFields.inline.map(renderSchemaField), mode === 'register' && hasLegacyFields && additionalFields.map(renderLegacyField), mode === 'register' && hasSchemaFields && schemaFields.postCredentials.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsx("div", { className: "auth-form-footer", children: jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && signupProminence !== 'none' && (jsxs("div", { className: "auth-divider", children: [jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), signupRedirectUrl && mode === 'login' ? (jsx("a", { href: signupRedirectUrl, target: "_top", className: "auth-link auth-link-bold", children: "Sign up" })) : (jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' }))] }))] }));
318
318
  };
319
319
 
320
320
  const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
@@ -10811,7 +10811,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
10811
10811
  : 'Resend code' }) })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : codeSent ? ('Verify Code') : ('Send Code') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to login" }) })] }));
10812
10812
  };
10813
10813
 
10814
- const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, }) => {
10814
+ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, resetEmail, }) => {
10815
10815
  const [email, setEmail] = useState('');
10816
10816
  const [password, setPassword] = useState('');
10817
10817
  const [confirmPassword, setConfirmPassword] = useState('');
@@ -10836,14 +10836,14 @@ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successM
10836
10836
  await onSubmit(email);
10837
10837
  }
10838
10838
  };
10839
- if (success) {
10839
+ if (success && !error) {
10840
10840
  return (jsxs("div", { className: "auth-form", children: [jsxs("div", { className: "auth-form-header", children: [jsx("div", { style: { textAlign: 'center', marginBottom: '1rem' }, children: jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", fill: "none", style: { margin: '0 auto' }, children: [jsx("circle", { cx: "32", cy: "32", r: "32", fill: "#10b981", fillOpacity: "0.1" }), jsx("path", { d: "M20 32l8 8 16-16", stroke: "#10b981", strokeWidth: "4", strokeLinecap: "round", strokeLinejoin: "round" })] }) }), jsx("h2", { className: "auth-form-title", children: token ? 'Password reset!' : 'Check your email' }), jsx("p", { className: "auth-form-subtitle", children: token
10841
10841
  ? (successMessage || 'Your password has been successfully reset. You can now sign in with your new password.')
10842
10842
  : (successMessage || `We've sent password reset instructions to ${email}`) })] }), jsx("button", { type: "button", className: "auth-button auth-button-primary", onClick: onBack, children: "Back to Sign in" })] }));
10843
10843
  }
10844
10844
  return (jsxs("form", { className: "auth-form", onSubmit: handleSubmit, children: [jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: token ? 'Set new password' : 'Reset your password' }), jsx("p", { className: "auth-form-subtitle", children: token
10845
10845
  ? 'Enter your new password below.'
10846
- : "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error || passwordError] })), token ? (jsxs(Fragment, { children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: loading, placeholder: "Enter new password", autoComplete: "new-password", minLength: 6 })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsx("input", { type: "password", id: "confirmPassword", className: "auth-input", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, disabled: loading, placeholder: "Confirm new password", autoComplete: "new-password", minLength: 6 })] })] })) : (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
10846
+ : "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error || passwordError] })), token ? (jsxs(Fragment, { children: [resetEmail && (jsx("input", { type: "email", value: resetEmail, readOnly: true, autoComplete: "username", style: { position: 'absolute', opacity: 0, height: 0, width: 0, overflow: 'hidden', pointerEvents: 'none' }, tabIndex: -1, "aria-hidden": "true" })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: loading, placeholder: "Enter new password", autoComplete: "new-password", minLength: 6 })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsx("input", { type: "password", id: "confirmPassword", className: "auth-input", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, disabled: loading, placeholder: "Confirm new password", autoComplete: "new-password", minLength: 6 })] })] })) : (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
10847
10847
  };
10848
10848
 
10849
10849
  const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, }) => {
@@ -10949,14 +10949,46 @@ class AuthAPI {
10949
10949
  return smartlinks.authKit.verifyPhoneCode(this.clientId, phoneNumber, code);
10950
10950
  }
10951
10951
  async requestPasswordReset(email, redirectUrl) {
10952
- return smartlinks.authKit.requestPasswordReset(this.clientId, {
10953
- email,
10954
- redirectUrl,
10955
- clientName: this.clientName
10956
- });
10952
+ console.log('[AuthKit:API] requestPasswordReset called', { clientId: this.clientId, email, redirectUrl });
10953
+ try {
10954
+ const result = await smartlinks.authKit.requestPasswordReset(this.clientId, {
10955
+ email,
10956
+ redirectUrl,
10957
+ clientName: this.clientName
10958
+ });
10959
+ console.log('[AuthKit:API] requestPasswordReset resolved:', JSON.stringify(result));
10960
+ return result;
10961
+ }
10962
+ catch (error) {
10963
+ const err = error;
10964
+ console.error('[AuthKit:API] requestPasswordReset threw:', {
10965
+ type: typeof error,
10966
+ message: err?.message,
10967
+ statusCode: err?.statusCode ?? err?.response?.status,
10968
+ errorCode: err?.errorCode ?? err?.details?.errorCode,
10969
+ details: err?.details ?? err?.response?.data,
10970
+ keys: err ? Object.keys(err) : [],
10971
+ });
10972
+ throw error;
10973
+ }
10957
10974
  }
10958
10975
  async verifyResetToken(token) {
10959
- return smartlinks.authKit.verifyResetToken(this.clientId, token);
10976
+ console.log('[AuthKit:API] verifyResetToken called');
10977
+ try {
10978
+ const result = await smartlinks.authKit.verifyResetToken(this.clientId, token);
10979
+ console.log('[AuthKit:API] verifyResetToken resolved:', JSON.stringify(result));
10980
+ return result;
10981
+ }
10982
+ catch (error) {
10983
+ const err = error;
10984
+ console.error('[AuthKit:API] verifyResetToken threw:', {
10985
+ type: typeof error,
10986
+ message: err?.message,
10987
+ statusCode: err?.statusCode ?? err?.response?.status,
10988
+ errorCode: err?.errorCode ?? err?.details?.errorCode,
10989
+ });
10990
+ throw error;
10991
+ }
10960
10992
  }
10961
10993
  async completePasswordReset(token, newPassword) {
10962
10994
  return smartlinks.authKit.completePasswordReset(this.clientId, token, newPassword);
@@ -10992,7 +11024,6 @@ class AuthAPI {
10992
11024
  this.log.warn('Failed to fetch UI config, using defaults:', error);
10993
11025
  return {
10994
11026
  branding: {
10995
- title: 'Smartlinks Auth',
10996
11027
  subtitle: 'Sign in to your account',
10997
11028
  primaryColor: '#3B82F6',
10998
11029
  secondaryColor: '#1D4ED8',
@@ -12388,6 +12419,10 @@ function getFriendlyErrorMessage(error) {
12388
12419
  if (isApiErrorLike(error)) {
12389
12420
  // First, check for specific error code (most precise)
12390
12421
  const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
12422
+ // For rate-limit errors, prefer the backend's message as it includes specific wait times
12423
+ if (errorCode === 'RATE_LIMIT_EXCEEDED' && error.message && error.message !== '[object Object]') {
12424
+ return error.message;
12425
+ }
12391
12426
  if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
12392
12427
  return ERROR_CODE_MESSAGES[errorCode];
12393
12428
  }
@@ -12395,6 +12430,10 @@ function getFriendlyErrorMessage(error) {
12395
12430
  if (error.statusCode >= 500) {
12396
12431
  return 'Server error. Please try again later.';
12397
12432
  }
12433
+ // For 429 status, prefer backend message (may include wait time)
12434
+ if (error.statusCode === 429 && error.message && error.message !== '[object Object]') {
12435
+ return error.message;
12436
+ }
12398
12437
  if (STATUS_MESSAGES[error.statusCode]) {
12399
12438
  return STATUS_MESSAGES[error.statusCode];
12400
12439
  }
@@ -12521,7 +12560,7 @@ function requiresEmailVerification(error) {
12521
12560
  }
12522
12561
 
12523
12562
  // VERSION: Update this when making changes to help identify which version is running
12524
- const AUTH_UI_VERSION = '44';
12563
+ const AUTH_UI_VERSION = '46';
12525
12564
  const LOG_PREFIX = `[SmartlinksAuthUI:v${AUTH_UI_VERSION}]`;
12526
12565
  // Helper to check for URL auth params synchronously (runs during initialization)
12527
12566
  // This prevents the form from flashing before detecting deep-link flows
@@ -12545,6 +12584,24 @@ const getExpirationFromResponse = (response) => {
12545
12584
  return Date.now() + response.expiresIn;
12546
12585
  return undefined; // Will use 7-day default in tokenStorage
12547
12586
  };
12587
+ const getActionResultErrorMessage = (result) => {
12588
+ if (!result || typeof result !== 'object')
12589
+ return null;
12590
+ const candidate = result;
12591
+ const numericStatusCode = typeof candidate.statusCode === 'string'
12592
+ ? Number(candidate.statusCode)
12593
+ : candidate.statusCode;
12594
+ const hasFailureStatus = typeof numericStatusCode === 'number' && !Number.isNaN(numericStatusCode) && numericStatusCode >= 400;
12595
+ const isFailure = candidate.success === false || candidate.ok === false || !!candidate.errorCode || hasFailureStatus;
12596
+ if (!isFailure)
12597
+ return null;
12598
+ return getFriendlyErrorMessage({
12599
+ statusCode: hasFailureStatus ? numericStatusCode : 400,
12600
+ errorCode: candidate.errorCode,
12601
+ message: candidate.message || 'Request failed. Please try again.',
12602
+ details: candidate.details,
12603
+ });
12604
+ };
12548
12605
  // Default Smartlinks Google OAuth Client ID (public - safe to expose)
12549
12606
  const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
12550
12607
  // Default Google OAuth proxy URL (hosted on our whitelisted domain)
@@ -12636,6 +12693,43 @@ const getNativeBridge = () => {
12636
12693
  return native;
12637
12694
  return null;
12638
12695
  };
12696
+ // Helper to register an AuthKit.onAuthResult handler for a specific callbackId
12697
+ // The Android native app calls: window.AuthKit.onAuthResult(resultJson)
12698
+ const registerAuthKitCallback = (callbackId, onResult, timeoutMs = 5000, onTimeout) => {
12699
+ const bridge = window.AuthKit;
12700
+ if (!bridge) {
12701
+ console.warn('[AuthKit] No AuthKit bridge found, cannot register callback');
12702
+ onTimeout?.();
12703
+ return () => { };
12704
+ }
12705
+ // Store the previous handler so we can restore on cleanup
12706
+ const previousHandler = bridge.onAuthResult;
12707
+ const timeout = setTimeout(() => {
12708
+ // Restore previous handler on timeout
12709
+ bridge.onAuthResult = previousHandler;
12710
+ onTimeout?.();
12711
+ }, timeoutMs);
12712
+ bridge.onAuthResult = (resultOrJson) => {
12713
+ const result = typeof resultOrJson === 'string' ? JSON.parse(resultOrJson) : resultOrJson;
12714
+ // Match by callbackId if present, otherwise accept any result
12715
+ if (result.callbackId && result.callbackId !== callbackId) {
12716
+ // Not for us — pass through to previous handler if any
12717
+ if (typeof previousHandler === 'function') {
12718
+ previousHandler(resultOrJson);
12719
+ }
12720
+ return;
12721
+ }
12722
+ clearTimeout(timeout);
12723
+ // Restore previous handler
12724
+ bridge.onAuthResult = previousHandler;
12725
+ onResult(result);
12726
+ };
12727
+ // Return cleanup function
12728
+ return () => {
12729
+ clearTimeout(timeout);
12730
+ bridge.onAuthResult = previousHandler;
12731
+ };
12732
+ };
12639
12733
  // Sign out from Google on the native side (clears cached Google account)
12640
12734
  // This is fire-and-forget with a timeout - gracefully degrades if not supported
12641
12735
  const signOutGoogleNative = async () => {
@@ -12644,21 +12738,7 @@ const signOutGoogleNative = async () => {
12644
12738
  return;
12645
12739
  const callbackId = `google_signout_${Date.now()}`;
12646
12740
  return new Promise((resolve) => {
12647
- const timeout = setTimeout(() => resolve(), 3000);
12648
- // Store original callback to restore later
12649
- const originalCallback = window.smartlinksNativeCallback;
12650
- window.smartlinksNativeCallback = (result) => {
12651
- if (result.callbackId === callbackId) {
12652
- clearTimeout(timeout);
12653
- // Restore original callback
12654
- window.smartlinksNativeCallback = originalCallback;
12655
- resolve();
12656
- }
12657
- else if (originalCallback) {
12658
- // Pass through to original callback for other messages
12659
- originalCallback(result);
12660
- }
12661
- };
12741
+ registerAuthKitCallback(callbackId, () => resolve(), 3000, () => resolve());
12662
12742
  const payload = JSON.stringify({
12663
12743
  type: 'GOOGLE_SIGN_OUT',
12664
12744
  callbackId,
@@ -12672,44 +12752,32 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
12672
12752
  return null;
12673
12753
  const callbackId = `google_check_${Date.now()}`;
12674
12754
  return new Promise((resolve) => {
12675
- const timeout = setTimeout(() => resolve(null), 5000);
12676
- // Store original callback to restore later
12677
- const originalCallback = window.smartlinksNativeCallback;
12678
- window.smartlinksNativeCallback = (result) => {
12679
- if (result.callbackId === callbackId) {
12680
- clearTimeout(timeout);
12681
- // Restore original callback
12682
- window.smartlinksNativeCallback = originalCallback;
12683
- if (result.success) {
12684
- resolve({
12685
- isSignedIn: result.isSignedIn || false,
12686
- idToken: result.idToken,
12687
- email: result.email,
12688
- name: result.name,
12689
- picture: result.picture,
12690
- });
12691
- }
12692
- else {
12693
- resolve(null);
12694
- }
12755
+ registerAuthKitCallback(callbackId, (result) => {
12756
+ if (result.success) {
12757
+ resolve({
12758
+ isSignedIn: result.isSignedIn || false,
12759
+ idToken: result.idToken,
12760
+ email: result.email,
12761
+ name: result.name,
12762
+ picture: result.picture,
12763
+ });
12695
12764
  }
12696
- else if (originalCallback) {
12697
- // Pass through to original callback for other messages
12698
- originalCallback(result);
12765
+ else {
12766
+ resolve(null);
12699
12767
  }
12700
- };
12768
+ }, 5000, () => resolve(null));
12701
12769
  const payload = JSON.stringify({
12702
12770
  type: 'GOOGLE_CHECK_SIGN_IN',
12703
12771
  clientId,
12704
12772
  googleClientId,
12705
- serverClientId: googleClientId, // Alias for Android SDK
12773
+ serverClientId: googleClientId,
12706
12774
  callbackId,
12707
12775
  });
12708
12776
  nativeBridge.checkGoogleSignIn(payload);
12709
12777
  });
12710
12778
  };
12711
12779
  // getFriendlyErrorMessage is now imported from ../utils/errorHandling
12712
- const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, onRedirect, enabledProviders = ['email', 'google', 'phone'], initialMode, signupProminence, redirectUrl, theme = 'auto', className, customization, skipConfigFetch = false, minimal = false, logger, proxyMode = false, collectionId, disableConfigCache = false, enableSilentGoogleSignIn = false, }) => {
12780
+ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, onRedirect, enabledProviders = ['email', 'google', 'phone'], initialMode, signupProminence, redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, minimal = false, logger, proxyMode = false, collectionId, disableConfigCache = false, enableSilentGoogleSignIn = false, }) => {
12713
12781
  // Resolve signup prominence from props, customization, config, or default
12714
12782
  const resolvedSignupProminence = signupProminence || customization?.signupProminence || 'minimal';
12715
12783
  // Determine initial mode based on signupProminence setting
@@ -12746,6 +12814,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12746
12814
  const [contactSchema, setContactSchema] = useState(null); // Schema for registration fields
12747
12815
  const [silentSignInChecked, setSilentSignInChecked] = useState(false); // Track if silent sign-in has been checked
12748
12816
  const [googleFallbackToPopup, setGoogleFallbackToPopup] = useState(false); // Show popup fallback when FedCM is blocked/dismissed
12817
+ const [googleNativeTimedOut, setGoogleNativeTimedOut] = useState(false); // Native bridge callback timed out
12749
12818
  const log = useMemo(() => createLoggerWrapper(logger), [logger]);
12750
12819
  const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
12751
12820
  const auth = useAuth();
@@ -13022,6 +13091,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13022
13091
  if (urlMode === 'verifyEmail') {
13023
13092
  log.log('Verifying email with token:', token);
13024
13093
  const response = await api.verifyEmailWithToken(token);
13094
+ log.log('Email verification response:', { hasToken: !!response.token, hasUser: !!response.user, emailVerificationMode: response.emailVerificationMode, isNewUser: response.isNewUser });
13025
13095
  // Get email verification mode from response or config
13026
13096
  const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-auto-login';
13027
13097
  if ((verificationMode === 'verify-auto-login' || verificationMode === 'immediate') && response.token) {
@@ -13066,9 +13136,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13066
13136
  log.log('Verifying reset token:', token);
13067
13137
  // Verify token is valid, then show password reset form
13068
13138
  const verifyResult = await api.verifyResetToken(token);
13069
- // Check if token is valid before proceeding
13070
- if (!verifyResult.valid) {
13071
- throw new Error(verifyResult.message || 'Invalid or expired token');
13139
+ // Guard against undefined result (proxy mode may resolve without data)
13140
+ if (!verifyResult || !verifyResult.valid) {
13141
+ throw new Error(verifyResult?.message || 'Invalid or expired token');
13072
13142
  }
13073
13143
  setUrlAuthProcessing(false); // Clear processing state - showing reset form
13074
13144
  setResetToken(token); // Store token for use in password reset
@@ -13083,6 +13153,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13083
13153
  else if (urlMode === 'magicLink') {
13084
13154
  log.log('Verifying magic link token:', token);
13085
13155
  const response = await api.verifyMagicLink(token);
13156
+ log.log('Magic link verification response:', { hasToken: !!response.token, hasUser: !!response.user, isNewUser: response.isNewUser });
13086
13157
  // Auto-login with magic link if token is provided
13087
13158
  if (response.token) {
13088
13159
  // Always await - auth.login now waits for parent ack automatically in iframe mode
@@ -13158,6 +13229,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13158
13229
  const redirectUri = state.redirectUri || window.location.origin + window.location.pathname;
13159
13230
  // Exchange authorization code for tokens
13160
13231
  const response = await api.loginWithGoogleCode(code, redirectUri);
13232
+ log.log('Google OAuth code exchange response:', { hasToken: !!response.token, hasUser: !!response.user, isNewUser: response.isNewUser });
13161
13233
  if (response.token) {
13162
13234
  // Await login to ensure token is persisted before any navigation
13163
13235
  await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response));
@@ -13188,13 +13260,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13188
13260
  setError(undefined);
13189
13261
  setAuthSuccess(false);
13190
13262
  try {
13263
+ log.log('Submitting email auth:', { mode, email: data.email });
13191
13264
  const response = mode === 'login'
13192
13265
  ? await api.login(data.email, data.password)
13193
13266
  : await api.register({
13194
13267
  ...data,
13195
13268
  accountData: mode === 'register' ? accountData : undefined,
13196
- redirectUrl: getRedirectUrl(), // Include redirect URL for email verification
13269
+ redirectUrl: getRedirectUrl(),
13197
13270
  });
13271
+ log.log('Email auth response:', { mode, hasToken: !!response?.token, hasUser: !!response?.user, isNewUser: response?.isNewUser, requiresEmailVerification: response?.requiresEmailVerification, accountLocked: response?.accountLocked, emailVerificationMode: response?.emailVerificationMode });
13198
13272
  // Defensive check: validate response before accessing properties
13199
13273
  // SDK should throw on 401/error responses, but handle edge cases gracefully
13200
13274
  if (!response) {
@@ -13342,17 +13416,46 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13342
13416
  return;
13343
13417
  setLoading(true);
13344
13418
  setError(undefined);
13419
+ setAuthSuccess(false);
13420
+ setResetSuccess(false);
13421
+ setSuccessMessage(undefined);
13422
+ setShowResendVerification(false);
13345
13423
  try {
13346
13424
  const result = await api.requestPasswordReset(resetRequestEmail, getRedirectUrl());
13347
- // Use resetSuccess (not authSuccess) to show password reset confirmation, not "Login successful"
13425
+ const resultError = getActionResultErrorMessage(result);
13426
+ if (resultError) {
13427
+ setAuthSuccess(false);
13428
+ setResetSuccess(false);
13429
+ setSuccessMessage(undefined);
13430
+ setError(resultError);
13431
+ setShowRequestNewReset(true);
13432
+ return;
13433
+ }
13434
+ const resultAny = result;
13435
+ const isConfirmedSuccess = result && (resultAny.success === true ||
13436
+ resultAny.ok === true ||
13437
+ (resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
13438
+ if (!isConfirmedSuccess) {
13439
+ setAuthSuccess(false);
13440
+ setResetSuccess(false);
13441
+ setSuccessMessage(undefined);
13442
+ setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
13443
+ setShowRequestNewReset(true);
13444
+ return;
13445
+ }
13446
+ setAuthSuccess(false);
13447
+ setMode('reset-password');
13348
13448
  setResetSuccess(true);
13349
- // Use backend message if available, otherwise default
13350
13449
  setSuccessMessage(result?.message || 'Password reset email sent! Please check your inbox.');
13351
13450
  setShowRequestNewReset(false);
13352
13451
  setResetRequestEmail('');
13353
13452
  }
13354
13453
  catch (err) {
13454
+ setAuthSuccess(false);
13455
+ setResetSuccess(false);
13456
+ setSuccessMessage(undefined);
13355
13457
  setError(getFriendlyErrorMessage(err));
13458
+ setShowRequestNewReset(true);
13356
13459
  onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13357
13460
  }
13358
13461
  finally {
@@ -13407,21 +13510,20 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13407
13510
  if (nativeBridge) {
13408
13511
  log.log('Using native bridge for Google Sign-In');
13409
13512
  const callbackId = `google_auth_${Date.now()}`;
13410
- window.smartlinksNativeCallback = async (result) => {
13411
- // Ignore stale callbacks
13412
- if (result.callbackId !== callbackId) {
13413
- log.log('Ignoring stale native callback:', result.callbackId);
13414
- return;
13415
- }
13416
- log.log('Native callback received:', {
13513
+ // Register callback via AuthKit.onAuthResult (the native app calls this)
13514
+ const cleanup = registerAuthKitCallback(callbackId, async (result) => {
13515
+ log.log('Native Google auth result received:', {
13417
13516
  success: result.success,
13418
13517
  hasIdToken: !!result.idToken,
13419
13518
  email: result.email,
13420
13519
  error: result.error,
13520
+ callbackId: result.callbackId,
13421
13521
  });
13522
+ setGoogleNativeTimedOut(false);
13422
13523
  try {
13423
13524
  if (result.success && result.idToken) {
13424
13525
  const authResponse = await api.loginWithGoogle(result.idToken);
13526
+ log.log('Native Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
13425
13527
  if (authResponse.token) {
13426
13528
  await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
13427
13529
  setAuthSuccess(true);
@@ -13444,7 +13546,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13444
13546
  finally {
13445
13547
  setLoading(false);
13446
13548
  }
13447
- };
13549
+ }, 30000, // 30s timeout for interactive Google Sign-In
13550
+ () => {
13551
+ log.log('Native Google Sign-In timed out after 30s');
13552
+ setLoading(false);
13553
+ setGoogleNativeTimedOut(true);
13554
+ });
13448
13555
  const payloadObj = {
13449
13556
  type: 'GOOGLE_SIGN_IN',
13450
13557
  clientId,
@@ -13456,12 +13563,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13456
13563
  requestServerAuthCode: false,
13457
13564
  };
13458
13565
  const payload = JSON.stringify(payloadObj);
13459
- log.log('Invoking native signInWithGoogle');
13566
+ log.log('Invoking native signInWithGoogle with callbackId:', callbackId);
13460
13567
  try {
13461
13568
  nativeBridge.signInWithGoogle(payload);
13462
13569
  }
13463
13570
  catch (invokeError) {
13464
13571
  console.error(`${LOG_PREFIX} Exception invoking signInWithGoogle:`, invokeError);
13572
+ cleanup(); // Clean up the callback registration
13465
13573
  throw invokeError;
13466
13574
  }
13467
13575
  // Don't set loading to false - waiting for native callback
@@ -13506,6 +13614,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13506
13614
  clearInterval(checkClosed);
13507
13615
  try {
13508
13616
  if (event.data.success && event.data.token) {
13617
+ log.log('Google proxy result received:', { success: true, hasUser: !!event.data.user, isNewUser: event.data.isNewUser });
13509
13618
  const { token, user, accountData, isNewUser, expiresAt, expiresIn } = event.data;
13510
13619
  const expiration = expiresAt || (expiresIn ? Date.now() + expiresIn : undefined);
13511
13620
  await auth.login(token, user, accountData, isNewUser, expiration);
@@ -13633,6 +13742,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13633
13742
  googleUserInfo: userInfo,
13634
13743
  tokenType: 'access_token',
13635
13744
  });
13745
+ log.log('Popup Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
13636
13746
  if (authResponse.token) {
13637
13747
  // Google OAuth can be login or signup - use isNewUser flag from backend if available
13638
13748
  await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
@@ -13672,12 +13782,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13672
13782
  client_id: googleClientId,
13673
13783
  origin: window.location.origin,
13674
13784
  });
13785
+ // Track whether the credential callback has fired (user selected an account)
13786
+ // so we don't show the "blocked" fallback when the prompt closes after a successful selection
13787
+ let credentialCallbackFired = false;
13675
13788
  google.accounts.id.initialize({
13676
13789
  client_id: googleClientId,
13677
13790
  callback: async (response) => {
13791
+ credentialCallbackFired = true;
13678
13792
  try {
13679
13793
  const idToken = response.credential;
13680
13794
  const authResponse = await api.loginWithGoogle(idToken);
13795
+ log.log('OneTap Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
13681
13796
  if (authResponse.token) {
13682
13797
  // Google OAuth can be login or signup - use isNewUser flag from backend if available
13683
13798
  await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
@@ -13701,23 +13816,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13701
13816
  cancel_on_tap_outside: true,
13702
13817
  use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
13703
13818
  });
13704
- // Use timeout fallback — if no prompt interaction after 5s, assume FedCM was blocked
13819
+ // Use timeout fallback — if no prompt interaction after 15s, assume FedCM was blocked
13820
+ // (15s gives users enough time to choose a Google account from the picker)
13705
13821
  const promptTimeout = setTimeout(() => {
13822
+ if (credentialCallbackFired)
13823
+ return; // Auth is already in progress, don't show fallback
13706
13824
  log.log('Google OneTap prompt timed out — FedCM may be blocked or unavailable');
13707
13825
  setGoogleFallbackToPopup(true);
13708
13826
  setLoading(false);
13709
- }, 5000);
13827
+ }, 15000);
13710
13828
  google.accounts.id.prompt((notification) => {
13711
13829
  clearTimeout(promptTimeout);
13830
+ // If the credential callback already fired, the user successfully selected an account
13831
+ // The prompt closing with 'dismissed' is expected — don't show fallback
13832
+ if (credentialCallbackFired) {
13833
+ log.log('Google OneTap prompt closed after credential selection — ignoring');
13834
+ return;
13835
+ }
13712
13836
  // Check for FedCM/OneTap dismissal or blocking
13713
- // notification may have getMomentType(), getDismissedReason(), getSkippedReason()
13714
13837
  const momentType = notification?.getMomentType?.();
13715
13838
  const dismissedReason = notification?.getDismissedReason?.();
13716
13839
  const skippedReason = notification?.getSkippedReason?.();
13717
13840
  log.log('Google OneTap prompt notification:', { momentType, dismissedReason, skippedReason });
13718
13841
  if (momentType === 'skipped' || momentType === 'dismissed') {
13719
- // User dismissed the prompt, or browser blocked it (FedCM disabled)
13720
- // Offer popup flow as alternative
13721
13842
  log.log('Google OneTap was dismissed/skipped, offering popup fallback');
13722
13843
  setGoogleFallbackToPopup(true);
13723
13844
  }
@@ -13744,6 +13865,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13744
13865
  else {
13745
13866
  // Verify code - Twilio identifies the verification by phone number
13746
13867
  const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
13868
+ log.log('Phone verification response:', { hasToken: !!response?.token, hasUser: !!response?.user, isNewUser: response?.isNewUser });
13747
13869
  // Defensive validation: API may return undefined or error object on failure
13748
13870
  if (!response) {
13749
13871
  throw new Error('Verification failed - please check your code and try again');
@@ -13778,6 +13900,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13778
13900
  const handlePasswordReset = async (emailOrPassword, confirmPassword) => {
13779
13901
  setLoading(true);
13780
13902
  setError(undefined);
13903
+ setAuthSuccess(false);
13904
+ setResetSuccess(false);
13905
+ setSuccessMessage(undefined);
13781
13906
  const effectiveRedirectUrl = getRedirectUrl();
13782
13907
  try {
13783
13908
  if (resetToken && confirmPassword) {
@@ -13786,6 +13911,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13786
13911
  // Auto-login with the new password if we have the email
13787
13912
  if (resetEmail) {
13788
13913
  try {
13914
+ log.log('Auto-login after password reset for:', resetEmail);
13789
13915
  const loginResponse = await api.login(resetEmail, emailOrPassword);
13790
13916
  if (loginResponse.token) {
13791
13917
  await auth.login(loginResponse.token, loginResponse.user, loginResponse.accountData, false);
@@ -13798,9 +13924,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13798
13924
  }
13799
13925
  }
13800
13926
  catch (loginErr) {
13801
- // Auto-login failed, fall back to showing success message
13927
+ log.warn('Auto-login after password reset failed, requiring manual login:', loginErr);
13802
13928
  }
13803
13929
  }
13930
+ else {
13931
+ log.warn('No resetEmail available for auto-login after password reset');
13932
+ }
13804
13933
  // Fallback: show success but require manual login
13805
13934
  setResetSuccess(true);
13806
13935
  setSuccessMessage('Password reset successful! Please sign in with your new password.');
@@ -13810,14 +13939,35 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13810
13939
  else {
13811
13940
  // Request password reset email
13812
13941
  const result = await api.requestPasswordReset(emailOrPassword, effectiveRedirectUrl);
13942
+ const resultError = getActionResultErrorMessage(result);
13943
+ if (resultError) {
13944
+ setAuthSuccess(false);
13945
+ setResetSuccess(false);
13946
+ setSuccessMessage(undefined);
13947
+ setError(resultError);
13948
+ return;
13949
+ }
13950
+ const resultAny = result;
13951
+ const isConfirmedSuccess = result && (resultAny.success === true ||
13952
+ resultAny.ok === true ||
13953
+ (resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
13954
+ if (!isConfirmedSuccess) {
13955
+ setAuthSuccess(false);
13956
+ setResetSuccess(false);
13957
+ setSuccessMessage(undefined);
13958
+ setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
13959
+ return;
13960
+ }
13813
13961
  setResetSuccess(true);
13814
- // Use backend message if available
13815
13962
  if (result?.message) {
13816
13963
  setSuccessMessage(result.message);
13817
13964
  }
13818
13965
  }
13819
13966
  }
13820
13967
  catch (err) {
13968
+ setAuthSuccess(false);
13969
+ setResetSuccess(false);
13970
+ setSuccessMessage(undefined);
13821
13971
  setError(getFriendlyErrorMessage(err));
13822
13972
  onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13823
13973
  }
@@ -13881,7 +14031,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13881
14031
  setResetToken(undefined); // Clear token when going back
13882
14032
  setResetEmail(undefined); // Clear email when going back
13883
14033
  setSuccessMessage(undefined); // Clear success message
13884
- }, loading: loading, error: error, success: resetSuccess, successMessage: successMessage, token: resetToken })) : (mode === 'login' || mode === 'register') ? (jsx(Fragment, { children: showResendVerification ? (jsxs("div", { style: {
14034
+ }, loading: loading, error: error, success: resetSuccess, successMessage: successMessage, token: resetToken, resetEmail: resetEmail })) : (mode === 'login' || mode === 'register') ? (jsx(Fragment, { children: showResendVerification ? (jsxs("div", { style: {
13885
14035
  marginTop: '1rem',
13886
14036
  padding: '1.5rem',
13887
14037
  backgroundColor: config?.branding?.inheritHostStyles
@@ -13979,7 +14129,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13979
14129
  color: config?.branding?.inheritHostStyles
13980
14130
  ? 'hsl(var(--foreground, 215 25% 15%))'
13981
14131
  : (resolvedTheme === 'dark' ? '#f1f5f9' : '#374151')
13982
- }, children: "Password Reset Link Expired" }), jsx("p", { style: {
14132
+ }, children: "Password Reset Link Expired" }), error && (jsx("div", { style: {
14133
+ marginBottom: '1rem',
14134
+ padding: '0.75rem',
14135
+ backgroundColor: resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.15)' : 'rgba(239, 68, 68, 0.1)',
14136
+ border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.3)' : 'rgba(239, 68, 68, 0.2)'}`,
14137
+ borderRadius: '0.375rem',
14138
+ color: resolvedTheme === 'dark' ? '#fca5a5' : '#dc2626',
14139
+ fontSize: '0.875rem',
14140
+ lineHeight: '1.5',
14141
+ }, children: error })), jsx("p", { style: {
13983
14142
  marginBottom: '1rem',
13984
14143
  fontSize: '0.875rem',
13985
14144
  color: config?.branding?.inheritHostStyles
@@ -14078,10 +14237,48 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14078
14237
  backgroundColor: 'transparent',
14079
14238
  border: 'none',
14080
14239
  cursor: 'pointer',
14081
- }, children: "Dismiss" })] })), (() => {
14240
+ }, children: "Dismiss" })] })), googleNativeTimedOut && (jsxs("div", { style: {
14241
+ marginBottom: '1rem',
14242
+ padding: '0.75rem 1rem',
14243
+ borderRadius: '0.5rem',
14244
+ backgroundColor: resolvedTheme === 'dark' ? 'rgba(245, 158, 11, 0.1)' : 'rgba(245, 158, 11, 0.05)',
14245
+ border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(245, 158, 11, 0.3)' : 'rgba(245, 158, 11, 0.2)'}`,
14246
+ }, children: [jsx("p", { style: {
14247
+ fontSize: '0.8125rem',
14248
+ color: resolvedTheme === 'dark' ? '#fbbf24' : '#92400e',
14249
+ marginBottom: '0.5rem',
14250
+ lineHeight: 1.4,
14251
+ }, children: 'Google sign-in didn\'t respond. You can try again or use another sign-in method.' }), jsx("button", { onClick: () => {
14252
+ setGoogleNativeTimedOut(false);
14253
+ handleGoogleLogin();
14254
+ }, style: {
14255
+ width: '100%',
14256
+ padding: '0.5rem 1rem',
14257
+ fontSize: '0.875rem',
14258
+ fontWeight: 500,
14259
+ color: '#fff',
14260
+ backgroundColor: '#4285F4',
14261
+ border: 'none',
14262
+ borderRadius: '0.375rem',
14263
+ cursor: 'pointer',
14264
+ }, children: 'Retry Google Sign-In' }), jsx("button", { onClick: () => setGoogleNativeTimedOut(false), style: {
14265
+ marginTop: '0.375rem',
14266
+ width: '100%',
14267
+ padding: '0.25rem',
14268
+ fontSize: '0.75rem',
14269
+ color: resolvedTheme === 'dark' ? '#64748b' : '#9ca3af',
14270
+ backgroundColor: 'transparent',
14271
+ border: 'none',
14272
+ cursor: 'pointer',
14273
+ }, children: 'Dismiss' })] })), (() => {
14082
14274
  const emailDisplayMode = config?.emailDisplayMode || 'form';
14083
14275
  const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
14084
14276
  const actualProviders = config?.enabledProviders || enabledProviders;
14277
+ const hasEmailProvider = actualProviders.includes('email');
14278
+ // If email provider is not enabled, only show provider buttons (no email/password form)
14279
+ if (!hasEmailProvider) {
14280
+ return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
14281
+ }
14085
14282
  // Button mode: show provider selection first, then email form if email is selected
14086
14283
  if (emailDisplayMode === 'button' && !showEmailForm) {
14087
14284
  return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
@@ -14103,7 +14300,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14103
14300
  setShowResendVerification(false);
14104
14301
  setShowRequestNewReset(false);
14105
14302
  setError(undefined);
14106
- }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
14303
+ }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, signupRedirectUrl: config?.signupRedirectUrl || customization?.signupRedirectUrl, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
14107
14304
  })()] })) })) : null }));
14108
14305
  };
14109
14306