@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/components/AccountManagement.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts +1 -31
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.esm.js +187 -51
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +186 -50
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +26 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
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(`/
|
|
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
|
-
//
|
|
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
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
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
|
-
|
|
11656
|
-
//
|
|
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
|
|
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
|
|
12109
|
-
const
|
|
12110
|
-
const percentUsed = (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('');
|