@proveanything/smartlinks-auth-ui 0.1.1 → 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.esm.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useEffect, useState, createContext, useCallback, useContext } from 'react';
2
+ import { useEffect, useState, useRef, createContext, useCallback, useContext } from 'react';
3
3
  import * as smartlinks from '@proveanything/smartlinks';
4
+ import PhoneInputComponent from 'react-phone-number-input';
5
+ import 'react-phone-number-input/style.css';
4
6
 
5
7
  const AuthContainer = ({ children, theme = 'light', className = '', config, }) => {
6
8
  // Apply CSS variables for customization
@@ -64,20 +66,159 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
64
66
  : 'Get started by creating your account.' })] }), 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 Doe" })] })), 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 === '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') }), 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' })] })] }));
65
67
  };
66
68
 
67
- const ProviderButtons = ({ enabledProviders, onGoogleLogin, onPhoneLogin, loading, }) => {
69
+ const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
68
70
  if (enabledProviders.length === 0)
69
71
  return null;
70
- return (jsxs(Fragment, { children: [jsx("div", { className: "auth-or-divider", children: jsx("span", { children: "or continue with" }) }), jsxs("div", { className: "auth-provider-buttons", children: [enabledProviders.includes('google') && (jsxs("button", { type: "button", className: "auth-provider-button", onClick: onGoogleLogin, disabled: loading, children: [jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [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" }), 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" }), 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" }), 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') && (jsxs("button", { type: "button", className: "auth-provider-button", onClick: onPhoneLogin, disabled: loading, children: [jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: 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"] }))] })] }));
72
+ // Determine the order of providers to display
73
+ const orderedProviders = providerOrder && providerOrder.length > 0
74
+ ? providerOrder.filter(p => enabledProviders.includes(p))
75
+ : enabledProviders;
76
+ // Provider button configurations
77
+ const providerConfigs = {
78
+ email: {
79
+ label: 'Continue with Email',
80
+ icon: (jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: 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" }) })),
81
+ onClick: () => onEmailLogin?.()
82
+ },
83
+ google: {
84
+ label: 'Continue with Google',
85
+ icon: (jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [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" }), 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" }), 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" }), 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" })] })),
86
+ onClick: onGoogleLogin
87
+ },
88
+ phone: {
89
+ label: 'Continue with Phone',
90
+ icon: (jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: 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" }) })),
91
+ onClick: onPhoneLogin
92
+ },
93
+ 'magic-link': {
94
+ label: 'Continue with Magic Link',
95
+ icon: (jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: 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" }) })),
96
+ onClick: () => onMagicLinkLogin?.()
97
+ }
98
+ };
99
+ // Show divider only if email handler is not provided (meaning this is showing alternative providers, not all providers)
100
+ const showDivider = !onEmailLogin;
101
+ return (jsxs(Fragment, { children: [showDivider && (jsx("div", { className: "auth-or-divider", children: jsx("span", { children: "or continue with" }) })), jsx("div", { className: "auth-provider-buttons", children: orderedProviders.map((provider) => {
102
+ const config = providerConfigs[provider];
103
+ if (!config)
104
+ return null;
105
+ return (jsxs("button", { type: "button", className: "auth-provider-button", onClick: config.onClick, disabled: loading, children: [config.icon, config.label] }, provider));
106
+ }) })] }));
107
+ };
108
+
109
+ const OTPInput = ({ length = 6, value, onChange, disabled = false, }) => {
110
+ const [otp, setOtp] = useState(Array(length).fill(''));
111
+ const inputRefs = useRef([]);
112
+ useEffect(() => {
113
+ const digits = value.split('').slice(0, length);
114
+ const newOtp = [...digits, ...Array(Math.max(0, length - digits.length)).fill('')];
115
+ setOtp(newOtp);
116
+ }, [value, length]);
117
+ const handleChange = (index, digit) => {
118
+ if (disabled)
119
+ return;
120
+ // Only allow numbers
121
+ if (digit && !/^\d$/.test(digit))
122
+ return;
123
+ const newOtp = [...otp];
124
+ newOtp[index] = digit;
125
+ setOtp(newOtp);
126
+ // Call onChange with the full OTP value
127
+ onChange(newOtp.join(''));
128
+ // Move to next input if digit was entered
129
+ if (digit && index < length - 1) {
130
+ inputRefs.current[index + 1]?.focus();
131
+ }
132
+ };
133
+ const handleKeyDown = (index, e) => {
134
+ if (disabled)
135
+ return;
136
+ // Handle backspace
137
+ if (e.key === 'Backspace' && !otp[index] && index > 0) {
138
+ inputRefs.current[index - 1]?.focus();
139
+ }
140
+ // Handle arrow keys
141
+ if (e.key === 'ArrowLeft' && index > 0) {
142
+ inputRefs.current[index - 1]?.focus();
143
+ }
144
+ if (e.key === 'ArrowRight' && index < length - 1) {
145
+ inputRefs.current[index + 1]?.focus();
146
+ }
147
+ };
148
+ const handlePaste = (e) => {
149
+ if (disabled)
150
+ return;
151
+ e.preventDefault();
152
+ const pastedData = e.clipboardData.getData('text/plain').slice(0, length);
153
+ const digits = pastedData.replace(/\D/g, '').split('');
154
+ const newOtp = [...Array(length).fill('')];
155
+ digits.forEach((digit, idx) => {
156
+ if (idx < length) {
157
+ newOtp[idx] = digit;
158
+ }
159
+ });
160
+ setOtp(newOtp);
161
+ onChange(newOtp.join(''));
162
+ // Focus the next empty input or the last one
163
+ const nextEmptyIndex = newOtp.findIndex(digit => !digit);
164
+ const focusIndex = nextEmptyIndex === -1 ? length - 1 : nextEmptyIndex;
165
+ inputRefs.current[focusIndex]?.focus();
166
+ };
167
+ return (jsx("div", { className: "otp-input-container", children: otp.map((digit, index) => (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))) }));
168
+ };
169
+
170
+ const PhoneInput = ({ value, onChange, disabled = false, }) => {
171
+ const [defaultCountry, setDefaultCountry] = useState('US');
172
+ useEffect(() => {
173
+ // Auto-detect country based on user's location
174
+ const detectCountry = async () => {
175
+ try {
176
+ const response = await fetch('https://ipapi.co/json/');
177
+ const data = await response.json();
178
+ if (data.country_code) {
179
+ setDefaultCountry(data.country_code);
180
+ }
181
+ }
182
+ catch (error) {
183
+ console.log('Could not detect country, using default');
184
+ }
185
+ };
186
+ detectCountry();
187
+ }, []);
188
+ return (jsx(PhoneInputComponent, { international: true, defaultCountry: defaultCountry, value: value, onChange: (value) => onChange(value || ''), disabled: disabled, className: "phone-input-wrapper" }));
71
189
  };
72
190
 
73
191
  const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
74
192
  const [phoneNumber, setPhoneNumber] = useState('');
75
193
  const [verificationCode, setVerificationCode] = useState('');
76
194
  const [codeSent, setCodeSent] = useState(false);
195
+ const [resendCooldown, setResendCooldown] = useState(0);
196
+ // Countdown timer for resend button
197
+ useEffect(() => {
198
+ if (resendCooldown > 0) {
199
+ const timer = setTimeout(() => {
200
+ setResendCooldown(resendCooldown - 1);
201
+ }, 1000);
202
+ return () => clearTimeout(timer);
203
+ }
204
+ }, [resendCooldown]);
77
205
  const handleSendCode = async (e) => {
78
206
  e.preventDefault();
79
207
  await onSubmit(phoneNumber);
80
208
  setCodeSent(true);
209
+ setResendCooldown(60); // 60 second cooldown
210
+ };
211
+ const handleResendCode = async () => {
212
+ if (resendCooldown > 0)
213
+ return;
214
+ try {
215
+ await onSubmit(phoneNumber);
216
+ setResendCooldown(60); // Reset cooldown
217
+ setVerificationCode(''); // Clear the code input
218
+ }
219
+ catch (err) {
220
+ // Error will be handled by parent component
221
+ }
81
222
  };
82
223
  const handleVerifyCode = async (e) => {
83
224
  e.preventDefault();
@@ -85,7 +226,12 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
85
226
  };
86
227
  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
87
228
  ? 'Enter the verification code sent to your phone.'
88
- : '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("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" })] })) : (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "verificationCode", className: "auth-label", children: "Verification Code" }), 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}" })] })), 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" }) })] }));
229
+ : '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: {
230
+ cursor: resendCooldown > 0 ? 'not-allowed' : 'pointer',
231
+ opacity: resendCooldown > 0 ? 0.5 : 1,
232
+ }, children: resendCooldown > 0
233
+ ? `Resend code in ${resendCooldown}s`
234
+ : '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" }) })] }));
89
235
  };
90
236
 
91
237
  const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, token, }) => {
@@ -123,6 +269,15 @@ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, token, }
123
269
  : "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" }) })] }));
124
270
  };
125
271
 
272
+ const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, }) => {
273
+ const [email, setEmail] = useState('');
274
+ const handleSubmit = async (e) => {
275
+ e.preventDefault();
276
+ await onSubmit(email);
277
+ };
278
+ 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" })] }));
279
+ };
280
+
126
281
  /**
127
282
  * AuthAPI - Thin wrapper around Smartlinks SDK authKit namespace
128
283
  * All authentication operations now use the global Smartlinks SDK
@@ -151,8 +306,8 @@ class AuthAPI {
151
306
  async sendPhoneCode(phoneNumber) {
152
307
  return smartlinks.authKit.sendPhoneCode(this.clientId, phoneNumber);
153
308
  }
154
- async verifyPhoneCode(verificationId, code) {
155
- return smartlinks.authKit.verifyPhoneCode(this.clientId, verificationId, code);
309
+ async verifyPhoneCode(phoneNumber, code) {
310
+ return smartlinks.authKit.verifyPhoneCode(this.clientId, phoneNumber, code);
156
311
  }
157
312
  async requestPasswordReset(email, redirectUrl) {
158
313
  return smartlinks.authKit.requestPasswordReset(this.clientId, {
@@ -205,6 +360,15 @@ class AuthAPI {
205
360
  };
206
361
  }
207
362
  }
363
+ async sendMagicLink(email, redirectUrl) {
364
+ return smartlinks.authKit.sendMagicLink(this.clientId, {
365
+ email,
366
+ redirectUrl
367
+ });
368
+ }
369
+ async verifyMagicLink(token) {
370
+ return smartlinks.authKit.verifyMagicLink(this.clientId, token);
371
+ }
208
372
  }
209
373
 
210
374
  const TOKEN_KEY = 'smartlinks_auth_token';
@@ -294,9 +458,10 @@ const AuthProvider = ({ children }) => {
294
458
  setToken(storedToken.token);
295
459
  setUser(storedUser);
296
460
  setAccountData(storedAccountData);
297
- // Set bearer token in global Smartlinks SDK
298
- // @ts-expect-error - setBearerToken exists in runtime but may not be in type definitions
299
- smartlinks.setBearerToken(storedToken.token);
461
+ // Set bearer token in global Smartlinks SDK via auth.verifyToken
462
+ smartlinks.auth.verifyToken(storedToken.token).catch(err => {
463
+ console.warn('Failed to restore bearer token on init:', err);
464
+ });
300
465
  }
301
466
  setIsLoading(false);
302
467
  }, []);
@@ -310,9 +475,11 @@ const AuthProvider = ({ children }) => {
310
475
  setToken(authToken);
311
476
  setUser(authUser);
312
477
  setAccountData(authAccountData || null);
313
- // Set bearer token in global Smartlinks SDK
314
- // @ts-expect-error - setBearerToken exists in runtime but may not be in type definitions
315
- smartlinks.setBearerToken(authToken);
478
+ // Set bearer token in global Smartlinks SDK via auth.verifyToken
479
+ // This both validates the token and sets it for future API calls
480
+ smartlinks.auth.verifyToken(authToken).catch(err => {
481
+ console.warn('Failed to set bearer token on login:', err);
482
+ });
316
483
  }, []);
317
484
  const logout = useCallback(async () => {
318
485
  // Clear local storage
@@ -321,8 +488,7 @@ const AuthProvider = ({ children }) => {
321
488
  setUser(null);
322
489
  setAccountData(null);
323
490
  // Clear bearer token from global Smartlinks SDK
324
- // @ts-expect-error - setBearerToken exists in runtime but may not be in type definitions
325
- smartlinks.setBearerToken(undefined);
491
+ smartlinks.auth.logout();
326
492
  }, []);
327
493
  const getToken = useCallback(() => {
328
494
  const storedToken = tokenStorage.getToken();
@@ -352,6 +518,269 @@ const useAuth = () => {
352
518
  return context;
353
519
  };
354
520
 
521
+ const AccountManagement = ({ apiEndpoint, clientId, onProfileUpdated, onEmailChangeRequested, onPasswordChanged, onAccountDeleted, onError, theme = 'light', className = '', customization = {}, }) => {
522
+ const auth = useAuth();
523
+ const [loading, setLoading] = useState(false);
524
+ const [profile, setProfile] = useState(null);
525
+ const [error, setError] = useState();
526
+ const [success, setSuccess] = useState();
527
+ // Profile form state
528
+ const [displayName, setDisplayName] = useState('');
529
+ // Email change state
530
+ const [newEmail, setNewEmail] = useState('');
531
+ const [emailPassword, setEmailPassword] = useState('');
532
+ // Password change state
533
+ const [currentPassword, setCurrentPassword] = useState('');
534
+ const [newPassword, setNewPassword] = useState('');
535
+ const [confirmPassword, setConfirmPassword] = useState('');
536
+ // Phone change state (reuses existing sendPhoneCode flow)
537
+ const [newPhone, setNewPhone] = useState('');
538
+ const [phoneCode, setPhoneCode] = useState('');
539
+ const [phoneCodeSent, setPhoneCodeSent] = useState(false);
540
+ // Account deletion state
541
+ const [deletePassword, setDeletePassword] = useState('');
542
+ const [deleteConfirmText, setDeleteConfirmText] = useState('');
543
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
544
+ const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = true, } = customization;
545
+ // Reinitialize Smartlinks SDK when apiEndpoint changes
546
+ useEffect(() => {
547
+ if (apiEndpoint) {
548
+ smartlinks.initializeApi({
549
+ baseURL: apiEndpoint,
550
+ proxyMode: false,
551
+ ngrokSkipBrowserWarning: true,
552
+ });
553
+ }
554
+ }, [apiEndpoint]);
555
+ // Load user profile on mount
556
+ useEffect(() => {
557
+ loadProfile();
558
+ }, [clientId]);
559
+ const loadProfile = async () => {
560
+ if (!auth.isAuthenticated) {
561
+ setError('You must be logged in to manage your account');
562
+ return;
563
+ }
564
+ setLoading(true);
565
+ setError(undefined);
566
+ try {
567
+ // TODO: Backend implementation required
568
+ // Endpoint: GET /api/v1/authkit/:clientId/account/profile
569
+ // SDK method: smartlinks.authKit.getProfile(clientId)
570
+ // Temporary mock data for UI testing
571
+ const profileData = {
572
+ uid: auth.user?.uid || '',
573
+ email: auth.user?.email,
574
+ displayName: auth.user?.displayName,
575
+ phoneNumber: auth.user?.phoneNumber,
576
+ photoURL: auth.user?.photoURL,
577
+ emailVerified: true,
578
+ accountData: auth.accountData || {},
579
+ };
580
+ setProfile(profileData);
581
+ setDisplayName(profileData.displayName || '');
582
+ }
583
+ catch (err) {
584
+ const errorMessage = err instanceof Error ? err.message : 'Failed to load profile';
585
+ setError(errorMessage);
586
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
587
+ }
588
+ finally {
589
+ setLoading(false);
590
+ }
591
+ };
592
+ const handleUpdateProfile = async (e) => {
593
+ e.preventDefault();
594
+ setLoading(true);
595
+ setError(undefined);
596
+ setSuccess(undefined);
597
+ try {
598
+ // TODO: Backend implementation required
599
+ // Endpoint: POST /api/v1/authkit/:clientId/account/update-profile
600
+ // SDK method: smartlinks.authKit.updateProfile(clientId, updateData)
601
+ setError('Backend API not yet implemented. See console for required endpoint.');
602
+ console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-profile');
603
+ console.log('Update data:', { displayName });
604
+ // Uncomment when backend is ready:
605
+ // const updateData: ProfileUpdateData = {
606
+ // displayName: displayName || undefined,
607
+ // photoURL: photoURL || undefined,
608
+ // };
609
+ // const updatedProfile = await smartlinks.authKit.updateProfile(clientId, updateData);
610
+ // setProfile(updatedProfile);
611
+ // setSuccess('Profile updated successfully!');
612
+ // onProfileUpdated?.(updatedProfile);
613
+ }
614
+ catch (err) {
615
+ const errorMessage = err instanceof Error ? err.message : 'Failed to update profile';
616
+ setError(errorMessage);
617
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
618
+ }
619
+ finally {
620
+ setLoading(false);
621
+ }
622
+ };
623
+ const handleChangeEmail = async (e) => {
624
+ e.preventDefault();
625
+ setLoading(true);
626
+ setError(undefined);
627
+ setSuccess(undefined);
628
+ try {
629
+ // TODO: Backend implementation required
630
+ // Endpoint: POST /api/v1/authkit/:clientId/account/change-email
631
+ // SDK method: smartlinks.authKit.changeEmail(clientId, newEmail, password)
632
+ // Note: No verification flow for now - direct email update
633
+ setError('Backend API not yet implemented. See console for required endpoint.');
634
+ console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-email');
635
+ console.log('Data:', { newEmail });
636
+ // Uncomment when backend is ready:
637
+ // await smartlinks.authKit.changeEmail(clientId, newEmail, emailPassword);
638
+ // setSuccess('Email changed successfully!');
639
+ // setNewEmail('');
640
+ // setEmailPassword('');
641
+ // onEmailChangeRequested?.();
642
+ // await loadProfile(); // Reload to show new email
643
+ }
644
+ catch (err) {
645
+ const errorMessage = err instanceof Error ? err.message : 'Failed to change email';
646
+ setError(errorMessage);
647
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
648
+ }
649
+ finally {
650
+ setLoading(false);
651
+ }
652
+ };
653
+ const handleChangePassword = async (e) => {
654
+ e.preventDefault();
655
+ if (newPassword !== confirmPassword) {
656
+ setError('New passwords do not match');
657
+ return;
658
+ }
659
+ if (newPassword.length < 6) {
660
+ setError('Password must be at least 6 characters');
661
+ return;
662
+ }
663
+ setLoading(true);
664
+ setError(undefined);
665
+ setSuccess(undefined);
666
+ try {
667
+ // TODO: Backend implementation required
668
+ // Endpoint: POST /api/v1/authkit/:clientId/account/change-password
669
+ // SDK method: smartlinks.authKit.changePassword(clientId, currentPassword, newPassword)
670
+ setError('Backend API not yet implemented. See console for required endpoint.');
671
+ console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-password');
672
+ console.log('Data: currentPassword and newPassword provided');
673
+ // Uncomment when backend is ready:
674
+ // await smartlinks.authKit.changePassword(clientId, currentPassword, newPassword);
675
+ // setSuccess('Password changed successfully!');
676
+ // setCurrentPassword('');
677
+ // setNewPassword('');
678
+ // setConfirmPassword('');
679
+ // onPasswordChanged?.();
680
+ }
681
+ catch (err) {
682
+ const errorMessage = err instanceof Error ? err.message : 'Failed to change password';
683
+ setError(errorMessage);
684
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
685
+ }
686
+ finally {
687
+ setLoading(false);
688
+ }
689
+ };
690
+ const handleSendPhoneCode = async () => {
691
+ setLoading(true);
692
+ setError(undefined);
693
+ try {
694
+ await smartlinks.authKit.sendPhoneCode(clientId, newPhone);
695
+ setPhoneCodeSent(true);
696
+ setSuccess('Verification code sent to your phone');
697
+ }
698
+ catch (err) {
699
+ const errorMessage = err instanceof Error ? err.message : 'Failed to send verification code';
700
+ setError(errorMessage);
701
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
702
+ }
703
+ finally {
704
+ setLoading(false);
705
+ }
706
+ };
707
+ const handleUpdatePhone = async (e) => {
708
+ e.preventDefault();
709
+ setLoading(true);
710
+ setError(undefined);
711
+ setSuccess(undefined);
712
+ try {
713
+ // TODO: Backend implementation required
714
+ // Endpoint: POST /api/v1/authkit/:clientId/account/update-phone
715
+ // SDK method: smartlinks.authKit.updatePhone(clientId, phoneNumber, verificationCode)
716
+ setError('Backend API not yet implemented. See console for required endpoint.');
717
+ console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-phone');
718
+ console.log('Data:', { phoneNumber: newPhone, code: phoneCode });
719
+ // Uncomment when backend is ready:
720
+ // await smartlinks.authKit.updatePhone(clientId, newPhone, phoneCode);
721
+ // setSuccess('Phone number updated successfully!');
722
+ // setNewPhone('');
723
+ // setPhoneCode('');
724
+ // setPhoneCodeSent(false);
725
+ // await loadProfile();
726
+ }
727
+ catch (err) {
728
+ const errorMessage = err instanceof Error ? err.message : 'Failed to update phone number';
729
+ setError(errorMessage);
730
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
731
+ }
732
+ finally {
733
+ setLoading(false);
734
+ }
735
+ };
736
+ const handleDeleteAccount = async () => {
737
+ if (deleteConfirmText !== 'DELETE') {
738
+ setError('Please type DELETE to confirm account deletion');
739
+ return;
740
+ }
741
+ if (!deletePassword) {
742
+ setError('Password is required');
743
+ return;
744
+ }
745
+ setLoading(true);
746
+ setError(undefined);
747
+ try {
748
+ // TODO: Backend implementation required
749
+ // Endpoint: DELETE /api/v1/authkit/:clientId/account/delete
750
+ // SDK method: smartlinks.authKit.deleteAccount(clientId, password, confirmText)
751
+ // Note: This performs a SOFT DELETE (marks as deleted, obfuscates email)
752
+ setError('Backend API not yet implemented. See console for required endpoint.');
753
+ console.log('Required API endpoint: DELETE /api/v1/authkit/:clientId/account/delete');
754
+ console.log('Data: password and confirmText="DELETE" provided');
755
+ console.log('Note: Backend should soft delete (mark deleted, obfuscate email, disable account)');
756
+ // Uncomment when backend is ready:
757
+ // await smartlinks.authKit.deleteAccount(clientId, deletePassword, deleteConfirmText);
758
+ // setSuccess('Account deleted successfully');
759
+ // onAccountDeleted?.();
760
+ // await auth.logout();
761
+ }
762
+ catch (err) {
763
+ const errorMessage = err instanceof Error ? err.message : 'Failed to delete account';
764
+ setError(errorMessage);
765
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
766
+ }
767
+ finally {
768
+ setLoading(false);
769
+ }
770
+ };
771
+ if (!auth.isAuthenticated) {
772
+ return (jsx(AuthContainer, { theme: theme, className: className, children: jsxs("div", { className: "auth-form", children: [jsx("h2", { className: "auth-title", children: "Account Management" }), jsx("p", { className: "text-muted-foreground", children: "Please log in to manage your account" })] }) }));
773
+ }
774
+ if (loading && !profile) {
775
+ return (jsx(AuthContainer, { theme: theme, className: className, children: jsx("div", { className: "auth-form", children: jsx("h2", { className: "auth-title", children: "Loading..." }) }) }));
776
+ }
777
+ return (jsx(AuthContainer, { theme: theme, className: className, children: jsxs("div", { className: "auth-form", style: { maxWidth: '600px' }, children: [jsx("h2", { className: "auth-title", children: "Account Management" }), error && (jsx("div", { className: "auth-error", role: "alert", children: error })), success && (jsx("div", { className: "auth-success", role: "alert", children: success })), showProfileSection && (jsxs("section", { className: "account-section", children: [jsx("h3", { className: "section-title", children: "Profile Information" }), jsxs("form", { onSubmit: handleUpdateProfile, className: "auth-form-fields", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "displayName", children: "Display Name" }), jsx("input", { id: "displayName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), placeholder: "Your display name", className: "auth-input" })] }), jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Updating...' : 'Update Profile' })] })] })), showEmailSection && (jsxs("section", { className: "account-section", children: [jsx("h3", { className: "section-title", children: "Email Management" }), jsxs("div", { className: "current-info", children: [jsxs("p", { children: [jsx("strong", { children: "Current Email:" }), " ", profile?.email || 'Not set'] }), jsxs("p", { children: [jsx("strong", { children: "Status:" }), ' ', jsx("span", { className: profile?.emailVerified ? 'text-success' : 'text-warning', children: profile?.emailVerified ? 'Verified' : 'Unverified' })] })] }), jsxs("form", { onSubmit: handleChangeEmail, className: "auth-form-fields", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newEmail", children: "New Email" }), jsx("input", { id: "newEmail", type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "new.email@example.com", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "emailPassword", children: "Confirm Password" }), jsx("input", { id: "emailPassword", type: "password", value: emailPassword, onChange: (e) => setEmailPassword(e.target.value), placeholder: "Enter your password", className: "auth-input", required: true })] }), jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Sending...' : 'Change Email' })] })] })), showPasswordSection && (jsxs("section", { className: "account-section", children: [jsx("h3", { className: "section-title", children: "Password Management" }), jsxs("form", { onSubmit: handleChangePassword, className: "auth-form-fields", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "currentPassword", children: "Current Password" }), jsx("input", { id: "currentPassword", type: "password", value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value), placeholder: "Enter current password", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPassword", children: "New Password" }), 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 })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "confirmPassword", children: "Confirm New Password" }), 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 })] }), jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' })] })] })), showPhoneSection && (jsxs("section", { className: "account-section", children: [jsx("h3", { className: "section-title", children: "Phone Number" }), jsx("div", { className: "current-info", children: jsxs("p", { children: [jsx("strong", { children: "Current Phone:" }), " ", profile?.phoneNumber || 'Not set'] }) }), jsxs("form", { onSubmit: handleUpdatePhone, className: "auth-form-fields", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPhone", children: "New Phone Number" }), jsx("input", { id: "newPhone", type: "tel", value: newPhone, onChange: (e) => setNewPhone(e.target.value), placeholder: "+1234567890", className: "auth-input", required: true })] }), !phoneCodeSent ? (jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Verification Code' })) : (jsxs(Fragment, { children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "phoneCode", children: "Verification Code" }), 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 })] }), jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Update Phone' })] }))] })] })), showDeleteAccount && (jsxs("section", { className: "account-section danger-zone", children: [jsx("h3", { className: "section-title text-danger", children: "Danger Zone" }), !showDeleteConfirm ? (jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "auth-button button-danger", children: "Delete Account" })) : (jsxs("div", { className: "delete-confirm", children: [jsx("p", { className: "warning-text", children: "\u26A0\uFE0F This action cannot be undone. This will permanently delete your account and all associated data." }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deletePassword", children: "Confirm Password" }), jsx("input", { id: "deletePassword", type: "password", value: deletePassword, onChange: (e) => setDeletePassword(e.target.value), placeholder: "Enter your password", className: "auth-input" })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deleteConfirm", children: "Type DELETE to confirm" }), jsx("input", { id: "deleteConfirm", type: "text", value: deleteConfirmText, onChange: (e) => setDeleteConfirmText(e.target.value), placeholder: "DELETE", className: "auth-input" })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleDeleteAccount, className: "auth-button button-danger", disabled: loading, children: loading ? 'Deleting...' : 'Permanently Delete Account' }), jsx("button", { type: "button", onClick: () => {
778
+ setShowDeleteConfirm(false);
779
+ setDeletePassword('');
780
+ setDeleteConfirmText('');
781
+ }, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }) }));
782
+ };
783
+
355
784
  const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
356
785
  const { isAuthenticated, isLoading } = useAuth();
357
786
  // Show loading state
@@ -370,24 +799,46 @@ const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
370
799
  return jsx(Fragment, { children: children });
371
800
  };
372
801
 
373
- const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], theme = 'light', className, }) => {
802
+ const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], providerOrder, emailDisplayMode = 'form', theme = 'light', className, }) => {
374
803
  const showEmail = enabledProviders.includes('email');
375
804
  const showGoogle = enabledProviders.includes('google');
376
805
  const showPhone = enabledProviders.includes('phone');
377
- const hasProviders = showGoogle || showPhone;
378
- return (jsxs(AuthContainer, { theme: theme, className: className, config: customization, children: [showEmail && (jsxs("div", { className: "auth-form", children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { className: "auth-label", children: "Email" }), jsx("input", { type: "email", className: "auth-input", placeholder: "Enter your email", disabled: true })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { className: "auth-label", children: "Password" }), jsx("input", { type: "password", className: "auth-input", placeholder: "Enter your password", disabled: true })] }), jsx("button", { className: "auth-button auth-button-primary", disabled: true, children: "Sign In" }), jsx("div", { style: { textAlign: 'center', marginTop: '1rem' }, children: jsx("button", { className: "auth-link", disabled: true, children: "Forgot password?" }) }), jsxs("div", { style: { textAlign: 'center', marginTop: '0.5rem', fontSize: '0.875rem', color: 'var(--auth-text-muted, #6B7280)' }, children: ["Don't have an account?", ' ', jsx("button", { className: "auth-link", disabled: true, children: "Sign up" })] })] })), hasProviders && (jsxs(Fragment, { children: [showEmail && (jsx("div", { className: "auth-or-divider", children: jsx("span", { children: "or continue with" }) })), jsxs("div", { className: "auth-provider-buttons", children: [showGoogle && (jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", children: [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" }), 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" }), 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" }), 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" })] }), jsx("span", { children: "Google" })] })), showPhone && (jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: 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" }) }), jsx("span", { children: "Phone" })] }))] })] }))] }));
806
+ const showMagicLink = enabledProviders.includes('magic-link');
807
+ // Determine ordered providers (excluding email if in button mode)
808
+ const orderedProviders = providerOrder && providerOrder.length > 0
809
+ ? providerOrder.filter(p => enabledProviders.includes(p) && p !== 'email')
810
+ : enabledProviders.filter(p => p !== 'email');
811
+ const hasOtherProviders = showGoogle || showPhone || showMagicLink;
812
+ // Render provider button helper
813
+ const renderProviderButton = (provider) => {
814
+ if (provider === 'google' && showGoogle) {
815
+ return (jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", children: [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" }), 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" }), 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" }), 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" })] }), jsx("span", { children: "Continue with Google" })] }, "google"));
816
+ }
817
+ if (provider === 'phone' && showPhone) {
818
+ return (jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: 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" }) }), jsx("span", { children: "Continue with Phone" })] }, "phone"));
819
+ }
820
+ if (provider === 'magic-link' && showMagicLink) {
821
+ return (jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsx("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: 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" }) }), jsx("span", { children: "Continue with Magic Link" })] }, "magic-link"));
822
+ }
823
+ if (provider === 'email' && showEmail && emailDisplayMode === 'button') {
824
+ return (jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsx("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: 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" }) }), jsx("span", { children: "Continue with Email" })] }, "email"));
825
+ }
826
+ return null;
827
+ };
828
+ return (jsx(AuthContainer, { theme: theme, className: className, config: customization, children: emailDisplayMode === 'button' ? (jsx("div", { className: "auth-provider-buttons", children: orderedProviders.concat(showEmail ? ['email'] : []).map(provider => renderProviderButton(provider)) })) : (
829
+ /* Form mode: show email form first, then other providers */
830
+ jsxs(Fragment, { children: [showEmail && (jsxs("div", { className: "auth-form", children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { className: "auth-label", children: "Email" }), jsx("input", { type: "email", className: "auth-input", placeholder: "Enter your email", disabled: true })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { className: "auth-label", children: "Password" }), jsx("input", { type: "password", className: "auth-input", placeholder: "Enter your password", disabled: true })] }), jsx("button", { className: "auth-button auth-button-primary", disabled: true, children: "Sign In" }), jsx("div", { style: { textAlign: 'center', marginTop: '1rem' }, children: jsx("button", { className: "auth-link", disabled: true, children: "Forgot password?" }) }), jsxs("div", { style: { textAlign: 'center', marginTop: '0.5rem', fontSize: '0.875rem', color: 'var(--auth-text-muted, #6B7280)' }, children: ["Don't have an account?", ' ', jsx("button", { className: "auth-link", disabled: true, children: "Sign up" })] })] })), hasOtherProviders && (jsxs(Fragment, { children: [showEmail && (jsx("div", { className: "auth-or-divider", children: jsx("span", { children: "or continue with" }) })), jsx("div", { className: "auth-provider-buttons", children: orderedProviders.map(provider => renderProviderButton(provider)) })] }))] })) }));
379
831
  };
380
832
 
381
833
  // Default Smartlinks Google OAuth Client ID (public - safe to expose)
382
834
  const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
383
- const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, }) => {
384
- const [mode, setMode] = useState('login');
835
+ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, }) => {
836
+ const [mode, setMode] = useState(initialMode);
385
837
  const [loading, setLoading] = useState(false);
386
838
  const [error, setError] = useState();
387
839
  const [resetSuccess, setResetSuccess] = useState(false);
388
840
  const [authSuccess, setAuthSuccess] = useState(false);
389
841
  const [successMessage, setSuccessMessage] = useState();
390
- const [verificationId, setVerificationId] = useState();
391
842
  const [showResendVerification, setShowResendVerification] = useState(false);
392
843
  const [resendEmail, setResendEmail] = useState();
393
844
  const [showRequestNewReset, setShowRequestNewReset] = useState(false);
@@ -395,8 +846,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
395
846
  const [resetToken, setResetToken] = useState(); // Store the reset token from URL
396
847
  const [config, setConfig] = useState(null);
397
848
  const [configLoading, setConfigLoading] = useState(!skipConfigFetch);
849
+ const [showEmailForm, setShowEmailForm] = useState(false); // Track if email form should be shown when emailDisplayMode is 'button'
398
850
  const api = new AuthAPI(apiEndpoint, clientId, clientName);
399
851
  const auth = useAuth();
852
+ // Reinitialize Smartlinks SDK when apiEndpoint changes (for test/dev scenarios)
853
+ // IMPORTANT: Preserve bearer token during reinitialization
854
+ useEffect(() => {
855
+ if (apiEndpoint) {
856
+ // Get current token before reinitializing
857
+ const currentToken = auth.getToken();
858
+ smartlinks.initializeApi({
859
+ baseURL: apiEndpoint,
860
+ proxyMode: false, // Direct API calls when custom endpoint is provided
861
+ ngrokSkipBrowserWarning: true,
862
+ });
863
+ // Restore bearer token after reinitialization using auth.verifyToken
864
+ if (currentToken) {
865
+ smartlinks.auth.verifyToken(currentToken).catch(err => {
866
+ console.warn('Failed to restore bearer token after reinit:', err);
867
+ });
868
+ }
869
+ }
870
+ }, [apiEndpoint, auth]);
400
871
  // Get the effective redirect URL (use prop or default to current page)
401
872
  const getRedirectUrl = () => {
402
873
  if (redirectUrl)
@@ -455,6 +926,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
455
926
  };
456
927
  fetchConfig();
457
928
  }, [apiEndpoint, clientId, customization, skipConfigFetch]);
929
+ // Reset showEmailForm when mode changes away from login/register
930
+ useEffect(() => {
931
+ if (mode !== 'login' && mode !== 'register') {
932
+ setShowEmailForm(false);
933
+ }
934
+ }, [mode]);
458
935
  // Handle URL-based auth flows (email verification, password reset)
459
936
  useEffect(() => {
460
937
  // Helper to get URL parameters from either hash or search
@@ -485,10 +962,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
485
962
  if (urlMode === 'verifyEmail') {
486
963
  console.log('Verifying email with token:', token);
487
964
  const response = await api.verifyEmailWithToken(token);
488
- // Get email verification mode from config (default: verify-then-auto-login)
489
- const verificationMode = config?.emailVerification?.mode || 'verify-then-auto-login';
490
- if (verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') {
491
- // Auto-login modes: Log the user in immediately
965
+ // Get email verification mode from response or config
966
+ const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
967
+ if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
968
+ // Auto-login modes: Log the user in immediately if token is provided
492
969
  auth.login(response.token, response.user, response.accountData);
493
970
  setAuthSuccess(true);
494
971
  setSuccessMessage('Email verified successfully! You are now logged in.');
@@ -504,7 +981,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
504
981
  }
505
982
  }
506
983
  else {
507
- // verify-then-manual-login mode: Show success but require manual login
984
+ // verify-then-manual-login mode or no token: Show success but require manual login
508
985
  setAuthSuccess(true);
509
986
  setSuccessMessage('Email verified successfully! Please log in with your credentials.');
510
987
  // Clear the URL parameters
@@ -527,6 +1004,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
527
1004
  const cleanUrl = window.location.href.split('?')[0];
528
1005
  window.history.replaceState({}, document.title, cleanUrl);
529
1006
  }
1007
+ else if (urlMode === 'magicLink') {
1008
+ console.log('Verifying magic link token:', token);
1009
+ const response = await api.verifyMagicLink(token);
1010
+ // Auto-login with magic link if token is provided
1011
+ if (response.token) {
1012
+ auth.login(response.token, response.user, response.accountData);
1013
+ setAuthSuccess(true);
1014
+ setSuccessMessage('Magic link verified! You are now logged in.');
1015
+ onAuthSuccess(response.token, response.user, response.accountData);
1016
+ // Clear the URL parameters
1017
+ const cleanUrl = window.location.href.split('?')[0];
1018
+ window.history.replaceState({}, document.title, cleanUrl);
1019
+ // Redirect after a brief delay to show success message
1020
+ if (redirectUrl) {
1021
+ setTimeout(() => {
1022
+ window.location.href = redirectUrl;
1023
+ }, 2000);
1024
+ }
1025
+ }
1026
+ else {
1027
+ throw new Error('Authentication failed - no token received');
1028
+ }
1029
+ }
530
1030
  }
531
1031
  catch (err) {
532
1032
  console.error('URL-based auth error:', err);
@@ -549,6 +1049,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
549
1049
  const cleanUrl = window.location.href.split('?')[0];
550
1050
  window.history.replaceState({}, document.title, cleanUrl);
551
1051
  }
1052
+ else if (urlMode === 'magicLink') {
1053
+ // If magic link is invalid/expired
1054
+ setError(`${errorMessage} - Please request a new magic link below.`);
1055
+ setMode('magic-link');
1056
+ // Clear the URL parameters
1057
+ const cleanUrl = window.location.href.split('?')[0];
1058
+ window.history.replaceState({}, document.title, cleanUrl);
1059
+ }
552
1060
  else {
553
1061
  setError(errorMessage);
554
1062
  }
@@ -570,17 +1078,22 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
570
1078
  accountData: mode === 'register' ? accountData : undefined,
571
1079
  redirectUrl: getRedirectUrl(), // Include redirect URL for email verification
572
1080
  });
573
- // Get email verification mode from config (default: verify-then-auto-login)
574
- const verificationMode = config?.emailVerification?.mode || 'verify-then-auto-login';
1081
+ // Get email verification mode from response or config (default: verify-then-auto-login)
1082
+ const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
575
1083
  const gracePeriodHours = config?.emailVerification?.gracePeriodHours || 24;
576
1084
  if (mode === 'register') {
577
1085
  // Handle different verification modes
578
- if (verificationMode === 'immediate') {
579
- // Immediate mode: Log in right away
1086
+ if (verificationMode === 'immediate' && response.token) {
1087
+ // Immediate mode: Log in right away if token is provided
580
1088
  auth.login(response.token, response.user, response.accountData);
581
1089
  setAuthSuccess(true);
582
- setSuccessMessage(`Account created! You're logged in now. Please verify your email within ${gracePeriodHours} hours to keep your account active.`);
583
- onAuthSuccess(response.token, response.user, response.accountData);
1090
+ const deadline = response.emailVerificationDeadline
1091
+ ? new Date(response.emailVerificationDeadline).toLocaleString()
1092
+ : `${gracePeriodHours} hours`;
1093
+ setSuccessMessage(`Account created! You're logged in now. Please verify your email by ${deadline} to keep your account active.`);
1094
+ if (response.token) {
1095
+ onAuthSuccess(response.token, response.user, response.accountData);
1096
+ }
584
1097
  if (redirectUrl) {
585
1098
  setTimeout(() => {
586
1099
  window.location.href = redirectUrl;
@@ -599,15 +1112,27 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
599
1112
  }
600
1113
  }
601
1114
  else {
602
- // Login mode - always log in
603
- auth.login(response.token, response.user, response.accountData);
604
- setAuthSuccess(true);
605
- setSuccessMessage('Login successful!');
606
- onAuthSuccess(response.token, response.user, response.accountData);
607
- if (redirectUrl) {
608
- setTimeout(() => {
609
- window.location.href = redirectUrl;
610
- }, 2000);
1115
+ // Login mode - always log in if token is provided
1116
+ if (response.token) {
1117
+ // Check for account lock or verification requirements
1118
+ if (response.accountLocked) {
1119
+ throw new Error('Your account has been locked due to unverified email. Please check your email or request a new verification link.');
1120
+ }
1121
+ if (response.requiresEmailVerification) {
1122
+ throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
1123
+ }
1124
+ auth.login(response.token, response.user, response.accountData);
1125
+ setAuthSuccess(true);
1126
+ setSuccessMessage('Login successful!');
1127
+ onAuthSuccess(response.token, response.user, response.accountData);
1128
+ if (redirectUrl) {
1129
+ setTimeout(() => {
1130
+ window.location.href = redirectUrl;
1131
+ }, 2000);
1132
+ }
1133
+ }
1134
+ else {
1135
+ throw new Error('Authentication failed - please verify your email before logging in.');
611
1136
  }
612
1137
  }
613
1138
  }
@@ -699,10 +1224,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
699
1224
  const accessToken = response.access_token;
700
1225
  // Send access token to backend
701
1226
  const authResponse = await api.loginWithGoogle(accessToken);
702
- auth.login(authResponse.token, authResponse.user, authResponse.accountData);
703
- setAuthSuccess(true);
704
- setSuccessMessage('Google login successful!');
705
- onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
1227
+ if (authResponse.token) {
1228
+ auth.login(authResponse.token, authResponse.user, authResponse.accountData);
1229
+ setAuthSuccess(true);
1230
+ setSuccessMessage('Google login successful!');
1231
+ onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
1232
+ }
1233
+ else {
1234
+ throw new Error('Authentication failed - no token received');
1235
+ }
706
1236
  if (redirectUrl) {
707
1237
  setTimeout(() => {
708
1238
  window.location.href = redirectUrl;
@@ -728,10 +1258,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
728
1258
  try {
729
1259
  const idToken = response.credential;
730
1260
  const authResponse = await api.loginWithGoogle(idToken);
731
- auth.login(authResponse.token, authResponse.user, authResponse.accountData);
732
- setAuthSuccess(true);
733
- setSuccessMessage('Google login successful!');
734
- onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
1261
+ if (authResponse.token) {
1262
+ auth.login(authResponse.token, authResponse.user, authResponse.accountData);
1263
+ setAuthSuccess(true);
1264
+ setSuccessMessage('Google login successful!');
1265
+ onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
1266
+ }
1267
+ else {
1268
+ throw new Error('Authentication failed - no token received');
1269
+ }
735
1270
  if (redirectUrl) {
736
1271
  setTimeout(() => {
737
1272
  window.location.href = redirectUrl;
@@ -768,21 +1303,24 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
768
1303
  setError(undefined);
769
1304
  try {
770
1305
  if (!verificationCode) {
771
- // Send verification code
772
- const result = await api.sendPhoneCode(phoneNumber);
773
- setVerificationId(result.verificationId);
1306
+ // Send verification code via Twilio Verify Service
1307
+ await api.sendPhoneCode(phoneNumber);
1308
+ // Twilio Verify Service tracks the verification by phone number
1309
+ // No need to store verificationId
774
1310
  }
775
1311
  else {
776
- // Verify code
777
- if (!verificationId) {
778
- throw new Error('Verification ID not found');
1312
+ // Verify code - Twilio identifies the verification by phone number
1313
+ const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
1314
+ // Update auth context with account data if token is provided
1315
+ if (response.token) {
1316
+ auth.login(response.token, response.user, response.accountData);
1317
+ onAuthSuccess(response.token, response.user, response.accountData);
1318
+ if (redirectUrl) {
1319
+ window.location.href = redirectUrl;
1320
+ }
779
1321
  }
780
- const response = await api.verifyPhoneCode(verificationId, verificationCode);
781
- // Update auth context with account data
782
- auth.login(response.token, response.user, response.accountData);
783
- onAuthSuccess(response.token, response.user, response.accountData);
784
- if (redirectUrl) {
785
- window.location.href = redirectUrl;
1322
+ else {
1323
+ throw new Error('Authentication failed - no token received');
786
1324
  }
787
1325
  }
788
1326
  }
@@ -820,6 +1358,23 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
820
1358
  setLoading(false);
821
1359
  }
822
1360
  };
1361
+ const handleMagicLink = async (email) => {
1362
+ setLoading(true);
1363
+ setError(undefined);
1364
+ try {
1365
+ await api.sendMagicLink(email, getRedirectUrl());
1366
+ setAuthSuccess(true);
1367
+ setSuccessMessage('Magic link sent! Check your email to log in.');
1368
+ }
1369
+ catch (err) {
1370
+ const errorMessage = err instanceof Error ? err.message : 'Failed to send magic link';
1371
+ setError(errorMessage);
1372
+ onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
1373
+ }
1374
+ finally {
1375
+ setLoading(false);
1376
+ }
1377
+ };
823
1378
  if (configLoading) {
824
1379
  return (jsx(AuthContainer, { theme: theme, className: className, children: jsx("div", { style: { textAlign: 'center', padding: '2rem' }, children: jsx("div", { className: "auth-spinner" }) }) }));
825
1380
  }
@@ -832,10 +1387,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
832
1387
  fontSize: '1.5rem',
833
1388
  fontWeight: 600
834
1389
  }, children: successMessage?.includes('verified') ? 'Email Verified!' :
835
- mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsx("p", { style: {
1390
+ successMessage?.includes('Magic link') ? 'Check Your Email!' :
1391
+ mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsx("p", { style: {
836
1392
  color: '#6B7280',
837
1393
  fontSize: '0.875rem'
838
- }, children: successMessage })] })) : mode === 'phone' ? (jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error })) : mode === 'reset-password' ? (jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
1394
+ }, 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: () => {
839
1395
  setMode('login');
840
1396
  setResetSuccess(false);
841
1397
  setResetToken(undefined); // Clear token when going back
@@ -905,13 +1461,34 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
905
1461
  fontSize: '0.875rem',
906
1462
  fontWeight: 500,
907
1463
  opacity: loading ? 0.6 : 1
908
- }, children: "Cancel" })] })] })) : (jsxs(Fragment, { children: [jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
909
- setMode(mode === 'login' ? 'register' : 'login');
910
- setShowResendVerification(false);
911
- setShowRequestNewReset(false);
912
- setError(undefined);
913
- }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error }), ((config?.enabledProviders || enabledProviders).length > 1) && (jsx(ProviderButtons, { enabledProviders: (config?.enabledProviders || enabledProviders).filter((p) => p !== 'email'), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), loading: loading }))] })) })) : null }));
1464
+ }, children: "Cancel" })] })] })) : (jsx(Fragment, { children: (() => {
1465
+ const emailDisplayMode = config?.emailDisplayMode || 'form';
1466
+ const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
1467
+ const actualProviders = config?.enabledProviders || enabledProviders;
1468
+ // Button mode: show provider selection first, then email form if email is selected
1469
+ if (emailDisplayMode === 'button' && !showEmailForm) {
1470
+ return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
1471
+ }
1472
+ // Form mode or email button was clicked: show email form with other providers
1473
+ return (jsxs(Fragment, { children: [emailDisplayMode === 'button' && showEmailForm && (jsx("button", { onClick: () => setShowEmailForm(false), style: {
1474
+ marginBottom: '1rem',
1475
+ padding: '0.5rem',
1476
+ background: 'none',
1477
+ border: 'none',
1478
+ color: 'var(--auth-text-color, #6B7280)',
1479
+ cursor: 'pointer',
1480
+ fontSize: '0.875rem',
1481
+ display: 'flex',
1482
+ alignItems: 'center',
1483
+ gap: '0.25rem'
1484
+ }, children: "\u2190 Back to options" })), jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
1485
+ setMode(mode === 'login' ? 'register' : 'login');
1486
+ setShowResendVerification(false);
1487
+ setShowRequestNewReset(false);
1488
+ setError(undefined);
1489
+ }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error }), 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 }))] }));
1490
+ })() })) })) : null }));
914
1491
  };
915
1492
 
916
- export { AuthProvider, AuthUIPreview, SmartlinksAuthUI as FirebaseAuthUI, ProtectedRoute, SmartlinksAuthUI, tokenStorage, useAuth };
1493
+ export { AccountManagement, AuthProvider, AuthUIPreview, SmartlinksAuthUI as FirebaseAuthUI, ProtectedRoute, SmartlinksAuthUI, tokenStorage, useAuth };
917
1494
  //# sourceMappingURL=index.esm.js.map