@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/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
- const logoUrl = config?.branding?.logoUrl || '/smartlinks-logo.png';
67
- return (jsxRuntime.jsx("div", { className: `auth-container auth-theme-${theme} ${className}`, children: jsxRuntime.jsxs("div", { className: "auth-card", style: { borderRadius: config?.branding?.buttonStyle === 'square' ? '4px' : '12px' }, children: [(jsxRuntime.jsxs("div", { className: "auth-header", children: [(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" })] }))] }) }));
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
- const TOKEN_KEY = 'smartlinks_auth_token';
10697
- const USER_KEY = 'smartlinks_auth_user';
10698
- const ACCOUNT_DATA_KEY = 'smartlinks_account_data';
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
- localStorage.setItem(TOKEN_KEY, JSON.stringify(authToken));
11114
+ await storage.setItem(TOKEN_KEY, authToken);
10706
11115
  },
10707
- getToken() {
10708
- const stored = localStorage.getItem(TOKEN_KEY);
10709
- if (!stored)
11116
+ async getToken() {
11117
+ const storage = await getStorage();
11118
+ const authToken = await storage.getItem(TOKEN_KEY);
11119
+ if (!authToken)
10710
11120
  return null;
10711
- try {
10712
- const authToken = JSON.parse(stored);
10713
- // Check if token is expired
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
- localStorage.removeItem(TOKEN_KEY);
11128
+ async clearToken() {
11129
+ const storage = await getStorage();
11130
+ await storage.removeItem(TOKEN_KEY);
10726
11131
  },
10727
- saveUser(user) {
10728
- localStorage.setItem(USER_KEY, JSON.stringify(user));
11132
+ async saveUser(user) {
11133
+ const storage = await getStorage();
11134
+ await storage.setItem(USER_KEY, user);
10729
11135
  },
10730
- getUser() {
10731
- const stored = localStorage.getItem(USER_KEY);
10732
- if (!stored)
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
- localStorage.removeItem(USER_KEY);
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
- localStorage.setItem(ACCOUNT_DATA_KEY, JSON.stringify(data));
11150
+ async saveAccountData(data) {
11151
+ const storage = await getStorage();
11152
+ await storage.setItem(ACCOUNT_DATA_KEY, data);
10751
11153
  },
10752
- getAccountData() {
10753
- const stored = localStorage.getItem(ACCOUNT_DATA_KEY);
10754
- if (!stored)
10755
- return null;
10756
- try {
10757
- return JSON.parse(stored);
10758
- }
10759
- catch {
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
- clearAccountData() {
10764
- localStorage.removeItem(ACCOUNT_DATA_KEY);
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
- // Initialize auth state from localStorage
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
- const storedToken = tokenStorage.getToken();
10777
- const storedUser = tokenStorage.getUser();
10778
- const storedAccountData = tokenStorage.getAccountData();
10779
- if (storedToken && storedUser) {
10780
- setToken(storedToken.token);
10781
- setUser(storedUser);
10782
- setAccountData(storedAccountData);
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
- smartlinks__namespace.auth.verifyToken(storedToken.token).catch(err => {
10785
- console.warn('Failed to restore bearer token on init:', err);
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
- setIsLoading(false);
10789
- }, []);
10790
- const login = React.useCallback((authToken, authUser, authAccountData) => {
10791
- // Store token, user, and account data
10792
- tokenStorage.saveToken(authToken);
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
- // Clear local storage
10808
- tokenStorage.clearAll();
10809
- setToken(null);
10810
- setUser(null);
10811
- setAccountData(null);
10812
- // Clear bearer token from global Smartlinks SDK
10813
- smartlinks__namespace.auth.logout();
10814
- }, []);
10815
- const getToken = React.useCallback(() => {
10816
- const storedToken = tokenStorage.getToken();
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
- if (apiEndpoint) {
11198
- // Get current token before reinitializing
11199
- const currentToken = auth.getToken();
11200
- smartlinks__namespace.initializeApi({
11201
- baseURL: apiEndpoint,
11202
- proxyMode: false, // Direct API calls when custom endpoint is provided
11203
- ngrokSkipBrowserWarning: true,
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