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