@proveanything/smartlinks-auth-ui 0.4.6 → 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 = '45';
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)
@@ -12740,7 +12797,7 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
12740
12797
  });
12741
12798
  };
12742
12799
  // getFriendlyErrorMessage is now imported from ../utils/errorHandling
12743
- 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, }) => {
12744
12801
  // Resolve signup prominence from props, customization, config, or default
12745
12802
  const resolvedSignupProminence = signupProminence || customization?.signupProminence || 'minimal';
12746
12803
  // Determine initial mode based on signupProminence setting
@@ -13099,10 +13156,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13099
13156
  log.log('Verifying reset token:', token);
13100
13157
  // Verify token is valid, then show password reset form
13101
13158
  const verifyResult = await api.verifyResetToken(token);
13102
- log.log('Reset token verification result:', { valid: verifyResult.valid, hasEmail: !!verifyResult.email, message: verifyResult.message });
13103
- // Check if token is valid before proceeding
13104
- if (!verifyResult.valid) {
13105
- 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');
13106
13162
  }
13107
13163
  setUrlAuthProcessing(false); // Clear processing state - showing reset form
13108
13164
  setResetToken(token); // Store token for use in password reset
@@ -13380,17 +13436,46 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13380
13436
  return;
13381
13437
  setLoading(true);
13382
13438
  setError(undefined);
13439
+ setAuthSuccess(false);
13440
+ setResetSuccess(false);
13441
+ setSuccessMessage(undefined);
13442
+ setShowResendVerification(false);
13383
13443
  try {
13384
13444
  const result = await api.requestPasswordReset(resetRequestEmail, getRedirectUrl());
13385
- // 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');
13386
13468
  setResetSuccess(true);
13387
- // Use backend message if available, otherwise default
13388
13469
  setSuccessMessage(result?.message || 'Password reset email sent! Please check your inbox.');
13389
13470
  setShowRequestNewReset(false);
13390
13471
  setResetRequestEmail('');
13391
13472
  }
13392
13473
  catch (err) {
13474
+ setAuthSuccess(false);
13475
+ setResetSuccess(false);
13476
+ setSuccessMessage(undefined);
13393
13477
  setError(getFriendlyErrorMessage(err));
13478
+ setShowRequestNewReset(true);
13394
13479
  onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13395
13480
  }
13396
13481
  finally {
@@ -13717,9 +13802,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13717
13802
  client_id: googleClientId,
13718
13803
  origin: window.location.origin,
13719
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;
13720
13808
  google.accounts.id.initialize({
13721
13809
  client_id: googleClientId,
13722
13810
  callback: async (response) => {
13811
+ credentialCallbackFired = true;
13723
13812
  try {
13724
13813
  const idToken = response.credential;
13725
13814
  const authResponse = await api.loginWithGoogle(idToken);
@@ -13747,23 +13836,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13747
13836
  cancel_on_tap_outside: true,
13748
13837
  use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
13749
13838
  });
13750
- // 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)
13751
13841
  const promptTimeout = setTimeout(() => {
13842
+ if (credentialCallbackFired)
13843
+ return; // Auth is already in progress, don't show fallback
13752
13844
  log.log('Google OneTap prompt timed out — FedCM may be blocked or unavailable');
13753
13845
  setGoogleFallbackToPopup(true);
13754
13846
  setLoading(false);
13755
- }, 5000);
13847
+ }, 15000);
13756
13848
  google.accounts.id.prompt((notification) => {
13757
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
+ }
13758
13856
  // Check for FedCM/OneTap dismissal or blocking
13759
- // notification may have getMomentType(), getDismissedReason(), getSkippedReason()
13760
13857
  const momentType = notification?.getMomentType?.();
13761
13858
  const dismissedReason = notification?.getDismissedReason?.();
13762
13859
  const skippedReason = notification?.getSkippedReason?.();
13763
13860
  log.log('Google OneTap prompt notification:', { momentType, dismissedReason, skippedReason });
13764
13861
  if (momentType === 'skipped' || momentType === 'dismissed') {
13765
- // User dismissed the prompt, or browser blocked it (FedCM disabled)
13766
- // Offer popup flow as alternative
13767
13862
  log.log('Google OneTap was dismissed/skipped, offering popup fallback');
13768
13863
  setGoogleFallbackToPopup(true);
13769
13864
  }
@@ -13825,6 +13920,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13825
13920
  const handlePasswordReset = async (emailOrPassword, confirmPassword) => {
13826
13921
  setLoading(true);
13827
13922
  setError(undefined);
13923
+ setAuthSuccess(false);
13924
+ setResetSuccess(false);
13925
+ setSuccessMessage(undefined);
13828
13926
  const effectiveRedirectUrl = getRedirectUrl();
13829
13927
  try {
13830
13928
  if (resetToken && confirmPassword) {
@@ -13833,6 +13931,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13833
13931
  // Auto-login with the new password if we have the email
13834
13932
  if (resetEmail) {
13835
13933
  try {
13934
+ log.log('Auto-login after password reset for:', resetEmail);
13836
13935
  const loginResponse = await api.login(resetEmail, emailOrPassword);
13837
13936
  if (loginResponse.token) {
13838
13937
  await auth.login(loginResponse.token, loginResponse.user, loginResponse.accountData, false);
@@ -13845,9 +13944,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13845
13944
  }
13846
13945
  }
13847
13946
  catch (loginErr) {
13848
- // Auto-login failed, fall back to showing success message
13947
+ log.warn('Auto-login after password reset failed, requiring manual login:', loginErr);
13849
13948
  }
13850
13949
  }
13950
+ else {
13951
+ log.warn('No resetEmail available for auto-login after password reset');
13952
+ }
13851
13953
  // Fallback: show success but require manual login
13852
13954
  setResetSuccess(true);
13853
13955
  setSuccessMessage('Password reset successful! Please sign in with your new password.');
@@ -13857,14 +13959,35 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13857
13959
  else {
13858
13960
  // Request password reset email
13859
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
+ }
13860
13981
  setResetSuccess(true);
13861
- // Use backend message if available
13862
13982
  if (result?.message) {
13863
13983
  setSuccessMessage(result.message);
13864
13984
  }
13865
13985
  }
13866
13986
  }
13867
13987
  catch (err) {
13988
+ setAuthSuccess(false);
13989
+ setResetSuccess(false);
13990
+ setSuccessMessage(undefined);
13868
13991
  setError(getFriendlyErrorMessage(err));
13869
13992
  onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13870
13993
  }
@@ -13928,7 +14051,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13928
14051
  setResetToken(undefined); // Clear token when going back
13929
14052
  setResetEmail(undefined); // Clear email when going back
13930
14053
  setSuccessMessage(undefined); // Clear success message
13931
- }, 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: {
13932
14055
  marginTop: '1rem',
13933
14056
  padding: '1.5rem',
13934
14057
  backgroundColor: config?.branding?.inheritHostStyles
@@ -14026,7 +14149,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14026
14149
  color: config?.branding?.inheritHostStyles
14027
14150
  ? 'hsl(var(--foreground, 215 25% 15%))'
14028
14151
  : (resolvedTheme === 'dark' ? '#f1f5f9' : '#374151')
14029
- }, 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: {
14030
14162
  marginBottom: '1rem',
14031
14163
  fontSize: '0.875rem',
14032
14164
  color: config?.branding?.inheritHostStyles
@@ -14162,6 +14294,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14162
14294
  const emailDisplayMode = config?.emailDisplayMode || 'form';
14163
14295
  const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
14164
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
+ }
14165
14302
  // Button mode: show provider selection first, then email form if email is selected
14166
14303
  if (emailDisplayMode === 'button' && !showEmailForm) {
14167
14304
  return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
@@ -14183,7 +14320,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14183
14320
  setShowResendVerification(false);
14184
14321
  setShowRequestNewReset(false);
14185
14322
  setError(undefined);
14186
- }, 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 }))] }));
14187
14324
  })()] })) })) : null }));
14188
14325
  };
14189
14326