@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.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import React, { useEffect, useState, useMemo, useRef, useCallback, createContext, useContext } from 'react';
|
|
3
3
|
import * as smartlinks from '@proveanything/smartlinks';
|
|
4
|
-
import {
|
|
4
|
+
import { SmartlinksApiError, iframe } from '@proveanything/smartlinks';
|
|
5
5
|
import { post, getApiHeaders, isProxyEnabled } from '@proveanything/smartlinks/dist/http';
|
|
6
6
|
|
|
7
7
|
const AuthContainer = ({ children, theme = 'light', className = '', config, minimal = false, }) => {
|
|
@@ -258,7 +258,7 @@ const AuthModeToggle = ({ mode, onModeChange, disabled = false, }) => {
|
|
|
258
258
|
return (jsxs("div", { className: "auth-mode-toggle", role: "tablist", "aria-label": "Authentication mode", children: [jsx("button", { type: "button", role: "tab", "aria-selected": mode === 'login', className: `auth-mode-toggle-button ${mode === 'login' ? 'auth-mode-toggle-button--active' : ''}`, onClick: () => onModeChange('login'), disabled: disabled, children: "Sign In" }), jsx("button", { type: "button", role: "tab", "aria-selected": mode === 'register', className: `auth-mode-toggle-button ${mode === 'register' ? 'auth-mode-toggle-button--active' : ''}`, onClick: () => onModeChange('register'), disabled: disabled, children: "Create Account" })] }));
|
|
259
259
|
};
|
|
260
260
|
|
|
261
|
-
const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, signupProminence = 'minimal', schema, registrationFieldsConfig = [], additionalFields = [], }) => {
|
|
261
|
+
const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, signupProminence = 'minimal', signupRedirectUrl, schema, registrationFieldsConfig = [], additionalFields = [], }) => {
|
|
262
262
|
const [formData, setFormData] = useState({
|
|
263
263
|
email: '',
|
|
264
264
|
password: '',
|
|
@@ -314,7 +314,7 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
|
|
|
314
314
|
if (newMode !== mode) {
|
|
315
315
|
onModeSwitch();
|
|
316
316
|
}
|
|
317
|
-
}, disabled: loading })), jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: title }), jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John Smith" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: formData.email || '', onChange: (e) => handleChange('email', e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: formData.password || '', onChange: (e) => handleChange('password', e.target.value), required: true, disabled: loading, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === 'login' ? 'current-password' : 'new-password', minLength: 6 })] }), mode === 'register' && hasSchemaFields && schemaFields.inline.map(renderSchemaField), mode === 'register' && hasLegacyFields && additionalFields.map(renderLegacyField), mode === 'register' && hasSchemaFields && schemaFields.postCredentials.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsx("div", { className: "auth-form-footer", children: jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && (jsxs("div", { className: "auth-divider", children: [jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] }))] }));
|
|
317
|
+
}, disabled: loading })), jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: title }), jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John Smith" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: formData.email || '', onChange: (e) => handleChange('email', e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: formData.password || '', onChange: (e) => handleChange('password', e.target.value), required: true, disabled: loading, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === 'login' ? 'current-password' : 'new-password', minLength: 6 })] }), mode === 'register' && hasSchemaFields && schemaFields.inline.map(renderSchemaField), mode === 'register' && hasLegacyFields && additionalFields.map(renderLegacyField), mode === 'register' && hasSchemaFields && schemaFields.postCredentials.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsx("div", { className: "auth-form-footer", children: jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && signupProminence !== 'none' && (jsxs("div", { className: "auth-divider", children: [jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), signupRedirectUrl && mode === 'login' ? (jsx("a", { href: signupRedirectUrl, target: "_top", className: "auth-link auth-link-bold", children: "Sign up" })) : (jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' }))] }))] }));
|
|
318
318
|
};
|
|
319
319
|
|
|
320
320
|
const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
|
|
@@ -10759,7 +10759,8 @@ const PhoneInput = ({ value, onChange, disabled = false, }) => {
|
|
|
10759
10759
|
return (jsx(PhoneInputComponent, { international: true, defaultCountry: defaultCountry, value: value, onChange: (value) => onChange(value || ''), disabled: disabled, className: "phone-input-wrapper" }));
|
|
10760
10760
|
};
|
|
10761
10761
|
|
|
10762
|
-
const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
10762
|
+
const PhoneAuthForm = ({ onSubmit, onBack, loading, error, collectName = false, }) => {
|
|
10763
|
+
const [displayName, setDisplayName] = useState('');
|
|
10763
10764
|
const [phoneNumber, setPhoneNumber] = useState('');
|
|
10764
10765
|
const [verificationCode, setVerificationCode] = useState('');
|
|
10765
10766
|
const [codeSent, setCodeSent] = useState(false);
|
|
@@ -10776,7 +10777,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10776
10777
|
const handleSendCode = async (e) => {
|
|
10777
10778
|
e.preventDefault();
|
|
10778
10779
|
try {
|
|
10779
|
-
await onSubmit(phoneNumber);
|
|
10780
|
+
await onSubmit(phoneNumber, undefined, collectName ? displayName || undefined : undefined);
|
|
10780
10781
|
// Only transition to code entry UI AFTER successful send
|
|
10781
10782
|
setCodeSent(true);
|
|
10782
10783
|
setResendCooldown(60); // 60 second cooldown
|
|
@@ -10803,7 +10804,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10803
10804
|
};
|
|
10804
10805
|
return (jsxs("form", { className: "auth-form", onSubmit: codeSent ? handleVerifyCode : handleSendCode, children: [jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: "Phone Authentication" }), jsx("p", { className: "auth-form-subtitle", children: codeSent
|
|
10805
10806
|
? 'Enter the verification code sent to your phone.'
|
|
10806
|
-
: 'Enter your phone number to receive a verification code.' })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), !codeSent ? (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "phoneNumber", className: "auth-label", children: "Phone Number" }), jsx(PhoneInput, { value: phoneNumber, onChange: setPhoneNumber, disabled: loading })] })) : (jsxs(Fragment, { children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "verificationCode", className: "auth-label", children: "Verification Code" }), jsx(OTPInput, { length: 6, value: verificationCode, onChange: setVerificationCode, disabled: loading })] }), jsx("div", { style: { marginTop: '0.5rem', textAlign: 'center' }, children: jsx("button", { type: "button", className: "auth-link", onClick: handleResendCode, disabled: loading || resendCooldown > 0, style: {
|
|
10807
|
+
: 'Enter your phone number to receive a verification code.' })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), !codeSent ? (jsxs(Fragment, { children: [collectName && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "phoneName", className: "auth-label", children: "Name" }), jsx("input", { id: "phoneName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), className: "auth-input", placeholder: "John Smith", disabled: loading, autoComplete: "name" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "phoneNumber", className: "auth-label", children: "Phone Number" }), jsx(PhoneInput, { value: phoneNumber, onChange: setPhoneNumber, disabled: loading })] })] })) : (jsxs(Fragment, { children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "verificationCode", className: "auth-label", children: "Verification Code" }), jsx(OTPInput, { length: 6, value: verificationCode, onChange: setVerificationCode, disabled: loading })] }), jsx("div", { style: { marginTop: '0.5rem', textAlign: 'center' }, children: jsx("button", { type: "button", className: "auth-link", onClick: handleResendCode, disabled: loading || resendCooldown > 0, style: {
|
|
10807
10808
|
cursor: resendCooldown > 0 ? 'not-allowed' : 'pointer',
|
|
10808
10809
|
opacity: resendCooldown > 0 ? 0.5 : 1,
|
|
10809
10810
|
}, children: resendCooldown > 0
|
|
@@ -10811,7 +10812,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10811
10812
|
: 'Resend code' }) })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : codeSent ? ('Verify Code') : ('Send Code') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to login" }) })] }));
|
|
10812
10813
|
};
|
|
10813
10814
|
|
|
10814
|
-
const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, }) => {
|
|
10815
|
+
const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, resetEmail, }) => {
|
|
10815
10816
|
const [email, setEmail] = useState('');
|
|
10816
10817
|
const [password, setPassword] = useState('');
|
|
10817
10818
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
@@ -10836,23 +10837,24 @@ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successM
|
|
|
10836
10837
|
await onSubmit(email);
|
|
10837
10838
|
}
|
|
10838
10839
|
};
|
|
10839
|
-
if (success) {
|
|
10840
|
+
if (success && !error) {
|
|
10840
10841
|
return (jsxs("div", { className: "auth-form", children: [jsxs("div", { className: "auth-form-header", children: [jsx("div", { style: { textAlign: 'center', marginBottom: '1rem' }, children: jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", fill: "none", style: { margin: '0 auto' }, children: [jsx("circle", { cx: "32", cy: "32", r: "32", fill: "#10b981", fillOpacity: "0.1" }), jsx("path", { d: "M20 32l8 8 16-16", stroke: "#10b981", strokeWidth: "4", strokeLinecap: "round", strokeLinejoin: "round" })] }) }), jsx("h2", { className: "auth-form-title", children: token ? 'Password reset!' : 'Check your email' }), jsx("p", { className: "auth-form-subtitle", children: token
|
|
10841
10842
|
? (successMessage || 'Your password has been successfully reset. You can now sign in with your new password.')
|
|
10842
10843
|
: (successMessage || `We've sent password reset instructions to ${email}`) })] }), jsx("button", { type: "button", className: "auth-button auth-button-primary", onClick: onBack, children: "Back to Sign in" })] }));
|
|
10843
10844
|
}
|
|
10844
10845
|
return (jsxs("form", { className: "auth-form", onSubmit: handleSubmit, children: [jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: token ? 'Set new password' : 'Reset your password' }), jsx("p", { className: "auth-form-subtitle", children: token
|
|
10845
10846
|
? 'Enter your new password below.'
|
|
10846
|
-
: "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error || passwordError] })), token ? (jsxs(Fragment, { children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: loading, placeholder: "Enter new password", autoComplete: "new-password", minLength: 6 })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsx("input", { type: "password", id: "confirmPassword", className: "auth-input", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, disabled: loading, placeholder: "Confirm new password", autoComplete: "new-password", minLength: 6 })] })] })) : (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
|
|
10847
|
+
: "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error || passwordError] })), token ? (jsxs(Fragment, { children: [resetEmail && (jsx("input", { type: "email", value: resetEmail, readOnly: true, autoComplete: "username", style: { position: 'absolute', opacity: 0, height: 0, width: 0, overflow: 'hidden', pointerEvents: 'none' }, tabIndex: -1, "aria-hidden": "true" })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: loading, placeholder: "Enter new password", autoComplete: "new-password", minLength: 6 })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsx("input", { type: "password", id: "confirmPassword", className: "auth-input", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, disabled: loading, placeholder: "Confirm new password", autoComplete: "new-password", minLength: 6 })] })] })) : (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
|
|
10847
10848
|
};
|
|
10848
10849
|
|
|
10849
|
-
const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, }) => {
|
|
10850
|
+
const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, collectName = false, }) => {
|
|
10850
10851
|
const [email, setEmail] = useState('');
|
|
10852
|
+
const [displayName, setDisplayName] = useState('');
|
|
10851
10853
|
const handleSubmit = async (e) => {
|
|
10852
10854
|
e.preventDefault();
|
|
10853
|
-
await onSubmit(email);
|
|
10855
|
+
await onSubmit(email, collectName ? displayName || undefined : undefined);
|
|
10854
10856
|
};
|
|
10855
|
-
return (jsxs("form", { onSubmit: handleSubmit, className: "auth-form", children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "magic-link-email", className: "auth-label", children: "Email Address" }), 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 && (jsx("div", { className: "auth-error-message", children: error })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading || !email, children: loading ? (jsxs(Fragment, { children: [jsx("span", { className: "auth-spinner" }), "Sending..."] })) : ('Send Magic Link') }), jsx("button", { type: "button", onClick: onCancel, className: "auth-button auth-button-secondary", disabled: loading, children: "Cancel" })] }));
|
|
10857
|
+
return (jsxs("form", { onSubmit: handleSubmit, className: "auth-form", children: [collectName && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "magic-link-name", className: "auth-label", children: "Name" }), 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" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "magic-link-email", className: "auth-label", children: "Email Address" }), 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 && (jsx("div", { className: "auth-error-message", children: error })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading || !email, children: loading ? (jsxs(Fragment, { children: [jsx("span", { className: "auth-spinner" }), "Sending..."] })) : ('Send Magic Link') }), jsx("button", { type: "button", onClick: onCancel, className: "auth-button auth-button-secondary", disabled: loading, children: "Cancel" })] }));
|
|
10856
10858
|
};
|
|
10857
10859
|
|
|
10858
10860
|
/**
|
|
@@ -10889,6 +10891,254 @@ function createLoggerWrapper(logger) {
|
|
|
10889
10891
|
};
|
|
10890
10892
|
}
|
|
10891
10893
|
|
|
10894
|
+
/**
|
|
10895
|
+
* Friendly error messages for common HTTP status codes.
|
|
10896
|
+
*/
|
|
10897
|
+
const STATUS_MESSAGES = {
|
|
10898
|
+
400: 'Invalid request. Please check your input and try again.',
|
|
10899
|
+
401: 'Invalid credentials. Please check your email and password.',
|
|
10900
|
+
403: 'Access denied. You do not have permission to perform this action.',
|
|
10901
|
+
404: 'Account not found. Please check your email or create a new account.',
|
|
10902
|
+
409: 'This email is already registered.',
|
|
10903
|
+
429: 'Too many attempts. Please wait a moment and try again.',
|
|
10904
|
+
};
|
|
10905
|
+
/**
|
|
10906
|
+
* Context-specific error messages for different auth operations.
|
|
10907
|
+
*/
|
|
10908
|
+
const ERROR_CODE_MESSAGES = {
|
|
10909
|
+
// 400 - Validation errors
|
|
10910
|
+
'MISSING_FIELDS': 'Email and password are required.',
|
|
10911
|
+
'MISSING_EMAIL': 'Email is required.',
|
|
10912
|
+
'MISSING_PASSWORD': 'Password is required.',
|
|
10913
|
+
'MISSING_TOKEN': 'Token is required.',
|
|
10914
|
+
'MISSING_PHONE_NUMBER': 'Phone number is required.',
|
|
10915
|
+
'MISSING_VERIFICATION_CODE': 'Phone number and verification code are required.',
|
|
10916
|
+
'MISSING_REDIRECT_URL': 'Redirect URL is required.',
|
|
10917
|
+
'MISSING_GOOGLE_TOKEN': 'Google token is required.',
|
|
10918
|
+
'INVALID_CLIENT_ID': 'Invalid client configuration.',
|
|
10919
|
+
'INVALID_REDIRECT_URL': 'Invalid redirect URL.',
|
|
10920
|
+
'INVALID_PHONE_NUMBER': 'Invalid phone number. Please check the format and try again.',
|
|
10921
|
+
'INVALID_GOOGLE_TOKEN': 'Invalid Google sign-in token.',
|
|
10922
|
+
'PASSWORD_TOO_SHORT': 'Password must be at least 8 characters long.',
|
|
10923
|
+
'PASSWORD_REQUIREMENTS_NOT_MET': 'New password must be at least 6 characters.',
|
|
10924
|
+
'EMAIL_ALREADY_VERIFIED': 'Your email is already verified.',
|
|
10925
|
+
'INVALID_CONFIRMATION': 'Please type DELETE to confirm account deletion.',
|
|
10926
|
+
// 401 - Authentication errors
|
|
10927
|
+
'INVALID_CREDENTIALS': 'Invalid email or password.',
|
|
10928
|
+
'INCORRECT_PASSWORD': 'Current password is incorrect.',
|
|
10929
|
+
'INVALID_VERIFICATION_CODE': 'Invalid or expired verification code. Please try again.',
|
|
10930
|
+
'INVALID_TOKEN': 'This link has expired or is invalid. Please request a new one.',
|
|
10931
|
+
'TOKEN_EXPIRED': 'This link has expired. Please request a new one.',
|
|
10932
|
+
'TOKEN_ALREADY_USED': 'This link has already been used. Please request a new one.',
|
|
10933
|
+
'UNAUTHORIZED': 'You must be logged in to perform this action.',
|
|
10934
|
+
'GOOGLE_TOKEN_AUDIENCE_MISMATCH': 'Google sign-in failed. Token was not issued for this application.',
|
|
10935
|
+
// 403 - Forbidden / verification required
|
|
10936
|
+
'EMAIL_NOT_VERIFIED': 'Please verify your email before signing in.',
|
|
10937
|
+
'ACCOUNT_LOCKED': 'Your account has been locked. Please verify your email to unlock.',
|
|
10938
|
+
'EMAIL_VERIFICATION_EXPIRED': 'Your verification deadline has passed and your account is locked. Please contact support.',
|
|
10939
|
+
// 404
|
|
10940
|
+
'USER_NOT_FOUND': 'Account not found. Please check your email or create a new account.',
|
|
10941
|
+
// 409 - Conflicts
|
|
10942
|
+
'EMAIL_ALREADY_EXISTS': 'This email is already registered.',
|
|
10943
|
+
'EMAIL_IN_USE': 'This email is already in use.',
|
|
10944
|
+
// 429 - Rate limiting
|
|
10945
|
+
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
|
|
10946
|
+
'TOO_MANY_MAGIC_LINKS': 'Too many magic link requests. Please try again later.',
|
|
10947
|
+
'TOO_MANY_VERIFICATION_ATTEMPTS': 'Too many verification attempts. Please wait and try again.',
|
|
10948
|
+
'MAX_VERIFICATION_ATTEMPTS': 'Maximum verification attempts reached. Please try again later.',
|
|
10949
|
+
// 500 - Server errors
|
|
10950
|
+
'LOGIN_FAILED': 'Login failed. Please try again later.',
|
|
10951
|
+
'REGISTRATION_FAILED': 'Registration failed. Please try again later.',
|
|
10952
|
+
'GOOGLE_AUTH_NOT_CONFIGURED': 'Google sign-in is not available for this application.',
|
|
10953
|
+
'GOOGLE_AUTH_FAILED': 'Google sign-in failed. Please try again.',
|
|
10954
|
+
'GOOGLE_USERINFO_FAILED': 'Failed to retrieve your Google account information. Please try again.',
|
|
10955
|
+
'PHONE_VERIFICATION_FAILED': 'Phone verification failed. Please try again.',
|
|
10956
|
+
'SEND_VERIFICATION_CODE_FAILED': 'Failed to send verification code. Please try again.',
|
|
10957
|
+
'MAGIC_LINK_SEND_FAILED': 'Failed to send magic link. Please try again.',
|
|
10958
|
+
'MAGIC_LINK_VERIFICATION_FAILED': 'Magic link verification failed. Please try again.',
|
|
10959
|
+
'PASSWORD_RESET_FAILED': 'Failed to process password reset. Please try again.',
|
|
10960
|
+
'PASSWORD_RESET_COMPLETE_FAILED': 'Failed to reset password. Please try again.',
|
|
10961
|
+
'EMAIL_VERIFICATION_SEND_FAILED': 'Failed to send verification email. Please try again.',
|
|
10962
|
+
'EMAIL_VERIFICATION_FAILED': 'Email verification failed. Please try again.',
|
|
10963
|
+
// Account management 500s
|
|
10964
|
+
'UPDATE_PROFILE_FAILED': 'Failed to update profile. Please try again.',
|
|
10965
|
+
'CHANGE_PASSWORD_FAILED': 'Failed to change password. Please try again.',
|
|
10966
|
+
'CHANGE_EMAIL_FAILED': 'Failed to change email. Please try again.',
|
|
10967
|
+
'UPDATE_PHONE_FAILED': 'Failed to update phone number. Please try again.',
|
|
10968
|
+
'DELETE_ACCOUNT_FAILED': 'Failed to delete account. Please try again.',
|
|
10969
|
+
'CONFIG_FETCH_FAILED': 'Failed to load configuration. Please try again.',
|
|
10970
|
+
'INTERNAL_ERROR': 'An unexpected error occurred. Please try again.',
|
|
10971
|
+
// Legacy aliases (kept for backward compatibility)
|
|
10972
|
+
'INVALID_CODE': 'Invalid verification code. Please check and try again.',
|
|
10973
|
+
'CODE_EXPIRED': 'This code has expired. Please request a new one.',
|
|
10974
|
+
'PHONE_NOT_SUPPORTED': 'This phone number is not supported. Please try a different number.',
|
|
10975
|
+
'INVALID_PHONE': 'Invalid phone number. Please check the format and try again.',
|
|
10976
|
+
'PASSWORD_TOO_WEAK': 'Password is too weak. Please use at least 8 characters with a mix of letters and numbers.',
|
|
10977
|
+
'RATE_LIMITED': 'Too many attempts. Please wait a moment and try again.',
|
|
10978
|
+
};
|
|
10979
|
+
function isApiErrorLike(error) {
|
|
10980
|
+
if (error instanceof SmartlinksApiError)
|
|
10981
|
+
return true;
|
|
10982
|
+
if (error && typeof error === 'object' && 'statusCode' in error && 'message' in error) {
|
|
10983
|
+
const e = error;
|
|
10984
|
+
return typeof e.statusCode === 'number' && typeof e.message === 'string';
|
|
10985
|
+
}
|
|
10986
|
+
return false;
|
|
10987
|
+
}
|
|
10988
|
+
/**
|
|
10989
|
+
* Extracts a user-friendly error message from an error.
|
|
10990
|
+
*
|
|
10991
|
+
* Handles:
|
|
10992
|
+
* - SmartlinksApiError (and duck-typed equivalents from proxy mode)
|
|
10993
|
+
* - Standard Error: Uses message property
|
|
10994
|
+
* - String: Passes through directly (for native bridge errors)
|
|
10995
|
+
* - Unknown: Returns generic message
|
|
10996
|
+
*/
|
|
10997
|
+
function getFriendlyErrorMessage(error) {
|
|
10998
|
+
// Handle SmartlinksApiError or duck-typed API errors (proxy mode)
|
|
10999
|
+
if (isApiErrorLike(error)) {
|
|
11000
|
+
// First, check for specific error code (most precise)
|
|
11001
|
+
const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
|
|
11002
|
+
// For rate-limit errors, prefer the backend's message as it includes specific wait times
|
|
11003
|
+
if (errorCode === 'RATE_LIMIT_EXCEEDED' && error.message && error.message !== '[object Object]') {
|
|
11004
|
+
return error.message;
|
|
11005
|
+
}
|
|
11006
|
+
if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
|
|
11007
|
+
return ERROR_CODE_MESSAGES[errorCode];
|
|
11008
|
+
}
|
|
11009
|
+
// Then, check status code for general category messages
|
|
11010
|
+
if (error.statusCode >= 500) {
|
|
11011
|
+
return 'Server error. Please try again later.';
|
|
11012
|
+
}
|
|
11013
|
+
// For 429 status, prefer backend message (may include wait time)
|
|
11014
|
+
if (error.statusCode === 429 && error.message && error.message !== '[object Object]') {
|
|
11015
|
+
return error.message;
|
|
11016
|
+
}
|
|
11017
|
+
if (STATUS_MESSAGES[error.statusCode]) {
|
|
11018
|
+
return STATUS_MESSAGES[error.statusCode];
|
|
11019
|
+
}
|
|
11020
|
+
// Fall back to the server's message (already human-readable from backend)
|
|
11021
|
+
return error.message;
|
|
11022
|
+
}
|
|
11023
|
+
// Handle standard Error objects
|
|
11024
|
+
if (error instanceof Error) {
|
|
11025
|
+
// SDK bug workaround: SDK may do `throw new Error(responseBodyObject)` which produces
|
|
11026
|
+
// message "[object Object]". Check for API error properties attached to the Error instance.
|
|
11027
|
+
const errAny = error;
|
|
11028
|
+
// Check if the Error has API error properties directly attached (e.g., error.statusCode, error.errorCode)
|
|
11029
|
+
if (typeof errAny.statusCode === 'number' || errAny.errorCode || errAny.response) {
|
|
11030
|
+
// Try to extract from attached properties
|
|
11031
|
+
const apiLike = errAny.response || errAny;
|
|
11032
|
+
if (isApiErrorLike(apiLike)) {
|
|
11033
|
+
return getFriendlyErrorMessage(apiLike);
|
|
11034
|
+
}
|
|
11035
|
+
}
|
|
11036
|
+
// Check if the Error has a `cause` with API error details (modern Error cause pattern)
|
|
11037
|
+
if (errAny.cause && typeof errAny.cause === 'object') {
|
|
11038
|
+
if (isApiErrorLike(errAny.cause)) {
|
|
11039
|
+
return getFriendlyErrorMessage(errAny.cause);
|
|
11040
|
+
}
|
|
11041
|
+
}
|
|
11042
|
+
// If the message is "[object Object]", the error was constructed from a plain object
|
|
11043
|
+
// This is useless - return a generic message instead
|
|
11044
|
+
if (error.message === '[object Object]') {
|
|
11045
|
+
// Log the actual error for debugging
|
|
11046
|
+
console.warn('[AuthKit] Error with [object Object] message. Raw error:', JSON.stringify(errAny, Object.getOwnPropertyNames(errAny)));
|
|
11047
|
+
return 'An unexpected error occurred. Please try again.';
|
|
11048
|
+
}
|
|
11049
|
+
// Check if the message itself contains a known API error pattern
|
|
11050
|
+
if (/already (registered|exists)/i.test(error.message)) {
|
|
11051
|
+
return 'This email is already registered.';
|
|
11052
|
+
}
|
|
11053
|
+
return error.message;
|
|
11054
|
+
}
|
|
11055
|
+
// Handle plain strings (e.g., from native bridge callbacks)
|
|
11056
|
+
if (typeof error === 'string') {
|
|
11057
|
+
return error;
|
|
11058
|
+
}
|
|
11059
|
+
// Unknown error type
|
|
11060
|
+
return 'An unexpected error occurred. Please try again.';
|
|
11061
|
+
}
|
|
11062
|
+
/**
|
|
11063
|
+
* Checks if an error represents a conflict (409) - typically duplicate registration.
|
|
11064
|
+
*/
|
|
11065
|
+
function isConflictError(error) {
|
|
11066
|
+
if (isApiErrorLike(error))
|
|
11067
|
+
return error.statusCode === 409;
|
|
11068
|
+
// Also check error message for keyword match (resilient fallback)
|
|
11069
|
+
if (error instanceof Error && /already (registered|exists)/i.test(error.message))
|
|
11070
|
+
return true;
|
|
11071
|
+
return false;
|
|
11072
|
+
}
|
|
11073
|
+
/**
|
|
11074
|
+
* Checks if an error represents invalid credentials (401).
|
|
11075
|
+
*/
|
|
11076
|
+
function isAuthError(error) {
|
|
11077
|
+
if (error instanceof SmartlinksApiError)
|
|
11078
|
+
return error.isAuthError();
|
|
11079
|
+
if (isApiErrorLike(error))
|
|
11080
|
+
return error.statusCode === 401 || error.statusCode === 403;
|
|
11081
|
+
return false;
|
|
11082
|
+
}
|
|
11083
|
+
/**
|
|
11084
|
+
* Checks if an error represents rate limiting (429).
|
|
11085
|
+
*/
|
|
11086
|
+
function isRateLimitError(error) {
|
|
11087
|
+
if (error instanceof SmartlinksApiError)
|
|
11088
|
+
return error.isRateLimited();
|
|
11089
|
+
if (isApiErrorLike(error))
|
|
11090
|
+
return error.statusCode === 429;
|
|
11091
|
+
return false;
|
|
11092
|
+
}
|
|
11093
|
+
/**
|
|
11094
|
+
* Checks if an error represents a server error (5xx).
|
|
11095
|
+
*/
|
|
11096
|
+
function isServerError(error) {
|
|
11097
|
+
if (error instanceof SmartlinksApiError)
|
|
11098
|
+
return error.isServerError();
|
|
11099
|
+
if (isApiErrorLike(error))
|
|
11100
|
+
return error.statusCode >= 500;
|
|
11101
|
+
return false;
|
|
11102
|
+
}
|
|
11103
|
+
/**
|
|
11104
|
+
* Gets the HTTP status code from an error, if available.
|
|
11105
|
+
*/
|
|
11106
|
+
function getErrorStatusCode(error) {
|
|
11107
|
+
if (isApiErrorLike(error))
|
|
11108
|
+
return error.statusCode;
|
|
11109
|
+
return undefined;
|
|
11110
|
+
}
|
|
11111
|
+
/**
|
|
11112
|
+
* Gets the server-specific error code from an error, if available.
|
|
11113
|
+
*/
|
|
11114
|
+
function getErrorCode(error) {
|
|
11115
|
+
if (isApiErrorLike(error)) {
|
|
11116
|
+
return error.errorCode || error.details?.errorCode || error.details?.error;
|
|
11117
|
+
}
|
|
11118
|
+
return undefined;
|
|
11119
|
+
}
|
|
11120
|
+
/**
|
|
11121
|
+
* Error codes that indicate the user needs to verify their email.
|
|
11122
|
+
*/
|
|
11123
|
+
const EMAIL_VERIFICATION_ERROR_CODES = new Set([
|
|
11124
|
+
'EMAIL_NOT_VERIFIED',
|
|
11125
|
+
'ACCOUNT_LOCKED',
|
|
11126
|
+
'EMAIL_VERIFICATION_EXPIRED',
|
|
11127
|
+
]);
|
|
11128
|
+
/**
|
|
11129
|
+
* Checks if an error requires email verification action from the user.
|
|
11130
|
+
*/
|
|
11131
|
+
function requiresEmailVerification(error) {
|
|
11132
|
+
const code = getErrorCode(error);
|
|
11133
|
+
if (code && EMAIL_VERIFICATION_ERROR_CODES.has(code))
|
|
11134
|
+
return true;
|
|
11135
|
+
// Also check the flag from the response body
|
|
11136
|
+
if (error && typeof error === 'object' && 'requiresEmailVerification' in error) {
|
|
11137
|
+
return error.requiresEmailVerification === true;
|
|
11138
|
+
}
|
|
11139
|
+
return false;
|
|
11140
|
+
}
|
|
11141
|
+
|
|
10892
11142
|
/**
|
|
10893
11143
|
* AuthAPI - Thin wrapper around Smartlinks SDK authKit namespace
|
|
10894
11144
|
* All authentication operations now use the global Smartlinks SDK
|
|
@@ -10949,14 +11199,46 @@ class AuthAPI {
|
|
|
10949
11199
|
return smartlinks.authKit.verifyPhoneCode(this.clientId, phoneNumber, code);
|
|
10950
11200
|
}
|
|
10951
11201
|
async requestPasswordReset(email, redirectUrl) {
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
11202
|
+
console.log('[AuthKit:API] requestPasswordReset called', { clientId: this.clientId, email, redirectUrl });
|
|
11203
|
+
try {
|
|
11204
|
+
const result = await smartlinks.authKit.requestPasswordReset(this.clientId, {
|
|
11205
|
+
email,
|
|
11206
|
+
redirectUrl,
|
|
11207
|
+
clientName: this.clientName
|
|
11208
|
+
});
|
|
11209
|
+
console.log('[AuthKit:API] requestPasswordReset resolved:', JSON.stringify(result));
|
|
11210
|
+
return result;
|
|
11211
|
+
}
|
|
11212
|
+
catch (error) {
|
|
11213
|
+
const err = error;
|
|
11214
|
+
console.error('[AuthKit:API] requestPasswordReset threw:', {
|
|
11215
|
+
type: typeof error,
|
|
11216
|
+
message: err?.message,
|
|
11217
|
+
statusCode: err?.statusCode ?? err?.response?.status,
|
|
11218
|
+
errorCode: err?.errorCode ?? err?.details?.errorCode,
|
|
11219
|
+
details: err?.details ?? err?.response?.data,
|
|
11220
|
+
keys: err ? Object.keys(err) : [],
|
|
11221
|
+
});
|
|
11222
|
+
throw error;
|
|
11223
|
+
}
|
|
10957
11224
|
}
|
|
10958
11225
|
async verifyResetToken(token) {
|
|
10959
|
-
|
|
11226
|
+
console.log('[AuthKit:API] verifyResetToken called');
|
|
11227
|
+
try {
|
|
11228
|
+
const result = await smartlinks.authKit.verifyResetToken(this.clientId, token);
|
|
11229
|
+
console.log('[AuthKit:API] verifyResetToken resolved:', JSON.stringify(result));
|
|
11230
|
+
return result;
|
|
11231
|
+
}
|
|
11232
|
+
catch (error) {
|
|
11233
|
+
const err = error;
|
|
11234
|
+
console.error('[AuthKit:API] verifyResetToken threw:', {
|
|
11235
|
+
type: typeof error,
|
|
11236
|
+
message: err?.message,
|
|
11237
|
+
statusCode: err?.statusCode ?? err?.response?.status,
|
|
11238
|
+
errorCode: err?.errorCode ?? err?.details?.errorCode,
|
|
11239
|
+
});
|
|
11240
|
+
throw error;
|
|
11241
|
+
}
|
|
10960
11242
|
}
|
|
10961
11243
|
async completePasswordReset(token, newPassword) {
|
|
10962
11244
|
return smartlinks.authKit.completePasswordReset(this.clientId, token, newPassword);
|
|
@@ -10992,7 +11274,6 @@ class AuthAPI {
|
|
|
10992
11274
|
this.log.warn('Failed to fetch UI config, using defaults:', error);
|
|
10993
11275
|
return {
|
|
10994
11276
|
branding: {
|
|
10995
|
-
title: 'Smartlinks Auth',
|
|
10996
11277
|
subtitle: 'Sign in to your account',
|
|
10997
11278
|
primaryColor: '#3B82F6',
|
|
10998
11279
|
secondaryColor: '#1D4ED8',
|
|
@@ -11003,6 +11284,32 @@ class AuthAPI {
|
|
|
11003
11284
|
};
|
|
11004
11285
|
}
|
|
11005
11286
|
}
|
|
11287
|
+
/**
|
|
11288
|
+
* Ensure an account exists for the given email (or phone).
|
|
11289
|
+
* Calls register and silently handles 409 (account already exists).
|
|
11290
|
+
* This enables passwordless flows (magic link, phone) to work for new users.
|
|
11291
|
+
*/
|
|
11292
|
+
async ensureAccount(data) {
|
|
11293
|
+
try {
|
|
11294
|
+
// Generate a random password since passwordless users won't use it
|
|
11295
|
+
const randomPassword = crypto.randomUUID?.() || Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
11296
|
+
await this.register({
|
|
11297
|
+
email: data.email,
|
|
11298
|
+
password: randomPassword,
|
|
11299
|
+
displayName: data.displayName,
|
|
11300
|
+
});
|
|
11301
|
+
this.log.log('ensureAccount: new account created for', data.email || data.phoneNumber);
|
|
11302
|
+
}
|
|
11303
|
+
catch (err) {
|
|
11304
|
+
// 409 = account already exists, which is fine
|
|
11305
|
+
if (isConflictError(err)) {
|
|
11306
|
+
this.log.log('ensureAccount: account already exists for', data.email || data.phoneNumber);
|
|
11307
|
+
return;
|
|
11308
|
+
}
|
|
11309
|
+
// Re-throw any other error
|
|
11310
|
+
throw err;
|
|
11311
|
+
}
|
|
11312
|
+
}
|
|
11006
11313
|
async sendMagicLink(email, redirectUrl) {
|
|
11007
11314
|
return smartlinks.authKit.sendMagicLink(this.clientId, {
|
|
11008
11315
|
email,
|
|
@@ -12160,368 +12467,128 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12160
12467
|
}, [getAccount]);
|
|
12161
12468
|
const clearAccountCache = useCallback(async () => {
|
|
12162
12469
|
if (!proxyMode) {
|
|
12163
|
-
await tokenStorage.clearAccountInfo();
|
|
12164
|
-
}
|
|
12165
|
-
setAccountInfo(null);
|
|
12166
|
-
}, [proxyMode]);
|
|
12167
|
-
const onAuthStateChange = useCallback((callback) => {
|
|
12168
|
-
callbacksRef.current.add(callback);
|
|
12169
|
-
return () => {
|
|
12170
|
-
callbacksRef.current.delete(callback);
|
|
12171
|
-
};
|
|
12172
|
-
}, []);
|
|
12173
|
-
const retryVerification = useCallback(async () => {
|
|
12174
|
-
if (!token || !user)
|
|
12175
|
-
return false;
|
|
12176
|
-
if (isVerified)
|
|
12177
|
-
return true;
|
|
12178
|
-
try {
|
|
12179
|
-
await smartlinks.auth.verifyToken(token);
|
|
12180
|
-
setIsVerified(true);
|
|
12181
|
-
pendingVerificationRef.current = false;
|
|
12182
|
-
notifyAuthStateChange('SESSION_VERIFIED', user, token, accountData, accountInfo, true, contact, contactId);
|
|
12183
|
-
return true;
|
|
12184
|
-
}
|
|
12185
|
-
catch (err) {
|
|
12186
|
-
if (isNetworkError(err)) {
|
|
12187
|
-
return false;
|
|
12188
|
-
}
|
|
12189
|
-
else {
|
|
12190
|
-
await logout();
|
|
12191
|
-
return false;
|
|
12192
|
-
}
|
|
12193
|
-
}
|
|
12194
|
-
}, [token, user, isVerified, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
|
|
12195
|
-
// Online/offline event listener for auto-retry verification
|
|
12196
|
-
useEffect(() => {
|
|
12197
|
-
if (proxyMode)
|
|
12198
|
-
return;
|
|
12199
|
-
const handleOnline = () => {
|
|
12200
|
-
setIsOnline(true);
|
|
12201
|
-
if (pendingVerificationRef.current && token && user) {
|
|
12202
|
-
retryVerification();
|
|
12203
|
-
}
|
|
12204
|
-
};
|
|
12205
|
-
const handleOffline = () => {
|
|
12206
|
-
setIsOnline(false);
|
|
12207
|
-
};
|
|
12208
|
-
window.addEventListener('online', handleOnline);
|
|
12209
|
-
window.addEventListener('offline', handleOffline);
|
|
12210
|
-
return () => {
|
|
12211
|
-
window.removeEventListener('online', handleOnline);
|
|
12212
|
-
window.removeEventListener('offline', handleOffline);
|
|
12213
|
-
};
|
|
12214
|
-
}, [proxyMode, token, user, retryVerification]);
|
|
12215
|
-
// Automatic background token refresh
|
|
12216
|
-
useEffect(() => {
|
|
12217
|
-
if (proxyMode || !enableAutoRefresh || !token || !user) {
|
|
12218
|
-
return;
|
|
12219
|
-
}
|
|
12220
|
-
const checkAndRefresh = async () => {
|
|
12221
|
-
try {
|
|
12222
|
-
const storedToken = await tokenStorage.getToken();
|
|
12223
|
-
if (!storedToken?.expiresAt)
|
|
12224
|
-
return;
|
|
12225
|
-
const now = Date.now();
|
|
12226
|
-
const remainingMs = storedToken.expiresAt - now;
|
|
12227
|
-
const totalLifetimeMs = 7 * 24 * 60 * 60 * 1000;
|
|
12228
|
-
const percentUsed = totalLifetimeMs > 0 ? ((totalLifetimeMs - remainingMs) / totalLifetimeMs) * 100 : 100;
|
|
12229
|
-
if (percentUsed >= refreshThresholdPercent) {
|
|
12230
|
-
try {
|
|
12231
|
-
await refreshToken();
|
|
12232
|
-
}
|
|
12233
|
-
catch (refreshError) {
|
|
12234
|
-
// Don't logout on refresh failure
|
|
12235
|
-
}
|
|
12236
|
-
}
|
|
12237
|
-
}
|
|
12238
|
-
catch (error) {
|
|
12239
|
-
console.error('[AuthContext] Error checking token for refresh:', error);
|
|
12240
|
-
}
|
|
12241
|
-
};
|
|
12242
|
-
checkAndRefresh();
|
|
12243
|
-
const intervalId = setInterval(checkAndRefresh, refreshCheckInterval);
|
|
12244
|
-
return () => {
|
|
12245
|
-
clearInterval(intervalId);
|
|
12246
|
-
};
|
|
12247
|
-
}, [proxyMode, enableAutoRefresh, refreshCheckInterval, refreshThresholdPercent, token, user, refreshToken]);
|
|
12248
|
-
const value = {
|
|
12249
|
-
user,
|
|
12250
|
-
token,
|
|
12251
|
-
accountData,
|
|
12252
|
-
accountInfo,
|
|
12253
|
-
isAuthenticated: !!user,
|
|
12254
|
-
isVerified,
|
|
12255
|
-
isLoading,
|
|
12256
|
-
isOnline,
|
|
12257
|
-
proxyMode,
|
|
12258
|
-
contact,
|
|
12259
|
-
contactId,
|
|
12260
|
-
getContact,
|
|
12261
|
-
updateContactCustomFields,
|
|
12262
|
-
login,
|
|
12263
|
-
logout,
|
|
12264
|
-
getToken,
|
|
12265
|
-
getTokenInfo,
|
|
12266
|
-
refreshToken,
|
|
12267
|
-
getAccount,
|
|
12268
|
-
refreshAccount,
|
|
12269
|
-
clearAccountCache,
|
|
12270
|
-
onAuthStateChange,
|
|
12271
|
-
retryVerification,
|
|
12272
|
-
};
|
|
12273
|
-
return jsx(AuthContext.Provider, { value: value, children: children });
|
|
12274
|
-
};
|
|
12275
|
-
const useAuth = () => {
|
|
12276
|
-
const context = useContext(AuthContext);
|
|
12277
|
-
if (context === undefined) {
|
|
12278
|
-
throw new Error('useAuth must be used within an AuthProvider');
|
|
12279
|
-
}
|
|
12280
|
-
return context;
|
|
12281
|
-
};
|
|
12282
|
-
|
|
12283
|
-
/**
|
|
12284
|
-
* Friendly error messages for common HTTP status codes.
|
|
12285
|
-
*/
|
|
12286
|
-
const STATUS_MESSAGES = {
|
|
12287
|
-
400: 'Invalid request. Please check your input and try again.',
|
|
12288
|
-
401: 'Invalid credentials. Please check your email and password.',
|
|
12289
|
-
403: 'Access denied. You do not have permission to perform this action.',
|
|
12290
|
-
404: 'Account not found. Please check your email or create a new account.',
|
|
12291
|
-
409: 'This email is already registered.',
|
|
12292
|
-
429: 'Too many attempts. Please wait a moment and try again.',
|
|
12293
|
-
};
|
|
12294
|
-
/**
|
|
12295
|
-
* Context-specific error messages for different auth operations.
|
|
12296
|
-
*/
|
|
12297
|
-
const ERROR_CODE_MESSAGES = {
|
|
12298
|
-
// 400 - Validation errors
|
|
12299
|
-
'MISSING_FIELDS': 'Email and password are required.',
|
|
12300
|
-
'MISSING_EMAIL': 'Email is required.',
|
|
12301
|
-
'MISSING_PASSWORD': 'Password is required.',
|
|
12302
|
-
'MISSING_TOKEN': 'Token is required.',
|
|
12303
|
-
'MISSING_PHONE_NUMBER': 'Phone number is required.',
|
|
12304
|
-
'MISSING_VERIFICATION_CODE': 'Phone number and verification code are required.',
|
|
12305
|
-
'MISSING_REDIRECT_URL': 'Redirect URL is required.',
|
|
12306
|
-
'MISSING_GOOGLE_TOKEN': 'Google token is required.',
|
|
12307
|
-
'INVALID_CLIENT_ID': 'Invalid client configuration.',
|
|
12308
|
-
'INVALID_REDIRECT_URL': 'Invalid redirect URL.',
|
|
12309
|
-
'INVALID_PHONE_NUMBER': 'Invalid phone number. Please check the format and try again.',
|
|
12310
|
-
'INVALID_GOOGLE_TOKEN': 'Invalid Google sign-in token.',
|
|
12311
|
-
'PASSWORD_TOO_SHORT': 'Password must be at least 8 characters long.',
|
|
12312
|
-
'PASSWORD_REQUIREMENTS_NOT_MET': 'New password must be at least 6 characters.',
|
|
12313
|
-
'EMAIL_ALREADY_VERIFIED': 'Your email is already verified.',
|
|
12314
|
-
'INVALID_CONFIRMATION': 'Please type DELETE to confirm account deletion.',
|
|
12315
|
-
// 401 - Authentication errors
|
|
12316
|
-
'INVALID_CREDENTIALS': 'Invalid email or password.',
|
|
12317
|
-
'INCORRECT_PASSWORD': 'Current password is incorrect.',
|
|
12318
|
-
'INVALID_VERIFICATION_CODE': 'Invalid or expired verification code. Please try again.',
|
|
12319
|
-
'INVALID_TOKEN': 'This link has expired or is invalid. Please request a new one.',
|
|
12320
|
-
'TOKEN_EXPIRED': 'This link has expired. Please request a new one.',
|
|
12321
|
-
'TOKEN_ALREADY_USED': 'This link has already been used. Please request a new one.',
|
|
12322
|
-
'UNAUTHORIZED': 'You must be logged in to perform this action.',
|
|
12323
|
-
'GOOGLE_TOKEN_AUDIENCE_MISMATCH': 'Google sign-in failed. Token was not issued for this application.',
|
|
12324
|
-
// 403 - Forbidden / verification required
|
|
12325
|
-
'EMAIL_NOT_VERIFIED': 'Please verify your email before signing in.',
|
|
12326
|
-
'ACCOUNT_LOCKED': 'Your account has been locked. Please verify your email to unlock.',
|
|
12327
|
-
'EMAIL_VERIFICATION_EXPIRED': 'Your verification deadline has passed and your account is locked. Please contact support.',
|
|
12328
|
-
// 404
|
|
12329
|
-
'USER_NOT_FOUND': 'Account not found. Please check your email or create a new account.',
|
|
12330
|
-
// 409 - Conflicts
|
|
12331
|
-
'EMAIL_ALREADY_EXISTS': 'This email is already registered.',
|
|
12332
|
-
'EMAIL_IN_USE': 'This email is already in use.',
|
|
12333
|
-
// 429 - Rate limiting
|
|
12334
|
-
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
|
|
12335
|
-
'TOO_MANY_MAGIC_LINKS': 'Too many magic link requests. Please try again later.',
|
|
12336
|
-
'TOO_MANY_VERIFICATION_ATTEMPTS': 'Too many verification attempts. Please wait and try again.',
|
|
12337
|
-
'MAX_VERIFICATION_ATTEMPTS': 'Maximum verification attempts reached. Please try again later.',
|
|
12338
|
-
// 500 - Server errors
|
|
12339
|
-
'LOGIN_FAILED': 'Login failed. Please try again later.',
|
|
12340
|
-
'REGISTRATION_FAILED': 'Registration failed. Please try again later.',
|
|
12341
|
-
'GOOGLE_AUTH_NOT_CONFIGURED': 'Google sign-in is not available for this application.',
|
|
12342
|
-
'GOOGLE_AUTH_FAILED': 'Google sign-in failed. Please try again.',
|
|
12343
|
-
'GOOGLE_USERINFO_FAILED': 'Failed to retrieve your Google account information. Please try again.',
|
|
12344
|
-
'PHONE_VERIFICATION_FAILED': 'Phone verification failed. Please try again.',
|
|
12345
|
-
'SEND_VERIFICATION_CODE_FAILED': 'Failed to send verification code. Please try again.',
|
|
12346
|
-
'MAGIC_LINK_SEND_FAILED': 'Failed to send magic link. Please try again.',
|
|
12347
|
-
'MAGIC_LINK_VERIFICATION_FAILED': 'Magic link verification failed. Please try again.',
|
|
12348
|
-
'PASSWORD_RESET_FAILED': 'Failed to process password reset. Please try again.',
|
|
12349
|
-
'PASSWORD_RESET_COMPLETE_FAILED': 'Failed to reset password. Please try again.',
|
|
12350
|
-
'EMAIL_VERIFICATION_SEND_FAILED': 'Failed to send verification email. Please try again.',
|
|
12351
|
-
'EMAIL_VERIFICATION_FAILED': 'Email verification failed. Please try again.',
|
|
12352
|
-
// Account management 500s
|
|
12353
|
-
'UPDATE_PROFILE_FAILED': 'Failed to update profile. Please try again.',
|
|
12354
|
-
'CHANGE_PASSWORD_FAILED': 'Failed to change password. Please try again.',
|
|
12355
|
-
'CHANGE_EMAIL_FAILED': 'Failed to change email. Please try again.',
|
|
12356
|
-
'UPDATE_PHONE_FAILED': 'Failed to update phone number. Please try again.',
|
|
12357
|
-
'DELETE_ACCOUNT_FAILED': 'Failed to delete account. Please try again.',
|
|
12358
|
-
'CONFIG_FETCH_FAILED': 'Failed to load configuration. Please try again.',
|
|
12359
|
-
'INTERNAL_ERROR': 'An unexpected error occurred. Please try again.',
|
|
12360
|
-
// Legacy aliases (kept for backward compatibility)
|
|
12361
|
-
'INVALID_CODE': 'Invalid verification code. Please check and try again.',
|
|
12362
|
-
'CODE_EXPIRED': 'This code has expired. Please request a new one.',
|
|
12363
|
-
'PHONE_NOT_SUPPORTED': 'This phone number is not supported. Please try a different number.',
|
|
12364
|
-
'INVALID_PHONE': 'Invalid phone number. Please check the format and try again.',
|
|
12365
|
-
'PASSWORD_TOO_WEAK': 'Password is too weak. Please use at least 8 characters with a mix of letters and numbers.',
|
|
12366
|
-
'RATE_LIMITED': 'Too many attempts. Please wait a moment and try again.',
|
|
12367
|
-
};
|
|
12368
|
-
function isApiErrorLike(error) {
|
|
12369
|
-
if (error instanceof SmartlinksApiError)
|
|
12370
|
-
return true;
|
|
12371
|
-
if (error && typeof error === 'object' && 'statusCode' in error && 'message' in error) {
|
|
12372
|
-
const e = error;
|
|
12373
|
-
return typeof e.statusCode === 'number' && typeof e.message === 'string';
|
|
12374
|
-
}
|
|
12375
|
-
return false;
|
|
12376
|
-
}
|
|
12377
|
-
/**
|
|
12378
|
-
* Extracts a user-friendly error message from an error.
|
|
12379
|
-
*
|
|
12380
|
-
* Handles:
|
|
12381
|
-
* - SmartlinksApiError (and duck-typed equivalents from proxy mode)
|
|
12382
|
-
* - Standard Error: Uses message property
|
|
12383
|
-
* - String: Passes through directly (for native bridge errors)
|
|
12384
|
-
* - Unknown: Returns generic message
|
|
12385
|
-
*/
|
|
12386
|
-
function getFriendlyErrorMessage(error) {
|
|
12387
|
-
// Handle SmartlinksApiError or duck-typed API errors (proxy mode)
|
|
12388
|
-
if (isApiErrorLike(error)) {
|
|
12389
|
-
// First, check for specific error code (most precise)
|
|
12390
|
-
const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12391
|
-
if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
|
|
12392
|
-
return ERROR_CODE_MESSAGES[errorCode];
|
|
12393
|
-
}
|
|
12394
|
-
// Then, check status code for general category messages
|
|
12395
|
-
if (error.statusCode >= 500) {
|
|
12396
|
-
return 'Server error. Please try again later.';
|
|
12470
|
+
await tokenStorage.clearAccountInfo();
|
|
12397
12471
|
}
|
|
12398
|
-
|
|
12399
|
-
|
|
12472
|
+
setAccountInfo(null);
|
|
12473
|
+
}, [proxyMode]);
|
|
12474
|
+
const onAuthStateChange = useCallback((callback) => {
|
|
12475
|
+
callbacksRef.current.add(callback);
|
|
12476
|
+
return () => {
|
|
12477
|
+
callbacksRef.current.delete(callback);
|
|
12478
|
+
};
|
|
12479
|
+
}, []);
|
|
12480
|
+
const retryVerification = useCallback(async () => {
|
|
12481
|
+
if (!token || !user)
|
|
12482
|
+
return false;
|
|
12483
|
+
if (isVerified)
|
|
12484
|
+
return true;
|
|
12485
|
+
try {
|
|
12486
|
+
await smartlinks.auth.verifyToken(token);
|
|
12487
|
+
setIsVerified(true);
|
|
12488
|
+
pendingVerificationRef.current = false;
|
|
12489
|
+
notifyAuthStateChange('SESSION_VERIFIED', user, token, accountData, accountInfo, true, contact, contactId);
|
|
12490
|
+
return true;
|
|
12400
12491
|
}
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
// Handle standard Error objects
|
|
12405
|
-
if (error instanceof Error) {
|
|
12406
|
-
// SDK bug workaround: SDK may do `throw new Error(responseBodyObject)` which produces
|
|
12407
|
-
// message "[object Object]". Check for API error properties attached to the Error instance.
|
|
12408
|
-
const errAny = error;
|
|
12409
|
-
// Check if the Error has API error properties directly attached (e.g., error.statusCode, error.errorCode)
|
|
12410
|
-
if (typeof errAny.statusCode === 'number' || errAny.errorCode || errAny.response) {
|
|
12411
|
-
// Try to extract from attached properties
|
|
12412
|
-
const apiLike = errAny.response || errAny;
|
|
12413
|
-
if (isApiErrorLike(apiLike)) {
|
|
12414
|
-
return getFriendlyErrorMessage(apiLike);
|
|
12492
|
+
catch (err) {
|
|
12493
|
+
if (isNetworkError(err)) {
|
|
12494
|
+
return false;
|
|
12415
12495
|
}
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
if (isApiErrorLike(errAny.cause)) {
|
|
12420
|
-
return getFriendlyErrorMessage(errAny.cause);
|
|
12496
|
+
else {
|
|
12497
|
+
await logout();
|
|
12498
|
+
return false;
|
|
12421
12499
|
}
|
|
12422
12500
|
}
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12501
|
+
}, [token, user, isVerified, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
|
|
12502
|
+
// Online/offline event listener for auto-retry verification
|
|
12503
|
+
useEffect(() => {
|
|
12504
|
+
if (proxyMode)
|
|
12505
|
+
return;
|
|
12506
|
+
const handleOnline = () => {
|
|
12507
|
+
setIsOnline(true);
|
|
12508
|
+
if (pendingVerificationRef.current && token && user) {
|
|
12509
|
+
retryVerification();
|
|
12510
|
+
}
|
|
12511
|
+
};
|
|
12512
|
+
const handleOffline = () => {
|
|
12513
|
+
setIsOnline(false);
|
|
12514
|
+
};
|
|
12515
|
+
window.addEventListener('online', handleOnline);
|
|
12516
|
+
window.addEventListener('offline', handleOffline);
|
|
12517
|
+
return () => {
|
|
12518
|
+
window.removeEventListener('online', handleOnline);
|
|
12519
|
+
window.removeEventListener('offline', handleOffline);
|
|
12520
|
+
};
|
|
12521
|
+
}, [proxyMode, token, user, retryVerification]);
|
|
12522
|
+
// Automatic background token refresh
|
|
12523
|
+
useEffect(() => {
|
|
12524
|
+
if (proxyMode || !enableAutoRefresh || !token || !user) {
|
|
12525
|
+
return;
|
|
12433
12526
|
}
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
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
|
-
* Gets the server-specific error code from an error, if available.
|
|
12494
|
-
*/
|
|
12495
|
-
function getErrorCode(error) {
|
|
12496
|
-
if (isApiErrorLike(error)) {
|
|
12497
|
-
return error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12498
|
-
}
|
|
12499
|
-
return undefined;
|
|
12500
|
-
}
|
|
12501
|
-
/**
|
|
12502
|
-
* Error codes that indicate the user needs to verify their email.
|
|
12503
|
-
*/
|
|
12504
|
-
const EMAIL_VERIFICATION_ERROR_CODES = new Set([
|
|
12505
|
-
'EMAIL_NOT_VERIFIED',
|
|
12506
|
-
'ACCOUNT_LOCKED',
|
|
12507
|
-
'EMAIL_VERIFICATION_EXPIRED',
|
|
12508
|
-
]);
|
|
12509
|
-
/**
|
|
12510
|
-
* Checks if an error requires email verification action from the user.
|
|
12511
|
-
*/
|
|
12512
|
-
function requiresEmailVerification(error) {
|
|
12513
|
-
const code = getErrorCode(error);
|
|
12514
|
-
if (code && EMAIL_VERIFICATION_ERROR_CODES.has(code))
|
|
12515
|
-
return true;
|
|
12516
|
-
// Also check the flag from the response body
|
|
12517
|
-
if (error && typeof error === 'object' && 'requiresEmailVerification' in error) {
|
|
12518
|
-
return error.requiresEmailVerification === true;
|
|
12527
|
+
const checkAndRefresh = async () => {
|
|
12528
|
+
try {
|
|
12529
|
+
const storedToken = await tokenStorage.getToken();
|
|
12530
|
+
if (!storedToken?.expiresAt)
|
|
12531
|
+
return;
|
|
12532
|
+
const now = Date.now();
|
|
12533
|
+
const remainingMs = storedToken.expiresAt - now;
|
|
12534
|
+
const totalLifetimeMs = 7 * 24 * 60 * 60 * 1000;
|
|
12535
|
+
const percentUsed = totalLifetimeMs > 0 ? ((totalLifetimeMs - remainingMs) / totalLifetimeMs) * 100 : 100;
|
|
12536
|
+
if (percentUsed >= refreshThresholdPercent) {
|
|
12537
|
+
try {
|
|
12538
|
+
await refreshToken();
|
|
12539
|
+
}
|
|
12540
|
+
catch (refreshError) {
|
|
12541
|
+
// Don't logout on refresh failure
|
|
12542
|
+
}
|
|
12543
|
+
}
|
|
12544
|
+
}
|
|
12545
|
+
catch (error) {
|
|
12546
|
+
console.error('[AuthContext] Error checking token for refresh:', error);
|
|
12547
|
+
}
|
|
12548
|
+
};
|
|
12549
|
+
checkAndRefresh();
|
|
12550
|
+
const intervalId = setInterval(checkAndRefresh, refreshCheckInterval);
|
|
12551
|
+
return () => {
|
|
12552
|
+
clearInterval(intervalId);
|
|
12553
|
+
};
|
|
12554
|
+
}, [proxyMode, enableAutoRefresh, refreshCheckInterval, refreshThresholdPercent, token, user, refreshToken]);
|
|
12555
|
+
const value = {
|
|
12556
|
+
user,
|
|
12557
|
+
token,
|
|
12558
|
+
accountData,
|
|
12559
|
+
accountInfo,
|
|
12560
|
+
isAuthenticated: !!user,
|
|
12561
|
+
isVerified,
|
|
12562
|
+
isLoading,
|
|
12563
|
+
isOnline,
|
|
12564
|
+
proxyMode,
|
|
12565
|
+
contact,
|
|
12566
|
+
contactId,
|
|
12567
|
+
getContact,
|
|
12568
|
+
updateContactCustomFields,
|
|
12569
|
+
login,
|
|
12570
|
+
logout,
|
|
12571
|
+
getToken,
|
|
12572
|
+
getTokenInfo,
|
|
12573
|
+
refreshToken,
|
|
12574
|
+
getAccount,
|
|
12575
|
+
refreshAccount,
|
|
12576
|
+
clearAccountCache,
|
|
12577
|
+
onAuthStateChange,
|
|
12578
|
+
retryVerification,
|
|
12579
|
+
};
|
|
12580
|
+
return jsx(AuthContext.Provider, { value: value, children: children });
|
|
12581
|
+
};
|
|
12582
|
+
const useAuth = () => {
|
|
12583
|
+
const context = useContext(AuthContext);
|
|
12584
|
+
if (context === undefined) {
|
|
12585
|
+
throw new Error('useAuth must be used within an AuthProvider');
|
|
12519
12586
|
}
|
|
12520
|
-
return
|
|
12521
|
-
}
|
|
12587
|
+
return context;
|
|
12588
|
+
};
|
|
12522
12589
|
|
|
12523
12590
|
// VERSION: Update this when making changes to help identify which version is running
|
|
12524
|
-
const AUTH_UI_VERSION = '
|
|
12591
|
+
const AUTH_UI_VERSION = '46';
|
|
12525
12592
|
const LOG_PREFIX = `[SmartlinksAuthUI:v${AUTH_UI_VERSION}]`;
|
|
12526
12593
|
// Helper to check for URL auth params synchronously (runs during initialization)
|
|
12527
12594
|
// This prevents the form from flashing before detecting deep-link flows
|
|
@@ -12545,6 +12612,24 @@ const getExpirationFromResponse = (response) => {
|
|
|
12545
12612
|
return Date.now() + response.expiresIn;
|
|
12546
12613
|
return undefined; // Will use 7-day default in tokenStorage
|
|
12547
12614
|
};
|
|
12615
|
+
const getActionResultErrorMessage = (result) => {
|
|
12616
|
+
if (!result || typeof result !== 'object')
|
|
12617
|
+
return null;
|
|
12618
|
+
const candidate = result;
|
|
12619
|
+
const numericStatusCode = typeof candidate.statusCode === 'string'
|
|
12620
|
+
? Number(candidate.statusCode)
|
|
12621
|
+
: candidate.statusCode;
|
|
12622
|
+
const hasFailureStatus = typeof numericStatusCode === 'number' && !Number.isNaN(numericStatusCode) && numericStatusCode >= 400;
|
|
12623
|
+
const isFailure = candidate.success === false || candidate.ok === false || !!candidate.errorCode || hasFailureStatus;
|
|
12624
|
+
if (!isFailure)
|
|
12625
|
+
return null;
|
|
12626
|
+
return getFriendlyErrorMessage({
|
|
12627
|
+
statusCode: hasFailureStatus ? numericStatusCode : 400,
|
|
12628
|
+
errorCode: candidate.errorCode,
|
|
12629
|
+
message: candidate.message || 'Request failed. Please try again.',
|
|
12630
|
+
details: candidate.details,
|
|
12631
|
+
});
|
|
12632
|
+
};
|
|
12548
12633
|
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
12549
12634
|
const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
|
|
12550
12635
|
// Default Google OAuth proxy URL (hosted on our whitelisted domain)
|
|
@@ -12720,7 +12805,7 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
|
|
|
12720
12805
|
});
|
|
12721
12806
|
};
|
|
12722
12807
|
// getFriendlyErrorMessage is now imported from ../utils/errorHandling
|
|
12723
|
-
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, onRedirect, enabledProviders = ['email', 'google', 'phone'], initialMode, signupProminence, redirectUrl, theme = '
|
|
12808
|
+
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, }) => {
|
|
12724
12809
|
// Resolve signup prominence from props, customization, config, or default
|
|
12725
12810
|
const resolvedSignupProminence = signupProminence || customization?.signupProminence || 'minimal';
|
|
12726
12811
|
// Determine initial mode based on signupProminence setting
|
|
@@ -13079,10 +13164,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13079
13164
|
log.log('Verifying reset token:', token);
|
|
13080
13165
|
// Verify token is valid, then show password reset form
|
|
13081
13166
|
const verifyResult = await api.verifyResetToken(token);
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
throw new Error(verifyResult.message || 'Invalid or expired token');
|
|
13167
|
+
// Guard against undefined result (proxy mode may resolve without data)
|
|
13168
|
+
if (!verifyResult || !verifyResult.valid) {
|
|
13169
|
+
throw new Error(verifyResult?.message || 'Invalid or expired token');
|
|
13086
13170
|
}
|
|
13087
13171
|
setUrlAuthProcessing(false); // Clear processing state - showing reset form
|
|
13088
13172
|
setResetToken(token); // Store token for use in password reset
|
|
@@ -13360,17 +13444,46 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13360
13444
|
return;
|
|
13361
13445
|
setLoading(true);
|
|
13362
13446
|
setError(undefined);
|
|
13447
|
+
setAuthSuccess(false);
|
|
13448
|
+
setResetSuccess(false);
|
|
13449
|
+
setSuccessMessage(undefined);
|
|
13450
|
+
setShowResendVerification(false);
|
|
13363
13451
|
try {
|
|
13364
13452
|
const result = await api.requestPasswordReset(resetRequestEmail, getRedirectUrl());
|
|
13365
|
-
|
|
13453
|
+
const resultError = getActionResultErrorMessage(result);
|
|
13454
|
+
if (resultError) {
|
|
13455
|
+
setAuthSuccess(false);
|
|
13456
|
+
setResetSuccess(false);
|
|
13457
|
+
setSuccessMessage(undefined);
|
|
13458
|
+
setError(resultError);
|
|
13459
|
+
setShowRequestNewReset(true);
|
|
13460
|
+
return;
|
|
13461
|
+
}
|
|
13462
|
+
const resultAny = result;
|
|
13463
|
+
const isConfirmedSuccess = result && (resultAny.success === true ||
|
|
13464
|
+
resultAny.ok === true ||
|
|
13465
|
+
(resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
|
|
13466
|
+
if (!isConfirmedSuccess) {
|
|
13467
|
+
setAuthSuccess(false);
|
|
13468
|
+
setResetSuccess(false);
|
|
13469
|
+
setSuccessMessage(undefined);
|
|
13470
|
+
setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
|
|
13471
|
+
setShowRequestNewReset(true);
|
|
13472
|
+
return;
|
|
13473
|
+
}
|
|
13474
|
+
setAuthSuccess(false);
|
|
13475
|
+
setMode('reset-password');
|
|
13366
13476
|
setResetSuccess(true);
|
|
13367
|
-
// Use backend message if available, otherwise default
|
|
13368
13477
|
setSuccessMessage(result?.message || 'Password reset email sent! Please check your inbox.');
|
|
13369
13478
|
setShowRequestNewReset(false);
|
|
13370
13479
|
setResetRequestEmail('');
|
|
13371
13480
|
}
|
|
13372
13481
|
catch (err) {
|
|
13482
|
+
setAuthSuccess(false);
|
|
13483
|
+
setResetSuccess(false);
|
|
13484
|
+
setSuccessMessage(undefined);
|
|
13373
13485
|
setError(getFriendlyErrorMessage(err));
|
|
13486
|
+
setShowRequestNewReset(true);
|
|
13374
13487
|
onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
|
|
13375
13488
|
}
|
|
13376
13489
|
finally {
|
|
@@ -13697,9 +13810,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13697
13810
|
client_id: googleClientId,
|
|
13698
13811
|
origin: window.location.origin,
|
|
13699
13812
|
});
|
|
13813
|
+
// Track whether the credential callback has fired (user selected an account)
|
|
13814
|
+
// so we don't show the "blocked" fallback when the prompt closes after a successful selection
|
|
13815
|
+
let credentialCallbackFired = false;
|
|
13700
13816
|
google.accounts.id.initialize({
|
|
13701
13817
|
client_id: googleClientId,
|
|
13702
13818
|
callback: async (response) => {
|
|
13819
|
+
credentialCallbackFired = true;
|
|
13703
13820
|
try {
|
|
13704
13821
|
const idToken = response.credential;
|
|
13705
13822
|
const authResponse = await api.loginWithGoogle(idToken);
|
|
@@ -13727,23 +13844,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13727
13844
|
cancel_on_tap_outside: true,
|
|
13728
13845
|
use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
|
|
13729
13846
|
});
|
|
13730
|
-
// Use timeout fallback — if no prompt interaction after
|
|
13847
|
+
// Use timeout fallback — if no prompt interaction after 15s, assume FedCM was blocked
|
|
13848
|
+
// (15s gives users enough time to choose a Google account from the picker)
|
|
13731
13849
|
const promptTimeout = setTimeout(() => {
|
|
13850
|
+
if (credentialCallbackFired)
|
|
13851
|
+
return; // Auth is already in progress, don't show fallback
|
|
13732
13852
|
log.log('Google OneTap prompt timed out — FedCM may be blocked or unavailable');
|
|
13733
13853
|
setGoogleFallbackToPopup(true);
|
|
13734
13854
|
setLoading(false);
|
|
13735
|
-
},
|
|
13855
|
+
}, 15000);
|
|
13736
13856
|
google.accounts.id.prompt((notification) => {
|
|
13737
13857
|
clearTimeout(promptTimeout);
|
|
13858
|
+
// If the credential callback already fired, the user successfully selected an account
|
|
13859
|
+
// The prompt closing with 'dismissed' is expected — don't show fallback
|
|
13860
|
+
if (credentialCallbackFired) {
|
|
13861
|
+
log.log('Google OneTap prompt closed after credential selection — ignoring');
|
|
13862
|
+
return;
|
|
13863
|
+
}
|
|
13738
13864
|
// Check for FedCM/OneTap dismissal or blocking
|
|
13739
|
-
// notification may have getMomentType(), getDismissedReason(), getSkippedReason()
|
|
13740
13865
|
const momentType = notification?.getMomentType?.();
|
|
13741
13866
|
const dismissedReason = notification?.getDismissedReason?.();
|
|
13742
13867
|
const skippedReason = notification?.getSkippedReason?.();
|
|
13743
13868
|
log.log('Google OneTap prompt notification:', { momentType, dismissedReason, skippedReason });
|
|
13744
13869
|
if (momentType === 'skipped' || momentType === 'dismissed') {
|
|
13745
|
-
// User dismissed the prompt, or browser blocked it (FedCM disabled)
|
|
13746
|
-
// Offer popup flow as alternative
|
|
13747
13870
|
log.log('Google OneTap was dismissed/skipped, offering popup fallback');
|
|
13748
13871
|
setGoogleFallbackToPopup(true);
|
|
13749
13872
|
}
|
|
@@ -13757,11 +13880,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13757
13880
|
setLoading(false);
|
|
13758
13881
|
}
|
|
13759
13882
|
};
|
|
13760
|
-
const handlePhoneAuth = async (phoneNumber, verificationCode) => {
|
|
13883
|
+
const handlePhoneAuth = async (phoneNumber, verificationCode, displayName) => {
|
|
13761
13884
|
setLoading(true);
|
|
13762
13885
|
setError(undefined);
|
|
13763
13886
|
try {
|
|
13764
13887
|
if (!verificationCode) {
|
|
13888
|
+
// Phone verify endpoint handles account creation on the backend side,
|
|
13889
|
+
// so no need for ensureAccount here (register requires email which phone users may not have)
|
|
13765
13890
|
// Send verification code via Twilio Verify Service
|
|
13766
13891
|
await api.sendPhoneCode(phoneNumber);
|
|
13767
13892
|
// Twilio Verify Service tracks the verification by phone number
|
|
@@ -13805,6 +13930,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13805
13930
|
const handlePasswordReset = async (emailOrPassword, confirmPassword) => {
|
|
13806
13931
|
setLoading(true);
|
|
13807
13932
|
setError(undefined);
|
|
13933
|
+
setAuthSuccess(false);
|
|
13934
|
+
setResetSuccess(false);
|
|
13935
|
+
setSuccessMessage(undefined);
|
|
13808
13936
|
const effectiveRedirectUrl = getRedirectUrl();
|
|
13809
13937
|
try {
|
|
13810
13938
|
if (resetToken && confirmPassword) {
|
|
@@ -13813,6 +13941,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13813
13941
|
// Auto-login with the new password if we have the email
|
|
13814
13942
|
if (resetEmail) {
|
|
13815
13943
|
try {
|
|
13944
|
+
log.log('Auto-login after password reset for:', resetEmail);
|
|
13816
13945
|
const loginResponse = await api.login(resetEmail, emailOrPassword);
|
|
13817
13946
|
if (loginResponse.token) {
|
|
13818
13947
|
await auth.login(loginResponse.token, loginResponse.user, loginResponse.accountData, false);
|
|
@@ -13825,9 +13954,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13825
13954
|
}
|
|
13826
13955
|
}
|
|
13827
13956
|
catch (loginErr) {
|
|
13828
|
-
|
|
13957
|
+
log.warn('Auto-login after password reset failed, requiring manual login:', loginErr);
|
|
13829
13958
|
}
|
|
13830
13959
|
}
|
|
13960
|
+
else {
|
|
13961
|
+
log.warn('No resetEmail available for auto-login after password reset');
|
|
13962
|
+
}
|
|
13831
13963
|
// Fallback: show success but require manual login
|
|
13832
13964
|
setResetSuccess(true);
|
|
13833
13965
|
setSuccessMessage('Password reset successful! Please sign in with your new password.');
|
|
@@ -13837,14 +13969,35 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13837
13969
|
else {
|
|
13838
13970
|
// Request password reset email
|
|
13839
13971
|
const result = await api.requestPasswordReset(emailOrPassword, effectiveRedirectUrl);
|
|
13972
|
+
const resultError = getActionResultErrorMessage(result);
|
|
13973
|
+
if (resultError) {
|
|
13974
|
+
setAuthSuccess(false);
|
|
13975
|
+
setResetSuccess(false);
|
|
13976
|
+
setSuccessMessage(undefined);
|
|
13977
|
+
setError(resultError);
|
|
13978
|
+
return;
|
|
13979
|
+
}
|
|
13980
|
+
const resultAny = result;
|
|
13981
|
+
const isConfirmedSuccess = result && (resultAny.success === true ||
|
|
13982
|
+
resultAny.ok === true ||
|
|
13983
|
+
(resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
|
|
13984
|
+
if (!isConfirmedSuccess) {
|
|
13985
|
+
setAuthSuccess(false);
|
|
13986
|
+
setResetSuccess(false);
|
|
13987
|
+
setSuccessMessage(undefined);
|
|
13988
|
+
setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
|
|
13989
|
+
return;
|
|
13990
|
+
}
|
|
13840
13991
|
setResetSuccess(true);
|
|
13841
|
-
// Use backend message if available
|
|
13842
13992
|
if (result?.message) {
|
|
13843
13993
|
setSuccessMessage(result.message);
|
|
13844
13994
|
}
|
|
13845
13995
|
}
|
|
13846
13996
|
}
|
|
13847
13997
|
catch (err) {
|
|
13998
|
+
setAuthSuccess(false);
|
|
13999
|
+
setResetSuccess(false);
|
|
14000
|
+
setSuccessMessage(undefined);
|
|
13848
14001
|
setError(getFriendlyErrorMessage(err));
|
|
13849
14002
|
onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
|
|
13850
14003
|
}
|
|
@@ -13852,10 +14005,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13852
14005
|
setLoading(false);
|
|
13853
14006
|
}
|
|
13854
14007
|
};
|
|
13855
|
-
const handleMagicLink = async (email) => {
|
|
14008
|
+
const handleMagicLink = async (email, displayName) => {
|
|
13856
14009
|
setLoading(true);
|
|
13857
14010
|
setError(undefined);
|
|
13858
14011
|
try {
|
|
14012
|
+
// Ensure account exists before sending magic link (creates if new, no-op if exists)
|
|
14013
|
+
await api.ensureAccount({ email, displayName });
|
|
13859
14014
|
await api.sendMagicLink(email, getRedirectUrl());
|
|
13860
14015
|
setAuthSuccess(true);
|
|
13861
14016
|
setSuccessMessage('Magic link sent! Check your email to log in.');
|
|
@@ -13902,13 +14057,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13902
14057
|
? 'hsl(var(--muted-foreground, 215 15% 45%))'
|
|
13903
14058
|
: (resolvedTheme === 'dark' ? '#94a3b8' : '#6B7280'),
|
|
13904
14059
|
fontSize: '0.875rem'
|
|
13905
|
-
}, children: successMessage })] })) : mode === 'magic-link' ? (jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error })) : mode === 'phone' ? (jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error })) : mode === 'reset-password' ? (jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
14060
|
+
}, children: successMessage })] })) : mode === 'magic-link' ? (jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'phone' ? (jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'reset-password' ? (jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
13906
14061
|
setMode('login');
|
|
13907
14062
|
setResetSuccess(false);
|
|
13908
14063
|
setResetToken(undefined); // Clear token when going back
|
|
13909
14064
|
setResetEmail(undefined); // Clear email when going back
|
|
13910
14065
|
setSuccessMessage(undefined); // Clear success message
|
|
13911
|
-
}, loading: loading, error: error, success: resetSuccess, successMessage: successMessage, token: resetToken })) : (mode === 'login' || mode === 'register') ? (jsx(Fragment, { children: showResendVerification ? (jsxs("div", { style: {
|
|
14066
|
+
}, loading: loading, error: error, success: resetSuccess, successMessage: successMessage, token: resetToken, resetEmail: resetEmail })) : (mode === 'login' || mode === 'register') ? (jsx(Fragment, { children: showResendVerification ? (jsxs("div", { style: {
|
|
13912
14067
|
marginTop: '1rem',
|
|
13913
14068
|
padding: '1.5rem',
|
|
13914
14069
|
backgroundColor: config?.branding?.inheritHostStyles
|
|
@@ -14006,7 +14161,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14006
14161
|
color: config?.branding?.inheritHostStyles
|
|
14007
14162
|
? 'hsl(var(--foreground, 215 25% 15%))'
|
|
14008
14163
|
: (resolvedTheme === 'dark' ? '#f1f5f9' : '#374151')
|
|
14009
|
-
}, children: "Password Reset Link Expired" }), jsx("
|
|
14164
|
+
}, children: "Password Reset Link Expired" }), error && (jsx("div", { style: {
|
|
14165
|
+
marginBottom: '1rem',
|
|
14166
|
+
padding: '0.75rem',
|
|
14167
|
+
backgroundColor: resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.15)' : 'rgba(239, 68, 68, 0.1)',
|
|
14168
|
+
border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.3)' : 'rgba(239, 68, 68, 0.2)'}`,
|
|
14169
|
+
borderRadius: '0.375rem',
|
|
14170
|
+
color: resolvedTheme === 'dark' ? '#fca5a5' : '#dc2626',
|
|
14171
|
+
fontSize: '0.875rem',
|
|
14172
|
+
lineHeight: '1.5',
|
|
14173
|
+
}, children: error })), jsx("p", { style: {
|
|
14010
14174
|
marginBottom: '1rem',
|
|
14011
14175
|
fontSize: '0.875rem',
|
|
14012
14176
|
color: config?.branding?.inheritHostStyles
|
|
@@ -14142,6 +14306,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14142
14306
|
const emailDisplayMode = config?.emailDisplayMode || 'form';
|
|
14143
14307
|
const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
|
|
14144
14308
|
const actualProviders = config?.enabledProviders || enabledProviders;
|
|
14309
|
+
const hasEmailProvider = actualProviders.includes('email');
|
|
14310
|
+
// If email provider is not enabled, only show provider buttons (no email/password form)
|
|
14311
|
+
if (!hasEmailProvider) {
|
|
14312
|
+
return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
14313
|
+
}
|
|
14145
14314
|
// Button mode: show provider selection first, then email form if email is selected
|
|
14146
14315
|
if (emailDisplayMode === 'button' && !showEmailForm) {
|
|
14147
14316
|
return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
@@ -14163,7 +14332,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14163
14332
|
setShowResendVerification(false);
|
|
14164
14333
|
setShowRequestNewReset(false);
|
|
14165
14334
|
setError(undefined);
|
|
14166
|
-
}, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
14335
|
+
}, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, signupRedirectUrl: config?.signupRedirectUrl || customization?.signupRedirectUrl, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
14167
14336
|
})()] })) })) : null }));
|
|
14168
14337
|
};
|
|
14169
14338
|
|