@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.js CHANGED
@@ -278,7 +278,7 @@ const AuthModeToggle = ({ mode, onModeChange, disabled = false, }) => {
278
278
  return (jsxRuntime.jsxs("div", { className: "auth-mode-toggle", role: "tablist", "aria-label": "Authentication mode", children: [jsxRuntime.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" }), jsxRuntime.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" })] }));
279
279
  };
280
280
 
281
- const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, signupProminence = 'minimal', schema, registrationFieldsConfig = [], additionalFields = [], }) => {
281
+ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, signupProminence = 'minimal', signupRedirectUrl, schema, registrationFieldsConfig = [], additionalFields = [], }) => {
282
282
  const [formData, setFormData] = React.useState({
283
283
  email: '',
284
284
  password: '',
@@ -334,7 +334,7 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
334
334
  if (newMode !== mode) {
335
335
  onModeSwitch();
336
336
  }
337
- }, disabled: loading })), jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: title }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.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' && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsxRuntime.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" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsxRuntime.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" })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsxRuntime.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 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsxRuntime.jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsxRuntime.jsx("div", { className: "auth-form-footer", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && (jsxRuntime.jsxs("div", { className: "auth-divider", children: [jsxRuntime.jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] }))] }));
337
+ }, disabled: loading })), jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: title }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.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' && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsxRuntime.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" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsxRuntime.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" })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsxRuntime.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 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsxRuntime.jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsxRuntime.jsx("div", { className: "auth-form-footer", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && signupProminence !== 'none' && (jsxRuntime.jsxs("div", { className: "auth-divider", children: [jsxRuntime.jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), signupRedirectUrl && mode === 'login' ? (jsxRuntime.jsx("a", { href: signupRedirectUrl, target: "_top", className: "auth-link auth-link-bold", children: "Sign up" })) : (jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' }))] }))] }));
338
338
  };
339
339
 
340
340
  const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
@@ -10831,7 +10831,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
10831
10831
  : 'Resend code' }) })] })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : codeSent ? ('Verify Code') : ('Send Code') }), jsxRuntime.jsx("div", { className: "auth-divider", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to login" }) })] }));
10832
10832
  };
10833
10833
 
10834
- const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, }) => {
10834
+ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, resetEmail, }) => {
10835
10835
  const [email, setEmail] = React.useState('');
10836
10836
  const [password, setPassword] = React.useState('');
10837
10837
  const [confirmPassword, setConfirmPassword] = React.useState('');
@@ -10856,14 +10856,14 @@ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successM
10856
10856
  await onSubmit(email);
10857
10857
  }
10858
10858
  };
10859
- if (success) {
10859
+ if (success && !error) {
10860
10860
  return (jsxRuntime.jsxs("div", { className: "auth-form", children: [jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("div", { style: { textAlign: 'center', marginBottom: '1rem' }, children: jsxRuntime.jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", fill: "none", style: { margin: '0 auto' }, children: [jsxRuntime.jsx("circle", { cx: "32", cy: "32", r: "32", fill: "#10b981", fillOpacity: "0.1" }), jsxRuntime.jsx("path", { d: "M20 32l8 8 16-16", stroke: "#10b981", strokeWidth: "4", strokeLinecap: "round", strokeLinejoin: "round" })] }) }), jsxRuntime.jsx("h2", { className: "auth-form-title", children: token ? 'Password reset!' : 'Check your email' }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: token
10861
10861
  ? (successMessage || 'Your password has been successfully reset. You can now sign in with your new password.')
10862
10862
  : (successMessage || `We've sent password reset instructions to ${email}`) })] }), jsxRuntime.jsx("button", { type: "button", className: "auth-button auth-button-primary", onClick: onBack, children: "Back to Sign in" })] }));
10863
10863
  }
10864
10864
  return (jsxRuntime.jsxs("form", { className: "auth-form", onSubmit: handleSubmit, children: [jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: token ? 'Set new password' : 'Reset your password' }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: token
10865
10865
  ? 'Enter your new password below.'
10866
- : "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.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 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsxRuntime.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 })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsxRuntime.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 })] })] })) : (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsxRuntime.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" })] })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsxRuntime.jsx("div", { className: "auth-divider", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
10866
+ : "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.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 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [resetEmail && (jsxRuntime.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" })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsxRuntime.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 })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsxRuntime.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 })] })] })) : (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsxRuntime.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" })] })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsxRuntime.jsx("div", { className: "auth-divider", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
10867
10867
  };
10868
10868
 
10869
10869
  const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, }) => {
@@ -10969,14 +10969,46 @@ class AuthAPI {
10969
10969
  return smartlinks__namespace.authKit.verifyPhoneCode(this.clientId, phoneNumber, code);
10970
10970
  }
10971
10971
  async requestPasswordReset(email, redirectUrl) {
10972
- return smartlinks__namespace.authKit.requestPasswordReset(this.clientId, {
10973
- email,
10974
- redirectUrl,
10975
- clientName: this.clientName
10976
- });
10972
+ console.log('[AuthKit:API] requestPasswordReset called', { clientId: this.clientId, email, redirectUrl });
10973
+ try {
10974
+ const result = await smartlinks__namespace.authKit.requestPasswordReset(this.clientId, {
10975
+ email,
10976
+ redirectUrl,
10977
+ clientName: this.clientName
10978
+ });
10979
+ console.log('[AuthKit:API] requestPasswordReset resolved:', JSON.stringify(result));
10980
+ return result;
10981
+ }
10982
+ catch (error) {
10983
+ const err = error;
10984
+ console.error('[AuthKit:API] requestPasswordReset threw:', {
10985
+ type: typeof error,
10986
+ message: err?.message,
10987
+ statusCode: err?.statusCode ?? err?.response?.status,
10988
+ errorCode: err?.errorCode ?? err?.details?.errorCode,
10989
+ details: err?.details ?? err?.response?.data,
10990
+ keys: err ? Object.keys(err) : [],
10991
+ });
10992
+ throw error;
10993
+ }
10977
10994
  }
10978
10995
  async verifyResetToken(token) {
10979
- return smartlinks__namespace.authKit.verifyResetToken(this.clientId, token);
10996
+ console.log('[AuthKit:API] verifyResetToken called');
10997
+ try {
10998
+ const result = await smartlinks__namespace.authKit.verifyResetToken(this.clientId, token);
10999
+ console.log('[AuthKit:API] verifyResetToken resolved:', JSON.stringify(result));
11000
+ return result;
11001
+ }
11002
+ catch (error) {
11003
+ const err = error;
11004
+ console.error('[AuthKit:API] verifyResetToken threw:', {
11005
+ type: typeof error,
11006
+ message: err?.message,
11007
+ statusCode: err?.statusCode ?? err?.response?.status,
11008
+ errorCode: err?.errorCode ?? err?.details?.errorCode,
11009
+ });
11010
+ throw error;
11011
+ }
10980
11012
  }
10981
11013
  async completePasswordReset(token, newPassword) {
10982
11014
  return smartlinks__namespace.authKit.completePasswordReset(this.clientId, token, newPassword);
@@ -11012,7 +11044,6 @@ class AuthAPI {
11012
11044
  this.log.warn('Failed to fetch UI config, using defaults:', error);
11013
11045
  return {
11014
11046
  branding: {
11015
- title: 'Smartlinks Auth',
11016
11047
  subtitle: 'Sign in to your account',
11017
11048
  primaryColor: '#3B82F6',
11018
11049
  secondaryColor: '#1D4ED8',
@@ -12408,6 +12439,10 @@ function getFriendlyErrorMessage(error) {
12408
12439
  if (isApiErrorLike(error)) {
12409
12440
  // First, check for specific error code (most precise)
12410
12441
  const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
12442
+ // For rate-limit errors, prefer the backend's message as it includes specific wait times
12443
+ if (errorCode === 'RATE_LIMIT_EXCEEDED' && error.message && error.message !== '[object Object]') {
12444
+ return error.message;
12445
+ }
12411
12446
  if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
12412
12447
  return ERROR_CODE_MESSAGES[errorCode];
12413
12448
  }
@@ -12415,6 +12450,10 @@ function getFriendlyErrorMessage(error) {
12415
12450
  if (error.statusCode >= 500) {
12416
12451
  return 'Server error. Please try again later.';
12417
12452
  }
12453
+ // For 429 status, prefer backend message (may include wait time)
12454
+ if (error.statusCode === 429 && error.message && error.message !== '[object Object]') {
12455
+ return error.message;
12456
+ }
12418
12457
  if (STATUS_MESSAGES[error.statusCode]) {
12419
12458
  return STATUS_MESSAGES[error.statusCode];
12420
12459
  }
@@ -12541,7 +12580,7 @@ function requiresEmailVerification(error) {
12541
12580
  }
12542
12581
 
12543
12582
  // VERSION: Update this when making changes to help identify which version is running
12544
- const AUTH_UI_VERSION = '44';
12583
+ const AUTH_UI_VERSION = '46';
12545
12584
  const LOG_PREFIX = `[SmartlinksAuthUI:v${AUTH_UI_VERSION}]`;
12546
12585
  // Helper to check for URL auth params synchronously (runs during initialization)
12547
12586
  // This prevents the form from flashing before detecting deep-link flows
@@ -12565,6 +12604,24 @@ const getExpirationFromResponse = (response) => {
12565
12604
  return Date.now() + response.expiresIn;
12566
12605
  return undefined; // Will use 7-day default in tokenStorage
12567
12606
  };
12607
+ const getActionResultErrorMessage = (result) => {
12608
+ if (!result || typeof result !== 'object')
12609
+ return null;
12610
+ const candidate = result;
12611
+ const numericStatusCode = typeof candidate.statusCode === 'string'
12612
+ ? Number(candidate.statusCode)
12613
+ : candidate.statusCode;
12614
+ const hasFailureStatus = typeof numericStatusCode === 'number' && !Number.isNaN(numericStatusCode) && numericStatusCode >= 400;
12615
+ const isFailure = candidate.success === false || candidate.ok === false || !!candidate.errorCode || hasFailureStatus;
12616
+ if (!isFailure)
12617
+ return null;
12618
+ return getFriendlyErrorMessage({
12619
+ statusCode: hasFailureStatus ? numericStatusCode : 400,
12620
+ errorCode: candidate.errorCode,
12621
+ message: candidate.message || 'Request failed. Please try again.',
12622
+ details: candidate.details,
12623
+ });
12624
+ };
12568
12625
  // Default Smartlinks Google OAuth Client ID (public - safe to expose)
12569
12626
  const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
12570
12627
  // Default Google OAuth proxy URL (hosted on our whitelisted domain)
@@ -12656,6 +12713,43 @@ const getNativeBridge = () => {
12656
12713
  return native;
12657
12714
  return null;
12658
12715
  };
12716
+ // Helper to register an AuthKit.onAuthResult handler for a specific callbackId
12717
+ // The Android native app calls: window.AuthKit.onAuthResult(resultJson)
12718
+ const registerAuthKitCallback = (callbackId, onResult, timeoutMs = 5000, onTimeout) => {
12719
+ const bridge = window.AuthKit;
12720
+ if (!bridge) {
12721
+ console.warn('[AuthKit] No AuthKit bridge found, cannot register callback');
12722
+ onTimeout?.();
12723
+ return () => { };
12724
+ }
12725
+ // Store the previous handler so we can restore on cleanup
12726
+ const previousHandler = bridge.onAuthResult;
12727
+ const timeout = setTimeout(() => {
12728
+ // Restore previous handler on timeout
12729
+ bridge.onAuthResult = previousHandler;
12730
+ onTimeout?.();
12731
+ }, timeoutMs);
12732
+ bridge.onAuthResult = (resultOrJson) => {
12733
+ const result = typeof resultOrJson === 'string' ? JSON.parse(resultOrJson) : resultOrJson;
12734
+ // Match by callbackId if present, otherwise accept any result
12735
+ if (result.callbackId && result.callbackId !== callbackId) {
12736
+ // Not for us — pass through to previous handler if any
12737
+ if (typeof previousHandler === 'function') {
12738
+ previousHandler(resultOrJson);
12739
+ }
12740
+ return;
12741
+ }
12742
+ clearTimeout(timeout);
12743
+ // Restore previous handler
12744
+ bridge.onAuthResult = previousHandler;
12745
+ onResult(result);
12746
+ };
12747
+ // Return cleanup function
12748
+ return () => {
12749
+ clearTimeout(timeout);
12750
+ bridge.onAuthResult = previousHandler;
12751
+ };
12752
+ };
12659
12753
  // Sign out from Google on the native side (clears cached Google account)
12660
12754
  // This is fire-and-forget with a timeout - gracefully degrades if not supported
12661
12755
  const signOutGoogleNative = async () => {
@@ -12664,21 +12758,7 @@ const signOutGoogleNative = async () => {
12664
12758
  return;
12665
12759
  const callbackId = `google_signout_${Date.now()}`;
12666
12760
  return new Promise((resolve) => {
12667
- const timeout = setTimeout(() => resolve(), 3000);
12668
- // Store original callback to restore later
12669
- const originalCallback = window.smartlinksNativeCallback;
12670
- window.smartlinksNativeCallback = (result) => {
12671
- if (result.callbackId === callbackId) {
12672
- clearTimeout(timeout);
12673
- // Restore original callback
12674
- window.smartlinksNativeCallback = originalCallback;
12675
- resolve();
12676
- }
12677
- else if (originalCallback) {
12678
- // Pass through to original callback for other messages
12679
- originalCallback(result);
12680
- }
12681
- };
12761
+ registerAuthKitCallback(callbackId, () => resolve(), 3000, () => resolve());
12682
12762
  const payload = JSON.stringify({
12683
12763
  type: 'GOOGLE_SIGN_OUT',
12684
12764
  callbackId,
@@ -12692,44 +12772,32 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
12692
12772
  return null;
12693
12773
  const callbackId = `google_check_${Date.now()}`;
12694
12774
  return new Promise((resolve) => {
12695
- const timeout = setTimeout(() => resolve(null), 5000);
12696
- // Store original callback to restore later
12697
- const originalCallback = window.smartlinksNativeCallback;
12698
- window.smartlinksNativeCallback = (result) => {
12699
- if (result.callbackId === callbackId) {
12700
- clearTimeout(timeout);
12701
- // Restore original callback
12702
- window.smartlinksNativeCallback = originalCallback;
12703
- if (result.success) {
12704
- resolve({
12705
- isSignedIn: result.isSignedIn || false,
12706
- idToken: result.idToken,
12707
- email: result.email,
12708
- name: result.name,
12709
- picture: result.picture,
12710
- });
12711
- }
12712
- else {
12713
- resolve(null);
12714
- }
12775
+ registerAuthKitCallback(callbackId, (result) => {
12776
+ if (result.success) {
12777
+ resolve({
12778
+ isSignedIn: result.isSignedIn || false,
12779
+ idToken: result.idToken,
12780
+ email: result.email,
12781
+ name: result.name,
12782
+ picture: result.picture,
12783
+ });
12715
12784
  }
12716
- else if (originalCallback) {
12717
- // Pass through to original callback for other messages
12718
- originalCallback(result);
12785
+ else {
12786
+ resolve(null);
12719
12787
  }
12720
- };
12788
+ }, 5000, () => resolve(null));
12721
12789
  const payload = JSON.stringify({
12722
12790
  type: 'GOOGLE_CHECK_SIGN_IN',
12723
12791
  clientId,
12724
12792
  googleClientId,
12725
- serverClientId: googleClientId, // Alias for Android SDK
12793
+ serverClientId: googleClientId,
12726
12794
  callbackId,
12727
12795
  });
12728
12796
  nativeBridge.checkGoogleSignIn(payload);
12729
12797
  });
12730
12798
  };
12731
12799
  // getFriendlyErrorMessage is now imported from ../utils/errorHandling
12732
- 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, }) => {
12800
+ 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, }) => {
12733
12801
  // Resolve signup prominence from props, customization, config, or default
12734
12802
  const resolvedSignupProminence = signupProminence || customization?.signupProminence || 'minimal';
12735
12803
  // Determine initial mode based on signupProminence setting
@@ -12766,6 +12834,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12766
12834
  const [contactSchema, setContactSchema] = React.useState(null); // Schema for registration fields
12767
12835
  const [silentSignInChecked, setSilentSignInChecked] = React.useState(false); // Track if silent sign-in has been checked
12768
12836
  const [googleFallbackToPopup, setGoogleFallbackToPopup] = React.useState(false); // Show popup fallback when FedCM is blocked/dismissed
12837
+ const [googleNativeTimedOut, setGoogleNativeTimedOut] = React.useState(false); // Native bridge callback timed out
12769
12838
  const log = React.useMemo(() => createLoggerWrapper(logger), [logger]);
12770
12839
  const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
12771
12840
  const auth = useAuth();
@@ -13042,6 +13111,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13042
13111
  if (urlMode === 'verifyEmail') {
13043
13112
  log.log('Verifying email with token:', token);
13044
13113
  const response = await api.verifyEmailWithToken(token);
13114
+ log.log('Email verification response:', { hasToken: !!response.token, hasUser: !!response.user, emailVerificationMode: response.emailVerificationMode, isNewUser: response.isNewUser });
13045
13115
  // Get email verification mode from response or config
13046
13116
  const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-auto-login';
13047
13117
  if ((verificationMode === 'verify-auto-login' || verificationMode === 'immediate') && response.token) {
@@ -13086,9 +13156,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13086
13156
  log.log('Verifying reset token:', token);
13087
13157
  // Verify token is valid, then show password reset form
13088
13158
  const verifyResult = await api.verifyResetToken(token);
13089
- // Check if token is valid before proceeding
13090
- if (!verifyResult.valid) {
13091
- throw new Error(verifyResult.message || 'Invalid or expired token');
13159
+ // Guard against undefined result (proxy mode may resolve without data)
13160
+ if (!verifyResult || !verifyResult.valid) {
13161
+ throw new Error(verifyResult?.message || 'Invalid or expired token');
13092
13162
  }
13093
13163
  setUrlAuthProcessing(false); // Clear processing state - showing reset form
13094
13164
  setResetToken(token); // Store token for use in password reset
@@ -13103,6 +13173,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13103
13173
  else if (urlMode === 'magicLink') {
13104
13174
  log.log('Verifying magic link token:', token);
13105
13175
  const response = await api.verifyMagicLink(token);
13176
+ log.log('Magic link verification response:', { hasToken: !!response.token, hasUser: !!response.user, isNewUser: response.isNewUser });
13106
13177
  // Auto-login with magic link if token is provided
13107
13178
  if (response.token) {
13108
13179
  // Always await - auth.login now waits for parent ack automatically in iframe mode
@@ -13178,6 +13249,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13178
13249
  const redirectUri = state.redirectUri || window.location.origin + window.location.pathname;
13179
13250
  // Exchange authorization code for tokens
13180
13251
  const response = await api.loginWithGoogleCode(code, redirectUri);
13252
+ log.log('Google OAuth code exchange response:', { hasToken: !!response.token, hasUser: !!response.user, isNewUser: response.isNewUser });
13181
13253
  if (response.token) {
13182
13254
  // Await login to ensure token is persisted before any navigation
13183
13255
  await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response));
@@ -13208,13 +13280,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13208
13280
  setError(undefined);
13209
13281
  setAuthSuccess(false);
13210
13282
  try {
13283
+ log.log('Submitting email auth:', { mode, email: data.email });
13211
13284
  const response = mode === 'login'
13212
13285
  ? await api.login(data.email, data.password)
13213
13286
  : await api.register({
13214
13287
  ...data,
13215
13288
  accountData: mode === 'register' ? accountData : undefined,
13216
- redirectUrl: getRedirectUrl(), // Include redirect URL for email verification
13289
+ redirectUrl: getRedirectUrl(),
13217
13290
  });
13291
+ log.log('Email auth response:', { mode, hasToken: !!response?.token, hasUser: !!response?.user, isNewUser: response?.isNewUser, requiresEmailVerification: response?.requiresEmailVerification, accountLocked: response?.accountLocked, emailVerificationMode: response?.emailVerificationMode });
13218
13292
  // Defensive check: validate response before accessing properties
13219
13293
  // SDK should throw on 401/error responses, but handle edge cases gracefully
13220
13294
  if (!response) {
@@ -13362,17 +13436,46 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13362
13436
  return;
13363
13437
  setLoading(true);
13364
13438
  setError(undefined);
13439
+ setAuthSuccess(false);
13440
+ setResetSuccess(false);
13441
+ setSuccessMessage(undefined);
13442
+ setShowResendVerification(false);
13365
13443
  try {
13366
13444
  const result = await api.requestPasswordReset(resetRequestEmail, getRedirectUrl());
13367
- // Use resetSuccess (not authSuccess) to show password reset confirmation, not "Login successful"
13445
+ const resultError = getActionResultErrorMessage(result);
13446
+ if (resultError) {
13447
+ setAuthSuccess(false);
13448
+ setResetSuccess(false);
13449
+ setSuccessMessage(undefined);
13450
+ setError(resultError);
13451
+ setShowRequestNewReset(true);
13452
+ return;
13453
+ }
13454
+ const resultAny = result;
13455
+ const isConfirmedSuccess = result && (resultAny.success === true ||
13456
+ resultAny.ok === true ||
13457
+ (resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
13458
+ if (!isConfirmedSuccess) {
13459
+ setAuthSuccess(false);
13460
+ setResetSuccess(false);
13461
+ setSuccessMessage(undefined);
13462
+ setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
13463
+ setShowRequestNewReset(true);
13464
+ return;
13465
+ }
13466
+ setAuthSuccess(false);
13467
+ setMode('reset-password');
13368
13468
  setResetSuccess(true);
13369
- // Use backend message if available, otherwise default
13370
13469
  setSuccessMessage(result?.message || 'Password reset email sent! Please check your inbox.');
13371
13470
  setShowRequestNewReset(false);
13372
13471
  setResetRequestEmail('');
13373
13472
  }
13374
13473
  catch (err) {
13474
+ setAuthSuccess(false);
13475
+ setResetSuccess(false);
13476
+ setSuccessMessage(undefined);
13375
13477
  setError(getFriendlyErrorMessage(err));
13478
+ setShowRequestNewReset(true);
13376
13479
  onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13377
13480
  }
13378
13481
  finally {
@@ -13427,21 +13530,20 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13427
13530
  if (nativeBridge) {
13428
13531
  log.log('Using native bridge for Google Sign-In');
13429
13532
  const callbackId = `google_auth_${Date.now()}`;
13430
- window.smartlinksNativeCallback = async (result) => {
13431
- // Ignore stale callbacks
13432
- if (result.callbackId !== callbackId) {
13433
- log.log('Ignoring stale native callback:', result.callbackId);
13434
- return;
13435
- }
13436
- log.log('Native callback received:', {
13533
+ // Register callback via AuthKit.onAuthResult (the native app calls this)
13534
+ const cleanup = registerAuthKitCallback(callbackId, async (result) => {
13535
+ log.log('Native Google auth result received:', {
13437
13536
  success: result.success,
13438
13537
  hasIdToken: !!result.idToken,
13439
13538
  email: result.email,
13440
13539
  error: result.error,
13540
+ callbackId: result.callbackId,
13441
13541
  });
13542
+ setGoogleNativeTimedOut(false);
13442
13543
  try {
13443
13544
  if (result.success && result.idToken) {
13444
13545
  const authResponse = await api.loginWithGoogle(result.idToken);
13546
+ log.log('Native Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
13445
13547
  if (authResponse.token) {
13446
13548
  await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
13447
13549
  setAuthSuccess(true);
@@ -13464,7 +13566,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13464
13566
  finally {
13465
13567
  setLoading(false);
13466
13568
  }
13467
- };
13569
+ }, 30000, // 30s timeout for interactive Google Sign-In
13570
+ () => {
13571
+ log.log('Native Google Sign-In timed out after 30s');
13572
+ setLoading(false);
13573
+ setGoogleNativeTimedOut(true);
13574
+ });
13468
13575
  const payloadObj = {
13469
13576
  type: 'GOOGLE_SIGN_IN',
13470
13577
  clientId,
@@ -13476,12 +13583,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13476
13583
  requestServerAuthCode: false,
13477
13584
  };
13478
13585
  const payload = JSON.stringify(payloadObj);
13479
- log.log('Invoking native signInWithGoogle');
13586
+ log.log('Invoking native signInWithGoogle with callbackId:', callbackId);
13480
13587
  try {
13481
13588
  nativeBridge.signInWithGoogle(payload);
13482
13589
  }
13483
13590
  catch (invokeError) {
13484
13591
  console.error(`${LOG_PREFIX} Exception invoking signInWithGoogle:`, invokeError);
13592
+ cleanup(); // Clean up the callback registration
13485
13593
  throw invokeError;
13486
13594
  }
13487
13595
  // Don't set loading to false - waiting for native callback
@@ -13526,6 +13634,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13526
13634
  clearInterval(checkClosed);
13527
13635
  try {
13528
13636
  if (event.data.success && event.data.token) {
13637
+ log.log('Google proxy result received:', { success: true, hasUser: !!event.data.user, isNewUser: event.data.isNewUser });
13529
13638
  const { token, user, accountData, isNewUser, expiresAt, expiresIn } = event.data;
13530
13639
  const expiration = expiresAt || (expiresIn ? Date.now() + expiresIn : undefined);
13531
13640
  await auth.login(token, user, accountData, isNewUser, expiration);
@@ -13653,6 +13762,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13653
13762
  googleUserInfo: userInfo,
13654
13763
  tokenType: 'access_token',
13655
13764
  });
13765
+ log.log('Popup Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
13656
13766
  if (authResponse.token) {
13657
13767
  // Google OAuth can be login or signup - use isNewUser flag from backend if available
13658
13768
  await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
@@ -13692,12 +13802,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13692
13802
  client_id: googleClientId,
13693
13803
  origin: window.location.origin,
13694
13804
  });
13805
+ // Track whether the credential callback has fired (user selected an account)
13806
+ // so we don't show the "blocked" fallback when the prompt closes after a successful selection
13807
+ let credentialCallbackFired = false;
13695
13808
  google.accounts.id.initialize({
13696
13809
  client_id: googleClientId,
13697
13810
  callback: async (response) => {
13811
+ credentialCallbackFired = true;
13698
13812
  try {
13699
13813
  const idToken = response.credential;
13700
13814
  const authResponse = await api.loginWithGoogle(idToken);
13815
+ log.log('OneTap Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
13701
13816
  if (authResponse.token) {
13702
13817
  // Google OAuth can be login or signup - use isNewUser flag from backend if available
13703
13818
  await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
@@ -13721,23 +13836,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13721
13836
  cancel_on_tap_outside: true,
13722
13837
  use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
13723
13838
  });
13724
- // Use timeout fallback — if no prompt interaction after 5s, assume FedCM was blocked
13839
+ // Use timeout fallback — if no prompt interaction after 15s, assume FedCM was blocked
13840
+ // (15s gives users enough time to choose a Google account from the picker)
13725
13841
  const promptTimeout = setTimeout(() => {
13842
+ if (credentialCallbackFired)
13843
+ return; // Auth is already in progress, don't show fallback
13726
13844
  log.log('Google OneTap prompt timed out — FedCM may be blocked or unavailable');
13727
13845
  setGoogleFallbackToPopup(true);
13728
13846
  setLoading(false);
13729
- }, 5000);
13847
+ }, 15000);
13730
13848
  google.accounts.id.prompt((notification) => {
13731
13849
  clearTimeout(promptTimeout);
13850
+ // If the credential callback already fired, the user successfully selected an account
13851
+ // The prompt closing with 'dismissed' is expected — don't show fallback
13852
+ if (credentialCallbackFired) {
13853
+ log.log('Google OneTap prompt closed after credential selection — ignoring');
13854
+ return;
13855
+ }
13732
13856
  // Check for FedCM/OneTap dismissal or blocking
13733
- // notification may have getMomentType(), getDismissedReason(), getSkippedReason()
13734
13857
  const momentType = notification?.getMomentType?.();
13735
13858
  const dismissedReason = notification?.getDismissedReason?.();
13736
13859
  const skippedReason = notification?.getSkippedReason?.();
13737
13860
  log.log('Google OneTap prompt notification:', { momentType, dismissedReason, skippedReason });
13738
13861
  if (momentType === 'skipped' || momentType === 'dismissed') {
13739
- // User dismissed the prompt, or browser blocked it (FedCM disabled)
13740
- // Offer popup flow as alternative
13741
13862
  log.log('Google OneTap was dismissed/skipped, offering popup fallback');
13742
13863
  setGoogleFallbackToPopup(true);
13743
13864
  }
@@ -13764,6 +13885,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13764
13885
  else {
13765
13886
  // Verify code - Twilio identifies the verification by phone number
13766
13887
  const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
13888
+ log.log('Phone verification response:', { hasToken: !!response?.token, hasUser: !!response?.user, isNewUser: response?.isNewUser });
13767
13889
  // Defensive validation: API may return undefined or error object on failure
13768
13890
  if (!response) {
13769
13891
  throw new Error('Verification failed - please check your code and try again');
@@ -13798,6 +13920,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13798
13920
  const handlePasswordReset = async (emailOrPassword, confirmPassword) => {
13799
13921
  setLoading(true);
13800
13922
  setError(undefined);
13923
+ setAuthSuccess(false);
13924
+ setResetSuccess(false);
13925
+ setSuccessMessage(undefined);
13801
13926
  const effectiveRedirectUrl = getRedirectUrl();
13802
13927
  try {
13803
13928
  if (resetToken && confirmPassword) {
@@ -13806,6 +13931,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13806
13931
  // Auto-login with the new password if we have the email
13807
13932
  if (resetEmail) {
13808
13933
  try {
13934
+ log.log('Auto-login after password reset for:', resetEmail);
13809
13935
  const loginResponse = await api.login(resetEmail, emailOrPassword);
13810
13936
  if (loginResponse.token) {
13811
13937
  await auth.login(loginResponse.token, loginResponse.user, loginResponse.accountData, false);
@@ -13818,9 +13944,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13818
13944
  }
13819
13945
  }
13820
13946
  catch (loginErr) {
13821
- // Auto-login failed, fall back to showing success message
13947
+ log.warn('Auto-login after password reset failed, requiring manual login:', loginErr);
13822
13948
  }
13823
13949
  }
13950
+ else {
13951
+ log.warn('No resetEmail available for auto-login after password reset');
13952
+ }
13824
13953
  // Fallback: show success but require manual login
13825
13954
  setResetSuccess(true);
13826
13955
  setSuccessMessage('Password reset successful! Please sign in with your new password.');
@@ -13830,14 +13959,35 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13830
13959
  else {
13831
13960
  // Request password reset email
13832
13961
  const result = await api.requestPasswordReset(emailOrPassword, effectiveRedirectUrl);
13962
+ const resultError = getActionResultErrorMessage(result);
13963
+ if (resultError) {
13964
+ setAuthSuccess(false);
13965
+ setResetSuccess(false);
13966
+ setSuccessMessage(undefined);
13967
+ setError(resultError);
13968
+ return;
13969
+ }
13970
+ const resultAny = result;
13971
+ const isConfirmedSuccess = result && (resultAny.success === true ||
13972
+ resultAny.ok === true ||
13973
+ (resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
13974
+ if (!isConfirmedSuccess) {
13975
+ setAuthSuccess(false);
13976
+ setResetSuccess(false);
13977
+ setSuccessMessage(undefined);
13978
+ setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
13979
+ return;
13980
+ }
13833
13981
  setResetSuccess(true);
13834
- // Use backend message if available
13835
13982
  if (result?.message) {
13836
13983
  setSuccessMessage(result.message);
13837
13984
  }
13838
13985
  }
13839
13986
  }
13840
13987
  catch (err) {
13988
+ setAuthSuccess(false);
13989
+ setResetSuccess(false);
13990
+ setSuccessMessage(undefined);
13841
13991
  setError(getFriendlyErrorMessage(err));
13842
13992
  onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13843
13993
  }
@@ -13901,7 +14051,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13901
14051
  setResetToken(undefined); // Clear token when going back
13902
14052
  setResetEmail(undefined); // Clear email when going back
13903
14053
  setSuccessMessage(undefined); // Clear success message
13904
- }, loading: loading, error: error, success: resetSuccess, successMessage: successMessage, token: resetToken })) : (mode === 'login' || mode === 'register') ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: showResendVerification ? (jsxRuntime.jsxs("div", { style: {
14054
+ }, loading: loading, error: error, success: resetSuccess, successMessage: successMessage, token: resetToken, resetEmail: resetEmail })) : (mode === 'login' || mode === 'register') ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: showResendVerification ? (jsxRuntime.jsxs("div", { style: {
13905
14055
  marginTop: '1rem',
13906
14056
  padding: '1.5rem',
13907
14057
  backgroundColor: config?.branding?.inheritHostStyles
@@ -13999,7 +14149,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13999
14149
  color: config?.branding?.inheritHostStyles
14000
14150
  ? 'hsl(var(--foreground, 215 25% 15%))'
14001
14151
  : (resolvedTheme === 'dark' ? '#f1f5f9' : '#374151')
14002
- }, children: "Password Reset Link Expired" }), jsxRuntime.jsx("p", { style: {
14152
+ }, children: "Password Reset Link Expired" }), error && (jsxRuntime.jsx("div", { style: {
14153
+ marginBottom: '1rem',
14154
+ padding: '0.75rem',
14155
+ backgroundColor: resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.15)' : 'rgba(239, 68, 68, 0.1)',
14156
+ border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.3)' : 'rgba(239, 68, 68, 0.2)'}`,
14157
+ borderRadius: '0.375rem',
14158
+ color: resolvedTheme === 'dark' ? '#fca5a5' : '#dc2626',
14159
+ fontSize: '0.875rem',
14160
+ lineHeight: '1.5',
14161
+ }, children: error })), jsxRuntime.jsx("p", { style: {
14003
14162
  marginBottom: '1rem',
14004
14163
  fontSize: '0.875rem',
14005
14164
  color: config?.branding?.inheritHostStyles
@@ -14098,10 +14257,48 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14098
14257
  backgroundColor: 'transparent',
14099
14258
  border: 'none',
14100
14259
  cursor: 'pointer',
14101
- }, children: "Dismiss" })] })), (() => {
14260
+ }, children: "Dismiss" })] })), googleNativeTimedOut && (jsxRuntime.jsxs("div", { style: {
14261
+ marginBottom: '1rem',
14262
+ padding: '0.75rem 1rem',
14263
+ borderRadius: '0.5rem',
14264
+ backgroundColor: resolvedTheme === 'dark' ? 'rgba(245, 158, 11, 0.1)' : 'rgba(245, 158, 11, 0.05)',
14265
+ border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(245, 158, 11, 0.3)' : 'rgba(245, 158, 11, 0.2)'}`,
14266
+ }, children: [jsxRuntime.jsx("p", { style: {
14267
+ fontSize: '0.8125rem',
14268
+ color: resolvedTheme === 'dark' ? '#fbbf24' : '#92400e',
14269
+ marginBottom: '0.5rem',
14270
+ lineHeight: 1.4,
14271
+ }, children: 'Google sign-in didn\'t respond. You can try again or use another sign-in method.' }), jsxRuntime.jsx("button", { onClick: () => {
14272
+ setGoogleNativeTimedOut(false);
14273
+ handleGoogleLogin();
14274
+ }, style: {
14275
+ width: '100%',
14276
+ padding: '0.5rem 1rem',
14277
+ fontSize: '0.875rem',
14278
+ fontWeight: 500,
14279
+ color: '#fff',
14280
+ backgroundColor: '#4285F4',
14281
+ border: 'none',
14282
+ borderRadius: '0.375rem',
14283
+ cursor: 'pointer',
14284
+ }, children: 'Retry Google Sign-In' }), jsxRuntime.jsx("button", { onClick: () => setGoogleNativeTimedOut(false), style: {
14285
+ marginTop: '0.375rem',
14286
+ width: '100%',
14287
+ padding: '0.25rem',
14288
+ fontSize: '0.75rem',
14289
+ color: resolvedTheme === 'dark' ? '#64748b' : '#9ca3af',
14290
+ backgroundColor: 'transparent',
14291
+ border: 'none',
14292
+ cursor: 'pointer',
14293
+ }, children: 'Dismiss' })] })), (() => {
14102
14294
  const emailDisplayMode = config?.emailDisplayMode || 'form';
14103
14295
  const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
14104
14296
  const actualProviders = config?.enabledProviders || enabledProviders;
14297
+ const hasEmailProvider = actualProviders.includes('email');
14298
+ // If email provider is not enabled, only show provider buttons (no email/password form)
14299
+ if (!hasEmailProvider) {
14300
+ return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
14301
+ }
14105
14302
  // Button mode: show provider selection first, then email form if email is selected
14106
14303
  if (emailDisplayMode === 'button' && !showEmailForm) {
14107
14304
  return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
@@ -14123,7 +14320,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14123
14320
  setShowResendVerification(false);
14124
14321
  setShowRequestNewReset(false);
14125
14322
  setError(undefined);
14126
- }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
14323
+ }, 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 && (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
14127
14324
  })()] })) })) : null }));
14128
14325
  };
14129
14326