@proveanything/smartlinks-auth-ui 0.1.3 → 0.1.4
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/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 +1 -0
- 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 +804 -116
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +804 -115
- 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 +1 -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
|
+
? '/smartlinks-logo.png' // Default
|
|
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,7 +11432,7 @@ const useAuth = () => {
|
|
|
10840
11432
|
return context;
|
|
10841
11433
|
};
|
|
10842
11434
|
|
|
10843
|
-
const AccountManagement = ({ apiEndpoint, clientId, onProfileUpdated, onEmailChangeRequested, onPasswordChanged, onAccountDeleted, onError, theme = 'light', className = '', customization = {}, }) => {
|
|
11435
|
+
const AccountManagement = ({ apiEndpoint, clientId, onProfileUpdated, onEmailChangeRequested, onPasswordChanged, onAccountDeleted, onError, theme = 'light', className = '', customization = {}, minimal = false, }) => {
|
|
10844
11436
|
const auth = useAuth();
|
|
10845
11437
|
const [loading, setLoading] = React.useState(false);
|
|
10846
11438
|
const [profile, setProfile] = React.useState(null);
|
|
@@ -11123,6 +11715,99 @@ const AccountManagement = ({ apiEndpoint, clientId, onProfileUpdated, onEmailCha
|
|
|
11123
11715
|
}, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
|
|
11124
11716
|
};
|
|
11125
11717
|
|
|
11718
|
+
const SmartlinksClaimUI = ({ apiEndpoint, clientId, clientName, collectionId, productId, proofId, onClaimSuccess, onClaimError, additionalFields = [], theme = 'light', className = '', minimal = false, customization = {}, }) => {
|
|
11719
|
+
const auth = useAuth();
|
|
11720
|
+
const [claimStep, setClaimStep] = React.useState(auth.isAuthenticated ? 'questions' : 'auth');
|
|
11721
|
+
const [claimData, setClaimData] = React.useState({});
|
|
11722
|
+
const [error, setError] = React.useState();
|
|
11723
|
+
const [loading, setLoading] = React.useState(false);
|
|
11724
|
+
const handleAuthSuccess = (token, user, accountData) => {
|
|
11725
|
+
// Authentication successful
|
|
11726
|
+
auth.login(token, user, accountData);
|
|
11727
|
+
// If no additional questions, proceed directly to claim
|
|
11728
|
+
if (additionalFields.length === 0) {
|
|
11729
|
+
executeClaim(user);
|
|
11730
|
+
}
|
|
11731
|
+
else {
|
|
11732
|
+
setClaimStep('questions');
|
|
11733
|
+
}
|
|
11734
|
+
};
|
|
11735
|
+
const handleQuestionSubmit = async (e) => {
|
|
11736
|
+
e.preventDefault();
|
|
11737
|
+
// Validate required fields
|
|
11738
|
+
const missingFields = additionalFields
|
|
11739
|
+
.filter(field => field.required && !claimData[field.name])
|
|
11740
|
+
.map(field => field.label);
|
|
11741
|
+
if (missingFields.length > 0) {
|
|
11742
|
+
setError(`Please fill in: ${missingFields.join(', ')}`);
|
|
11743
|
+
return;
|
|
11744
|
+
}
|
|
11745
|
+
// Execute claim with collected data
|
|
11746
|
+
if (auth.user) {
|
|
11747
|
+
executeClaim(auth.user);
|
|
11748
|
+
}
|
|
11749
|
+
};
|
|
11750
|
+
const executeClaim = async (user) => {
|
|
11751
|
+
setClaimStep('claiming');
|
|
11752
|
+
setLoading(true);
|
|
11753
|
+
setError(undefined);
|
|
11754
|
+
try {
|
|
11755
|
+
// Create attestation to claim the proof
|
|
11756
|
+
const response = await smartlinks__namespace.attestation.create(collectionId, productId, proofId, {
|
|
11757
|
+
public: {
|
|
11758
|
+
claimed: true,
|
|
11759
|
+
claimedAt: new Date().toISOString(),
|
|
11760
|
+
claimedBy: user.uid,
|
|
11761
|
+
...claimData,
|
|
11762
|
+
},
|
|
11763
|
+
private: {},
|
|
11764
|
+
proof: {},
|
|
11765
|
+
});
|
|
11766
|
+
setClaimStep('success');
|
|
11767
|
+
// Call success callback
|
|
11768
|
+
onClaimSuccess({
|
|
11769
|
+
proofId,
|
|
11770
|
+
user,
|
|
11771
|
+
claimData,
|
|
11772
|
+
attestationId: response.id,
|
|
11773
|
+
});
|
|
11774
|
+
}
|
|
11775
|
+
catch (err) {
|
|
11776
|
+
console.error('Claim error:', err);
|
|
11777
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to claim proof';
|
|
11778
|
+
setError(errorMessage);
|
|
11779
|
+
onClaimError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11780
|
+
setClaimStep(additionalFields.length > 0 ? 'questions' : 'auth');
|
|
11781
|
+
}
|
|
11782
|
+
finally {
|
|
11783
|
+
setLoading(false);
|
|
11784
|
+
}
|
|
11785
|
+
};
|
|
11786
|
+
const handleFieldChange = (fieldName, value) => {
|
|
11787
|
+
setClaimData(prev => ({
|
|
11788
|
+
...prev,
|
|
11789
|
+
[fieldName]: value,
|
|
11790
|
+
}));
|
|
11791
|
+
};
|
|
11792
|
+
// Render authentication step
|
|
11793
|
+
if (claimStep === 'auth') {
|
|
11794
|
+
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 }) }));
|
|
11795
|
+
}
|
|
11796
|
+
// Render additional questions step
|
|
11797
|
+
if (claimStep === 'questions') {
|
|
11798
|
+
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' })] })] }));
|
|
11799
|
+
}
|
|
11800
|
+
// Render claiming step (loading state)
|
|
11801
|
+
if (claimStep === 'claiming') {
|
|
11802
|
+
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..." })] }));
|
|
11803
|
+
}
|
|
11804
|
+
// Render success step
|
|
11805
|
+
if (claimStep === 'success') {
|
|
11806
|
+
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.' })] }));
|
|
11807
|
+
}
|
|
11808
|
+
return null;
|
|
11809
|
+
};
|
|
11810
|
+
|
|
11126
11811
|
const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
|
|
11127
11812
|
const { isAuthenticated, isLoading } = useAuth();
|
|
11128
11813
|
// Show loading state
|
|
@@ -11141,7 +11826,7 @@ const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
|
|
|
11141
11826
|
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
|
|
11142
11827
|
};
|
|
11143
11828
|
|
|
11144
|
-
const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], providerOrder, emailDisplayMode = 'form', theme = 'light', className, }) => {
|
|
11829
|
+
const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], providerOrder, emailDisplayMode = 'form', theme = 'light', className, minimal = false, }) => {
|
|
11145
11830
|
const showEmail = enabledProviders.includes('email');
|
|
11146
11831
|
const showGoogle = enabledProviders.includes('google');
|
|
11147
11832
|
const showPhone = enabledProviders.includes('phone');
|
|
@@ -11167,14 +11852,14 @@ const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', '
|
|
|
11167
11852
|
}
|
|
11168
11853
|
return null;
|
|
11169
11854
|
};
|
|
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)) })) : (
|
|
11855
|
+
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)) })) : (
|
|
11171
11856
|
/* Form mode: show email form first, then other providers */
|
|
11172
11857
|
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
11858
|
};
|
|
11174
11859
|
|
|
11175
11860
|
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
11176
11861
|
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, }) => {
|
|
11862
|
+
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, minimal = false, }) => {
|
|
11178
11863
|
const [mode, setMode] = React.useState(initialMode);
|
|
11179
11864
|
const [loading, setLoading] = React.useState(false);
|
|
11180
11865
|
const [error, setError] = React.useState();
|
|
@@ -11194,21 +11879,24 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11194
11879
|
// Reinitialize Smartlinks SDK when apiEndpoint changes (for test/dev scenarios)
|
|
11195
11880
|
// IMPORTANT: Preserve bearer token during reinitialization
|
|
11196
11881
|
React.useEffect(() => {
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
11202
|
-
|
|
11203
|
-
|
|
11204
|
-
|
|
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);
|
|
11882
|
+
const reinitializeWithToken = async () => {
|
|
11883
|
+
if (apiEndpoint) {
|
|
11884
|
+
// Get current token before reinitializing
|
|
11885
|
+
const currentToken = await auth.getToken();
|
|
11886
|
+
smartlinks__namespace.initializeApi({
|
|
11887
|
+
baseURL: apiEndpoint,
|
|
11888
|
+
proxyMode: false, // Direct API calls when custom endpoint is provided
|
|
11889
|
+
ngrokSkipBrowserWarning: true,
|
|
11209
11890
|
});
|
|
11891
|
+
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11892
|
+
if (currentToken) {
|
|
11893
|
+
smartlinks__namespace.auth.verifyToken(currentToken).catch(err => {
|
|
11894
|
+
console.warn('Failed to restore bearer token after reinit:', err);
|
|
11895
|
+
});
|
|
11896
|
+
}
|
|
11210
11897
|
}
|
|
11211
|
-
}
|
|
11898
|
+
};
|
|
11899
|
+
reinitializeWithToken();
|
|
11212
11900
|
}, [apiEndpoint, auth]);
|
|
11213
11901
|
// Get the effective redirect URL (use prop or default to current page)
|
|
11214
11902
|
const getRedirectUrl = () => {
|
|
@@ -11718,9 +12406,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11718
12406
|
}
|
|
11719
12407
|
};
|
|
11720
12408
|
if (configLoading) {
|
|
11721
|
-
return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, children: jsxRuntime.jsx("div", { style: { textAlign: 'center', padding: '2rem' }, children: jsxRuntime.jsx("div", { className: "auth-spinner" }) }) }));
|
|
12409
|
+
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" }) }) }));
|
|
11722
12410
|
}
|
|
11723
|
-
return (jsxRuntime.jsx(AuthContainer, { theme: theme, className: className, config: config, children: authSuccess ? (jsxRuntime.jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [jsxRuntime.jsx("div", { style: {
|
|
12411
|
+
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: {
|
|
11724
12412
|
color: 'var(--auth-primary-color, #4F46E5)',
|
|
11725
12413
|
fontSize: '3rem',
|
|
11726
12414
|
marginBottom: '1rem'
|
|
@@ -11828,7 +12516,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11828
12516
|
setShowResendVerification(false);
|
|
11829
12517
|
setShowRequestNewReset(false);
|
|
11830
12518
|
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 }))] }));
|
|
12519
|
+
}, 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 }))] }));
|
|
11832
12520
|
})() })) })) : null }));
|
|
11833
12521
|
};
|
|
11834
12522
|
|
|
@@ -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
|