@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountManagement.d.ts","sourceRoot":"","sources":["../../src/components/AccountManagement.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAMhE,OAAO,KAAK,EAAE,sBAAsB,EAAe,MAAM,UAAU,CAAC;AACpE,OAAO,qBAAqB,CAAC;AAO7B,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,
|
|
1
|
+
{"version":3,"file":"AccountManagement.d.ts","sourceRoot":"","sources":["../../src/components/AccountManagement.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAMhE,OAAO,KAAK,EAAE,sBAAsB,EAAe,MAAM,UAAU,CAAC;AACpE,OAAO,qBAAqB,CAAC;AAO7B,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CA0wB9D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SmartlinksAuthUI.d.ts","sourceRoot":"","sources":["../../src/components/SmartlinksAuthUI.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAY5D,OAAO,KAAK,EAAE,qBAAqB,EAAyF,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"SmartlinksAuthUI.d.ts","sourceRoot":"","sources":["../../src/components/SmartlinksAuthUI.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAY5D,OAAO,KAAK,EAAE,qBAAqB,EAAyF,MAAM,UAAU,CAAC;AA0I7I,QAAA,MAAM,mBAAmB,QAAa,OAAO,CAAC,IAAI,CAmCjD,CAAC;AAwEF,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAK/B,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA0mD5D,CAAC"}
|
|
@@ -1,35 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
import type { AuthUser, AuthStateChangeCallback, AuthProviderProps } from '../types';
|
|
4
|
-
interface AuthContextValue {
|
|
5
|
-
user: AuthUser | null;
|
|
6
|
-
token: string | null;
|
|
7
|
-
accountData: Record<string, any> | null;
|
|
8
|
-
accountInfo: Record<string, any> | null;
|
|
9
|
-
isAuthenticated: boolean;
|
|
10
|
-
isVerified: boolean;
|
|
11
|
-
isLoading: boolean;
|
|
12
|
-
isOnline: boolean;
|
|
13
|
-
proxyMode: boolean;
|
|
14
|
-
contact: ContactResponse | null;
|
|
15
|
-
contactId: string | null;
|
|
16
|
-
getContact: (forceRefresh?: boolean) => Promise<ContactResponse | null>;
|
|
17
|
-
updateContactCustomFields: (customFields: Record<string, any>) => Promise<ContactResponse>;
|
|
18
|
-
login: (token: string, user: AuthUser, accountData?: Record<string, any>, isNewUser?: boolean, expiresAt?: number) => Promise<void>;
|
|
19
|
-
logout: () => Promise<void>;
|
|
20
|
-
getToken: () => Promise<string | null>;
|
|
21
|
-
getTokenInfo: () => Promise<{
|
|
22
|
-
token: string;
|
|
23
|
-
expiresAt: number;
|
|
24
|
-
expiresIn: number;
|
|
25
|
-
} | null>;
|
|
26
|
-
refreshToken: () => Promise<string>;
|
|
27
|
-
getAccount: (forceRefresh?: boolean) => Promise<Record<string, any>>;
|
|
28
|
-
refreshAccount: () => Promise<Record<string, any>>;
|
|
29
|
-
clearAccountCache: () => Promise<void>;
|
|
30
|
-
onAuthStateChange: (callback: AuthStateChangeCallback) => () => void;
|
|
31
|
-
retryVerification: () => Promise<boolean>;
|
|
32
|
-
}
|
|
2
|
+
import type { AuthProviderProps, AuthContextValue } from '../types';
|
|
33
3
|
export declare const AuthContext: React.Context<AuthContextValue | undefined>;
|
|
34
4
|
export type { AuthContextValue };
|
|
35
5
|
export declare const AuthProvider: React.FC<AuthProviderProps>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../../src/context/AuthContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../../src/context/AuthContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAC;AAOnG,OAAO,KAAK,EAAqC,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGvG,eAAO,MAAM,WAAW,6CAAyD,CAAC;AAGlF,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA+0BpD,CAAC;AAEF,eAAO,MAAM,OAAO,QAAO,gBAM1B,CAAC"}
|
package/dist/index.esm.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import React, { useEffect, useState, useMemo, useRef, useCallback, createContext, useContext } from 'react';
|
|
3
3
|
import * as smartlinks from '@proveanything/smartlinks';
|
|
4
4
|
import { iframe, SmartlinksApiError } from '@proveanything/smartlinks';
|
|
5
|
-
import { post } from '@proveanything/smartlinks/dist/http';
|
|
5
|
+
import { post, getApiHeaders, isProxyEnabled } from '@proveanything/smartlinks/dist/http';
|
|
6
6
|
|
|
7
7
|
const AuthContainer = ({ children, theme = 'light', className = '', config, minimal = false, }) => {
|
|
8
8
|
// Apply CSS variables for customization
|
|
@@ -10849,7 +10849,7 @@ class AuthAPI {
|
|
|
10849
10849
|
});
|
|
10850
10850
|
// Exchange authorization code for tokens via backend
|
|
10851
10851
|
// Use direct HTTP call since SDK may not have this method in authKit namespace yet
|
|
10852
|
-
return post(`/
|
|
10852
|
+
return post(`/authkit/${this.clientId}/google-code`, {
|
|
10853
10853
|
code,
|
|
10854
10854
|
redirectUri,
|
|
10855
10855
|
});
|
|
@@ -11530,21 +11530,48 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
11530
11530
|
return null;
|
|
11531
11531
|
}
|
|
11532
11532
|
}, [collectionId, shouldSyncContacts, proxyMode, token, accountData, accountInfo, isVerified, notifyAuthStateChange]);
|
|
11533
|
+
// Detect legacy vs new interactionConfig shape
|
|
11534
|
+
const isLegacyConfig = interactionConfig && !('interactionId' in interactionConfig) && ('login' in interactionConfig || 'logout' in interactionConfig || 'signup' in interactionConfig);
|
|
11535
|
+
if (isLegacyConfig) {
|
|
11536
|
+
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.');
|
|
11537
|
+
}
|
|
11533
11538
|
// Track interaction event (non-blocking)
|
|
11534
|
-
//
|
|
11535
|
-
// Interaction IDs must be pre-created in SmartLinks - you cannot use arbitrary IDs.
|
|
11539
|
+
// Uses scope-based architecture: single interactionId + scope field to differentiate events.
|
|
11536
11540
|
const trackInteraction = useCallback(async (eventType, userId, currentContactId, metadata) => {
|
|
11537
11541
|
if (!collectionId || !shouldTrackInteractions)
|
|
11538
11542
|
return;
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11543
|
+
let interactionId;
|
|
11544
|
+
let scope;
|
|
11545
|
+
if (isLegacyConfig) {
|
|
11546
|
+
// Legacy: per-event-type IDs (deprecated)
|
|
11547
|
+
const legacyConfig = interactionConfig;
|
|
11548
|
+
const legacyMap = {
|
|
11549
|
+
login: legacyConfig?.login,
|
|
11550
|
+
logout: legacyConfig?.logout,
|
|
11551
|
+
signup: legacyConfig?.signup,
|
|
11552
|
+
session_restore: legacyConfig?.sessionRestore,
|
|
11553
|
+
};
|
|
11554
|
+
interactionId = legacyMap[eventType];
|
|
11555
|
+
if (!interactionId)
|
|
11556
|
+
return;
|
|
11557
|
+
}
|
|
11558
|
+
else if (interactionConfig && 'interactionId' in interactionConfig) {
|
|
11559
|
+
// New: single ID + scope
|
|
11560
|
+
interactionId = interactionConfig.interactionId;
|
|
11561
|
+
if (!interactionId)
|
|
11562
|
+
return;
|
|
11563
|
+
const defaultScopes = {
|
|
11564
|
+
login: 'login',
|
|
11565
|
+
logout: 'logout',
|
|
11566
|
+
signup: 'signup',
|
|
11567
|
+
session_restore: 'session_restore',
|
|
11568
|
+
};
|
|
11569
|
+
const customScopes = interactionConfig.scopes || {};
|
|
11570
|
+
scope = customScopes[eventType === 'session_restore' ? 'sessionRestore' : eventType] || defaultScopes[eventType];
|
|
11571
|
+
}
|
|
11572
|
+
else {
|
|
11547
11573
|
return;
|
|
11574
|
+
}
|
|
11548
11575
|
try {
|
|
11549
11576
|
await smartlinks.interactions.submitPublicEvent(collectionId, {
|
|
11550
11577
|
collectionId,
|
|
@@ -11552,7 +11579,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
11552
11579
|
userId,
|
|
11553
11580
|
contactId: currentContactId || undefined,
|
|
11554
11581
|
appId: interactionAppId,
|
|
11555
|
-
|
|
11582
|
+
scope,
|
|
11556
11583
|
outcome: 'completed',
|
|
11557
11584
|
metadata: {
|
|
11558
11585
|
...metadata,
|
|
@@ -11565,7 +11592,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
11565
11592
|
catch (err) {
|
|
11566
11593
|
// Non-blocking
|
|
11567
11594
|
}
|
|
11568
|
-
}, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
|
|
11595
|
+
}, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, isLegacyConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
|
|
11569
11596
|
// Keep refs in sync with latest callbacks (avoids stale closures)
|
|
11570
11597
|
useEffect(() => {
|
|
11571
11598
|
syncContactRef.current = syncContact;
|
|
@@ -11613,32 +11640,42 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
11613
11640
|
const initializeAuth = async () => {
|
|
11614
11641
|
try {
|
|
11615
11642
|
if (proxyMode) {
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11643
|
+
// Check if credentials exist before making the API call
|
|
11644
|
+
const headers = getApiHeaders();
|
|
11645
|
+
const hasBearer = !!headers['Authorization'];
|
|
11646
|
+
const hasSdkProxy = isProxyEnabled();
|
|
11647
|
+
if (!hasBearer && !hasSdkProxy) {
|
|
11648
|
+
console.debug('[AuthContext] Skipping getAccount - no credentials available');
|
|
11649
|
+
// Fall through to "no valid session" state
|
|
11650
|
+
}
|
|
11651
|
+
else {
|
|
11652
|
+
try {
|
|
11653
|
+
const accountResponse = await smartlinks.auth.getAccount();
|
|
11654
|
+
const accountAny = accountResponse;
|
|
11655
|
+
const hasValidSession = accountAny?.uid && accountAny.uid.length > 0;
|
|
11656
|
+
if (hasValidSession && isMounted) {
|
|
11657
|
+
const userFromAccount = {
|
|
11658
|
+
uid: accountAny.uid,
|
|
11659
|
+
email: accountAny?.email,
|
|
11660
|
+
displayName: accountAny?.displayName || accountAny?.name,
|
|
11661
|
+
phoneNumber: accountAny?.phoneNumber,
|
|
11662
|
+
};
|
|
11663
|
+
setUser(userFromAccount);
|
|
11664
|
+
setAccountData(accountResponse);
|
|
11665
|
+
setAccountInfo(accountResponse);
|
|
11666
|
+
setIsVerified(true);
|
|
11667
|
+
notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse, true);
|
|
11668
|
+
// Sync contact in background (proxy mode) - use ref for stable dependency
|
|
11669
|
+
syncContactRef.current?.(userFromAccount, accountResponse);
|
|
11670
|
+
}
|
|
11671
|
+
else if (isMounted) {
|
|
11672
|
+
// No valid session, awaiting login
|
|
11673
|
+
}
|
|
11634
11674
|
}
|
|
11635
|
-
|
|
11636
|
-
//
|
|
11675
|
+
catch (error) {
|
|
11676
|
+
// auth.getAccount() failed, awaiting login
|
|
11637
11677
|
}
|
|
11638
|
-
}
|
|
11639
|
-
catch (error) {
|
|
11640
|
-
// auth.getAccount() failed, awaiting login
|
|
11641
|
-
}
|
|
11678
|
+
} // end else (has credentials)
|
|
11642
11679
|
if (isMounted) {
|
|
11643
11680
|
setIsLoading(false);
|
|
11644
11681
|
initializingRef.current = false;
|
|
@@ -11670,7 +11707,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
11670
11707
|
pendingVerificationRef.current = false;
|
|
11671
11708
|
notifyAuthStateChange('SESSION_VERIFIED', storedUser, storedToken.token, storedAccountData || null, null, true, null, storedContactId);
|
|
11672
11709
|
// Track session restore interaction (optional) - use ref for stable dependency
|
|
11673
|
-
if (interactionConfig?.sessionRestore) {
|
|
11710
|
+
if (interactionConfig && ('interactionId' in interactionConfig || interactionConfig?.sessionRestore)) {
|
|
11674
11711
|
trackInteractionRef.current?.('session_restore', storedUser.uid, storedContactId);
|
|
11675
11712
|
}
|
|
11676
11713
|
}
|
|
@@ -11716,7 +11753,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
11716
11753
|
return () => {
|
|
11717
11754
|
isMounted = false;
|
|
11718
11755
|
};
|
|
11719
|
-
}, [proxyMode, notifyAuthStateChange, isNetworkError, interactionConfig
|
|
11756
|
+
}, [proxyMode, notifyAuthStateChange, isNetworkError, interactionConfig]);
|
|
11720
11757
|
// Listen for parent auth state changes (proxy mode only)
|
|
11721
11758
|
useEffect(() => {
|
|
11722
11759
|
if (!proxyMode)
|
|
@@ -12085,9 +12122,9 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12085
12122
|
if (!storedToken?.expiresAt)
|
|
12086
12123
|
return;
|
|
12087
12124
|
const now = Date.now();
|
|
12088
|
-
const
|
|
12089
|
-
const
|
|
12090
|
-
const percentUsed = (
|
|
12125
|
+
const remainingMs = storedToken.expiresAt - now;
|
|
12126
|
+
const totalLifetimeMs = 7 * 24 * 60 * 60 * 1000;
|
|
12127
|
+
const percentUsed = totalLifetimeMs > 0 ? ((totalLifetimeMs - remainingMs) / totalLifetimeMs) * 100 : 100;
|
|
12091
12128
|
if (percentUsed >= refreshThresholdPercent) {
|
|
12092
12129
|
try {
|
|
12093
12130
|
await refreshToken();
|
|
@@ -12283,6 +12320,25 @@ const getExpirationFromResponse = (response) => {
|
|
|
12283
12320
|
};
|
|
12284
12321
|
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
12285
12322
|
const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
|
|
12323
|
+
// Default Google OAuth proxy URL (hosted on our whitelisted domain)
|
|
12324
|
+
const DEFAULT_GOOGLE_PROXY_URL = 'https://smartlinks-auth-kit.lovable.app/google-proxy.html';
|
|
12325
|
+
// Exact hostnames where Google OAuth is registered and inline/OneTap flow works directly.
|
|
12326
|
+
// Only specific registered origins — NOT broad wildcards like *.lovable.app
|
|
12327
|
+
const WHITELISTED_GOOGLE_OAUTH_HOSTS = [
|
|
12328
|
+
'smartlinks-auth-kit.lovable.app', // This app's dev/preview domain (registered in Google Console)
|
|
12329
|
+
'smartlinks.app', // Production root
|
|
12330
|
+
'localhost', // Local dev
|
|
12331
|
+
'127.0.0.1', // Local dev
|
|
12332
|
+
];
|
|
12333
|
+
/**
|
|
12334
|
+
* Check if the current domain is whitelisted for direct Google OAuth.
|
|
12335
|
+
* Uses exact hostname match (plus subdomain match for smartlinks.app production).
|
|
12336
|
+
* Returns true if OneTap/inline flow can work without a proxy.
|
|
12337
|
+
*/
|
|
12338
|
+
const isWhitelistedGoogleDomain = () => {
|
|
12339
|
+
const hostname = window.location.hostname;
|
|
12340
|
+
return WHITELISTED_GOOGLE_OAUTH_HOSTS.some(domain => hostname === domain || hostname.endsWith(`.${domain}`));
|
|
12341
|
+
};
|
|
12286
12342
|
// Default auth UI configuration when no clientId is provided
|
|
12287
12343
|
const DEFAULT_AUTH_CONFIG = {
|
|
12288
12344
|
branding: {
|
|
@@ -13051,16 +13107,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13051
13107
|
}
|
|
13052
13108
|
};
|
|
13053
13109
|
const handleGoogleLogin = async () => {
|
|
13110
|
+
const hasCustomGoogleClientId = !!config?.googleClientId;
|
|
13054
13111
|
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
13055
13112
|
const configuredFlow = config?.googleOAuthFlow || 'oneTap';
|
|
13056
13113
|
const isWebView = detectWebView();
|
|
13057
13114
|
const nativeBridge = getNativeBridge();
|
|
13058
13115
|
const oauthFlow = (configuredFlow === 'oneTap' && isWebView && !nativeBridge) ? 'redirect' : configuredFlow;
|
|
13116
|
+
// Auto-detect proxy need:
|
|
13117
|
+
// - If explicitly configured, use that URL
|
|
13118
|
+
// - If user has their own Google Client ID, they've registered their domains — no proxy needed
|
|
13119
|
+
// - If on a whitelisted SmartLinks domain, inline flow works directly
|
|
13120
|
+
// - Otherwise, auto-use the default proxy URL
|
|
13121
|
+
const isWhitelisted = isWhitelistedGoogleDomain();
|
|
13122
|
+
const googleProxyUrl = config?.googleOAuthProxyUrl
|
|
13123
|
+
|| (!hasCustomGoogleClientId && !isWhitelisted ? DEFAULT_GOOGLE_PROXY_URL : undefined);
|
|
13059
13124
|
log.log('Google Auth initiated:', {
|
|
13060
13125
|
configuredFlow,
|
|
13061
13126
|
effectiveFlow: oauthFlow,
|
|
13062
13127
|
isWebView,
|
|
13063
13128
|
hasNativeBridge: !!nativeBridge,
|
|
13129
|
+
hasCustomGoogleClientId,
|
|
13130
|
+
isWhitelistedDomain: isWhitelisted,
|
|
13131
|
+
usingProxy: !!googleProxyUrl,
|
|
13132
|
+
proxyUrl: googleProxyUrl,
|
|
13064
13133
|
});
|
|
13065
13134
|
setLoading(true);
|
|
13066
13135
|
setError(undefined);
|
|
@@ -13135,6 +13204,70 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13135
13204
|
setLoading(false);
|
|
13136
13205
|
return;
|
|
13137
13206
|
}
|
|
13207
|
+
// Priority 3: Google OAuth Proxy popup (for custom domains not registered in Google Console)
|
|
13208
|
+
if (googleProxyUrl) {
|
|
13209
|
+
log.log('Using Google OAuth proxy popup:', googleProxyUrl);
|
|
13210
|
+
const proxyParams = new URLSearchParams({
|
|
13211
|
+
clientId,
|
|
13212
|
+
returnOrigin: window.location.origin,
|
|
13213
|
+
...(apiEndpoint ? { apiEndpoint } : {}),
|
|
13214
|
+
});
|
|
13215
|
+
const popupUrl = `${googleProxyUrl}?${proxyParams.toString()}`;
|
|
13216
|
+
const popup = window.open(popupUrl, 'google-oauth-proxy', 'width=500,height=600,menubar=no,toolbar=no,location=yes');
|
|
13217
|
+
if (!popup) {
|
|
13218
|
+
setError('Popup blocked. Please allow popups for this site and try again.');
|
|
13219
|
+
setLoading(false);
|
|
13220
|
+
return;
|
|
13221
|
+
}
|
|
13222
|
+
// Listen for result from proxy popup
|
|
13223
|
+
const handleProxyMessage = async (event) => {
|
|
13224
|
+
// Validate origin matches proxy URL
|
|
13225
|
+
try {
|
|
13226
|
+
const proxyOrigin = new URL(googleProxyUrl).origin;
|
|
13227
|
+
if (event.origin !== proxyOrigin)
|
|
13228
|
+
return;
|
|
13229
|
+
}
|
|
13230
|
+
catch {
|
|
13231
|
+
return;
|
|
13232
|
+
}
|
|
13233
|
+
if (event.data?.type !== 'smartlinks:google-proxy:result')
|
|
13234
|
+
return;
|
|
13235
|
+
window.removeEventListener('message', handleProxyMessage);
|
|
13236
|
+
clearInterval(checkClosed);
|
|
13237
|
+
try {
|
|
13238
|
+
if (event.data.success && event.data.token) {
|
|
13239
|
+
const { token, user, accountData, isNewUser, expiresAt, expiresIn } = event.data;
|
|
13240
|
+
const expiration = expiresAt || (expiresIn ? Date.now() + expiresIn : undefined);
|
|
13241
|
+
await auth.login(token, user, accountData, isNewUser, expiration);
|
|
13242
|
+
setAuthSuccess(true);
|
|
13243
|
+
setSuccessMessage('Google login successful!');
|
|
13244
|
+
onAuthSuccess(token, user, accountData);
|
|
13245
|
+
}
|
|
13246
|
+
else {
|
|
13247
|
+
const errorMsg = event.data.error || 'Google login failed';
|
|
13248
|
+
setError(getFriendlyErrorMessage(errorMsg));
|
|
13249
|
+
onAuthError?.(new Error(errorMsg));
|
|
13250
|
+
}
|
|
13251
|
+
}
|
|
13252
|
+
catch (err) {
|
|
13253
|
+
setError(getFriendlyErrorMessage(err));
|
|
13254
|
+
onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
|
|
13255
|
+
}
|
|
13256
|
+
finally {
|
|
13257
|
+
setLoading(false);
|
|
13258
|
+
}
|
|
13259
|
+
};
|
|
13260
|
+
window.addEventListener('message', handleProxyMessage);
|
|
13261
|
+
// Monitor popup close without result (user closed it)
|
|
13262
|
+
const checkClosed = setInterval(() => {
|
|
13263
|
+
if (popup.closed) {
|
|
13264
|
+
clearInterval(checkClosed);
|
|
13265
|
+
window.removeEventListener('message', handleProxyMessage);
|
|
13266
|
+
setLoading(false);
|
|
13267
|
+
}
|
|
13268
|
+
}, 500);
|
|
13269
|
+
return; // Don't set loading to false - waiting for popup
|
|
13270
|
+
}
|
|
13138
13271
|
log.log('Using web-based OAuth flow:', oauthFlow);
|
|
13139
13272
|
// Priority 3: Web-based flows (redirect, popup, oneTap)
|
|
13140
13273
|
// Dynamically load Google Identity Services if not already loaded
|
|
@@ -13368,7 +13501,6 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13368
13501
|
if (resetToken && confirmPassword) {
|
|
13369
13502
|
// Complete password reset with token
|
|
13370
13503
|
await api.completePasswordReset(resetToken, emailOrPassword);
|
|
13371
|
-
await api.completePasswordReset(resetToken, emailOrPassword);
|
|
13372
13504
|
// Auto-login with the new password if we have the email
|
|
13373
13505
|
if (resetEmail) {
|
|
13374
13506
|
try {
|
|
@@ -13693,6 +13825,12 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
13693
13825
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
13694
13826
|
const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, showCustomFields = true, // New: show schema-driven custom fields
|
|
13695
13827
|
} = customization;
|
|
13828
|
+
// Provider awareness helpers
|
|
13829
|
+
// If providers is undefined (backend not updated yet), fall back to showing everything
|
|
13830
|
+
const providers = profile?.providers;
|
|
13831
|
+
const hasPassword = profile?.hasPassword ?? (providers === undefined ? undefined : providers?.includes('password'));
|
|
13832
|
+
const isGoogleOnly = providers !== undefined && providers.includes('google.com') && !providers.includes('password');
|
|
13833
|
+
const isPhoneOnly = providers !== undefined && providers.includes('phone') && providers.length === 1;
|
|
13696
13834
|
// Reinitialize Smartlinks SDK when apiEndpoint changes
|
|
13697
13835
|
useEffect(() => {
|
|
13698
13836
|
if (apiEndpoint) {
|
|
@@ -13710,13 +13848,11 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
13710
13848
|
const loadSchema = async () => {
|
|
13711
13849
|
setSchemaLoading(true);
|
|
13712
13850
|
try {
|
|
13713
|
-
console.log('[AccountManagement] Loading schema for collection:', collectionId);
|
|
13714
13851
|
const schemaResult = await smartlinks.contact.publicGetSchema(collectionId);
|
|
13715
|
-
console.log('[AccountManagement] Schema loaded:', schemaResult);
|
|
13716
13852
|
setSchema(schemaResult);
|
|
13717
13853
|
}
|
|
13718
13854
|
catch (err) {
|
|
13719
|
-
|
|
13855
|
+
// Non-fatal - component works without schema
|
|
13720
13856
|
// Non-fatal - component works without schema
|
|
13721
13857
|
}
|
|
13722
13858
|
finally {
|
|
@@ -13731,7 +13867,6 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
13731
13867
|
// where getContact() is called before the server knows about the user.
|
|
13732
13868
|
useEffect(() => {
|
|
13733
13869
|
if (!auth.isAuthenticated || !auth.isVerified) {
|
|
13734
|
-
console.log('[AccountManagement] Waiting for verified authentication before loading profile...');
|
|
13735
13870
|
return;
|
|
13736
13871
|
}
|
|
13737
13872
|
loadProfile();
|
|
@@ -13832,7 +13967,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
13832
13967
|
setError(undefined);
|
|
13833
13968
|
setSuccess(undefined);
|
|
13834
13969
|
try {
|
|
13835
|
-
|
|
13970
|
+
await auth.updateContactCustomFields?.(customFieldValues);
|
|
13836
13971
|
await auth.updateContactCustomFields?.(customFieldValues);
|
|
13837
13972
|
setSuccess('Profile updated successfully!');
|
|
13838
13973
|
setEditingSection(null);
|
|
@@ -13958,7 +14093,8 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
13958
14093
|
setError('Please type DELETE to confirm account deletion');
|
|
13959
14094
|
return;
|
|
13960
14095
|
}
|
|
13961
|
-
|
|
14096
|
+
// Only require password for users who have one
|
|
14097
|
+
if (hasPassword !== false && !deletePassword) {
|
|
13962
14098
|
setError('Password is required');
|
|
13963
14099
|
return;
|
|
13964
14100
|
}
|
|
@@ -13988,7 +14124,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
13988
14124
|
}
|
|
13989
14125
|
return (jsxs("div", { className: `account-management ${className}`, style: { maxWidth: '600px' }, children: [error && (jsx("div", { className: "auth-error", role: "alert", children: error })), success && (jsx("div", { className: "auth-success", role: "alert", children: success })), showProfileSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Display Name" }), jsx("div", { className: "field-value", children: profile?.displayName || 'Not set' })] }), editingSection !== 'profile' && (jsx("button", { type: "button", onClick: () => setEditingSection('profile'), className: "auth-button button-secondary", children: "Change" }))] }), editingSection === 'profile' && (jsxs("form", { onSubmit: handleUpdateProfile, className: "edit-form", children: [jsx("div", { className: "form-group", children: jsx("input", { id: "displayName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), placeholder: "Your display name", className: "auth-input" }) }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showCustomFields && collectionId && editableCustomFields.length > 0 && (jsxs("section", { className: "account-section", children: [jsx("h3", { className: "section-title", style: { fontSize: '0.9rem', fontWeight: 600, marginBottom: '12px' }, children: "Additional Information" }), editingSection !== 'customFields' ? (jsxs(Fragment, { children: [editableCustomFields.map((field) => (jsx("div", { className: "account-field", children: jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: field.label }), jsx("div", { className: "field-value", children: customFieldValues[field.key] !== undefined && customFieldValues[field.key] !== ''
|
|
13990
14126
|
? String(customFieldValues[field.key])
|
|
13991
|
-
: 'Not set' })] }) }, field.key))), jsx("button", { type: "button", onClick: () => setEditingSection('customFields'), className: "auth-button button-secondary", style: { marginTop: '8px' }, children: "Edit Information" })] })) : (jsxs("form", { onSubmit: handleUpdateCustomFields, className: "edit-form", children: [editableCustomFields.map((field) => (jsx(SchemaFieldRenderer, { field: field, value: customFieldValues[field.key], onChange: handleCustomFieldChange, disabled: loading }, field.key))), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showEmailSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Email Address" }), jsxs("div", { className: "field-value", children: [profile?.email || 'Not set', profile?.emailVerified && (jsx("span", { className: "verification-badge verified", children: "Verified" })), profile?.email && !profile?.emailVerified && (jsx("span", { className: "verification-badge unverified", children: "Unverified" }))] })] }), editingSection !== 'email' && (jsx("button", { type: "button", onClick: () => setEditingSection('email'), className: "auth-button button-secondary", children: "Change Email" }))] }), editingSection === 'email' && (jsxs("form", { onSubmit: handleChangeEmail, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newEmail", children: "New Email" }), jsx("input", { id: "newEmail", type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "new.email@example.com", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "emailPassword", children: "Confirm Password" }), jsx("input", { id: "emailPassword", type: "password", value: emailPassword, onChange: (e) => setEmailPassword(e.target.value), placeholder: "Enter your password", className: "auth-input", required: true })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Email' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPasswordSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Password" }), jsx("div", { className: "field-value", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), editingSection !== 'password' && (jsx("button", { type: "button", onClick: () => setEditingSection('password'), className: "auth-button button-secondary", children: "Change Password" }))] }), editingSection === 'password' && (jsxs("form", { onSubmit: handleChangePassword, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "currentPassword", children: "Current Password" }), jsx("input", { id: "currentPassword", type: "password", value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value), placeholder: "Enter current password", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPassword", children: "New Password" }), jsx("input", { id: "newPassword", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), placeholder: "Enter new password", className: "auth-input", required: true, minLength: 6 })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "confirmPassword", children: "Confirm New Password" }), jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), placeholder: "Confirm new password", className: "auth-input", required: true, minLength: 6 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPhoneSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Phone Number" }), jsx("div", { className: "field-value", children: profile?.phoneNumber || 'Not set' })] }), editingSection !== 'phone' && (jsx("button", { type: "button", onClick: () => setEditingSection('phone'), className: "auth-button button-secondary", children: "Change Phone" }))] }), editingSection === 'phone' && (jsxs("form", { onSubmit: handleUpdatePhone, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPhone", children: "New Phone Number" }), jsx("input", { id: "newPhone", type: "tel", value: newPhone, onChange: (e) => setNewPhone(e.target.value), placeholder: "+1234567890", className: "auth-input", required: true })] }), !phoneCodeSent ? (jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Code' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })) : (jsxs(Fragment, { children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "phoneCode", children: "Verification Code" }), jsx("input", { id: "phoneCode", type: "text", value: phoneCode, onChange: (e) => setPhoneCode(e.target.value), placeholder: "Enter 6-digit code", className: "auth-input", required: true, maxLength: 6 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] })), showDeleteAccount && (jsxs("section", { className: "account-section danger-zone", children: [jsx("h3", { className: "section-title text-danger", children: "Danger Zone" }), !showDeleteConfirm ? (jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "auth-button button-danger", children: "Delete Account" })) : (jsxs("div", { className: "delete-confirm", children: [jsx("p", { className: "warning-text", children: "\u26A0\uFE0F This action cannot be undone. This will permanently delete your account and all associated data." }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deletePassword", children: "Confirm Password" }), jsx("input", { id: "deletePassword", type: "password", value: deletePassword, onChange: (e) => setDeletePassword(e.target.value), placeholder: "Enter your password", className: "auth-input" })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deleteConfirm", children: "Type DELETE to confirm" }), jsx("input", { id: "deleteConfirm", type: "text", value: deleteConfirmText, onChange: (e) => setDeleteConfirmText(e.target.value), placeholder: "DELETE", className: "auth-input" })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleDeleteAccount, className: "auth-button button-danger", disabled: loading, children: loading ? 'Deleting...' : 'Permanently Delete Account' }), jsx("button", { type: "button", onClick: () => {
|
|
14127
|
+
: 'Not set' })] }) }, field.key))), jsx("button", { type: "button", onClick: () => setEditingSection('customFields'), className: "auth-button button-secondary", style: { marginTop: '8px' }, children: "Edit Information" })] })) : (jsxs("form", { onSubmit: handleUpdateCustomFields, className: "edit-form", children: [editableCustomFields.map((field) => (jsx(SchemaFieldRenderer, { field: field, value: customFieldValues[field.key], onChange: handleCustomFieldChange, disabled: loading }, field.key))), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showEmailSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Email Address" }), jsxs("div", { className: "field-value", children: [profile?.email || 'Not set', profile?.emailVerified && (jsx("span", { className: "verification-badge verified", children: "Verified" })), profile?.email && !profile?.emailVerified && (jsx("span", { className: "verification-badge unverified", children: "Unverified" }))] }), isGoogleOnly && (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 && (jsx("button", { type: "button", onClick: () => setEditingSection('email'), className: "auth-button button-secondary", children: "Change Email" }))] }), editingSection === 'email' && !isGoogleOnly && !isPhoneOnly && (jsxs("form", { onSubmit: handleChangeEmail, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newEmail", children: "New Email" }), jsx("input", { id: "newEmail", type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "new.email@example.com", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "emailPassword", children: "Confirm Password" }), jsx("input", { id: "emailPassword", type: "password", value: emailPassword, onChange: (e) => setEmailPassword(e.target.value), placeholder: "Enter your password", className: "auth-input", required: true })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Email' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPasswordSection && !isGoogleOnly && !isPhoneOnly && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Password" }), jsx("div", { className: "field-value", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), editingSection !== 'password' && (jsx("button", { type: "button", onClick: () => setEditingSection('password'), className: "auth-button button-secondary", children: "Change Password" }))] }), editingSection === 'password' && (jsxs("form", { onSubmit: handleChangePassword, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "currentPassword", children: "Current Password" }), jsx("input", { id: "currentPassword", type: "password", value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value), placeholder: "Enter current password", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPassword", children: "New Password" }), jsx("input", { id: "newPassword", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), placeholder: "Enter new password", className: "auth-input", required: true, minLength: 6 })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "confirmPassword", children: "Confirm New Password" }), jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), placeholder: "Confirm new password", className: "auth-input", required: true, minLength: 6 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPhoneSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Phone Number" }), jsx("div", { className: "field-value", children: profile?.phoneNumber || 'Not set' })] }), editingSection !== 'phone' && (jsx("button", { type: "button", onClick: () => setEditingSection('phone'), className: "auth-button button-secondary", children: "Change Phone" }))] }), editingSection === 'phone' && (jsxs("form", { onSubmit: handleUpdatePhone, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPhone", children: "New Phone Number" }), jsx("input", { id: "newPhone", type: "tel", value: newPhone, onChange: (e) => setNewPhone(e.target.value), placeholder: "+1234567890", className: "auth-input", required: true })] }), !phoneCodeSent ? (jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Code' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })) : (jsxs(Fragment, { children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "phoneCode", children: "Verification Code" }), jsx("input", { id: "phoneCode", type: "text", value: phoneCode, onChange: (e) => setPhoneCode(e.target.value), placeholder: "Enter 6-digit code", className: "auth-input", required: true, maxLength: 6 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] })), showDeleteAccount && (jsxs("section", { className: "account-section danger-zone", children: [jsx("h3", { className: "section-title text-danger", children: "Danger Zone" }), !showDeleteConfirm ? (jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "auth-button button-danger", children: "Delete Account" })) : (jsxs("div", { className: "delete-confirm", children: [jsx("p", { className: "warning-text", children: "\u26A0\uFE0F This action cannot be undone. This will permanently delete your account and all associated data." }), hasPassword !== false && (jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deletePassword", children: "Confirm Password" }), jsx("input", { id: "deletePassword", type: "password", value: deletePassword, onChange: (e) => setDeletePassword(e.target.value), placeholder: "Enter your password", className: "auth-input" })] })), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deleteConfirm", children: "Type DELETE to confirm" }), jsx("input", { id: "deleteConfirm", type: "text", value: deleteConfirmText, onChange: (e) => setDeleteConfirmText(e.target.value), placeholder: "DELETE", className: "auth-input" })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleDeleteAccount, className: "auth-button button-danger", disabled: loading, children: loading ? 'Deleting...' : 'Permanently Delete Account' }), jsx("button", { type: "button", onClick: () => {
|
|
13992
14128
|
setShowDeleteConfirm(false);
|
|
13993
14129
|
setDeletePassword('');
|
|
13994
14130
|
setDeleteConfirmText('');
|