@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/api.d.ts +8 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/components/AccountManagement.d.ts +5 -0
- package/dist/components/AccountManagement.d.ts.map +1 -0
- package/dist/components/AuthUIPreview.d.ts +2 -0
- package/dist/components/AuthUIPreview.d.ts.map +1 -1
- package/dist/components/MagicLinkForm.d.ts +10 -0
- package/dist/components/MagicLinkForm.d.ts.map +1 -0
- package/dist/components/OTPInput.d.ts +11 -0
- package/dist/components/OTPInput.d.ts.map +1 -0
- package/dist/components/PhoneAuthForm.d.ts.map +1 -1
- package/dist/components/PhoneInput.d.ts +11 -0
- package/dist/components/PhoneInput.d.ts.map +1 -0
- package/dist/components/ProviderButtons.d.ts +3 -0
- package/dist/components/ProviderButtons.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +646 -69
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +645 -67
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +76 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
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(
|
|
176
|
-
return smartlinks__namespace.authKit.verifyPhoneCode(this.clientId,
|
|
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
|
-
|
|
320
|
-
|
|
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
|
-
//
|
|
336
|
-
smartlinks__namespace.
|
|
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
|
-
|
|
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,24 +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
|
|
399
|
-
|
|
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
854
|
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
403
855
|
const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
|
|
404
|
-
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, }) => {
|
|
405
|
-
const [mode, setMode] = react.useState(
|
|
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);
|
|
406
858
|
const [loading, setLoading] = react.useState(false);
|
|
407
859
|
const [error, setError] = react.useState();
|
|
408
860
|
const [resetSuccess, setResetSuccess] = react.useState(false);
|
|
409
861
|
const [authSuccess, setAuthSuccess] = react.useState(false);
|
|
410
862
|
const [successMessage, setSuccessMessage] = react.useState();
|
|
411
|
-
const [verificationId, setVerificationId] = react.useState();
|
|
412
863
|
const [showResendVerification, setShowResendVerification] = react.useState(false);
|
|
413
864
|
const [resendEmail, setResendEmail] = react.useState();
|
|
414
865
|
const [showRequestNewReset, setShowRequestNewReset] = react.useState(false);
|
|
@@ -416,8 +867,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
416
867
|
const [resetToken, setResetToken] = react.useState(); // Store the reset token from URL
|
|
417
868
|
const [config, setConfig] = react.useState(null);
|
|
418
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'
|
|
419
871
|
const api = new AuthAPI(apiEndpoint, clientId, clientName);
|
|
420
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]);
|
|
421
892
|
// Get the effective redirect URL (use prop or default to current page)
|
|
422
893
|
const getRedirectUrl = () => {
|
|
423
894
|
if (redirectUrl)
|
|
@@ -476,6 +947,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
476
947
|
};
|
|
477
948
|
fetchConfig();
|
|
478
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]);
|
|
479
956
|
// Handle URL-based auth flows (email verification, password reset)
|
|
480
957
|
react.useEffect(() => {
|
|
481
958
|
// Helper to get URL parameters from either hash or search
|
|
@@ -506,10 +983,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
506
983
|
if (urlMode === 'verifyEmail') {
|
|
507
984
|
console.log('Verifying email with token:', token);
|
|
508
985
|
const response = await api.verifyEmailWithToken(token);
|
|
509
|
-
// Get email verification mode from
|
|
510
|
-
const verificationMode = config?.emailVerification?.mode || 'verify-then-auto-login';
|
|
511
|
-
if (verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') {
|
|
512
|
-
// 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
|
|
513
990
|
auth.login(response.token, response.user, response.accountData);
|
|
514
991
|
setAuthSuccess(true);
|
|
515
992
|
setSuccessMessage('Email verified successfully! You are now logged in.');
|
|
@@ -525,7 +1002,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
525
1002
|
}
|
|
526
1003
|
}
|
|
527
1004
|
else {
|
|
528
|
-
// 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
|
|
529
1006
|
setAuthSuccess(true);
|
|
530
1007
|
setSuccessMessage('Email verified successfully! Please log in with your credentials.');
|
|
531
1008
|
// Clear the URL parameters
|
|
@@ -548,6 +1025,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
548
1025
|
const cleanUrl = window.location.href.split('?')[0];
|
|
549
1026
|
window.history.replaceState({}, document.title, cleanUrl);
|
|
550
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
|
+
}
|
|
551
1051
|
}
|
|
552
1052
|
catch (err) {
|
|
553
1053
|
console.error('URL-based auth error:', err);
|
|
@@ -570,6 +1070,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
570
1070
|
const cleanUrl = window.location.href.split('?')[0];
|
|
571
1071
|
window.history.replaceState({}, document.title, cleanUrl);
|
|
572
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
|
+
}
|
|
573
1081
|
else {
|
|
574
1082
|
setError(errorMessage);
|
|
575
1083
|
}
|
|
@@ -591,17 +1099,22 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
591
1099
|
accountData: mode === 'register' ? accountData : undefined,
|
|
592
1100
|
redirectUrl: getRedirectUrl(), // Include redirect URL for email verification
|
|
593
1101
|
});
|
|
594
|
-
// Get email verification mode from config (default: verify-then-auto-login)
|
|
595
|
-
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';
|
|
596
1104
|
const gracePeriodHours = config?.emailVerification?.gracePeriodHours || 24;
|
|
597
1105
|
if (mode === 'register') {
|
|
598
1106
|
// Handle different verification modes
|
|
599
|
-
if (verificationMode === 'immediate') {
|
|
600
|
-
// Immediate mode: Log in right away
|
|
1107
|
+
if (verificationMode === 'immediate' && response.token) {
|
|
1108
|
+
// Immediate mode: Log in right away if token is provided
|
|
601
1109
|
auth.login(response.token, response.user, response.accountData);
|
|
602
1110
|
setAuthSuccess(true);
|
|
603
|
-
|
|
604
|
-
|
|
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
|
+
}
|
|
605
1118
|
if (redirectUrl) {
|
|
606
1119
|
setTimeout(() => {
|
|
607
1120
|
window.location.href = redirectUrl;
|
|
@@ -620,15 +1133,27 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
620
1133
|
}
|
|
621
1134
|
}
|
|
622
1135
|
else {
|
|
623
|
-
// Login mode - always log in
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
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.');
|
|
632
1157
|
}
|
|
633
1158
|
}
|
|
634
1159
|
}
|
|
@@ -720,10 +1245,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
720
1245
|
const accessToken = response.access_token;
|
|
721
1246
|
// Send access token to backend
|
|
722
1247
|
const authResponse = await api.loginWithGoogle(accessToken);
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
+
}
|
|
727
1257
|
if (redirectUrl) {
|
|
728
1258
|
setTimeout(() => {
|
|
729
1259
|
window.location.href = redirectUrl;
|
|
@@ -749,10 +1279,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
749
1279
|
try {
|
|
750
1280
|
const idToken = response.credential;
|
|
751
1281
|
const authResponse = await api.loginWithGoogle(idToken);
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
+
}
|
|
756
1291
|
if (redirectUrl) {
|
|
757
1292
|
setTimeout(() => {
|
|
758
1293
|
window.location.href = redirectUrl;
|
|
@@ -789,21 +1324,24 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
789
1324
|
setError(undefined);
|
|
790
1325
|
try {
|
|
791
1326
|
if (!verificationCode) {
|
|
792
|
-
// Send verification code
|
|
793
|
-
|
|
794
|
-
|
|
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
|
|
795
1331
|
}
|
|
796
1332
|
else {
|
|
797
|
-
// Verify code
|
|
798
|
-
|
|
799
|
-
|
|
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
|
+
}
|
|
800
1342
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
auth.login(response.token, response.user, response.accountData);
|
|
804
|
-
onAuthSuccess(response.token, response.user, response.accountData);
|
|
805
|
-
if (redirectUrl) {
|
|
806
|
-
window.location.href = redirectUrl;
|
|
1343
|
+
else {
|
|
1344
|
+
throw new Error('Authentication failed - no token received');
|
|
807
1345
|
}
|
|
808
1346
|
}
|
|
809
1347
|
}
|
|
@@ -841,6 +1379,23 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
841
1379
|
setLoading(false);
|
|
842
1380
|
}
|
|
843
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
|
+
};
|
|
844
1399
|
if (configLoading) {
|
|
845
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" }) }) }));
|
|
846
1401
|
}
|
|
@@ -853,10 +1408,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
853
1408
|
fontSize: '1.5rem',
|
|
854
1409
|
fontWeight: 600
|
|
855
1410
|
}, children: successMessage?.includes('verified') ? 'Email Verified!' :
|
|
856
|
-
|
|
1411
|
+
successMessage?.includes('Magic link') ? 'Check Your Email!' :
|
|
1412
|
+
mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsxRuntime.jsx("p", { style: {
|
|
857
1413
|
color: '#6B7280',
|
|
858
1414
|
fontSize: '0.875rem'
|
|
859
|
-
}, 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: () => {
|
|
860
1416
|
setMode('login');
|
|
861
1417
|
setResetSuccess(false);
|
|
862
1418
|
setResetToken(undefined); // Clear token when going back
|
|
@@ -926,14 +1482,36 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
926
1482
|
fontSize: '0.875rem',
|
|
927
1483
|
fontWeight: 500,
|
|
928
1484
|
opacity: loading ? 0.6 : 1
|
|
929
|
-
}, children: "Cancel" })] })] })) : (jsxRuntime.
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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 }));
|
|
935
1512
|
};
|
|
936
1513
|
|
|
1514
|
+
exports.AccountManagement = AccountManagement;
|
|
937
1515
|
exports.AuthProvider = AuthProvider;
|
|
938
1516
|
exports.AuthUIPreview = AuthUIPreview;
|
|
939
1517
|
exports.FirebaseAuthUI = SmartlinksAuthUI;
|