@proveanything/smartlinks-auth-ui 0.4.6 → 0.4.8
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 +10 -0
- 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/MagicLinkForm.d.ts +2 -1
- package/dist/components/MagicLinkForm.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/PhoneAuthForm.d.ts +2 -1
- package/dist/components/PhoneAuthForm.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/index.esm.js +561 -392
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +560 -391
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +13 -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, }) => {
|
|
@@ -10779,7 +10779,8 @@ const PhoneInput = ({ value, onChange, disabled = false, }) => {
|
|
|
10779
10779
|
return (jsxRuntime.jsx(PhoneInputComponent, { international: true, defaultCountry: defaultCountry, value: value, onChange: (value) => onChange(value || ''), disabled: disabled, className: "phone-input-wrapper" }));
|
|
10780
10780
|
};
|
|
10781
10781
|
|
|
10782
|
-
const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
10782
|
+
const PhoneAuthForm = ({ onSubmit, onBack, loading, error, collectName = false, }) => {
|
|
10783
|
+
const [displayName, setDisplayName] = React.useState('');
|
|
10783
10784
|
const [phoneNumber, setPhoneNumber] = React.useState('');
|
|
10784
10785
|
const [verificationCode, setVerificationCode] = React.useState('');
|
|
10785
10786
|
const [codeSent, setCodeSent] = React.useState(false);
|
|
@@ -10796,7 +10797,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10796
10797
|
const handleSendCode = async (e) => {
|
|
10797
10798
|
e.preventDefault();
|
|
10798
10799
|
try {
|
|
10799
|
-
await onSubmit(phoneNumber);
|
|
10800
|
+
await onSubmit(phoneNumber, undefined, collectName ? displayName || undefined : undefined);
|
|
10800
10801
|
// Only transition to code entry UI AFTER successful send
|
|
10801
10802
|
setCodeSent(true);
|
|
10802
10803
|
setResendCooldown(60); // 60 second cooldown
|
|
@@ -10823,7 +10824,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10823
10824
|
};
|
|
10824
10825
|
return (jsxRuntime.jsxs("form", { className: "auth-form", onSubmit: codeSent ? handleVerifyCode : handleSendCode, children: [jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: "Phone Authentication" }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: codeSent
|
|
10825
10826
|
? 'Enter the verification code sent to your phone.'
|
|
10826
|
-
: 'Enter your phone number to receive a verification code.' })] }), 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] })), !codeSent ? (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneNumber", className: "auth-label", children: "Phone Number" }), jsxRuntime.jsx(PhoneInput, { value: phoneNumber, onChange: setPhoneNumber, disabled: loading })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "verificationCode", className: "auth-label", children: "Verification Code" }), jsxRuntime.jsx(OTPInput, { length: 6, value: verificationCode, onChange: setVerificationCode, disabled: loading })] }), jsxRuntime.jsx("div", { style: { marginTop: '0.5rem', textAlign: 'center' }, children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: handleResendCode, disabled: loading || resendCooldown > 0, style: {
|
|
10827
|
+
: 'Enter your phone number to receive a verification code.' })] }), 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] })), !codeSent ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [collectName && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneName", className: "auth-label", children: "Name" }), jsxRuntime.jsx("input", { id: "phoneName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), className: "auth-input", placeholder: "John Smith", disabled: loading, autoComplete: "name" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneNumber", className: "auth-label", children: "Phone Number" }), jsxRuntime.jsx(PhoneInput, { value: phoneNumber, onChange: setPhoneNumber, disabled: loading })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "verificationCode", className: "auth-label", children: "Verification Code" }), jsxRuntime.jsx(OTPInput, { length: 6, value: verificationCode, onChange: setVerificationCode, disabled: loading })] }), jsxRuntime.jsx("div", { style: { marginTop: '0.5rem', textAlign: 'center' }, children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: handleResendCode, disabled: loading || resendCooldown > 0, style: {
|
|
10827
10828
|
cursor: resendCooldown > 0 ? 'not-allowed' : 'pointer',
|
|
10828
10829
|
opacity: resendCooldown > 0 ? 0.5 : 1,
|
|
10829
10830
|
}, children: resendCooldown > 0
|
|
@@ -10831,7 +10832,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10831
10832
|
: '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
10833
|
};
|
|
10833
10834
|
|
|
10834
|
-
const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, }) => {
|
|
10835
|
+
const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, resetEmail, }) => {
|
|
10835
10836
|
const [email, setEmail] = React.useState('');
|
|
10836
10837
|
const [password, setPassword] = React.useState('');
|
|
10837
10838
|
const [confirmPassword, setConfirmPassword] = React.useState('');
|
|
@@ -10856,23 +10857,24 @@ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successM
|
|
|
10856
10857
|
await onSubmit(email);
|
|
10857
10858
|
}
|
|
10858
10859
|
};
|
|
10859
|
-
if (success) {
|
|
10860
|
+
if (success && !error) {
|
|
10860
10861
|
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
10862
|
? (successMessage || 'Your password has been successfully reset. You can now sign in with your new password.')
|
|
10862
10863
|
: (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
10864
|
}
|
|
10864
10865
|
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
10866
|
? '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" }) })] }));
|
|
10867
|
+
: "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
10868
|
};
|
|
10868
10869
|
|
|
10869
|
-
const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, }) => {
|
|
10870
|
+
const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, collectName = false, }) => {
|
|
10870
10871
|
const [email, setEmail] = React.useState('');
|
|
10872
|
+
const [displayName, setDisplayName] = React.useState('');
|
|
10871
10873
|
const handleSubmit = async (e) => {
|
|
10872
10874
|
e.preventDefault();
|
|
10873
|
-
await onSubmit(email);
|
|
10875
|
+
await onSubmit(email, collectName ? displayName || undefined : undefined);
|
|
10874
10876
|
};
|
|
10875
|
-
return (jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "auth-form", children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "magic-link-email", className: "auth-label", children: "Email Address" }), jsxRuntime.jsx("input", { id: "magic-link-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), className: "auth-input", placeholder: "you@example.com", required: true, disabled: loading })] }), error && (jsxRuntime.jsx("div", { className: "auth-error-message", children: error })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading || !email, children: loading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "auth-spinner" }), "Sending..."] })) : ('Send Magic Link') }), jsxRuntime.jsx("button", { type: "button", onClick: onCancel, className: "auth-button auth-button-secondary", disabled: loading, children: "Cancel" })] }));
|
|
10877
|
+
return (jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "auth-form", children: [collectName && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "magic-link-name", className: "auth-label", children: "Name" }), jsxRuntime.jsx("input", { id: "magic-link-name", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), className: "auth-input", placeholder: "John Smith", disabled: loading, autoComplete: "name" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "magic-link-email", className: "auth-label", children: "Email Address" }), jsxRuntime.jsx("input", { id: "magic-link-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), className: "auth-input", placeholder: "you@example.com", required: true, disabled: loading })] }), error && (jsxRuntime.jsx("div", { className: "auth-error-message", children: error })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading || !email, children: loading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "auth-spinner" }), "Sending..."] })) : ('Send Magic Link') }), jsxRuntime.jsx("button", { type: "button", onClick: onCancel, className: "auth-button auth-button-secondary", disabled: loading, children: "Cancel" })] }));
|
|
10876
10878
|
};
|
|
10877
10879
|
|
|
10878
10880
|
/**
|
|
@@ -10909,6 +10911,254 @@ function createLoggerWrapper(logger) {
|
|
|
10909
10911
|
};
|
|
10910
10912
|
}
|
|
10911
10913
|
|
|
10914
|
+
/**
|
|
10915
|
+
* Friendly error messages for common HTTP status codes.
|
|
10916
|
+
*/
|
|
10917
|
+
const STATUS_MESSAGES = {
|
|
10918
|
+
400: 'Invalid request. Please check your input and try again.',
|
|
10919
|
+
401: 'Invalid credentials. Please check your email and password.',
|
|
10920
|
+
403: 'Access denied. You do not have permission to perform this action.',
|
|
10921
|
+
404: 'Account not found. Please check your email or create a new account.',
|
|
10922
|
+
409: 'This email is already registered.',
|
|
10923
|
+
429: 'Too many attempts. Please wait a moment and try again.',
|
|
10924
|
+
};
|
|
10925
|
+
/**
|
|
10926
|
+
* Context-specific error messages for different auth operations.
|
|
10927
|
+
*/
|
|
10928
|
+
const ERROR_CODE_MESSAGES = {
|
|
10929
|
+
// 400 - Validation errors
|
|
10930
|
+
'MISSING_FIELDS': 'Email and password are required.',
|
|
10931
|
+
'MISSING_EMAIL': 'Email is required.',
|
|
10932
|
+
'MISSING_PASSWORD': 'Password is required.',
|
|
10933
|
+
'MISSING_TOKEN': 'Token is required.',
|
|
10934
|
+
'MISSING_PHONE_NUMBER': 'Phone number is required.',
|
|
10935
|
+
'MISSING_VERIFICATION_CODE': 'Phone number and verification code are required.',
|
|
10936
|
+
'MISSING_REDIRECT_URL': 'Redirect URL is required.',
|
|
10937
|
+
'MISSING_GOOGLE_TOKEN': 'Google token is required.',
|
|
10938
|
+
'INVALID_CLIENT_ID': 'Invalid client configuration.',
|
|
10939
|
+
'INVALID_REDIRECT_URL': 'Invalid redirect URL.',
|
|
10940
|
+
'INVALID_PHONE_NUMBER': 'Invalid phone number. Please check the format and try again.',
|
|
10941
|
+
'INVALID_GOOGLE_TOKEN': 'Invalid Google sign-in token.',
|
|
10942
|
+
'PASSWORD_TOO_SHORT': 'Password must be at least 8 characters long.',
|
|
10943
|
+
'PASSWORD_REQUIREMENTS_NOT_MET': 'New password must be at least 6 characters.',
|
|
10944
|
+
'EMAIL_ALREADY_VERIFIED': 'Your email is already verified.',
|
|
10945
|
+
'INVALID_CONFIRMATION': 'Please type DELETE to confirm account deletion.',
|
|
10946
|
+
// 401 - Authentication errors
|
|
10947
|
+
'INVALID_CREDENTIALS': 'Invalid email or password.',
|
|
10948
|
+
'INCORRECT_PASSWORD': 'Current password is incorrect.',
|
|
10949
|
+
'INVALID_VERIFICATION_CODE': 'Invalid or expired verification code. Please try again.',
|
|
10950
|
+
'INVALID_TOKEN': 'This link has expired or is invalid. Please request a new one.',
|
|
10951
|
+
'TOKEN_EXPIRED': 'This link has expired. Please request a new one.',
|
|
10952
|
+
'TOKEN_ALREADY_USED': 'This link has already been used. Please request a new one.',
|
|
10953
|
+
'UNAUTHORIZED': 'You must be logged in to perform this action.',
|
|
10954
|
+
'GOOGLE_TOKEN_AUDIENCE_MISMATCH': 'Google sign-in failed. Token was not issued for this application.',
|
|
10955
|
+
// 403 - Forbidden / verification required
|
|
10956
|
+
'EMAIL_NOT_VERIFIED': 'Please verify your email before signing in.',
|
|
10957
|
+
'ACCOUNT_LOCKED': 'Your account has been locked. Please verify your email to unlock.',
|
|
10958
|
+
'EMAIL_VERIFICATION_EXPIRED': 'Your verification deadline has passed and your account is locked. Please contact support.',
|
|
10959
|
+
// 404
|
|
10960
|
+
'USER_NOT_FOUND': 'Account not found. Please check your email or create a new account.',
|
|
10961
|
+
// 409 - Conflicts
|
|
10962
|
+
'EMAIL_ALREADY_EXISTS': 'This email is already registered.',
|
|
10963
|
+
'EMAIL_IN_USE': 'This email is already in use.',
|
|
10964
|
+
// 429 - Rate limiting
|
|
10965
|
+
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
|
|
10966
|
+
'TOO_MANY_MAGIC_LINKS': 'Too many magic link requests. Please try again later.',
|
|
10967
|
+
'TOO_MANY_VERIFICATION_ATTEMPTS': 'Too many verification attempts. Please wait and try again.',
|
|
10968
|
+
'MAX_VERIFICATION_ATTEMPTS': 'Maximum verification attempts reached. Please try again later.',
|
|
10969
|
+
// 500 - Server errors
|
|
10970
|
+
'LOGIN_FAILED': 'Login failed. Please try again later.',
|
|
10971
|
+
'REGISTRATION_FAILED': 'Registration failed. Please try again later.',
|
|
10972
|
+
'GOOGLE_AUTH_NOT_CONFIGURED': 'Google sign-in is not available for this application.',
|
|
10973
|
+
'GOOGLE_AUTH_FAILED': 'Google sign-in failed. Please try again.',
|
|
10974
|
+
'GOOGLE_USERINFO_FAILED': 'Failed to retrieve your Google account information. Please try again.',
|
|
10975
|
+
'PHONE_VERIFICATION_FAILED': 'Phone verification failed. Please try again.',
|
|
10976
|
+
'SEND_VERIFICATION_CODE_FAILED': 'Failed to send verification code. Please try again.',
|
|
10977
|
+
'MAGIC_LINK_SEND_FAILED': 'Failed to send magic link. Please try again.',
|
|
10978
|
+
'MAGIC_LINK_VERIFICATION_FAILED': 'Magic link verification failed. Please try again.',
|
|
10979
|
+
'PASSWORD_RESET_FAILED': 'Failed to process password reset. Please try again.',
|
|
10980
|
+
'PASSWORD_RESET_COMPLETE_FAILED': 'Failed to reset password. Please try again.',
|
|
10981
|
+
'EMAIL_VERIFICATION_SEND_FAILED': 'Failed to send verification email. Please try again.',
|
|
10982
|
+
'EMAIL_VERIFICATION_FAILED': 'Email verification failed. Please try again.',
|
|
10983
|
+
// Account management 500s
|
|
10984
|
+
'UPDATE_PROFILE_FAILED': 'Failed to update profile. Please try again.',
|
|
10985
|
+
'CHANGE_PASSWORD_FAILED': 'Failed to change password. Please try again.',
|
|
10986
|
+
'CHANGE_EMAIL_FAILED': 'Failed to change email. Please try again.',
|
|
10987
|
+
'UPDATE_PHONE_FAILED': 'Failed to update phone number. Please try again.',
|
|
10988
|
+
'DELETE_ACCOUNT_FAILED': 'Failed to delete account. Please try again.',
|
|
10989
|
+
'CONFIG_FETCH_FAILED': 'Failed to load configuration. Please try again.',
|
|
10990
|
+
'INTERNAL_ERROR': 'An unexpected error occurred. Please try again.',
|
|
10991
|
+
// Legacy aliases (kept for backward compatibility)
|
|
10992
|
+
'INVALID_CODE': 'Invalid verification code. Please check and try again.',
|
|
10993
|
+
'CODE_EXPIRED': 'This code has expired. Please request a new one.',
|
|
10994
|
+
'PHONE_NOT_SUPPORTED': 'This phone number is not supported. Please try a different number.',
|
|
10995
|
+
'INVALID_PHONE': 'Invalid phone number. Please check the format and try again.',
|
|
10996
|
+
'PASSWORD_TOO_WEAK': 'Password is too weak. Please use at least 8 characters with a mix of letters and numbers.',
|
|
10997
|
+
'RATE_LIMITED': 'Too many attempts. Please wait a moment and try again.',
|
|
10998
|
+
};
|
|
10999
|
+
function isApiErrorLike(error) {
|
|
11000
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
11001
|
+
return true;
|
|
11002
|
+
if (error && typeof error === 'object' && 'statusCode' in error && 'message' in error) {
|
|
11003
|
+
const e = error;
|
|
11004
|
+
return typeof e.statusCode === 'number' && typeof e.message === 'string';
|
|
11005
|
+
}
|
|
11006
|
+
return false;
|
|
11007
|
+
}
|
|
11008
|
+
/**
|
|
11009
|
+
* Extracts a user-friendly error message from an error.
|
|
11010
|
+
*
|
|
11011
|
+
* Handles:
|
|
11012
|
+
* - SmartlinksApiError (and duck-typed equivalents from proxy mode)
|
|
11013
|
+
* - Standard Error: Uses message property
|
|
11014
|
+
* - String: Passes through directly (for native bridge errors)
|
|
11015
|
+
* - Unknown: Returns generic message
|
|
11016
|
+
*/
|
|
11017
|
+
function getFriendlyErrorMessage(error) {
|
|
11018
|
+
// Handle SmartlinksApiError or duck-typed API errors (proxy mode)
|
|
11019
|
+
if (isApiErrorLike(error)) {
|
|
11020
|
+
// First, check for specific error code (most precise)
|
|
11021
|
+
const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
|
|
11022
|
+
// For rate-limit errors, prefer the backend's message as it includes specific wait times
|
|
11023
|
+
if (errorCode === 'RATE_LIMIT_EXCEEDED' && error.message && error.message !== '[object Object]') {
|
|
11024
|
+
return error.message;
|
|
11025
|
+
}
|
|
11026
|
+
if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
|
|
11027
|
+
return ERROR_CODE_MESSAGES[errorCode];
|
|
11028
|
+
}
|
|
11029
|
+
// Then, check status code for general category messages
|
|
11030
|
+
if (error.statusCode >= 500) {
|
|
11031
|
+
return 'Server error. Please try again later.';
|
|
11032
|
+
}
|
|
11033
|
+
// For 429 status, prefer backend message (may include wait time)
|
|
11034
|
+
if (error.statusCode === 429 && error.message && error.message !== '[object Object]') {
|
|
11035
|
+
return error.message;
|
|
11036
|
+
}
|
|
11037
|
+
if (STATUS_MESSAGES[error.statusCode]) {
|
|
11038
|
+
return STATUS_MESSAGES[error.statusCode];
|
|
11039
|
+
}
|
|
11040
|
+
// Fall back to the server's message (already human-readable from backend)
|
|
11041
|
+
return error.message;
|
|
11042
|
+
}
|
|
11043
|
+
// Handle standard Error objects
|
|
11044
|
+
if (error instanceof Error) {
|
|
11045
|
+
// SDK bug workaround: SDK may do `throw new Error(responseBodyObject)` which produces
|
|
11046
|
+
// message "[object Object]". Check for API error properties attached to the Error instance.
|
|
11047
|
+
const errAny = error;
|
|
11048
|
+
// Check if the Error has API error properties directly attached (e.g., error.statusCode, error.errorCode)
|
|
11049
|
+
if (typeof errAny.statusCode === 'number' || errAny.errorCode || errAny.response) {
|
|
11050
|
+
// Try to extract from attached properties
|
|
11051
|
+
const apiLike = errAny.response || errAny;
|
|
11052
|
+
if (isApiErrorLike(apiLike)) {
|
|
11053
|
+
return getFriendlyErrorMessage(apiLike);
|
|
11054
|
+
}
|
|
11055
|
+
}
|
|
11056
|
+
// Check if the Error has a `cause` with API error details (modern Error cause pattern)
|
|
11057
|
+
if (errAny.cause && typeof errAny.cause === 'object') {
|
|
11058
|
+
if (isApiErrorLike(errAny.cause)) {
|
|
11059
|
+
return getFriendlyErrorMessage(errAny.cause);
|
|
11060
|
+
}
|
|
11061
|
+
}
|
|
11062
|
+
// If the message is "[object Object]", the error was constructed from a plain object
|
|
11063
|
+
// This is useless - return a generic message instead
|
|
11064
|
+
if (error.message === '[object Object]') {
|
|
11065
|
+
// Log the actual error for debugging
|
|
11066
|
+
console.warn('[AuthKit] Error with [object Object] message. Raw error:', JSON.stringify(errAny, Object.getOwnPropertyNames(errAny)));
|
|
11067
|
+
return 'An unexpected error occurred. Please try again.';
|
|
11068
|
+
}
|
|
11069
|
+
// Check if the message itself contains a known API error pattern
|
|
11070
|
+
if (/already (registered|exists)/i.test(error.message)) {
|
|
11071
|
+
return 'This email is already registered.';
|
|
11072
|
+
}
|
|
11073
|
+
return error.message;
|
|
11074
|
+
}
|
|
11075
|
+
// Handle plain strings (e.g., from native bridge callbacks)
|
|
11076
|
+
if (typeof error === 'string') {
|
|
11077
|
+
return error;
|
|
11078
|
+
}
|
|
11079
|
+
// Unknown error type
|
|
11080
|
+
return 'An unexpected error occurred. Please try again.';
|
|
11081
|
+
}
|
|
11082
|
+
/**
|
|
11083
|
+
* Checks if an error represents a conflict (409) - typically duplicate registration.
|
|
11084
|
+
*/
|
|
11085
|
+
function isConflictError(error) {
|
|
11086
|
+
if (isApiErrorLike(error))
|
|
11087
|
+
return error.statusCode === 409;
|
|
11088
|
+
// Also check error message for keyword match (resilient fallback)
|
|
11089
|
+
if (error instanceof Error && /already (registered|exists)/i.test(error.message))
|
|
11090
|
+
return true;
|
|
11091
|
+
return false;
|
|
11092
|
+
}
|
|
11093
|
+
/**
|
|
11094
|
+
* Checks if an error represents invalid credentials (401).
|
|
11095
|
+
*/
|
|
11096
|
+
function isAuthError(error) {
|
|
11097
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
11098
|
+
return error.isAuthError();
|
|
11099
|
+
if (isApiErrorLike(error))
|
|
11100
|
+
return error.statusCode === 401 || error.statusCode === 403;
|
|
11101
|
+
return false;
|
|
11102
|
+
}
|
|
11103
|
+
/**
|
|
11104
|
+
* Checks if an error represents rate limiting (429).
|
|
11105
|
+
*/
|
|
11106
|
+
function isRateLimitError(error) {
|
|
11107
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
11108
|
+
return error.isRateLimited();
|
|
11109
|
+
if (isApiErrorLike(error))
|
|
11110
|
+
return error.statusCode === 429;
|
|
11111
|
+
return false;
|
|
11112
|
+
}
|
|
11113
|
+
/**
|
|
11114
|
+
* Checks if an error represents a server error (5xx).
|
|
11115
|
+
*/
|
|
11116
|
+
function isServerError(error) {
|
|
11117
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
11118
|
+
return error.isServerError();
|
|
11119
|
+
if (isApiErrorLike(error))
|
|
11120
|
+
return error.statusCode >= 500;
|
|
11121
|
+
return false;
|
|
11122
|
+
}
|
|
11123
|
+
/**
|
|
11124
|
+
* Gets the HTTP status code from an error, if available.
|
|
11125
|
+
*/
|
|
11126
|
+
function getErrorStatusCode(error) {
|
|
11127
|
+
if (isApiErrorLike(error))
|
|
11128
|
+
return error.statusCode;
|
|
11129
|
+
return undefined;
|
|
11130
|
+
}
|
|
11131
|
+
/**
|
|
11132
|
+
* Gets the server-specific error code from an error, if available.
|
|
11133
|
+
*/
|
|
11134
|
+
function getErrorCode(error) {
|
|
11135
|
+
if (isApiErrorLike(error)) {
|
|
11136
|
+
return error.errorCode || error.details?.errorCode || error.details?.error;
|
|
11137
|
+
}
|
|
11138
|
+
return undefined;
|
|
11139
|
+
}
|
|
11140
|
+
/**
|
|
11141
|
+
* Error codes that indicate the user needs to verify their email.
|
|
11142
|
+
*/
|
|
11143
|
+
const EMAIL_VERIFICATION_ERROR_CODES = new Set([
|
|
11144
|
+
'EMAIL_NOT_VERIFIED',
|
|
11145
|
+
'ACCOUNT_LOCKED',
|
|
11146
|
+
'EMAIL_VERIFICATION_EXPIRED',
|
|
11147
|
+
]);
|
|
11148
|
+
/**
|
|
11149
|
+
* Checks if an error requires email verification action from the user.
|
|
11150
|
+
*/
|
|
11151
|
+
function requiresEmailVerification(error) {
|
|
11152
|
+
const code = getErrorCode(error);
|
|
11153
|
+
if (code && EMAIL_VERIFICATION_ERROR_CODES.has(code))
|
|
11154
|
+
return true;
|
|
11155
|
+
// Also check the flag from the response body
|
|
11156
|
+
if (error && typeof error === 'object' && 'requiresEmailVerification' in error) {
|
|
11157
|
+
return error.requiresEmailVerification === true;
|
|
11158
|
+
}
|
|
11159
|
+
return false;
|
|
11160
|
+
}
|
|
11161
|
+
|
|
10912
11162
|
/**
|
|
10913
11163
|
* AuthAPI - Thin wrapper around Smartlinks SDK authKit namespace
|
|
10914
11164
|
* All authentication operations now use the global Smartlinks SDK
|
|
@@ -10969,14 +11219,46 @@ class AuthAPI {
|
|
|
10969
11219
|
return smartlinks__namespace.authKit.verifyPhoneCode(this.clientId, phoneNumber, code);
|
|
10970
11220
|
}
|
|
10971
11221
|
async requestPasswordReset(email, redirectUrl) {
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
11222
|
+
console.log('[AuthKit:API] requestPasswordReset called', { clientId: this.clientId, email, redirectUrl });
|
|
11223
|
+
try {
|
|
11224
|
+
const result = await smartlinks__namespace.authKit.requestPasswordReset(this.clientId, {
|
|
11225
|
+
email,
|
|
11226
|
+
redirectUrl,
|
|
11227
|
+
clientName: this.clientName
|
|
11228
|
+
});
|
|
11229
|
+
console.log('[AuthKit:API] requestPasswordReset resolved:', JSON.stringify(result));
|
|
11230
|
+
return result;
|
|
11231
|
+
}
|
|
11232
|
+
catch (error) {
|
|
11233
|
+
const err = error;
|
|
11234
|
+
console.error('[AuthKit:API] requestPasswordReset threw:', {
|
|
11235
|
+
type: typeof error,
|
|
11236
|
+
message: err?.message,
|
|
11237
|
+
statusCode: err?.statusCode ?? err?.response?.status,
|
|
11238
|
+
errorCode: err?.errorCode ?? err?.details?.errorCode,
|
|
11239
|
+
details: err?.details ?? err?.response?.data,
|
|
11240
|
+
keys: err ? Object.keys(err) : [],
|
|
11241
|
+
});
|
|
11242
|
+
throw error;
|
|
11243
|
+
}
|
|
10977
11244
|
}
|
|
10978
11245
|
async verifyResetToken(token) {
|
|
10979
|
-
|
|
11246
|
+
console.log('[AuthKit:API] verifyResetToken called');
|
|
11247
|
+
try {
|
|
11248
|
+
const result = await smartlinks__namespace.authKit.verifyResetToken(this.clientId, token);
|
|
11249
|
+
console.log('[AuthKit:API] verifyResetToken resolved:', JSON.stringify(result));
|
|
11250
|
+
return result;
|
|
11251
|
+
}
|
|
11252
|
+
catch (error) {
|
|
11253
|
+
const err = error;
|
|
11254
|
+
console.error('[AuthKit:API] verifyResetToken threw:', {
|
|
11255
|
+
type: typeof error,
|
|
11256
|
+
message: err?.message,
|
|
11257
|
+
statusCode: err?.statusCode ?? err?.response?.status,
|
|
11258
|
+
errorCode: err?.errorCode ?? err?.details?.errorCode,
|
|
11259
|
+
});
|
|
11260
|
+
throw error;
|
|
11261
|
+
}
|
|
10980
11262
|
}
|
|
10981
11263
|
async completePasswordReset(token, newPassword) {
|
|
10982
11264
|
return smartlinks__namespace.authKit.completePasswordReset(this.clientId, token, newPassword);
|
|
@@ -11012,7 +11294,6 @@ class AuthAPI {
|
|
|
11012
11294
|
this.log.warn('Failed to fetch UI config, using defaults:', error);
|
|
11013
11295
|
return {
|
|
11014
11296
|
branding: {
|
|
11015
|
-
title: 'Smartlinks Auth',
|
|
11016
11297
|
subtitle: 'Sign in to your account',
|
|
11017
11298
|
primaryColor: '#3B82F6',
|
|
11018
11299
|
secondaryColor: '#1D4ED8',
|
|
@@ -11023,6 +11304,32 @@ class AuthAPI {
|
|
|
11023
11304
|
};
|
|
11024
11305
|
}
|
|
11025
11306
|
}
|
|
11307
|
+
/**
|
|
11308
|
+
* Ensure an account exists for the given email (or phone).
|
|
11309
|
+
* Calls register and silently handles 409 (account already exists).
|
|
11310
|
+
* This enables passwordless flows (magic link, phone) to work for new users.
|
|
11311
|
+
*/
|
|
11312
|
+
async ensureAccount(data) {
|
|
11313
|
+
try {
|
|
11314
|
+
// Generate a random password since passwordless users won't use it
|
|
11315
|
+
const randomPassword = crypto.randomUUID?.() || Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
11316
|
+
await this.register({
|
|
11317
|
+
email: data.email,
|
|
11318
|
+
password: randomPassword,
|
|
11319
|
+
displayName: data.displayName,
|
|
11320
|
+
});
|
|
11321
|
+
this.log.log('ensureAccount: new account created for', data.email || data.phoneNumber);
|
|
11322
|
+
}
|
|
11323
|
+
catch (err) {
|
|
11324
|
+
// 409 = account already exists, which is fine
|
|
11325
|
+
if (isConflictError(err)) {
|
|
11326
|
+
this.log.log('ensureAccount: account already exists for', data.email || data.phoneNumber);
|
|
11327
|
+
return;
|
|
11328
|
+
}
|
|
11329
|
+
// Re-throw any other error
|
|
11330
|
+
throw err;
|
|
11331
|
+
}
|
|
11332
|
+
}
|
|
11026
11333
|
async sendMagicLink(email, redirectUrl) {
|
|
11027
11334
|
return smartlinks__namespace.authKit.sendMagicLink(this.clientId, {
|
|
11028
11335
|
email,
|
|
@@ -12180,368 +12487,128 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12180
12487
|
}, [getAccount]);
|
|
12181
12488
|
const clearAccountCache = React.useCallback(async () => {
|
|
12182
12489
|
if (!proxyMode) {
|
|
12183
|
-
await tokenStorage.clearAccountInfo();
|
|
12184
|
-
}
|
|
12185
|
-
setAccountInfo(null);
|
|
12186
|
-
}, [proxyMode]);
|
|
12187
|
-
const onAuthStateChange = React.useCallback((callback) => {
|
|
12188
|
-
callbacksRef.current.add(callback);
|
|
12189
|
-
return () => {
|
|
12190
|
-
callbacksRef.current.delete(callback);
|
|
12191
|
-
};
|
|
12192
|
-
}, []);
|
|
12193
|
-
const retryVerification = React.useCallback(async () => {
|
|
12194
|
-
if (!token || !user)
|
|
12195
|
-
return false;
|
|
12196
|
-
if (isVerified)
|
|
12197
|
-
return true;
|
|
12198
|
-
try {
|
|
12199
|
-
await smartlinks__namespace.auth.verifyToken(token);
|
|
12200
|
-
setIsVerified(true);
|
|
12201
|
-
pendingVerificationRef.current = false;
|
|
12202
|
-
notifyAuthStateChange('SESSION_VERIFIED', user, token, accountData, accountInfo, true, contact, contactId);
|
|
12203
|
-
return true;
|
|
12204
|
-
}
|
|
12205
|
-
catch (err) {
|
|
12206
|
-
if (isNetworkError(err)) {
|
|
12207
|
-
return false;
|
|
12208
|
-
}
|
|
12209
|
-
else {
|
|
12210
|
-
await logout();
|
|
12211
|
-
return false;
|
|
12212
|
-
}
|
|
12213
|
-
}
|
|
12214
|
-
}, [token, user, isVerified, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
|
|
12215
|
-
// Online/offline event listener for auto-retry verification
|
|
12216
|
-
React.useEffect(() => {
|
|
12217
|
-
if (proxyMode)
|
|
12218
|
-
return;
|
|
12219
|
-
const handleOnline = () => {
|
|
12220
|
-
setIsOnline(true);
|
|
12221
|
-
if (pendingVerificationRef.current && token && user) {
|
|
12222
|
-
retryVerification();
|
|
12223
|
-
}
|
|
12224
|
-
};
|
|
12225
|
-
const handleOffline = () => {
|
|
12226
|
-
setIsOnline(false);
|
|
12227
|
-
};
|
|
12228
|
-
window.addEventListener('online', handleOnline);
|
|
12229
|
-
window.addEventListener('offline', handleOffline);
|
|
12230
|
-
return () => {
|
|
12231
|
-
window.removeEventListener('online', handleOnline);
|
|
12232
|
-
window.removeEventListener('offline', handleOffline);
|
|
12233
|
-
};
|
|
12234
|
-
}, [proxyMode, token, user, retryVerification]);
|
|
12235
|
-
// Automatic background token refresh
|
|
12236
|
-
React.useEffect(() => {
|
|
12237
|
-
if (proxyMode || !enableAutoRefresh || !token || !user) {
|
|
12238
|
-
return;
|
|
12239
|
-
}
|
|
12240
|
-
const checkAndRefresh = async () => {
|
|
12241
|
-
try {
|
|
12242
|
-
const storedToken = await tokenStorage.getToken();
|
|
12243
|
-
if (!storedToken?.expiresAt)
|
|
12244
|
-
return;
|
|
12245
|
-
const now = Date.now();
|
|
12246
|
-
const remainingMs = storedToken.expiresAt - now;
|
|
12247
|
-
const totalLifetimeMs = 7 * 24 * 60 * 60 * 1000;
|
|
12248
|
-
const percentUsed = totalLifetimeMs > 0 ? ((totalLifetimeMs - remainingMs) / totalLifetimeMs) * 100 : 100;
|
|
12249
|
-
if (percentUsed >= refreshThresholdPercent) {
|
|
12250
|
-
try {
|
|
12251
|
-
await refreshToken();
|
|
12252
|
-
}
|
|
12253
|
-
catch (refreshError) {
|
|
12254
|
-
// Don't logout on refresh failure
|
|
12255
|
-
}
|
|
12256
|
-
}
|
|
12257
|
-
}
|
|
12258
|
-
catch (error) {
|
|
12259
|
-
console.error('[AuthContext] Error checking token for refresh:', error);
|
|
12260
|
-
}
|
|
12261
|
-
};
|
|
12262
|
-
checkAndRefresh();
|
|
12263
|
-
const intervalId = setInterval(checkAndRefresh, refreshCheckInterval);
|
|
12264
|
-
return () => {
|
|
12265
|
-
clearInterval(intervalId);
|
|
12266
|
-
};
|
|
12267
|
-
}, [proxyMode, enableAutoRefresh, refreshCheckInterval, refreshThresholdPercent, token, user, refreshToken]);
|
|
12268
|
-
const value = {
|
|
12269
|
-
user,
|
|
12270
|
-
token,
|
|
12271
|
-
accountData,
|
|
12272
|
-
accountInfo,
|
|
12273
|
-
isAuthenticated: !!user,
|
|
12274
|
-
isVerified,
|
|
12275
|
-
isLoading,
|
|
12276
|
-
isOnline,
|
|
12277
|
-
proxyMode,
|
|
12278
|
-
contact,
|
|
12279
|
-
contactId,
|
|
12280
|
-
getContact,
|
|
12281
|
-
updateContactCustomFields,
|
|
12282
|
-
login,
|
|
12283
|
-
logout,
|
|
12284
|
-
getToken,
|
|
12285
|
-
getTokenInfo,
|
|
12286
|
-
refreshToken,
|
|
12287
|
-
getAccount,
|
|
12288
|
-
refreshAccount,
|
|
12289
|
-
clearAccountCache,
|
|
12290
|
-
onAuthStateChange,
|
|
12291
|
-
retryVerification,
|
|
12292
|
-
};
|
|
12293
|
-
return jsxRuntime.jsx(AuthContext.Provider, { value: value, children: children });
|
|
12294
|
-
};
|
|
12295
|
-
const useAuth = () => {
|
|
12296
|
-
const context = React.useContext(AuthContext);
|
|
12297
|
-
if (context === undefined) {
|
|
12298
|
-
throw new Error('useAuth must be used within an AuthProvider');
|
|
12299
|
-
}
|
|
12300
|
-
return context;
|
|
12301
|
-
};
|
|
12302
|
-
|
|
12303
|
-
/**
|
|
12304
|
-
* Friendly error messages for common HTTP status codes.
|
|
12305
|
-
*/
|
|
12306
|
-
const STATUS_MESSAGES = {
|
|
12307
|
-
400: 'Invalid request. Please check your input and try again.',
|
|
12308
|
-
401: 'Invalid credentials. Please check your email and password.',
|
|
12309
|
-
403: 'Access denied. You do not have permission to perform this action.',
|
|
12310
|
-
404: 'Account not found. Please check your email or create a new account.',
|
|
12311
|
-
409: 'This email is already registered.',
|
|
12312
|
-
429: 'Too many attempts. Please wait a moment and try again.',
|
|
12313
|
-
};
|
|
12314
|
-
/**
|
|
12315
|
-
* Context-specific error messages for different auth operations.
|
|
12316
|
-
*/
|
|
12317
|
-
const ERROR_CODE_MESSAGES = {
|
|
12318
|
-
// 400 - Validation errors
|
|
12319
|
-
'MISSING_FIELDS': 'Email and password are required.',
|
|
12320
|
-
'MISSING_EMAIL': 'Email is required.',
|
|
12321
|
-
'MISSING_PASSWORD': 'Password is required.',
|
|
12322
|
-
'MISSING_TOKEN': 'Token is required.',
|
|
12323
|
-
'MISSING_PHONE_NUMBER': 'Phone number is required.',
|
|
12324
|
-
'MISSING_VERIFICATION_CODE': 'Phone number and verification code are required.',
|
|
12325
|
-
'MISSING_REDIRECT_URL': 'Redirect URL is required.',
|
|
12326
|
-
'MISSING_GOOGLE_TOKEN': 'Google token is required.',
|
|
12327
|
-
'INVALID_CLIENT_ID': 'Invalid client configuration.',
|
|
12328
|
-
'INVALID_REDIRECT_URL': 'Invalid redirect URL.',
|
|
12329
|
-
'INVALID_PHONE_NUMBER': 'Invalid phone number. Please check the format and try again.',
|
|
12330
|
-
'INVALID_GOOGLE_TOKEN': 'Invalid Google sign-in token.',
|
|
12331
|
-
'PASSWORD_TOO_SHORT': 'Password must be at least 8 characters long.',
|
|
12332
|
-
'PASSWORD_REQUIREMENTS_NOT_MET': 'New password must be at least 6 characters.',
|
|
12333
|
-
'EMAIL_ALREADY_VERIFIED': 'Your email is already verified.',
|
|
12334
|
-
'INVALID_CONFIRMATION': 'Please type DELETE to confirm account deletion.',
|
|
12335
|
-
// 401 - Authentication errors
|
|
12336
|
-
'INVALID_CREDENTIALS': 'Invalid email or password.',
|
|
12337
|
-
'INCORRECT_PASSWORD': 'Current password is incorrect.',
|
|
12338
|
-
'INVALID_VERIFICATION_CODE': 'Invalid or expired verification code. Please try again.',
|
|
12339
|
-
'INVALID_TOKEN': 'This link has expired or is invalid. Please request a new one.',
|
|
12340
|
-
'TOKEN_EXPIRED': 'This link has expired. Please request a new one.',
|
|
12341
|
-
'TOKEN_ALREADY_USED': 'This link has already been used. Please request a new one.',
|
|
12342
|
-
'UNAUTHORIZED': 'You must be logged in to perform this action.',
|
|
12343
|
-
'GOOGLE_TOKEN_AUDIENCE_MISMATCH': 'Google sign-in failed. Token was not issued for this application.',
|
|
12344
|
-
// 403 - Forbidden / verification required
|
|
12345
|
-
'EMAIL_NOT_VERIFIED': 'Please verify your email before signing in.',
|
|
12346
|
-
'ACCOUNT_LOCKED': 'Your account has been locked. Please verify your email to unlock.',
|
|
12347
|
-
'EMAIL_VERIFICATION_EXPIRED': 'Your verification deadline has passed and your account is locked. Please contact support.',
|
|
12348
|
-
// 404
|
|
12349
|
-
'USER_NOT_FOUND': 'Account not found. Please check your email or create a new account.',
|
|
12350
|
-
// 409 - Conflicts
|
|
12351
|
-
'EMAIL_ALREADY_EXISTS': 'This email is already registered.',
|
|
12352
|
-
'EMAIL_IN_USE': 'This email is already in use.',
|
|
12353
|
-
// 429 - Rate limiting
|
|
12354
|
-
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
|
|
12355
|
-
'TOO_MANY_MAGIC_LINKS': 'Too many magic link requests. Please try again later.',
|
|
12356
|
-
'TOO_MANY_VERIFICATION_ATTEMPTS': 'Too many verification attempts. Please wait and try again.',
|
|
12357
|
-
'MAX_VERIFICATION_ATTEMPTS': 'Maximum verification attempts reached. Please try again later.',
|
|
12358
|
-
// 500 - Server errors
|
|
12359
|
-
'LOGIN_FAILED': 'Login failed. Please try again later.',
|
|
12360
|
-
'REGISTRATION_FAILED': 'Registration failed. Please try again later.',
|
|
12361
|
-
'GOOGLE_AUTH_NOT_CONFIGURED': 'Google sign-in is not available for this application.',
|
|
12362
|
-
'GOOGLE_AUTH_FAILED': 'Google sign-in failed. Please try again.',
|
|
12363
|
-
'GOOGLE_USERINFO_FAILED': 'Failed to retrieve your Google account information. Please try again.',
|
|
12364
|
-
'PHONE_VERIFICATION_FAILED': 'Phone verification failed. Please try again.',
|
|
12365
|
-
'SEND_VERIFICATION_CODE_FAILED': 'Failed to send verification code. Please try again.',
|
|
12366
|
-
'MAGIC_LINK_SEND_FAILED': 'Failed to send magic link. Please try again.',
|
|
12367
|
-
'MAGIC_LINK_VERIFICATION_FAILED': 'Magic link verification failed. Please try again.',
|
|
12368
|
-
'PASSWORD_RESET_FAILED': 'Failed to process password reset. Please try again.',
|
|
12369
|
-
'PASSWORD_RESET_COMPLETE_FAILED': 'Failed to reset password. Please try again.',
|
|
12370
|
-
'EMAIL_VERIFICATION_SEND_FAILED': 'Failed to send verification email. Please try again.',
|
|
12371
|
-
'EMAIL_VERIFICATION_FAILED': 'Email verification failed. Please try again.',
|
|
12372
|
-
// Account management 500s
|
|
12373
|
-
'UPDATE_PROFILE_FAILED': 'Failed to update profile. Please try again.',
|
|
12374
|
-
'CHANGE_PASSWORD_FAILED': 'Failed to change password. Please try again.',
|
|
12375
|
-
'CHANGE_EMAIL_FAILED': 'Failed to change email. Please try again.',
|
|
12376
|
-
'UPDATE_PHONE_FAILED': 'Failed to update phone number. Please try again.',
|
|
12377
|
-
'DELETE_ACCOUNT_FAILED': 'Failed to delete account. Please try again.',
|
|
12378
|
-
'CONFIG_FETCH_FAILED': 'Failed to load configuration. Please try again.',
|
|
12379
|
-
'INTERNAL_ERROR': 'An unexpected error occurred. Please try again.',
|
|
12380
|
-
// Legacy aliases (kept for backward compatibility)
|
|
12381
|
-
'INVALID_CODE': 'Invalid verification code. Please check and try again.',
|
|
12382
|
-
'CODE_EXPIRED': 'This code has expired. Please request a new one.',
|
|
12383
|
-
'PHONE_NOT_SUPPORTED': 'This phone number is not supported. Please try a different number.',
|
|
12384
|
-
'INVALID_PHONE': 'Invalid phone number. Please check the format and try again.',
|
|
12385
|
-
'PASSWORD_TOO_WEAK': 'Password is too weak. Please use at least 8 characters with a mix of letters and numbers.',
|
|
12386
|
-
'RATE_LIMITED': 'Too many attempts. Please wait a moment and try again.',
|
|
12387
|
-
};
|
|
12388
|
-
function isApiErrorLike(error) {
|
|
12389
|
-
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12390
|
-
return true;
|
|
12391
|
-
if (error && typeof error === 'object' && 'statusCode' in error && 'message' in error) {
|
|
12392
|
-
const e = error;
|
|
12393
|
-
return typeof e.statusCode === 'number' && typeof e.message === 'string';
|
|
12394
|
-
}
|
|
12395
|
-
return false;
|
|
12396
|
-
}
|
|
12397
|
-
/**
|
|
12398
|
-
* Extracts a user-friendly error message from an error.
|
|
12399
|
-
*
|
|
12400
|
-
* Handles:
|
|
12401
|
-
* - SmartlinksApiError (and duck-typed equivalents from proxy mode)
|
|
12402
|
-
* - Standard Error: Uses message property
|
|
12403
|
-
* - String: Passes through directly (for native bridge errors)
|
|
12404
|
-
* - Unknown: Returns generic message
|
|
12405
|
-
*/
|
|
12406
|
-
function getFriendlyErrorMessage(error) {
|
|
12407
|
-
// Handle SmartlinksApiError or duck-typed API errors (proxy mode)
|
|
12408
|
-
if (isApiErrorLike(error)) {
|
|
12409
|
-
// First, check for specific error code (most precise)
|
|
12410
|
-
const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12411
|
-
if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
|
|
12412
|
-
return ERROR_CODE_MESSAGES[errorCode];
|
|
12413
|
-
}
|
|
12414
|
-
// Then, check status code for general category messages
|
|
12415
|
-
if (error.statusCode >= 500) {
|
|
12416
|
-
return 'Server error. Please try again later.';
|
|
12490
|
+
await tokenStorage.clearAccountInfo();
|
|
12417
12491
|
}
|
|
12418
|
-
|
|
12419
|
-
|
|
12492
|
+
setAccountInfo(null);
|
|
12493
|
+
}, [proxyMode]);
|
|
12494
|
+
const onAuthStateChange = React.useCallback((callback) => {
|
|
12495
|
+
callbacksRef.current.add(callback);
|
|
12496
|
+
return () => {
|
|
12497
|
+
callbacksRef.current.delete(callback);
|
|
12498
|
+
};
|
|
12499
|
+
}, []);
|
|
12500
|
+
const retryVerification = React.useCallback(async () => {
|
|
12501
|
+
if (!token || !user)
|
|
12502
|
+
return false;
|
|
12503
|
+
if (isVerified)
|
|
12504
|
+
return true;
|
|
12505
|
+
try {
|
|
12506
|
+
await smartlinks__namespace.auth.verifyToken(token);
|
|
12507
|
+
setIsVerified(true);
|
|
12508
|
+
pendingVerificationRef.current = false;
|
|
12509
|
+
notifyAuthStateChange('SESSION_VERIFIED', user, token, accountData, accountInfo, true, contact, contactId);
|
|
12510
|
+
return true;
|
|
12420
12511
|
}
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
// Handle standard Error objects
|
|
12425
|
-
if (error instanceof Error) {
|
|
12426
|
-
// SDK bug workaround: SDK may do `throw new Error(responseBodyObject)` which produces
|
|
12427
|
-
// message "[object Object]". Check for API error properties attached to the Error instance.
|
|
12428
|
-
const errAny = error;
|
|
12429
|
-
// Check if the Error has API error properties directly attached (e.g., error.statusCode, error.errorCode)
|
|
12430
|
-
if (typeof errAny.statusCode === 'number' || errAny.errorCode || errAny.response) {
|
|
12431
|
-
// Try to extract from attached properties
|
|
12432
|
-
const apiLike = errAny.response || errAny;
|
|
12433
|
-
if (isApiErrorLike(apiLike)) {
|
|
12434
|
-
return getFriendlyErrorMessage(apiLike);
|
|
12512
|
+
catch (err) {
|
|
12513
|
+
if (isNetworkError(err)) {
|
|
12514
|
+
return false;
|
|
12435
12515
|
}
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
if (isApiErrorLike(errAny.cause)) {
|
|
12440
|
-
return getFriendlyErrorMessage(errAny.cause);
|
|
12516
|
+
else {
|
|
12517
|
+
await logout();
|
|
12518
|
+
return false;
|
|
12441
12519
|
}
|
|
12442
12520
|
}
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12521
|
+
}, [token, user, isVerified, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
|
|
12522
|
+
// Online/offline event listener for auto-retry verification
|
|
12523
|
+
React.useEffect(() => {
|
|
12524
|
+
if (proxyMode)
|
|
12525
|
+
return;
|
|
12526
|
+
const handleOnline = () => {
|
|
12527
|
+
setIsOnline(true);
|
|
12528
|
+
if (pendingVerificationRef.current && token && user) {
|
|
12529
|
+
retryVerification();
|
|
12530
|
+
}
|
|
12531
|
+
};
|
|
12532
|
+
const handleOffline = () => {
|
|
12533
|
+
setIsOnline(false);
|
|
12534
|
+
};
|
|
12535
|
+
window.addEventListener('online', handleOnline);
|
|
12536
|
+
window.addEventListener('offline', handleOffline);
|
|
12537
|
+
return () => {
|
|
12538
|
+
window.removeEventListener('online', handleOnline);
|
|
12539
|
+
window.removeEventListener('offline', handleOffline);
|
|
12540
|
+
};
|
|
12541
|
+
}, [proxyMode, token, user, retryVerification]);
|
|
12542
|
+
// Automatic background token refresh
|
|
12543
|
+
React.useEffect(() => {
|
|
12544
|
+
if (proxyMode || !enableAutoRefresh || !token || !user) {
|
|
12545
|
+
return;
|
|
12453
12546
|
}
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
* Gets the server-specific error code from an error, if available.
|
|
12514
|
-
*/
|
|
12515
|
-
function getErrorCode(error) {
|
|
12516
|
-
if (isApiErrorLike(error)) {
|
|
12517
|
-
return error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12518
|
-
}
|
|
12519
|
-
return undefined;
|
|
12520
|
-
}
|
|
12521
|
-
/**
|
|
12522
|
-
* Error codes that indicate the user needs to verify their email.
|
|
12523
|
-
*/
|
|
12524
|
-
const EMAIL_VERIFICATION_ERROR_CODES = new Set([
|
|
12525
|
-
'EMAIL_NOT_VERIFIED',
|
|
12526
|
-
'ACCOUNT_LOCKED',
|
|
12527
|
-
'EMAIL_VERIFICATION_EXPIRED',
|
|
12528
|
-
]);
|
|
12529
|
-
/**
|
|
12530
|
-
* Checks if an error requires email verification action from the user.
|
|
12531
|
-
*/
|
|
12532
|
-
function requiresEmailVerification(error) {
|
|
12533
|
-
const code = getErrorCode(error);
|
|
12534
|
-
if (code && EMAIL_VERIFICATION_ERROR_CODES.has(code))
|
|
12535
|
-
return true;
|
|
12536
|
-
// Also check the flag from the response body
|
|
12537
|
-
if (error && typeof error === 'object' && 'requiresEmailVerification' in error) {
|
|
12538
|
-
return error.requiresEmailVerification === true;
|
|
12547
|
+
const checkAndRefresh = async () => {
|
|
12548
|
+
try {
|
|
12549
|
+
const storedToken = await tokenStorage.getToken();
|
|
12550
|
+
if (!storedToken?.expiresAt)
|
|
12551
|
+
return;
|
|
12552
|
+
const now = Date.now();
|
|
12553
|
+
const remainingMs = storedToken.expiresAt - now;
|
|
12554
|
+
const totalLifetimeMs = 7 * 24 * 60 * 60 * 1000;
|
|
12555
|
+
const percentUsed = totalLifetimeMs > 0 ? ((totalLifetimeMs - remainingMs) / totalLifetimeMs) * 100 : 100;
|
|
12556
|
+
if (percentUsed >= refreshThresholdPercent) {
|
|
12557
|
+
try {
|
|
12558
|
+
await refreshToken();
|
|
12559
|
+
}
|
|
12560
|
+
catch (refreshError) {
|
|
12561
|
+
// Don't logout on refresh failure
|
|
12562
|
+
}
|
|
12563
|
+
}
|
|
12564
|
+
}
|
|
12565
|
+
catch (error) {
|
|
12566
|
+
console.error('[AuthContext] Error checking token for refresh:', error);
|
|
12567
|
+
}
|
|
12568
|
+
};
|
|
12569
|
+
checkAndRefresh();
|
|
12570
|
+
const intervalId = setInterval(checkAndRefresh, refreshCheckInterval);
|
|
12571
|
+
return () => {
|
|
12572
|
+
clearInterval(intervalId);
|
|
12573
|
+
};
|
|
12574
|
+
}, [proxyMode, enableAutoRefresh, refreshCheckInterval, refreshThresholdPercent, token, user, refreshToken]);
|
|
12575
|
+
const value = {
|
|
12576
|
+
user,
|
|
12577
|
+
token,
|
|
12578
|
+
accountData,
|
|
12579
|
+
accountInfo,
|
|
12580
|
+
isAuthenticated: !!user,
|
|
12581
|
+
isVerified,
|
|
12582
|
+
isLoading,
|
|
12583
|
+
isOnline,
|
|
12584
|
+
proxyMode,
|
|
12585
|
+
contact,
|
|
12586
|
+
contactId,
|
|
12587
|
+
getContact,
|
|
12588
|
+
updateContactCustomFields,
|
|
12589
|
+
login,
|
|
12590
|
+
logout,
|
|
12591
|
+
getToken,
|
|
12592
|
+
getTokenInfo,
|
|
12593
|
+
refreshToken,
|
|
12594
|
+
getAccount,
|
|
12595
|
+
refreshAccount,
|
|
12596
|
+
clearAccountCache,
|
|
12597
|
+
onAuthStateChange,
|
|
12598
|
+
retryVerification,
|
|
12599
|
+
};
|
|
12600
|
+
return jsxRuntime.jsx(AuthContext.Provider, { value: value, children: children });
|
|
12601
|
+
};
|
|
12602
|
+
const useAuth = () => {
|
|
12603
|
+
const context = React.useContext(AuthContext);
|
|
12604
|
+
if (context === undefined) {
|
|
12605
|
+
throw new Error('useAuth must be used within an AuthProvider');
|
|
12539
12606
|
}
|
|
12540
|
-
return
|
|
12541
|
-
}
|
|
12607
|
+
return context;
|
|
12608
|
+
};
|
|
12542
12609
|
|
|
12543
12610
|
// VERSION: Update this when making changes to help identify which version is running
|
|
12544
|
-
const AUTH_UI_VERSION = '
|
|
12611
|
+
const AUTH_UI_VERSION = '46';
|
|
12545
12612
|
const LOG_PREFIX = `[SmartlinksAuthUI:v${AUTH_UI_VERSION}]`;
|
|
12546
12613
|
// Helper to check for URL auth params synchronously (runs during initialization)
|
|
12547
12614
|
// This prevents the form from flashing before detecting deep-link flows
|
|
@@ -12565,6 +12632,24 @@ const getExpirationFromResponse = (response) => {
|
|
|
12565
12632
|
return Date.now() + response.expiresIn;
|
|
12566
12633
|
return undefined; // Will use 7-day default in tokenStorage
|
|
12567
12634
|
};
|
|
12635
|
+
const getActionResultErrorMessage = (result) => {
|
|
12636
|
+
if (!result || typeof result !== 'object')
|
|
12637
|
+
return null;
|
|
12638
|
+
const candidate = result;
|
|
12639
|
+
const numericStatusCode = typeof candidate.statusCode === 'string'
|
|
12640
|
+
? Number(candidate.statusCode)
|
|
12641
|
+
: candidate.statusCode;
|
|
12642
|
+
const hasFailureStatus = typeof numericStatusCode === 'number' && !Number.isNaN(numericStatusCode) && numericStatusCode >= 400;
|
|
12643
|
+
const isFailure = candidate.success === false || candidate.ok === false || !!candidate.errorCode || hasFailureStatus;
|
|
12644
|
+
if (!isFailure)
|
|
12645
|
+
return null;
|
|
12646
|
+
return getFriendlyErrorMessage({
|
|
12647
|
+
statusCode: hasFailureStatus ? numericStatusCode : 400,
|
|
12648
|
+
errorCode: candidate.errorCode,
|
|
12649
|
+
message: candidate.message || 'Request failed. Please try again.',
|
|
12650
|
+
details: candidate.details,
|
|
12651
|
+
});
|
|
12652
|
+
};
|
|
12568
12653
|
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
12569
12654
|
const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
|
|
12570
12655
|
// Default Google OAuth proxy URL (hosted on our whitelisted domain)
|
|
@@ -12740,7 +12825,7 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
|
|
|
12740
12825
|
});
|
|
12741
12826
|
};
|
|
12742
12827
|
// 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 = '
|
|
12828
|
+
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
12829
|
// Resolve signup prominence from props, customization, config, or default
|
|
12745
12830
|
const resolvedSignupProminence = signupProminence || customization?.signupProminence || 'minimal';
|
|
12746
12831
|
// Determine initial mode based on signupProminence setting
|
|
@@ -13099,10 +13184,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13099
13184
|
log.log('Verifying reset token:', token);
|
|
13100
13185
|
// Verify token is valid, then show password reset form
|
|
13101
13186
|
const verifyResult = await api.verifyResetToken(token);
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
throw new Error(verifyResult.message || 'Invalid or expired token');
|
|
13187
|
+
// Guard against undefined result (proxy mode may resolve without data)
|
|
13188
|
+
if (!verifyResult || !verifyResult.valid) {
|
|
13189
|
+
throw new Error(verifyResult?.message || 'Invalid or expired token');
|
|
13106
13190
|
}
|
|
13107
13191
|
setUrlAuthProcessing(false); // Clear processing state - showing reset form
|
|
13108
13192
|
setResetToken(token); // Store token for use in password reset
|
|
@@ -13380,17 +13464,46 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13380
13464
|
return;
|
|
13381
13465
|
setLoading(true);
|
|
13382
13466
|
setError(undefined);
|
|
13467
|
+
setAuthSuccess(false);
|
|
13468
|
+
setResetSuccess(false);
|
|
13469
|
+
setSuccessMessage(undefined);
|
|
13470
|
+
setShowResendVerification(false);
|
|
13383
13471
|
try {
|
|
13384
13472
|
const result = await api.requestPasswordReset(resetRequestEmail, getRedirectUrl());
|
|
13385
|
-
|
|
13473
|
+
const resultError = getActionResultErrorMessage(result);
|
|
13474
|
+
if (resultError) {
|
|
13475
|
+
setAuthSuccess(false);
|
|
13476
|
+
setResetSuccess(false);
|
|
13477
|
+
setSuccessMessage(undefined);
|
|
13478
|
+
setError(resultError);
|
|
13479
|
+
setShowRequestNewReset(true);
|
|
13480
|
+
return;
|
|
13481
|
+
}
|
|
13482
|
+
const resultAny = result;
|
|
13483
|
+
const isConfirmedSuccess = result && (resultAny.success === true ||
|
|
13484
|
+
resultAny.ok === true ||
|
|
13485
|
+
(resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
|
|
13486
|
+
if (!isConfirmedSuccess) {
|
|
13487
|
+
setAuthSuccess(false);
|
|
13488
|
+
setResetSuccess(false);
|
|
13489
|
+
setSuccessMessage(undefined);
|
|
13490
|
+
setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
|
|
13491
|
+
setShowRequestNewReset(true);
|
|
13492
|
+
return;
|
|
13493
|
+
}
|
|
13494
|
+
setAuthSuccess(false);
|
|
13495
|
+
setMode('reset-password');
|
|
13386
13496
|
setResetSuccess(true);
|
|
13387
|
-
// Use backend message if available, otherwise default
|
|
13388
13497
|
setSuccessMessage(result?.message || 'Password reset email sent! Please check your inbox.');
|
|
13389
13498
|
setShowRequestNewReset(false);
|
|
13390
13499
|
setResetRequestEmail('');
|
|
13391
13500
|
}
|
|
13392
13501
|
catch (err) {
|
|
13502
|
+
setAuthSuccess(false);
|
|
13503
|
+
setResetSuccess(false);
|
|
13504
|
+
setSuccessMessage(undefined);
|
|
13393
13505
|
setError(getFriendlyErrorMessage(err));
|
|
13506
|
+
setShowRequestNewReset(true);
|
|
13394
13507
|
onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
|
|
13395
13508
|
}
|
|
13396
13509
|
finally {
|
|
@@ -13717,9 +13830,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13717
13830
|
client_id: googleClientId,
|
|
13718
13831
|
origin: window.location.origin,
|
|
13719
13832
|
});
|
|
13833
|
+
// Track whether the credential callback has fired (user selected an account)
|
|
13834
|
+
// so we don't show the "blocked" fallback when the prompt closes after a successful selection
|
|
13835
|
+
let credentialCallbackFired = false;
|
|
13720
13836
|
google.accounts.id.initialize({
|
|
13721
13837
|
client_id: googleClientId,
|
|
13722
13838
|
callback: async (response) => {
|
|
13839
|
+
credentialCallbackFired = true;
|
|
13723
13840
|
try {
|
|
13724
13841
|
const idToken = response.credential;
|
|
13725
13842
|
const authResponse = await api.loginWithGoogle(idToken);
|
|
@@ -13747,23 +13864,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13747
13864
|
cancel_on_tap_outside: true,
|
|
13748
13865
|
use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
|
|
13749
13866
|
});
|
|
13750
|
-
// Use timeout fallback — if no prompt interaction after
|
|
13867
|
+
// Use timeout fallback — if no prompt interaction after 15s, assume FedCM was blocked
|
|
13868
|
+
// (15s gives users enough time to choose a Google account from the picker)
|
|
13751
13869
|
const promptTimeout = setTimeout(() => {
|
|
13870
|
+
if (credentialCallbackFired)
|
|
13871
|
+
return; // Auth is already in progress, don't show fallback
|
|
13752
13872
|
log.log('Google OneTap prompt timed out — FedCM may be blocked or unavailable');
|
|
13753
13873
|
setGoogleFallbackToPopup(true);
|
|
13754
13874
|
setLoading(false);
|
|
13755
|
-
},
|
|
13875
|
+
}, 15000);
|
|
13756
13876
|
google.accounts.id.prompt((notification) => {
|
|
13757
13877
|
clearTimeout(promptTimeout);
|
|
13878
|
+
// If the credential callback already fired, the user successfully selected an account
|
|
13879
|
+
// The prompt closing with 'dismissed' is expected — don't show fallback
|
|
13880
|
+
if (credentialCallbackFired) {
|
|
13881
|
+
log.log('Google OneTap prompt closed after credential selection — ignoring');
|
|
13882
|
+
return;
|
|
13883
|
+
}
|
|
13758
13884
|
// Check for FedCM/OneTap dismissal or blocking
|
|
13759
|
-
// notification may have getMomentType(), getDismissedReason(), getSkippedReason()
|
|
13760
13885
|
const momentType = notification?.getMomentType?.();
|
|
13761
13886
|
const dismissedReason = notification?.getDismissedReason?.();
|
|
13762
13887
|
const skippedReason = notification?.getSkippedReason?.();
|
|
13763
13888
|
log.log('Google OneTap prompt notification:', { momentType, dismissedReason, skippedReason });
|
|
13764
13889
|
if (momentType === 'skipped' || momentType === 'dismissed') {
|
|
13765
|
-
// User dismissed the prompt, or browser blocked it (FedCM disabled)
|
|
13766
|
-
// Offer popup flow as alternative
|
|
13767
13890
|
log.log('Google OneTap was dismissed/skipped, offering popup fallback');
|
|
13768
13891
|
setGoogleFallbackToPopup(true);
|
|
13769
13892
|
}
|
|
@@ -13777,11 +13900,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13777
13900
|
setLoading(false);
|
|
13778
13901
|
}
|
|
13779
13902
|
};
|
|
13780
|
-
const handlePhoneAuth = async (phoneNumber, verificationCode) => {
|
|
13903
|
+
const handlePhoneAuth = async (phoneNumber, verificationCode, displayName) => {
|
|
13781
13904
|
setLoading(true);
|
|
13782
13905
|
setError(undefined);
|
|
13783
13906
|
try {
|
|
13784
13907
|
if (!verificationCode) {
|
|
13908
|
+
// Phone verify endpoint handles account creation on the backend side,
|
|
13909
|
+
// so no need for ensureAccount here (register requires email which phone users may not have)
|
|
13785
13910
|
// Send verification code via Twilio Verify Service
|
|
13786
13911
|
await api.sendPhoneCode(phoneNumber);
|
|
13787
13912
|
// Twilio Verify Service tracks the verification by phone number
|
|
@@ -13825,6 +13950,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13825
13950
|
const handlePasswordReset = async (emailOrPassword, confirmPassword) => {
|
|
13826
13951
|
setLoading(true);
|
|
13827
13952
|
setError(undefined);
|
|
13953
|
+
setAuthSuccess(false);
|
|
13954
|
+
setResetSuccess(false);
|
|
13955
|
+
setSuccessMessage(undefined);
|
|
13828
13956
|
const effectiveRedirectUrl = getRedirectUrl();
|
|
13829
13957
|
try {
|
|
13830
13958
|
if (resetToken && confirmPassword) {
|
|
@@ -13833,6 +13961,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13833
13961
|
// Auto-login with the new password if we have the email
|
|
13834
13962
|
if (resetEmail) {
|
|
13835
13963
|
try {
|
|
13964
|
+
log.log('Auto-login after password reset for:', resetEmail);
|
|
13836
13965
|
const loginResponse = await api.login(resetEmail, emailOrPassword);
|
|
13837
13966
|
if (loginResponse.token) {
|
|
13838
13967
|
await auth.login(loginResponse.token, loginResponse.user, loginResponse.accountData, false);
|
|
@@ -13845,9 +13974,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13845
13974
|
}
|
|
13846
13975
|
}
|
|
13847
13976
|
catch (loginErr) {
|
|
13848
|
-
|
|
13977
|
+
log.warn('Auto-login after password reset failed, requiring manual login:', loginErr);
|
|
13849
13978
|
}
|
|
13850
13979
|
}
|
|
13980
|
+
else {
|
|
13981
|
+
log.warn('No resetEmail available for auto-login after password reset');
|
|
13982
|
+
}
|
|
13851
13983
|
// Fallback: show success but require manual login
|
|
13852
13984
|
setResetSuccess(true);
|
|
13853
13985
|
setSuccessMessage('Password reset successful! Please sign in with your new password.');
|
|
@@ -13857,14 +13989,35 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13857
13989
|
else {
|
|
13858
13990
|
// Request password reset email
|
|
13859
13991
|
const result = await api.requestPasswordReset(emailOrPassword, effectiveRedirectUrl);
|
|
13992
|
+
const resultError = getActionResultErrorMessage(result);
|
|
13993
|
+
if (resultError) {
|
|
13994
|
+
setAuthSuccess(false);
|
|
13995
|
+
setResetSuccess(false);
|
|
13996
|
+
setSuccessMessage(undefined);
|
|
13997
|
+
setError(resultError);
|
|
13998
|
+
return;
|
|
13999
|
+
}
|
|
14000
|
+
const resultAny = result;
|
|
14001
|
+
const isConfirmedSuccess = result && (resultAny.success === true ||
|
|
14002
|
+
resultAny.ok === true ||
|
|
14003
|
+
(resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
|
|
14004
|
+
if (!isConfirmedSuccess) {
|
|
14005
|
+
setAuthSuccess(false);
|
|
14006
|
+
setResetSuccess(false);
|
|
14007
|
+
setSuccessMessage(undefined);
|
|
14008
|
+
setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
|
|
14009
|
+
return;
|
|
14010
|
+
}
|
|
13860
14011
|
setResetSuccess(true);
|
|
13861
|
-
// Use backend message if available
|
|
13862
14012
|
if (result?.message) {
|
|
13863
14013
|
setSuccessMessage(result.message);
|
|
13864
14014
|
}
|
|
13865
14015
|
}
|
|
13866
14016
|
}
|
|
13867
14017
|
catch (err) {
|
|
14018
|
+
setAuthSuccess(false);
|
|
14019
|
+
setResetSuccess(false);
|
|
14020
|
+
setSuccessMessage(undefined);
|
|
13868
14021
|
setError(getFriendlyErrorMessage(err));
|
|
13869
14022
|
onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
|
|
13870
14023
|
}
|
|
@@ -13872,10 +14025,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13872
14025
|
setLoading(false);
|
|
13873
14026
|
}
|
|
13874
14027
|
};
|
|
13875
|
-
const handleMagicLink = async (email) => {
|
|
14028
|
+
const handleMagicLink = async (email, displayName) => {
|
|
13876
14029
|
setLoading(true);
|
|
13877
14030
|
setError(undefined);
|
|
13878
14031
|
try {
|
|
14032
|
+
// Ensure account exists before sending magic link (creates if new, no-op if exists)
|
|
14033
|
+
await api.ensureAccount({ email, displayName });
|
|
13879
14034
|
await api.sendMagicLink(email, getRedirectUrl());
|
|
13880
14035
|
setAuthSuccess(true);
|
|
13881
14036
|
setSuccessMessage('Magic link sent! Check your email to log in.');
|
|
@@ -13922,13 +14077,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13922
14077
|
? 'hsl(var(--muted-foreground, 215 15% 45%))'
|
|
13923
14078
|
: (resolvedTheme === 'dark' ? '#94a3b8' : '#6B7280'),
|
|
13924
14079
|
fontSize: '0.875rem'
|
|
13925
|
-
}, children: successMessage })] })) : mode === 'magic-link' ? (jsxRuntime.jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error })) : mode === 'phone' ? (jsxRuntime.jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error })) : mode === 'reset-password' ? (jsxRuntime.jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
14080
|
+
}, children: successMessage })] })) : mode === 'magic-link' ? (jsxRuntime.jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'phone' ? (jsxRuntime.jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'reset-password' ? (jsxRuntime.jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
13926
14081
|
setMode('login');
|
|
13927
14082
|
setResetSuccess(false);
|
|
13928
14083
|
setResetToken(undefined); // Clear token when going back
|
|
13929
14084
|
setResetEmail(undefined); // Clear email when going back
|
|
13930
14085
|
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: {
|
|
14086
|
+
}, 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
14087
|
marginTop: '1rem',
|
|
13933
14088
|
padding: '1.5rem',
|
|
13934
14089
|
backgroundColor: config?.branding?.inheritHostStyles
|
|
@@ -14026,7 +14181,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14026
14181
|
color: config?.branding?.inheritHostStyles
|
|
14027
14182
|
? 'hsl(var(--foreground, 215 25% 15%))'
|
|
14028
14183
|
: (resolvedTheme === 'dark' ? '#f1f5f9' : '#374151')
|
|
14029
|
-
}, children: "Password Reset Link Expired" }), jsxRuntime.jsx("
|
|
14184
|
+
}, children: "Password Reset Link Expired" }), error && (jsxRuntime.jsx("div", { style: {
|
|
14185
|
+
marginBottom: '1rem',
|
|
14186
|
+
padding: '0.75rem',
|
|
14187
|
+
backgroundColor: resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.15)' : 'rgba(239, 68, 68, 0.1)',
|
|
14188
|
+
border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.3)' : 'rgba(239, 68, 68, 0.2)'}`,
|
|
14189
|
+
borderRadius: '0.375rem',
|
|
14190
|
+
color: resolvedTheme === 'dark' ? '#fca5a5' : '#dc2626',
|
|
14191
|
+
fontSize: '0.875rem',
|
|
14192
|
+
lineHeight: '1.5',
|
|
14193
|
+
}, children: error })), jsxRuntime.jsx("p", { style: {
|
|
14030
14194
|
marginBottom: '1rem',
|
|
14031
14195
|
fontSize: '0.875rem',
|
|
14032
14196
|
color: config?.branding?.inheritHostStyles
|
|
@@ -14162,6 +14326,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14162
14326
|
const emailDisplayMode = config?.emailDisplayMode || 'form';
|
|
14163
14327
|
const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
|
|
14164
14328
|
const actualProviders = config?.enabledProviders || enabledProviders;
|
|
14329
|
+
const hasEmailProvider = actualProviders.includes('email');
|
|
14330
|
+
// If email provider is not enabled, only show provider buttons (no email/password form)
|
|
14331
|
+
if (!hasEmailProvider) {
|
|
14332
|
+
return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
14333
|
+
}
|
|
14165
14334
|
// Button mode: show provider selection first, then email form if email is selected
|
|
14166
14335
|
if (emailDisplayMode === 'button' && !showEmailForm) {
|
|
14167
14336
|
return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
@@ -14183,7 +14352,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14183
14352
|
setShowResendVerification(false);
|
|
14184
14353
|
setShowRequestNewReset(false);
|
|
14185
14354
|
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 }))] }));
|
|
14355
|
+
}, 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
14356
|
})()] })) })) : null }));
|
|
14188
14357
|
};
|
|
14189
14358
|
|