@proveanything/smartlinks-auth-ui 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var react = require('react');
5
5
  var smartlinks = require('@proveanything/smartlinks');
6
+ var PhoneInputComponent = require('react-phone-number-input');
7
+ require('react-phone-number-input/style.css');
6
8
 
7
9
  function _interopNamespaceDefault(e) {
8
10
  var n = Object.create(null);
@@ -85,20 +87,159 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
85
87
  : 'Get started by creating your account.' })] }), 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 Doe" })] })), 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 === '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') }), 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' })] })] }));
86
88
  };
87
89
 
88
- const ProviderButtons = ({ enabledProviders, onGoogleLogin, onPhoneLogin, loading, }) => {
90
+ const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
89
91
  if (enabledProviders.length === 0)
90
92
  return null;
91
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "auth-or-divider", children: jsxRuntime.jsx("span", { children: "or continue with" }) }), jsxRuntime.jsxs("div", { className: "auth-provider-buttons", children: [enabledProviders.includes('google') && (jsxRuntime.jsxs("button", { type: "button", className: "auth-provider-button", onClick: onGoogleLogin, disabled: loading, children: [jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [jsxRuntime.jsx("path", { d: "M19.6 10.227c0-.709-.064-1.39-.182-2.045H10v3.868h5.382a4.6 4.6 0 01-1.996 3.018v2.51h3.232c1.891-1.742 2.982-4.305 2.982-7.35z", fill: "#4285F4" }), jsxRuntime.jsx("path", { d: "M10 20c2.7 0 4.964-.895 6.618-2.423l-3.232-2.509c-.895.6-2.04.955-3.386.955-2.605 0-4.81-1.76-5.595-4.123H1.064v2.59A9.996 9.996 0 0010 20z", fill: "#34A853" }), jsxRuntime.jsx("path", { d: "M4.405 11.9c-.2-.6-.314-1.24-.314-1.9 0-.66.114-1.3.314-1.9V5.51H1.064A9.996 9.996 0 000 10c0 1.614.386 3.14 1.064 4.49l3.34-2.59z", fill: "#FBBC05" }), jsxRuntime.jsx("path", { d: "M10 3.977c1.468 0 2.786.505 3.823 1.496l2.868-2.868C14.959.99 12.695 0 10 0 6.09 0 2.71 2.24 1.064 5.51l3.34 2.59C5.19 5.736 7.395 3.977 10 3.977z", fill: "#EA4335" })] }), "Continue with Google"] })), enabledProviders.includes('phone') && (jsxRuntime.jsxs("button", { type: "button", className: "auth-provider-button", onClick: onPhoneLogin, disabled: loading, children: [jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V15a2 2 0 01-2 2h-1C7.82 17 2 11.18 2 5V4z" }) }), "Continue with Phone"] }))] })] }));
93
+ // Determine the order of providers to display
94
+ const orderedProviders = providerOrder && providerOrder.length > 0
95
+ ? providerOrder.filter(p => enabledProviders.includes(p))
96
+ : enabledProviders;
97
+ // Provider button configurations
98
+ const providerConfigs = {
99
+ email: {
100
+ label: 'Continue with Email',
101
+ icon: (jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) })),
102
+ onClick: () => onEmailLogin?.()
103
+ },
104
+ google: {
105
+ label: 'Continue with Google',
106
+ icon: (jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [jsxRuntime.jsx("path", { d: "M19.6 10.227c0-.709-.064-1.39-.182-2.045H10v3.868h5.382a4.6 4.6 0 01-1.996 3.018v2.51h3.232c1.891-1.742 2.982-4.305 2.982-7.35z", fill: "#4285F4" }), jsxRuntime.jsx("path", { d: "M10 20c2.7 0 4.964-.895 6.618-2.423l-3.232-2.509c-.895.6-2.04.955-3.386.955-2.605 0-4.81-1.76-5.595-4.123H1.064v2.59A9.996 9.996 0 0010 20z", fill: "#34A853" }), jsxRuntime.jsx("path", { d: "M4.405 11.9c-.2-.6-.314-1.24-.314-1.9 0-.66.114-1.3.314-1.9V5.51H1.064A9.996 9.996 0 000 10c0 1.614.386 3.14 1.064 4.49l3.34-2.59z", fill: "#FBBC05" }), jsxRuntime.jsx("path", { d: "M10 3.977c1.468 0 2.786.505 3.823 1.496l2.868-2.868C14.959.99 12.695 0 10 0 6.09 0 2.71 2.24 1.064 5.51l3.34 2.59C5.19 5.736 7.395 3.977 10 3.977z", fill: "#EA4335" })] })),
107
+ onClick: onGoogleLogin
108
+ },
109
+ phone: {
110
+ label: 'Continue with Phone',
111
+ icon: (jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V15a2 2 0 01-2 2h-1C7.82 17 2 11.18 2 5V4z" }) })),
112
+ onClick: onPhoneLogin
113
+ },
114
+ 'magic-link': {
115
+ label: 'Continue with Magic Link',
116
+ icon: (jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) })),
117
+ onClick: () => onMagicLinkLogin?.()
118
+ }
119
+ };
120
+ // Show divider only if email handler is not provided (meaning this is showing alternative providers, not all providers)
121
+ const showDivider = !onEmailLogin;
122
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showDivider && (jsxRuntime.jsx("div", { className: "auth-or-divider", children: jsxRuntime.jsx("span", { children: "or continue with" }) })), jsxRuntime.jsx("div", { className: "auth-provider-buttons", children: orderedProviders.map((provider) => {
123
+ const config = providerConfigs[provider];
124
+ if (!config)
125
+ return null;
126
+ return (jsxRuntime.jsxs("button", { type: "button", className: "auth-provider-button", onClick: config.onClick, disabled: loading, children: [config.icon, config.label] }, provider));
127
+ }) })] }));
128
+ };
129
+
130
+ const OTPInput = ({ length = 6, value, onChange, disabled = false, }) => {
131
+ const [otp, setOtp] = react.useState(Array(length).fill(''));
132
+ const inputRefs = react.useRef([]);
133
+ react.useEffect(() => {
134
+ const digits = value.split('').slice(0, length);
135
+ const newOtp = [...digits, ...Array(Math.max(0, length - digits.length)).fill('')];
136
+ setOtp(newOtp);
137
+ }, [value, length]);
138
+ const handleChange = (index, digit) => {
139
+ if (disabled)
140
+ return;
141
+ // Only allow numbers
142
+ if (digit && !/^\d$/.test(digit))
143
+ return;
144
+ const newOtp = [...otp];
145
+ newOtp[index] = digit;
146
+ setOtp(newOtp);
147
+ // Call onChange with the full OTP value
148
+ onChange(newOtp.join(''));
149
+ // Move to next input if digit was entered
150
+ if (digit && index < length - 1) {
151
+ inputRefs.current[index + 1]?.focus();
152
+ }
153
+ };
154
+ const handleKeyDown = (index, e) => {
155
+ if (disabled)
156
+ return;
157
+ // Handle backspace
158
+ if (e.key === 'Backspace' && !otp[index] && index > 0) {
159
+ inputRefs.current[index - 1]?.focus();
160
+ }
161
+ // Handle arrow keys
162
+ if (e.key === 'ArrowLeft' && index > 0) {
163
+ inputRefs.current[index - 1]?.focus();
164
+ }
165
+ if (e.key === 'ArrowRight' && index < length - 1) {
166
+ inputRefs.current[index + 1]?.focus();
167
+ }
168
+ };
169
+ const handlePaste = (e) => {
170
+ if (disabled)
171
+ return;
172
+ e.preventDefault();
173
+ const pastedData = e.clipboardData.getData('text/plain').slice(0, length);
174
+ const digits = pastedData.replace(/\D/g, '').split('');
175
+ const newOtp = [...Array(length).fill('')];
176
+ digits.forEach((digit, idx) => {
177
+ if (idx < length) {
178
+ newOtp[idx] = digit;
179
+ }
180
+ });
181
+ setOtp(newOtp);
182
+ onChange(newOtp.join(''));
183
+ // Focus the next empty input or the last one
184
+ const nextEmptyIndex = newOtp.findIndex(digit => !digit);
185
+ const focusIndex = nextEmptyIndex === -1 ? length - 1 : nextEmptyIndex;
186
+ inputRefs.current[focusIndex]?.focus();
187
+ };
188
+ return (jsxRuntime.jsx("div", { className: "otp-input-container", children: otp.map((digit, index) => (jsxRuntime.jsx("input", { ref: (el) => (inputRefs.current[index] = el), type: "text", inputMode: "numeric", maxLength: 1, value: digit, onChange: (e) => handleChange(index, e.target.value), onKeyDown: (e) => handleKeyDown(index, e), onPaste: handlePaste, disabled: disabled, className: "otp-input-box", autoComplete: "off" }, index))) }));
189
+ };
190
+
191
+ const PhoneInput = ({ value, onChange, disabled = false, }) => {
192
+ const [defaultCountry, setDefaultCountry] = react.useState('US');
193
+ react.useEffect(() => {
194
+ // Auto-detect country based on user's location
195
+ const detectCountry = async () => {
196
+ try {
197
+ const response = await fetch('https://ipapi.co/json/');
198
+ const data = await response.json();
199
+ if (data.country_code) {
200
+ setDefaultCountry(data.country_code);
201
+ }
202
+ }
203
+ catch (error) {
204
+ console.log('Could not detect country, using default');
205
+ }
206
+ };
207
+ detectCountry();
208
+ }, []);
209
+ return (jsxRuntime.jsx(PhoneInputComponent, { international: true, defaultCountry: defaultCountry, value: value, onChange: (value) => onChange(value || ''), disabled: disabled, className: "phone-input-wrapper" }));
92
210
  };
93
211
 
94
212
  const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
95
213
  const [phoneNumber, setPhoneNumber] = react.useState('');
96
214
  const [verificationCode, setVerificationCode] = react.useState('');
97
215
  const [codeSent, setCodeSent] = react.useState(false);
216
+ const [resendCooldown, setResendCooldown] = react.useState(0);
217
+ // Countdown timer for resend button
218
+ react.useEffect(() => {
219
+ if (resendCooldown > 0) {
220
+ const timer = setTimeout(() => {
221
+ setResendCooldown(resendCooldown - 1);
222
+ }, 1000);
223
+ return () => clearTimeout(timer);
224
+ }
225
+ }, [resendCooldown]);
98
226
  const handleSendCode = async (e) => {
99
227
  e.preventDefault();
100
228
  await onSubmit(phoneNumber);
101
229
  setCodeSent(true);
230
+ setResendCooldown(60); // 60 second cooldown
231
+ };
232
+ const handleResendCode = async () => {
233
+ if (resendCooldown > 0)
234
+ return;
235
+ try {
236
+ await onSubmit(phoneNumber);
237
+ setResendCooldown(60); // Reset cooldown
238
+ setVerificationCode(''); // Clear the code input
239
+ }
240
+ catch (err) {
241
+ // Error will be handled by parent component
242
+ }
102
243
  };
103
244
  const handleVerifyCode = async (e) => {
104
245
  e.preventDefault();
@@ -106,7 +247,12 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
106
247
  };
107
248
  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
108
249
  ? 'Enter the verification code sent to your phone.'
109
- : '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("input", { type: "tel", id: "phoneNumber", className: "auth-input", value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value), required: true, disabled: loading, placeholder: "+1 (555) 123-4567" })] })) : (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "verificationCode", className: "auth-label", children: "Verification Code" }), jsxRuntime.jsx("input", { type: "text", id: "verificationCode", className: "auth-input", value: verificationCode, onChange: (e) => setVerificationCode(e.target.value), required: true, disabled: loading, placeholder: "123456", maxLength: 6, pattern: "[0-9]{6}" })] })), 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" }) })] }));
250
+ : '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: {
251
+ cursor: resendCooldown > 0 ? 'not-allowed' : 'pointer',
252
+ opacity: resendCooldown > 0 ? 0.5 : 1,
253
+ }, children: resendCooldown > 0
254
+ ? `Resend code in ${resendCooldown}s`
255
+ : '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" }) })] }));
110
256
  };
111
257
 
112
258
  const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, token, }) => {
@@ -144,6 +290,15 @@ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, token, }
144
290
  : "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" }) })] }));
145
291
  };
146
292
 
293
+ const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, }) => {
294
+ const [email, setEmail] = react.useState('');
295
+ const handleSubmit = async (e) => {
296
+ e.preventDefault();
297
+ await onSubmit(email);
298
+ };
299
+ 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" })] }));
300
+ };
301
+
147
302
  /**
148
303
  * AuthAPI - Thin wrapper around Smartlinks SDK authKit namespace
149
304
  * All authentication operations now use the global Smartlinks SDK
@@ -172,8 +327,8 @@ class AuthAPI {
172
327
  async sendPhoneCode(phoneNumber) {
173
328
  return smartlinks__namespace.authKit.sendPhoneCode(this.clientId, phoneNumber);
174
329
  }
175
- async verifyPhoneCode(verificationId, code) {
176
- return smartlinks__namespace.authKit.verifyPhoneCode(this.clientId, verificationId, code);
330
+ async verifyPhoneCode(phoneNumber, code) {
331
+ return smartlinks__namespace.authKit.verifyPhoneCode(this.clientId, phoneNumber, code);
177
332
  }
178
333
  async requestPasswordReset(email, redirectUrl) {
179
334
  return smartlinks__namespace.authKit.requestPasswordReset(this.clientId, {
@@ -226,6 +381,15 @@ class AuthAPI {
226
381
  };
227
382
  }
228
383
  }
384
+ async sendMagicLink(email, redirectUrl) {
385
+ return smartlinks__namespace.authKit.sendMagicLink(this.clientId, {
386
+ email,
387
+ redirectUrl
388
+ });
389
+ }
390
+ async verifyMagicLink(token) {
391
+ return smartlinks__namespace.authKit.verifyMagicLink(this.clientId, token);
392
+ }
229
393
  }
230
394
 
231
395
  const TOKEN_KEY = 'smartlinks_auth_token';
@@ -315,9 +479,10 @@ const AuthProvider = ({ children }) => {
315
479
  setToken(storedToken.token);
316
480
  setUser(storedUser);
317
481
  setAccountData(storedAccountData);
318
- // Set bearer token in global Smartlinks SDK
319
- // @ts-expect-error - setBearerToken exists in runtime but may not be in type definitions
320
- smartlinks__namespace.setBearerToken(storedToken.token);
482
+ // Set bearer token in global Smartlinks SDK via auth.verifyToken
483
+ smartlinks__namespace.auth.verifyToken(storedToken.token).catch(err => {
484
+ console.warn('Failed to restore bearer token on init:', err);
485
+ });
321
486
  }
322
487
  setIsLoading(false);
323
488
  }, []);
@@ -331,9 +496,11 @@ const AuthProvider = ({ children }) => {
331
496
  setToken(authToken);
332
497
  setUser(authUser);
333
498
  setAccountData(authAccountData || null);
334
- // Set bearer token in global Smartlinks SDK
335
- // @ts-expect-error - setBearerToken exists in runtime but may not be in type definitions
336
- smartlinks__namespace.setBearerToken(authToken);
499
+ // Set bearer token in global Smartlinks SDK via auth.verifyToken
500
+ // This both validates the token and sets it for future API calls
501
+ smartlinks__namespace.auth.verifyToken(authToken).catch(err => {
502
+ console.warn('Failed to set bearer token on login:', err);
503
+ });
337
504
  }, []);
338
505
  const logout = react.useCallback(async () => {
339
506
  // Clear local storage
@@ -342,8 +509,7 @@ const AuthProvider = ({ children }) => {
342
509
  setUser(null);
343
510
  setAccountData(null);
344
511
  // Clear bearer token from global Smartlinks SDK
345
- // @ts-expect-error - setBearerToken exists in runtime but may not be in type definitions
346
- smartlinks__namespace.setBearerToken(undefined);
512
+ smartlinks__namespace.auth.logout();
347
513
  }, []);
348
514
  const getToken = react.useCallback(() => {
349
515
  const storedToken = tokenStorage.getToken();
@@ -373,6 +539,269 @@ const useAuth = () => {
373
539
  return context;
374
540
  };
375
541
 
542
+ const AccountManagement = ({ apiEndpoint, clientId, onProfileUpdated, onEmailChangeRequested, onPasswordChanged, onAccountDeleted, onError, theme = 'light', className = '', customization = {}, }) => {
543
+ const auth = useAuth();
544
+ const [loading, setLoading] = react.useState(false);
545
+ const [profile, setProfile] = react.useState(null);
546
+ const [error, setError] = react.useState();
547
+ const [success, setSuccess] = react.useState();
548
+ // Profile form state
549
+ const [displayName, setDisplayName] = react.useState('');
550
+ // Email change state
551
+ const [newEmail, setNewEmail] = react.useState('');
552
+ const [emailPassword, setEmailPassword] = react.useState('');
553
+ // Password change state
554
+ const [currentPassword, setCurrentPassword] = react.useState('');
555
+ const [newPassword, setNewPassword] = react.useState('');
556
+ const [confirmPassword, setConfirmPassword] = react.useState('');
557
+ // Phone change state (reuses existing sendPhoneCode flow)
558
+ const [newPhone, setNewPhone] = react.useState('');
559
+ const [phoneCode, setPhoneCode] = react.useState('');
560
+ const [phoneCodeSent, setPhoneCodeSent] = react.useState(false);
561
+ // Account deletion state
562
+ const [deletePassword, setDeletePassword] = react.useState('');
563
+ const [deleteConfirmText, setDeleteConfirmText] = react.useState('');
564
+ const [showDeleteConfirm, setShowDeleteConfirm] = react.useState(false);
565
+ const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = true, } = customization;
566
+ // Reinitialize Smartlinks SDK when apiEndpoint changes
567
+ react.useEffect(() => {
568
+ if (apiEndpoint) {
569
+ smartlinks__namespace.initializeApi({
570
+ baseURL: apiEndpoint,
571
+ proxyMode: false,
572
+ ngrokSkipBrowserWarning: true,
573
+ });
574
+ }
575
+ }, [apiEndpoint]);
576
+ // Load user profile on mount
577
+ react.useEffect(() => {
578
+ loadProfile();
579
+ }, [clientId]);
580
+ const loadProfile = async () => {
581
+ if (!auth.isAuthenticated) {
582
+ setError('You must be logged in to manage your account');
583
+ return;
584
+ }
585
+ setLoading(true);
586
+ setError(undefined);
587
+ try {
588
+ // TODO: Backend implementation required
589
+ // Endpoint: GET /api/v1/authkit/:clientId/account/profile
590
+ // SDK method: smartlinks.authKit.getProfile(clientId)
591
+ // Temporary mock data for UI testing
592
+ const profileData = {
593
+ uid: auth.user?.uid || '',
594
+ email: auth.user?.email,
595
+ displayName: auth.user?.displayName,
596
+ phoneNumber: auth.user?.phoneNumber,
597
+ photoURL: auth.user?.photoURL,
598
+ emailVerified: true,
599
+ accountData: auth.accountData || {},
600
+ };
601
+ setProfile(profileData);
602
+ setDisplayName(profileData.displayName || '');
603
+ }
604
+ catch (err) {
605
+ const errorMessage = err instanceof Error ? err.message : 'Failed to load profile';
606
+ setError(errorMessage);
607
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
608
+ }
609
+ finally {
610
+ setLoading(false);
611
+ }
612
+ };
613
+ const handleUpdateProfile = async (e) => {
614
+ e.preventDefault();
615
+ setLoading(true);
616
+ setError(undefined);
617
+ setSuccess(undefined);
618
+ try {
619
+ // TODO: Backend implementation required
620
+ // Endpoint: POST /api/v1/authkit/:clientId/account/update-profile
621
+ // SDK method: smartlinks.authKit.updateProfile(clientId, updateData)
622
+ setError('Backend API not yet implemented. See console for required endpoint.');
623
+ console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-profile');
624
+ console.log('Update data:', { displayName });
625
+ // Uncomment when backend is ready:
626
+ // const updateData: ProfileUpdateData = {
627
+ // displayName: displayName || undefined,
628
+ // photoURL: photoURL || undefined,
629
+ // };
630
+ // const updatedProfile = await smartlinks.authKit.updateProfile(clientId, updateData);
631
+ // setProfile(updatedProfile);
632
+ // setSuccess('Profile updated successfully!');
633
+ // onProfileUpdated?.(updatedProfile);
634
+ }
635
+ catch (err) {
636
+ const errorMessage = err instanceof Error ? err.message : 'Failed to update profile';
637
+ setError(errorMessage);
638
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
639
+ }
640
+ finally {
641
+ setLoading(false);
642
+ }
643
+ };
644
+ const handleChangeEmail = async (e) => {
645
+ e.preventDefault();
646
+ setLoading(true);
647
+ setError(undefined);
648
+ setSuccess(undefined);
649
+ try {
650
+ // TODO: Backend implementation required
651
+ // Endpoint: POST /api/v1/authkit/:clientId/account/change-email
652
+ // SDK method: smartlinks.authKit.changeEmail(clientId, newEmail, password)
653
+ // Note: No verification flow for now - direct email update
654
+ setError('Backend API not yet implemented. See console for required endpoint.');
655
+ console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-email');
656
+ console.log('Data:', { newEmail });
657
+ // Uncomment when backend is ready:
658
+ // await smartlinks.authKit.changeEmail(clientId, newEmail, emailPassword);
659
+ // setSuccess('Email changed successfully!');
660
+ // setNewEmail('');
661
+ // setEmailPassword('');
662
+ // onEmailChangeRequested?.();
663
+ // await loadProfile(); // Reload to show new email
664
+ }
665
+ catch (err) {
666
+ const errorMessage = err instanceof Error ? err.message : 'Failed to change email';
667
+ setError(errorMessage);
668
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
669
+ }
670
+ finally {
671
+ setLoading(false);
672
+ }
673
+ };
674
+ const handleChangePassword = async (e) => {
675
+ e.preventDefault();
676
+ if (newPassword !== confirmPassword) {
677
+ setError('New passwords do not match');
678
+ return;
679
+ }
680
+ if (newPassword.length < 6) {
681
+ setError('Password must be at least 6 characters');
682
+ return;
683
+ }
684
+ setLoading(true);
685
+ setError(undefined);
686
+ setSuccess(undefined);
687
+ try {
688
+ // TODO: Backend implementation required
689
+ // Endpoint: POST /api/v1/authkit/:clientId/account/change-password
690
+ // SDK method: smartlinks.authKit.changePassword(clientId, currentPassword, newPassword)
691
+ setError('Backend API not yet implemented. See console for required endpoint.');
692
+ console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-password');
693
+ console.log('Data: currentPassword and newPassword provided');
694
+ // Uncomment when backend is ready:
695
+ // await smartlinks.authKit.changePassword(clientId, currentPassword, newPassword);
696
+ // setSuccess('Password changed successfully!');
697
+ // setCurrentPassword('');
698
+ // setNewPassword('');
699
+ // setConfirmPassword('');
700
+ // onPasswordChanged?.();
701
+ }
702
+ catch (err) {
703
+ const errorMessage = err instanceof Error ? err.message : 'Failed to change password';
704
+ setError(errorMessage);
705
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
706
+ }
707
+ finally {
708
+ setLoading(false);
709
+ }
710
+ };
711
+ const handleSendPhoneCode = async () => {
712
+ setLoading(true);
713
+ setError(undefined);
714
+ try {
715
+ await smartlinks__namespace.authKit.sendPhoneCode(clientId, newPhone);
716
+ setPhoneCodeSent(true);
717
+ setSuccess('Verification code sent to your phone');
718
+ }
719
+ catch (err) {
720
+ const errorMessage = err instanceof Error ? err.message : 'Failed to send verification code';
721
+ setError(errorMessage);
722
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
723
+ }
724
+ finally {
725
+ setLoading(false);
726
+ }
727
+ };
728
+ const handleUpdatePhone = async (e) => {
729
+ e.preventDefault();
730
+ setLoading(true);
731
+ setError(undefined);
732
+ setSuccess(undefined);
733
+ try {
734
+ // TODO: Backend implementation required
735
+ // Endpoint: POST /api/v1/authkit/:clientId/account/update-phone
736
+ // SDK method: smartlinks.authKit.updatePhone(clientId, phoneNumber, verificationCode)
737
+ setError('Backend API not yet implemented. See console for required endpoint.');
738
+ console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-phone');
739
+ console.log('Data:', { phoneNumber: newPhone, code: phoneCode });
740
+ // Uncomment when backend is ready:
741
+ // await smartlinks.authKit.updatePhone(clientId, newPhone, phoneCode);
742
+ // setSuccess('Phone number updated successfully!');
743
+ // setNewPhone('');
744
+ // setPhoneCode('');
745
+ // setPhoneCodeSent(false);
746
+ // await loadProfile();
747
+ }
748
+ catch (err) {
749
+ const errorMessage = err instanceof Error ? err.message : 'Failed to update phone number';
750
+ setError(errorMessage);
751
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
752
+ }
753
+ finally {
754
+ setLoading(false);
755
+ }
756
+ };
757
+ const handleDeleteAccount = async () => {
758
+ if (deleteConfirmText !== 'DELETE') {
759
+ setError('Please type DELETE to confirm account deletion');
760
+ return;
761
+ }
762
+ if (!deletePassword) {
763
+ setError('Password is required');
764
+ return;
765
+ }
766
+ setLoading(true);
767
+ setError(undefined);
768
+ try {
769
+ // TODO: Backend implementation required
770
+ // Endpoint: DELETE /api/v1/authkit/:clientId/account/delete
771
+ // SDK method: smartlinks.authKit.deleteAccount(clientId, password, confirmText)
772
+ // Note: This performs a SOFT DELETE (marks as deleted, obfuscates email)
773
+ setError('Backend API not yet implemented. See console for required endpoint.');
774
+ console.log('Required API endpoint: DELETE /api/v1/authkit/:clientId/account/delete');
775
+ console.log('Data: password and confirmText="DELETE" provided');
776
+ console.log('Note: Backend should soft delete (mark deleted, obfuscate email, disable account)');
777
+ // Uncomment when backend is ready:
778
+ // await smartlinks.authKit.deleteAccount(clientId, deletePassword, deleteConfirmText);
779
+ // setSuccess('Account deleted successfully');
780
+ // onAccountDeleted?.();
781
+ // await auth.logout();
782
+ }
783
+ catch (err) {
784
+ const errorMessage = err instanceof Error ? err.message : 'Failed to delete account';
785
+ setError(errorMessage);
786
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
787
+ }
788
+ finally {
789
+ setLoading(false);
790
+ }
791
+ };
792
+ if (!auth.isAuthenticated) {
793
+ return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, children: jsxRuntime.jsxs("div", { className: "auth-form", children: [jsxRuntime.jsx("h2", { className: "auth-title", children: "Account Management" }), jsxRuntime.jsx("p", { className: "text-muted-foreground", children: "Please log in to manage your account" })] }) }));
794
+ }
795
+ if (loading && !profile) {
796
+ return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, children: jsxRuntime.jsx("div", { className: "auth-form", children: jsxRuntime.jsx("h2", { className: "auth-title", children: "Loading..." }) }) }));
797
+ }
798
+ return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, children: jsxRuntime.jsxs("div", { className: "auth-form", style: { maxWidth: '600px' }, children: [jsxRuntime.jsx("h2", { className: "auth-title", children: "Account Management" }), error && (jsxRuntime.jsx("div", { className: "auth-error", role: "alert", children: error })), success && (jsxRuntime.jsx("div", { className: "auth-success", role: "alert", children: success })), showProfileSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsx("h3", { className: "section-title", children: "Profile Information" }), jsxRuntime.jsxs("form", { onSubmit: handleUpdateProfile, className: "auth-form-fields", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "displayName", children: "Display Name" }), jsxRuntime.jsx("input", { id: "displayName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), placeholder: "Your display name", className: "auth-input" })] }), jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Updating...' : 'Update Profile' })] })] })), showEmailSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsx("h3", { className: "section-title", children: "Email Management" }), jsxRuntime.jsxs("div", { className: "current-info", children: [jsxRuntime.jsxs("p", { children: [jsxRuntime.jsx("strong", { children: "Current Email:" }), " ", profile?.email || 'Not set'] }), jsxRuntime.jsxs("p", { children: [jsxRuntime.jsx("strong", { children: "Status:" }), ' ', jsxRuntime.jsx("span", { className: profile?.emailVerified ? 'text-success' : 'text-warning', children: profile?.emailVerified ? 'Verified' : 'Unverified' })] })] }), jsxRuntime.jsxs("form", { onSubmit: handleChangeEmail, className: "auth-form-fields", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newEmail", children: "New Email" }), jsxRuntime.jsx("input", { id: "newEmail", type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "new.email@example.com", className: "auth-input", required: true })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "emailPassword", children: "Confirm Password" }), jsxRuntime.jsx("input", { id: "emailPassword", type: "password", value: emailPassword, onChange: (e) => setEmailPassword(e.target.value), placeholder: "Enter your password", className: "auth-input", required: true })] }), jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Sending...' : 'Change Email' })] })] })), showPasswordSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsx("h3", { className: "section-title", children: "Password Management" }), jsxRuntime.jsxs("form", { onSubmit: handleChangePassword, className: "auth-form-fields", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "currentPassword", children: "Current Password" }), jsxRuntime.jsx("input", { id: "currentPassword", type: "password", value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value), placeholder: "Enter current password", className: "auth-input", required: true })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newPassword", children: "New Password" }), jsxRuntime.jsx("input", { id: "newPassword", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), placeholder: "Enter new password", className: "auth-input", required: true, minLength: 6 })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "confirmPassword", children: "Confirm New Password" }), jsxRuntime.jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), placeholder: "Confirm new password", className: "auth-input", required: true, minLength: 6 })] }), jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' })] })] })), showPhoneSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsx("h3", { className: "section-title", children: "Phone Number" }), jsxRuntime.jsx("div", { className: "current-info", children: jsxRuntime.jsxs("p", { children: [jsxRuntime.jsx("strong", { children: "Current Phone:" }), " ", profile?.phoneNumber || 'Not set'] }) }), jsxRuntime.jsxs("form", { onSubmit: handleUpdatePhone, className: "auth-form-fields", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newPhone", children: "New Phone Number" }), jsxRuntime.jsx("input", { id: "newPhone", type: "tel", value: newPhone, onChange: (e) => setNewPhone(e.target.value), placeholder: "+1234567890", className: "auth-input", required: true })] }), !phoneCodeSent ? (jsxRuntime.jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Verification Code' })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneCode", children: "Verification Code" }), jsxRuntime.jsx("input", { id: "phoneCode", type: "text", value: phoneCode, onChange: (e) => setPhoneCode(e.target.value), placeholder: "Enter 6-digit code", className: "auth-input", required: true, maxLength: 6 })] }), jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Update Phone' })] }))] })] })), showDeleteAccount && (jsxRuntime.jsxs("section", { className: "account-section danger-zone", children: [jsxRuntime.jsx("h3", { className: "section-title text-danger", children: "Danger Zone" }), !showDeleteConfirm ? (jsxRuntime.jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "auth-button button-danger", children: "Delete Account" })) : (jsxRuntime.jsxs("div", { className: "delete-confirm", children: [jsxRuntime.jsx("p", { className: "warning-text", children: "\u26A0\uFE0F This action cannot be undone. This will permanently delete your account and all associated data." }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "deletePassword", children: "Confirm Password" }), jsxRuntime.jsx("input", { id: "deletePassword", type: "password", value: deletePassword, onChange: (e) => setDeletePassword(e.target.value), placeholder: "Enter your password", className: "auth-input" })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "deleteConfirm", children: "Type DELETE to confirm" }), jsxRuntime.jsx("input", { id: "deleteConfirm", type: "text", value: deleteConfirmText, onChange: (e) => setDeleteConfirmText(e.target.value), placeholder: "DELETE", className: "auth-input" })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "button", onClick: handleDeleteAccount, className: "auth-button button-danger", disabled: loading, children: loading ? 'Deleting...' : 'Permanently Delete Account' }), jsxRuntime.jsx("button", { type: "button", onClick: () => {
799
+ setShowDeleteConfirm(false);
800
+ setDeletePassword('');
801
+ setDeleteConfirmText('');
802
+ }, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }) }));
803
+ };
804
+
376
805
  const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
377
806
  const { isAuthenticated, isLoading } = useAuth();
378
807
  // Show loading state
@@ -391,22 +820,46 @@ const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
391
820
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
392
821
  };
393
822
 
394
- const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], theme = 'light', className, }) => {
823
+ const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], providerOrder, emailDisplayMode = 'form', theme = 'light', className, }) => {
395
824
  const showEmail = enabledProviders.includes('email');
396
825
  const showGoogle = enabledProviders.includes('google');
397
826
  const showPhone = enabledProviders.includes('phone');
398
- const hasProviders = showGoogle || showPhone;
399
- return (jsxRuntime.jsxs(AuthContainer, { theme: theme, className: className, config: customization, children: [showEmail && (jsxRuntime.jsxs("div", { className: "auth-form", children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { className: "auth-label", children: "Email" }), jsxRuntime.jsx("input", { type: "email", className: "auth-input", placeholder: "Enter your email", disabled: true })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { className: "auth-label", children: "Password" }), jsxRuntime.jsx("input", { type: "password", className: "auth-input", placeholder: "Enter your password", disabled: true })] }), jsxRuntime.jsx("button", { className: "auth-button auth-button-primary", disabled: true, children: "Sign In" }), jsxRuntime.jsx("div", { style: { textAlign: 'center', marginTop: '1rem' }, children: jsxRuntime.jsx("button", { className: "auth-link", disabled: true, children: "Forgot password?" }) }), jsxRuntime.jsxs("div", { style: { textAlign: 'center', marginTop: '0.5rem', fontSize: '0.875rem', color: 'var(--auth-text-muted, #6B7280)' }, children: ["Don't have an account?", ' ', jsxRuntime.jsx("button", { className: "auth-link", disabled: true, children: "Sign up" })] })] })), hasProviders && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showEmail && (jsxRuntime.jsx("div", { className: "auth-or-divider", children: jsxRuntime.jsx("span", { children: "or continue with" }) })), jsxRuntime.jsxs("div", { className: "auth-provider-buttons", children: [showGoogle && (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", children: [jsxRuntime.jsx("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z", fill: "#4285F4" }), jsxRuntime.jsx("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.183l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332C2.438 15.983 5.482 18 9 18z", fill: "#34A853" }), jsxRuntime.jsx("path", { d: "M3.964 10.707c-.18-.54-.282-1.117-.282-1.707 0-.593.102-1.167.282-1.707V4.961H.957C.347 6.175 0 7.548 0 9s.348 2.825.957 4.039l3.007-2.332z", fill: "#FBBC05" }), jsxRuntime.jsx("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0 5.482 0 2.438 2.017.957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z", fill: "#EA4335" })] }), jsxRuntime.jsx("span", { children: "Google" })] })), showPhone && (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsxRuntime.jsx("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" }) }), jsxRuntime.jsx("span", { children: "Phone" })] }))] })] }))] }));
827
+ const showMagicLink = enabledProviders.includes('magic-link');
828
+ // Determine ordered providers (excluding email if in button mode)
829
+ const orderedProviders = providerOrder && providerOrder.length > 0
830
+ ? providerOrder.filter(p => enabledProviders.includes(p) && p !== 'email')
831
+ : enabledProviders.filter(p => p !== 'email');
832
+ const hasOtherProviders = showGoogle || showPhone || showMagicLink;
833
+ // Render provider button helper
834
+ const renderProviderButton = (provider) => {
835
+ if (provider === 'google' && showGoogle) {
836
+ return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", children: [jsxRuntime.jsx("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z", fill: "#4285F4" }), jsxRuntime.jsx("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.183l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332C2.438 15.983 5.482 18 9 18z", fill: "#34A853" }), jsxRuntime.jsx("path", { d: "M3.964 10.707c-.18-.54-.282-1.117-.282-1.707 0-.593.102-1.167.282-1.707V4.961H.957C.347 6.175 0 7.548 0 9s.348 2.825.957 4.039l3.007-2.332z", fill: "#FBBC05" }), jsxRuntime.jsx("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0 5.482 0 2.438 2.017.957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z", fill: "#EA4335" })] }), jsxRuntime.jsx("span", { children: "Continue with Google" })] }, "google"));
837
+ }
838
+ if (provider === 'phone' && showPhone) {
839
+ return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsxRuntime.jsx("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" }) }), jsxRuntime.jsx("span", { children: "Continue with Phone" })] }, "phone"));
840
+ }
841
+ if (provider === 'magic-link' && showMagicLink) {
842
+ return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) }), jsxRuntime.jsx("span", { children: "Continue with Magic Link" })] }, "magic-link"));
843
+ }
844
+ if (provider === 'email' && showEmail && emailDisplayMode === 'button') {
845
+ return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) }), jsxRuntime.jsx("span", { children: "Continue with Email" })] }, "email"));
846
+ }
847
+ return null;
848
+ };
849
+ return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, config: customization, children: emailDisplayMode === 'button' ? (jsxRuntime.jsx("div", { className: "auth-provider-buttons", children: orderedProviders.concat(showEmail ? ['email'] : []).map(provider => renderProviderButton(provider)) })) : (
850
+ /* Form mode: show email form first, then other providers */
851
+ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showEmail && (jsxRuntime.jsxs("div", { className: "auth-form", children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { className: "auth-label", children: "Email" }), jsxRuntime.jsx("input", { type: "email", className: "auth-input", placeholder: "Enter your email", disabled: true })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { className: "auth-label", children: "Password" }), jsxRuntime.jsx("input", { type: "password", className: "auth-input", placeholder: "Enter your password", disabled: true })] }), jsxRuntime.jsx("button", { className: "auth-button auth-button-primary", disabled: true, children: "Sign In" }), jsxRuntime.jsx("div", { style: { textAlign: 'center', marginTop: '1rem' }, children: jsxRuntime.jsx("button", { className: "auth-link", disabled: true, children: "Forgot password?" }) }), jsxRuntime.jsxs("div", { style: { textAlign: 'center', marginTop: '0.5rem', fontSize: '0.875rem', color: 'var(--auth-text-muted, #6B7280)' }, children: ["Don't have an account?", ' ', jsxRuntime.jsx("button", { className: "auth-link", disabled: true, children: "Sign up" })] })] })), hasOtherProviders && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showEmail && (jsxRuntime.jsx("div", { className: "auth-or-divider", children: jsxRuntime.jsx("span", { children: "or continue with" }) })), jsxRuntime.jsx("div", { className: "auth-provider-buttons", children: orderedProviders.map(provider => renderProviderButton(provider)) })] }))] })) }));
400
852
  };
401
853
 
402
- const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, }) => {
403
- const [mode, setMode] = react.useState('login');
854
+ // Default Smartlinks Google OAuth Client ID (public - safe to expose)
855
+ const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
856
+ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, }) => {
857
+ const [mode, setMode] = react.useState(initialMode);
404
858
  const [loading, setLoading] = react.useState(false);
405
859
  const [error, setError] = react.useState();
406
860
  const [resetSuccess, setResetSuccess] = react.useState(false);
407
861
  const [authSuccess, setAuthSuccess] = react.useState(false);
408
862
  const [successMessage, setSuccessMessage] = react.useState();
409
- const [verificationId, setVerificationId] = react.useState();
410
863
  const [showResendVerification, setShowResendVerification] = react.useState(false);
411
864
  const [resendEmail, setResendEmail] = react.useState();
412
865
  const [showRequestNewReset, setShowRequestNewReset] = react.useState(false);
@@ -414,8 +867,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
414
867
  const [resetToken, setResetToken] = react.useState(); // Store the reset token from URL
415
868
  const [config, setConfig] = react.useState(null);
416
869
  const [configLoading, setConfigLoading] = react.useState(!skipConfigFetch);
870
+ const [showEmailForm, setShowEmailForm] = react.useState(false); // Track if email form should be shown when emailDisplayMode is 'button'
417
871
  const api = new AuthAPI(apiEndpoint, clientId, clientName);
418
872
  const auth = useAuth();
873
+ // Reinitialize Smartlinks SDK when apiEndpoint changes (for test/dev scenarios)
874
+ // IMPORTANT: Preserve bearer token during reinitialization
875
+ react.useEffect(() => {
876
+ if (apiEndpoint) {
877
+ // Get current token before reinitializing
878
+ const currentToken = auth.getToken();
879
+ smartlinks__namespace.initializeApi({
880
+ baseURL: apiEndpoint,
881
+ proxyMode: false, // Direct API calls when custom endpoint is provided
882
+ ngrokSkipBrowserWarning: true,
883
+ });
884
+ // Restore bearer token after reinitialization using auth.verifyToken
885
+ if (currentToken) {
886
+ smartlinks__namespace.auth.verifyToken(currentToken).catch(err => {
887
+ console.warn('Failed to restore bearer token after reinit:', err);
888
+ });
889
+ }
890
+ }
891
+ }, [apiEndpoint, auth]);
419
892
  // Get the effective redirect URL (use prop or default to current page)
420
893
  const getRedirectUrl = () => {
421
894
  if (redirectUrl)
@@ -474,6 +947,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
474
947
  };
475
948
  fetchConfig();
476
949
  }, [apiEndpoint, clientId, customization, skipConfigFetch]);
950
+ // Reset showEmailForm when mode changes away from login/register
951
+ react.useEffect(() => {
952
+ if (mode !== 'login' && mode !== 'register') {
953
+ setShowEmailForm(false);
954
+ }
955
+ }, [mode]);
477
956
  // Handle URL-based auth flows (email verification, password reset)
478
957
  react.useEffect(() => {
479
958
  // Helper to get URL parameters from either hash or search
@@ -504,10 +983,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
504
983
  if (urlMode === 'verifyEmail') {
505
984
  console.log('Verifying email with token:', token);
506
985
  const response = await api.verifyEmailWithToken(token);
507
- // Get email verification mode from config (default: verify-then-auto-login)
508
- const verificationMode = config?.emailVerification?.mode || 'verify-then-auto-login';
509
- if (verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') {
510
- // Auto-login modes: Log the user in immediately
986
+ // Get email verification mode from response or config
987
+ const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
988
+ if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
989
+ // Auto-login modes: Log the user in immediately if token is provided
511
990
  auth.login(response.token, response.user, response.accountData);
512
991
  setAuthSuccess(true);
513
992
  setSuccessMessage('Email verified successfully! You are now logged in.');
@@ -523,7 +1002,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
523
1002
  }
524
1003
  }
525
1004
  else {
526
- // verify-then-manual-login mode: Show success but require manual login
1005
+ // verify-then-manual-login mode or no token: Show success but require manual login
527
1006
  setAuthSuccess(true);
528
1007
  setSuccessMessage('Email verified successfully! Please log in with your credentials.');
529
1008
  // Clear the URL parameters
@@ -546,6 +1025,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
546
1025
  const cleanUrl = window.location.href.split('?')[0];
547
1026
  window.history.replaceState({}, document.title, cleanUrl);
548
1027
  }
1028
+ else if (urlMode === 'magicLink') {
1029
+ console.log('Verifying magic link token:', token);
1030
+ const response = await api.verifyMagicLink(token);
1031
+ // Auto-login with magic link if token is provided
1032
+ if (response.token) {
1033
+ auth.login(response.token, response.user, response.accountData);
1034
+ setAuthSuccess(true);
1035
+ setSuccessMessage('Magic link verified! You are now logged in.');
1036
+ onAuthSuccess(response.token, response.user, response.accountData);
1037
+ // Clear the URL parameters
1038
+ const cleanUrl = window.location.href.split('?')[0];
1039
+ window.history.replaceState({}, document.title, cleanUrl);
1040
+ // Redirect after a brief delay to show success message
1041
+ if (redirectUrl) {
1042
+ setTimeout(() => {
1043
+ window.location.href = redirectUrl;
1044
+ }, 2000);
1045
+ }
1046
+ }
1047
+ else {
1048
+ throw new Error('Authentication failed - no token received');
1049
+ }
1050
+ }
549
1051
  }
550
1052
  catch (err) {
551
1053
  console.error('URL-based auth error:', err);
@@ -568,6 +1070,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
568
1070
  const cleanUrl = window.location.href.split('?')[0];
569
1071
  window.history.replaceState({}, document.title, cleanUrl);
570
1072
  }
1073
+ else if (urlMode === 'magicLink') {
1074
+ // If magic link is invalid/expired
1075
+ setError(`${errorMessage} - Please request a new magic link below.`);
1076
+ setMode('magic-link');
1077
+ // Clear the URL parameters
1078
+ const cleanUrl = window.location.href.split('?')[0];
1079
+ window.history.replaceState({}, document.title, cleanUrl);
1080
+ }
571
1081
  else {
572
1082
  setError(errorMessage);
573
1083
  }
@@ -589,17 +1099,22 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
589
1099
  accountData: mode === 'register' ? accountData : undefined,
590
1100
  redirectUrl: getRedirectUrl(), // Include redirect URL for email verification
591
1101
  });
592
- // Get email verification mode from config (default: verify-then-auto-login)
593
- const verificationMode = config?.emailVerification?.mode || 'verify-then-auto-login';
1102
+ // Get email verification mode from response or config (default: verify-then-auto-login)
1103
+ const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
594
1104
  const gracePeriodHours = config?.emailVerification?.gracePeriodHours || 24;
595
1105
  if (mode === 'register') {
596
1106
  // Handle different verification modes
597
- if (verificationMode === 'immediate') {
598
- // Immediate mode: Log in right away
1107
+ if (verificationMode === 'immediate' && response.token) {
1108
+ // Immediate mode: Log in right away if token is provided
599
1109
  auth.login(response.token, response.user, response.accountData);
600
1110
  setAuthSuccess(true);
601
- setSuccessMessage(`Account created! You're logged in now. Please verify your email within ${gracePeriodHours} hours to keep your account active.`);
602
- onAuthSuccess(response.token, response.user, response.accountData);
1111
+ const deadline = response.emailVerificationDeadline
1112
+ ? new Date(response.emailVerificationDeadline).toLocaleString()
1113
+ : `${gracePeriodHours} hours`;
1114
+ setSuccessMessage(`Account created! You're logged in now. Please verify your email by ${deadline} to keep your account active.`);
1115
+ if (response.token) {
1116
+ onAuthSuccess(response.token, response.user, response.accountData);
1117
+ }
603
1118
  if (redirectUrl) {
604
1119
  setTimeout(() => {
605
1120
  window.location.href = redirectUrl;
@@ -618,15 +1133,27 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
618
1133
  }
619
1134
  }
620
1135
  else {
621
- // Login mode - always log in
622
- auth.login(response.token, response.user, response.accountData);
623
- setAuthSuccess(true);
624
- setSuccessMessage('Login successful!');
625
- onAuthSuccess(response.token, response.user, response.accountData);
626
- if (redirectUrl) {
627
- setTimeout(() => {
628
- window.location.href = redirectUrl;
629
- }, 2000);
1136
+ // Login mode - always log in if token is provided
1137
+ if (response.token) {
1138
+ // Check for account lock or verification requirements
1139
+ if (response.accountLocked) {
1140
+ throw new Error('Your account has been locked due to unverified email. Please check your email or request a new verification link.');
1141
+ }
1142
+ if (response.requiresEmailVerification) {
1143
+ throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
1144
+ }
1145
+ auth.login(response.token, response.user, response.accountData);
1146
+ setAuthSuccess(true);
1147
+ setSuccessMessage('Login successful!');
1148
+ onAuthSuccess(response.token, response.user, response.accountData);
1149
+ if (redirectUrl) {
1150
+ setTimeout(() => {
1151
+ window.location.href = redirectUrl;
1152
+ }, 2000);
1153
+ }
1154
+ }
1155
+ else {
1156
+ throw new Error('Authentication failed - please verify your email before logging in.');
630
1157
  }
631
1158
  }
632
1159
  }
@@ -691,19 +1218,104 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
691
1218
  }
692
1219
  };
693
1220
  const handleGoogleLogin = async () => {
1221
+ // Use custom client ID from config, or fall back to default Smartlinks client ID
1222
+ const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
1223
+ // Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
1224
+ const oauthFlow = config?.googleOAuthFlow || 'oneTap';
694
1225
  setLoading(true);
695
1226
  setError(undefined);
696
1227
  try {
697
- // In a real implementation, this would open Google OAuth flow
698
- // For now, this is a placeholder that the consuming app would implement
699
- throw new Error('Google OAuth flow must be implemented in your application');
1228
+ const google = window.google;
1229
+ if (!google) {
1230
+ throw new Error('Google Identity Services not loaded. Please check your internet connection.');
1231
+ }
1232
+ if (oauthFlow === 'popup') {
1233
+ // Use OAuth2 popup flow (works in iframes but requires popup permission)
1234
+ if (!google.accounts.oauth2) {
1235
+ throw new Error('Google OAuth2 not available');
1236
+ }
1237
+ const client = google.accounts.oauth2.initTokenClient({
1238
+ client_id: googleClientId,
1239
+ scope: 'openid email profile',
1240
+ callback: async (response) => {
1241
+ try {
1242
+ if (response.error) {
1243
+ throw new Error(response.error_description || response.error);
1244
+ }
1245
+ const accessToken = response.access_token;
1246
+ // Send access token to backend
1247
+ const authResponse = await api.loginWithGoogle(accessToken);
1248
+ if (authResponse.token) {
1249
+ auth.login(authResponse.token, authResponse.user, authResponse.accountData);
1250
+ setAuthSuccess(true);
1251
+ setSuccessMessage('Google login successful!');
1252
+ onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
1253
+ }
1254
+ else {
1255
+ throw new Error('Authentication failed - no token received');
1256
+ }
1257
+ if (redirectUrl) {
1258
+ setTimeout(() => {
1259
+ window.location.href = redirectUrl;
1260
+ }, 2000);
1261
+ }
1262
+ setLoading(false);
1263
+ }
1264
+ catch (err) {
1265
+ const errorMessage = err instanceof Error ? err.message : 'Google login failed';
1266
+ setError(errorMessage);
1267
+ onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
1268
+ setLoading(false);
1269
+ }
1270
+ },
1271
+ });
1272
+ client.requestAccessToken();
1273
+ }
1274
+ else {
1275
+ // Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
1276
+ google.accounts.id.initialize({
1277
+ client_id: googleClientId,
1278
+ callback: async (response) => {
1279
+ try {
1280
+ const idToken = response.credential;
1281
+ const authResponse = await api.loginWithGoogle(idToken);
1282
+ if (authResponse.token) {
1283
+ auth.login(authResponse.token, authResponse.user, authResponse.accountData);
1284
+ setAuthSuccess(true);
1285
+ setSuccessMessage('Google login successful!');
1286
+ onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
1287
+ }
1288
+ else {
1289
+ throw new Error('Authentication failed - no token received');
1290
+ }
1291
+ if (redirectUrl) {
1292
+ setTimeout(() => {
1293
+ window.location.href = redirectUrl;
1294
+ }, 2000);
1295
+ }
1296
+ setLoading(false);
1297
+ }
1298
+ catch (err) {
1299
+ const errorMessage = err instanceof Error ? err.message : 'Google login failed';
1300
+ setError(errorMessage);
1301
+ onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
1302
+ setLoading(false);
1303
+ }
1304
+ },
1305
+ auto_select: false,
1306
+ cancel_on_tap_outside: true,
1307
+ });
1308
+ google.accounts.id.prompt((notification) => {
1309
+ if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
1310
+ setLoading(false);
1311
+ }
1312
+ });
1313
+ }
700
1314
  }
701
1315
  catch (err) {
702
1316
  const errorMessage = err instanceof Error ? err.message : 'Google login failed';
703
1317
  setError(errorMessage);
704
1318
  onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
705
- }
706
- finally {
707
1319
  setLoading(false);
708
1320
  }
709
1321
  };
@@ -712,21 +1324,24 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
712
1324
  setError(undefined);
713
1325
  try {
714
1326
  if (!verificationCode) {
715
- // Send verification code
716
- const result = await api.sendPhoneCode(phoneNumber);
717
- setVerificationId(result.verificationId);
1327
+ // Send verification code via Twilio Verify Service
1328
+ await api.sendPhoneCode(phoneNumber);
1329
+ // Twilio Verify Service tracks the verification by phone number
1330
+ // No need to store verificationId
718
1331
  }
719
1332
  else {
720
- // Verify code
721
- if (!verificationId) {
722
- throw new Error('Verification ID not found');
1333
+ // Verify code - Twilio identifies the verification by phone number
1334
+ const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
1335
+ // Update auth context with account data if token is provided
1336
+ if (response.token) {
1337
+ auth.login(response.token, response.user, response.accountData);
1338
+ onAuthSuccess(response.token, response.user, response.accountData);
1339
+ if (redirectUrl) {
1340
+ window.location.href = redirectUrl;
1341
+ }
723
1342
  }
724
- const response = await api.verifyPhoneCode(verificationId, verificationCode);
725
- // Update auth context with account data
726
- auth.login(response.token, response.user, response.accountData);
727
- onAuthSuccess(response.token, response.user, response.accountData);
728
- if (redirectUrl) {
729
- window.location.href = redirectUrl;
1343
+ else {
1344
+ throw new Error('Authentication failed - no token received');
730
1345
  }
731
1346
  }
732
1347
  }
@@ -764,6 +1379,23 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
764
1379
  setLoading(false);
765
1380
  }
766
1381
  };
1382
+ const handleMagicLink = async (email) => {
1383
+ setLoading(true);
1384
+ setError(undefined);
1385
+ try {
1386
+ await api.sendMagicLink(email, getRedirectUrl());
1387
+ setAuthSuccess(true);
1388
+ setSuccessMessage('Magic link sent! Check your email to log in.');
1389
+ }
1390
+ catch (err) {
1391
+ const errorMessage = err instanceof Error ? err.message : 'Failed to send magic link';
1392
+ setError(errorMessage);
1393
+ onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
1394
+ }
1395
+ finally {
1396
+ setLoading(false);
1397
+ }
1398
+ };
767
1399
  if (configLoading) {
768
1400
  return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, children: jsxRuntime.jsx("div", { style: { textAlign: 'center', padding: '2rem' }, children: jsxRuntime.jsx("div", { className: "auth-spinner" }) }) }));
769
1401
  }
@@ -776,10 +1408,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
776
1408
  fontSize: '1.5rem',
777
1409
  fontWeight: 600
778
1410
  }, children: successMessage?.includes('verified') ? 'Email Verified!' :
779
- mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsxRuntime.jsx("p", { style: {
1411
+ successMessage?.includes('Magic link') ? 'Check Your Email!' :
1412
+ mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsxRuntime.jsx("p", { style: {
780
1413
  color: '#6B7280',
781
1414
  fontSize: '0.875rem'
782
- }, children: successMessage })] })) : mode === 'phone' ? (jsxRuntime.jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error })) : mode === 'reset-password' ? (jsxRuntime.jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
1415
+ }, 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: () => {
783
1416
  setMode('login');
784
1417
  setResetSuccess(false);
785
1418
  setResetToken(undefined); // Clear token when going back
@@ -849,14 +1482,36 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
849
1482
  fontSize: '0.875rem',
850
1483
  fontWeight: 500,
851
1484
  opacity: loading ? 0.6 : 1
852
- }, children: "Cancel" })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
853
- setMode(mode === 'login' ? 'register' : 'login');
854
- setShowResendVerification(false);
855
- setShowRequestNewReset(false);
856
- setError(undefined);
857
- }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error }), ((config?.enabledProviders || enabledProviders).length > 1) && (jsxRuntime.jsx(ProviderButtons, { enabledProviders: (config?.enabledProviders || enabledProviders).filter((p) => p !== 'email'), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), loading: loading }))] })) })) : null }));
1485
+ }, children: "Cancel" })] })] })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: (() => {
1486
+ const emailDisplayMode = config?.emailDisplayMode || 'form';
1487
+ const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
1488
+ const actualProviders = config?.enabledProviders || enabledProviders;
1489
+ // Button mode: show provider selection first, then email form if email is selected
1490
+ if (emailDisplayMode === 'button' && !showEmailForm) {
1491
+ return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
1492
+ }
1493
+ // Form mode or email button was clicked: show email form with other providers
1494
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [emailDisplayMode === 'button' && showEmailForm && (jsxRuntime.jsx("button", { onClick: () => setShowEmailForm(false), style: {
1495
+ marginBottom: '1rem',
1496
+ padding: '0.5rem',
1497
+ background: 'none',
1498
+ border: 'none',
1499
+ color: 'var(--auth-text-color, #6B7280)',
1500
+ cursor: 'pointer',
1501
+ fontSize: '0.875rem',
1502
+ display: 'flex',
1503
+ alignItems: 'center',
1504
+ gap: '0.25rem'
1505
+ }, children: "\u2190 Back to options" })), jsxRuntime.jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
1506
+ setMode(mode === 'login' ? 'register' : 'login');
1507
+ setShowResendVerification(false);
1508
+ setShowRequestNewReset(false);
1509
+ setError(undefined);
1510
+ }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error }), 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 }))] }));
1511
+ })() })) })) : null }));
858
1512
  };
859
1513
 
1514
+ exports.AccountManagement = AccountManagement;
860
1515
  exports.AuthProvider = AuthProvider;
861
1516
  exports.AuthUIPreview = AuthUIPreview;
862
1517
  exports.FirebaseAuthUI = SmartlinksAuthUI;