@proveanything/smartlinks-auth-ui 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -10702,14 +10702,18 @@ class AuthAPI {
10702
10702
  });
10703
10703
  }
10704
10704
  async fetchConfig() {
10705
+ console.log('[AuthAPI] 📋 fetchConfig called with clientId:', this.clientId);
10705
10706
  this.log.log('fetchConfig called with clientId:', this.clientId);
10706
10707
  try {
10708
+ console.log('[AuthAPI] 🌐 Calling smartlinks.authKit.load() - this uses current SDK config (including proxyMode)');
10707
10709
  this.log.log('Calling smartlinks.authKit.load...');
10708
10710
  const result = await smartlinks.authKit.load(this.clientId);
10711
+ console.log('[AuthAPI] ✅ smartlinks.authKit.load returned:', result);
10709
10712
  this.log.log('smartlinks.authKit.load returned:', result);
10710
10713
  return result;
10711
10714
  }
10712
10715
  catch (error) {
10716
+ console.log('[AuthAPI] ❌ Failed to fetch UI config:', error);
10713
10717
  this.log.warn('Failed to fetch UI config, using defaults:', error);
10714
10718
  return {
10715
10719
  branding: {
@@ -11128,6 +11132,7 @@ const TOKEN_KEY = 'token';
11128
11132
  const USER_KEY = 'user';
11129
11133
  const ACCOUNT_DATA_KEY = 'account_data';
11130
11134
  const ACCOUNT_INFO_KEY = 'account_info';
11135
+ const CONTACT_ID_KEY = 'contact_id';
11131
11136
  const ACCOUNT_INFO_TTL = 5 * 60 * 1000; // 5 minutes default
11132
11137
  /**
11133
11138
  * Token Storage Layer
@@ -11178,6 +11183,7 @@ const tokenStorage = {
11178
11183
  await this.clearUser();
11179
11184
  await this.clearAccountData();
11180
11185
  await this.clearAccountInfo();
11186
+ await this.clearContactId();
11181
11187
  },
11182
11188
  async saveAccountData(data) {
11183
11189
  const storage = await getStorage();
@@ -11215,20 +11221,69 @@ const tokenStorage = {
11215
11221
  const storage = await getStorage();
11216
11222
  await storage.removeItem(ACCOUNT_INFO_KEY);
11217
11223
  },
11224
+ async saveContactId(contactId) {
11225
+ const storage = await getStorage();
11226
+ await storage.setItem(CONTACT_ID_KEY, contactId);
11227
+ },
11228
+ async getContactId() {
11229
+ const storage = await getStorage();
11230
+ return await storage.getItem(CONTACT_ID_KEY);
11231
+ },
11232
+ async clearContactId() {
11233
+ const storage = await getStorage();
11234
+ await storage.removeItem(CONTACT_ID_KEY);
11235
+ },
11218
11236
  };
11219
11237
 
11220
11238
  const AuthContext = createContext(undefined);
11221
- const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
11239
+ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false,
11240
+ // Contact & Interaction features
11241
+ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, interactionConfig, }) => {
11222
11242
  const [user, setUser] = useState(null);
11223
11243
  const [token, setToken] = useState(null);
11224
11244
  const [accountData, setAccountData] = useState(null);
11225
11245
  const [accountInfo, setAccountInfo] = useState(null);
11226
11246
  const [isLoading, setIsLoading] = useState(true);
11247
+ const [isVerified, setIsVerified] = useState(false);
11248
+ const [isOnline, setIsOnline] = useState(typeof navigator !== 'undefined' ? navigator.onLine : true);
11249
+ // Contact state
11250
+ const [contact, setContact] = useState(null);
11251
+ const [contactId, setContactId] = useState(null);
11227
11252
  const callbacksRef = useRef(new Set());
11228
- // Initialization guard to prevent concurrent runs (NOT persisted across remounts)
11229
11253
  const initializingRef = useRef(false);
11254
+ const pendingVerificationRef = useRef(false);
11255
+ // Default to enabled if collectionId is provided
11256
+ const shouldSyncContacts = enableContactSync ?? !!collectionId;
11257
+ const shouldTrackInteractions = enableInteractionTracking ?? !!collectionId;
11258
+ // Helper to detect if an error is a network error vs auth error
11259
+ const isNetworkError = useCallback((error) => {
11260
+ if (!error)
11261
+ return false;
11262
+ const errorMessage = error?.message?.toLowerCase() || '';
11263
+ const errorName = error?.name?.toLowerCase() || '';
11264
+ if (errorName === 'typeerror' && errorMessage.includes('fetch'))
11265
+ return true;
11266
+ if (errorMessage.includes('network') || errorMessage.includes('offline'))
11267
+ return true;
11268
+ if (errorMessage.includes('failed to fetch'))
11269
+ return true;
11270
+ if (errorMessage.includes('net::err_'))
11271
+ return true;
11272
+ if (errorMessage.includes('timeout'))
11273
+ return true;
11274
+ if (errorMessage.includes('dns'))
11275
+ return true;
11276
+ if (error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT')
11277
+ return true;
11278
+ const status = error?.status || error?.response?.status;
11279
+ if (status === 401 || status === 403)
11280
+ return false;
11281
+ if (typeof navigator !== 'undefined' && !navigator.onLine)
11282
+ return true;
11283
+ return false;
11284
+ }, []);
11230
11285
  // Notify all subscribers of auth state changes
11231
- const notifyAuthStateChange = useCallback((type, currentUser, currentToken, currentAccountData, currentAccountInfo) => {
11286
+ const notifyAuthStateChange = useCallback((type, currentUser, currentToken, currentAccountData, currentAccountInfo, verified, currentContact, currentContactId) => {
11232
11287
  callbacksRef.current.forEach(callback => {
11233
11288
  try {
11234
11289
  callback({
@@ -11236,7 +11291,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11236
11291
  user: currentUser,
11237
11292
  token: currentToken,
11238
11293
  accountData: currentAccountData,
11239
- accountInfo: currentAccountInfo
11294
+ accountInfo: currentAccountInfo,
11295
+ isVerified: verified,
11296
+ contact: currentContact,
11297
+ contactId: currentContactId,
11240
11298
  });
11241
11299
  }
11242
11300
  catch (error) {
@@ -11244,9 +11302,128 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11244
11302
  }
11245
11303
  });
11246
11304
  }, []);
11247
- // Initialize auth state - different behavior for proxy mode vs standalone mode
11305
+ // Sync contact to Smartlinks (non-blocking)
11306
+ const syncContact = useCallback(async (authUser, customFields) => {
11307
+ if (!collectionId || !shouldSyncContacts) {
11308
+ console.log('[AuthContext] Contact sync skipped: no collectionId or disabled');
11309
+ return null;
11310
+ }
11311
+ try {
11312
+ console.log('[AuthContext] Syncing contact for user:', authUser.uid);
11313
+ const result = await smartlinks.contact.publicUpsert(collectionId, {
11314
+ userId: authUser.uid,
11315
+ email: authUser.email,
11316
+ displayName: authUser.displayName,
11317
+ phone: authUser.phoneNumber,
11318
+ customFields: customFields || {},
11319
+ source: 'authkit',
11320
+ });
11321
+ console.log('[AuthContext] Contact synced:', result.contactId);
11322
+ // Store contact ID locally
11323
+ if (!proxyMode) {
11324
+ await tokenStorage.saveContactId(result.contactId);
11325
+ }
11326
+ setContactId(result.contactId);
11327
+ // Fetch full contact to get customFields
11328
+ try {
11329
+ const fullContact = await smartlinks.contact.lookup(collectionId, {
11330
+ email: authUser.email
11331
+ });
11332
+ setContact(fullContact);
11333
+ notifyAuthStateChange('CONTACT_SYNCED', authUser, token, accountData, accountInfo, isVerified, fullContact, result.contactId);
11334
+ }
11335
+ catch (lookupErr) {
11336
+ console.warn('[AuthContext] Failed to lookup full contact:', lookupErr);
11337
+ }
11338
+ return result.contactId;
11339
+ }
11340
+ catch (err) {
11341
+ console.warn('[AuthContext] Contact sync failed (non-blocking):', err);
11342
+ return null;
11343
+ }
11344
+ }, [collectionId, shouldSyncContacts, proxyMode, token, accountData, accountInfo, isVerified, notifyAuthStateChange]);
11345
+ // Track interaction event (non-blocking)
11346
+ const trackInteraction = useCallback(async (eventType, userId, currentContactId, metadata) => {
11347
+ if (!collectionId || !shouldTrackInteractions) {
11348
+ console.log('[AuthContext] Interaction tracking skipped: no collectionId or disabled');
11349
+ return;
11350
+ }
11351
+ const interactionIdMap = {
11352
+ login: interactionConfig?.login || 'authkit.login',
11353
+ logout: interactionConfig?.logout || 'authkit.logout',
11354
+ signup: interactionConfig?.signup || 'authkit.signup',
11355
+ session_restore: interactionConfig?.sessionRestore,
11356
+ };
11357
+ const interactionId = interactionIdMap[eventType];
11358
+ if (!interactionId) {
11359
+ console.log(`[AuthContext] No interaction ID for ${eventType}, skipping`);
11360
+ return;
11361
+ }
11362
+ try {
11363
+ console.log(`[AuthContext] Tracking interaction: ${interactionId}`);
11364
+ await smartlinks.interactions.submitPublicEvent(collectionId, {
11365
+ collectionId,
11366
+ interactionId,
11367
+ userId,
11368
+ contactId: currentContactId || undefined,
11369
+ appId: interactionAppId,
11370
+ eventType,
11371
+ outcome: 'completed',
11372
+ metadata: {
11373
+ ...metadata,
11374
+ timestamp: new Date().toISOString(),
11375
+ source: 'authkit',
11376
+ },
11377
+ });
11378
+ console.log(`[AuthContext] Tracked interaction: ${interactionId}`);
11379
+ notifyAuthStateChange('INTERACTION_TRACKED', user, token, accountData, accountInfo, isVerified, contact, contactId);
11380
+ }
11381
+ catch (err) {
11382
+ console.warn('[AuthContext] Interaction tracking failed (non-blocking):', err);
11383
+ }
11384
+ }, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
11385
+ // Get contact (with optional refresh)
11386
+ const getContact = useCallback(async (forceRefresh = false) => {
11387
+ if (!collectionId) {
11388
+ console.log('[AuthContext] getContact: no collectionId');
11389
+ return null;
11390
+ }
11391
+ // Need either email or userId to lookup contact
11392
+ if (!user?.email && !user?.uid) {
11393
+ console.log('[AuthContext] getContact: no user email or uid');
11394
+ return null;
11395
+ }
11396
+ if (contact && !forceRefresh) {
11397
+ return contact;
11398
+ }
11399
+ try {
11400
+ // Prefer email lookup, fallback to userId for phone-only users
11401
+ const lookupParams = user.email
11402
+ ? { email: user.email }
11403
+ : { userId: user.uid };
11404
+ console.log('[AuthContext] Fetching contact with:', lookupParams);
11405
+ const result = await smartlinks.contact.lookup(collectionId, lookupParams);
11406
+ setContact(result);
11407
+ setContactId(result.contactId);
11408
+ return result;
11409
+ }
11410
+ catch (err) {
11411
+ console.warn('[AuthContext] Failed to get contact:', err);
11412
+ return null;
11413
+ }
11414
+ }, [collectionId, user, contact]);
11415
+ // Update contact custom fields
11416
+ const updateContactCustomFields = useCallback(async (customFields) => {
11417
+ if (!collectionId || !contactId) {
11418
+ throw new Error('No contact to update. Ensure collectionId is provided and user is synced.');
11419
+ }
11420
+ console.log('[AuthContext] Updating contact custom fields:', contactId);
11421
+ const updated = await smartlinks.contact.update(collectionId, contactId, { customFields });
11422
+ setContact(updated);
11423
+ return updated;
11424
+ }, [collectionId, contactId]);
11425
+ // Initialize auth state
11248
11426
  useEffect(() => {
11249
- // Prevent concurrent initialization only
11250
11427
  if (initializingRef.current) {
11251
11428
  console.log('[AuthContext] Skipping initialization - already in progress');
11252
11429
  return;
@@ -11260,12 +11437,9 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11260
11437
  console.log('[AuthContext] Proxy mode: checking for existing session via auth.getAccount()');
11261
11438
  try {
11262
11439
  const accountResponse = await smartlinks.auth.getAccount();
11263
- // auth.getAccount() always returns a response object, but uid will be
11264
- // empty/undefined if no user is logged in
11265
11440
  const accountAny = accountResponse;
11266
11441
  const hasValidSession = accountAny?.uid && accountAny.uid.length > 0;
11267
11442
  if (hasValidSession && isMounted) {
11268
- // User is logged in with valid account
11269
11443
  const userFromAccount = {
11270
11444
  uid: accountAny.uid,
11271
11445
  email: accountAny?.email,
@@ -11275,8 +11449,11 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11275
11449
  setUser(userFromAccount);
11276
11450
  setAccountData(accountResponse);
11277
11451
  setAccountInfo(accountResponse);
11452
+ setIsVerified(true);
11278
11453
  console.log('[AuthContext] Proxy mode: initialized from parent account, uid:', accountAny.uid);
11279
- notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse);
11454
+ notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse, true);
11455
+ // Sync contact in background (proxy mode)
11456
+ syncContact(userFromAccount, accountResponse);
11280
11457
  }
11281
11458
  else if (isMounted) {
11282
11459
  console.log('[AuthContext] Proxy mode: no valid session (no uid), awaiting login');
@@ -11291,29 +11468,56 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11291
11468
  }
11292
11469
  return;
11293
11470
  }
11294
- // STANDALONE MODE: Load from persistent storage
11471
+ // STANDALONE MODE: Optimistic restoration with background verification
11295
11472
  const storedToken = await tokenStorage.getToken();
11296
11473
  const storedUser = await tokenStorage.getUser();
11297
11474
  const storedAccountData = await tokenStorage.getAccountData();
11475
+ const storedContactId = await tokenStorage.getContactId();
11298
11476
  if (storedToken && storedUser) {
11299
- // Verify token FIRST before setting state
11477
+ if (isMounted) {
11478
+ setToken(storedToken.token);
11479
+ setUser(storedUser);
11480
+ setAccountData(storedAccountData);
11481
+ if (storedContactId) {
11482
+ setContactId(storedContactId);
11483
+ }
11484
+ console.log('[AuthContext] Session restored optimistically (pending verification)');
11485
+ notifyAuthStateChange('SESSION_RESTORED_OFFLINE', storedUser, storedToken.token, storedAccountData || null, null, false, null, storedContactId);
11486
+ }
11487
+ // BACKGROUND: Verify token
11300
11488
  try {
11301
- console.log('[AuthContext] Verifying stored token...');
11489
+ console.log('[AuthContext] Verifying stored token in background...');
11302
11490
  await smartlinks.auth.verifyToken(storedToken.token);
11303
- // Only set state if verification succeeded and component still mounted
11304
11491
  if (isMounted) {
11305
- setToken(storedToken.token);
11306
- setUser(storedUser);
11307
- setAccountData(storedAccountData);
11308
- console.log('[AuthContext] Session restored successfully');
11309
- // Notify subscribers that session was restored (critical for app to know we're logged in)
11310
- notifyAuthStateChange('LOGIN', storedUser, storedToken.token, storedAccountData || null);
11492
+ setIsVerified(true);
11493
+ pendingVerificationRef.current = false;
11494
+ console.log('[AuthContext] Session verified successfully');
11495
+ notifyAuthStateChange('SESSION_VERIFIED', storedUser, storedToken.token, storedAccountData || null, null, true, null, storedContactId);
11496
+ // Track session restore interaction (optional)
11497
+ if (interactionConfig?.sessionRestore) {
11498
+ trackInteraction('session_restore', storedUser.uid, storedContactId);
11499
+ }
11311
11500
  }
11312
11501
  }
11313
11502
  catch (err) {
11314
- console.warn('[AuthContext] Token verification failed, clearing stored credentials:', err);
11315
- await tokenStorage.clearAll();
11316
- // Don't set user state - leave as logged out
11503
+ if (isNetworkError(err)) {
11504
+ console.warn('[AuthContext] Network error during verification, will retry on reconnect:', err);
11505
+ pendingVerificationRef.current = true;
11506
+ }
11507
+ else {
11508
+ console.warn('[AuthContext] Token verification failed (auth error), clearing credentials:', err);
11509
+ if (isMounted) {
11510
+ setToken(null);
11511
+ setUser(null);
11512
+ setAccountData(null);
11513
+ setContactId(null);
11514
+ setContact(null);
11515
+ setIsVerified(false);
11516
+ pendingVerificationRef.current = false;
11517
+ }
11518
+ await tokenStorage.clearAll();
11519
+ notifyAuthStateChange('LOGOUT', null, null, null, null, false);
11520
+ }
11317
11521
  }
11318
11522
  }
11319
11523
  // Load cached account info if available
@@ -11335,18 +11539,16 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11335
11539
  }
11336
11540
  };
11337
11541
  initializeAuth();
11338
- // Cleanup for hot reload
11339
11542
  return () => {
11340
11543
  isMounted = false;
11341
11544
  };
11342
- }, [proxyMode, notifyAuthStateChange]);
11545
+ }, [proxyMode, notifyAuthStateChange, isNetworkError, syncContact, trackInteraction, interactionConfig?.sessionRestore]);
11343
11546
  // Listen for parent auth state changes (proxy mode only)
11344
11547
  useEffect(() => {
11345
11548
  if (!proxyMode)
11346
11549
  return;
11347
11550
  console.log('[AuthContext] Proxy mode: setting up parent message listener');
11348
11551
  const handleParentMessage = (event) => {
11349
- // Handle auth state pushed from parent
11350
11552
  if (event.data?.type === 'smartlinks:authkit:state') {
11351
11553
  const { user: parentUser, accountData: parentAccountData, authenticated } = event.data.payload || {};
11352
11554
  console.log('[AuthContext] Proxy mode: received state from parent:', { authenticated });
@@ -11360,15 +11562,20 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11360
11562
  setUser(userObj);
11361
11563
  setAccountData(parentAccountData || null);
11362
11564
  setAccountInfo(parentAccountData || null);
11363
- notifyAuthStateChange('CROSS_TAB_SYNC', userObj, null, parentAccountData || null, parentAccountData || null);
11565
+ setIsVerified(true);
11566
+ notifyAuthStateChange('CROSS_TAB_SYNC', userObj, null, parentAccountData || null, parentAccountData || null, true);
11567
+ // Sync contact on cross-tab state
11568
+ syncContact(userObj, parentAccountData);
11364
11569
  }
11365
11570
  else {
11366
- // Parent indicates no session / logged out
11367
11571
  setUser(null);
11368
11572
  setToken(null);
11369
11573
  setAccountData(null);
11370
11574
  setAccountInfo(null);
11371
- notifyAuthStateChange('LOGOUT', null, null, null, null);
11575
+ setContact(null);
11576
+ setContactId(null);
11577
+ setIsVerified(false);
11578
+ notifyAuthStateChange('LOGOUT', null, null, null, null, false);
11372
11579
  }
11373
11580
  }
11374
11581
  };
@@ -11377,54 +11584,60 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11377
11584
  console.log('[AuthContext] Proxy mode: cleaning up parent message listener');
11378
11585
  window.removeEventListener('message', handleParentMessage);
11379
11586
  };
11380
- }, [proxyMode, notifyAuthStateChange]);
11587
+ }, [proxyMode, notifyAuthStateChange, syncContact]);
11381
11588
  // Cross-tab synchronization - standalone mode only
11382
11589
  useEffect(() => {
11383
11590
  if (proxyMode)
11384
- return; // Skip cross-tab sync in proxy mode
11591
+ return;
11385
11592
  console.log('[AuthContext] Setting up cross-tab synchronization');
11386
11593
  const unsubscribe = onStorageChange(async (event) => {
11387
11594
  console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
11388
11595
  try {
11389
11596
  if (event.type === 'clear') {
11390
- // Another tab cleared all storage (logout)
11391
11597
  console.log('[AuthContext] Detected logout in another tab');
11392
11598
  setToken(null);
11393
11599
  setUser(null);
11394
11600
  setAccountData(null);
11395
11601
  setAccountInfo(null);
11602
+ setContact(null);
11603
+ setContactId(null);
11604
+ setIsVerified(false);
11396
11605
  smartlinks.auth.logout();
11397
- notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
11606
+ notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null, null, false);
11398
11607
  }
11399
11608
  else if (event.type === 'remove' && (event.key === 'token' || event.key === 'user')) {
11400
- // Another tab removed token or user (logout)
11401
11609
  console.log('[AuthContext] Detected token/user removal in another tab');
11402
11610
  setToken(null);
11403
11611
  setUser(null);
11404
11612
  setAccountData(null);
11405
11613
  setAccountInfo(null);
11614
+ setContact(null);
11615
+ setContactId(null);
11616
+ setIsVerified(false);
11406
11617
  smartlinks.auth.logout();
11407
- notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
11618
+ notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null, null, false);
11408
11619
  }
11409
11620
  else if (event.type === 'set' && event.key === 'token') {
11410
- // Another tab set a new token (login)
11411
11621
  console.log('[AuthContext] Detected login in another tab');
11412
11622
  const storedToken = await tokenStorage.getToken();
11413
11623
  const storedUser = await tokenStorage.getUser();
11414
11624
  const storedAccountData = await tokenStorage.getAccountData();
11625
+ const storedContactId = await tokenStorage.getContactId();
11415
11626
  if (storedToken && storedUser) {
11416
11627
  setToken(storedToken.token);
11417
11628
  setUser(storedUser);
11418
11629
  setAccountData(storedAccountData);
11419
- // Set bearer token in global Smartlinks SDK
11630
+ if (storedContactId) {
11631
+ setContactId(storedContactId);
11632
+ }
11633
+ setIsVerified(true);
11420
11634
  smartlinks.auth.verifyToken(storedToken.token).catch(err => {
11421
11635
  console.warn('[AuthContext] Failed to restore bearer token from cross-tab sync:', err);
11422
11636
  });
11423
- notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData);
11637
+ notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData, null, true, null, storedContactId);
11424
11638
  }
11425
11639
  }
11426
11640
  else if (event.type === 'set' && event.key === 'account_info') {
11427
- // Another tab fetched fresh account info
11428
11641
  const cached = await tokenStorage.getAccountInfo();
11429
11642
  if (cached && !cached.isStale) {
11430
11643
  setAccountInfo(cached.data);
@@ -11441,7 +11654,7 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11441
11654
  unsubscribe();
11442
11655
  };
11443
11656
  }, [proxyMode, notifyAuthStateChange]);
11444
- const login = useCallback(async (authToken, authUser, authAccountData) => {
11657
+ const login = useCallback(async (authToken, authUser, authAccountData, isNewUser) => {
11445
11658
  try {
11446
11659
  // Only persist to storage in standalone mode
11447
11660
  if (!proxyMode) {
@@ -11450,7 +11663,6 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11450
11663
  if (authAccountData) {
11451
11664
  await tokenStorage.saveAccountData(authAccountData);
11452
11665
  }
11453
- // Set bearer token in global Smartlinks SDK via auth.verifyToken
11454
11666
  smartlinks.auth.verifyToken(authToken).catch(err => {
11455
11667
  console.warn('Failed to set bearer token on login:', err);
11456
11668
  });
@@ -11459,8 +11671,9 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11459
11671
  setToken(authToken);
11460
11672
  setUser(authUser);
11461
11673
  setAccountData(authAccountData || null);
11674
+ setIsVerified(true);
11675
+ pendingVerificationRef.current = false;
11462
11676
  // Cross-iframe auth state synchronization
11463
- // Always notify parent frame of login (both modes, but especially important in proxy mode)
11464
11677
  if (iframe.isIframe()) {
11465
11678
  console.log('[AuthContext] Notifying parent of login via postMessage');
11466
11679
  iframe.sendParentCustom('smartlinks:authkit:login', {
@@ -11469,7 +11682,13 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11469
11682
  accountData: authAccountData || null
11470
11683
  });
11471
11684
  }
11472
- notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null);
11685
+ notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null, null, true);
11686
+ // Sync contact (non-blocking)
11687
+ const newContactId = await syncContact(authUser, authAccountData);
11688
+ // Track interaction (non-blocking)
11689
+ trackInteraction(isNewUser ? 'signup' : 'login', authUser.uid, newContactId, {
11690
+ provider: authUser.email ? 'email' : 'phone',
11691
+ });
11473
11692
  // Optionally preload account info on login (standalone mode only)
11474
11693
  if (!proxyMode && preloadAccountInfo) {
11475
11694
  getAccount(true).catch(error => {
@@ -11481,8 +11700,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11481
11700
  console.error('Failed to save auth data to storage:', error);
11482
11701
  throw error;
11483
11702
  }
11484
- }, [proxyMode, notifyAuthStateChange, preloadAccountInfo]);
11703
+ }, [proxyMode, notifyAuthStateChange, preloadAccountInfo, syncContact, trackInteraction]);
11485
11704
  const logout = useCallback(async () => {
11705
+ const currentUser = user;
11706
+ const currentContactId = contactId;
11486
11707
  try {
11487
11708
  // Only clear persistent storage in standalone mode
11488
11709
  if (!proxyMode) {
@@ -11494,21 +11715,27 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11494
11715
  setUser(null);
11495
11716
  setAccountData(null);
11496
11717
  setAccountInfo(null);
11718
+ setContact(null);
11719
+ setContactId(null);
11720
+ setIsVerified(false);
11721
+ pendingVerificationRef.current = false;
11497
11722
  // Cross-iframe auth state synchronization
11498
- // Always notify parent frame of logout
11499
11723
  if (iframe.isIframe()) {
11500
11724
  console.log('[AuthContext] Notifying parent of logout via postMessage');
11501
11725
  iframe.sendParentCustom('smartlinks:authkit:logout', {});
11502
11726
  }
11503
- notifyAuthStateChange('LOGOUT', null, null, null);
11727
+ notifyAuthStateChange('LOGOUT', null, null, null, null, false);
11728
+ // Track logout interaction (fire and forget)
11729
+ if (currentUser && collectionId && shouldTrackInteractions) {
11730
+ trackInteraction('logout', currentUser.uid, currentContactId);
11731
+ }
11504
11732
  }
11505
11733
  catch (error) {
11506
11734
  console.error('Failed to clear auth data from storage:', error);
11507
11735
  }
11508
- }, [proxyMode, notifyAuthStateChange]);
11736
+ }, [proxyMode, notifyAuthStateChange, user, contactId, collectionId, shouldTrackInteractions, trackInteraction]);
11509
11737
  const getToken = useCallback(async () => {
11510
11738
  if (proxyMode) {
11511
- // In proxy mode, token is managed by parent - return memory state
11512
11739
  return token;
11513
11740
  }
11514
11741
  const storedToken = await tokenStorage.getToken();
@@ -11517,23 +11744,19 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11517
11744
  const refreshToken = useCallback(async () => {
11518
11745
  throw new Error('Token refresh must be implemented via your backend API');
11519
11746
  }, []);
11520
- // Get account with intelligent caching (or direct parent fetch in proxy mode)
11521
11747
  const getAccount = useCallback(async (forceRefresh = false) => {
11522
11748
  try {
11523
11749
  if (proxyMode) {
11524
- // PROXY MODE: Always fetch from parent via proxied API, no local cache
11525
11750
  console.log('[AuthContext] Proxy mode: fetching account from parent');
11526
11751
  const freshAccountInfo = await smartlinks.auth.getAccount();
11527
11752
  setAccountInfo(freshAccountInfo);
11528
11753
  setAccountData(freshAccountInfo);
11529
- notifyAuthStateChange('ACCOUNT_REFRESH', user, token, freshAccountInfo, freshAccountInfo);
11754
+ notifyAuthStateChange('ACCOUNT_REFRESH', user, token, freshAccountInfo, freshAccountInfo, isVerified, contact, contactId);
11530
11755
  return freshAccountInfo;
11531
11756
  }
11532
- // STANDALONE MODE: Use caching
11533
11757
  if (!token) {
11534
11758
  throw new Error('Not authenticated. Please login first.');
11535
11759
  }
11536
- // Check cache unless force refresh
11537
11760
  if (!forceRefresh) {
11538
11761
  const cached = await tokenStorage.getAccountInfo();
11539
11762
  if (cached && !cached.isStale) {
@@ -11541,18 +11764,15 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11541
11764
  return cached.data;
11542
11765
  }
11543
11766
  }
11544
- // Fetch fresh data from API
11545
11767
  console.log('[AuthContext] Fetching fresh account info from API');
11546
11768
  const freshAccountInfo = await smartlinks.auth.getAccount();
11547
- // Cache the fresh data
11548
11769
  await tokenStorage.saveAccountInfo(freshAccountInfo, accountCacheTTL);
11549
11770
  setAccountInfo(freshAccountInfo);
11550
- notifyAuthStateChange('ACCOUNT_REFRESH', user, token, accountData, freshAccountInfo);
11771
+ notifyAuthStateChange('ACCOUNT_REFRESH', user, token, accountData, freshAccountInfo, isVerified, contact, contactId);
11551
11772
  return freshAccountInfo;
11552
11773
  }
11553
11774
  catch (error) {
11554
11775
  console.error('[AuthContext] Failed to get account info:', error);
11555
- // Fallback to stale cache if API fails (standalone mode only)
11556
11776
  if (!proxyMode) {
11557
11777
  const cached = await tokenStorage.getAccountInfo();
11558
11778
  if (cached) {
@@ -11562,12 +11782,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11562
11782
  }
11563
11783
  throw error;
11564
11784
  }
11565
- }, [proxyMode, token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
11566
- // Convenience method for explicit refresh
11785
+ }, [proxyMode, token, accountCacheTTL, user, accountData, isVerified, contact, contactId, notifyAuthStateChange]);
11567
11786
  const refreshAccount = useCallback(async () => {
11568
11787
  return await getAccount(true);
11569
11788
  }, [getAccount]);
11570
- // Clear account cache (no-op in proxy mode)
11571
11789
  const clearAccountCache = useCallback(async () => {
11572
11790
  if (!proxyMode) {
11573
11791
  await tokenStorage.clearAccountInfo();
@@ -11576,19 +11794,77 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11576
11794
  }, [proxyMode]);
11577
11795
  const onAuthStateChange = useCallback((callback) => {
11578
11796
  callbacksRef.current.add(callback);
11579
- // Return unsubscribe function
11580
11797
  return () => {
11581
11798
  callbacksRef.current.delete(callback);
11582
11799
  };
11583
11800
  }, []);
11801
+ const retryVerification = useCallback(async () => {
11802
+ if (!token || !user) {
11803
+ console.log('[AuthContext] No session to verify');
11804
+ return false;
11805
+ }
11806
+ if (isVerified) {
11807
+ console.log('[AuthContext] Session already verified');
11808
+ return true;
11809
+ }
11810
+ try {
11811
+ console.log('[AuthContext] Retrying session verification...');
11812
+ await smartlinks.auth.verifyToken(token);
11813
+ setIsVerified(true);
11814
+ pendingVerificationRef.current = false;
11815
+ console.log('[AuthContext] Session verified on retry');
11816
+ notifyAuthStateChange('SESSION_VERIFIED', user, token, accountData, accountInfo, true, contact, contactId);
11817
+ return true;
11818
+ }
11819
+ catch (err) {
11820
+ if (isNetworkError(err)) {
11821
+ console.warn('[AuthContext] Network still unavailable, will retry later');
11822
+ return false;
11823
+ }
11824
+ else {
11825
+ console.warn('[AuthContext] Session invalid on retry, logging out');
11826
+ await logout();
11827
+ return false;
11828
+ }
11829
+ }
11830
+ }, [token, user, isVerified, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
11831
+ // Online/offline event listener for auto-retry verification
11832
+ useEffect(() => {
11833
+ if (proxyMode)
11834
+ return;
11835
+ const handleOnline = () => {
11836
+ console.log('[AuthContext] Network reconnected');
11837
+ setIsOnline(true);
11838
+ if (pendingVerificationRef.current && token && user) {
11839
+ console.log('[AuthContext] Retrying pending verification after reconnect...');
11840
+ retryVerification();
11841
+ }
11842
+ };
11843
+ const handleOffline = () => {
11844
+ console.log('[AuthContext] Network disconnected');
11845
+ setIsOnline(false);
11846
+ };
11847
+ window.addEventListener('online', handleOnline);
11848
+ window.addEventListener('offline', handleOffline);
11849
+ return () => {
11850
+ window.removeEventListener('online', handleOnline);
11851
+ window.removeEventListener('offline', handleOffline);
11852
+ };
11853
+ }, [proxyMode, token, user, retryVerification]);
11584
11854
  const value = {
11585
11855
  user,
11586
11856
  token,
11587
11857
  accountData,
11588
11858
  accountInfo,
11589
11859
  isAuthenticated: !!user,
11860
+ isVerified,
11590
11861
  isLoading,
11862
+ isOnline,
11591
11863
  proxyMode,
11864
+ contact,
11865
+ contactId,
11866
+ getContact,
11867
+ updateContactCustomFields,
11592
11868
  login,
11593
11869
  logout,
11594
11870
  getToken,
@@ -11597,6 +11873,7 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
11597
11873
  refreshAccount,
11598
11874
  clearAccountCache,
11599
11875
  onAuthStateChange,
11876
+ retryVerification,
11600
11877
  };
11601
11878
  return jsx(AuthContext.Provider, { value: value, children: children });
11602
11879
  };
@@ -11714,10 +11991,21 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11714
11991
  // Reinitialize Smartlinks SDK when apiEndpoint or proxyMode changes
11715
11992
  // IMPORTANT: Preserve bearer token during reinitialization
11716
11993
  useEffect(() => {
11994
+ console.log('[SmartlinksAuthUI] 🔧 SDK INIT useEffect triggered', {
11995
+ apiEndpoint,
11996
+ proxyMode,
11997
+ hasLogger: !!logger,
11998
+ timestamp: new Date().toISOString()
11999
+ });
11717
12000
  log.log('SDK reinitialize useEffect triggered', { apiEndpoint, proxyMode });
11718
12001
  setSdkReady(false); // Mark SDK as not ready during reinitialization
11719
12002
  const reinitializeWithToken = async () => {
11720
12003
  if (apiEndpoint) {
12004
+ console.log('[SmartlinksAuthUI] 🔧 Reinitializing SDK with:', {
12005
+ baseURL: apiEndpoint,
12006
+ proxyMode: proxyMode,
12007
+ ngrokSkipBrowserWarning: true
12008
+ });
11721
12009
  log.log('Reinitializing SDK with baseURL:', apiEndpoint, 'proxyMode:', proxyMode);
11722
12010
  // Get current token before reinitializing (only in standalone mode)
11723
12011
  const currentToken = !proxyMode ? await auth.getToken() : null;
@@ -11727,6 +12015,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11727
12015
  ngrokSkipBrowserWarning: true,
11728
12016
  logger: logger, // Pass logger to SDK for verbose SDK logging
11729
12017
  });
12018
+ console.log('[SmartlinksAuthUI] ✅ SDK reinitialized, proxyMode:', proxyMode);
11730
12019
  log.log('SDK reinitialized successfully');
11731
12020
  // Restore bearer token after reinitialization using auth.verifyToken (standalone mode only)
11732
12021
  if (currentToken && !proxyMode) {
@@ -11735,14 +12024,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11735
12024
  });
11736
12025
  }
11737
12026
  // Mark SDK as ready
12027
+ console.log('[SmartlinksAuthUI] ✅ Setting sdkReady=true (with apiEndpoint)');
11738
12028
  setSdkReady(true);
11739
12029
  }
11740
12030
  else if (proxyMode) {
11741
12031
  // In proxy mode without custom endpoint, SDK should already be initialized by parent
12032
+ console.log('[SmartlinksAuthUI] ⚠️ Proxy mode WITHOUT apiEndpoint - expecting SDK already initialized by parent');
11742
12033
  log.log('Proxy mode without apiEndpoint, SDK already initialized by parent');
11743
12034
  setSdkReady(true);
11744
12035
  }
11745
12036
  else {
12037
+ console.log('[SmartlinksAuthUI] ℹ️ No apiEndpoint, no proxyMode - SDK already initialized by App');
11746
12038
  log.log('No apiEndpoint, SDK already initialized by App');
11747
12039
  // SDK was initialized by App component, mark as ready
11748
12040
  setSdkReady(true);
@@ -11761,6 +12053,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11761
12053
  };
11762
12054
  // Fetch UI configuration
11763
12055
  useEffect(() => {
12056
+ console.log('[SmartlinksAuthUI] 📋 CONFIG FETCH useEffect triggered', {
12057
+ skipConfigFetch,
12058
+ clientId,
12059
+ apiEndpoint,
12060
+ sdkReady,
12061
+ proxyMode,
12062
+ timestamp: new Date().toISOString()
12063
+ });
11764
12064
  log.log('Config fetch useEffect triggered', {
11765
12065
  skipConfigFetch,
11766
12066
  clientId,
@@ -11771,10 +12071,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11771
12071
  });
11772
12072
  // Wait for SDK to be ready before fetching config
11773
12073
  if (!sdkReady) {
12074
+ console.log('[SmartlinksAuthUI] ⏳ SDK not ready yet, waiting before config fetch...');
11774
12075
  log.log('SDK not ready yet, waiting...');
11775
12076
  return;
11776
12077
  }
11777
12078
  if (skipConfigFetch) {
12079
+ console.log('[SmartlinksAuthUI] ⏭️ Skipping config fetch - skipConfigFetch is true');
11778
12080
  log.log('Skipping config fetch - skipConfigFetch is true');
11779
12081
  setConfig(customization || {});
11780
12082
  return;
@@ -11782,6 +12084,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11782
12084
  const fetchConfig = async () => {
11783
12085
  // If no clientId provided, use default config immediately without API call
11784
12086
  if (!clientId) {
12087
+ console.log('[SmartlinksAuthUI] ⚠️ No clientId provided, using default config');
11785
12088
  log.log('No clientId provided, using default config');
11786
12089
  const defaultConfig = {
11787
12090
  ...DEFAULT_AUTH_CONFIG,
@@ -11800,21 +12103,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11800
12103
  const age = Date.now() - timestamp;
11801
12104
  // Use cache if less than 1 hour old
11802
12105
  if (age < 3600000) {
12106
+ console.log('[SmartlinksAuthUI] 📦 Using cached config (age:', Math.round(age / 1000), 'seconds)');
11803
12107
  setConfig({ ...cachedConfig, ...customization });
11804
12108
  setConfigLoading(false);
11805
12109
  // Fetch in background to update cache
12110
+ console.log('[SmartlinksAuthUI] 🔄 Background refresh of config via SDK...');
11806
12111
  api.fetchConfig().then(freshConfig => {
12112
+ console.log('[SmartlinksAuthUI] ✅ Background config refresh complete');
11807
12113
  localStorage.setItem(cacheKey, JSON.stringify({
11808
12114
  config: freshConfig,
11809
12115
  timestamp: Date.now()
11810
12116
  }));
12117
+ }).catch(err => {
12118
+ console.log('[SmartlinksAuthUI] ❌ Background config refresh failed:', err);
11811
12119
  });
11812
12120
  return;
11813
12121
  }
11814
12122
  }
11815
12123
  // Fetch from API
12124
+ console.log('[SmartlinksAuthUI] 🌐 Fetching config via SDK for clientId:', clientId, 'proxyMode:', proxyMode);
11816
12125
  log.log('Fetching config from API for clientId:', clientId);
11817
12126
  const fetchedConfig = await api.fetchConfig();
12127
+ console.log('[SmartlinksAuthUI] ✅ Config fetched successfully:', fetchedConfig);
11818
12128
  log.log('Received config:', fetchedConfig);
11819
12129
  // Merge with customization props (props take precedence)
11820
12130
  const mergedConfig = { ...fetchedConfig, ...customization };
@@ -11826,6 +12136,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11826
12136
  }));
11827
12137
  }
11828
12138
  catch (err) {
12139
+ console.log('[SmartlinksAuthUI] ❌ Config fetch failed:', err);
11829
12140
  log.error('Failed to fetch config:', err);
11830
12141
  setConfig(customization || {});
11831
12142
  }
@@ -11834,7 +12145,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11834
12145
  }
11835
12146
  };
11836
12147
  fetchConfig();
11837
- }, [apiEndpoint, clientId, customization, skipConfigFetch, sdkReady, log]);
12148
+ }, [apiEndpoint, clientId, customization, skipConfigFetch, sdkReady, proxyMode, log]);
11838
12149
  // Reset showEmailForm when mode changes away from login/register
11839
12150
  useEffect(() => {
11840
12151
  if (mode !== 'login' && mode !== 'register') {
@@ -11875,7 +12186,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11875
12186
  const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
11876
12187
  if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
11877
12188
  // Auto-login modes: Log the user in immediately if token is provided
11878
- auth.login(response.token, response.user, response.accountData);
12189
+ auth.login(response.token, response.user, response.accountData, true);
11879
12190
  setAuthSuccess(true);
11880
12191
  setSuccessMessage('Email verified successfully! You are now logged in.');
11881
12192
  onAuthSuccess(response.token, response.user, response.accountData);
@@ -11918,7 +12229,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11918
12229
  const response = await api.verifyMagicLink(token);
11919
12230
  // Auto-login with magic link if token is provided
11920
12231
  if (response.token) {
11921
- auth.login(response.token, response.user, response.accountData);
12232
+ auth.login(response.token, response.user, response.accountData, true);
11922
12233
  setAuthSuccess(true);
11923
12234
  setSuccessMessage('Magic link verified! You are now logged in.');
11924
12235
  onAuthSuccess(response.token, response.user, response.accountData);
@@ -11993,8 +12304,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11993
12304
  if (mode === 'register') {
11994
12305
  // Handle different verification modes
11995
12306
  if (verificationMode === 'immediate' && response.token) {
11996
- // Immediate mode: Log in right away if token is provided
11997
- auth.login(response.token, response.user, response.accountData);
12307
+ // Immediate mode: Log in right away if token is provided (isNewUser=true for registration)
12308
+ auth.login(response.token, response.user, response.accountData, true);
11998
12309
  setAuthSuccess(true);
11999
12310
  const deadline = response.emailVerificationDeadline
12000
12311
  ? new Date(response.emailVerificationDeadline).toLocaleString()
@@ -12030,7 +12341,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12030
12341
  if (response.requiresEmailVerification) {
12031
12342
  throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
12032
12343
  }
12033
- auth.login(response.token, response.user, response.accountData);
12344
+ auth.login(response.token, response.user, response.accountData, false);
12034
12345
  setAuthSuccess(true);
12035
12346
  setSuccessMessage('Login successful!');
12036
12347
  onAuthSuccess(response.token, response.user, response.accountData);
@@ -12190,7 +12501,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12190
12501
  tokenType: 'access_token',
12191
12502
  });
12192
12503
  if (authResponse.token) {
12193
- auth.login(authResponse.token, authResponse.user, authResponse.accountData);
12504
+ // Google OAuth can be login or signup - use isNewUser flag from backend if available
12505
+ auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser);
12194
12506
  setAuthSuccess(true);
12195
12507
  setSuccessMessage('Google login successful!');
12196
12508
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
@@ -12239,7 +12551,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12239
12551
  const idToken = response.credential;
12240
12552
  const authResponse = await api.loginWithGoogle(idToken);
12241
12553
  if (authResponse.token) {
12242
- auth.login(authResponse.token, authResponse.user, authResponse.accountData);
12554
+ // Google OAuth can be login or signup - use isNewUser flag from backend if available
12555
+ auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser);
12243
12556
  setAuthSuccess(true);
12244
12557
  setSuccessMessage('Google login successful!');
12245
12558
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
@@ -12300,7 +12613,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12300
12613
  const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
12301
12614
  // Update auth context with account data if token is provided
12302
12615
  if (response.token) {
12303
- auth.login(response.token, response.user, response.accountData);
12616
+ // Phone auth can be login or signup - use isNewUser flag from backend if available
12617
+ auth.login(response.token, response.user, response.accountData, response.isNewUser);
12304
12618
  onAuthSuccess(response.token, response.user, response.accountData);
12305
12619
  if (redirectUrl) {
12306
12620
  window.location.href = redirectUrl;