@proveanything/smartlinks-auth-ui 0.1.3 → 0.1.5
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/README.md +385 -228
- package/dist/components/AccountManagement.d.ts.map +1 -1
- package/dist/components/AuthContainer.d.ts +1 -0
- package/dist/components/AuthContainer.d.ts.map +1 -1
- package/dist/components/AuthUIPreview.d.ts +1 -0
- package/dist/components/AuthUIPreview.d.ts.map +1 -1
- package/dist/components/ClaimUI.d.ts +4 -0
- package/dist/components/ClaimUI.d.ts.map +1 -0
- package/dist/components/EmailAuthForm.d.ts +2 -1
- package/dist/components/EmailAuthForm.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts +4 -0
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -0
- package/dist/context/AuthContext.d.ts +10 -3
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +1602 -914
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1602 -913
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +66 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/persistentStorage.d.ts +39 -0
- package/dist/utils/persistentStorage.d.ts.map +1 -0
- package/dist/utils/tokenStorage.d.ts +23 -10
- package/dist/utils/tokenStorage.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ function _interopNamespaceDefault(e) {
|
|
|
23
23
|
|
|
24
24
|
var smartlinks__namespace = /*#__PURE__*/_interopNamespaceDefault(smartlinks);
|
|
25
25
|
|
|
26
|
-
const AuthContainer = ({ children, theme = 'light', className = '', config, }) => {
|
|
26
|
+
const AuthContainer = ({ children, theme = 'light', className = '', config, minimal = false, }) => {
|
|
27
27
|
// Apply CSS variables for customization
|
|
28
28
|
React.useEffect(() => {
|
|
29
29
|
if (!config?.branding)
|
|
@@ -63,11 +63,21 @@ const AuthContainer = ({ children, theme = 'light', className = '', config, }) =
|
|
|
63
63
|
}, [config]);
|
|
64
64
|
const title = config?.branding?.title;
|
|
65
65
|
const subtitle = config?.branding?.subtitle;
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
// Logo URL logic:
|
|
67
|
+
// - undefined/not set: use default Smartlinks logo
|
|
68
|
+
// - null or empty string: hide logo completely
|
|
69
|
+
// - string URL: use custom logo
|
|
70
|
+
const logoUrl = config?.branding?.logoUrl === undefined
|
|
71
|
+
? 'https://smartlinks.app/smartlinks/landscape-medium.png' // Default Smartlinks logo
|
|
72
|
+
: config?.branding?.logoUrl || null; // Custom or explicitly hidden
|
|
73
|
+
const containerClass = minimal
|
|
74
|
+
? `auth-minimal auth-theme-${theme} ${className}`
|
|
75
|
+
: `auth-container auth-theme-${theme} ${className}`;
|
|
76
|
+
const cardClass = minimal ? 'auth-minimal-card' : 'auth-card';
|
|
77
|
+
return (jsxRuntime.jsx("div", { className: containerClass, children: jsxRuntime.jsxs("div", { className: cardClass, style: !minimal && config?.branding?.buttonStyle === 'square' ? { borderRadius: '4px' } : undefined, children: [(logoUrl || title || subtitle) && (jsxRuntime.jsxs("div", { className: "auth-header", children: [logoUrl && (jsxRuntime.jsx("div", { className: "auth-logo", children: jsxRuntime.jsx("img", { src: logoUrl, alt: "Logo", style: { maxWidth: '200px', height: 'auto', objectFit: 'contain' } }) })), title && jsxRuntime.jsx("h1", { className: "auth-title", children: title }), subtitle && jsxRuntime.jsx("p", { className: "auth-subtitle", children: subtitle })] })), jsxRuntime.jsx("div", { className: "auth-content", children: children }), (config?.branding?.termsUrl || config?.branding?.privacyUrl) && (jsxRuntime.jsxs("div", { className: "auth-footer", children: [config.branding.termsUrl && jsxRuntime.jsx("a", { href: config.branding.termsUrl, target: "_blank", rel: "noopener noreferrer", children: "Terms" }), config.branding.termsUrl && config.branding.privacyUrl && jsxRuntime.jsx("span", { children: "\u2022" }), config.branding.privacyUrl && jsxRuntime.jsx("a", { href: config.branding.privacyUrl, target: "_blank", rel: "noopener noreferrer", children: "Privacy" })] }))] }) }));
|
|
68
78
|
};
|
|
69
79
|
|
|
70
|
-
const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, }) => {
|
|
80
|
+
const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, additionalFields = [], }) => {
|
|
71
81
|
const [formData, setFormData] = React.useState({
|
|
72
82
|
email: '',
|
|
73
83
|
password: '',
|
|
@@ -82,7 +92,7 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
|
|
|
82
92
|
};
|
|
83
93
|
return (jsxRuntime.jsxs("form", { className: "auth-form", onSubmit: handleSubmit, children: [jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: mode === 'login' ? 'Sign in' : 'Create account' }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: mode === 'login'
|
|
84
94
|
? 'Welcome back! Please enter your credentials.'
|
|
85
|
-
: 'Get started by creating your account.' })] }), error && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsxRuntime.jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John Doe" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsxRuntime.jsx("input", { type: "email", id: "email", className: "auth-input", value: formData.email || '', onChange: (e) => handleChange('email', e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsxRuntime.jsx("input", { type: "password", id: "password", className: "auth-input", value: formData.password || '', onChange: (e) => handleChange('password', e.target.value), required: true, disabled: loading, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === 'login' ? 'current-password' : 'new-password', minLength: 6 })] }), mode === 'login' && (jsxRuntime.jsx("div", { className: "auth-form-footer", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), jsxRuntime.jsxs("div", { className: "auth-divider", children: [jsxRuntime.jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] })] }));
|
|
95
|
+
: 'Get started by creating your account.' })] }), error && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsxRuntime.jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John Doe" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsxRuntime.jsx("input", { type: "email", id: "email", className: "auth-input", value: formData.email || '', onChange: (e) => handleChange('email', e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsxRuntime.jsx("input", { type: "password", id: "password", className: "auth-input", value: formData.password || '', onChange: (e) => handleChange('password', e.target.value), required: true, disabled: loading, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === 'login' ? 'current-password' : 'new-password', minLength: 6 })] }), mode === 'register' && additionalFields.map((field) => (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsxs("label", { htmlFor: field.name, className: "auth-label", children: [field.label, field.required && jsxRuntime.jsx("span", { style: { color: 'var(--auth-error-color, #ef4444)' }, children: " *" })] }), field.type === 'select' ? (jsxRuntime.jsxs("select", { id: field.name, className: "auth-input", value: formData[field.name] || '', onChange: (e) => handleChange(field.name, e.target.value), required: field.required, disabled: loading, children: [jsxRuntime.jsx("option", { value: "", children: "Select..." }), field.options?.map((option) => (jsxRuntime.jsx("option", { value: option, children: option }, option)))] })) : field.type === 'textarea' ? (jsxRuntime.jsx("textarea", { id: field.name, className: "auth-input", value: formData[field.name] || '', onChange: (e) => handleChange(field.name, e.target.value), required: field.required, disabled: loading, placeholder: field.placeholder, rows: 3, style: { minHeight: '80px', resize: 'vertical' } })) : (jsxRuntime.jsx("input", { type: field.type, id: field.name, className: "auth-input", value: formData[field.name] || '', onChange: (e) => handleChange(field.name, e.target.value), required: field.required, disabled: loading, placeholder: field.placeholder }))] }, field.name))), mode === 'login' && (jsxRuntime.jsx("div", { className: "auth-form-footer", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), jsxRuntime.jsxs("div", { className: "auth-divider", children: [jsxRuntime.jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] })] }));
|
|
86
96
|
};
|
|
87
97
|
|
|
88
98
|
const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
|
|
@@ -10693,142 +10703,724 @@ class AuthAPI {
|
|
|
10693
10703
|
}
|
|
10694
10704
|
}
|
|
10695
10705
|
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10706
|
+
/**
|
|
10707
|
+
* Persistent Storage Layer for Smartlinks Auth
|
|
10708
|
+
*
|
|
10709
|
+
* Implements a tiered persistence strategy similar to Firebase Auth:
|
|
10710
|
+
* 1. Primary: IndexedDB (async, large capacity, cross-tab)
|
|
10711
|
+
* 2. Fallback: localStorage (sync, universal support)
|
|
10712
|
+
*
|
|
10713
|
+
* Provides unified async API for all storage operations with automatic
|
|
10714
|
+
* fallback handling and cross-tab synchronization.
|
|
10715
|
+
*/
|
|
10716
|
+
const DB_NAME = 'smartlinks_auth_db';
|
|
10717
|
+
const DB_VERSION = 1;
|
|
10718
|
+
const STORE_NAME = 'auth_state';
|
|
10719
|
+
const STORAGE_CHANNEL = 'smartlinks_auth_storage';
|
|
10720
|
+
/**
|
|
10721
|
+
* IndexedDB Storage Implementation
|
|
10722
|
+
*/
|
|
10723
|
+
class IndexedDBStorage {
|
|
10724
|
+
constructor() {
|
|
10725
|
+
this.db = null;
|
|
10726
|
+
this.initPromise = null;
|
|
10727
|
+
this.channel = null;
|
|
10728
|
+
this.initPromise = this.initialize();
|
|
10729
|
+
// Set up BroadcastChannel for cross-tab communication
|
|
10730
|
+
if (typeof BroadcastChannel !== 'undefined') {
|
|
10731
|
+
this.channel = new BroadcastChannel(STORAGE_CHANNEL);
|
|
10732
|
+
}
|
|
10733
|
+
}
|
|
10734
|
+
async initialize() {
|
|
10735
|
+
return new Promise((resolve, reject) => {
|
|
10736
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
10737
|
+
request.onerror = () => {
|
|
10738
|
+
console.warn('IndexedDB initialization failed:', request.error);
|
|
10739
|
+
reject(request.error);
|
|
10740
|
+
};
|
|
10741
|
+
request.onsuccess = () => {
|
|
10742
|
+
this.db = request.result;
|
|
10743
|
+
resolve();
|
|
10744
|
+
};
|
|
10745
|
+
request.onupgradeneeded = (event) => {
|
|
10746
|
+
const db = event.target.result;
|
|
10747
|
+
// Create object store if it doesn't exist
|
|
10748
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
10749
|
+
db.createObjectStore(STORE_NAME);
|
|
10750
|
+
}
|
|
10751
|
+
};
|
|
10752
|
+
});
|
|
10753
|
+
}
|
|
10754
|
+
async ensureInitialized() {
|
|
10755
|
+
if (this.initPromise) {
|
|
10756
|
+
await this.initPromise;
|
|
10757
|
+
}
|
|
10758
|
+
}
|
|
10759
|
+
broadcastChange(event) {
|
|
10760
|
+
if (this.channel) {
|
|
10761
|
+
try {
|
|
10762
|
+
this.channel.postMessage(event);
|
|
10763
|
+
}
|
|
10764
|
+
catch (error) {
|
|
10765
|
+
console.warn('Failed to broadcast storage change:', error);
|
|
10766
|
+
}
|
|
10767
|
+
}
|
|
10768
|
+
}
|
|
10769
|
+
async setItem(key, value) {
|
|
10770
|
+
await this.ensureInitialized();
|
|
10771
|
+
return new Promise((resolve, reject) => {
|
|
10772
|
+
if (!this.db) {
|
|
10773
|
+
reject(new Error('IndexedDB not initialized'));
|
|
10774
|
+
return;
|
|
10775
|
+
}
|
|
10776
|
+
const transaction = this.db.transaction([STORE_NAME], 'readwrite');
|
|
10777
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
10778
|
+
const request = store.put(value, key);
|
|
10779
|
+
request.onsuccess = () => {
|
|
10780
|
+
this.broadcastChange({
|
|
10781
|
+
type: 'set',
|
|
10782
|
+
key,
|
|
10783
|
+
value,
|
|
10784
|
+
timestamp: Date.now(),
|
|
10785
|
+
});
|
|
10786
|
+
resolve();
|
|
10787
|
+
};
|
|
10788
|
+
request.onerror = () => {
|
|
10789
|
+
reject(request.error);
|
|
10790
|
+
};
|
|
10791
|
+
});
|
|
10792
|
+
}
|
|
10793
|
+
async getItem(key) {
|
|
10794
|
+
await this.ensureInitialized();
|
|
10795
|
+
return new Promise((resolve, reject) => {
|
|
10796
|
+
if (!this.db) {
|
|
10797
|
+
reject(new Error('IndexedDB not initialized'));
|
|
10798
|
+
return;
|
|
10799
|
+
}
|
|
10800
|
+
const transaction = this.db.transaction([STORE_NAME], 'readonly');
|
|
10801
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
10802
|
+
const request = store.get(key);
|
|
10803
|
+
request.onsuccess = () => {
|
|
10804
|
+
resolve(request.result !== undefined ? request.result : null);
|
|
10805
|
+
};
|
|
10806
|
+
request.onerror = () => {
|
|
10807
|
+
reject(request.error);
|
|
10808
|
+
};
|
|
10809
|
+
});
|
|
10810
|
+
}
|
|
10811
|
+
async removeItem(key) {
|
|
10812
|
+
await this.ensureInitialized();
|
|
10813
|
+
return new Promise((resolve, reject) => {
|
|
10814
|
+
if (!this.db) {
|
|
10815
|
+
reject(new Error('IndexedDB not initialized'));
|
|
10816
|
+
return;
|
|
10817
|
+
}
|
|
10818
|
+
const transaction = this.db.transaction([STORE_NAME], 'readwrite');
|
|
10819
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
10820
|
+
const request = store.delete(key);
|
|
10821
|
+
request.onsuccess = () => {
|
|
10822
|
+
this.broadcastChange({
|
|
10823
|
+
type: 'remove',
|
|
10824
|
+
key,
|
|
10825
|
+
timestamp: Date.now(),
|
|
10826
|
+
});
|
|
10827
|
+
resolve();
|
|
10828
|
+
};
|
|
10829
|
+
request.onerror = () => {
|
|
10830
|
+
reject(request.error);
|
|
10831
|
+
};
|
|
10832
|
+
});
|
|
10833
|
+
}
|
|
10834
|
+
async clear() {
|
|
10835
|
+
await this.ensureInitialized();
|
|
10836
|
+
return new Promise((resolve, reject) => {
|
|
10837
|
+
if (!this.db) {
|
|
10838
|
+
reject(new Error('IndexedDB not initialized'));
|
|
10839
|
+
return;
|
|
10840
|
+
}
|
|
10841
|
+
const transaction = this.db.transaction([STORE_NAME], 'readwrite');
|
|
10842
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
10843
|
+
const request = store.clear();
|
|
10844
|
+
request.onsuccess = () => {
|
|
10845
|
+
this.broadcastChange({
|
|
10846
|
+
type: 'clear',
|
|
10847
|
+
timestamp: Date.now(),
|
|
10848
|
+
});
|
|
10849
|
+
resolve();
|
|
10850
|
+
};
|
|
10851
|
+
request.onerror = () => {
|
|
10852
|
+
reject(request.error);
|
|
10853
|
+
};
|
|
10854
|
+
});
|
|
10855
|
+
}
|
|
10856
|
+
async isAvailable() {
|
|
10857
|
+
try {
|
|
10858
|
+
await this.ensureInitialized();
|
|
10859
|
+
return this.db !== null;
|
|
10860
|
+
}
|
|
10861
|
+
catch {
|
|
10862
|
+
return false;
|
|
10863
|
+
}
|
|
10864
|
+
}
|
|
10865
|
+
getBackend() {
|
|
10866
|
+
return 'indexeddb';
|
|
10867
|
+
}
|
|
10868
|
+
destroy() {
|
|
10869
|
+
if (this.db) {
|
|
10870
|
+
this.db.close();
|
|
10871
|
+
this.db = null;
|
|
10872
|
+
}
|
|
10873
|
+
if (this.channel) {
|
|
10874
|
+
this.channel.close();
|
|
10875
|
+
this.channel = null;
|
|
10876
|
+
}
|
|
10877
|
+
}
|
|
10878
|
+
}
|
|
10879
|
+
/**
|
|
10880
|
+
* localStorage Storage Implementation (Fallback)
|
|
10881
|
+
*/
|
|
10882
|
+
class LocalStorageAdapter {
|
|
10883
|
+
constructor() {
|
|
10884
|
+
this.prefix = 'smartlinks_auth_';
|
|
10885
|
+
this.channel = null;
|
|
10886
|
+
// Set up BroadcastChannel for cross-tab communication
|
|
10887
|
+
if (typeof BroadcastChannel !== 'undefined') {
|
|
10888
|
+
this.channel = new BroadcastChannel(STORAGE_CHANNEL);
|
|
10889
|
+
}
|
|
10890
|
+
}
|
|
10891
|
+
broadcastChange(event) {
|
|
10892
|
+
if (this.channel) {
|
|
10893
|
+
try {
|
|
10894
|
+
this.channel.postMessage(event);
|
|
10895
|
+
}
|
|
10896
|
+
catch (error) {
|
|
10897
|
+
console.warn('Failed to broadcast storage change:', error);
|
|
10898
|
+
}
|
|
10899
|
+
}
|
|
10900
|
+
}
|
|
10901
|
+
async setItem(key, value) {
|
|
10902
|
+
try {
|
|
10903
|
+
const serialized = JSON.stringify(value);
|
|
10904
|
+
localStorage.setItem(this.prefix + key, serialized);
|
|
10905
|
+
this.broadcastChange({
|
|
10906
|
+
type: 'set',
|
|
10907
|
+
key,
|
|
10908
|
+
value,
|
|
10909
|
+
timestamp: Date.now(),
|
|
10910
|
+
});
|
|
10911
|
+
}
|
|
10912
|
+
catch (error) {
|
|
10913
|
+
console.error('localStorage setItem failed:', error);
|
|
10914
|
+
throw error;
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
async getItem(key) {
|
|
10918
|
+
try {
|
|
10919
|
+
const stored = localStorage.getItem(this.prefix + key);
|
|
10920
|
+
if (!stored)
|
|
10921
|
+
return null;
|
|
10922
|
+
return JSON.parse(stored);
|
|
10923
|
+
}
|
|
10924
|
+
catch (error) {
|
|
10925
|
+
console.error('localStorage getItem failed:', error);
|
|
10926
|
+
return null;
|
|
10927
|
+
}
|
|
10928
|
+
}
|
|
10929
|
+
async removeItem(key) {
|
|
10930
|
+
try {
|
|
10931
|
+
localStorage.removeItem(this.prefix + key);
|
|
10932
|
+
this.broadcastChange({
|
|
10933
|
+
type: 'remove',
|
|
10934
|
+
key,
|
|
10935
|
+
timestamp: Date.now(),
|
|
10936
|
+
});
|
|
10937
|
+
}
|
|
10938
|
+
catch (error) {
|
|
10939
|
+
console.error('localStorage removeItem failed:', error);
|
|
10940
|
+
throw error;
|
|
10941
|
+
}
|
|
10942
|
+
}
|
|
10943
|
+
async clear() {
|
|
10944
|
+
try {
|
|
10945
|
+
const keys = Object.keys(localStorage);
|
|
10946
|
+
keys.forEach(key => {
|
|
10947
|
+
if (key.startsWith(this.prefix)) {
|
|
10948
|
+
localStorage.removeItem(key);
|
|
10949
|
+
}
|
|
10950
|
+
});
|
|
10951
|
+
this.broadcastChange({
|
|
10952
|
+
type: 'clear',
|
|
10953
|
+
timestamp: Date.now(),
|
|
10954
|
+
});
|
|
10955
|
+
}
|
|
10956
|
+
catch (error) {
|
|
10957
|
+
console.error('localStorage clear failed:', error);
|
|
10958
|
+
throw error;
|
|
10959
|
+
}
|
|
10960
|
+
}
|
|
10961
|
+
async isAvailable() {
|
|
10962
|
+
try {
|
|
10963
|
+
const testKey = '__smartlinks_test__';
|
|
10964
|
+
localStorage.setItem(testKey, 'test');
|
|
10965
|
+
localStorage.removeItem(testKey);
|
|
10966
|
+
return true;
|
|
10967
|
+
}
|
|
10968
|
+
catch {
|
|
10969
|
+
return false;
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10972
|
+
getBackend() {
|
|
10973
|
+
return 'localstorage';
|
|
10974
|
+
}
|
|
10975
|
+
destroy() {
|
|
10976
|
+
if (this.channel) {
|
|
10977
|
+
this.channel.close();
|
|
10978
|
+
this.channel = null;
|
|
10979
|
+
}
|
|
10980
|
+
}
|
|
10981
|
+
}
|
|
10982
|
+
/**
|
|
10983
|
+
* In-Memory Storage Implementation (No Persistence)
|
|
10984
|
+
*/
|
|
10985
|
+
class InMemoryStorage {
|
|
10986
|
+
constructor() {
|
|
10987
|
+
this.storage = new Map();
|
|
10988
|
+
}
|
|
10989
|
+
async setItem(key, value) {
|
|
10990
|
+
this.storage.set(key, value);
|
|
10991
|
+
}
|
|
10992
|
+
async getItem(key) {
|
|
10993
|
+
return this.storage.has(key) ? this.storage.get(key) : null;
|
|
10994
|
+
}
|
|
10995
|
+
async removeItem(key) {
|
|
10996
|
+
this.storage.delete(key);
|
|
10997
|
+
}
|
|
10998
|
+
async clear() {
|
|
10999
|
+
this.storage.clear();
|
|
11000
|
+
}
|
|
11001
|
+
async isAvailable() {
|
|
11002
|
+
return true;
|
|
11003
|
+
}
|
|
11004
|
+
getBackend() {
|
|
11005
|
+
return 'none';
|
|
11006
|
+
}
|
|
11007
|
+
destroy() {
|
|
11008
|
+
this.storage.clear();
|
|
11009
|
+
}
|
|
11010
|
+
}
|
|
11011
|
+
/**
|
|
11012
|
+
* Storage Factory - Creates appropriate storage backend with automatic fallback
|
|
11013
|
+
*/
|
|
11014
|
+
class StorageFactory {
|
|
11015
|
+
static async createStorage() {
|
|
11016
|
+
if (this.instance) {
|
|
11017
|
+
return this.instance;
|
|
11018
|
+
}
|
|
11019
|
+
// Try IndexedDB first
|
|
11020
|
+
try {
|
|
11021
|
+
const indexedDB = new IndexedDBStorage();
|
|
11022
|
+
const available = await indexedDB.isAvailable();
|
|
11023
|
+
if (available) {
|
|
11024
|
+
console.log('[PersistentStorage] Using IndexedDB backend');
|
|
11025
|
+
this.instance = indexedDB;
|
|
11026
|
+
return indexedDB;
|
|
11027
|
+
}
|
|
11028
|
+
indexedDB.destroy();
|
|
11029
|
+
}
|
|
11030
|
+
catch (error) {
|
|
11031
|
+
console.warn('[PersistentStorage] IndexedDB failed:', error);
|
|
11032
|
+
}
|
|
11033
|
+
// Fallback to localStorage
|
|
11034
|
+
try {
|
|
11035
|
+
const localStorage = new LocalStorageAdapter();
|
|
11036
|
+
const available = await localStorage.isAvailable();
|
|
11037
|
+
if (available) {
|
|
11038
|
+
console.log('[PersistentStorage] Using localStorage backend (fallback)');
|
|
11039
|
+
this.instance = localStorage;
|
|
11040
|
+
return localStorage;
|
|
11041
|
+
}
|
|
11042
|
+
localStorage.destroy();
|
|
11043
|
+
}
|
|
11044
|
+
catch (error) {
|
|
11045
|
+
console.warn('[PersistentStorage] localStorage failed:', error);
|
|
11046
|
+
}
|
|
11047
|
+
// Final fallback to in-memory (no persistence)
|
|
11048
|
+
console.warn('[PersistentStorage] Using in-memory storage (no persistence)');
|
|
11049
|
+
this.instance = new InMemoryStorage();
|
|
11050
|
+
return this.instance;
|
|
11051
|
+
}
|
|
11052
|
+
static getInstance() {
|
|
11053
|
+
return this.instance;
|
|
11054
|
+
}
|
|
11055
|
+
static reset() {
|
|
11056
|
+
if (this.instance && 'destroy' in this.instance) {
|
|
11057
|
+
this.instance.destroy();
|
|
11058
|
+
}
|
|
11059
|
+
this.instance = null;
|
|
11060
|
+
}
|
|
11061
|
+
}
|
|
11062
|
+
StorageFactory.instance = null;
|
|
11063
|
+
/**
|
|
11064
|
+
* Main storage export - creates singleton storage instance
|
|
11065
|
+
*/
|
|
11066
|
+
let storageInstance = null;
|
|
11067
|
+
let storageInitPromise = null;
|
|
11068
|
+
async function getStorage() {
|
|
11069
|
+
if (storageInstance) {
|
|
11070
|
+
return storageInstance;
|
|
11071
|
+
}
|
|
11072
|
+
if (!storageInitPromise) {
|
|
11073
|
+
storageInitPromise = StorageFactory.createStorage();
|
|
11074
|
+
}
|
|
11075
|
+
storageInstance = await storageInitPromise;
|
|
11076
|
+
return storageInstance;
|
|
11077
|
+
}
|
|
11078
|
+
/**
|
|
11079
|
+
* Listen for storage changes from other tabs
|
|
11080
|
+
*/
|
|
11081
|
+
function onStorageChange(callback) {
|
|
11082
|
+
if (typeof BroadcastChannel === 'undefined') {
|
|
11083
|
+
console.warn('BroadcastChannel not supported, cross-tab sync disabled');
|
|
11084
|
+
return () => { };
|
|
11085
|
+
}
|
|
11086
|
+
const channel = new BroadcastChannel(STORAGE_CHANNEL);
|
|
11087
|
+
channel.onmessage = (event) => {
|
|
11088
|
+
callback(event.data);
|
|
11089
|
+
};
|
|
11090
|
+
return () => {
|
|
11091
|
+
channel.close();
|
|
11092
|
+
};
|
|
11093
|
+
}
|
|
11094
|
+
|
|
11095
|
+
const TOKEN_KEY = 'token';
|
|
11096
|
+
const USER_KEY = 'user';
|
|
11097
|
+
const ACCOUNT_DATA_KEY = 'account_data';
|
|
11098
|
+
const ACCOUNT_INFO_KEY = 'account_info';
|
|
11099
|
+
const ACCOUNT_INFO_TTL = 5 * 60 * 1000; // 5 minutes default
|
|
11100
|
+
/**
|
|
11101
|
+
* Token Storage Layer
|
|
11102
|
+
*
|
|
11103
|
+
* Manages authentication tokens, user data, and account data using
|
|
11104
|
+
* the persistent storage layer (IndexedDB + localStorage fallback).
|
|
11105
|
+
* All methods are async to support IndexedDB operations.
|
|
11106
|
+
*/
|
|
10699
11107
|
const tokenStorage = {
|
|
10700
|
-
saveToken(token, expiresAt) {
|
|
11108
|
+
async saveToken(token, expiresAt) {
|
|
11109
|
+
const storage = await getStorage();
|
|
10701
11110
|
const authToken = {
|
|
10702
11111
|
token,
|
|
10703
11112
|
expiresAt: expiresAt || Date.now() + 3600000, // Default 1 hour
|
|
10704
11113
|
};
|
|
10705
|
-
|
|
11114
|
+
await storage.setItem(TOKEN_KEY, authToken);
|
|
10706
11115
|
},
|
|
10707
|
-
getToken() {
|
|
10708
|
-
const
|
|
10709
|
-
|
|
11116
|
+
async getToken() {
|
|
11117
|
+
const storage = await getStorage();
|
|
11118
|
+
const authToken = await storage.getItem(TOKEN_KEY);
|
|
11119
|
+
if (!authToken)
|
|
10710
11120
|
return null;
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
if (authToken.expiresAt && authToken.expiresAt < Date.now()) {
|
|
10715
|
-
this.clearToken();
|
|
10716
|
-
return null;
|
|
10717
|
-
}
|
|
10718
|
-
return authToken;
|
|
10719
|
-
}
|
|
10720
|
-
catch {
|
|
11121
|
+
// Check if token is expired
|
|
11122
|
+
if (authToken.expiresAt && authToken.expiresAt < Date.now()) {
|
|
11123
|
+
await this.clearToken();
|
|
10721
11124
|
return null;
|
|
10722
11125
|
}
|
|
11126
|
+
return authToken;
|
|
10723
11127
|
},
|
|
10724
|
-
clearToken() {
|
|
10725
|
-
|
|
11128
|
+
async clearToken() {
|
|
11129
|
+
const storage = await getStorage();
|
|
11130
|
+
await storage.removeItem(TOKEN_KEY);
|
|
10726
11131
|
},
|
|
10727
|
-
saveUser(user) {
|
|
10728
|
-
|
|
11132
|
+
async saveUser(user) {
|
|
11133
|
+
const storage = await getStorage();
|
|
11134
|
+
await storage.setItem(USER_KEY, user);
|
|
10729
11135
|
},
|
|
10730
|
-
getUser() {
|
|
10731
|
-
const
|
|
10732
|
-
|
|
10733
|
-
return null;
|
|
10734
|
-
try {
|
|
10735
|
-
return JSON.parse(stored);
|
|
10736
|
-
}
|
|
10737
|
-
catch {
|
|
10738
|
-
return null;
|
|
10739
|
-
}
|
|
11136
|
+
async getUser() {
|
|
11137
|
+
const storage = await getStorage();
|
|
11138
|
+
return await storage.getItem(USER_KEY);
|
|
10740
11139
|
},
|
|
10741
|
-
clearUser() {
|
|
10742
|
-
|
|
11140
|
+
async clearUser() {
|
|
11141
|
+
const storage = await getStorage();
|
|
11142
|
+
await storage.removeItem(USER_KEY);
|
|
10743
11143
|
},
|
|
10744
|
-
clearAll() {
|
|
10745
|
-
this.clearToken();
|
|
10746
|
-
this.clearUser();
|
|
10747
|
-
this.clearAccountData();
|
|
11144
|
+
async clearAll() {
|
|
11145
|
+
await this.clearToken();
|
|
11146
|
+
await this.clearUser();
|
|
11147
|
+
await this.clearAccountData();
|
|
11148
|
+
await this.clearAccountInfo();
|
|
10748
11149
|
},
|
|
10749
|
-
saveAccountData(data) {
|
|
10750
|
-
|
|
11150
|
+
async saveAccountData(data) {
|
|
11151
|
+
const storage = await getStorage();
|
|
11152
|
+
await storage.setItem(ACCOUNT_DATA_KEY, data);
|
|
10751
11153
|
},
|
|
10752
|
-
getAccountData() {
|
|
10753
|
-
const
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
11154
|
+
async getAccountData() {
|
|
11155
|
+
const storage = await getStorage();
|
|
11156
|
+
return await storage.getItem(ACCOUNT_DATA_KEY);
|
|
11157
|
+
},
|
|
11158
|
+
async clearAccountData() {
|
|
11159
|
+
const storage = await getStorage();
|
|
11160
|
+
await storage.removeItem(ACCOUNT_DATA_KEY);
|
|
11161
|
+
},
|
|
11162
|
+
async saveAccountInfo(accountInfo, ttl = ACCOUNT_INFO_TTL) {
|
|
11163
|
+
const storage = await getStorage();
|
|
11164
|
+
const cachedData = {
|
|
11165
|
+
data: accountInfo,
|
|
11166
|
+
cachedAt: Date.now(),
|
|
11167
|
+
expiresAt: Date.now() + ttl,
|
|
11168
|
+
};
|
|
11169
|
+
await storage.setItem(ACCOUNT_INFO_KEY, cachedData);
|
|
11170
|
+
},
|
|
11171
|
+
async getAccountInfo() {
|
|
11172
|
+
const storage = await getStorage();
|
|
11173
|
+
const cached = await storage.getItem(ACCOUNT_INFO_KEY);
|
|
11174
|
+
if (!cached)
|
|
10760
11175
|
return null;
|
|
10761
|
-
|
|
11176
|
+
const isStale = cached.expiresAt < Date.now();
|
|
11177
|
+
return {
|
|
11178
|
+
data: cached.data,
|
|
11179
|
+
isStale,
|
|
11180
|
+
};
|
|
10762
11181
|
},
|
|
10763
|
-
|
|
10764
|
-
|
|
11182
|
+
async clearAccountInfo() {
|
|
11183
|
+
const storage = await getStorage();
|
|
11184
|
+
await storage.removeItem(ACCOUNT_INFO_KEY);
|
|
10765
11185
|
},
|
|
10766
11186
|
};
|
|
10767
11187
|
|
|
10768
11188
|
const AuthContext = React.createContext(undefined);
|
|
10769
|
-
const AuthProvider = ({ children }) => {
|
|
11189
|
+
const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
|
|
10770
11190
|
const [user, setUser] = React.useState(null);
|
|
10771
11191
|
const [token, setToken] = React.useState(null);
|
|
10772
11192
|
const [accountData, setAccountData] = React.useState(null);
|
|
11193
|
+
const [accountInfo, setAccountInfo] = React.useState(null);
|
|
10773
11194
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
10774
|
-
|
|
11195
|
+
const callbacksRef = React.useRef(new Set());
|
|
11196
|
+
// Notify all subscribers of auth state changes
|
|
11197
|
+
const notifyAuthStateChange = React.useCallback((type, currentUser, currentToken, currentAccountData, currentAccountInfo) => {
|
|
11198
|
+
callbacksRef.current.forEach(callback => {
|
|
11199
|
+
try {
|
|
11200
|
+
callback({
|
|
11201
|
+
type,
|
|
11202
|
+
user: currentUser,
|
|
11203
|
+
token: currentToken,
|
|
11204
|
+
accountData: currentAccountData,
|
|
11205
|
+
accountInfo: currentAccountInfo
|
|
11206
|
+
});
|
|
11207
|
+
}
|
|
11208
|
+
catch (error) {
|
|
11209
|
+
console.error('[AuthContext] Error in auth state change callback:', error);
|
|
11210
|
+
}
|
|
11211
|
+
});
|
|
11212
|
+
}, []);
|
|
11213
|
+
// Initialize auth state from persistent storage
|
|
11214
|
+
React.useEffect(() => {
|
|
11215
|
+
const initializeAuth = async () => {
|
|
11216
|
+
try {
|
|
11217
|
+
const storedToken = await tokenStorage.getToken();
|
|
11218
|
+
const storedUser = await tokenStorage.getUser();
|
|
11219
|
+
const storedAccountData = await tokenStorage.getAccountData();
|
|
11220
|
+
if (storedToken && storedUser) {
|
|
11221
|
+
setToken(storedToken.token);
|
|
11222
|
+
setUser(storedUser);
|
|
11223
|
+
setAccountData(storedAccountData);
|
|
11224
|
+
// Set bearer token in global Smartlinks SDK via auth.verifyToken
|
|
11225
|
+
smartlinks__namespace.auth.verifyToken(storedToken.token).catch(err => {
|
|
11226
|
+
console.warn('Failed to restore bearer token on init:', err);
|
|
11227
|
+
});
|
|
11228
|
+
}
|
|
11229
|
+
// Load cached account info if available
|
|
11230
|
+
const cachedAccountInfo = await tokenStorage.getAccountInfo();
|
|
11231
|
+
if (cachedAccountInfo && !cachedAccountInfo.isStale) {
|
|
11232
|
+
setAccountInfo(cachedAccountInfo.data);
|
|
11233
|
+
}
|
|
11234
|
+
}
|
|
11235
|
+
catch (error) {
|
|
11236
|
+
console.error('Failed to initialize auth from storage:', error);
|
|
11237
|
+
}
|
|
11238
|
+
finally {
|
|
11239
|
+
setIsLoading(false);
|
|
11240
|
+
}
|
|
11241
|
+
};
|
|
11242
|
+
initializeAuth();
|
|
11243
|
+
}, []);
|
|
11244
|
+
// Cross-tab synchronization - listen for auth changes in other tabs
|
|
10775
11245
|
React.useEffect(() => {
|
|
10776
|
-
|
|
10777
|
-
const
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
11246
|
+
console.log('[AuthContext] Setting up cross-tab synchronization');
|
|
11247
|
+
const unsubscribe = onStorageChange(async (event) => {
|
|
11248
|
+
console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
|
|
11249
|
+
try {
|
|
11250
|
+
if (event.type === 'clear') {
|
|
11251
|
+
// Another tab cleared all storage (logout)
|
|
11252
|
+
console.log('[AuthContext] Detected logout in another tab');
|
|
11253
|
+
setToken(null);
|
|
11254
|
+
setUser(null);
|
|
11255
|
+
setAccountData(null);
|
|
11256
|
+
setAccountInfo(null);
|
|
11257
|
+
smartlinks__namespace.auth.logout();
|
|
11258
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
|
|
11259
|
+
}
|
|
11260
|
+
else if (event.type === 'remove' && (event.key === 'token' || event.key === 'user')) {
|
|
11261
|
+
// Another tab removed token or user (logout)
|
|
11262
|
+
console.log('[AuthContext] Detected token/user removal in another tab');
|
|
11263
|
+
setToken(null);
|
|
11264
|
+
setUser(null);
|
|
11265
|
+
setAccountData(null);
|
|
11266
|
+
setAccountInfo(null);
|
|
11267
|
+
smartlinks__namespace.auth.logout();
|
|
11268
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
|
|
11269
|
+
}
|
|
11270
|
+
else if (event.type === 'set' && event.key === 'token') {
|
|
11271
|
+
// Another tab set a new token (login)
|
|
11272
|
+
console.log('[AuthContext] Detected login in another tab');
|
|
11273
|
+
const storedToken = await tokenStorage.getToken();
|
|
11274
|
+
const storedUser = await tokenStorage.getUser();
|
|
11275
|
+
const storedAccountData = await tokenStorage.getAccountData();
|
|
11276
|
+
if (storedToken && storedUser) {
|
|
11277
|
+
setToken(storedToken.token);
|
|
11278
|
+
setUser(storedUser);
|
|
11279
|
+
setAccountData(storedAccountData);
|
|
11280
|
+
// Set bearer token in global Smartlinks SDK
|
|
11281
|
+
smartlinks__namespace.auth.verifyToken(storedToken.token).catch(err => {
|
|
11282
|
+
console.warn('[AuthContext] Failed to restore bearer token from cross-tab sync:', err);
|
|
11283
|
+
});
|
|
11284
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData);
|
|
11285
|
+
}
|
|
11286
|
+
}
|
|
11287
|
+
else if (event.type === 'set' && event.key === 'account_info') {
|
|
11288
|
+
// Another tab fetched fresh account info
|
|
11289
|
+
const cached = await tokenStorage.getAccountInfo();
|
|
11290
|
+
if (cached && !cached.isStale) {
|
|
11291
|
+
setAccountInfo(cached.data);
|
|
11292
|
+
console.log('[AuthContext] Account info synced from another tab');
|
|
11293
|
+
}
|
|
11294
|
+
}
|
|
11295
|
+
}
|
|
11296
|
+
catch (error) {
|
|
11297
|
+
console.error('[AuthContext] Error handling cross-tab sync:', error);
|
|
11298
|
+
}
|
|
11299
|
+
});
|
|
11300
|
+
return () => {
|
|
11301
|
+
console.log('[AuthContext] Cleaning up cross-tab synchronization');
|
|
11302
|
+
unsubscribe();
|
|
11303
|
+
};
|
|
11304
|
+
}, [notifyAuthStateChange]);
|
|
11305
|
+
const login = React.useCallback(async (authToken, authUser, authAccountData) => {
|
|
11306
|
+
try {
|
|
11307
|
+
// Store token, user, and account data
|
|
11308
|
+
await tokenStorage.saveToken(authToken);
|
|
11309
|
+
await tokenStorage.saveUser(authUser);
|
|
11310
|
+
if (authAccountData) {
|
|
11311
|
+
await tokenStorage.saveAccountData(authAccountData);
|
|
11312
|
+
}
|
|
11313
|
+
setToken(authToken);
|
|
11314
|
+
setUser(authUser);
|
|
11315
|
+
setAccountData(authAccountData || null);
|
|
10783
11316
|
// Set bearer token in global Smartlinks SDK via auth.verifyToken
|
|
10784
|
-
|
|
10785
|
-
|
|
11317
|
+
// This both validates the token and sets it for future API calls
|
|
11318
|
+
smartlinks__namespace.auth.verifyToken(authToken).catch(err => {
|
|
11319
|
+
console.warn('Failed to set bearer token on login:', err);
|
|
10786
11320
|
});
|
|
11321
|
+
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null);
|
|
11322
|
+
// Optionally preload account info on login
|
|
11323
|
+
if (preloadAccountInfo) {
|
|
11324
|
+
// Preload after login completes (non-blocking)
|
|
11325
|
+
getAccount(true).catch(error => {
|
|
11326
|
+
console.warn('[AuthContext] Failed to preload account info:', error);
|
|
11327
|
+
});
|
|
11328
|
+
}
|
|
10787
11329
|
}
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
tokenStorage.saveUser(authUser);
|
|
10794
|
-
if (authAccountData) {
|
|
10795
|
-
tokenStorage.saveAccountData(authAccountData);
|
|
10796
|
-
}
|
|
10797
|
-
setToken(authToken);
|
|
10798
|
-
setUser(authUser);
|
|
10799
|
-
setAccountData(authAccountData || null);
|
|
10800
|
-
// Set bearer token in global Smartlinks SDK via auth.verifyToken
|
|
10801
|
-
// This both validates the token and sets it for future API calls
|
|
10802
|
-
smartlinks__namespace.auth.verifyToken(authToken).catch(err => {
|
|
10803
|
-
console.warn('Failed to set bearer token on login:', err);
|
|
10804
|
-
});
|
|
10805
|
-
}, []);
|
|
11330
|
+
catch (error) {
|
|
11331
|
+
console.error('Failed to save auth data to storage:', error);
|
|
11332
|
+
throw error;
|
|
11333
|
+
}
|
|
11334
|
+
}, [notifyAuthStateChange, preloadAccountInfo]);
|
|
10806
11335
|
const logout = React.useCallback(async () => {
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
11336
|
+
try {
|
|
11337
|
+
// Clear persistent storage
|
|
11338
|
+
await tokenStorage.clearAll();
|
|
11339
|
+
setToken(null);
|
|
11340
|
+
setUser(null);
|
|
11341
|
+
setAccountData(null);
|
|
11342
|
+
setAccountInfo(null);
|
|
11343
|
+
// Clear bearer token from global Smartlinks SDK
|
|
11344
|
+
smartlinks__namespace.auth.logout();
|
|
11345
|
+
notifyAuthStateChange('LOGOUT', null, null, null);
|
|
11346
|
+
}
|
|
11347
|
+
catch (error) {
|
|
11348
|
+
console.error('Failed to clear auth data from storage:', error);
|
|
11349
|
+
}
|
|
11350
|
+
}, [notifyAuthStateChange]);
|
|
11351
|
+
const getToken = React.useCallback(async () => {
|
|
11352
|
+
const storedToken = await tokenStorage.getToken();
|
|
10817
11353
|
return storedToken ? storedToken.token : null;
|
|
10818
11354
|
}, []);
|
|
10819
11355
|
const refreshToken = React.useCallback(async () => {
|
|
10820
11356
|
throw new Error('Token refresh must be implemented via your backend API');
|
|
10821
11357
|
}, []);
|
|
11358
|
+
// Get account with intelligent caching
|
|
11359
|
+
const getAccount = React.useCallback(async (forceRefresh = false) => {
|
|
11360
|
+
try {
|
|
11361
|
+
// Check if user is authenticated
|
|
11362
|
+
if (!token) {
|
|
11363
|
+
throw new Error('Not authenticated. Please login first.');
|
|
11364
|
+
}
|
|
11365
|
+
// Check cache unless force refresh
|
|
11366
|
+
if (!forceRefresh) {
|
|
11367
|
+
const cached = await tokenStorage.getAccountInfo();
|
|
11368
|
+
if (cached && !cached.isStale) {
|
|
11369
|
+
console.log('[AuthContext] Returning cached account info');
|
|
11370
|
+
return cached.data;
|
|
11371
|
+
}
|
|
11372
|
+
}
|
|
11373
|
+
// Fetch fresh data from API
|
|
11374
|
+
console.log('[AuthContext] Fetching fresh account info from API');
|
|
11375
|
+
const freshAccountInfo = await smartlinks__namespace.auth.getAccount();
|
|
11376
|
+
// Cache the fresh data
|
|
11377
|
+
await tokenStorage.saveAccountInfo(freshAccountInfo, accountCacheTTL);
|
|
11378
|
+
setAccountInfo(freshAccountInfo);
|
|
11379
|
+
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, accountData, freshAccountInfo);
|
|
11380
|
+
return freshAccountInfo;
|
|
11381
|
+
}
|
|
11382
|
+
catch (error) {
|
|
11383
|
+
console.error('[AuthContext] Failed to get account info:', error);
|
|
11384
|
+
// Fallback to stale cache if API fails
|
|
11385
|
+
const cached = await tokenStorage.getAccountInfo();
|
|
11386
|
+
if (cached) {
|
|
11387
|
+
console.warn('[AuthContext] Returning stale cached data due to API error');
|
|
11388
|
+
return cached.data;
|
|
11389
|
+
}
|
|
11390
|
+
throw error;
|
|
11391
|
+
}
|
|
11392
|
+
}, [token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
|
|
11393
|
+
// Convenience method for explicit refresh
|
|
11394
|
+
const refreshAccount = React.useCallback(async () => {
|
|
11395
|
+
return await getAccount(true);
|
|
11396
|
+
}, [getAccount]);
|
|
11397
|
+
// Clear account cache
|
|
11398
|
+
const clearAccountCache = React.useCallback(async () => {
|
|
11399
|
+
await tokenStorage.clearAccountInfo();
|
|
11400
|
+
setAccountInfo(null);
|
|
11401
|
+
}, []);
|
|
11402
|
+
const onAuthStateChange = React.useCallback((callback) => {
|
|
11403
|
+
callbacksRef.current.add(callback);
|
|
11404
|
+
// Return unsubscribe function
|
|
11405
|
+
return () => {
|
|
11406
|
+
callbacksRef.current.delete(callback);
|
|
11407
|
+
};
|
|
11408
|
+
}, []);
|
|
10822
11409
|
const value = {
|
|
10823
11410
|
user,
|
|
10824
11411
|
token,
|
|
10825
11412
|
accountData,
|
|
11413
|
+
accountInfo,
|
|
10826
11414
|
isAuthenticated: !!token && !!user,
|
|
10827
11415
|
isLoading,
|
|
10828
11416
|
login,
|
|
10829
11417
|
logout,
|
|
10830
11418
|
getToken,
|
|
10831
11419
|
refreshToken,
|
|
11420
|
+
getAccount,
|
|
11421
|
+
refreshAccount,
|
|
11422
|
+
clearAccountCache,
|
|
11423
|
+
onAuthStateChange,
|
|
10832
11424
|
};
|
|
10833
11425
|
return jsxRuntime.jsx(AuthContext.Provider, { value: value, children: children });
|
|
10834
11426
|
};
|
|
@@ -10840,996 +11432,1092 @@ const useAuth = () => {
|
|
|
10840
11432
|
return context;
|
|
10841
11433
|
};
|
|
10842
11434
|
|
|
10843
|
-
|
|
10844
|
-
|
|
11435
|
+
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
11436
|
+
const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
|
|
11437
|
+
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, minimal = false, }) => {
|
|
11438
|
+
const [mode, setMode] = React.useState(initialMode);
|
|
10845
11439
|
const [loading, setLoading] = React.useState(false);
|
|
10846
|
-
const [profile, setProfile] = React.useState(null);
|
|
10847
11440
|
const [error, setError] = React.useState();
|
|
10848
|
-
const [
|
|
10849
|
-
|
|
10850
|
-
const [
|
|
10851
|
-
|
|
10852
|
-
const [
|
|
10853
|
-
|
|
10854
|
-
const [
|
|
10855
|
-
const [
|
|
10856
|
-
|
|
10857
|
-
const [
|
|
10858
|
-
const [
|
|
10859
|
-
const
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
const [phoneCodeSent, setPhoneCodeSent] = React.useState(false);
|
|
10864
|
-
// Account deletion state
|
|
10865
|
-
const [deletePassword, setDeletePassword] = React.useState('');
|
|
10866
|
-
const [deleteConfirmText, setDeleteConfirmText] = React.useState('');
|
|
10867
|
-
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
10868
|
-
const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, } = customization;
|
|
10869
|
-
// Reinitialize Smartlinks SDK when apiEndpoint changes
|
|
11441
|
+
const [resetSuccess, setResetSuccess] = React.useState(false);
|
|
11442
|
+
const [authSuccess, setAuthSuccess] = React.useState(false);
|
|
11443
|
+
const [successMessage, setSuccessMessage] = React.useState();
|
|
11444
|
+
const [showResendVerification, setShowResendVerification] = React.useState(false);
|
|
11445
|
+
const [resendEmail, setResendEmail] = React.useState();
|
|
11446
|
+
const [showRequestNewReset, setShowRequestNewReset] = React.useState(false);
|
|
11447
|
+
const [resetRequestEmail, setResetRequestEmail] = React.useState();
|
|
11448
|
+
const [resetToken, setResetToken] = React.useState(); // Store the reset token from URL
|
|
11449
|
+
const [config, setConfig] = React.useState(null);
|
|
11450
|
+
const [configLoading, setConfigLoading] = React.useState(!skipConfigFetch);
|
|
11451
|
+
const [showEmailForm, setShowEmailForm] = React.useState(false); // Track if email form should be shown when emailDisplayMode is 'button'
|
|
11452
|
+
const api = new AuthAPI(apiEndpoint, clientId, clientName);
|
|
11453
|
+
const auth = useAuth();
|
|
11454
|
+
// Reinitialize Smartlinks SDK when apiEndpoint changes (for test/dev scenarios)
|
|
11455
|
+
// IMPORTANT: Preserve bearer token during reinitialization
|
|
10870
11456
|
React.useEffect(() => {
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
11457
|
+
const reinitializeWithToken = async () => {
|
|
11458
|
+
if (apiEndpoint) {
|
|
11459
|
+
// Get current token before reinitializing
|
|
11460
|
+
const currentToken = await auth.getToken();
|
|
11461
|
+
smartlinks__namespace.initializeApi({
|
|
11462
|
+
baseURL: apiEndpoint,
|
|
11463
|
+
proxyMode: false, // Direct API calls when custom endpoint is provided
|
|
11464
|
+
ngrokSkipBrowserWarning: true,
|
|
11465
|
+
});
|
|
11466
|
+
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11467
|
+
if (currentToken) {
|
|
11468
|
+
smartlinks__namespace.auth.verifyToken(currentToken).catch(err => {
|
|
11469
|
+
console.warn('Failed to restore bearer token after reinit:', err);
|
|
11470
|
+
});
|
|
11471
|
+
}
|
|
11472
|
+
}
|
|
11473
|
+
};
|
|
11474
|
+
reinitializeWithToken();
|
|
11475
|
+
}, [apiEndpoint, auth]);
|
|
11476
|
+
// Get the effective redirect URL (use prop or default to current page)
|
|
11477
|
+
const getRedirectUrl = () => {
|
|
11478
|
+
if (redirectUrl)
|
|
11479
|
+
return redirectUrl;
|
|
11480
|
+
// Get the full current URL including hash routes
|
|
11481
|
+
// Remove any existing query parameters to avoid duplication
|
|
11482
|
+
const currentUrl = window.location.href.split('?')[0];
|
|
11483
|
+
return currentUrl;
|
|
11484
|
+
};
|
|
11485
|
+
// Fetch UI configuration
|
|
10880
11486
|
React.useEffect(() => {
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
const loadProfile = async () => {
|
|
10884
|
-
if (!auth.isAuthenticated) {
|
|
10885
|
-
setError('You must be logged in to manage your account');
|
|
11487
|
+
if (skipConfigFetch) {
|
|
11488
|
+
setConfig(customization || {});
|
|
10886
11489
|
return;
|
|
10887
11490
|
}
|
|
11491
|
+
const fetchConfig = async () => {
|
|
11492
|
+
try {
|
|
11493
|
+
// Check localStorage cache first
|
|
11494
|
+
const cacheKey = `auth_ui_config_${clientId || 'default'}`;
|
|
11495
|
+
const cached = localStorage.getItem(cacheKey);
|
|
11496
|
+
if (cached) {
|
|
11497
|
+
const { config: cachedConfig, timestamp } = JSON.parse(cached);
|
|
11498
|
+
const age = Date.now() - timestamp;
|
|
11499
|
+
// Use cache if less than 1 hour old
|
|
11500
|
+
if (age < 3600000) {
|
|
11501
|
+
setConfig({ ...cachedConfig, ...customization });
|
|
11502
|
+
setConfigLoading(false);
|
|
11503
|
+
// Fetch in background to update cache
|
|
11504
|
+
api.fetchConfig().then(freshConfig => {
|
|
11505
|
+
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11506
|
+
config: freshConfig,
|
|
11507
|
+
timestamp: Date.now()
|
|
11508
|
+
}));
|
|
11509
|
+
});
|
|
11510
|
+
return;
|
|
11511
|
+
}
|
|
11512
|
+
}
|
|
11513
|
+
// Fetch from API
|
|
11514
|
+
const fetchedConfig = await api.fetchConfig();
|
|
11515
|
+
// Merge with customization props (props take precedence)
|
|
11516
|
+
const mergedConfig = { ...fetchedConfig, ...customization };
|
|
11517
|
+
setConfig(mergedConfig);
|
|
11518
|
+
// Cache the fetched config
|
|
11519
|
+
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11520
|
+
config: fetchedConfig,
|
|
11521
|
+
timestamp: Date.now()
|
|
11522
|
+
}));
|
|
11523
|
+
}
|
|
11524
|
+
catch (err) {
|
|
11525
|
+
console.error('Failed to fetch config:', err);
|
|
11526
|
+
setConfig(customization || {});
|
|
11527
|
+
}
|
|
11528
|
+
finally {
|
|
11529
|
+
setConfigLoading(false);
|
|
11530
|
+
}
|
|
11531
|
+
};
|
|
11532
|
+
fetchConfig();
|
|
11533
|
+
}, [apiEndpoint, clientId, customization, skipConfigFetch]);
|
|
11534
|
+
// Reset showEmailForm when mode changes away from login/register
|
|
11535
|
+
React.useEffect(() => {
|
|
11536
|
+
if (mode !== 'login' && mode !== 'register') {
|
|
11537
|
+
setShowEmailForm(false);
|
|
11538
|
+
}
|
|
11539
|
+
}, [mode]);
|
|
11540
|
+
// Handle URL-based auth flows (email verification, password reset)
|
|
11541
|
+
React.useEffect(() => {
|
|
11542
|
+
// Helper to get URL parameters from either hash or search
|
|
11543
|
+
const getUrlParams = () => {
|
|
11544
|
+
// First check if there are params in the hash (for hash routing)
|
|
11545
|
+
const hash = window.location.hash;
|
|
11546
|
+
const hashQueryIndex = hash.indexOf('?');
|
|
11547
|
+
if (hashQueryIndex !== -1) {
|
|
11548
|
+
// Extract query string from hash (e.g., #/test?mode=verifyEmail&token=abc)
|
|
11549
|
+
const hashQuery = hash.substring(hashQueryIndex + 1);
|
|
11550
|
+
return new URLSearchParams(hashQuery);
|
|
11551
|
+
}
|
|
11552
|
+
// Fall back to regular search params (for non-hash routing)
|
|
11553
|
+
return new URLSearchParams(window.location.search);
|
|
11554
|
+
};
|
|
11555
|
+
const params = getUrlParams();
|
|
11556
|
+
const urlMode = params.get('mode');
|
|
11557
|
+
const token = params.get('token');
|
|
11558
|
+
console.log('URL params detected:', { urlMode, token, hash: window.location.hash, search: window.location.search });
|
|
11559
|
+
if (urlMode && token) {
|
|
11560
|
+
handleURLBasedAuth(urlMode, token);
|
|
11561
|
+
}
|
|
11562
|
+
}, []);
|
|
11563
|
+
const handleURLBasedAuth = async (urlMode, token) => {
|
|
10888
11564
|
setLoading(true);
|
|
10889
11565
|
setError(undefined);
|
|
10890
11566
|
try {
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
11567
|
+
if (urlMode === 'verifyEmail') {
|
|
11568
|
+
console.log('Verifying email with token:', token);
|
|
11569
|
+
const response = await api.verifyEmailWithToken(token);
|
|
11570
|
+
// Get email verification mode from response or config
|
|
11571
|
+
const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
|
|
11572
|
+
if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
|
|
11573
|
+
// Auto-login modes: Log the user in immediately if token is provided
|
|
11574
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11575
|
+
setAuthSuccess(true);
|
|
11576
|
+
setSuccessMessage('Email verified successfully! You are now logged in.');
|
|
11577
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11578
|
+
// Clear the URL parameters
|
|
11579
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11580
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11581
|
+
// Redirect after a brief delay to show success message
|
|
11582
|
+
if (redirectUrl) {
|
|
11583
|
+
setTimeout(() => {
|
|
11584
|
+
window.location.href = redirectUrl;
|
|
11585
|
+
}, 2000);
|
|
11586
|
+
}
|
|
11587
|
+
}
|
|
11588
|
+
else {
|
|
11589
|
+
// verify-then-manual-login mode or no token: Show success but require manual login
|
|
11590
|
+
setAuthSuccess(true);
|
|
11591
|
+
setSuccessMessage('Email verified successfully! Please log in with your credentials.');
|
|
11592
|
+
// Clear the URL parameters
|
|
11593
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11594
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11595
|
+
// Switch back to login mode after a delay
|
|
11596
|
+
setTimeout(() => {
|
|
11597
|
+
setAuthSuccess(false);
|
|
11598
|
+
setMode('login');
|
|
11599
|
+
}, 3000);
|
|
11600
|
+
}
|
|
11601
|
+
}
|
|
11602
|
+
else if (urlMode === 'resetPassword') {
|
|
11603
|
+
console.log('Verifying reset token:', token);
|
|
11604
|
+
// Verify token is valid, then show password reset form
|
|
11605
|
+
await api.verifyResetToken(token);
|
|
11606
|
+
setResetToken(token); // Store token for use in password reset
|
|
11607
|
+
setMode('reset-password');
|
|
11608
|
+
// Clear the URL parameters
|
|
11609
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11610
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11611
|
+
}
|
|
11612
|
+
else if (urlMode === 'magicLink') {
|
|
11613
|
+
console.log('Verifying magic link token:', token);
|
|
11614
|
+
const response = await api.verifyMagicLink(token);
|
|
11615
|
+
// Auto-login with magic link if token is provided
|
|
11616
|
+
if (response.token) {
|
|
11617
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11618
|
+
setAuthSuccess(true);
|
|
11619
|
+
setSuccessMessage('Magic link verified! You are now logged in.');
|
|
11620
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11621
|
+
// Clear the URL parameters
|
|
11622
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11623
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11624
|
+
// Redirect after a brief delay to show success message
|
|
11625
|
+
if (redirectUrl) {
|
|
11626
|
+
setTimeout(() => {
|
|
11627
|
+
window.location.href = redirectUrl;
|
|
11628
|
+
}, 2000);
|
|
11629
|
+
}
|
|
11630
|
+
}
|
|
11631
|
+
else {
|
|
11632
|
+
throw new Error('Authentication failed - no token received');
|
|
11633
|
+
}
|
|
11634
|
+
}
|
|
10906
11635
|
}
|
|
10907
11636
|
catch (err) {
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
11637
|
+
console.error('URL-based auth error:', err);
|
|
11638
|
+
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
|
|
11639
|
+
// If it's an email verification error (expired/invalid token), show resend option
|
|
11640
|
+
if (urlMode === 'verifyEmail') {
|
|
11641
|
+
setError(`${errorMessage} - Please enter your email below to receive a new verification link.`);
|
|
11642
|
+
setShowResendVerification(true);
|
|
11643
|
+
setMode('login'); // Show the login form UI
|
|
11644
|
+
// Clear the URL parameters
|
|
11645
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11646
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11647
|
+
}
|
|
11648
|
+
else if (urlMode === 'resetPassword') {
|
|
11649
|
+
// If password reset token is invalid/expired, show request new reset link option
|
|
11650
|
+
setError(`${errorMessage} - Please enter your email below to receive a new password reset link.`);
|
|
11651
|
+
setShowRequestNewReset(true);
|
|
11652
|
+
setMode('login');
|
|
11653
|
+
// Clear the URL parameters
|
|
11654
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11655
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11656
|
+
}
|
|
11657
|
+
else if (urlMode === 'magicLink') {
|
|
11658
|
+
// If magic link is invalid/expired
|
|
11659
|
+
setError(`${errorMessage} - Please request a new magic link below.`);
|
|
11660
|
+
setMode('magic-link');
|
|
11661
|
+
// Clear the URL parameters
|
|
11662
|
+
const cleanUrl = window.location.href.split('?')[0];
|
|
11663
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
11664
|
+
}
|
|
11665
|
+
else {
|
|
11666
|
+
setError(errorMessage);
|
|
11667
|
+
}
|
|
11668
|
+
onAuthError?.(err instanceof Error ? err : new Error('An error occurred'));
|
|
10911
11669
|
}
|
|
10912
11670
|
finally {
|
|
10913
11671
|
setLoading(false);
|
|
10914
11672
|
}
|
|
10915
11673
|
};
|
|
10916
|
-
const
|
|
10917
|
-
e.preventDefault();
|
|
11674
|
+
const handleEmailAuth = async (data) => {
|
|
10918
11675
|
setLoading(true);
|
|
10919
11676
|
setError(undefined);
|
|
10920
|
-
|
|
11677
|
+
setAuthSuccess(false);
|
|
10921
11678
|
try {
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
//
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
11679
|
+
const response = mode === 'login'
|
|
11680
|
+
? await api.login(data.email, data.password)
|
|
11681
|
+
: await api.register({
|
|
11682
|
+
...data,
|
|
11683
|
+
accountData: mode === 'register' ? accountData : undefined,
|
|
11684
|
+
redirectUrl: getRedirectUrl(), // Include redirect URL for email verification
|
|
11685
|
+
});
|
|
11686
|
+
// Get email verification mode from response or config (default: verify-then-auto-login)
|
|
11687
|
+
const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
|
|
11688
|
+
const gracePeriodHours = config?.emailVerification?.gracePeriodHours || 24;
|
|
11689
|
+
if (mode === 'register') {
|
|
11690
|
+
// Handle different verification modes
|
|
11691
|
+
if (verificationMode === 'immediate' && response.token) {
|
|
11692
|
+
// Immediate mode: Log in right away if token is provided
|
|
11693
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11694
|
+
setAuthSuccess(true);
|
|
11695
|
+
const deadline = response.emailVerificationDeadline
|
|
11696
|
+
? new Date(response.emailVerificationDeadline).toLocaleString()
|
|
11697
|
+
: `${gracePeriodHours} hours`;
|
|
11698
|
+
setSuccessMessage(`Account created! You're logged in now. Please verify your email by ${deadline} to keep your account active.`);
|
|
11699
|
+
if (response.token) {
|
|
11700
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11701
|
+
}
|
|
11702
|
+
if (redirectUrl) {
|
|
11703
|
+
setTimeout(() => {
|
|
11704
|
+
window.location.href = redirectUrl;
|
|
11705
|
+
}, 2000);
|
|
11706
|
+
}
|
|
11707
|
+
}
|
|
11708
|
+
else if (verificationMode === 'verify-then-auto-login') {
|
|
11709
|
+
// Verify-then-auto-login mode: Don't log in yet, but will auto-login after email verification
|
|
11710
|
+
setAuthSuccess(true);
|
|
11711
|
+
setSuccessMessage('Account created! Please check your email and click the verification link to complete your registration.');
|
|
11712
|
+
}
|
|
11713
|
+
else {
|
|
11714
|
+
// verify-then-manual-login mode: Traditional flow
|
|
11715
|
+
setAuthSuccess(true);
|
|
11716
|
+
setSuccessMessage('Account created successfully! Please check your email to verify your account, then log in.');
|
|
11717
|
+
}
|
|
11718
|
+
}
|
|
11719
|
+
else {
|
|
11720
|
+
// Login mode - always log in if token is provided
|
|
11721
|
+
if (response.token) {
|
|
11722
|
+
// Check for account lock or verification requirements
|
|
11723
|
+
if (response.accountLocked) {
|
|
11724
|
+
throw new Error('Your account has been locked due to unverified email. Please check your email or request a new verification link.');
|
|
11725
|
+
}
|
|
11726
|
+
if (response.requiresEmailVerification) {
|
|
11727
|
+
throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
|
|
11728
|
+
}
|
|
11729
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11730
|
+
setAuthSuccess(true);
|
|
11731
|
+
setSuccessMessage('Login successful!');
|
|
11732
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11733
|
+
if (redirectUrl) {
|
|
11734
|
+
setTimeout(() => {
|
|
11735
|
+
window.location.href = redirectUrl;
|
|
11736
|
+
}, 2000);
|
|
11737
|
+
}
|
|
11738
|
+
}
|
|
11739
|
+
else {
|
|
11740
|
+
throw new Error('Authentication failed - please verify your email before logging in.');
|
|
11741
|
+
}
|
|
11742
|
+
}
|
|
10938
11743
|
}
|
|
10939
11744
|
catch (err) {
|
|
10940
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
10941
|
-
|
|
10942
|
-
|
|
11745
|
+
const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
|
|
11746
|
+
// Check if error is about email already registered
|
|
11747
|
+
if (mode === 'register' && errorMessage.toLowerCase().includes('already') && errorMessage.toLowerCase().includes('email')) {
|
|
11748
|
+
setShowResendVerification(true);
|
|
11749
|
+
setResendEmail(data.email);
|
|
11750
|
+
setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
|
|
11751
|
+
}
|
|
11752
|
+
else {
|
|
11753
|
+
setError(errorMessage);
|
|
11754
|
+
}
|
|
11755
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
10943
11756
|
}
|
|
10944
11757
|
finally {
|
|
10945
11758
|
setLoading(false);
|
|
10946
11759
|
}
|
|
10947
11760
|
};
|
|
10948
|
-
const
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
setNewEmail('');
|
|
10952
|
-
setEmailPassword('');
|
|
10953
|
-
setCurrentPassword('');
|
|
10954
|
-
setNewPassword('');
|
|
10955
|
-
setConfirmPassword('');
|
|
10956
|
-
setNewPhone('');
|
|
10957
|
-
setPhoneCode('');
|
|
10958
|
-
setPhoneCodeSent(false);
|
|
10959
|
-
setError(undefined);
|
|
10960
|
-
setSuccess(undefined);
|
|
10961
|
-
};
|
|
10962
|
-
const handleChangeEmail = async (e) => {
|
|
10963
|
-
e.preventDefault();
|
|
11761
|
+
const handleResendVerification = async () => {
|
|
11762
|
+
if (!resendEmail)
|
|
11763
|
+
return;
|
|
10964
11764
|
setLoading(true);
|
|
10965
11765
|
setError(undefined);
|
|
10966
|
-
setSuccess(undefined);
|
|
10967
11766
|
try {
|
|
10968
|
-
//
|
|
10969
|
-
//
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
console.log('Data:', { newEmail });
|
|
10975
|
-
// Uncomment when backend is ready:
|
|
10976
|
-
// await smartlinks.authKit.changeEmail(clientId, newEmail, emailPassword);
|
|
10977
|
-
// setSuccess('Email changed successfully!');
|
|
10978
|
-
// setEditingSection(null);
|
|
10979
|
-
// setNewEmail('');
|
|
10980
|
-
// setEmailPassword('');
|
|
10981
|
-
// onEmailChangeRequested?.();
|
|
10982
|
-
// await loadProfile(); // Reload to show new email
|
|
11767
|
+
// For resend, we need the userId. If we don't have it, we need to handle this differently
|
|
11768
|
+
// The backend should ideally handle this case
|
|
11769
|
+
await api.resendVerification('unknown', resendEmail, getRedirectUrl());
|
|
11770
|
+
setAuthSuccess(true);
|
|
11771
|
+
setSuccessMessage('Verification email sent! Please check your inbox.');
|
|
11772
|
+
setShowResendVerification(false);
|
|
10983
11773
|
}
|
|
10984
11774
|
catch (err) {
|
|
10985
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to
|
|
11775
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to resend verification email';
|
|
10986
11776
|
setError(errorMessage);
|
|
10987
|
-
|
|
11777
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
10988
11778
|
}
|
|
10989
11779
|
finally {
|
|
10990
11780
|
setLoading(false);
|
|
10991
11781
|
}
|
|
10992
11782
|
};
|
|
10993
|
-
const
|
|
10994
|
-
|
|
10995
|
-
if (newPassword !== confirmPassword) {
|
|
10996
|
-
setError('New passwords do not match');
|
|
10997
|
-
return;
|
|
10998
|
-
}
|
|
10999
|
-
if (newPassword.length < 6) {
|
|
11000
|
-
setError('Password must be at least 6 characters');
|
|
11783
|
+
const handleRequestNewReset = async () => {
|
|
11784
|
+
if (!resetRequestEmail)
|
|
11001
11785
|
return;
|
|
11002
|
-
}
|
|
11003
11786
|
setLoading(true);
|
|
11004
11787
|
setError(undefined);
|
|
11005
|
-
setSuccess(undefined);
|
|
11006
11788
|
try {
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
console.log('Data: currentPassword and newPassword provided');
|
|
11013
|
-
// Uncomment when backend is ready:
|
|
11014
|
-
// await smartlinks.authKit.changePassword(clientId, currentPassword, newPassword);
|
|
11015
|
-
// setSuccess('Password changed successfully!');
|
|
11016
|
-
// setEditingSection(null);
|
|
11017
|
-
// setCurrentPassword('');
|
|
11018
|
-
// setNewPassword('');
|
|
11019
|
-
// setConfirmPassword('');
|
|
11020
|
-
// onPasswordChanged?.();
|
|
11789
|
+
await api.requestPasswordReset(resetRequestEmail, getRedirectUrl());
|
|
11790
|
+
setAuthSuccess(true);
|
|
11791
|
+
setSuccessMessage('Password reset email sent! Please check your inbox.');
|
|
11792
|
+
setShowRequestNewReset(false);
|
|
11793
|
+
setResetRequestEmail('');
|
|
11021
11794
|
}
|
|
11022
11795
|
catch (err) {
|
|
11023
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to
|
|
11796
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to send password reset email';
|
|
11024
11797
|
setError(errorMessage);
|
|
11025
|
-
|
|
11798
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11026
11799
|
}
|
|
11027
11800
|
finally {
|
|
11028
11801
|
setLoading(false);
|
|
11029
11802
|
}
|
|
11030
11803
|
};
|
|
11031
|
-
const
|
|
11804
|
+
const handleGoogleLogin = async () => {
|
|
11805
|
+
// Use custom client ID from config, or fall back to default Smartlinks client ID
|
|
11806
|
+
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
11807
|
+
// Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
|
|
11808
|
+
const oauthFlow = config?.googleOAuthFlow || 'oneTap';
|
|
11032
11809
|
setLoading(true);
|
|
11033
11810
|
setError(undefined);
|
|
11034
11811
|
try {
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11812
|
+
const google = window.google;
|
|
11813
|
+
if (!google) {
|
|
11814
|
+
throw new Error('Google Identity Services not loaded. Please check your internet connection.');
|
|
11815
|
+
}
|
|
11816
|
+
if (oauthFlow === 'popup') {
|
|
11817
|
+
// Use OAuth2 popup flow (works in iframes but requires popup permission)
|
|
11818
|
+
if (!google.accounts.oauth2) {
|
|
11819
|
+
throw new Error('Google OAuth2 not available');
|
|
11820
|
+
}
|
|
11821
|
+
const client = google.accounts.oauth2.initTokenClient({
|
|
11822
|
+
client_id: googleClientId,
|
|
11823
|
+
scope: 'openid email profile',
|
|
11824
|
+
callback: async (response) => {
|
|
11825
|
+
try {
|
|
11826
|
+
if (response.error) {
|
|
11827
|
+
throw new Error(response.error_description || response.error);
|
|
11828
|
+
}
|
|
11829
|
+
const accessToken = response.access_token;
|
|
11830
|
+
// Send access token to backend
|
|
11831
|
+
const authResponse = await api.loginWithGoogle(accessToken);
|
|
11832
|
+
if (authResponse.token) {
|
|
11833
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11834
|
+
setAuthSuccess(true);
|
|
11835
|
+
setSuccessMessage('Google login successful!');
|
|
11836
|
+
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11837
|
+
}
|
|
11838
|
+
else {
|
|
11839
|
+
throw new Error('Authentication failed - no token received');
|
|
11840
|
+
}
|
|
11841
|
+
if (redirectUrl) {
|
|
11842
|
+
setTimeout(() => {
|
|
11843
|
+
window.location.href = redirectUrl;
|
|
11844
|
+
}, 2000);
|
|
11845
|
+
}
|
|
11846
|
+
setLoading(false);
|
|
11847
|
+
}
|
|
11848
|
+
catch (err) {
|
|
11849
|
+
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
11850
|
+
setError(errorMessage);
|
|
11851
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11852
|
+
setLoading(false);
|
|
11853
|
+
}
|
|
11854
|
+
},
|
|
11855
|
+
});
|
|
11856
|
+
client.requestAccessToken();
|
|
11857
|
+
}
|
|
11858
|
+
else {
|
|
11859
|
+
// Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
|
|
11860
|
+
google.accounts.id.initialize({
|
|
11861
|
+
client_id: googleClientId,
|
|
11862
|
+
callback: async (response) => {
|
|
11863
|
+
try {
|
|
11864
|
+
const idToken = response.credential;
|
|
11865
|
+
const authResponse = await api.loginWithGoogle(idToken);
|
|
11866
|
+
if (authResponse.token) {
|
|
11867
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11868
|
+
setAuthSuccess(true);
|
|
11869
|
+
setSuccessMessage('Google login successful!');
|
|
11870
|
+
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11871
|
+
}
|
|
11872
|
+
else {
|
|
11873
|
+
throw new Error('Authentication failed - no token received');
|
|
11874
|
+
}
|
|
11875
|
+
if (redirectUrl) {
|
|
11876
|
+
setTimeout(() => {
|
|
11877
|
+
window.location.href = redirectUrl;
|
|
11878
|
+
}, 2000);
|
|
11879
|
+
}
|
|
11880
|
+
setLoading(false);
|
|
11881
|
+
}
|
|
11882
|
+
catch (err) {
|
|
11883
|
+
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
11884
|
+
setError(errorMessage);
|
|
11885
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11886
|
+
setLoading(false);
|
|
11887
|
+
}
|
|
11888
|
+
},
|
|
11889
|
+
auto_select: false,
|
|
11890
|
+
cancel_on_tap_outside: true,
|
|
11891
|
+
});
|
|
11892
|
+
google.accounts.id.prompt((notification) => {
|
|
11893
|
+
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
|
|
11894
|
+
setLoading(false);
|
|
11895
|
+
}
|
|
11896
|
+
});
|
|
11897
|
+
}
|
|
11038
11898
|
}
|
|
11039
11899
|
catch (err) {
|
|
11040
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
11900
|
+
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
11041
11901
|
setError(errorMessage);
|
|
11042
|
-
|
|
11043
|
-
}
|
|
11044
|
-
finally {
|
|
11902
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11045
11903
|
setLoading(false);
|
|
11046
11904
|
}
|
|
11047
11905
|
};
|
|
11048
|
-
const
|
|
11049
|
-
e.preventDefault();
|
|
11906
|
+
const handlePhoneAuth = async (phoneNumber, verificationCode) => {
|
|
11050
11907
|
setLoading(true);
|
|
11051
11908
|
setError(undefined);
|
|
11052
|
-
setSuccess(undefined);
|
|
11053
11909
|
try {
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11910
|
+
if (!verificationCode) {
|
|
11911
|
+
// Send verification code via Twilio Verify Service
|
|
11912
|
+
await api.sendPhoneCode(phoneNumber);
|
|
11913
|
+
// Twilio Verify Service tracks the verification by phone number
|
|
11914
|
+
// No need to store verificationId
|
|
11915
|
+
}
|
|
11916
|
+
else {
|
|
11917
|
+
// Verify code - Twilio identifies the verification by phone number
|
|
11918
|
+
const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
|
|
11919
|
+
// Update auth context with account data if token is provided
|
|
11920
|
+
if (response.token) {
|
|
11921
|
+
auth.login(response.token, response.user, response.accountData);
|
|
11922
|
+
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11923
|
+
if (redirectUrl) {
|
|
11924
|
+
window.location.href = redirectUrl;
|
|
11925
|
+
}
|
|
11926
|
+
}
|
|
11927
|
+
else {
|
|
11928
|
+
throw new Error('Authentication failed - no token received');
|
|
11929
|
+
}
|
|
11930
|
+
}
|
|
11068
11931
|
}
|
|
11069
11932
|
catch (err) {
|
|
11070
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
11933
|
+
const errorMessage = err instanceof Error ? err.message : 'Phone authentication failed';
|
|
11071
11934
|
setError(errorMessage);
|
|
11072
|
-
|
|
11935
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11073
11936
|
}
|
|
11074
11937
|
finally {
|
|
11075
11938
|
setLoading(false);
|
|
11076
11939
|
}
|
|
11077
11940
|
};
|
|
11078
|
-
const
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11941
|
+
const handlePasswordReset = async (emailOrPassword, confirmPassword) => {
|
|
11942
|
+
setLoading(true);
|
|
11943
|
+
setError(undefined);
|
|
11944
|
+
try {
|
|
11945
|
+
if (resetToken && confirmPassword) {
|
|
11946
|
+
// Complete password reset with token
|
|
11947
|
+
await api.completePasswordReset(resetToken, emailOrPassword);
|
|
11948
|
+
setResetSuccess(true);
|
|
11949
|
+
setResetToken(undefined); // Clear token after successful reset
|
|
11950
|
+
}
|
|
11951
|
+
else {
|
|
11952
|
+
// Request password reset email
|
|
11953
|
+
await api.requestPasswordReset(emailOrPassword, getRedirectUrl());
|
|
11954
|
+
setResetSuccess(true);
|
|
11955
|
+
}
|
|
11082
11956
|
}
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11957
|
+
catch (err) {
|
|
11958
|
+
const errorMessage = err instanceof Error ? err.message : 'Password reset failed';
|
|
11959
|
+
setError(errorMessage);
|
|
11960
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11961
|
+
}
|
|
11962
|
+
finally {
|
|
11963
|
+
setLoading(false);
|
|
11086
11964
|
}
|
|
11965
|
+
};
|
|
11966
|
+
const handleMagicLink = async (email) => {
|
|
11087
11967
|
setLoading(true);
|
|
11088
11968
|
setError(undefined);
|
|
11089
11969
|
try {
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
// Note: This performs a SOFT DELETE (marks as deleted, obfuscates email)
|
|
11094
|
-
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
11095
|
-
console.log('Required API endpoint: DELETE /api/v1/authkit/:clientId/account/delete');
|
|
11096
|
-
console.log('Data: password and confirmText="DELETE" provided');
|
|
11097
|
-
console.log('Note: Backend should soft delete (mark deleted, obfuscate email, disable account)');
|
|
11098
|
-
// Uncomment when backend is ready:
|
|
11099
|
-
// await smartlinks.authKit.deleteAccount(clientId, deletePassword, deleteConfirmText);
|
|
11100
|
-
// setSuccess('Account deleted successfully');
|
|
11101
|
-
// onAccountDeleted?.();
|
|
11102
|
-
// await auth.logout();
|
|
11970
|
+
await api.sendMagicLink(email, getRedirectUrl());
|
|
11971
|
+
setAuthSuccess(true);
|
|
11972
|
+
setSuccessMessage('Magic link sent! Check your email to log in.');
|
|
11103
11973
|
}
|
|
11104
11974
|
catch (err) {
|
|
11105
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to
|
|
11975
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to send magic link';
|
|
11106
11976
|
setError(errorMessage);
|
|
11107
|
-
|
|
11977
|
+
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11108
11978
|
}
|
|
11109
11979
|
finally {
|
|
11110
11980
|
setLoading(false);
|
|
11111
11981
|
}
|
|
11112
11982
|
};
|
|
11113
|
-
if (
|
|
11114
|
-
return (jsxRuntime.jsx(
|
|
11115
|
-
}
|
|
11116
|
-
if (loading && !profile) {
|
|
11117
|
-
return (jsxRuntime.jsx("div", { className: `account-management ${className}`, children: jsxRuntime.jsx("p", { className: "text-muted-foreground", children: "Loading..." }) }));
|
|
11118
|
-
}
|
|
11119
|
-
return (jsxRuntime.jsxs("div", { className: `account-management ${className}`, style: { maxWidth: '600px' }, children: [error && (jsxRuntime.jsx("div", { className: "auth-error", role: "alert", children: error })), success && (jsxRuntime.jsx("div", { className: "auth-success", role: "alert", children: success })), showProfileSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsxs("div", { className: "account-field", children: [jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: "Display Name" }), jsxRuntime.jsx("div", { className: "field-value", children: profile?.displayName || 'Not set' })] }), editingSection !== 'profile' && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('profile'), className: "auth-button button-secondary", children: "Change" }))] }), editingSection === 'profile' && (jsxRuntime.jsxs("form", { onSubmit: handleUpdateProfile, className: "edit-form", children: [jsxRuntime.jsx("div", { className: "form-group", children: jsxRuntime.jsx("input", { id: "displayName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), placeholder: "Your display name", className: "auth-input" }) }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showEmailSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsxs("div", { className: "account-field", children: [jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: "Email Address" }), jsxRuntime.jsxs("div", { className: "field-value", children: [profile?.email || 'Not set', profile?.emailVerified && (jsxRuntime.jsx("span", { className: "verification-badge verified", children: "Verified" })), profile?.email && !profile?.emailVerified && (jsxRuntime.jsx("span", { className: "verification-badge unverified", children: "Unverified" }))] })] }), editingSection !== 'email' && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('email'), className: "auth-button button-secondary", children: "Change Email" }))] }), editingSection === 'email' && (jsxRuntime.jsxs("form", { onSubmit: handleChangeEmail, className: "edit-form", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newEmail", children: "New Email" }), jsxRuntime.jsx("input", { id: "newEmail", type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "new.email@example.com", className: "auth-input", required: true })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "emailPassword", children: "Confirm Password" }), jsxRuntime.jsx("input", { id: "emailPassword", type: "password", value: emailPassword, onChange: (e) => setEmailPassword(e.target.value), placeholder: "Enter your password", className: "auth-input", required: true })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Email' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPasswordSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsxs("div", { className: "account-field", children: [jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: "Password" }), jsxRuntime.jsx("div", { className: "field-value", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), editingSection !== 'password' && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('password'), className: "auth-button button-secondary", children: "Change Password" }))] }), editingSection === 'password' && (jsxRuntime.jsxs("form", { onSubmit: handleChangePassword, className: "edit-form", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "currentPassword", children: "Current Password" }), jsxRuntime.jsx("input", { id: "currentPassword", type: "password", value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value), placeholder: "Enter current password", className: "auth-input", required: true })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newPassword", children: "New Password" }), jsxRuntime.jsx("input", { id: "newPassword", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), placeholder: "Enter new password", className: "auth-input", required: true, minLength: 6 })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "confirmPassword", children: "Confirm New Password" }), jsxRuntime.jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), placeholder: "Confirm new password", className: "auth-input", required: true, minLength: 6 })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPhoneSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsxs("div", { className: "account-field", children: [jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: "Phone Number" }), jsxRuntime.jsx("div", { className: "field-value", children: profile?.phoneNumber || 'Not set' })] }), editingSection !== 'phone' && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('phone'), className: "auth-button button-secondary", children: "Change Phone" }))] }), editingSection === 'phone' && (jsxRuntime.jsxs("form", { onSubmit: handleUpdatePhone, className: "edit-form", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newPhone", children: "New Phone Number" }), jsxRuntime.jsx("input", { id: "newPhone", type: "tel", value: newPhone, onChange: (e) => setNewPhone(e.target.value), placeholder: "+1234567890", className: "auth-input", required: true })] }), !phoneCodeSent ? (jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Code' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneCode", children: "Verification Code" }), jsxRuntime.jsx("input", { id: "phoneCode", type: "text", value: phoneCode, onChange: (e) => setPhoneCode(e.target.value), placeholder: "Enter 6-digit code", className: "auth-input", required: true, maxLength: 6 })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Save' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] })), showDeleteAccount && (jsxRuntime.jsxs("section", { className: "account-section danger-zone", children: [jsxRuntime.jsx("h3", { className: "section-title text-danger", children: "Danger Zone" }), !showDeleteConfirm ? (jsxRuntime.jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "auth-button button-danger", children: "Delete Account" })) : (jsxRuntime.jsxs("div", { className: "delete-confirm", children: [jsxRuntime.jsx("p", { className: "warning-text", children: "\u26A0\uFE0F This action cannot be undone. This will permanently delete your account and all associated data." }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "deletePassword", children: "Confirm Password" }), jsxRuntime.jsx("input", { id: "deletePassword", type: "password", value: deletePassword, onChange: (e) => setDeletePassword(e.target.value), placeholder: "Enter your password", className: "auth-input" })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "deleteConfirm", children: "Type DELETE to confirm" }), jsxRuntime.jsx("input", { id: "deleteConfirm", type: "text", value: deleteConfirmText, onChange: (e) => setDeleteConfirmText(e.target.value), placeholder: "DELETE", className: "auth-input" })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "button", onClick: handleDeleteAccount, className: "auth-button button-danger", disabled: loading, children: loading ? 'Deleting...' : 'Permanently Delete Account' }), jsxRuntime.jsx("button", { type: "button", onClick: () => {
|
|
11120
|
-
setShowDeleteConfirm(false);
|
|
11121
|
-
setDeletePassword('');
|
|
11122
|
-
setDeleteConfirmText('');
|
|
11123
|
-
}, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
|
|
11124
|
-
};
|
|
11125
|
-
|
|
11126
|
-
const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
|
|
11127
|
-
const { isAuthenticated, isLoading } = useAuth();
|
|
11128
|
-
// Show loading state
|
|
11129
|
-
if (isLoading) {
|
|
11130
|
-
return jsxRuntime.jsx("div", { children: "Loading..." });
|
|
11131
|
-
}
|
|
11132
|
-
// If not authenticated, redirect or show fallback
|
|
11133
|
-
if (!isAuthenticated) {
|
|
11134
|
-
if (redirectTo) {
|
|
11135
|
-
window.location.href = redirectTo;
|
|
11136
|
-
return null;
|
|
11137
|
-
}
|
|
11138
|
-
return fallback ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }) : jsxRuntime.jsx("div", { children: "Access denied. Please log in." });
|
|
11983
|
+
if (configLoading) {
|
|
11984
|
+
return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, minimal: minimal || config?.branding?.minimal || false, children: jsxRuntime.jsx("div", { style: { textAlign: 'center', padding: '2rem' }, children: jsxRuntime.jsx("div", { className: "auth-spinner" }) }) }));
|
|
11139
11985
|
}
|
|
11140
|
-
|
|
11141
|
-
|
|
11986
|
+
return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, config: config, minimal: minimal || config?.branding?.minimal || false, children: authSuccess ? (jsxRuntime.jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [jsxRuntime.jsx("div", { style: {
|
|
11987
|
+
color: 'var(--auth-primary-color, #4F46E5)',
|
|
11988
|
+
fontSize: '3rem',
|
|
11989
|
+
marginBottom: '1rem'
|
|
11990
|
+
}, children: "\u2713" }), jsxRuntime.jsx("h2", { style: {
|
|
11991
|
+
marginBottom: '0.5rem',
|
|
11992
|
+
fontSize: '1.5rem',
|
|
11993
|
+
fontWeight: 600
|
|
11994
|
+
}, children: successMessage?.includes('verified') ? 'Email Verified!' :
|
|
11995
|
+
successMessage?.includes('Magic link') ? 'Check Your Email!' :
|
|
11996
|
+
mode === 'register' ? 'Account Created!' : 'Login Successful!' }), jsxRuntime.jsx("p", { style: {
|
|
11997
|
+
color: '#6B7280',
|
|
11998
|
+
fontSize: '0.875rem'
|
|
11999
|
+
}, children: successMessage })] })) : mode === 'magic-link' ? (jsxRuntime.jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error })) : mode === 'phone' ? (jsxRuntime.jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error })) : mode === 'reset-password' ? (jsxRuntime.jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
12000
|
+
setMode('login');
|
|
12001
|
+
setResetSuccess(false);
|
|
12002
|
+
setResetToken(undefined); // Clear token when going back
|
|
12003
|
+
}, loading: loading, error: error, success: resetSuccess, token: resetToken })) : (mode === 'login' || mode === 'register') ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: showResendVerification ? (jsxRuntime.jsxs("div", { style: { marginTop: '1rem', padding: '1.5rem', backgroundColor: 'rgba(79, 70, 229, 0.05)', borderRadius: '0.5rem' }, children: [jsxRuntime.jsx("h3", { style: { marginBottom: '0.75rem', fontSize: '1rem', fontWeight: 600, color: 'var(--auth-text-color, #374151)' }, children: "Verification Link Expired" }), jsxRuntime.jsx("p", { style: { marginBottom: '1rem', fontSize: '0.875rem', color: 'var(--auth-text-color, #6B7280)', lineHeight: '1.5' }, children: "Your verification link has expired or is no longer valid. Please enter your email address below and we'll send you a new verification link." }), jsxRuntime.jsx("input", { type: "email", value: resendEmail || '', onChange: (e) => setResendEmail(e.target.value), placeholder: "your@email.com", style: {
|
|
12004
|
+
width: '100%',
|
|
12005
|
+
padding: '0.625rem',
|
|
12006
|
+
marginBottom: '1rem',
|
|
12007
|
+
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12008
|
+
borderRadius: '0.375rem',
|
|
12009
|
+
fontSize: '0.875rem',
|
|
12010
|
+
boxSizing: 'border-box'
|
|
12011
|
+
} }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: '0.75rem' }, children: [jsxRuntime.jsx("button", { onClick: handleResendVerification, disabled: loading || !resendEmail, style: {
|
|
12012
|
+
flex: 1,
|
|
12013
|
+
padding: '0.625rem 1rem',
|
|
12014
|
+
backgroundColor: 'var(--auth-primary-color, #4F46E5)',
|
|
12015
|
+
color: 'white',
|
|
12016
|
+
border: 'none',
|
|
12017
|
+
borderRadius: '0.375rem',
|
|
12018
|
+
cursor: (loading || !resendEmail) ? 'not-allowed' : 'pointer',
|
|
12019
|
+
fontSize: '0.875rem',
|
|
12020
|
+
fontWeight: 500,
|
|
12021
|
+
opacity: (loading || !resendEmail) ? 0.6 : 1
|
|
12022
|
+
}, children: loading ? 'Sending...' : 'Send New Verification Link' }), jsxRuntime.jsx("button", { onClick: () => {
|
|
12023
|
+
setShowResendVerification(false);
|
|
12024
|
+
setResendEmail('');
|
|
12025
|
+
setError(undefined);
|
|
12026
|
+
}, disabled: loading, style: {
|
|
12027
|
+
padding: '0.625rem 1rem',
|
|
12028
|
+
backgroundColor: 'transparent',
|
|
12029
|
+
color: 'var(--auth-text-color, #6B7280)',
|
|
12030
|
+
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12031
|
+
borderRadius: '0.375rem',
|
|
12032
|
+
cursor: loading ? 'not-allowed' : 'pointer',
|
|
12033
|
+
fontSize: '0.875rem',
|
|
12034
|
+
fontWeight: 500,
|
|
12035
|
+
opacity: loading ? 0.6 : 1
|
|
12036
|
+
}, children: "Cancel" })] })] })) : showRequestNewReset ? (jsxRuntime.jsxs("div", { style: { marginTop: '1rem', padding: '1.5rem', backgroundColor: 'rgba(239, 68, 68, 0.05)', borderRadius: '0.5rem' }, children: [jsxRuntime.jsx("h3", { style: { marginBottom: '0.75rem', fontSize: '1rem', fontWeight: 600, color: 'var(--auth-text-color, #374151)' }, children: "Password Reset Link Expired" }), jsxRuntime.jsx("p", { style: { marginBottom: '1rem', fontSize: '0.875rem', color: 'var(--auth-text-color, #6B7280)', lineHeight: '1.5' }, children: "Your password reset link has expired or is no longer valid. Please enter your email address below and we'll send you a new password reset link." }), jsxRuntime.jsx("input", { type: "email", value: resetRequestEmail || '', onChange: (e) => setResetRequestEmail(e.target.value), placeholder: "your@email.com", style: {
|
|
12037
|
+
width: '100%',
|
|
12038
|
+
padding: '0.625rem',
|
|
12039
|
+
marginBottom: '1rem',
|
|
12040
|
+
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12041
|
+
borderRadius: '0.375rem',
|
|
12042
|
+
fontSize: '0.875rem',
|
|
12043
|
+
boxSizing: 'border-box'
|
|
12044
|
+
} }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: '0.75rem' }, children: [jsxRuntime.jsx("button", { onClick: handleRequestNewReset, disabled: loading || !resetRequestEmail, style: {
|
|
12045
|
+
flex: 1,
|
|
12046
|
+
padding: '0.625rem 1rem',
|
|
12047
|
+
backgroundColor: '#EF4444',
|
|
12048
|
+
color: 'white',
|
|
12049
|
+
border: 'none',
|
|
12050
|
+
borderRadius: '0.375rem',
|
|
12051
|
+
cursor: (loading || !resetRequestEmail) ? 'not-allowed' : 'pointer',
|
|
12052
|
+
fontSize: '0.875rem',
|
|
12053
|
+
fontWeight: 500,
|
|
12054
|
+
opacity: (loading || !resetRequestEmail) ? 0.6 : 1
|
|
12055
|
+
}, children: loading ? 'Sending...' : 'Send New Reset Link' }), jsxRuntime.jsx("button", { onClick: () => {
|
|
12056
|
+
setShowRequestNewReset(false);
|
|
12057
|
+
setResetRequestEmail('');
|
|
12058
|
+
setError(undefined);
|
|
12059
|
+
}, disabled: loading, style: {
|
|
12060
|
+
padding: '0.625rem 1rem',
|
|
12061
|
+
backgroundColor: 'transparent',
|
|
12062
|
+
color: 'var(--auth-text-color, #6B7280)',
|
|
12063
|
+
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
12064
|
+
borderRadius: '0.375rem',
|
|
12065
|
+
cursor: loading ? 'not-allowed' : 'pointer',
|
|
12066
|
+
fontSize: '0.875rem',
|
|
12067
|
+
fontWeight: 500,
|
|
12068
|
+
opacity: loading ? 0.6 : 1
|
|
12069
|
+
}, children: "Cancel" })] })] })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: (() => {
|
|
12070
|
+
const emailDisplayMode = config?.emailDisplayMode || 'form';
|
|
12071
|
+
const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
|
|
12072
|
+
const actualProviders = config?.enabledProviders || enabledProviders;
|
|
12073
|
+
// Button mode: show provider selection first, then email form if email is selected
|
|
12074
|
+
if (emailDisplayMode === 'button' && !showEmailForm) {
|
|
12075
|
+
return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
12076
|
+
}
|
|
12077
|
+
// Form mode or email button was clicked: show email form with other providers
|
|
12078
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [emailDisplayMode === 'button' && showEmailForm && (jsxRuntime.jsx("button", { onClick: () => setShowEmailForm(false), style: {
|
|
12079
|
+
marginBottom: '1rem',
|
|
12080
|
+
padding: '0.5rem',
|
|
12081
|
+
background: 'none',
|
|
12082
|
+
border: 'none',
|
|
12083
|
+
color: 'var(--auth-text-color, #6B7280)',
|
|
12084
|
+
cursor: 'pointer',
|
|
12085
|
+
fontSize: '0.875rem',
|
|
12086
|
+
display: 'flex',
|
|
12087
|
+
alignItems: 'center',
|
|
12088
|
+
gap: '0.25rem'
|
|
12089
|
+
}, children: "\u2190 Back to options" })), jsxRuntime.jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
|
|
12090
|
+
setMode(mode === 'login' ? 'register' : 'login');
|
|
12091
|
+
setShowResendVerification(false);
|
|
12092
|
+
setShowRequestNewReset(false);
|
|
12093
|
+
setError(undefined);
|
|
12094
|
+
}, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
12095
|
+
})() })) })) : null }));
|
|
11142
12096
|
};
|
|
11143
12097
|
|
|
11144
|
-
const
|
|
11145
|
-
const
|
|
11146
|
-
const showGoogle = enabledProviders.includes('google');
|
|
11147
|
-
const showPhone = enabledProviders.includes('phone');
|
|
11148
|
-
const showMagicLink = enabledProviders.includes('magic-link');
|
|
11149
|
-
// Determine ordered providers (excluding email if in button mode)
|
|
11150
|
-
const orderedProviders = providerOrder && providerOrder.length > 0
|
|
11151
|
-
? providerOrder.filter(p => enabledProviders.includes(p) && p !== 'email')
|
|
11152
|
-
: enabledProviders.filter(p => p !== 'email');
|
|
11153
|
-
const hasOtherProviders = showGoogle || showPhone || showMagicLink;
|
|
11154
|
-
// Render provider button helper
|
|
11155
|
-
const renderProviderButton = (provider) => {
|
|
11156
|
-
if (provider === 'google' && showGoogle) {
|
|
11157
|
-
return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", children: [jsxRuntime.jsx("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z", fill: "#4285F4" }), jsxRuntime.jsx("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.183l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332C2.438 15.983 5.482 18 9 18z", fill: "#34A853" }), jsxRuntime.jsx("path", { d: "M3.964 10.707c-.18-.54-.282-1.117-.282-1.707 0-.593.102-1.167.282-1.707V4.961H.957C.347 6.175 0 7.548 0 9s.348 2.825.957 4.039l3.007-2.332z", fill: "#FBBC05" }), jsxRuntime.jsx("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0 5.482 0 2.438 2.017.957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z", fill: "#EA4335" })] }), jsxRuntime.jsx("span", { children: "Continue with Google" })] }, "google"));
|
|
11158
|
-
}
|
|
11159
|
-
if (provider === 'phone' && showPhone) {
|
|
11160
|
-
return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsxRuntime.jsx("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" }) }), jsxRuntime.jsx("span", { children: "Continue with Phone" })] }, "phone"));
|
|
11161
|
-
}
|
|
11162
|
-
if (provider === 'magic-link' && showMagicLink) {
|
|
11163
|
-
return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) }), jsxRuntime.jsx("span", { children: "Continue with Magic Link" })] }, "magic-link"));
|
|
11164
|
-
}
|
|
11165
|
-
if (provider === 'email' && showEmail && emailDisplayMode === 'button') {
|
|
11166
|
-
return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) }), jsxRuntime.jsx("span", { children: "Continue with Email" })] }, "email"));
|
|
11167
|
-
}
|
|
11168
|
-
return null;
|
|
11169
|
-
};
|
|
11170
|
-
return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, config: customization, children: emailDisplayMode === 'button' ? (jsxRuntime.jsx("div", { className: "auth-provider-buttons", children: orderedProviders.concat(showEmail ? ['email'] : []).map(provider => renderProviderButton(provider)) })) : (
|
|
11171
|
-
/* Form mode: show email form first, then other providers */
|
|
11172
|
-
jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showEmail && (jsxRuntime.jsxs("div", { className: "auth-form", children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { className: "auth-label", children: "Email" }), jsxRuntime.jsx("input", { type: "email", className: "auth-input", placeholder: "Enter your email", disabled: true })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { className: "auth-label", children: "Password" }), jsxRuntime.jsx("input", { type: "password", className: "auth-input", placeholder: "Enter your password", disabled: true })] }), jsxRuntime.jsx("button", { className: "auth-button auth-button-primary", disabled: true, children: "Sign In" }), jsxRuntime.jsx("div", { style: { textAlign: 'center', marginTop: '1rem' }, children: jsxRuntime.jsx("button", { className: "auth-link", disabled: true, children: "Forgot password?" }) }), jsxRuntime.jsxs("div", { style: { textAlign: 'center', marginTop: '0.5rem', fontSize: '0.875rem', color: 'var(--auth-text-muted, #6B7280)' }, children: ["Don't have an account?", ' ', jsxRuntime.jsx("button", { className: "auth-link", disabled: true, children: "Sign up" })] })] })), hasOtherProviders && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showEmail && (jsxRuntime.jsx("div", { className: "auth-or-divider", children: jsxRuntime.jsx("span", { children: "or continue with" }) })), jsxRuntime.jsx("div", { className: "auth-provider-buttons", children: orderedProviders.map(provider => renderProviderButton(provider)) })] }))] })) }));
|
|
11173
|
-
};
|
|
11174
|
-
|
|
11175
|
-
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
11176
|
-
const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
|
|
11177
|
-
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, }) => {
|
|
11178
|
-
const [mode, setMode] = React.useState(initialMode);
|
|
12098
|
+
const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', customization = {}, }) => {
|
|
12099
|
+
const auth = useAuth();
|
|
11179
12100
|
const [loading, setLoading] = React.useState(false);
|
|
12101
|
+
const [profile, setProfile] = React.useState(null);
|
|
11180
12102
|
const [error, setError] = React.useState();
|
|
11181
|
-
const [
|
|
11182
|
-
|
|
11183
|
-
const [
|
|
11184
|
-
|
|
11185
|
-
const [
|
|
11186
|
-
|
|
11187
|
-
const [
|
|
11188
|
-
const [
|
|
11189
|
-
|
|
11190
|
-
const [
|
|
11191
|
-
const [
|
|
11192
|
-
const
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
12103
|
+
const [success, setSuccess] = React.useState();
|
|
12104
|
+
// Track which section is being edited
|
|
12105
|
+
const [editingSection, setEditingSection] = React.useState(null);
|
|
12106
|
+
// Profile form state
|
|
12107
|
+
const [displayName, setDisplayName] = React.useState('');
|
|
12108
|
+
// Email change state
|
|
12109
|
+
const [newEmail, setNewEmail] = React.useState('');
|
|
12110
|
+
const [emailPassword, setEmailPassword] = React.useState('');
|
|
12111
|
+
// Password change state
|
|
12112
|
+
const [currentPassword, setCurrentPassword] = React.useState('');
|
|
12113
|
+
const [newPassword, setNewPassword] = React.useState('');
|
|
12114
|
+
const [confirmPassword, setConfirmPassword] = React.useState('');
|
|
12115
|
+
// Phone change state (reuses existing sendPhoneCode flow)
|
|
12116
|
+
const [newPhone, setNewPhone] = React.useState('');
|
|
12117
|
+
const [phoneCode, setPhoneCode] = React.useState('');
|
|
12118
|
+
const [phoneCodeSent, setPhoneCodeSent] = React.useState(false);
|
|
12119
|
+
// Account deletion state
|
|
12120
|
+
const [deletePassword, setDeletePassword] = React.useState('');
|
|
12121
|
+
const [deleteConfirmText, setDeleteConfirmText] = React.useState('');
|
|
12122
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
12123
|
+
const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, } = customization;
|
|
12124
|
+
// Reinitialize Smartlinks SDK when apiEndpoint changes
|
|
11196
12125
|
React.useEffect(() => {
|
|
11197
12126
|
if (apiEndpoint) {
|
|
11198
|
-
// Get current token before reinitializing
|
|
11199
|
-
const currentToken = auth.getToken();
|
|
11200
12127
|
smartlinks__namespace.initializeApi({
|
|
11201
12128
|
baseURL: apiEndpoint,
|
|
11202
|
-
proxyMode: false,
|
|
12129
|
+
proxyMode: false,
|
|
11203
12130
|
ngrokSkipBrowserWarning: true,
|
|
11204
12131
|
});
|
|
11205
|
-
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11206
|
-
if (currentToken) {
|
|
11207
|
-
smartlinks__namespace.auth.verifyToken(currentToken).catch(err => {
|
|
11208
|
-
console.warn('Failed to restore bearer token after reinit:', err);
|
|
11209
|
-
});
|
|
11210
|
-
}
|
|
11211
12132
|
}
|
|
11212
|
-
}, [apiEndpoint
|
|
11213
|
-
//
|
|
11214
|
-
const getRedirectUrl = () => {
|
|
11215
|
-
if (redirectUrl)
|
|
11216
|
-
return redirectUrl;
|
|
11217
|
-
// Get the full current URL including hash routes
|
|
11218
|
-
// Remove any existing query parameters to avoid duplication
|
|
11219
|
-
const currentUrl = window.location.href.split('?')[0];
|
|
11220
|
-
return currentUrl;
|
|
11221
|
-
};
|
|
11222
|
-
// Fetch UI configuration
|
|
12133
|
+
}, [apiEndpoint]);
|
|
12134
|
+
// Load user profile on mount
|
|
11223
12135
|
React.useEffect(() => {
|
|
11224
|
-
|
|
11225
|
-
|
|
12136
|
+
loadProfile();
|
|
12137
|
+
}, [clientId]);
|
|
12138
|
+
const loadProfile = async () => {
|
|
12139
|
+
if (!auth.isAuthenticated) {
|
|
12140
|
+
setError('You must be logged in to manage your account');
|
|
11226
12141
|
return;
|
|
11227
12142
|
}
|
|
11228
|
-
const fetchConfig = async () => {
|
|
11229
|
-
try {
|
|
11230
|
-
// Check localStorage cache first
|
|
11231
|
-
const cacheKey = `auth_ui_config_${clientId || 'default'}`;
|
|
11232
|
-
const cached = localStorage.getItem(cacheKey);
|
|
11233
|
-
if (cached) {
|
|
11234
|
-
const { config: cachedConfig, timestamp } = JSON.parse(cached);
|
|
11235
|
-
const age = Date.now() - timestamp;
|
|
11236
|
-
// Use cache if less than 1 hour old
|
|
11237
|
-
if (age < 3600000) {
|
|
11238
|
-
setConfig({ ...cachedConfig, ...customization });
|
|
11239
|
-
setConfigLoading(false);
|
|
11240
|
-
// Fetch in background to update cache
|
|
11241
|
-
api.fetchConfig().then(freshConfig => {
|
|
11242
|
-
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11243
|
-
config: freshConfig,
|
|
11244
|
-
timestamp: Date.now()
|
|
11245
|
-
}));
|
|
11246
|
-
});
|
|
11247
|
-
return;
|
|
11248
|
-
}
|
|
11249
|
-
}
|
|
11250
|
-
// Fetch from API
|
|
11251
|
-
const fetchedConfig = await api.fetchConfig();
|
|
11252
|
-
// Merge with customization props (props take precedence)
|
|
11253
|
-
const mergedConfig = { ...fetchedConfig, ...customization };
|
|
11254
|
-
setConfig(mergedConfig);
|
|
11255
|
-
// Cache the fetched config
|
|
11256
|
-
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11257
|
-
config: fetchedConfig,
|
|
11258
|
-
timestamp: Date.now()
|
|
11259
|
-
}));
|
|
11260
|
-
}
|
|
11261
|
-
catch (err) {
|
|
11262
|
-
console.error('Failed to fetch config:', err);
|
|
11263
|
-
setConfig(customization || {});
|
|
11264
|
-
}
|
|
11265
|
-
finally {
|
|
11266
|
-
setConfigLoading(false);
|
|
11267
|
-
}
|
|
11268
|
-
};
|
|
11269
|
-
fetchConfig();
|
|
11270
|
-
}, [apiEndpoint, clientId, customization, skipConfigFetch]);
|
|
11271
|
-
// Reset showEmailForm when mode changes away from login/register
|
|
11272
|
-
React.useEffect(() => {
|
|
11273
|
-
if (mode !== 'login' && mode !== 'register') {
|
|
11274
|
-
setShowEmailForm(false);
|
|
11275
|
-
}
|
|
11276
|
-
}, [mode]);
|
|
11277
|
-
// Handle URL-based auth flows (email verification, password reset)
|
|
11278
|
-
React.useEffect(() => {
|
|
11279
|
-
// Helper to get URL parameters from either hash or search
|
|
11280
|
-
const getUrlParams = () => {
|
|
11281
|
-
// First check if there are params in the hash (for hash routing)
|
|
11282
|
-
const hash = window.location.hash;
|
|
11283
|
-
const hashQueryIndex = hash.indexOf('?');
|
|
11284
|
-
if (hashQueryIndex !== -1) {
|
|
11285
|
-
// Extract query string from hash (e.g., #/test?mode=verifyEmail&token=abc)
|
|
11286
|
-
const hashQuery = hash.substring(hashQueryIndex + 1);
|
|
11287
|
-
return new URLSearchParams(hashQuery);
|
|
11288
|
-
}
|
|
11289
|
-
// Fall back to regular search params (for non-hash routing)
|
|
11290
|
-
return new URLSearchParams(window.location.search);
|
|
11291
|
-
};
|
|
11292
|
-
const params = getUrlParams();
|
|
11293
|
-
const urlMode = params.get('mode');
|
|
11294
|
-
const token = params.get('token');
|
|
11295
|
-
console.log('URL params detected:', { urlMode, token, hash: window.location.hash, search: window.location.search });
|
|
11296
|
-
if (urlMode && token) {
|
|
11297
|
-
handleURLBasedAuth(urlMode, token);
|
|
11298
|
-
}
|
|
11299
|
-
}, []);
|
|
11300
|
-
const handleURLBasedAuth = async (urlMode, token) => {
|
|
11301
|
-
setLoading(true);
|
|
11302
|
-
setError(undefined);
|
|
11303
|
-
try {
|
|
11304
|
-
if (urlMode === 'verifyEmail') {
|
|
11305
|
-
console.log('Verifying email with token:', token);
|
|
11306
|
-
const response = await api.verifyEmailWithToken(token);
|
|
11307
|
-
// Get email verification mode from response or config
|
|
11308
|
-
const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
|
|
11309
|
-
if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
|
|
11310
|
-
// Auto-login modes: Log the user in immediately if token is provided
|
|
11311
|
-
auth.login(response.token, response.user, response.accountData);
|
|
11312
|
-
setAuthSuccess(true);
|
|
11313
|
-
setSuccessMessage('Email verified successfully! You are now logged in.');
|
|
11314
|
-
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11315
|
-
// Clear the URL parameters
|
|
11316
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
11317
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
11318
|
-
// Redirect after a brief delay to show success message
|
|
11319
|
-
if (redirectUrl) {
|
|
11320
|
-
setTimeout(() => {
|
|
11321
|
-
window.location.href = redirectUrl;
|
|
11322
|
-
}, 2000);
|
|
11323
|
-
}
|
|
11324
|
-
}
|
|
11325
|
-
else {
|
|
11326
|
-
// verify-then-manual-login mode or no token: Show success but require manual login
|
|
11327
|
-
setAuthSuccess(true);
|
|
11328
|
-
setSuccessMessage('Email verified successfully! Please log in with your credentials.');
|
|
11329
|
-
// Clear the URL parameters
|
|
11330
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
11331
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
11332
|
-
// Switch back to login mode after a delay
|
|
11333
|
-
setTimeout(() => {
|
|
11334
|
-
setAuthSuccess(false);
|
|
11335
|
-
setMode('login');
|
|
11336
|
-
}, 3000);
|
|
11337
|
-
}
|
|
11338
|
-
}
|
|
11339
|
-
else if (urlMode === 'resetPassword') {
|
|
11340
|
-
console.log('Verifying reset token:', token);
|
|
11341
|
-
// Verify token is valid, then show password reset form
|
|
11342
|
-
await api.verifyResetToken(token);
|
|
11343
|
-
setResetToken(token); // Store token for use in password reset
|
|
11344
|
-
setMode('reset-password');
|
|
11345
|
-
// Clear the URL parameters
|
|
11346
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
11347
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
11348
|
-
}
|
|
11349
|
-
else if (urlMode === 'magicLink') {
|
|
11350
|
-
console.log('Verifying magic link token:', token);
|
|
11351
|
-
const response = await api.verifyMagicLink(token);
|
|
11352
|
-
// Auto-login with magic link if token is provided
|
|
11353
|
-
if (response.token) {
|
|
11354
|
-
auth.login(response.token, response.user, response.accountData);
|
|
11355
|
-
setAuthSuccess(true);
|
|
11356
|
-
setSuccessMessage('Magic link verified! You are now logged in.');
|
|
11357
|
-
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11358
|
-
// Clear the URL parameters
|
|
11359
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
11360
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
11361
|
-
// Redirect after a brief delay to show success message
|
|
11362
|
-
if (redirectUrl) {
|
|
11363
|
-
setTimeout(() => {
|
|
11364
|
-
window.location.href = redirectUrl;
|
|
11365
|
-
}, 2000);
|
|
11366
|
-
}
|
|
11367
|
-
}
|
|
11368
|
-
else {
|
|
11369
|
-
throw new Error('Authentication failed - no token received');
|
|
11370
|
-
}
|
|
11371
|
-
}
|
|
11372
|
-
}
|
|
11373
|
-
catch (err) {
|
|
11374
|
-
console.error('URL-based auth error:', err);
|
|
11375
|
-
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
|
|
11376
|
-
// If it's an email verification error (expired/invalid token), show resend option
|
|
11377
|
-
if (urlMode === 'verifyEmail') {
|
|
11378
|
-
setError(`${errorMessage} - Please enter your email below to receive a new verification link.`);
|
|
11379
|
-
setShowResendVerification(true);
|
|
11380
|
-
setMode('login'); // Show the login form UI
|
|
11381
|
-
// Clear the URL parameters
|
|
11382
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
11383
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
11384
|
-
}
|
|
11385
|
-
else if (urlMode === 'resetPassword') {
|
|
11386
|
-
// If password reset token is invalid/expired, show request new reset link option
|
|
11387
|
-
setError(`${errorMessage} - Please enter your email below to receive a new password reset link.`);
|
|
11388
|
-
setShowRequestNewReset(true);
|
|
11389
|
-
setMode('login');
|
|
11390
|
-
// Clear the URL parameters
|
|
11391
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
11392
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
11393
|
-
}
|
|
11394
|
-
else if (urlMode === 'magicLink') {
|
|
11395
|
-
// If magic link is invalid/expired
|
|
11396
|
-
setError(`${errorMessage} - Please request a new magic link below.`);
|
|
11397
|
-
setMode('magic-link');
|
|
11398
|
-
// Clear the URL parameters
|
|
11399
|
-
const cleanUrl = window.location.href.split('?')[0];
|
|
11400
|
-
window.history.replaceState({}, document.title, cleanUrl);
|
|
11401
|
-
}
|
|
11402
|
-
else {
|
|
11403
|
-
setError(errorMessage);
|
|
11404
|
-
}
|
|
11405
|
-
onAuthError?.(err instanceof Error ? err : new Error('An error occurred'));
|
|
11406
|
-
}
|
|
11407
|
-
finally {
|
|
11408
|
-
setLoading(false);
|
|
11409
|
-
}
|
|
11410
|
-
};
|
|
11411
|
-
const handleEmailAuth = async (data) => {
|
|
11412
12143
|
setLoading(true);
|
|
11413
12144
|
setError(undefined);
|
|
11414
|
-
setAuthSuccess(false);
|
|
11415
12145
|
try {
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
setAuthSuccess(true);
|
|
11432
|
-
const deadline = response.emailVerificationDeadline
|
|
11433
|
-
? new Date(response.emailVerificationDeadline).toLocaleString()
|
|
11434
|
-
: `${gracePeriodHours} hours`;
|
|
11435
|
-
setSuccessMessage(`Account created! You're logged in now. Please verify your email by ${deadline} to keep your account active.`);
|
|
11436
|
-
if (response.token) {
|
|
11437
|
-
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11438
|
-
}
|
|
11439
|
-
if (redirectUrl) {
|
|
11440
|
-
setTimeout(() => {
|
|
11441
|
-
window.location.href = redirectUrl;
|
|
11442
|
-
}, 2000);
|
|
11443
|
-
}
|
|
11444
|
-
}
|
|
11445
|
-
else if (verificationMode === 'verify-then-auto-login') {
|
|
11446
|
-
// Verify-then-auto-login mode: Don't log in yet, but will auto-login after email verification
|
|
11447
|
-
setAuthSuccess(true);
|
|
11448
|
-
setSuccessMessage('Account created! Please check your email and click the verification link to complete your registration.');
|
|
11449
|
-
}
|
|
11450
|
-
else {
|
|
11451
|
-
// verify-then-manual-login mode: Traditional flow
|
|
11452
|
-
setAuthSuccess(true);
|
|
11453
|
-
setSuccessMessage('Account created successfully! Please check your email to verify your account, then log in.');
|
|
11454
|
-
}
|
|
11455
|
-
}
|
|
11456
|
-
else {
|
|
11457
|
-
// Login mode - always log in if token is provided
|
|
11458
|
-
if (response.token) {
|
|
11459
|
-
// Check for account lock or verification requirements
|
|
11460
|
-
if (response.accountLocked) {
|
|
11461
|
-
throw new Error('Your account has been locked due to unverified email. Please check your email or request a new verification link.');
|
|
11462
|
-
}
|
|
11463
|
-
if (response.requiresEmailVerification) {
|
|
11464
|
-
throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
|
|
11465
|
-
}
|
|
11466
|
-
auth.login(response.token, response.user, response.accountData);
|
|
11467
|
-
setAuthSuccess(true);
|
|
11468
|
-
setSuccessMessage('Login successful!');
|
|
11469
|
-
onAuthSuccess(response.token, response.user, response.accountData);
|
|
11470
|
-
if (redirectUrl) {
|
|
11471
|
-
setTimeout(() => {
|
|
11472
|
-
window.location.href = redirectUrl;
|
|
11473
|
-
}, 2000);
|
|
11474
|
-
}
|
|
11475
|
-
}
|
|
11476
|
-
else {
|
|
11477
|
-
throw new Error('Authentication failed - please verify your email before logging in.');
|
|
11478
|
-
}
|
|
11479
|
-
}
|
|
12146
|
+
// TODO: Backend implementation required
|
|
12147
|
+
// Endpoint: GET /api/v1/authkit/:clientId/account/profile
|
|
12148
|
+
// SDK method: smartlinks.authKit.getProfile(clientId)
|
|
12149
|
+
// Temporary mock data for UI testing
|
|
12150
|
+
const profileData = {
|
|
12151
|
+
uid: auth.user?.uid || '',
|
|
12152
|
+
email: auth.user?.email,
|
|
12153
|
+
displayName: auth.user?.displayName,
|
|
12154
|
+
phoneNumber: auth.user?.phoneNumber,
|
|
12155
|
+
photoURL: auth.user?.photoURL,
|
|
12156
|
+
emailVerified: true,
|
|
12157
|
+
accountData: auth.accountData || {},
|
|
12158
|
+
};
|
|
12159
|
+
setProfile(profileData);
|
|
12160
|
+
setDisplayName(profileData.displayName || '');
|
|
11480
12161
|
}
|
|
11481
12162
|
catch (err) {
|
|
11482
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
setShowResendVerification(true);
|
|
11486
|
-
setResendEmail(data.email);
|
|
11487
|
-
setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
|
|
11488
|
-
}
|
|
11489
|
-
else {
|
|
11490
|
-
setError(errorMessage);
|
|
11491
|
-
}
|
|
11492
|
-
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12163
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to load profile';
|
|
12164
|
+
setError(errorMessage);
|
|
12165
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11493
12166
|
}
|
|
11494
12167
|
finally {
|
|
11495
12168
|
setLoading(false);
|
|
11496
12169
|
}
|
|
11497
12170
|
};
|
|
11498
|
-
const
|
|
11499
|
-
|
|
11500
|
-
return;
|
|
12171
|
+
const handleUpdateProfile = async (e) => {
|
|
12172
|
+
e.preventDefault();
|
|
11501
12173
|
setLoading(true);
|
|
11502
12174
|
setError(undefined);
|
|
12175
|
+
setSuccess(undefined);
|
|
11503
12176
|
try {
|
|
11504
|
-
//
|
|
11505
|
-
//
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
12177
|
+
// TODO: Backend implementation required
|
|
12178
|
+
// Endpoint: POST /api/v1/authkit/:clientId/account/update-profile
|
|
12179
|
+
// SDK method: smartlinks.authKit.updateProfile(clientId, updateData)
|
|
12180
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12181
|
+
console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-profile');
|
|
12182
|
+
console.log('Update data:', { displayName });
|
|
12183
|
+
// Uncomment when backend is ready:
|
|
12184
|
+
// const updateData: ProfileUpdateData = {
|
|
12185
|
+
// displayName: displayName || undefined,
|
|
12186
|
+
// photoURL: photoURL || undefined,
|
|
12187
|
+
// };
|
|
12188
|
+
// const updatedProfile = await smartlinks.authKit.updateProfile(clientId, updateData);
|
|
12189
|
+
// setProfile(updatedProfile);
|
|
12190
|
+
// setSuccess('Profile updated successfully!');
|
|
12191
|
+
// setEditingSection(null);
|
|
12192
|
+
// onProfileUpdated?.(updatedProfile);
|
|
11510
12193
|
}
|
|
11511
12194
|
catch (err) {
|
|
11512
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to
|
|
12195
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to update profile';
|
|
11513
12196
|
setError(errorMessage);
|
|
11514
|
-
|
|
12197
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11515
12198
|
}
|
|
11516
12199
|
finally {
|
|
11517
12200
|
setLoading(false);
|
|
11518
12201
|
}
|
|
11519
12202
|
};
|
|
11520
|
-
const
|
|
11521
|
-
|
|
11522
|
-
|
|
12203
|
+
const cancelEdit = () => {
|
|
12204
|
+
setEditingSection(null);
|
|
12205
|
+
setDisplayName(profile?.displayName || '');
|
|
12206
|
+
setNewEmail('');
|
|
12207
|
+
setEmailPassword('');
|
|
12208
|
+
setCurrentPassword('');
|
|
12209
|
+
setNewPassword('');
|
|
12210
|
+
setConfirmPassword('');
|
|
12211
|
+
setNewPhone('');
|
|
12212
|
+
setPhoneCode('');
|
|
12213
|
+
setPhoneCodeSent(false);
|
|
12214
|
+
setError(undefined);
|
|
12215
|
+
setSuccess(undefined);
|
|
12216
|
+
};
|
|
12217
|
+
const handleChangeEmail = async (e) => {
|
|
12218
|
+
e.preventDefault();
|
|
11523
12219
|
setLoading(true);
|
|
11524
12220
|
setError(undefined);
|
|
12221
|
+
setSuccess(undefined);
|
|
11525
12222
|
try {
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
12223
|
+
// TODO: Backend implementation required
|
|
12224
|
+
// Endpoint: POST /api/v1/authkit/:clientId/account/change-email
|
|
12225
|
+
// SDK method: smartlinks.authKit.changeEmail(clientId, newEmail, password)
|
|
12226
|
+
// Note: No verification flow for now - direct email update
|
|
12227
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12228
|
+
console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-email');
|
|
12229
|
+
console.log('Data:', { newEmail });
|
|
12230
|
+
// Uncomment when backend is ready:
|
|
12231
|
+
// await smartlinks.authKit.changeEmail(clientId, newEmail, emailPassword);
|
|
12232
|
+
// setSuccess('Email changed successfully!');
|
|
12233
|
+
// setEditingSection(null);
|
|
12234
|
+
// setNewEmail('');
|
|
12235
|
+
// setEmailPassword('');
|
|
12236
|
+
// onEmailChangeRequested?.();
|
|
12237
|
+
// await loadProfile(); // Reload to show new email
|
|
11531
12238
|
}
|
|
11532
12239
|
catch (err) {
|
|
11533
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to
|
|
12240
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to change email';
|
|
11534
12241
|
setError(errorMessage);
|
|
11535
|
-
|
|
12242
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11536
12243
|
}
|
|
11537
12244
|
finally {
|
|
11538
12245
|
setLoading(false);
|
|
11539
12246
|
}
|
|
11540
12247
|
};
|
|
11541
|
-
const
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
12248
|
+
const handleChangePassword = async (e) => {
|
|
12249
|
+
e.preventDefault();
|
|
12250
|
+
if (newPassword !== confirmPassword) {
|
|
12251
|
+
setError('New passwords do not match');
|
|
12252
|
+
return;
|
|
12253
|
+
}
|
|
12254
|
+
if (newPassword.length < 6) {
|
|
12255
|
+
setError('Password must be at least 6 characters');
|
|
12256
|
+
return;
|
|
12257
|
+
}
|
|
11546
12258
|
setLoading(true);
|
|
11547
12259
|
setError(undefined);
|
|
12260
|
+
setSuccess(undefined);
|
|
11548
12261
|
try {
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
if (response.error) {
|
|
11564
|
-
throw new Error(response.error_description || response.error);
|
|
11565
|
-
}
|
|
11566
|
-
const accessToken = response.access_token;
|
|
11567
|
-
// Send access token to backend
|
|
11568
|
-
const authResponse = await api.loginWithGoogle(accessToken);
|
|
11569
|
-
if (authResponse.token) {
|
|
11570
|
-
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11571
|
-
setAuthSuccess(true);
|
|
11572
|
-
setSuccessMessage('Google login successful!');
|
|
11573
|
-
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11574
|
-
}
|
|
11575
|
-
else {
|
|
11576
|
-
throw new Error('Authentication failed - no token received');
|
|
11577
|
-
}
|
|
11578
|
-
if (redirectUrl) {
|
|
11579
|
-
setTimeout(() => {
|
|
11580
|
-
window.location.href = redirectUrl;
|
|
11581
|
-
}, 2000);
|
|
11582
|
-
}
|
|
11583
|
-
setLoading(false);
|
|
11584
|
-
}
|
|
11585
|
-
catch (err) {
|
|
11586
|
-
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
11587
|
-
setError(errorMessage);
|
|
11588
|
-
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11589
|
-
setLoading(false);
|
|
11590
|
-
}
|
|
11591
|
-
},
|
|
11592
|
-
});
|
|
11593
|
-
client.requestAccessToken();
|
|
11594
|
-
}
|
|
11595
|
-
else {
|
|
11596
|
-
// Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
|
|
11597
|
-
google.accounts.id.initialize({
|
|
11598
|
-
client_id: googleClientId,
|
|
11599
|
-
callback: async (response) => {
|
|
11600
|
-
try {
|
|
11601
|
-
const idToken = response.credential;
|
|
11602
|
-
const authResponse = await api.loginWithGoogle(idToken);
|
|
11603
|
-
if (authResponse.token) {
|
|
11604
|
-
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11605
|
-
setAuthSuccess(true);
|
|
11606
|
-
setSuccessMessage('Google login successful!');
|
|
11607
|
-
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11608
|
-
}
|
|
11609
|
-
else {
|
|
11610
|
-
throw new Error('Authentication failed - no token received');
|
|
11611
|
-
}
|
|
11612
|
-
if (redirectUrl) {
|
|
11613
|
-
setTimeout(() => {
|
|
11614
|
-
window.location.href = redirectUrl;
|
|
11615
|
-
}, 2000);
|
|
11616
|
-
}
|
|
11617
|
-
setLoading(false);
|
|
11618
|
-
}
|
|
11619
|
-
catch (err) {
|
|
11620
|
-
const errorMessage = err instanceof Error ? err.message : 'Google login failed';
|
|
11621
|
-
setError(errorMessage);
|
|
11622
|
-
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11623
|
-
setLoading(false);
|
|
11624
|
-
}
|
|
11625
|
-
},
|
|
11626
|
-
auto_select: false,
|
|
11627
|
-
cancel_on_tap_outside: true,
|
|
11628
|
-
});
|
|
11629
|
-
google.accounts.id.prompt((notification) => {
|
|
11630
|
-
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
|
|
11631
|
-
setLoading(false);
|
|
11632
|
-
}
|
|
11633
|
-
});
|
|
11634
|
-
}
|
|
12262
|
+
// TODO: Backend implementation required
|
|
12263
|
+
// Endpoint: POST /api/v1/authkit/:clientId/account/change-password
|
|
12264
|
+
// SDK method: smartlinks.authKit.changePassword(clientId, currentPassword, newPassword)
|
|
12265
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12266
|
+
console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-password');
|
|
12267
|
+
console.log('Data: currentPassword and newPassword provided');
|
|
12268
|
+
// Uncomment when backend is ready:
|
|
12269
|
+
// await smartlinks.authKit.changePassword(clientId, currentPassword, newPassword);
|
|
12270
|
+
// setSuccess('Password changed successfully!');
|
|
12271
|
+
// setEditingSection(null);
|
|
12272
|
+
// setCurrentPassword('');
|
|
12273
|
+
// setNewPassword('');
|
|
12274
|
+
// setConfirmPassword('');
|
|
12275
|
+
// onPasswordChanged?.();
|
|
11635
12276
|
}
|
|
11636
12277
|
catch (err) {
|
|
11637
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
12278
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to change password';
|
|
11638
12279
|
setError(errorMessage);
|
|
11639
|
-
|
|
12280
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12281
|
+
}
|
|
12282
|
+
finally {
|
|
11640
12283
|
setLoading(false);
|
|
11641
12284
|
}
|
|
11642
12285
|
};
|
|
11643
|
-
const
|
|
12286
|
+
const handleSendPhoneCode = async () => {
|
|
11644
12287
|
setLoading(true);
|
|
11645
12288
|
setError(undefined);
|
|
11646
12289
|
try {
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
11667
|
-
|
|
12290
|
+
await smartlinks__namespace.authKit.sendPhoneCode(clientId, newPhone);
|
|
12291
|
+
setPhoneCodeSent(true);
|
|
12292
|
+
setSuccess('Verification code sent to your phone');
|
|
12293
|
+
}
|
|
12294
|
+
catch (err) {
|
|
12295
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to send verification code';
|
|
12296
|
+
setError(errorMessage);
|
|
12297
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12298
|
+
}
|
|
12299
|
+
finally {
|
|
12300
|
+
setLoading(false);
|
|
12301
|
+
}
|
|
12302
|
+
};
|
|
12303
|
+
const handleUpdatePhone = async (e) => {
|
|
12304
|
+
e.preventDefault();
|
|
12305
|
+
setLoading(true);
|
|
12306
|
+
setError(undefined);
|
|
12307
|
+
setSuccess(undefined);
|
|
12308
|
+
try {
|
|
12309
|
+
// TODO: Backend implementation required
|
|
12310
|
+
// Endpoint: POST /api/v1/authkit/:clientId/account/update-phone
|
|
12311
|
+
// SDK method: smartlinks.authKit.updatePhone(clientId, phoneNumber, verificationCode)
|
|
12312
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12313
|
+
console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-phone');
|
|
12314
|
+
console.log('Data:', { phoneNumber: newPhone, code: phoneCode });
|
|
12315
|
+
// Uncomment when backend is ready:
|
|
12316
|
+
// await smartlinks.authKit.updatePhone(clientId, newPhone, phoneCode);
|
|
12317
|
+
// setSuccess('Phone number updated successfully!');
|
|
12318
|
+
// setEditingSection(null);
|
|
12319
|
+
// setNewPhone('');
|
|
12320
|
+
// setPhoneCode('');
|
|
12321
|
+
// setPhoneCodeSent(false);
|
|
12322
|
+
// await loadProfile();
|
|
11668
12323
|
}
|
|
11669
12324
|
catch (err) {
|
|
11670
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
12325
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to update phone number';
|
|
11671
12326
|
setError(errorMessage);
|
|
11672
|
-
|
|
12327
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11673
12328
|
}
|
|
11674
12329
|
finally {
|
|
11675
12330
|
setLoading(false);
|
|
11676
12331
|
}
|
|
11677
12332
|
};
|
|
11678
|
-
const
|
|
12333
|
+
const handleDeleteAccount = async () => {
|
|
12334
|
+
if (deleteConfirmText !== 'DELETE') {
|
|
12335
|
+
setError('Please type DELETE to confirm account deletion');
|
|
12336
|
+
return;
|
|
12337
|
+
}
|
|
12338
|
+
if (!deletePassword) {
|
|
12339
|
+
setError('Password is required');
|
|
12340
|
+
return;
|
|
12341
|
+
}
|
|
11679
12342
|
setLoading(true);
|
|
11680
12343
|
setError(undefined);
|
|
11681
12344
|
try {
|
|
11682
|
-
|
|
11683
|
-
|
|
11684
|
-
|
|
11685
|
-
|
|
11686
|
-
|
|
11687
|
-
|
|
11688
|
-
|
|
11689
|
-
|
|
11690
|
-
|
|
11691
|
-
|
|
11692
|
-
|
|
12345
|
+
// TODO: Backend implementation required
|
|
12346
|
+
// Endpoint: DELETE /api/v1/authkit/:clientId/account/delete
|
|
12347
|
+
// SDK method: smartlinks.authKit.deleteAccount(clientId, password, confirmText)
|
|
12348
|
+
// Note: This performs a SOFT DELETE (marks as deleted, obfuscates email)
|
|
12349
|
+
setError('Backend API not yet implemented. See console for required endpoint.');
|
|
12350
|
+
console.log('Required API endpoint: DELETE /api/v1/authkit/:clientId/account/delete');
|
|
12351
|
+
console.log('Data: password and confirmText="DELETE" provided');
|
|
12352
|
+
console.log('Note: Backend should soft delete (mark deleted, obfuscate email, disable account)');
|
|
12353
|
+
// Uncomment when backend is ready:
|
|
12354
|
+
// await smartlinks.authKit.deleteAccount(clientId, deletePassword, deleteConfirmText);
|
|
12355
|
+
// setSuccess('Account deleted successfully');
|
|
12356
|
+
// onAccountDeleted?.();
|
|
12357
|
+
// await auth.logout();
|
|
11693
12358
|
}
|
|
11694
12359
|
catch (err) {
|
|
11695
|
-
const errorMessage = err instanceof Error ? err.message : '
|
|
12360
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to delete account';
|
|
11696
12361
|
setError(errorMessage);
|
|
11697
|
-
|
|
12362
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11698
12363
|
}
|
|
11699
12364
|
finally {
|
|
11700
12365
|
setLoading(false);
|
|
11701
12366
|
}
|
|
11702
12367
|
};
|
|
11703
|
-
|
|
12368
|
+
if (!auth.isAuthenticated) {
|
|
12369
|
+
return (jsxRuntime.jsx("div", { className: `account-management ${className}`, children: jsxRuntime.jsx("p", { className: "text-muted-foreground", children: "Please log in to manage your account" }) }));
|
|
12370
|
+
}
|
|
12371
|
+
if (loading && !profile) {
|
|
12372
|
+
return (jsxRuntime.jsx("div", { className: `account-management ${className}`, children: jsxRuntime.jsx("p", { className: "text-muted-foreground", children: "Loading..." }) }));
|
|
12373
|
+
}
|
|
12374
|
+
return (jsxRuntime.jsxs("div", { className: `account-management ${className}`, style: { maxWidth: '600px' }, children: [error && (jsxRuntime.jsx("div", { className: "auth-error", role: "alert", children: error })), success && (jsxRuntime.jsx("div", { className: "auth-success", role: "alert", children: success })), showProfileSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsxs("div", { className: "account-field", children: [jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: "Display Name" }), jsxRuntime.jsx("div", { className: "field-value", children: profile?.displayName || 'Not set' })] }), editingSection !== 'profile' && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('profile'), className: "auth-button button-secondary", children: "Change" }))] }), editingSection === 'profile' && (jsxRuntime.jsxs("form", { onSubmit: handleUpdateProfile, className: "edit-form", children: [jsxRuntime.jsx("div", { className: "form-group", children: jsxRuntime.jsx("input", { id: "displayName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), placeholder: "Your display name", className: "auth-input" }) }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showEmailSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsxs("div", { className: "account-field", children: [jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: "Email Address" }), jsxRuntime.jsxs("div", { className: "field-value", children: [profile?.email || 'Not set', profile?.emailVerified && (jsxRuntime.jsx("span", { className: "verification-badge verified", children: "Verified" })), profile?.email && !profile?.emailVerified && (jsxRuntime.jsx("span", { className: "verification-badge unverified", children: "Unverified" }))] })] }), editingSection !== 'email' && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('email'), className: "auth-button button-secondary", children: "Change Email" }))] }), editingSection === 'email' && (jsxRuntime.jsxs("form", { onSubmit: handleChangeEmail, className: "edit-form", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newEmail", children: "New Email" }), jsxRuntime.jsx("input", { id: "newEmail", type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "new.email@example.com", className: "auth-input", required: true })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "emailPassword", children: "Confirm Password" }), jsxRuntime.jsx("input", { id: "emailPassword", type: "password", value: emailPassword, onChange: (e) => setEmailPassword(e.target.value), placeholder: "Enter your password", className: "auth-input", required: true })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Email' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPasswordSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsxs("div", { className: "account-field", children: [jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: "Password" }), jsxRuntime.jsx("div", { className: "field-value", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), editingSection !== 'password' && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('password'), className: "auth-button button-secondary", children: "Change Password" }))] }), editingSection === 'password' && (jsxRuntime.jsxs("form", { onSubmit: handleChangePassword, className: "edit-form", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "currentPassword", children: "Current Password" }), jsxRuntime.jsx("input", { id: "currentPassword", type: "password", value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value), placeholder: "Enter current password", className: "auth-input", required: true })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newPassword", children: "New Password" }), jsxRuntime.jsx("input", { id: "newPassword", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), placeholder: "Enter new password", className: "auth-input", required: true, minLength: 6 })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "confirmPassword", children: "Confirm New Password" }), jsxRuntime.jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), placeholder: "Confirm new password", className: "auth-input", required: true, minLength: 6 })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPhoneSection && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsxs("div", { className: "account-field", children: [jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: "Phone Number" }), jsxRuntime.jsx("div", { className: "field-value", children: profile?.phoneNumber || 'Not set' })] }), editingSection !== 'phone' && (jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('phone'), className: "auth-button button-secondary", children: "Change Phone" }))] }), editingSection === 'phone' && (jsxRuntime.jsxs("form", { onSubmit: handleUpdatePhone, className: "edit-form", children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "newPhone", children: "New Phone Number" }), jsxRuntime.jsx("input", { id: "newPhone", type: "tel", value: newPhone, onChange: (e) => setNewPhone(e.target.value), placeholder: "+1234567890", className: "auth-input", required: true })] }), !phoneCodeSent ? (jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Code' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneCode", children: "Verification Code" }), jsxRuntime.jsx("input", { id: "phoneCode", type: "text", value: phoneCode, onChange: (e) => setPhoneCode(e.target.value), placeholder: "Enter 6-digit code", className: "auth-input", required: true, maxLength: 6 })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Save' }), jsxRuntime.jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] })), showDeleteAccount && (jsxRuntime.jsxs("section", { className: "account-section danger-zone", children: [jsxRuntime.jsx("h3", { className: "section-title text-danger", children: "Danger Zone" }), !showDeleteConfirm ? (jsxRuntime.jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "auth-button button-danger", children: "Delete Account" })) : (jsxRuntime.jsxs("div", { className: "delete-confirm", children: [jsxRuntime.jsx("p", { className: "warning-text", children: "\u26A0\uFE0F This action cannot be undone. This will permanently delete your account and all associated data." }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "deletePassword", children: "Confirm Password" }), jsxRuntime.jsx("input", { id: "deletePassword", type: "password", value: deletePassword, onChange: (e) => setDeletePassword(e.target.value), placeholder: "Enter your password", className: "auth-input" })] }), jsxRuntime.jsxs("div", { className: "form-group", children: [jsxRuntime.jsx("label", { htmlFor: "deleteConfirm", children: "Type DELETE to confirm" }), jsxRuntime.jsx("input", { id: "deleteConfirm", type: "text", value: deleteConfirmText, onChange: (e) => setDeleteConfirmText(e.target.value), placeholder: "DELETE", className: "auth-input" })] }), jsxRuntime.jsxs("div", { className: "button-group", children: [jsxRuntime.jsx("button", { type: "button", onClick: handleDeleteAccount, className: "auth-button button-danger", disabled: loading, children: loading ? 'Deleting...' : 'Permanently Delete Account' }), jsxRuntime.jsx("button", { type: "button", onClick: () => {
|
|
12375
|
+
setShowDeleteConfirm(false);
|
|
12376
|
+
setDeletePassword('');
|
|
12377
|
+
setDeleteConfirmText('');
|
|
12378
|
+
}, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
|
|
12379
|
+
};
|
|
12380
|
+
|
|
12381
|
+
const SmartlinksClaimUI = ({ apiEndpoint, clientId, clientName, collectionId, productId, proofId, onClaimSuccess, onClaimError, additionalFields = [], theme = 'light', className = '', minimal = false, customization = {}, }) => {
|
|
12382
|
+
const auth = useAuth();
|
|
12383
|
+
const [claimStep, setClaimStep] = React.useState(auth.isAuthenticated ? 'questions' : 'auth');
|
|
12384
|
+
const [claimData, setClaimData] = React.useState({});
|
|
12385
|
+
const [error, setError] = React.useState();
|
|
12386
|
+
const [loading, setLoading] = React.useState(false);
|
|
12387
|
+
const handleAuthSuccess = (token, user, accountData) => {
|
|
12388
|
+
// Authentication successful
|
|
12389
|
+
auth.login(token, user, accountData);
|
|
12390
|
+
// If no additional questions, proceed directly to claim
|
|
12391
|
+
if (additionalFields.length === 0) {
|
|
12392
|
+
executeClaim(user);
|
|
12393
|
+
}
|
|
12394
|
+
else {
|
|
12395
|
+
setClaimStep('questions');
|
|
12396
|
+
}
|
|
12397
|
+
};
|
|
12398
|
+
const handleQuestionSubmit = async (e) => {
|
|
12399
|
+
e.preventDefault();
|
|
12400
|
+
// Validate required fields
|
|
12401
|
+
const missingFields = additionalFields
|
|
12402
|
+
.filter(field => field.required && !claimData[field.name])
|
|
12403
|
+
.map(field => field.label);
|
|
12404
|
+
if (missingFields.length > 0) {
|
|
12405
|
+
setError(`Please fill in: ${missingFields.join(', ')}`);
|
|
12406
|
+
return;
|
|
12407
|
+
}
|
|
12408
|
+
// Execute claim with collected data
|
|
12409
|
+
if (auth.user) {
|
|
12410
|
+
executeClaim(auth.user);
|
|
12411
|
+
}
|
|
12412
|
+
};
|
|
12413
|
+
const executeClaim = async (user) => {
|
|
12414
|
+
setClaimStep('claiming');
|
|
11704
12415
|
setLoading(true);
|
|
11705
12416
|
setError(undefined);
|
|
11706
12417
|
try {
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
12418
|
+
// Create attestation to claim the proof
|
|
12419
|
+
const response = await smartlinks__namespace.attestation.create(collectionId, productId, proofId, {
|
|
12420
|
+
public: {
|
|
12421
|
+
claimed: true,
|
|
12422
|
+
claimedAt: new Date().toISOString(),
|
|
12423
|
+
claimedBy: user.uid,
|
|
12424
|
+
...claimData,
|
|
12425
|
+
},
|
|
12426
|
+
private: {},
|
|
12427
|
+
proof: {},
|
|
12428
|
+
});
|
|
12429
|
+
setClaimStep('success');
|
|
12430
|
+
// Call success callback
|
|
12431
|
+
onClaimSuccess({
|
|
12432
|
+
proofId,
|
|
12433
|
+
user,
|
|
12434
|
+
claimData,
|
|
12435
|
+
attestationId: response.id,
|
|
12436
|
+
});
|
|
11710
12437
|
}
|
|
11711
12438
|
catch (err) {
|
|
11712
|
-
|
|
12439
|
+
console.error('Claim error:', err);
|
|
12440
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to claim proof';
|
|
11713
12441
|
setError(errorMessage);
|
|
11714
|
-
|
|
12442
|
+
onClaimError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
12443
|
+
setClaimStep(additionalFields.length > 0 ? 'questions' : 'auth');
|
|
11715
12444
|
}
|
|
11716
12445
|
finally {
|
|
11717
12446
|
setLoading(false);
|
|
11718
12447
|
}
|
|
11719
12448
|
};
|
|
11720
|
-
|
|
11721
|
-
|
|
12449
|
+
const handleFieldChange = (fieldName, value) => {
|
|
12450
|
+
setClaimData(prev => ({
|
|
12451
|
+
...prev,
|
|
12452
|
+
[fieldName]: value,
|
|
12453
|
+
}));
|
|
12454
|
+
};
|
|
12455
|
+
// Render authentication step
|
|
12456
|
+
if (claimStep === 'auth') {
|
|
12457
|
+
return (jsxRuntime.jsx("div", { className: className, children: jsxRuntime.jsx(SmartlinksAuthUI, { apiEndpoint: apiEndpoint, clientId: clientId, clientName: clientName, onAuthSuccess: handleAuthSuccess, onAuthError: onClaimError, theme: theme, minimal: minimal, customization: customization.authConfig }) }));
|
|
11722
12458
|
}
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
|
|
11778
|
-
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
color: 'white',
|
|
11786
|
-
border: 'none',
|
|
11787
|
-
borderRadius: '0.375rem',
|
|
11788
|
-
cursor: (loading || !resetRequestEmail) ? 'not-allowed' : 'pointer',
|
|
11789
|
-
fontSize: '0.875rem',
|
|
11790
|
-
fontWeight: 500,
|
|
11791
|
-
opacity: (loading || !resetRequestEmail) ? 0.6 : 1
|
|
11792
|
-
}, children: loading ? 'Sending...' : 'Send New Reset Link' }), jsxRuntime.jsx("button", { onClick: () => {
|
|
11793
|
-
setShowRequestNewReset(false);
|
|
11794
|
-
setResetRequestEmail('');
|
|
11795
|
-
setError(undefined);
|
|
11796
|
-
}, disabled: loading, style: {
|
|
11797
|
-
padding: '0.625rem 1rem',
|
|
11798
|
-
backgroundColor: 'transparent',
|
|
11799
|
-
color: 'var(--auth-text-color, #6B7280)',
|
|
11800
|
-
border: '1px solid var(--auth-border-color, #D1D5DB)',
|
|
11801
|
-
borderRadius: '0.375rem',
|
|
11802
|
-
cursor: loading ? 'not-allowed' : 'pointer',
|
|
11803
|
-
fontSize: '0.875rem',
|
|
11804
|
-
fontWeight: 500,
|
|
11805
|
-
opacity: loading ? 0.6 : 1
|
|
11806
|
-
}, children: "Cancel" })] })] })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: (() => {
|
|
11807
|
-
const emailDisplayMode = config?.emailDisplayMode || 'form';
|
|
11808
|
-
const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
|
|
11809
|
-
const actualProviders = config?.enabledProviders || enabledProviders;
|
|
11810
|
-
// Button mode: show provider selection first, then email form if email is selected
|
|
11811
|
-
if (emailDisplayMode === 'button' && !showEmailForm) {
|
|
11812
|
-
return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
11813
|
-
}
|
|
11814
|
-
// Form mode or email button was clicked: show email form with other providers
|
|
11815
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [emailDisplayMode === 'button' && showEmailForm && (jsxRuntime.jsx("button", { onClick: () => setShowEmailForm(false), style: {
|
|
11816
|
-
marginBottom: '1rem',
|
|
11817
|
-
padding: '0.5rem',
|
|
11818
|
-
background: 'none',
|
|
11819
|
-
border: 'none',
|
|
11820
|
-
color: 'var(--auth-text-color, #6B7280)',
|
|
11821
|
-
cursor: 'pointer',
|
|
11822
|
-
fontSize: '0.875rem',
|
|
11823
|
-
display: 'flex',
|
|
11824
|
-
alignItems: 'center',
|
|
11825
|
-
gap: '0.25rem'
|
|
11826
|
-
}, children: "\u2190 Back to options" })), jsxRuntime.jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
|
|
11827
|
-
setMode(mode === 'login' ? 'register' : 'login');
|
|
11828
|
-
setShowResendVerification(false);
|
|
11829
|
-
setShowRequestNewReset(false);
|
|
11830
|
-
setError(undefined);
|
|
11831
|
-
}, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
11832
|
-
})() })) })) : null }));
|
|
12459
|
+
// Render additional questions step
|
|
12460
|
+
if (claimStep === 'questions') {
|
|
12461
|
+
return (jsxRuntime.jsxs("div", { className: `claim-questions ${className}`, children: [jsxRuntime.jsxs("div", { className: "claim-header mb-6", children: [jsxRuntime.jsx("h2", { className: "text-2xl font-bold mb-2", children: customization.claimTitle || 'Complete Your Claim' }), customization.claimDescription && (jsxRuntime.jsx("p", { className: "text-muted-foreground", children: customization.claimDescription }))] }), error && (jsxRuntime.jsx("div", { className: "claim-error bg-destructive/10 text-destructive px-4 py-3 rounded-md mb-4", children: error })), jsxRuntime.jsxs("form", { onSubmit: handleQuestionSubmit, className: "claim-form space-y-4", children: [additionalFields.map((field) => (jsxRuntime.jsxs("div", { className: "claim-field", children: [jsxRuntime.jsxs("label", { htmlFor: field.name, className: "block text-sm font-medium mb-2", children: [field.label, field.required && jsxRuntime.jsx("span", { className: "text-destructive ml-1", children: "*" })] }), field.type === 'textarea' ? (jsxRuntime.jsx("textarea", { id: field.name, name: field.name, placeholder: field.placeholder, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background", rows: 4 })) : field.type === 'select' ? (jsxRuntime.jsxs("select", { id: field.name, name: field.name, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background", children: [jsxRuntime.jsx("option", { value: "", children: "Select..." }), field.options?.map((option) => (jsxRuntime.jsx("option", { value: option, children: option }, option)))] })) : (jsxRuntime.jsx("input", { id: field.name, name: field.name, type: field.type, placeholder: field.placeholder, value: claimData[field.name] || '', onChange: (e) => handleFieldChange(field.name, e.target.value), required: field.required, className: "w-full px-3 py-2 border border-input rounded-md bg-background" }))] }, field.name))), jsxRuntime.jsx("button", { type: "submit", disabled: loading, className: "claim-submit-button w-full bg-primary text-primary-foreground px-4 py-2 rounded-md font-medium hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed", children: loading ? 'Claiming...' : 'Submit Claim' })] })] }));
|
|
12462
|
+
}
|
|
12463
|
+
// Render claiming step (loading state)
|
|
12464
|
+
if (claimStep === 'claiming') {
|
|
12465
|
+
return (jsxRuntime.jsxs("div", { className: `claim-loading ${className} flex flex-col items-center justify-center py-12`, children: [jsxRuntime.jsx("div", { className: "claim-spinner w-12 h-12 border-4 border-primary border-t-transparent rounded-full animate-spin mb-4" }), jsxRuntime.jsx("p", { className: "text-muted-foreground", children: "Claiming your product..." })] }));
|
|
12466
|
+
}
|
|
12467
|
+
// Render success step
|
|
12468
|
+
if (claimStep === 'success') {
|
|
12469
|
+
return (jsxRuntime.jsxs("div", { className: `claim-success ${className} text-center py-12`, children: [jsxRuntime.jsx("div", { className: "claim-success-icon w-16 h-16 bg-green-500 text-white rounded-full flex items-center justify-center text-3xl font-bold mx-auto mb-4", children: "\u2713" }), jsxRuntime.jsx("h2", { className: "text-2xl font-bold mb-2", children: "Claim Successful!" }), jsxRuntime.jsx("p", { className: "text-muted-foreground", children: customization.successMessage || 'Your product has been successfully claimed and registered to your account.' })] }));
|
|
12470
|
+
}
|
|
12471
|
+
return null;
|
|
12472
|
+
};
|
|
12473
|
+
|
|
12474
|
+
const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
|
|
12475
|
+
const { isAuthenticated, isLoading } = useAuth();
|
|
12476
|
+
// Show loading state
|
|
12477
|
+
if (isLoading) {
|
|
12478
|
+
return jsxRuntime.jsx("div", { children: "Loading..." });
|
|
12479
|
+
}
|
|
12480
|
+
// If not authenticated, redirect or show fallback
|
|
12481
|
+
if (!isAuthenticated) {
|
|
12482
|
+
if (redirectTo) {
|
|
12483
|
+
window.location.href = redirectTo;
|
|
12484
|
+
return null;
|
|
12485
|
+
}
|
|
12486
|
+
return fallback ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }) : jsxRuntime.jsx("div", { children: "Access denied. Please log in." });
|
|
12487
|
+
}
|
|
12488
|
+
// Render protected content
|
|
12489
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
|
|
12490
|
+
};
|
|
12491
|
+
|
|
12492
|
+
const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], providerOrder, emailDisplayMode = 'form', theme = 'light', className, minimal = false, }) => {
|
|
12493
|
+
const showEmail = enabledProviders.includes('email');
|
|
12494
|
+
const showGoogle = enabledProviders.includes('google');
|
|
12495
|
+
const showPhone = enabledProviders.includes('phone');
|
|
12496
|
+
const showMagicLink = enabledProviders.includes('magic-link');
|
|
12497
|
+
// Determine ordered providers (excluding email if in button mode)
|
|
12498
|
+
const orderedProviders = providerOrder && providerOrder.length > 0
|
|
12499
|
+
? providerOrder.filter(p => enabledProviders.includes(p) && p !== 'email')
|
|
12500
|
+
: enabledProviders.filter(p => p !== 'email');
|
|
12501
|
+
const hasOtherProviders = showGoogle || showPhone || showMagicLink;
|
|
12502
|
+
// Render provider button helper
|
|
12503
|
+
const renderProviderButton = (provider) => {
|
|
12504
|
+
if (provider === 'google' && showGoogle) {
|
|
12505
|
+
return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", children: [jsxRuntime.jsx("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z", fill: "#4285F4" }), jsxRuntime.jsx("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.183l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332C2.438 15.983 5.482 18 9 18z", fill: "#34A853" }), jsxRuntime.jsx("path", { d: "M3.964 10.707c-.18-.54-.282-1.117-.282-1.707 0-.593.102-1.167.282-1.707V4.961H.957C.347 6.175 0 7.548 0 9s.348 2.825.957 4.039l3.007-2.332z", fill: "#FBBC05" }), jsxRuntime.jsx("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0 5.482 0 2.438 2.017.957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z", fill: "#EA4335" })] }), jsxRuntime.jsx("span", { children: "Continue with Google" })] }, "google"));
|
|
12506
|
+
}
|
|
12507
|
+
if (provider === 'phone' && showPhone) {
|
|
12508
|
+
return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsxRuntime.jsx("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" }) }), jsxRuntime.jsx("span", { children: "Continue with Phone" })] }, "phone"));
|
|
12509
|
+
}
|
|
12510
|
+
if (provider === 'magic-link' && showMagicLink) {
|
|
12511
|
+
return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) }), jsxRuntime.jsx("span", { children: "Continue with Magic Link" })] }, "magic-link"));
|
|
12512
|
+
}
|
|
12513
|
+
if (provider === 'email' && showEmail && emailDisplayMode === 'button') {
|
|
12514
|
+
return (jsxRuntime.jsxs("button", { className: "auth-provider-button", disabled: true, children: [jsxRuntime.jsx("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) }), jsxRuntime.jsx("span", { children: "Continue with Email" })] }, "email"));
|
|
12515
|
+
}
|
|
12516
|
+
return null;
|
|
12517
|
+
};
|
|
12518
|
+
return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, config: customization, minimal: minimal, children: emailDisplayMode === 'button' ? (jsxRuntime.jsx("div", { className: "auth-provider-buttons", children: orderedProviders.concat(showEmail ? ['email'] : []).map(provider => renderProviderButton(provider)) })) : (
|
|
12519
|
+
/* Form mode: show email form first, then other providers */
|
|
12520
|
+
jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showEmail && (jsxRuntime.jsxs("div", { className: "auth-form", children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { className: "auth-label", children: "Email" }), jsxRuntime.jsx("input", { type: "email", className: "auth-input", placeholder: "Enter your email", disabled: true })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { className: "auth-label", children: "Password" }), jsxRuntime.jsx("input", { type: "password", className: "auth-input", placeholder: "Enter your password", disabled: true })] }), jsxRuntime.jsx("button", { className: "auth-button auth-button-primary", disabled: true, children: "Sign In" }), jsxRuntime.jsx("div", { style: { textAlign: 'center', marginTop: '1rem' }, children: jsxRuntime.jsx("button", { className: "auth-link", disabled: true, children: "Forgot password?" }) }), jsxRuntime.jsxs("div", { style: { textAlign: 'center', marginTop: '0.5rem', fontSize: '0.875rem', color: 'var(--auth-text-muted, #6B7280)' }, children: ["Don't have an account?", ' ', jsxRuntime.jsx("button", { className: "auth-link", disabled: true, children: "Sign up" })] })] })), hasOtherProviders && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showEmail && (jsxRuntime.jsx("div", { className: "auth-or-divider", children: jsxRuntime.jsx("span", { children: "or continue with" }) })), jsxRuntime.jsx("div", { className: "auth-provider-buttons", children: orderedProviders.map(provider => renderProviderButton(provider)) })] }))] })) }));
|
|
11833
12521
|
};
|
|
11834
12522
|
|
|
11835
12523
|
exports.AccountManagement = AccountManagement;
|
|
@@ -11838,6 +12526,7 @@ exports.AuthUIPreview = AuthUIPreview;
|
|
|
11838
12526
|
exports.FirebaseAuthUI = SmartlinksAuthUI;
|
|
11839
12527
|
exports.ProtectedRoute = ProtectedRoute;
|
|
11840
12528
|
exports.SmartlinksAuthUI = SmartlinksAuthUI;
|
|
12529
|
+
exports.SmartlinksClaimUI = SmartlinksClaimUI;
|
|
11841
12530
|
exports.tokenStorage = tokenStorage;
|
|
11842
12531
|
exports.useAuth = useAuth;
|
|
11843
12532
|
//# sourceMappingURL=index.js.map
|