@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/api.d.ts.map +1 -1
- package/dist/components/EmailAuthForm.d.ts +1 -0
- package/dist/components/EmailAuthForm.d.ts.map +1 -1
- package/dist/components/PasswordResetForm.d.ts +1 -0
- package/dist/components/PasswordResetForm.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/index.esm.js +167 -30
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +167 -30
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/errorHandling.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
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
|
-
|
|
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 = '
|
|
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 = '
|
|
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
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
},
|
|
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
|
-
|
|
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("
|
|
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
|
|