@proveanything/smartlinks-auth-ui 0.1.4 → 0.1.6
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/components/AccountManagement.d.ts.map +1 -1
- package/dist/components/ClaimUI.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts +4 -0
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +2 -4
- 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 +953 -928
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +953 -928
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.esm.js
CHANGED
|
@@ -47,7 +47,7 @@ const AuthContainer = ({ children, theme = 'light', className = '', config, mini
|
|
|
47
47
|
// - null or empty string: hide logo completely
|
|
48
48
|
// - string URL: use custom logo
|
|
49
49
|
const logoUrl = config?.branding?.logoUrl === undefined
|
|
50
|
-
? '/smartlinks-
|
|
50
|
+
? 'https://smartlinks.app/smartlinks/landscape-medium.png' // Default Smartlinks logo
|
|
51
51
|
: config?.branding?.logoUrl || null; // Custom or explicitly hidden
|
|
52
52
|
const containerClass = minimal
|
|
53
53
|
? `auth-minimal auth-theme-${theme} ${className}`
|
|
@@ -11411,1092 +11411,1117 @@ const useAuth = () => {
|
|
|
11411
11411
|
return context;
|
|
11412
11412
|
};
|
|
11413
11413
|
|
|
11414
|
-
|
|
11415
|
-
|
|
11414
|
+
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
11415
|
+
const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
|
|
11416
|
+
// Default auth UI configuration when no clientId is provided
|
|
11417
|
+
const DEFAULT_AUTH_CONFIG = {
|
|
11418
|
+
branding: {
|
|
11419
|
+
title: '', // No title by default (minimal)
|
|
11420
|
+
subtitle: 'Sign in to your account',
|
|
11421
|
+
logoUrl: 'https://smartlinks.app/smartlinks/landscape-medium.png',
|
|
11422
|
+
primaryColor: '#3B82F6',
|
|
11423
|
+
secondaryColor: '#1D4ED8',
|
|
11424
|
+
backgroundColor: '#e0f2fe',
|
|
11425
|
+
buttonStyle: 'rounded',
|
|
11426
|
+
fontFamily: 'Inter, sans-serif'
|
|
11427
|
+
},
|
|
11428
|
+
emailDisplayMode: 'button',
|
|
11429
|
+
googleOAuthFlow: 'oneTap'
|
|
11430
|
+
};
|
|
11431
|
+
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, minimal = false, }) => {
|
|
11432
|
+
const [mode, setMode] = useState(initialMode);
|
|
11416
11433
|
const [loading, setLoading] = useState(false);
|
|
11417
|
-
const [profile, setProfile] = useState(null);
|
|
11418
11434
|
const [error, setError] = useState();
|
|
11419
|
-
const [
|
|
11420
|
-
|
|
11421
|
-
const [
|
|
11422
|
-
|
|
11423
|
-
const [
|
|
11424
|
-
|
|
11425
|
-
const [
|
|
11426
|
-
const [
|
|
11427
|
-
|
|
11428
|
-
const [
|
|
11429
|
-
const [
|
|
11430
|
-
const
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
const [phoneCodeSent, setPhoneCodeSent] = useState(false);
|
|
11435
|
-
// Account deletion state
|
|
11436
|
-
const [deletePassword, setDeletePassword] = useState('');
|
|
11437
|
-
const [deleteConfirmText, setDeleteConfirmText] = useState('');
|
|
11438
|
-
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
11439
|
-
const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, } = customization;
|
|
11440
|
-
// Reinitialize Smartlinks SDK when apiEndpoint changes
|
|
11435
|
+
const [resetSuccess, setResetSuccess] = useState(false);
|
|
11436
|
+
const [authSuccess, setAuthSuccess] = useState(false);
|
|
11437
|
+
const [successMessage, setSuccessMessage] = useState();
|
|
11438
|
+
const [showResendVerification, setShowResendVerification] = useState(false);
|
|
11439
|
+
const [resendEmail, setResendEmail] = useState();
|
|
11440
|
+
const [showRequestNewReset, setShowRequestNewReset] = useState(false);
|
|
11441
|
+
const [resetRequestEmail, setResetRequestEmail] = useState();
|
|
11442
|
+
const [resetToken, setResetToken] = useState(); // Store the reset token from URL
|
|
11443
|
+
const [config, setConfig] = useState(null);
|
|
11444
|
+
const [configLoading, setConfigLoading] = useState(!skipConfigFetch);
|
|
11445
|
+
const [showEmailForm, setShowEmailForm] = useState(false); // Track if email form should be shown when emailDisplayMode is 'button'
|
|
11446
|
+
const api = new AuthAPI(apiEndpoint, clientId, clientName);
|
|
11447
|
+
const auth = useAuth();
|
|
11448
|
+
// Reinitialize Smartlinks SDK when apiEndpoint changes (for test/dev scenarios)
|
|
11449
|
+
// IMPORTANT: Preserve bearer token during reinitialization
|
|
11441
11450
|
useEffect(() => {
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
+
const reinitializeWithToken = async () => {
|
|
11452
|
+
if (apiEndpoint) {
|
|
11453
|
+
// Get current token before reinitializing
|
|
11454
|
+
const currentToken = await auth.getToken();
|
|
11455
|
+
smartlinks.initializeApi({
|
|
11456
|
+
baseURL: apiEndpoint,
|
|
11457
|
+
proxyMode: false, // Direct API calls when custom endpoint is provided
|
|
11458
|
+
ngrokSkipBrowserWarning: true,
|
|
11459
|
+
});
|
|
11460
|
+
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11461
|
+
if (currentToken) {
|
|
11462
|
+
smartlinks.auth.verifyToken(currentToken).catch(err => {
|
|
11463
|
+
console.warn('Failed to restore bearer token after reinit:', err);
|
|
11464
|
+
});
|
|
11465
|
+
}
|
|
11466
|
+
}
|
|
11467
|
+
};
|
|
11468
|
+
reinitializeWithToken();
|
|
11469
|
+
}, [apiEndpoint, auth]);
|
|
11470
|
+
// Get the effective redirect URL (use prop or default to current page)
|
|
11471
|
+
const getRedirectUrl = () => {
|
|
11472
|
+
if (redirectUrl)
|
|
11473
|
+
return redirectUrl;
|
|
11474
|
+
// Get the full current URL including hash routes
|
|
11475
|
+
// Remove any existing query parameters to avoid duplication
|
|
11476
|
+
const currentUrl = window.location.href.split('?')[0];
|
|
11477
|
+
return currentUrl;
|
|
11478
|
+
};
|
|
11479
|
+
// Fetch UI configuration
|
|
11451
11480
|
useEffect(() => {
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
const loadProfile = async () => {
|
|
11455
|
-
if (!auth.isAuthenticated) {
|
|
11456
|
-
setError('You must be logged in to manage your account');
|
|
11481
|
+
if (skipConfigFetch) {
|
|
11482
|
+
setConfig(customization || {});
|
|
11457
11483
|
return;
|
|
11458
11484
|
}
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
}
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11485
|
+
const fetchConfig = async () => {
|
|
11486
|
+
// If no clientId provided, use default config immediately without API call
|
|
11487
|
+
if (!clientId) {
|
|
11488
|
+
const defaultConfig = {
|
|
11489
|
+
...DEFAULT_AUTH_CONFIG,
|
|
11490
|
+
enabledProviders: enabledProviders || ['email', 'google', 'phone']
|
|
11491
|
+
};
|
|
11492
|
+
setConfig({ ...defaultConfig, ...customization });
|
|
11493
|
+
setConfigLoading(false);
|
|
11494
|
+
return;
|
|
11495
|
+
}
|
|
11496
|
+
try {
|
|
11497
|
+
// Check localStorage cache first
|
|
11498
|
+
const cacheKey = `auth_ui_config_${clientId}`;
|
|
11499
|
+
const cached = localStorage.getItem(cacheKey);
|
|
11500
|
+
if (cached) {
|
|
11501
|
+
const { config: cachedConfig, timestamp } = JSON.parse(cached);
|
|
11502
|
+
const age = Date.now() - timestamp;
|
|
11503
|
+
// Use cache if less than 1 hour old
|
|
11504
|
+
if (age < 3600000) {
|
|
11505
|
+
setConfig({ ...cachedConfig, ...customization });
|
|
11506
|
+
setConfigLoading(false);
|
|
11507
|
+
// Fetch in background to update cache
|
|
11508
|
+
api.fetchConfig().then(freshConfig => {
|
|
11509
|
+
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11510
|
+
config: freshConfig,
|
|
11511
|
+
timestamp: Date.now()
|
|
11512
|
+
}));
|
|
11513
|
+
});
|
|
11514
|
+
return;
|
|
11515
|
+
}
|
|
11516
|
+
}
|
|
11517
|
+
// Fetch from API
|
|
11518
|
+
const fetchedConfig = await api.fetchConfig();
|
|
11519
|
+
// Merge with customization props (props take precedence)
|
|
11520
|
+
const mergedConfig = { ...fetchedConfig, ...customization };
|
|
11521
|
+
setConfig(mergedConfig);
|
|
11522
|
+
// Cache the fetched config
|
|
11523
|
+
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11524
|
+
config: fetchedConfig,
|
|
11525
|
+
timestamp: Date.now()
|
|
11526
|
+
}));
|
|
11527
|
+
}
|
|
11528
|
+
catch (err) {
|
|
11529
|
+
console.error('Failed to fetch config:', err);
|
|
11530
|
+
setConfig(customization || {});
|
|
11531
|
+
}
|
|
11532
|
+
finally {
|
|
11533
|
+
setConfigLoading(false);
|
|
11534
|
+
}
|
|
11535
|
+
};
|
|
11536
|
+
fetchConfig();
|
|
11537
|
+
}, [apiEndpoint, clientId, customization, skipConfigFetch]);
|
|
11538
|
+
// Reset showEmailForm when mode changes away from login/register
|
|
11539
|
+
useEffect(() => {
|
|
11540
|
+
if (mode !== 'login' && mode !== 'register') {
|
|
11541
|
+
setShowEmailForm(false);
|
|
11514
11542
|
}
|
|
11515
|
-
|
|
11516
|
-
|
|
11543
|
+
}, [mode]);
|
|
11544
|
+
// Handle URL-based auth flows (email verification, password reset)
|
|
11545
|
+
useEffect(() => {
|
|
11546
|
+
// Helper to get URL parameters from either hash or search
|
|
11547
|
+
const getUrlParams = () => {
|
|
11548
|
+
// First check if there are params in the hash (for hash routing)
|
|
11549
|
+
const hash = window.location.hash;
|
|
11550
|
+
const hashQueryIndex = hash.indexOf('?');
|
|
11551
|
+
if (hashQueryIndex !== -1) {
|
|
11552
|
+
// Extract query string from hash (e.g., #/test?mode=verifyEmail&token=abc)
|
|
11553
|
+
const hashQuery = hash.substring(hashQueryIndex + 1);
|
|
11554
|
+
return new URLSearchParams(hashQuery);
|
|
11555
|
+
}
|
|
11556
|
+
// Fall back to regular search params (for non-hash routing)
|
|
11557
|
+
return new URLSearchParams(window.location.search);
|
|
11558
|
+
};
|
|
11559
|
+
const params = getUrlParams();
|
|
11560
|
+
const urlMode = params.get('mode');
|
|
11561
|
+
const token = params.get('token');
|
|
11562
|
+
console.log('URL params detected:', { urlMode, token, hash: window.location.hash, search: window.location.search });
|
|
11563
|
+
if (urlMode && token) {
|
|
11564
|
+
handleURLBasedAuth(urlMode, token);
|
|
11517
11565
|
}
|
|
11518
|
-
};
|
|
11519
|
-
const
|
|
11520
|
-
setEditingSection(null);
|
|
11521
|
-
setDisplayName(profile?.displayName || '');
|
|
11522
|
-
setNewEmail('');
|
|
11523
|
-
setEmailPassword('');
|
|
11524
|
-
setCurrentPassword('');
|
|
11525
|
-
setNewPassword('');
|
|
11526
|
-
setConfirmPassword('');
|
|
11527
|
-
setNewPhone('');
|
|
11528
|
-
setPhoneCode('');
|
|
11529
|
-
setPhoneCodeSent(false);
|
|
11530
|
-
setError(undefined);
|
|
11531
|
-
setSuccess(undefined);
|
|
11532
|
-
};
|
|
11533
|
-
const handleChangeEmail = async (e) => {
|
|
11534
|
-
e.preventDefault();
|
|
11566
|
+
}, []);
|
|
11567
|
+
const handleURLBasedAuth = async (urlMode, token) => {
|
|
11535
11568
|
setLoading(true);
|
|
11536
11569
|
setError(undefined);
|
|
11537
|
-
setSuccess(undefined);
|
|
11538
11570
|
try {
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11571
|
+
if (urlMode === 'verifyEmail') {
|
|
11572
|
+
console.log('Verifying email with token:', token);
|
|
11573
|
+
const response = await api.verifyEmailWithToken(token);
|
|
11574
|
+
// Get email verification mode from response or config
|
|
11575
|
+
const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
|
|
11576
|
+
if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
|
|
11577
|
+
// Auto-login modes: Log the user in immediately if token is provided
|
|
11578
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11579
|
+
setAuthSuccess(true);
|
|
11580
|
+
setSuccessMessage('Email verified successfully! You are now logged in.');
|
|
11581
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11582
|
+
// Clear the URL parameters
|
|
11583
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11584
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11585
|
+
// Redirect after a brief delay to show success message
|
|
11586
|
+
if (redirectUrl) {
|
|
11587
|
+
setTimeout(() => {
|
|
11588
|
+
window.location.href = redirectUrl;
|
|
11589
|
+
}, 2000);
|
|
11590
|
+
}
|
|
11591
|
+
}
|
|
11592
|
+
else {
|
|
11593
|
+
// verify-then-manual-login mode or no token: Show success but require manual login
|
|
11594
|
+
setAuthSuccess(true);
|
|
11595
|
+
setSuccessMessage('Email verified successfully! Please log in with your credentials.');
|
|
11596
|
+
// Clear the URL parameters
|
|
11597
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11598
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11599
|
+
// Switch back to login mode after a delay
|
|
11600
|
+
setTimeout(() => {
|
|
11601
|
+
setAuthSuccess(false);
|
|
11602
|
+
setMode('login');
|
|
11603
|
+
}, 3000);
|
|
11604
|
+
}
|
|
11605
|
+
}
|
|
11606
|
+
else if (urlMode === 'resetPassword') {
|
|
11607
|
+
console.log('Verifying reset token:', token);
|
|
11608
|
+
// Verify token is valid, then show password reset form
|
|
11609
|
+
await api.verifyResetToken(token);
|
|
11610
|
+
setResetToken(token); // Store token for use in password reset
|
|
11611
|
+
setMode('reset-password');
|
|
11612
|
+
// Clear the URL parameters
|
|
11613
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11614
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11615
|
+
}
|
|
11616
|
+
else if (urlMode === 'magicLink') {
|
|
11617
|
+
console.log('Verifying magic link token:', token);
|
|
11618
|
+
const response = await api.verifyMagicLink(token);
|
|
11619
|
+
// Auto-login with magic link if token is provided
|
|
11620
|
+
if (response.token) {
|
|
11621
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11622
|
+
setAuthSuccess(true);
|
|
11623
|
+
setSuccessMessage('Magic link verified! You are now logged in.');
|
|
11624
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11625
|
+
// Clear the URL parameters
|
|
11626
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11627
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11628
|
+
// Redirect after a brief delay to show success message
|
|
11629
|
+
if (redirectUrl) {
|
|
11630
|
+
setTimeout(() => {
|
|
11631
|
+
window.location.href = redirectUrl;
|
|
11632
|
+
}, 2000);
|
|
11633
|
+
}
|
|
11634
|
+
}
|
|
11635
|
+
else {
|
|
11636
|
+
throw new Error('Authentication failed - no token received');
|
|
11637
|
+
}
|
|
11638
|
+
}
|
|
11639
|
+
}
|
|
11640
|
+
catch (err) {
|
|
11641
|
+
console.error('URL-based auth error:', err);
|
|
11642
|
+
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
|
|
11643
|
+
// If it's an email verification error (expired/invalid token), show resend option
|
|
11644
|
+
if (urlMode === 'verifyEmail') {
|
|
11645
|
+
setError(`${errorMessage} - Please enter your email below to receive a new verification link.`);
|
|
11646
|
+
setShowResendVerification(true);
|
|
11647
|
+
setMode('login'); // Show the login form UI
|
|
11648
|
+
// Clear the URL parameters
|
|
11649
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11650
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11651
|
+
}
|
|
11652
|
+
else if (urlMode === 'resetPassword') {
|
|
11653
|
+
// If password reset token is invalid/expired, show request new reset link option
|
|
11654
|
+
setError(`${errorMessage} - Please enter your email below to receive a new password reset link.`);
|
|
11655
|
+
setShowRequestNewReset(true);
|
|
11656
|
+
setMode('login');
|
|
11657
|
+
// Clear the URL parameters
|
|
11658
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11659
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11660
|
+
}
|
|
11661
|
+
else if (urlMode === 'magicLink') {
|
|
11662
|
+
// If magic link is invalid/expired
|
|
11663
|
+
setError(`${errorMessage} - Please request a new magic link below.`);
|
|
11664
|
+
setMode('magic-link');
|
|
11665
|
+
// Clear the URL parameters
|
|
11666
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11667
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11668
|
+
}
|
|
11669
|
+
else {
|
|
11670
|
+
setError(errorMessage);
|
|
11671
|
+
}
|
|
11672
|
+
onAuthError?.(err instanceof Error ? err : new Error('An error occurred'));
|
|
11559
11673
|
}
|
|
11560
11674
|
finally {
|
|
11561
11675
|
setLoading(false);
|
|
11562
11676
|
}
|
|
11563
11677
|
};
|
|
11564
|
-
const
|
|
11565
|
-
e.preventDefault();
|
|
11566
|
-
if (newPassword !== confirmPassword) {
|
|
11567
|
-
setError('New passwords do not match');
|
|
11568
|
-
return;
|
|
11569
|
-
}
|
|
11570
|
-
if (newPassword.length < 6) {
|
|
11571
|
-
setError('Password must be at least 6 characters');
|
|
11572
|
-
return;
|
|
11573
|
-
}
|
|
11678
|
+
const handleEmailAuth = async (data) => {
|
|
11574
11679
|
setLoading(true);
|
|
11575
11680
|
setError(undefined);
|
|
11576
|
-
|
|
11681
|
+
setAuthSuccess(false);
|
|
11577
11682
|
try {
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
//
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11683
|
+
const response = mode === 'login'
|
|
11684
|
+
? await api.login(data.email, data.password)
|
|
11685
|
+
: await api.register({
|
|
11686
|
+
...data,
|
|
11687
|
+
accountData: mode === 'register' ? accountData : undefined,
|
|
11688
|
+
redirectUrl: getRedirectUrl(), // Include redirect URL for email verification
|
|
11689
|
+
});
|
|
11690
|
+
// Get email verification mode from response or config (default: verify-then-auto-login)
|
|
11691
|
+
const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
|
|
11692
|
+
const gracePeriodHours = config?.emailVerification?.gracePeriodHours || 24;
|
|
11693
|
+
if (mode === 'register') {
|
|
11694
|
+
// Handle different verification modes
|
|
11695
|
+
if (verificationMode === 'immediate' && response.token) {
|
|
11696
|
+
// Immediate mode: Log in right away if token is provided
|
|
11697
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11698
|
+
setAuthSuccess(true);
|
|
11699
|
+
const deadline = response.emailVerificationDeadline
|
|
11700
|
+
? new Date(response.emailVerificationDeadline).toLocaleString()
|
|
11701
|
+
: `${gracePeriodHours} hours`;
|
|
11702
|
+
setSuccessMessage(`Account created! You're logged in now. Please verify your email by ${deadline} to keep your account active.`);
|
|
11703
|
+
if (response.token) {
|
|
11704
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11705
|
+
}
|
|
11706
|
+
if (redirectUrl) {
|
|
11707
|
+
setTimeout(() => {
|
|
11708
|
+
window.location.href = redirectUrl;
|
|
11709
|
+
}, 2000);
|
|
11710
|
+
}
|
|
11711
|
+
}
|
|
11712
|
+
else if (verificationMode === 'verify-then-auto-login') {
|
|
11713
|
+
// Verify-then-auto-login mode: Don't log in yet, but will auto-login after email verification
|
|
11714
|
+
setAuthSuccess(true);
|
|
11715
|
+
setSuccessMessage('Account created! Please check your email and click the verification link to complete your registration.');
|
|
11716
|
+
}
|
|
11717
|
+
else {
|
|
11718
|
+
// verify-then-manual-login mode: Traditional flow
|
|
11719
|
+
setAuthSuccess(true);
|
|
11720
|
+
setSuccessMessage('Account created successfully! Please check your email to verify your account, then log in.');
|
|
11721
|
+
}
|
|
11722
|
+
}
|
|
11723
|
+
else {
|
|
11724
|
+
// Login mode - always log in if token is provided
|
|
11725
|
+
if (response.token) {
|
|
11726
|
+
// Check for account lock or verification requirements
|
|
11727
|
+
if (response.accountLocked) {
|
|
11728
|
+
throw new Error('Your account has been locked due to unverified email. Please check your email or request a new verification link.');
|
|
11729
|
+
}
|
|
11730
|
+
if (response.requiresEmailVerification) {
|
|
11731
|
+
throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
|
|
11732
|
+
}
|
|
11733
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11734
|
+
setAuthSuccess(true);
|
|
11735
|
+
setSuccessMessage('Login successful!');
|
|
11736
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11737
|
+
if (redirectUrl) {
|
|
11738
|
+
setTimeout(() => {
|
|
11739
|
+
window.location.href = redirectUrl;
|
|
11740
|
+
}, 2000);
|
|
11741
|
+
}
|
|
11742
|
+
}
|
|
11743
|
+
else {
|
|
11744
|
+
throw new Error('Authentication failed - please verify your email before logging in.');
|
|
11745
|
+
}
|
|
11746
|
+
}
|
|
11592
11747
|
}
|
|
11593
11748
|
catch (err) {
|
|
11594
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
11595
|
-
|
|
11596
|
-
|
|
11749
|
+
const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
|
|
11750
|
+
// Check if error is about email already registered
|
|
11751
|
+
if (mode === 'register' && errorMessage.toLowerCase().includes('already') && errorMessage.toLowerCase().includes('email')) {
|
|
11752
|
+
setShowResendVerification(true);
|
|
11753
|
+
setResendEmail(data.email);
|
|
11754
|
+
setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
|
|
11755
|
+
}
|
|
11756
|
+
else {
|
|
11757
|
+
setError(errorMessage);
|
|
11758
|
+
}
|
|
11759
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11597
11760
|
}
|
|
11598
11761
|
finally {
|
|
11599
11762
|
setLoading(false);
|
|
11600
11763
|
}
|
|
11601
11764
|
};
|
|
11602
|
-
const
|
|
11765
|
+
const handleResendVerification = async () => {
|
|
11766
|
+
if (!resendEmail)
|
|
11767
|
+
return;
|
|
11603
11768
|
setLoading(true);
|
|
11604
11769
|
setError(undefined);
|
|
11605
11770
|
try {
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11771
|
+
// For resend, we need the userId. If we don't have it, we need to handle this differently
|
|
11772
|
+
// The backend should ideally handle this case
|
|
11773
|
+
await api.resendVerification('unknown', resendEmail, getRedirectUrl());
|
|
11774
|
+
setAuthSuccess(true);
|
|
11775
|
+
setSuccessMessage('Verification email sent! Please check your inbox.');
|
|
11776
|
+
setShowResendVerification(false);
|
|
11609
11777
|
}
|
|
11610
11778
|
catch (err) {
|
|
11611
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to
|
|
11779
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to resend verification email';
|
|
11612
11780
|
setError(errorMessage);
|
|
11613
|
-
|
|
11781
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11614
11782
|
}
|
|
11615
11783
|
finally {
|
|
11616
11784
|
setLoading(false);
|
|
11617
11785
|
}
|
|
11618
11786
|
};
|
|
11619
|
-
const
|
|
11620
|
-
|
|
11787
|
+
const handleRequestNewReset = async () => {
|
|
11788
|
+
if (!resetRequestEmail)
|
|
11789
|
+
return;
|
|
11621
11790
|
setLoading(true);
|
|
11622
11791
|
setError(undefined);
|
|
11623
|
-
setSuccess(undefined);
|
|
11624
11792
|
try {
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
console.log('Data:', { phoneNumber: newPhone, code: phoneCode });
|
|
11631
|
-
// Uncomment when backend is ready:
|
|
11632
|
-
// await smartlinks.authKit.updatePhone(clientId, newPhone, phoneCode);
|
|
11633
|
-
// setSuccess('Phone number updated successfully!');
|
|
11634
|
-
// setEditingSection(null);
|
|
11635
|
-
// setNewPhone('');
|
|
11636
|
-
// setPhoneCode('');
|
|
11637
|
-
// setPhoneCodeSent(false);
|
|
11638
|
-
// await loadProfile();
|
|
11793
|
+
await api.requestPasswordReset(resetRequestEmail, getRedirectUrl());
|
|
11794
|
+
setAuthSuccess(true);
|
|
11795
|
+
setSuccessMessage('Password reset email sent! Please check your inbox.');
|
|
11796
|
+
setShowRequestNewReset(false);
|
|
11797
|
+
setResetRequestEmail('');
|
|
11639
11798
|
}
|
|
11640
11799
|
catch (err) {
|
|
11641
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to
|
|
11800
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to send password reset email';
|
|
11642
11801
|
setError(errorMessage);
|
|
11643
|
-
|
|
11802
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11644
11803
|
}
|
|
11645
11804
|
finally {
|
|
11646
11805
|
setLoading(false);
|
|
11647
11806
|
}
|
|
11648
11807
|
};
|
|
11649
|
-
const
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11808
|
+
const handleGoogleLogin = async () => {
|
|
11809
|
+
// Use custom client ID from config, or fall back to default Smartlinks client ID
|
|
11810
|
+
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
11811
|
+
// Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
|
|
11812
|
+
const oauthFlow = config?.googleOAuthFlow || 'oneTap';
|
|
11813
|
+
setLoading(true);
|
|
11814
|
+
setError(undefined);
|
|
11815
|
+
try {
|
|
11816
|
+
const google = window.google;
|
|
11817
|
+
if (!google) {
|
|
11818
|
+
throw new Error('Google Identity Services not loaded. Please check your internet connection.');
|
|
11819
|
+
}
|
|
11820
|
+
if (oauthFlow === 'popup') {
|
|
11821
|
+
// Use OAuth2 popup flow (works in iframes but requires popup permission)
|
|
11822
|
+
if (!google.accounts.oauth2) {
|
|
11823
|
+
throw new Error('Google OAuth2 not available');
|
|
11824
|
+
}
|
|
11825
|
+
const client = google.accounts.oauth2.initTokenClient({
|
|
11826
|
+
client_id: googleClientId,
|
|
11827
|
+
scope: 'openid email profile',
|
|
11828
|
+
callback: async (response) => {
|
|
11829
|
+
try {
|
|
11830
|
+
if (response.error) {
|
|
11831
|
+
throw new Error(response.error_description || response.error);
|
|
11832
|
+
}
|
|
11833
|
+
const accessToken = response.access_token;
|
|
11834
|
+
// Send access token to backend
|
|
11835
|
+
const authResponse = await api.loginWithGoogle(accessToken);
|
|
11836
|
+
if (authResponse.token) {
|
|
11837
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11838
|
+
setAuthSuccess(true);
|
|
11839
|
+
setSuccessMessage('Google login successful!');
|
|
11840
|
+
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11841
|
+
}
|
|
11842
|
+
else {
|
|
11843
|
+
throw new Error('Authentication failed - no token received');
|
|
11844
|
+
}
|
|
11845
|
+
if (redirectUrl) {
|
|
11846
|
+
setTimeout(() => {
|
|
11847
|
+
window.location.href = redirectUrl;
|
|
11848
|
+
}, 2000);
|
|
11849
|
+
}
|
|
11850
|
+
setLoading(false);
|
|
11851
|
+
}
|
|
11852
|
+
catch (err) {
|
|
11853
|
+
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
11854
|
+
setError(errorMessage);
|
|
11855
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11856
|
+
setLoading(false);
|
|
11857
|
+
}
|
|
11858
|
+
},
|
|
11859
|
+
});
|
|
11860
|
+
client.requestAccessToken();
|
|
11861
|
+
}
|
|
11862
|
+
else {
|
|
11863
|
+
// Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
|
|
11864
|
+
google.accounts.id.initialize({
|
|
11865
|
+
client_id: googleClientId,
|
|
11866
|
+
callback: async (response) => {
|
|
11867
|
+
try {
|
|
11868
|
+
const idToken = response.credential;
|
|
11869
|
+
const authResponse = await api.loginWithGoogle(idToken);
|
|
11870
|
+
if (authResponse.token) {
|
|
11871
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11872
|
+
setAuthSuccess(true);
|
|
11873
|
+
setSuccessMessage('Google login successful!');
|
|
11874
|
+
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11875
|
+
}
|
|
11876
|
+
else {
|
|
11877
|
+
throw new Error('Authentication failed - no token received');
|
|
11878
|
+
}
|
|
11879
|
+
if (redirectUrl) {
|
|
11880
|
+
setTimeout(() => {
|
|
11881
|
+
window.location.href = redirectUrl;
|
|
11882
|
+
}, 2000);
|
|
11883
|
+
}
|
|
11884
|
+
setLoading(false);
|
|
11885
|
+
}
|
|
11886
|
+
catch (err) {
|
|
11887
|
+
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
11888
|
+
setError(errorMessage);
|
|
11889
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11890
|
+
setLoading(false);
|
|
11891
|
+
}
|
|
11892
|
+
},
|
|
11893
|
+
auto_select: false,
|
|
11894
|
+
cancel_on_tap_outside: true,
|
|
11895
|
+
});
|
|
11896
|
+
google.accounts.id.prompt((notification) => {
|
|
11897
|
+
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
|
|
11898
|
+
setLoading(false);
|
|
11899
|
+
}
|
|
11900
|
+
});
|
|
11901
|
+
}
|
|
11653
11902
|
}
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11903
|
+
catch (err) {
|
|
11904
|
+
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
11905
|
+
setError(errorMessage);
|
|
11906
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11907
|
+
setLoading(false);
|
|
11657
11908
|
}
|
|
11909
|
+
};
|
|
11910
|
+
const handlePhoneAuth = async (phoneNumber, verificationCode) => {
|
|
11658
11911
|
setLoading(true);
|
|
11659
11912
|
setError(undefined);
|
|
11660
11913
|
try {
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11914
|
+
if (!verificationCode) {
|
|
11915
|
+
// Send verification code via Twilio Verify Service
|
|
11916
|
+
await api.sendPhoneCode(phoneNumber);
|
|
11917
|
+
// Twilio Verify Service tracks the verification by phone number
|
|
11918
|
+
// No need to store verificationId
|
|
11919
|
+
}
|
|
11920
|
+
else {
|
|
11921
|
+
// Verify code - Twilio identifies the verification by phone number
|
|
11922
|
+
const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
|
|
11923
|
+
// Update auth context with account data if token is provided
|
|
11924
|
+
if (response.token) {
|
|
11925
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11926
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11927
|
+
if (redirectUrl) {
|
|
11928
|
+
window.location.href = redirectUrl;
|
|
11929
|
+
}
|
|
11930
|
+
}
|
|
11931
|
+
else {
|
|
11932
|
+
throw new Error('Authentication failed - no token received');
|
|
11933
|
+
}
|
|
11934
|
+
}
|
|
11674
11935
|
}
|
|
11675
11936
|
catch (err) {
|
|
11676
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
11937
|
+
const errorMessage = err instanceof Error ? err.message : 'Phone authentication failed';
|
|
11677
11938
|
setError(errorMessage);
|
|
11678
|
-
|
|
11939
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11679
11940
|
}
|
|
11680
11941
|
finally {
|
|
11681
11942
|
setLoading(false);
|
|
11682
11943
|
}
|
|
11683
11944
|
};
|
|
11684
|
-
|
|
11685
|
-
|
|
11686
|
-
|
|
11687
|
-
|
|
11688
|
-
|
|
11689
|
-
|
|
11690
|
-
|
|
11691
|
-
|
|
11692
|
-
|
|
11693
|
-
|
|
11694
|
-
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
const [claimStep, setClaimStep] = useState(auth.isAuthenticated ? 'questions' : 'auth');
|
|
11700
|
-
const [claimData, setClaimData] = useState({});
|
|
11701
|
-
const [error, setError] = useState();
|
|
11702
|
-
const [loading, setLoading] = useState(false);
|
|
11703
|
-
const handleAuthSuccess = (token, user, accountData) => {
|
|
11704
|
-
// Authentication successful
|
|
11705
|
-
auth.login(token, user, accountData);
|
|
11706
|
-
// If no additional questions, proceed directly to claim
|
|
11707
|
-
if (additionalFields.length === 0) {
|
|
11708
|
-
executeClaim(user);
|
|
11709
|
-
}
|
|
11710
|
-
else {
|
|
11711
|
-
setClaimStep('questions');
|
|
11945
|
+
const handlePasswordReset = async (emailOrPassword, confirmPassword) => {
|
|
11946
|
+
setLoading(true);
|
|
11947
|
+
setError(undefined);
|
|
11948
|
+
try {
|
|
11949
|
+
if (resetToken && confirmPassword) {
|
|
11950
|
+
// Complete password reset with token
|
|
11951
|
+
await api.completePasswordReset(resetToken, emailOrPassword);
|
|
11952
|
+
setResetSuccess(true);
|
|
11953
|
+
setResetToken(undefined); // Clear token after successful reset
|
|
11954
|
+
}
|
|
11955
|
+
else {
|
|
11956
|
+
// Request password reset email
|
|
11957
|
+
await api.requestPasswordReset(emailOrPassword, getRedirectUrl());
|
|
11958
|
+
setResetSuccess(true);
|
|
11959
|
+
}
|
|
11712
11960
|
}
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
const missingFields = additionalFields
|
|
11718
|
-
.filter(field => field.required && !claimData[field.name])
|
|
11719
|
-
.map(field => field.label);
|
|
11720
|
-
if (missingFields.length > 0) {
|
|
11721
|
-
setError(`Please fill in: ${missingFields.join(', ')}`);
|
|
11722
|
-
return;
|
|
11961
|
+
catch (err) {
|
|
11962
|
+
const errorMessage = err instanceof Error ? err.message : 'Password reset failed';
|
|
11963
|
+
setError(errorMessage);
|
|
11964
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11723
11965
|
}
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
executeClaim(auth.user);
|
|
11966
|
+
finally {
|
|
11967
|
+
setLoading(false);
|
|
11727
11968
|
}
|
|
11728
11969
|
};
|
|
11729
|
-
const
|
|
11730
|
-
setClaimStep('claiming');
|
|
11970
|
+
const handleMagicLink = async (email) => {
|
|
11731
11971
|
setLoading(true);
|
|
11732
11972
|
setError(undefined);
|
|
11733
11973
|
try {
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
claimed: true,
|
|
11738
|
-
claimedAt: new Date().toISOString(),
|
|
11739
|
-
claimedBy: user.uid,
|
|
11740
|
-
...claimData,
|
|
11741
|
-
},
|
|
11742
|
-
private: {},
|
|
11743
|
-
proof: {},
|
|
11744
|
-
});
|
|
11745
|
-
setClaimStep('success');
|
|
11746
|
-
// Call success callback
|
|
11747
|
-
onClaimSuccess({
|
|
11748
|
-
proofId,
|
|
11749
|
-
user,
|
|
11750
|
-
claimData,
|
|
11751
|
-
attestationId: response.id,
|
|
11752
|
-
});
|
|
11974
|
+
await api.sendMagicLink(email, getRedirectUrl());
|
|
11975
|
+
setAuthSuccess(true);
|
|
11976
|
+
setSuccessMessage('Magic link sent! Check your email to log in.');
|
|
11753
11977
|
}
|
|
11754
11978
|
catch (err) {
|
|
11755
|
-
|
|
11756
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to claim proof';
|
|
11979
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to send magic link';
|
|
11757
11980
|
setError(errorMessage);
|
|
11758
|
-
|
|
11759
|
-
setClaimStep(additionalFields.length > 0 ? 'questions' : 'auth');
|
|
11981
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11760
11982
|
}
|
|
11761
11983
|
finally {
|
|
11762
11984
|
setLoading(false);
|
|
11763
11985
|
}
|
|
11764
11986
|
};
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
...prev,
|
|
11768
|
-
[fieldName]: value,
|
|
11769
|
-
}));
|
|
11770
|
-
};
|
|
11771
|
-
// Render authentication step
|
|
11772
|
-
if (claimStep === 'auth') {
|
|
11773
|
-
return (jsx("div", { className: className, children: jsx(SmartlinksAuthUI, { apiEndpoint: apiEndpoint, clientId: clientId, clientName: clientName, onAuthSuccess: handleAuthSuccess, onAuthError: onClaimError, theme: theme, minimal: minimal, customization: customization.authConfig }) }));
|
|
11774
|
-
}
|
|
11775
|
-
// Render additional questions step
|
|
11776
|
-
if (claimStep === 'questions') {
|
|
11777
|
-
return (jsxs("div", { className: `claim-questions ${className}`, children: [jsxs("div", { className: "claim-header mb-6", children: [jsx("h2", { className: "text-2xl font-bold mb-2", children: customization.claimTitle || 'Complete Your Claim' }), customization.claimDescription && (jsx("p", { className: "text-muted-foreground", children: customization.claimDescription }))] }), error && (jsx("div", { className: "claim-error bg-destructive/10 text-destructive px-4 py-3 rounded-md mb-4", children: error })), jsxs("form", { onSubmit: handleQuestionSubmit, className: "claim-form space-y-4", children: [additionalFields.map((field) => (jsxs("div", { className: "claim-field", children: [jsxs("label", { htmlFor: field.name, className: "block text-sm font-medium mb-2", children: [field.label, field.required && jsx("span", { className: "text-destructive ml-1", children: "*" })] }), field.type === 'textarea' ? (jsx("textarea", { id: field.name, name: field.name, placeholder: field.placeholder, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background", rows: 4 })) : field.type === 'select' ? (jsxs("select", { id: field.name, name: field.name, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background", children: [jsx("option", { value: "", children: "Select..." }), field.options?.map((option) => (jsx("option", { value: option, children: option }, option)))] })) : (jsx("input", { id: field.name, name: field.name, type: field.type, placeholder: field.placeholder, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background" }))] }, field.name))), jsx("button", { type: "submit", disabled: loading, className: "claim-submit-button w-full bg-primary text-primary-foreground px-4 py-2 rounded-md font-medium hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed", children: loading ? 'Claiming...' : 'Submit Claim' })] })] }));
|
|
11987
|
+
if (configLoading) {
|
|
11988
|
+
return (jsx(AuthContainer, { theme: theme, className: className, minimal: minimal || config?.branding?.minimal || false, children: jsx("div", { style: { textAlign: 'center', padding: '2rem' }, children: jsx("div", { className: "auth-spinner" }) }) }));
|
|
11778
11989
|
}
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
11790
|
-
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11990
|
+
return (jsx(AuthContainer, { theme: theme, className: className, config: config, minimal: minimal || config?.branding?.minimal || false, children: authSuccess ? (jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [jsx("div", { style: {
|
|
11991
|
+
color: 'var(--auth-primary-color, #4F46E5)',
|
|
11992
|
+
fontSize: '3rem',
|
|
11993
|
+
marginBottom: '1rem'
|
|
11994
|
+
}, children: "\u2713" }), jsx("h2", { style: {
|
|
11995
|
+
marginBottom: '0.5rem',
|
|
11996
|
+
fontSize: '1.5rem',
|
|
11997
|
+
fontWeight: 600
|
|
11998
|
+
}, children: successMessage?.includes('verified') ? 'Email Verified!' :
|
|
11999
|
+
successMessage?.includes('Magic link') ? 'Check Your Email!' :
|
|
12000
|
+
mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsx("p", { style: {
|
|
12001
|
+
color: '#6B7280',
|
|
12002
|
+
fontSize: '0.875rem'
|
|
12003
|
+
}, 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: () => {
|
|
12004
|
+
setMode('login');
|
|
12005
|
+
setResetSuccess(false);
|
|
12006
|
+
setResetToken(undefined); // Clear token when going back
|
|
12007
|
+
}, loading: loading, error: error, success: resetSuccess, token: resetToken })) : (mode === 'login' || mode === 'register') ? (jsx(Fragment, { children: showResendVerification ? (jsxs("div", { style: { marginTop: '1rem', padding: '1.5rem', backgroundColor: 'rgba(79, 70, 229, 0.05)', borderRadius: '0.5rem' }, children: [jsx("h3", { style: { marginBottom: '0.75rem', fontSize: '1rem', fontWeight: 600, color: 'var(--auth-text-color, #374151)' }, children: "Verification Link Expired" }), jsx("p", { style: { marginBottom: '1rem', fontSize: '0.875rem', color: 'var(--auth-text-color, #6B7280)', lineHeight: '1.5' }, children: "Your verification link has expired or is no longer valid. Please enter your email address below and we'll send you a new verification link." }), jsx("input", { type: "email", value: resendEmail || '', onChange: (e) => setResendEmail(e.target.value), placeholder: "your@email.com", style: {
|
|
12008
|
+
width: '100%',
|
|
12009
|
+
padding: '0.625rem',
|
|
12010
|
+
marginBottom: '1rem',
|
|
12011
|
+
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12012
|
+
borderRadius: '0.375rem',
|
|
12013
|
+
fontSize: '0.875rem',
|
|
12014
|
+
boxSizing: 'border-box'
|
|
12015
|
+
} }), jsxs("div", { style: { display: 'flex', gap: '0.75rem' }, children: [jsx("button", { onClick: handleResendVerification, disabled: loading || !resendEmail, style: {
|
|
12016
|
+
flex: 1,
|
|
12017
|
+
padding: '0.625rem 1rem',
|
|
12018
|
+
backgroundColor: 'var(--auth-primary-color, #4F46E5)',
|
|
12019
|
+
color: 'white',
|
|
12020
|
+
border: 'none',
|
|
12021
|
+
borderRadius: '0.375rem',
|
|
12022
|
+
cursor: (loading || !resendEmail) ? 'not-allowed' : 'pointer',
|
|
12023
|
+
fontSize: '0.875rem',
|
|
12024
|
+
fontWeight: 500,
|
|
12025
|
+
opacity: (loading || !resendEmail) ? 0.6 : 1
|
|
12026
|
+
}, children: loading ? 'Sending...' : 'Send New Verification Link' }), jsx("button", { onClick: () => {
|
|
12027
|
+
setShowResendVerification(false);
|
|
12028
|
+
setResendEmail('');
|
|
12029
|
+
setError(undefined);
|
|
12030
|
+
}, disabled: loading, style: {
|
|
12031
|
+
padding: '0.625rem 1rem',
|
|
12032
|
+
backgroundColor: 'transparent',
|
|
12033
|
+
color: 'var(--auth-text-color, #6B7280)',
|
|
12034
|
+
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12035
|
+
borderRadius: '0.375rem',
|
|
12036
|
+
cursor: loading ? 'not-allowed' : 'pointer',
|
|
12037
|
+
fontSize: '0.875rem',
|
|
12038
|
+
fontWeight: 500,
|
|
12039
|
+
opacity: loading ? 0.6 : 1
|
|
12040
|
+
}, children: "Cancel" })] })] })) : showRequestNewReset ? (jsxs("div", { style: { marginTop: '1rem', padding: '1.5rem', backgroundColor: 'rgba(239, 68, 68, 0.05)', borderRadius: '0.5rem' }, children: [jsx("h3", { style: { marginBottom: '0.75rem', fontSize: '1rem', fontWeight: 600, color: 'var(--auth-text-color, #374151)' }, children: "Password Reset Link Expired" }), jsx("p", { style: { marginBottom: '1rem', fontSize: '0.875rem', color: 'var(--auth-text-color, #6B7280)', lineHeight: '1.5' }, children: "Your password reset link has expired or is no longer valid. Please enter your email address below and we'll send you a new password reset link." }), jsx("input", { type: "email", value: resetRequestEmail || '', onChange: (e) => setResetRequestEmail(e.target.value), placeholder: "your@email.com", style: {
|
|
12041
|
+
width: '100%',
|
|
12042
|
+
padding: '0.625rem',
|
|
12043
|
+
marginBottom: '1rem',
|
|
12044
|
+
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12045
|
+
borderRadius: '0.375rem',
|
|
12046
|
+
fontSize: '0.875rem',
|
|
12047
|
+
boxSizing: 'border-box'
|
|
12048
|
+
} }), jsxs("div", { style: { display: 'flex', gap: '0.75rem' }, children: [jsx("button", { onClick: handleRequestNewReset, disabled: loading || !resetRequestEmail, style: {
|
|
12049
|
+
flex: 1,
|
|
12050
|
+
padding: '0.625rem 1rem',
|
|
12051
|
+
backgroundColor: '#EF4444',
|
|
12052
|
+
color: 'white',
|
|
12053
|
+
border: 'none',
|
|
12054
|
+
borderRadius: '0.375rem',
|
|
12055
|
+
cursor: (loading || !resetRequestEmail) ? 'not-allowed' : 'pointer',
|
|
12056
|
+
fontSize: '0.875rem',
|
|
12057
|
+
fontWeight: 500,
|
|
12058
|
+
opacity: (loading || !resetRequestEmail) ? 0.6 : 1
|
|
12059
|
+
}, children: loading ? 'Sending...' : 'Send New Reset Link' }), jsx("button", { onClick: () => {
|
|
12060
|
+
setShowRequestNewReset(false);
|
|
12061
|
+
setResetRequestEmail('');
|
|
12062
|
+
setError(undefined);
|
|
12063
|
+
}, disabled: loading, style: {
|
|
12064
|
+
padding: '0.625rem 1rem',
|
|
12065
|
+
backgroundColor: 'transparent',
|
|
12066
|
+
color: 'var(--auth-text-color, #6B7280)',
|
|
12067
|
+
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12068
|
+
borderRadius: '0.375rem',
|
|
12069
|
+
cursor: loading ? 'not-allowed' : 'pointer',
|
|
12070
|
+
fontSize: '0.875rem',
|
|
12071
|
+
fontWeight: 500,
|
|
12072
|
+
opacity: loading ? 0.6 : 1
|
|
12073
|
+
}, children: "Cancel" })] })] })) : (jsx(Fragment, { children: (() => {
|
|
12074
|
+
const emailDisplayMode = config?.emailDisplayMode || 'form';
|
|
12075
|
+
const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
|
|
12076
|
+
const actualProviders = config?.enabledProviders || enabledProviders;
|
|
12077
|
+
// Button mode: show provider selection first, then email form if email is selected
|
|
12078
|
+
if (emailDisplayMode === 'button' && !showEmailForm) {
|
|
12079
|
+
return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
12080
|
+
}
|
|
12081
|
+
// Form mode or email button was clicked: show email form with other providers
|
|
12082
|
+
return (jsxs(Fragment, { children: [emailDisplayMode === 'button' && showEmailForm && (jsx("button", { onClick: () => setShowEmailForm(false), style: {
|
|
12083
|
+
marginBottom: '1rem',
|
|
12084
|
+
padding: '0.5rem',
|
|
12085
|
+
background: 'none',
|
|
12086
|
+
border: 'none',
|
|
12087
|
+
color: 'var(--auth-text-color, #6B7280)',
|
|
12088
|
+
cursor: 'pointer',
|
|
12089
|
+
fontSize: '0.875rem',
|
|
12090
|
+
display: 'flex',
|
|
12091
|
+
alignItems: 'center',
|
|
12092
|
+
gap: '0.25rem'
|
|
12093
|
+
}, children: "\u2190 Back to options" })), jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
|
|
12094
|
+
setMode(mode === 'login' ? 'register' : 'login');
|
|
12095
|
+
setShowResendVerification(false);
|
|
12096
|
+
setShowRequestNewReset(false);
|
|
12097
|
+
setError(undefined);
|
|
12098
|
+
}, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
12099
|
+
})() })) })) : null }));
|
|
11837
12100
|
};
|
|
11838
12101
|
|
|
11839
|
-
|
|
11840
|
-
const
|
|
11841
|
-
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, minimal = false, }) => {
|
|
11842
|
-
const [mode, setMode] = useState(initialMode);
|
|
12102
|
+
const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', customization = {}, }) => {
|
|
12103
|
+
const auth = useAuth();
|
|
11843
12104
|
const [loading, setLoading] = useState(false);
|
|
12105
|
+
const [profile, setProfile] = useState(null);
|
|
11844
12106
|
const [error, setError] = useState();
|
|
11845
|
-
const [
|
|
11846
|
-
|
|
11847
|
-
const [
|
|
11848
|
-
|
|
11849
|
-
const [
|
|
11850
|
-
|
|
11851
|
-
const [
|
|
11852
|
-
const [
|
|
11853
|
-
|
|
11854
|
-
const [
|
|
11855
|
-
const [
|
|
11856
|
-
const
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
proxyMode: false, // Direct API calls when custom endpoint is provided
|
|
11868
|
-
ngrokSkipBrowserWarning: true,
|
|
11869
|
-
});
|
|
11870
|
-
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11871
|
-
if (currentToken) {
|
|
11872
|
-
smartlinks.auth.verifyToken(currentToken).catch(err => {
|
|
11873
|
-
console.warn('Failed to restore bearer token after reinit:', err);
|
|
11874
|
-
});
|
|
11875
|
-
}
|
|
11876
|
-
}
|
|
11877
|
-
};
|
|
11878
|
-
reinitializeWithToken();
|
|
11879
|
-
}, [apiEndpoint, auth]);
|
|
11880
|
-
// Get the effective redirect URL (use prop or default to current page)
|
|
11881
|
-
const getRedirectUrl = () => {
|
|
11882
|
-
if (redirectUrl)
|
|
11883
|
-
return redirectUrl;
|
|
11884
|
-
// Get the full current URL including hash routes
|
|
11885
|
-
// Remove any existing query parameters to avoid duplication
|
|
11886
|
-
const currentUrl = window.location.href.split('?')[0];
|
|
11887
|
-
return currentUrl;
|
|
11888
|
-
};
|
|
11889
|
-
// Fetch UI configuration
|
|
11890
|
-
useEffect(() => {
|
|
11891
|
-
if (skipConfigFetch) {
|
|
11892
|
-
setConfig(customization || {});
|
|
11893
|
-
return;
|
|
11894
|
-
}
|
|
11895
|
-
const fetchConfig = async () => {
|
|
11896
|
-
try {
|
|
11897
|
-
// Check localStorage cache first
|
|
11898
|
-
const cacheKey = `auth_ui_config_${clientId || 'default'}`;
|
|
11899
|
-
const cached = localStorage.getItem(cacheKey);
|
|
11900
|
-
if (cached) {
|
|
11901
|
-
const { config: cachedConfig, timestamp } = JSON.parse(cached);
|
|
11902
|
-
const age = Date.now() - timestamp;
|
|
11903
|
-
// Use cache if less than 1 hour old
|
|
11904
|
-
if (age < 3600000) {
|
|
11905
|
-
setConfig({ ...cachedConfig, ...customization });
|
|
11906
|
-
setConfigLoading(false);
|
|
11907
|
-
// Fetch in background to update cache
|
|
11908
|
-
api.fetchConfig().then(freshConfig => {
|
|
11909
|
-
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11910
|
-
config: freshConfig,
|
|
11911
|
-
timestamp: Date.now()
|
|
11912
|
-
}));
|
|
11913
|
-
});
|
|
11914
|
-
return;
|
|
11915
|
-
}
|
|
11916
|
-
}
|
|
11917
|
-
// Fetch from API
|
|
11918
|
-
const fetchedConfig = await api.fetchConfig();
|
|
11919
|
-
// Merge with customization props (props take precedence)
|
|
11920
|
-
const mergedConfig = { ...fetchedConfig, ...customization };
|
|
11921
|
-
setConfig(mergedConfig);
|
|
11922
|
-
// Cache the fetched config
|
|
11923
|
-
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11924
|
-
config: fetchedConfig,
|
|
11925
|
-
timestamp: Date.now()
|
|
11926
|
-
}));
|
|
11927
|
-
}
|
|
11928
|
-
catch (err) {
|
|
11929
|
-
console.error('Failed to fetch config:', err);
|
|
11930
|
-
setConfig(customization || {});
|
|
11931
|
-
}
|
|
11932
|
-
finally {
|
|
11933
|
-
setConfigLoading(false);
|
|
11934
|
-
}
|
|
11935
|
-
};
|
|
11936
|
-
fetchConfig();
|
|
11937
|
-
}, [apiEndpoint, clientId, customization, skipConfigFetch]);
|
|
11938
|
-
// Reset showEmailForm when mode changes away from login/register
|
|
12107
|
+
const [success, setSuccess] = useState();
|
|
12108
|
+
// Track which section is being edited
|
|
12109
|
+
const [editingSection, setEditingSection] = useState(null);
|
|
12110
|
+
// Profile form state
|
|
12111
|
+
const [displayName, setDisplayName] = useState('');
|
|
12112
|
+
// Email change state
|
|
12113
|
+
const [newEmail, setNewEmail] = useState('');
|
|
12114
|
+
const [emailPassword, setEmailPassword] = useState('');
|
|
12115
|
+
// Password change state
|
|
12116
|
+
const [currentPassword, setCurrentPassword] = useState('');
|
|
12117
|
+
const [newPassword, setNewPassword] = useState('');
|
|
12118
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
12119
|
+
// Phone change state (reuses existing sendPhoneCode flow)
|
|
12120
|
+
const [newPhone, setNewPhone] = useState('');
|
|
12121
|
+
const [phoneCode, setPhoneCode] = useState('');
|
|
12122
|
+
const [phoneCodeSent, setPhoneCodeSent] = useState(false);
|
|
12123
|
+
// Account deletion state
|
|
12124
|
+
const [deletePassword, setDeletePassword] = useState('');
|
|
12125
|
+
const [deleteConfirmText, setDeleteConfirmText] = useState('');
|
|
12126
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
12127
|
+
const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, } = customization;
|
|
12128
|
+
// Reinitialize Smartlinks SDK when apiEndpoint changes
|
|
11939
12129
|
useEffect(() => {
|
|
11940
|
-
if (
|
|
11941
|
-
|
|
12130
|
+
if (apiEndpoint) {
|
|
12131
|
+
smartlinks.initializeApi({
|
|
12132
|
+
baseURL: apiEndpoint,
|
|
12133
|
+
proxyMode: false,
|
|
12134
|
+
ngrokSkipBrowserWarning: true,
|
|
12135
|
+
});
|
|
11942
12136
|
}
|
|
11943
|
-
}, [
|
|
11944
|
-
//
|
|
12137
|
+
}, [apiEndpoint]);
|
|
12138
|
+
// Load user profile on mount
|
|
11945
12139
|
useEffect(() => {
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
// Extract query string from hash (e.g., #/test?mode=verifyEmail&token=abc)
|
|
11953
|
-
const hashQuery = hash.substring(hashQueryIndex + 1);
|
|
11954
|
-
return new URLSearchParams(hashQuery);
|
|
11955
|
-
}
|
|
11956
|
-
// Fall back to regular search params (for non-hash routing)
|
|
11957
|
-
return new URLSearchParams(window.location.search);
|
|
11958
|
-
};
|
|
11959
|
-
const params = getUrlParams();
|
|
11960
|
-
const urlMode = params.get('mode');
|
|
11961
|
-
const token = params.get('token');
|
|
11962
|
-
console.log('URL params detected:', { urlMode, token, hash: window.location.hash, search: window.location.search });
|
|
11963
|
-
if (urlMode && token) {
|
|
11964
|
-
handleURLBasedAuth(urlMode, token);
|
|
12140
|
+
loadProfile();
|
|
12141
|
+
}, [clientId]);
|
|
12142
|
+
const loadProfile = async () => {
|
|
12143
|
+
if (!auth.isAuthenticated) {
|
|
12144
|
+
setError('You must be logged in to manage your account');
|
|
12145
|
+
return;
|
|
11965
12146
|
}
|
|
11966
|
-
}, []);
|
|
11967
|
-
const handleURLBasedAuth = async (urlMode, token) => {
|
|
11968
12147
|
setLoading(true);
|
|
11969
12148
|
setError(undefined);
|
|
11970
12149
|
try {
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
if (redirectUrl) {
|
|
11987
|
-
setTimeout(() => {
|
|
11988
|
-
window.location.href = redirectUrl;
|
|
11989
|
-
}, 2000);
|
|
11990
|
-
}
|
|
11991
|
-
}
|
|
11992
|
-
else {
|
|
11993
|
-
// verify-then-manual-login mode or no token: Show success but require manual login
|
|
11994
|
-
setAuthSuccess(true);
|
|
11995
|
-
setSuccessMessage('Email verified successfully! Please log in with your credentials.');
|
|
11996
|
-
// Clear the URL parameters
|
|
11997
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
11998
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
11999
|
-
// Switch back to login mode after a delay
|
|
12000
|
-
setTimeout(() => {
|
|
12001
|
-
setAuthSuccess(false);
|
|
12002
|
-
setMode('login');
|
|
12003
|
-
}, 3000);
|
|
12004
|
-
}
|
|
12005
|
-
}
|
|
12006
|
-
else if (urlMode === 'resetPassword') {
|
|
12007
|
-
console.log('Verifying reset token:', token);
|
|
12008
|
-
// Verify token is valid, then show password reset form
|
|
12009
|
-
await api.verifyResetToken(token);
|
|
12010
|
-
setResetToken(token); // Store token for use in password reset
|
|
12011
|
-
setMode('reset-password');
|
|
12012
|
-
// Clear the URL parameters
|
|
12013
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
12014
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
12015
|
-
}
|
|
12016
|
-
else if (urlMode === 'magicLink') {
|
|
12017
|
-
console.log('Verifying magic link token:', token);
|
|
12018
|
-
const response = await api.verifyMagicLink(token);
|
|
12019
|
-
// Auto-login with magic link if token is provided
|
|
12020
|
-
if (response.token) {
|
|
12021
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12022
|
-
setAuthSuccess(true);
|
|
12023
|
-
setSuccessMessage('Magic link verified! You are now logged in.');
|
|
12024
|
-
onAuthSuccess(response.token, response.user, response.accountData);
|
|
12025
|
-
// Clear the URL parameters
|
|
12026
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
12027
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
12028
|
-
// Redirect after a brief delay to show success message
|
|
12029
|
-
if (redirectUrl) {
|
|
12030
|
-
setTimeout(() => {
|
|
12031
|
-
window.location.href = redirectUrl;
|
|
12032
|
-
}, 2000);
|
|
12033
|
-
}
|
|
12034
|
-
}
|
|
12035
|
-
else {
|
|
12036
|
-
throw new Error('Authentication failed - no token received');
|
|
12037
|
-
}
|
|
12038
|
-
}
|
|
12150
|
+
// TODO: Backend implementation required
|
|
12151
|
+
// Endpoint: GET /api/v1/authkit/:clientId/account/profile
|
|
12152
|
+
// SDK method: smartlinks.authKit.getProfile(clientId)
|
|
12153
|
+
// Temporary mock data for UI testing
|
|
12154
|
+
const profileData = {
|
|
12155
|
+
uid: auth.user?.uid || '',
|
|
12156
|
+
email: auth.user?.email,
|
|
12157
|
+
displayName: auth.user?.displayName,
|
|
12158
|
+
phoneNumber: auth.user?.phoneNumber,
|
|
12159
|
+
photoURL: auth.user?.photoURL,
|
|
12160
|
+
emailVerified: true,
|
|
12161
|
+
accountData: auth.accountData || {},
|
|
12162
|
+
};
|
|
12163
|
+
setProfile(profileData);
|
|
12164
|
+
setDisplayName(profileData.displayName || '');
|
|
12039
12165
|
}
|
|
12040
12166
|
catch (err) {
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
if (urlMode === 'verifyEmail') {
|
|
12045
|
-
setError(`${errorMessage} - Please enter your email below to receive a new verification link.`);
|
|
12046
|
-
setShowResendVerification(true);
|
|
12047
|
-
setMode('login'); // Show the login form UI
|
|
12048
|
-
// Clear the URL parameters
|
|
12049
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
12050
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
12051
|
-
}
|
|
12052
|
-
else if (urlMode === 'resetPassword') {
|
|
12053
|
-
// If password reset token is invalid/expired, show request new reset link option
|
|
12054
|
-
setError(`${errorMessage} - Please enter your email below to receive a new password reset link.`);
|
|
12055
|
-
setShowRequestNewReset(true);
|
|
12056
|
-
setMode('login');
|
|
12057
|
-
// Clear the URL parameters
|
|
12058
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
12059
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
12060
|
-
}
|
|
12061
|
-
else if (urlMode === 'magicLink') {
|
|
12062
|
-
// If magic link is invalid/expired
|
|
12063
|
-
setError(`${errorMessage} - Please request a new magic link below.`);
|
|
12064
|
-
setMode('magic-link');
|
|
12065
|
-
// Clear the URL parameters
|
|
12066
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
12067
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
12068
|
-
}
|
|
12069
|
-
else {
|
|
12070
|
-
setError(errorMessage);
|
|
12071
|
-
}
|
|
12072
|
-
onAuthError?.(err instanceof Error ? err : new Error('An error occurred'));
|
|
12167
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to load profile';
|
|
12168
|
+
setError(errorMessage);
|
|
12169
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12073
12170
|
}
|
|
12074
12171
|
finally {
|
|
12075
12172
|
setLoading(false);
|
|
12076
12173
|
}
|
|
12077
12174
|
};
|
|
12078
|
-
const
|
|
12175
|
+
const handleUpdateProfile = async (e) => {
|
|
12176
|
+
e.preventDefault();
|
|
12079
12177
|
setLoading(true);
|
|
12080
12178
|
setError(undefined);
|
|
12081
|
-
|
|
12179
|
+
setSuccess(undefined);
|
|
12082
12180
|
try {
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
//
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
const deadline = response.emailVerificationDeadline
|
|
12100
|
-
? new Date(response.emailVerificationDeadline).toLocaleString()
|
|
12101
|
-
: `${gracePeriodHours} hours`;
|
|
12102
|
-
setSuccessMessage(`Account created! You're logged in now. Please verify your email by ${deadline} to keep your account active.`);
|
|
12103
|
-
if (response.token) {
|
|
12104
|
-
onAuthSuccess(response.token, response.user, response.accountData);
|
|
12105
|
-
}
|
|
12106
|
-
if (redirectUrl) {
|
|
12107
|
-
setTimeout(() => {
|
|
12108
|
-
window.location.href = redirectUrl;
|
|
12109
|
-
}, 2000);
|
|
12110
|
-
}
|
|
12111
|
-
}
|
|
12112
|
-
else if (verificationMode === 'verify-then-auto-login') {
|
|
12113
|
-
// Verify-then-auto-login mode: Don't log in yet, but will auto-login after email verification
|
|
12114
|
-
setAuthSuccess(true);
|
|
12115
|
-
setSuccessMessage('Account created! Please check your email and click the verification link to complete your registration.');
|
|
12116
|
-
}
|
|
12117
|
-
else {
|
|
12118
|
-
// verify-then-manual-login mode: Traditional flow
|
|
12119
|
-
setAuthSuccess(true);
|
|
12120
|
-
setSuccessMessage('Account created successfully! Please check your email to verify your account, then log in.');
|
|
12121
|
-
}
|
|
12122
|
-
}
|
|
12123
|
-
else {
|
|
12124
|
-
// Login mode - always log in if token is provided
|
|
12125
|
-
if (response.token) {
|
|
12126
|
-
// Check for account lock or verification requirements
|
|
12127
|
-
if (response.accountLocked) {
|
|
12128
|
-
throw new Error('Your account has been locked due to unverified email. Please check your email or request a new verification link.');
|
|
12129
|
-
}
|
|
12130
|
-
if (response.requiresEmailVerification) {
|
|
12131
|
-
throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
|
|
12132
|
-
}
|
|
12133
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12134
|
-
setAuthSuccess(true);
|
|
12135
|
-
setSuccessMessage('Login successful!');
|
|
12136
|
-
onAuthSuccess(response.token, response.user, response.accountData);
|
|
12137
|
-
if (redirectUrl) {
|
|
12138
|
-
setTimeout(() => {
|
|
12139
|
-
window.location.href = redirectUrl;
|
|
12140
|
-
}, 2000);
|
|
12141
|
-
}
|
|
12142
|
-
}
|
|
12143
|
-
else {
|
|
12144
|
-
throw new Error('Authentication failed - please verify your email before logging in.');
|
|
12145
|
-
}
|
|
12146
|
-
}
|
|
12181
|
+
// TODO: Backend implementation required
|
|
12182
|
+
// Endpoint: POST /api/v1/authkit/:clientId/account/update-profile
|
|
12183
|
+
// SDK method: smartlinks.authKit.updateProfile(clientId, updateData)
|
|
12184
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12185
|
+
console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-profile');
|
|
12186
|
+
console.log('Update data:', { displayName });
|
|
12187
|
+
// Uncomment when backend is ready:
|
|
12188
|
+
// const updateData: ProfileUpdateData = {
|
|
12189
|
+
// displayName: displayName || undefined,
|
|
12190
|
+
// photoURL: photoURL || undefined,
|
|
12191
|
+
// };
|
|
12192
|
+
// const updatedProfile = await smartlinks.authKit.updateProfile(clientId, updateData);
|
|
12193
|
+
// setProfile(updatedProfile);
|
|
12194
|
+
// setSuccess('Profile updated successfully!');
|
|
12195
|
+
// setEditingSection(null);
|
|
12196
|
+
// onProfileUpdated?.(updatedProfile);
|
|
12147
12197
|
}
|
|
12148
12198
|
catch (err) {
|
|
12149
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12199
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to update profile';
|
|
12200
|
+
setError(errorMessage);
|
|
12201
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12202
|
+
}
|
|
12203
|
+
finally {
|
|
12204
|
+
setLoading(false);
|
|
12205
|
+
}
|
|
12206
|
+
};
|
|
12207
|
+
const cancelEdit = () => {
|
|
12208
|
+
setEditingSection(null);
|
|
12209
|
+
setDisplayName(profile?.displayName || '');
|
|
12210
|
+
setNewEmail('');
|
|
12211
|
+
setEmailPassword('');
|
|
12212
|
+
setCurrentPassword('');
|
|
12213
|
+
setNewPassword('');
|
|
12214
|
+
setConfirmPassword('');
|
|
12215
|
+
setNewPhone('');
|
|
12216
|
+
setPhoneCode('');
|
|
12217
|
+
setPhoneCodeSent(false);
|
|
12218
|
+
setError(undefined);
|
|
12219
|
+
setSuccess(undefined);
|
|
12220
|
+
};
|
|
12221
|
+
const handleChangeEmail = async (e) => {
|
|
12222
|
+
e.preventDefault();
|
|
12223
|
+
setLoading(true);
|
|
12224
|
+
setError(undefined);
|
|
12225
|
+
setSuccess(undefined);
|
|
12226
|
+
try {
|
|
12227
|
+
// TODO: Backend implementation required
|
|
12228
|
+
// Endpoint: POST /api/v1/authkit/:clientId/account/change-email
|
|
12229
|
+
// SDK method: smartlinks.authKit.changeEmail(clientId, newEmail, password)
|
|
12230
|
+
// Note: No verification flow for now - direct email update
|
|
12231
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12232
|
+
console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-email');
|
|
12233
|
+
console.log('Data:', { newEmail });
|
|
12234
|
+
// Uncomment when backend is ready:
|
|
12235
|
+
// await smartlinks.authKit.changeEmail(clientId, newEmail, emailPassword);
|
|
12236
|
+
// setSuccess('Email changed successfully!');
|
|
12237
|
+
// setEditingSection(null);
|
|
12238
|
+
// setNewEmail('');
|
|
12239
|
+
// setEmailPassword('');
|
|
12240
|
+
// onEmailChangeRequested?.();
|
|
12241
|
+
// await loadProfile(); // Reload to show new email
|
|
12242
|
+
}
|
|
12243
|
+
catch (err) {
|
|
12244
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to change email';
|
|
12245
|
+
setError(errorMessage);
|
|
12246
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12160
12247
|
}
|
|
12161
12248
|
finally {
|
|
12162
12249
|
setLoading(false);
|
|
12163
12250
|
}
|
|
12164
12251
|
};
|
|
12165
|
-
const
|
|
12166
|
-
|
|
12252
|
+
const handleChangePassword = async (e) => {
|
|
12253
|
+
e.preventDefault();
|
|
12254
|
+
if (newPassword !== confirmPassword) {
|
|
12255
|
+
setError('New passwords do not match');
|
|
12256
|
+
return;
|
|
12257
|
+
}
|
|
12258
|
+
if (newPassword.length < 6) {
|
|
12259
|
+
setError('Password must be at least 6 characters');
|
|
12167
12260
|
return;
|
|
12261
|
+
}
|
|
12168
12262
|
setLoading(true);
|
|
12169
12263
|
setError(undefined);
|
|
12264
|
+
setSuccess(undefined);
|
|
12170
12265
|
try {
|
|
12171
|
-
//
|
|
12172
|
-
//
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12266
|
+
// TODO: Backend implementation required
|
|
12267
|
+
// Endpoint: POST /api/v1/authkit/:clientId/account/change-password
|
|
12268
|
+
// SDK method: smartlinks.authKit.changePassword(clientId, currentPassword, newPassword)
|
|
12269
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12270
|
+
console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-password');
|
|
12271
|
+
console.log('Data: currentPassword and newPassword provided');
|
|
12272
|
+
// Uncomment when backend is ready:
|
|
12273
|
+
// await smartlinks.authKit.changePassword(clientId, currentPassword, newPassword);
|
|
12274
|
+
// setSuccess('Password changed successfully!');
|
|
12275
|
+
// setEditingSection(null);
|
|
12276
|
+
// setCurrentPassword('');
|
|
12277
|
+
// setNewPassword('');
|
|
12278
|
+
// setConfirmPassword('');
|
|
12279
|
+
// onPasswordChanged?.();
|
|
12177
12280
|
}
|
|
12178
12281
|
catch (err) {
|
|
12179
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to
|
|
12282
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to change password';
|
|
12180
12283
|
setError(errorMessage);
|
|
12181
|
-
|
|
12284
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12182
12285
|
}
|
|
12183
12286
|
finally {
|
|
12184
12287
|
setLoading(false);
|
|
12185
12288
|
}
|
|
12186
12289
|
};
|
|
12187
|
-
const
|
|
12188
|
-
if (!resetRequestEmail)
|
|
12189
|
-
return;
|
|
12290
|
+
const handleSendPhoneCode = async () => {
|
|
12190
12291
|
setLoading(true);
|
|
12191
12292
|
setError(undefined);
|
|
12192
12293
|
try {
|
|
12193
|
-
await
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
setShowRequestNewReset(false);
|
|
12197
|
-
setResetRequestEmail('');
|
|
12294
|
+
await smartlinks.authKit.sendPhoneCode(clientId, newPhone);
|
|
12295
|
+
setPhoneCodeSent(true);
|
|
12296
|
+
setSuccess('Verification code sent to your phone');
|
|
12198
12297
|
}
|
|
12199
12298
|
catch (err) {
|
|
12200
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to send
|
|
12299
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to send verification code';
|
|
12201
12300
|
setError(errorMessage);
|
|
12202
|
-
|
|
12301
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12203
12302
|
}
|
|
12204
12303
|
finally {
|
|
12205
12304
|
setLoading(false);
|
|
12206
12305
|
}
|
|
12207
12306
|
};
|
|
12208
|
-
const
|
|
12209
|
-
|
|
12210
|
-
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
12211
|
-
// Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
|
|
12212
|
-
const oauthFlow = config?.googleOAuthFlow || 'oneTap';
|
|
12307
|
+
const handleUpdatePhone = async (e) => {
|
|
12308
|
+
e.preventDefault();
|
|
12213
12309
|
setLoading(true);
|
|
12214
12310
|
setError(undefined);
|
|
12311
|
+
setSuccess(undefined);
|
|
12215
12312
|
try {
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
12230
|
-
if (response.error) {
|
|
12231
|
-
throw new Error(response.error_description || response.error);
|
|
12232
|
-
}
|
|
12233
|
-
const accessToken = response.access_token;
|
|
12234
|
-
// Send access token to backend
|
|
12235
|
-
const authResponse = await api.loginWithGoogle(accessToken);
|
|
12236
|
-
if (authResponse.token) {
|
|
12237
|
-
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12238
|
-
setAuthSuccess(true);
|
|
12239
|
-
setSuccessMessage('Google login successful!');
|
|
12240
|
-
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12241
|
-
}
|
|
12242
|
-
else {
|
|
12243
|
-
throw new Error('Authentication failed - no token received');
|
|
12244
|
-
}
|
|
12245
|
-
if (redirectUrl) {
|
|
12246
|
-
setTimeout(() => {
|
|
12247
|
-
window.location.href = redirectUrl;
|
|
12248
|
-
}, 2000);
|
|
12249
|
-
}
|
|
12250
|
-
setLoading(false);
|
|
12251
|
-
}
|
|
12252
|
-
catch (err) {
|
|
12253
|
-
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
12254
|
-
setError(errorMessage);
|
|
12255
|
-
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12256
|
-
setLoading(false);
|
|
12257
|
-
}
|
|
12258
|
-
},
|
|
12259
|
-
});
|
|
12260
|
-
client.requestAccessToken();
|
|
12261
|
-
}
|
|
12262
|
-
else {
|
|
12263
|
-
// Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
|
|
12264
|
-
google.accounts.id.initialize({
|
|
12265
|
-
client_id: googleClientId,
|
|
12266
|
-
callback: async (response) => {
|
|
12267
|
-
try {
|
|
12268
|
-
const idToken = response.credential;
|
|
12269
|
-
const authResponse = await api.loginWithGoogle(idToken);
|
|
12270
|
-
if (authResponse.token) {
|
|
12271
|
-
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12272
|
-
setAuthSuccess(true);
|
|
12273
|
-
setSuccessMessage('Google login successful!');
|
|
12274
|
-
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12275
|
-
}
|
|
12276
|
-
else {
|
|
12277
|
-
throw new Error('Authentication failed - no token received');
|
|
12278
|
-
}
|
|
12279
|
-
if (redirectUrl) {
|
|
12280
|
-
setTimeout(() => {
|
|
12281
|
-
window.location.href = redirectUrl;
|
|
12282
|
-
}, 2000);
|
|
12283
|
-
}
|
|
12284
|
-
setLoading(false);
|
|
12285
|
-
}
|
|
12286
|
-
catch (err) {
|
|
12287
|
-
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
12288
|
-
setError(errorMessage);
|
|
12289
|
-
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12290
|
-
setLoading(false);
|
|
12291
|
-
}
|
|
12292
|
-
},
|
|
12293
|
-
auto_select: false,
|
|
12294
|
-
cancel_on_tap_outside: true,
|
|
12295
|
-
});
|
|
12296
|
-
google.accounts.id.prompt((notification) => {
|
|
12297
|
-
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
|
|
12298
|
-
setLoading(false);
|
|
12299
|
-
}
|
|
12300
|
-
});
|
|
12301
|
-
}
|
|
12313
|
+
// TODO: Backend implementation required
|
|
12314
|
+
// Endpoint: POST /api/v1/authkit/:clientId/account/update-phone
|
|
12315
|
+
// SDK method: smartlinks.authKit.updatePhone(clientId, phoneNumber, verificationCode)
|
|
12316
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12317
|
+
console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-phone');
|
|
12318
|
+
console.log('Data:', { phoneNumber: newPhone, code: phoneCode });
|
|
12319
|
+
// Uncomment when backend is ready:
|
|
12320
|
+
// await smartlinks.authKit.updatePhone(clientId, newPhone, phoneCode);
|
|
12321
|
+
// setSuccess('Phone number updated successfully!');
|
|
12322
|
+
// setEditingSection(null);
|
|
12323
|
+
// setNewPhone('');
|
|
12324
|
+
// setPhoneCode('');
|
|
12325
|
+
// setPhoneCodeSent(false);
|
|
12326
|
+
// await loadProfile();
|
|
12302
12327
|
}
|
|
12303
12328
|
catch (err) {
|
|
12304
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
12329
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to update phone number';
|
|
12305
12330
|
setError(errorMessage);
|
|
12306
|
-
|
|
12331
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12332
|
+
}
|
|
12333
|
+
finally {
|
|
12307
12334
|
setLoading(false);
|
|
12308
12335
|
}
|
|
12309
12336
|
};
|
|
12310
|
-
const
|
|
12337
|
+
const handleDeleteAccount = async () => {
|
|
12338
|
+
if (deleteConfirmText !== 'DELETE') {
|
|
12339
|
+
setError('Please type DELETE to confirm account deletion');
|
|
12340
|
+
return;
|
|
12341
|
+
}
|
|
12342
|
+
if (!deletePassword) {
|
|
12343
|
+
setError('Password is required');
|
|
12344
|
+
return;
|
|
12345
|
+
}
|
|
12311
12346
|
setLoading(true);
|
|
12312
12347
|
setError(undefined);
|
|
12313
12348
|
try {
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12349
|
+
// TODO: Backend implementation required
|
|
12350
|
+
// Endpoint: DELETE /api/v1/authkit/:clientId/account/delete
|
|
12351
|
+
// SDK method: smartlinks.authKit.deleteAccount(clientId, password, confirmText)
|
|
12352
|
+
// Note: This performs a SOFT DELETE (marks as deleted, obfuscates email)
|
|
12353
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12354
|
+
console.log('Required API endpoint: DELETE /api/v1/authkit/:clientId/account/delete');
|
|
12355
|
+
console.log('Data: password and confirmText="DELETE" provided');
|
|
12356
|
+
console.log('Note: Backend should soft delete (mark deleted, obfuscate email, disable account)');
|
|
12357
|
+
// Uncomment when backend is ready:
|
|
12358
|
+
// await smartlinks.authKit.deleteAccount(clientId, deletePassword, deleteConfirmText);
|
|
12359
|
+
// setSuccess('Account deleted successfully');
|
|
12360
|
+
// onAccountDeleted?.();
|
|
12361
|
+
// await auth.logout();
|
|
12362
|
+
}
|
|
12363
|
+
catch (err) {
|
|
12364
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to delete account';
|
|
12365
|
+
setError(errorMessage);
|
|
12366
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12367
|
+
}
|
|
12368
|
+
finally {
|
|
12369
|
+
setLoading(false);
|
|
12370
|
+
}
|
|
12371
|
+
};
|
|
12372
|
+
if (!auth.isAuthenticated) {
|
|
12373
|
+
return (jsx("div", { className: `account-management ${className}`, children: jsx("p", { className: "text-muted-foreground", children: "Please log in to manage your account" }) }));
|
|
12374
|
+
}
|
|
12375
|
+
if (loading && !profile) {
|
|
12376
|
+
return (jsx("div", { className: `account-management ${className}`, children: jsx("p", { className: "text-muted-foreground", children: "Loading..." }) }));
|
|
12377
|
+
}
|
|
12378
|
+
return (jsxs("div", { className: `account-management ${className}`, style: { maxWidth: '600px' }, children: [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: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Display Name" }), jsx("div", { className: "field-value", children: profile?.displayName || 'Not set' })] }), editingSection !== 'profile' && (jsx("button", { type: "button", onClick: () => setEditingSection('profile'), className: "auth-button button-secondary", children: "Change" }))] }), editingSection === 'profile' && (jsxs("form", { onSubmit: handleUpdateProfile, className: "edit-form", children: [jsx("div", { className: "form-group", children: jsx("input", { id: "displayName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), placeholder: "Your display name", className: "auth-input" }) }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showEmailSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Email Address" }), jsxs("div", { className: "field-value", children: [profile?.email || 'Not set', profile?.emailVerified && (jsx("span", { className: "verification-badge verified", children: "Verified" })), profile?.email && !profile?.emailVerified && (jsx("span", { className: "verification-badge unverified", children: "Unverified" }))] })] }), editingSection !== 'email' && (jsx("button", { type: "button", onClick: () => setEditingSection('email'), className: "auth-button button-secondary", children: "Change Email" }))] }), editingSection === 'email' && (jsxs("form", { onSubmit: handleChangeEmail, className: "edit-form", 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 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Email' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPasswordSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Password" }), jsx("div", { className: "field-value", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), editingSection !== 'password' && (jsx("button", { type: "button", onClick: () => setEditingSection('password'), className: "auth-button button-secondary", children: "Change Password" }))] }), editingSection === 'password' && (jsxs("form", { onSubmit: handleChangePassword, className: "edit-form", 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 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPhoneSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Phone Number" }), jsx("div", { className: "field-value", children: profile?.phoneNumber || 'Not set' })] }), editingSection !== 'phone' && (jsx("button", { type: "button", onClick: () => setEditingSection('phone'), className: "auth-button button-secondary", children: "Change Phone" }))] }), editingSection === 'phone' && (jsxs("form", { onSubmit: handleUpdatePhone, className: "edit-form", 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 ? (jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Code' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })) : (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 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] })), 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: () => {
|
|
12379
|
+
setShowDeleteConfirm(false);
|
|
12380
|
+
setDeletePassword('');
|
|
12381
|
+
setDeleteConfirmText('');
|
|
12382
|
+
}, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
|
|
12383
|
+
};
|
|
12384
|
+
|
|
12385
|
+
const SmartlinksClaimUI = ({ apiEndpoint, clientId, clientName, collectionId, productId, proofId, onClaimSuccess, onClaimError, additionalFields = [], theme = 'light', className = '', minimal = false, customization = {}, }) => {
|
|
12386
|
+
const auth = useAuth();
|
|
12387
|
+
const [claimStep, setClaimStep] = useState(auth.isAuthenticated ? 'questions' : 'auth');
|
|
12388
|
+
const [claimData, setClaimData] = useState({});
|
|
12389
|
+
const [error, setError] = useState();
|
|
12390
|
+
const [loading, setLoading] = useState(false);
|
|
12391
|
+
const handleAuthSuccess = (token, user, accountData) => {
|
|
12392
|
+
// Authentication successful
|
|
12393
|
+
auth.login(token, user, accountData);
|
|
12394
|
+
// If no additional questions, proceed directly to claim
|
|
12395
|
+
if (additionalFields.length === 0) {
|
|
12396
|
+
executeClaim(user);
|
|
12335
12397
|
}
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
setError(errorMessage);
|
|
12339
|
-
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12398
|
+
else {
|
|
12399
|
+
setClaimStep('questions');
|
|
12340
12400
|
}
|
|
12341
|
-
|
|
12342
|
-
|
|
12401
|
+
};
|
|
12402
|
+
const handleQuestionSubmit = async (e) => {
|
|
12403
|
+
e.preventDefault();
|
|
12404
|
+
// Validate required fields
|
|
12405
|
+
const missingFields = additionalFields
|
|
12406
|
+
.filter(field => field.required && !claimData[field.name])
|
|
12407
|
+
.map(field => field.label);
|
|
12408
|
+
if (missingFields.length > 0) {
|
|
12409
|
+
setError(`Please fill in: ${missingFields.join(', ')}`);
|
|
12410
|
+
return;
|
|
12411
|
+
}
|
|
12412
|
+
// Execute claim with collected data
|
|
12413
|
+
if (auth.user) {
|
|
12414
|
+
executeClaim(auth.user);
|
|
12343
12415
|
}
|
|
12344
12416
|
};
|
|
12345
|
-
const
|
|
12417
|
+
const executeClaim = async (user) => {
|
|
12418
|
+
setClaimStep('claiming');
|
|
12346
12419
|
setLoading(true);
|
|
12347
12420
|
setError(undefined);
|
|
12348
12421
|
try {
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
}
|
|
12422
|
+
// Create attestation to claim the proof
|
|
12423
|
+
const response = await smartlinks.attestation.create(collectionId, productId, proofId, {
|
|
12424
|
+
public: {
|
|
12425
|
+
claimed: true,
|
|
12426
|
+
claimedAt: new Date().toISOString(),
|
|
12427
|
+
claimedBy: user.uid,
|
|
12428
|
+
...claimData,
|
|
12429
|
+
},
|
|
12430
|
+
private: {},
|
|
12431
|
+
proof: {},
|
|
12432
|
+
});
|
|
12433
|
+
setClaimStep('success');
|
|
12434
|
+
// Call success callback
|
|
12435
|
+
onClaimSuccess({
|
|
12436
|
+
proofId,
|
|
12437
|
+
user,
|
|
12438
|
+
claimData,
|
|
12439
|
+
attestationId: response.id,
|
|
12440
|
+
});
|
|
12360
12441
|
}
|
|
12361
12442
|
catch (err) {
|
|
12362
|
-
|
|
12443
|
+
console.error('Claim error:', err);
|
|
12444
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to claim proof';
|
|
12363
12445
|
setError(errorMessage);
|
|
12364
|
-
|
|
12446
|
+
onClaimError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12447
|
+
setClaimStep(additionalFields.length > 0 ? 'questions' : 'auth');
|
|
12365
12448
|
}
|
|
12366
12449
|
finally {
|
|
12367
12450
|
setLoading(false);
|
|
12368
12451
|
}
|
|
12369
12452
|
};
|
|
12370
|
-
const
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12453
|
+
const handleFieldChange = (fieldName, value) => {
|
|
12454
|
+
setClaimData(prev => ({
|
|
12455
|
+
...prev,
|
|
12456
|
+
[fieldName]: value,
|
|
12457
|
+
}));
|
|
12458
|
+
};
|
|
12459
|
+
// Render authentication step
|
|
12460
|
+
if (claimStep === 'auth') {
|
|
12461
|
+
return (jsx("div", { className: className, children: jsx(SmartlinksAuthUI, { apiEndpoint: apiEndpoint, clientId: clientId, clientName: clientName, onAuthSuccess: handleAuthSuccess, onAuthError: onClaimError, theme: theme, minimal: minimal, customization: customization.authConfig }) }));
|
|
12462
|
+
}
|
|
12463
|
+
// Render additional questions step
|
|
12464
|
+
if (claimStep === 'questions') {
|
|
12465
|
+
return (jsxs("div", { className: `claim-questions ${className}`, children: [jsxs("div", { className: "claim-header mb-6", children: [jsx("h2", { className: "text-2xl font-bold mb-2", children: customization.claimTitle || 'Complete Your Claim' }), customization.claimDescription && (jsx("p", { className: "text-muted-foreground", children: customization.claimDescription }))] }), error && (jsx("div", { className: "claim-error bg-destructive/10 text-destructive px-4 py-3 rounded-md mb-4", children: error })), jsxs("form", { onSubmit: handleQuestionSubmit, className: "claim-form space-y-4", children: [additionalFields.map((field) => (jsxs("div", { className: "claim-field", children: [jsxs("label", { htmlFor: field.name, className: "block text-sm font-medium mb-2", children: [field.label, field.required && jsx("span", { className: "text-destructive ml-1", children: "*" })] }), field.type === 'textarea' ? (jsx("textarea", { id: field.name, name: field.name, placeholder: field.placeholder, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background", rows: 4 })) : field.type === 'select' ? (jsxs("select", { id: field.name, name: field.name, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background", children: [jsx("option", { value: "", children: "Select..." }), field.options?.map((option) => (jsx("option", { value: option, children: option }, option)))] })) : (jsx("input", { id: field.name, name: field.name, type: field.type, placeholder: field.placeholder, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background" }))] }, field.name))), jsx("button", { type: "submit", disabled: loading, className: "claim-submit-button w-full bg-primary text-primary-foreground px-4 py-2 rounded-md font-medium hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed", children: loading ? 'Claiming...' : 'Submit Claim' })] })] }));
|
|
12466
|
+
}
|
|
12467
|
+
// Render claiming step (loading state)
|
|
12468
|
+
if (claimStep === 'claiming') {
|
|
12469
|
+
return (jsxs("div", { className: `claim-loading ${className} flex flex-col items-center justify-center py-12`, children: [jsx("div", { className: "claim-spinner w-12 h-12 border-4 border-primary border-t-transparent rounded-full animate-spin mb-4" }), jsx("p", { className: "text-muted-foreground", children: "Claiming your product..." })] }));
|
|
12470
|
+
}
|
|
12471
|
+
// Render success step
|
|
12472
|
+
if (claimStep === 'success') {
|
|
12473
|
+
return (jsxs("div", { className: `claim-success ${className} text-center py-12`, children: [jsx("div", { className: "claim-success-icon w-16 h-16 bg-green-500 text-white rounded-full flex items-center justify-center text-3xl font-bold mx-auto mb-4", children: "\u2713" }), jsx("h2", { className: "text-2xl font-bold mb-2", children: "Claim Successful!" }), jsx("p", { className: "text-muted-foreground", children: customization.successMessage || 'Your product has been successfully claimed and registered to your account.' })] }));
|
|
12474
|
+
}
|
|
12475
|
+
return null;
|
|
12476
|
+
};
|
|
12477
|
+
|
|
12478
|
+
const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
|
|
12479
|
+
const { isAuthenticated, isLoading } = useAuth();
|
|
12480
|
+
// Show loading state
|
|
12481
|
+
if (isLoading) {
|
|
12482
|
+
return jsx("div", { children: "Loading..." });
|
|
12483
|
+
}
|
|
12484
|
+
// If not authenticated, redirect or show fallback
|
|
12485
|
+
if (!isAuthenticated) {
|
|
12486
|
+
if (redirectTo) {
|
|
12487
|
+
window.location.href = redirectTo;
|
|
12488
|
+
return null;
|
|
12377
12489
|
}
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12490
|
+
return fallback ? jsx(Fragment, { children: fallback }) : jsx("div", { children: "Access denied. Please log in." });
|
|
12491
|
+
}
|
|
12492
|
+
// Render protected content
|
|
12493
|
+
return jsx(Fragment, { children: children });
|
|
12494
|
+
};
|
|
12495
|
+
|
|
12496
|
+
const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], providerOrder, emailDisplayMode = 'form', theme = 'light', className, minimal = false, }) => {
|
|
12497
|
+
const showEmail = enabledProviders.includes('email');
|
|
12498
|
+
const showGoogle = enabledProviders.includes('google');
|
|
12499
|
+
const showPhone = enabledProviders.includes('phone');
|
|
12500
|
+
const showMagicLink = enabledProviders.includes('magic-link');
|
|
12501
|
+
// Determine ordered providers (excluding email if in button mode)
|
|
12502
|
+
const orderedProviders = providerOrder && providerOrder.length > 0
|
|
12503
|
+
? providerOrder.filter(p => enabledProviders.includes(p) && p !== 'email')
|
|
12504
|
+
: enabledProviders.filter(p => p !== 'email');
|
|
12505
|
+
const hasOtherProviders = showGoogle || showPhone || showMagicLink;
|
|
12506
|
+
// Render provider button helper
|
|
12507
|
+
const renderProviderButton = (provider) => {
|
|
12508
|
+
if (provider === 'google' && showGoogle) {
|
|
12509
|
+
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"));
|
|
12382
12510
|
}
|
|
12383
|
-
|
|
12384
|
-
|
|
12511
|
+
if (provider === 'phone' && showPhone) {
|
|
12512
|
+
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"));
|
|
12513
|
+
}
|
|
12514
|
+
if (provider === 'magic-link' && showMagicLink) {
|
|
12515
|
+
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"));
|
|
12385
12516
|
}
|
|
12517
|
+
if (provider === 'email' && showEmail && emailDisplayMode === 'button') {
|
|
12518
|
+
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"));
|
|
12519
|
+
}
|
|
12520
|
+
return null;
|
|
12386
12521
|
};
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
return (jsx(AuthContainer, { theme: theme, className: className, config: config, minimal: minimal || config?.branding?.minimal || false, children: authSuccess ? (jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [jsx("div", { style: {
|
|
12391
|
-
color: 'var(--auth-primary-color, #4F46E5)',
|
|
12392
|
-
fontSize: '3rem',
|
|
12393
|
-
marginBottom: '1rem'
|
|
12394
|
-
}, children: "\u2713" }), jsx("h2", { style: {
|
|
12395
|
-
marginBottom: '0.5rem',
|
|
12396
|
-
fontSize: '1.5rem',
|
|
12397
|
-
fontWeight: 600
|
|
12398
|
-
}, children: successMessage?.includes('verified') ? 'Email Verified!' :
|
|
12399
|
-
successMessage?.includes('Magic link') ? 'Check Your Email!' :
|
|
12400
|
-
mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsx("p", { style: {
|
|
12401
|
-
color: '#6B7280',
|
|
12402
|
-
fontSize: '0.875rem'
|
|
12403
|
-
}, 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: () => {
|
|
12404
|
-
setMode('login');
|
|
12405
|
-
setResetSuccess(false);
|
|
12406
|
-
setResetToken(undefined); // Clear token when going back
|
|
12407
|
-
}, loading: loading, error: error, success: resetSuccess, token: resetToken })) : (mode === 'login' || mode === 'register') ? (jsx(Fragment, { children: showResendVerification ? (jsxs("div", { style: { marginTop: '1rem', padding: '1.5rem', backgroundColor: 'rgba(79, 70, 229, 0.05)', borderRadius: '0.5rem' }, children: [jsx("h3", { style: { marginBottom: '0.75rem', fontSize: '1rem', fontWeight: 600, color: 'var(--auth-text-color, #374151)' }, children: "Verification Link Expired" }), jsx("p", { style: { marginBottom: '1rem', fontSize: '0.875rem', color: 'var(--auth-text-color, #6B7280)', lineHeight: '1.5' }, children: "Your verification link has expired or is no longer valid. Please enter your email address below and we'll send you a new verification link." }), jsx("input", { type: "email", value: resendEmail || '', onChange: (e) => setResendEmail(e.target.value), placeholder: "your@email.com", style: {
|
|
12408
|
-
width: '100%',
|
|
12409
|
-
padding: '0.625rem',
|
|
12410
|
-
marginBottom: '1rem',
|
|
12411
|
-
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12412
|
-
borderRadius: '0.375rem',
|
|
12413
|
-
fontSize: '0.875rem',
|
|
12414
|
-
boxSizing: 'border-box'
|
|
12415
|
-
} }), jsxs("div", { style: { display: 'flex', gap: '0.75rem' }, children: [jsx("button", { onClick: handleResendVerification, disabled: loading || !resendEmail, style: {
|
|
12416
|
-
flex: 1,
|
|
12417
|
-
padding: '0.625rem 1rem',
|
|
12418
|
-
backgroundColor: 'var(--auth-primary-color, #4F46E5)',
|
|
12419
|
-
color: 'white',
|
|
12420
|
-
border: 'none',
|
|
12421
|
-
borderRadius: '0.375rem',
|
|
12422
|
-
cursor: (loading || !resendEmail) ? 'not-allowed' : 'pointer',
|
|
12423
|
-
fontSize: '0.875rem',
|
|
12424
|
-
fontWeight: 500,
|
|
12425
|
-
opacity: (loading || !resendEmail) ? 0.6 : 1
|
|
12426
|
-
}, children: loading ? 'Sending...' : 'Send New Verification Link' }), jsx("button", { onClick: () => {
|
|
12427
|
-
setShowResendVerification(false);
|
|
12428
|
-
setResendEmail('');
|
|
12429
|
-
setError(undefined);
|
|
12430
|
-
}, disabled: loading, style: {
|
|
12431
|
-
padding: '0.625rem 1rem',
|
|
12432
|
-
backgroundColor: 'transparent',
|
|
12433
|
-
color: 'var(--auth-text-color, #6B7280)',
|
|
12434
|
-
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12435
|
-
borderRadius: '0.375rem',
|
|
12436
|
-
cursor: loading ? 'not-allowed' : 'pointer',
|
|
12437
|
-
fontSize: '0.875rem',
|
|
12438
|
-
fontWeight: 500,
|
|
12439
|
-
opacity: loading ? 0.6 : 1
|
|
12440
|
-
}, children: "Cancel" })] })] })) : showRequestNewReset ? (jsxs("div", { style: { marginTop: '1rem', padding: '1.5rem', backgroundColor: 'rgba(239, 68, 68, 0.05)', borderRadius: '0.5rem' }, children: [jsx("h3", { style: { marginBottom: '0.75rem', fontSize: '1rem', fontWeight: 600, color: 'var(--auth-text-color, #374151)' }, children: "Password Reset Link Expired" }), jsx("p", { style: { marginBottom: '1rem', fontSize: '0.875rem', color: 'var(--auth-text-color, #6B7280)', lineHeight: '1.5' }, children: "Your password reset link has expired or is no longer valid. Please enter your email address below and we'll send you a new password reset link." }), jsx("input", { type: "email", value: resetRequestEmail || '', onChange: (e) => setResetRequestEmail(e.target.value), placeholder: "your@email.com", style: {
|
|
12441
|
-
width: '100%',
|
|
12442
|
-
padding: '0.625rem',
|
|
12443
|
-
marginBottom: '1rem',
|
|
12444
|
-
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12445
|
-
borderRadius: '0.375rem',
|
|
12446
|
-
fontSize: '0.875rem',
|
|
12447
|
-
boxSizing: 'border-box'
|
|
12448
|
-
} }), jsxs("div", { style: { display: 'flex', gap: '0.75rem' }, children: [jsx("button", { onClick: handleRequestNewReset, disabled: loading || !resetRequestEmail, style: {
|
|
12449
|
-
flex: 1,
|
|
12450
|
-
padding: '0.625rem 1rem',
|
|
12451
|
-
backgroundColor: '#EF4444',
|
|
12452
|
-
color: 'white',
|
|
12453
|
-
border: 'none',
|
|
12454
|
-
borderRadius: '0.375rem',
|
|
12455
|
-
cursor: (loading || !resetRequestEmail) ? 'not-allowed' : 'pointer',
|
|
12456
|
-
fontSize: '0.875rem',
|
|
12457
|
-
fontWeight: 500,
|
|
12458
|
-
opacity: (loading || !resetRequestEmail) ? 0.6 : 1
|
|
12459
|
-
}, children: loading ? 'Sending...' : 'Send New Reset Link' }), jsx("button", { onClick: () => {
|
|
12460
|
-
setShowRequestNewReset(false);
|
|
12461
|
-
setResetRequestEmail('');
|
|
12462
|
-
setError(undefined);
|
|
12463
|
-
}, disabled: loading, style: {
|
|
12464
|
-
padding: '0.625rem 1rem',
|
|
12465
|
-
backgroundColor: 'transparent',
|
|
12466
|
-
color: 'var(--auth-text-color, #6B7280)',
|
|
12467
|
-
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12468
|
-
borderRadius: '0.375rem',
|
|
12469
|
-
cursor: loading ? 'not-allowed' : 'pointer',
|
|
12470
|
-
fontSize: '0.875rem',
|
|
12471
|
-
fontWeight: 500,
|
|
12472
|
-
opacity: loading ? 0.6 : 1
|
|
12473
|
-
}, children: "Cancel" })] })] })) : (jsx(Fragment, { children: (() => {
|
|
12474
|
-
const emailDisplayMode = config?.emailDisplayMode || 'form';
|
|
12475
|
-
const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
|
|
12476
|
-
const actualProviders = config?.enabledProviders || enabledProviders;
|
|
12477
|
-
// Button mode: show provider selection first, then email form if email is selected
|
|
12478
|
-
if (emailDisplayMode === 'button' && !showEmailForm) {
|
|
12479
|
-
return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
12480
|
-
}
|
|
12481
|
-
// Form mode or email button was clicked: show email form with other providers
|
|
12482
|
-
return (jsxs(Fragment, { children: [emailDisplayMode === 'button' && showEmailForm && (jsx("button", { onClick: () => setShowEmailForm(false), style: {
|
|
12483
|
-
marginBottom: '1rem',
|
|
12484
|
-
padding: '0.5rem',
|
|
12485
|
-
background: 'none',
|
|
12486
|
-
border: 'none',
|
|
12487
|
-
color: 'var(--auth-text-color, #6B7280)',
|
|
12488
|
-
cursor: 'pointer',
|
|
12489
|
-
fontSize: '0.875rem',
|
|
12490
|
-
display: 'flex',
|
|
12491
|
-
alignItems: 'center',
|
|
12492
|
-
gap: '0.25rem'
|
|
12493
|
-
}, children: "\u2190 Back to options" })), jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
|
|
12494
|
-
setMode(mode === 'login' ? 'register' : 'login');
|
|
12495
|
-
setShowResendVerification(false);
|
|
12496
|
-
setShowRequestNewReset(false);
|
|
12497
|
-
setError(undefined);
|
|
12498
|
-
}, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
12499
|
-
})() })) })) : null }));
|
|
12522
|
+
return (jsx(AuthContainer, { theme: theme, className: className, config: customization, minimal: minimal, children: emailDisplayMode === 'button' ? (jsx("div", { className: "auth-provider-buttons", children: orderedProviders.concat(showEmail ? ['email'] : []).map(provider => renderProviderButton(provider)) })) : (
|
|
12523
|
+
/* Form mode: show email form first, then other providers */
|
|
12524
|
+
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)) })] }))] })) }));
|
|
12500
12525
|
};
|
|
12501
12526
|
|
|
12502
12527
|
export { AccountManagement, AuthProvider, AuthUIPreview, SmartlinksAuthUI as FirebaseAuthUI, ProtectedRoute, SmartlinksAuthUI, SmartlinksClaimUI, tokenStorage, useAuth };
|