@proveanything/smartlinks-auth-ui 0.3.10 → 0.3.12

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
@@ -10869,7 +10869,7 @@ class AuthAPI {
10869
10869
  });
10870
10870
  // Exchange authorization code for tokens via backend
10871
10871
  // Use direct HTTP call since SDK may not have this method in authKit namespace yet
10872
- return http.post(`/api/v1/authkit/${this.clientId}/google-code`, {
10872
+ return http.post(`/authkit/${this.clientId}/google-code`, {
10873
10873
  code,
10874
10874
  redirectUri,
10875
10875
  });
@@ -11550,21 +11550,48 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11550
11550
  return null;
11551
11551
  }
11552
11552
  }, [collectionId, shouldSyncContacts, proxyMode, token, accountData, accountInfo, isVerified, notifyAuthStateChange]);
11553
+ // Detect legacy vs new interactionConfig shape
11554
+ const isLegacyConfig = interactionConfig && !('interactionId' in interactionConfig) && ('login' in interactionConfig || 'logout' in interactionConfig || 'signup' in interactionConfig);
11555
+ if (isLegacyConfig) {
11556
+ console.warn('[AuthContext] Deprecated: interactionConfig uses legacy per-event-type IDs. Migrate to { interactionId, scopes? } shape. Legacy support will be removed in a future release.');
11557
+ }
11553
11558
  // Track interaction event (non-blocking)
11554
- // IMPORTANT: Only tracks if an explicit interaction ID is configured for the event type.
11555
- // Interaction IDs must be pre-created in SmartLinks - you cannot use arbitrary IDs.
11559
+ // Uses scope-based architecture: single interactionId + scope field to differentiate events.
11556
11560
  const trackInteraction = React.useCallback(async (eventType, userId, currentContactId, metadata) => {
11557
11561
  if (!collectionId || !shouldTrackInteractions)
11558
11562
  return;
11559
- const interactionIdMap = {
11560
- login: interactionConfig?.login,
11561
- logout: interactionConfig?.logout,
11562
- signup: interactionConfig?.signup,
11563
- session_restore: interactionConfig?.sessionRestore,
11564
- };
11565
- const interactionId = interactionIdMap[eventType];
11566
- if (!interactionId)
11563
+ let interactionId;
11564
+ let scope;
11565
+ if (isLegacyConfig) {
11566
+ // Legacy: per-event-type IDs (deprecated)
11567
+ const legacyConfig = interactionConfig;
11568
+ const legacyMap = {
11569
+ login: legacyConfig?.login,
11570
+ logout: legacyConfig?.logout,
11571
+ signup: legacyConfig?.signup,
11572
+ session_restore: legacyConfig?.sessionRestore,
11573
+ };
11574
+ interactionId = legacyMap[eventType];
11575
+ if (!interactionId)
11576
+ return;
11577
+ }
11578
+ else if (interactionConfig && 'interactionId' in interactionConfig) {
11579
+ // New: single ID + scope
11580
+ interactionId = interactionConfig.interactionId;
11581
+ if (!interactionId)
11582
+ return;
11583
+ const defaultScopes = {
11584
+ login: 'login',
11585
+ logout: 'logout',
11586
+ signup: 'signup',
11587
+ session_restore: 'session_restore',
11588
+ };
11589
+ const customScopes = interactionConfig.scopes || {};
11590
+ scope = customScopes[eventType === 'session_restore' ? 'sessionRestore' : eventType] || defaultScopes[eventType];
11591
+ }
11592
+ else {
11567
11593
  return;
11594
+ }
11568
11595
  try {
11569
11596
  await smartlinks__namespace.interactions.submitPublicEvent(collectionId, {
11570
11597
  collectionId,
@@ -11572,7 +11599,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11572
11599
  userId,
11573
11600
  contactId: currentContactId || undefined,
11574
11601
  appId: interactionAppId,
11575
- eventType,
11602
+ scope,
11576
11603
  outcome: 'completed',
11577
11604
  metadata: {
11578
11605
  ...metadata,
@@ -11585,7 +11612,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11585
11612
  catch (err) {
11586
11613
  // Non-blocking
11587
11614
  }
11588
- }, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
11615
+ }, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, isLegacyConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
11589
11616
  // Keep refs in sync with latest callbacks (avoids stale closures)
11590
11617
  React.useEffect(() => {
11591
11618
  syncContactRef.current = syncContact;
@@ -11633,32 +11660,42 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11633
11660
  const initializeAuth = async () => {
11634
11661
  try {
11635
11662
  if (proxyMode) {
11636
- try {
11637
- const accountResponse = await smartlinks__namespace.auth.getAccount();
11638
- const accountAny = accountResponse;
11639
- const hasValidSession = accountAny?.uid && accountAny.uid.length > 0;
11640
- if (hasValidSession && isMounted) {
11641
- const userFromAccount = {
11642
- uid: accountAny.uid,
11643
- email: accountAny?.email,
11644
- displayName: accountAny?.displayName || accountAny?.name,
11645
- phoneNumber: accountAny?.phoneNumber,
11646
- };
11647
- setUser(userFromAccount);
11648
- setAccountData(accountResponse);
11649
- setAccountInfo(accountResponse);
11650
- setIsVerified(true);
11651
- notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse, true);
11652
- // Sync contact in background (proxy mode) - use ref for stable dependency
11653
- syncContactRef.current?.(userFromAccount, accountResponse);
11663
+ // Check if credentials exist before making the API call
11664
+ const headers = http.getApiHeaders();
11665
+ const hasBearer = !!headers['Authorization'];
11666
+ const hasSdkProxy = http.isProxyEnabled();
11667
+ if (!hasBearer && !hasSdkProxy) {
11668
+ console.debug('[AuthContext] Skipping getAccount - no credentials available');
11669
+ // Fall through to "no valid session" state
11670
+ }
11671
+ else {
11672
+ try {
11673
+ const accountResponse = await smartlinks__namespace.auth.getAccount();
11674
+ const accountAny = accountResponse;
11675
+ const hasValidSession = accountAny?.uid && accountAny.uid.length > 0;
11676
+ if (hasValidSession && isMounted) {
11677
+ const userFromAccount = {
11678
+ uid: accountAny.uid,
11679
+ email: accountAny?.email,
11680
+ displayName: accountAny?.displayName || accountAny?.name,
11681
+ phoneNumber: accountAny?.phoneNumber,
11682
+ };
11683
+ setUser(userFromAccount);
11684
+ setAccountData(accountResponse);
11685
+ setAccountInfo(accountResponse);
11686
+ setIsVerified(true);
11687
+ notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse, true);
11688
+ // Sync contact in background (proxy mode) - use ref for stable dependency
11689
+ syncContactRef.current?.(userFromAccount, accountResponse);
11690
+ }
11691
+ else if (isMounted) {
11692
+ // No valid session, awaiting login
11693
+ }
11654
11694
  }
11655
- else if (isMounted) {
11656
- // No valid session, awaiting login
11695
+ catch (error) {
11696
+ // auth.getAccount() failed, awaiting login
11657
11697
  }
11658
- }
11659
- catch (error) {
11660
- // auth.getAccount() failed, awaiting login
11661
- }
11698
+ } // end else (has credentials)
11662
11699
  if (isMounted) {
11663
11700
  setIsLoading(false);
11664
11701
  initializingRef.current = false;
@@ -11690,7 +11727,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11690
11727
  pendingVerificationRef.current = false;
11691
11728
  notifyAuthStateChange('SESSION_VERIFIED', storedUser, storedToken.token, storedAccountData || null, null, true, null, storedContactId);
11692
11729
  // Track session restore interaction (optional) - use ref for stable dependency
11693
- if (interactionConfig?.sessionRestore) {
11730
+ if (interactionConfig && ('interactionId' in interactionConfig || interactionConfig?.sessionRestore)) {
11694
11731
  trackInteractionRef.current?.('session_restore', storedUser.uid, storedContactId);
11695
11732
  }
11696
11733
  }
@@ -11736,7 +11773,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11736
11773
  return () => {
11737
11774
  isMounted = false;
11738
11775
  };
11739
- }, [proxyMode, notifyAuthStateChange, isNetworkError, interactionConfig?.sessionRestore]);
11776
+ }, [proxyMode, notifyAuthStateChange, isNetworkError, interactionConfig]);
11740
11777
  // Listen for parent auth state changes (proxy mode only)
11741
11778
  React.useEffect(() => {
11742
11779
  if (!proxyMode)
@@ -12105,9 +12142,9 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12105
12142
  if (!storedToken?.expiresAt)
12106
12143
  return;
12107
12144
  const now = Date.now();
12108
- const tokenLifetime = storedToken.expiresAt - (storedToken.expiresAt - (7 * 24 * 60 * 60 * 1000));
12109
- const tokenAge = now - (storedToken.expiresAt - (7 * 24 * 60 * 60 * 1000));
12110
- const percentUsed = (tokenAge / tokenLifetime) * 100;
12145
+ const remainingMs = storedToken.expiresAt - now;
12146
+ const totalLifetimeMs = 7 * 24 * 60 * 60 * 1000;
12147
+ const percentUsed = totalLifetimeMs > 0 ? ((totalLifetimeMs - remainingMs) / totalLifetimeMs) * 100 : 100;
12111
12148
  if (percentUsed >= refreshThresholdPercent) {
12112
12149
  try {
12113
12150
  await refreshToken();
@@ -12303,6 +12340,25 @@ const getExpirationFromResponse = (response) => {
12303
12340
  };
12304
12341
  // Default Smartlinks Google OAuth Client ID (public - safe to expose)
12305
12342
  const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
12343
+ // Default Google OAuth proxy URL (hosted on our whitelisted domain)
12344
+ const DEFAULT_GOOGLE_PROXY_URL = 'https://smartlinks-auth-kit.lovable.app/google-proxy.html';
12345
+ // Exact hostnames where Google OAuth is registered and inline/OneTap flow works directly.
12346
+ // Only specific registered origins — NOT broad wildcards like *.lovable.app
12347
+ const WHITELISTED_GOOGLE_OAUTH_HOSTS = [
12348
+ 'smartlinks-auth-kit.lovable.app', // This app's dev/preview domain (registered in Google Console)
12349
+ 'smartlinks.app', // Production root
12350
+ 'localhost', // Local dev
12351
+ '127.0.0.1', // Local dev
12352
+ ];
12353
+ /**
12354
+ * Check if the current domain is whitelisted for direct Google OAuth.
12355
+ * Uses exact hostname match (plus subdomain match for smartlinks.app production).
12356
+ * Returns true if OneTap/inline flow can work without a proxy.
12357
+ */
12358
+ const isWhitelistedGoogleDomain = () => {
12359
+ const hostname = window.location.hostname;
12360
+ return WHITELISTED_GOOGLE_OAUTH_HOSTS.some(domain => hostname === domain || hostname.endsWith(`.${domain}`));
12361
+ };
12306
12362
  // Default auth UI configuration when no clientId is provided
12307
12363
  const DEFAULT_AUTH_CONFIG = {
12308
12364
  branding: {
@@ -13071,16 +13127,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13071
13127
  }
13072
13128
  };
13073
13129
  const handleGoogleLogin = async () => {
13130
+ const hasCustomGoogleClientId = !!config?.googleClientId;
13074
13131
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
13075
13132
  const configuredFlow = config?.googleOAuthFlow || 'oneTap';
13076
13133
  const isWebView = detectWebView();
13077
13134
  const nativeBridge = getNativeBridge();
13078
13135
  const oauthFlow = (configuredFlow === 'oneTap' && isWebView && !nativeBridge) ? 'redirect' : configuredFlow;
13136
+ // Auto-detect proxy need:
13137
+ // - If explicitly configured, use that URL
13138
+ // - If user has their own Google Client ID, they've registered their domains — no proxy needed
13139
+ // - If on a whitelisted SmartLinks domain, inline flow works directly
13140
+ // - Otherwise, auto-use the default proxy URL
13141
+ const isWhitelisted = isWhitelistedGoogleDomain();
13142
+ const googleProxyUrl = config?.googleOAuthProxyUrl
13143
+ || (!hasCustomGoogleClientId && !isWhitelisted ? DEFAULT_GOOGLE_PROXY_URL : undefined);
13079
13144
  log.log('Google Auth initiated:', {
13080
13145
  configuredFlow,
13081
13146
  effectiveFlow: oauthFlow,
13082
13147
  isWebView,
13083
13148
  hasNativeBridge: !!nativeBridge,
13149
+ hasCustomGoogleClientId,
13150
+ isWhitelistedDomain: isWhitelisted,
13151
+ usingProxy: !!googleProxyUrl,
13152
+ proxyUrl: googleProxyUrl,
13084
13153
  });
13085
13154
  setLoading(true);
13086
13155
  setError(undefined);
@@ -13155,6 +13224,70 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13155
13224
  setLoading(false);
13156
13225
  return;
13157
13226
  }
13227
+ // Priority 3: Google OAuth Proxy popup (for custom domains not registered in Google Console)
13228
+ if (googleProxyUrl) {
13229
+ log.log('Using Google OAuth proxy popup:', googleProxyUrl);
13230
+ const proxyParams = new URLSearchParams({
13231
+ clientId,
13232
+ returnOrigin: window.location.origin,
13233
+ ...(apiEndpoint ? { apiEndpoint } : {}),
13234
+ });
13235
+ const popupUrl = `${googleProxyUrl}?${proxyParams.toString()}`;
13236
+ const popup = window.open(popupUrl, 'google-oauth-proxy', 'width=500,height=600,menubar=no,toolbar=no,location=yes');
13237
+ if (!popup) {
13238
+ setError('Popup blocked. Please allow popups for this site and try again.');
13239
+ setLoading(false);
13240
+ return;
13241
+ }
13242
+ // Listen for result from proxy popup
13243
+ const handleProxyMessage = async (event) => {
13244
+ // Validate origin matches proxy URL
13245
+ try {
13246
+ const proxyOrigin = new URL(googleProxyUrl).origin;
13247
+ if (event.origin !== proxyOrigin)
13248
+ return;
13249
+ }
13250
+ catch {
13251
+ return;
13252
+ }
13253
+ if (event.data?.type !== 'smartlinks:google-proxy:result')
13254
+ return;
13255
+ window.removeEventListener('message', handleProxyMessage);
13256
+ clearInterval(checkClosed);
13257
+ try {
13258
+ if (event.data.success && event.data.token) {
13259
+ const { token, user, accountData, isNewUser, expiresAt, expiresIn } = event.data;
13260
+ const expiration = expiresAt || (expiresIn ? Date.now() + expiresIn : undefined);
13261
+ await auth.login(token, user, accountData, isNewUser, expiration);
13262
+ setAuthSuccess(true);
13263
+ setSuccessMessage('Google login successful!');
13264
+ onAuthSuccess(token, user, accountData);
13265
+ }
13266
+ else {
13267
+ const errorMsg = event.data.error || 'Google login failed';
13268
+ setError(getFriendlyErrorMessage(errorMsg));
13269
+ onAuthError?.(new Error(errorMsg));
13270
+ }
13271
+ }
13272
+ catch (err) {
13273
+ setError(getFriendlyErrorMessage(err));
13274
+ onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13275
+ }
13276
+ finally {
13277
+ setLoading(false);
13278
+ }
13279
+ };
13280
+ window.addEventListener('message', handleProxyMessage);
13281
+ // Monitor popup close without result (user closed it)
13282
+ const checkClosed = setInterval(() => {
13283
+ if (popup.closed) {
13284
+ clearInterval(checkClosed);
13285
+ window.removeEventListener('message', handleProxyMessage);
13286
+ setLoading(false);
13287
+ }
13288
+ }, 500);
13289
+ return; // Don't set loading to false - waiting for popup
13290
+ }
13158
13291
  log.log('Using web-based OAuth flow:', oauthFlow);
13159
13292
  // Priority 3: Web-based flows (redirect, popup, oneTap)
13160
13293
  // Dynamically load Google Identity Services if not already loaded
@@ -13388,7 +13521,6 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13388
13521
  if (resetToken && confirmPassword) {
13389
13522
  // Complete password reset with token
13390
13523
  await api.completePasswordReset(resetToken, emailOrPassword);
13391
- await api.completePasswordReset(resetToken, emailOrPassword);
13392
13524
  // Auto-login with the new password if we have the email
13393
13525
  if (resetEmail) {
13394
13526
  try {
@@ -13713,6 +13845,12 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
13713
13845
  const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
13714
13846
  const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, showCustomFields = true, // New: show schema-driven custom fields
13715
13847
  } = customization;
13848
+ // Provider awareness helpers
13849
+ // If providers is undefined (backend not updated yet), fall back to showing everything
13850
+ const providers = profile?.providers;
13851
+ const hasPassword = profile?.hasPassword ?? (providers === undefined ? undefined : providers?.includes('password'));
13852
+ const isGoogleOnly = providers !== undefined && providers.includes('google.com') && !providers.includes('password');
13853
+ const isPhoneOnly = providers !== undefined && providers.includes('phone') && providers.length === 1;
13716
13854
  // Reinitialize Smartlinks SDK when apiEndpoint changes
13717
13855
  React.useEffect(() => {
13718
13856
  if (apiEndpoint) {
@@ -13730,13 +13868,11 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
13730
13868
  const loadSchema = async () => {
13731
13869
  setSchemaLoading(true);
13732
13870
  try {
13733
- console.log('[AccountManagement] Loading schema for collection:', collectionId);
13734
13871
  const schemaResult = await smartlinks__namespace.contact.publicGetSchema(collectionId);
13735
- console.log('[AccountManagement] Schema loaded:', schemaResult);
13736
13872
  setSchema(schemaResult);
13737
13873
  }
13738
13874
  catch (err) {
13739
- console.warn('[AccountManagement] Failed to load schema:', err);
13875
+ // Non-fatal - component works without schema
13740
13876
  // Non-fatal - component works without schema
13741
13877
  }
13742
13878
  finally {
@@ -13751,7 +13887,6 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
13751
13887
  // where getContact() is called before the server knows about the user.
13752
13888
  React.useEffect(() => {
13753
13889
  if (!auth.isAuthenticated || !auth.isVerified) {
13754
- console.log('[AccountManagement] Waiting for verified authentication before loading profile...');
13755
13890
  return;
13756
13891
  }
13757
13892
  loadProfile();
@@ -13852,7 +13987,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
13852
13987
  setError(undefined);
13853
13988
  setSuccess(undefined);
13854
13989
  try {
13855
- console.log('[AccountManagement] Updating custom fields:', customFieldValues);
13990
+ await auth.updateContactCustomFields?.(customFieldValues);
13856
13991
  await auth.updateContactCustomFields?.(customFieldValues);
13857
13992
  setSuccess('Profile updated successfully!');
13858
13993
  setEditingSection(null);
@@ -13978,7 +14113,8 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
13978
14113
  setError('Please type DELETE to confirm account deletion');
13979
14114
  return;
13980
14115
  }
13981
- if (!deletePassword) {
14116
+ // Only require password for users who have one
14117
+ if (hasPassword !== false && !deletePassword) {
13982
14118
  setError('Password is required');
13983
14119
  return;
13984
14120
  }
@@ -14008,7 +14144,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14008
14144
  }
14009
14145
  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" })] })] }))] })), showCustomFields && collectionId && editableCustomFields.length > 0 && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsx("h3", { className: "section-title", style: { fontSize: '0.9rem', fontWeight: 600, marginBottom: '12px' }, children: "Additional Information" }), editingSection !== 'customFields' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [editableCustomFields.map((field) => (jsxRuntime.jsx("div", { className: "account-field", children: jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: field.label }), jsxRuntime.jsx("div", { className: "field-value", children: customFieldValues[field.key] !== undefined && customFieldValues[field.key] !== ''
14010
14146
  ? String(customFieldValues[field.key])
14011
- : 'Not set' })] }) }, field.key))), jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('customFields'), className: "auth-button button-secondary", style: { marginTop: '8px' }, children: "Edit Information" })] })) : (jsxRuntime.jsxs("form", { onSubmit: handleUpdateCustomFields, className: "edit-form", children: [editableCustomFields.map((field) => (jsxRuntime.jsx(SchemaFieldRenderer, { field: field, value: customFieldValues[field.key], onChange: handleCustomFieldChange, disabled: loading }, field.key))), 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: () => {
14147
+ : 'Not set' })] }) }, field.key))), jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('customFields'), className: "auth-button button-secondary", style: { marginTop: '8px' }, children: "Edit Information" })] })) : (jsxRuntime.jsxs("form", { onSubmit: handleUpdateCustomFields, className: "edit-form", children: [editableCustomFields.map((field) => (jsxRuntime.jsx(SchemaFieldRenderer, { field: field, value: customFieldValues[field.key], onChange: handleCustomFieldChange, disabled: loading }, field.key))), 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" }))] }), isGoogleOnly && (jsxRuntime.jsx("div", { className: "field-hint", style: { fontSize: '0.8rem', color: 'var(--color-muted-foreground, #888)', marginTop: '4px' }, children: "This email is managed by your Google account" }))] }), editingSection !== 'email' && !isGoogleOnly && !isPhoneOnly && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('email'), className: "auth-button button-secondary", children: "Change Email" }))] }), editingSection === 'email' && !isGoogleOnly && !isPhoneOnly && (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 && !isGoogleOnly && !isPhoneOnly && (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." }), hasPassword !== false && (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: () => {
14012
14148
  setShowDeleteConfirm(false);
14013
14149
  setDeletePassword('');
14014
14150
  setDeleteConfirmText('');