@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.esm.js CHANGED
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import React, { useEffect, useState, useRef, useCallback, useMemo, createContext, useContext } from 'react';
3
3
  import * as smartlinks from '@proveanything/smartlinks';
4
4
 
5
- const AuthContainer = ({ children, theme = 'light', className = '', config, }) => {
5
+ const AuthContainer = ({ children, theme = 'light', className = '', config, minimal = false, }) => {
6
6
  // Apply CSS variables for customization
7
7
  useEffect(() => {
8
8
  if (!config?.branding)
@@ -42,11 +42,21 @@ const AuthContainer = ({ children, theme = 'light', className = '', config, }) =
42
42
  }, [config]);
43
43
  const title = config?.branding?.title;
44
44
  const subtitle = config?.branding?.subtitle;
45
- const logoUrl = config?.branding?.logoUrl || '/smartlinks-logo.png';
46
- return (jsx("div", { className: `auth-container auth-theme-${theme} ${className}`, children: jsxs("div", { className: "auth-card", style: { borderRadius: config?.branding?.buttonStyle === 'square' ? '4px' : '12px' }, children: [(jsxs("div", { className: "auth-header", children: [(jsx("div", { className: "auth-logo", children: jsx("img", { src: logoUrl, alt: "Logo", style: { maxWidth: '200px', height: 'auto', objectFit: 'contain' } }) })), title && jsx("h1", { className: "auth-title", children: title }), subtitle && jsx("p", { className: "auth-subtitle", children: subtitle })] })), jsx("div", { className: "auth-content", children: children }), (config?.branding?.termsUrl || config?.branding?.privacyUrl) && (jsxs("div", { className: "auth-footer", children: [config.branding.termsUrl && jsx("a", { href: config.branding.termsUrl, target: "_blank", rel: "noopener noreferrer", children: "Terms" }), config.branding.termsUrl && config.branding.privacyUrl && jsx("span", { children: "\u2022" }), config.branding.privacyUrl && jsx("a", { href: config.branding.privacyUrl, target: "_blank", rel: "noopener noreferrer", children: "Privacy" })] }))] }) }));
45
+ // Logo URL logic:
46
+ // - undefined/not set: use default Smartlinks logo
47
+ // - null or empty string: hide logo completely
48
+ // - string URL: use custom logo
49
+ const logoUrl = config?.branding?.logoUrl === undefined
50
+ ? '/smartlinks-logo.png' // Default
51
+ : config?.branding?.logoUrl || null; // Custom or explicitly hidden
52
+ const containerClass = minimal
53
+ ? `auth-minimal auth-theme-${theme} ${className}`
54
+ : `auth-container auth-theme-${theme} ${className}`;
55
+ const cardClass = minimal ? 'auth-minimal-card' : 'auth-card';
56
+ return (jsx("div", { className: containerClass, children: jsxs("div", { className: cardClass, style: !minimal && config?.branding?.buttonStyle === 'square' ? { borderRadius: '4px' } : undefined, children: [(logoUrl || title || subtitle) && (jsxs("div", { className: "auth-header", children: [logoUrl && (jsx("div", { className: "auth-logo", children: jsx("img", { src: logoUrl, alt: "Logo", style: { maxWidth: '200px', height: 'auto', objectFit: 'contain' } }) })), title && jsx("h1", { className: "auth-title", children: title }), subtitle && jsx("p", { className: "auth-subtitle", children: subtitle })] })), jsx("div", { className: "auth-content", children: children }), (config?.branding?.termsUrl || config?.branding?.privacyUrl) && (jsxs("div", { className: "auth-footer", children: [config.branding.termsUrl && jsx("a", { href: config.branding.termsUrl, target: "_blank", rel: "noopener noreferrer", children: "Terms" }), config.branding.termsUrl && config.branding.privacyUrl && jsx("span", { children: "\u2022" }), config.branding.privacyUrl && jsx("a", { href: config.branding.privacyUrl, target: "_blank", rel: "noopener noreferrer", children: "Privacy" })] }))] }) }));
47
57
  };
48
58
 
49
- const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, }) => {
59
+ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, additionalFields = [], }) => {
50
60
  const [formData, setFormData] = useState({
51
61
  email: '',
52
62
  password: '',
@@ -61,7 +71,7 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
61
71
  };
62
72
  return (jsxs("form", { className: "auth-form", onSubmit: handleSubmit, children: [jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: mode === 'login' ? 'Sign in' : 'Create account' }), jsx("p", { className: "auth-form-subtitle", children: mode === 'login'
63
73
  ? 'Welcome back! Please enter your credentials.'
64
- : 'Get started by creating your account.' })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: 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' && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), 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" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), 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" })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), 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' && (jsx("div", { className: "auth-form-footer", children: jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), jsxs("div", { className: "auth-divider", children: [jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] })] }));
74
+ : 'Get started by creating your account.' })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: 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' && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), 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" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), 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" })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), 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) => (jsxs("div", { className: "auth-form-group", children: [jsxs("label", { htmlFor: field.name, className: "auth-label", children: [field.label, field.required && jsx("span", { style: { color: 'var(--auth-error-color, #ef4444)' }, children: " *" })] }), field.type === 'select' ? (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: [jsx("option", { value: "", children: "Select..." }), field.options?.map((option) => (jsx("option", { value: option, children: option }, option)))] })) : field.type === 'textarea' ? (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' } })) : (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' && (jsx("div", { className: "auth-form-footer", children: jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), jsxs("div", { className: "auth-divider", children: [jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] })] }));
65
75
  };
66
76
 
67
77
  const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
@@ -10672,142 +10682,724 @@ class AuthAPI {
10672
10682
  }
10673
10683
  }
10674
10684
 
10675
- const TOKEN_KEY = 'smartlinks_auth_token';
10676
- const USER_KEY = 'smartlinks_auth_user';
10677
- const ACCOUNT_DATA_KEY = 'smartlinks_account_data';
10685
+ /**
10686
+ * Persistent Storage Layer for Smartlinks Auth
10687
+ *
10688
+ * Implements a tiered persistence strategy similar to Firebase Auth:
10689
+ * 1. Primary: IndexedDB (async, large capacity, cross-tab)
10690
+ * 2. Fallback: localStorage (sync, universal support)
10691
+ *
10692
+ * Provides unified async API for all storage operations with automatic
10693
+ * fallback handling and cross-tab synchronization.
10694
+ */
10695
+ const DB_NAME = 'smartlinks_auth_db';
10696
+ const DB_VERSION = 1;
10697
+ const STORE_NAME = 'auth_state';
10698
+ const STORAGE_CHANNEL = 'smartlinks_auth_storage';
10699
+ /**
10700
+ * IndexedDB Storage Implementation
10701
+ */
10702
+ class IndexedDBStorage {
10703
+ constructor() {
10704
+ this.db = null;
10705
+ this.initPromise = null;
10706
+ this.channel = null;
10707
+ this.initPromise = this.initialize();
10708
+ // Set up BroadcastChannel for cross-tab communication
10709
+ if (typeof BroadcastChannel !== 'undefined') {
10710
+ this.channel = new BroadcastChannel(STORAGE_CHANNEL);
10711
+ }
10712
+ }
10713
+ async initialize() {
10714
+ return new Promise((resolve, reject) => {
10715
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
10716
+ request.onerror = () => {
10717
+ console.warn('IndexedDB initialization failed:', request.error);
10718
+ reject(request.error);
10719
+ };
10720
+ request.onsuccess = () => {
10721
+ this.db = request.result;
10722
+ resolve();
10723
+ };
10724
+ request.onupgradeneeded = (event) => {
10725
+ const db = event.target.result;
10726
+ // Create object store if it doesn't exist
10727
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
10728
+ db.createObjectStore(STORE_NAME);
10729
+ }
10730
+ };
10731
+ });
10732
+ }
10733
+ async ensureInitialized() {
10734
+ if (this.initPromise) {
10735
+ await this.initPromise;
10736
+ }
10737
+ }
10738
+ broadcastChange(event) {
10739
+ if (this.channel) {
10740
+ try {
10741
+ this.channel.postMessage(event);
10742
+ }
10743
+ catch (error) {
10744
+ console.warn('Failed to broadcast storage change:', error);
10745
+ }
10746
+ }
10747
+ }
10748
+ async setItem(key, value) {
10749
+ await this.ensureInitialized();
10750
+ return new Promise((resolve, reject) => {
10751
+ if (!this.db) {
10752
+ reject(new Error('IndexedDB not initialized'));
10753
+ return;
10754
+ }
10755
+ const transaction = this.db.transaction([STORE_NAME], 'readwrite');
10756
+ const store = transaction.objectStore(STORE_NAME);
10757
+ const request = store.put(value, key);
10758
+ request.onsuccess = () => {
10759
+ this.broadcastChange({
10760
+ type: 'set',
10761
+ key,
10762
+ value,
10763
+ timestamp: Date.now(),
10764
+ });
10765
+ resolve();
10766
+ };
10767
+ request.onerror = () => {
10768
+ reject(request.error);
10769
+ };
10770
+ });
10771
+ }
10772
+ async getItem(key) {
10773
+ await this.ensureInitialized();
10774
+ return new Promise((resolve, reject) => {
10775
+ if (!this.db) {
10776
+ reject(new Error('IndexedDB not initialized'));
10777
+ return;
10778
+ }
10779
+ const transaction = this.db.transaction([STORE_NAME], 'readonly');
10780
+ const store = transaction.objectStore(STORE_NAME);
10781
+ const request = store.get(key);
10782
+ request.onsuccess = () => {
10783
+ resolve(request.result !== undefined ? request.result : null);
10784
+ };
10785
+ request.onerror = () => {
10786
+ reject(request.error);
10787
+ };
10788
+ });
10789
+ }
10790
+ async removeItem(key) {
10791
+ await this.ensureInitialized();
10792
+ return new Promise((resolve, reject) => {
10793
+ if (!this.db) {
10794
+ reject(new Error('IndexedDB not initialized'));
10795
+ return;
10796
+ }
10797
+ const transaction = this.db.transaction([STORE_NAME], 'readwrite');
10798
+ const store = transaction.objectStore(STORE_NAME);
10799
+ const request = store.delete(key);
10800
+ request.onsuccess = () => {
10801
+ this.broadcastChange({
10802
+ type: 'remove',
10803
+ key,
10804
+ timestamp: Date.now(),
10805
+ });
10806
+ resolve();
10807
+ };
10808
+ request.onerror = () => {
10809
+ reject(request.error);
10810
+ };
10811
+ });
10812
+ }
10813
+ async clear() {
10814
+ await this.ensureInitialized();
10815
+ return new Promise((resolve, reject) => {
10816
+ if (!this.db) {
10817
+ reject(new Error('IndexedDB not initialized'));
10818
+ return;
10819
+ }
10820
+ const transaction = this.db.transaction([STORE_NAME], 'readwrite');
10821
+ const store = transaction.objectStore(STORE_NAME);
10822
+ const request = store.clear();
10823
+ request.onsuccess = () => {
10824
+ this.broadcastChange({
10825
+ type: 'clear',
10826
+ timestamp: Date.now(),
10827
+ });
10828
+ resolve();
10829
+ };
10830
+ request.onerror = () => {
10831
+ reject(request.error);
10832
+ };
10833
+ });
10834
+ }
10835
+ async isAvailable() {
10836
+ try {
10837
+ await this.ensureInitialized();
10838
+ return this.db !== null;
10839
+ }
10840
+ catch {
10841
+ return false;
10842
+ }
10843
+ }
10844
+ getBackend() {
10845
+ return 'indexeddb';
10846
+ }
10847
+ destroy() {
10848
+ if (this.db) {
10849
+ this.db.close();
10850
+ this.db = null;
10851
+ }
10852
+ if (this.channel) {
10853
+ this.channel.close();
10854
+ this.channel = null;
10855
+ }
10856
+ }
10857
+ }
10858
+ /**
10859
+ * localStorage Storage Implementation (Fallback)
10860
+ */
10861
+ class LocalStorageAdapter {
10862
+ constructor() {
10863
+ this.prefix = 'smartlinks_auth_';
10864
+ this.channel = null;
10865
+ // Set up BroadcastChannel for cross-tab communication
10866
+ if (typeof BroadcastChannel !== 'undefined') {
10867
+ this.channel = new BroadcastChannel(STORAGE_CHANNEL);
10868
+ }
10869
+ }
10870
+ broadcastChange(event) {
10871
+ if (this.channel) {
10872
+ try {
10873
+ this.channel.postMessage(event);
10874
+ }
10875
+ catch (error) {
10876
+ console.warn('Failed to broadcast storage change:', error);
10877
+ }
10878
+ }
10879
+ }
10880
+ async setItem(key, value) {
10881
+ try {
10882
+ const serialized = JSON.stringify(value);
10883
+ localStorage.setItem(this.prefix + key, serialized);
10884
+ this.broadcastChange({
10885
+ type: 'set',
10886
+ key,
10887
+ value,
10888
+ timestamp: Date.now(),
10889
+ });
10890
+ }
10891
+ catch (error) {
10892
+ console.error('localStorage setItem failed:', error);
10893
+ throw error;
10894
+ }
10895
+ }
10896
+ async getItem(key) {
10897
+ try {
10898
+ const stored = localStorage.getItem(this.prefix + key);
10899
+ if (!stored)
10900
+ return null;
10901
+ return JSON.parse(stored);
10902
+ }
10903
+ catch (error) {
10904
+ console.error('localStorage getItem failed:', error);
10905
+ return null;
10906
+ }
10907
+ }
10908
+ async removeItem(key) {
10909
+ try {
10910
+ localStorage.removeItem(this.prefix + key);
10911
+ this.broadcastChange({
10912
+ type: 'remove',
10913
+ key,
10914
+ timestamp: Date.now(),
10915
+ });
10916
+ }
10917
+ catch (error) {
10918
+ console.error('localStorage removeItem failed:', error);
10919
+ throw error;
10920
+ }
10921
+ }
10922
+ async clear() {
10923
+ try {
10924
+ const keys = Object.keys(localStorage);
10925
+ keys.forEach(key => {
10926
+ if (key.startsWith(this.prefix)) {
10927
+ localStorage.removeItem(key);
10928
+ }
10929
+ });
10930
+ this.broadcastChange({
10931
+ type: 'clear',
10932
+ timestamp: Date.now(),
10933
+ });
10934
+ }
10935
+ catch (error) {
10936
+ console.error('localStorage clear failed:', error);
10937
+ throw error;
10938
+ }
10939
+ }
10940
+ async isAvailable() {
10941
+ try {
10942
+ const testKey = '__smartlinks_test__';
10943
+ localStorage.setItem(testKey, 'test');
10944
+ localStorage.removeItem(testKey);
10945
+ return true;
10946
+ }
10947
+ catch {
10948
+ return false;
10949
+ }
10950
+ }
10951
+ getBackend() {
10952
+ return 'localstorage';
10953
+ }
10954
+ destroy() {
10955
+ if (this.channel) {
10956
+ this.channel.close();
10957
+ this.channel = null;
10958
+ }
10959
+ }
10960
+ }
10961
+ /**
10962
+ * In-Memory Storage Implementation (No Persistence)
10963
+ */
10964
+ class InMemoryStorage {
10965
+ constructor() {
10966
+ this.storage = new Map();
10967
+ }
10968
+ async setItem(key, value) {
10969
+ this.storage.set(key, value);
10970
+ }
10971
+ async getItem(key) {
10972
+ return this.storage.has(key) ? this.storage.get(key) : null;
10973
+ }
10974
+ async removeItem(key) {
10975
+ this.storage.delete(key);
10976
+ }
10977
+ async clear() {
10978
+ this.storage.clear();
10979
+ }
10980
+ async isAvailable() {
10981
+ return true;
10982
+ }
10983
+ getBackend() {
10984
+ return 'none';
10985
+ }
10986
+ destroy() {
10987
+ this.storage.clear();
10988
+ }
10989
+ }
10990
+ /**
10991
+ * Storage Factory - Creates appropriate storage backend with automatic fallback
10992
+ */
10993
+ class StorageFactory {
10994
+ static async createStorage() {
10995
+ if (this.instance) {
10996
+ return this.instance;
10997
+ }
10998
+ // Try IndexedDB first
10999
+ try {
11000
+ const indexedDB = new IndexedDBStorage();
11001
+ const available = await indexedDB.isAvailable();
11002
+ if (available) {
11003
+ console.log('[PersistentStorage] Using IndexedDB backend');
11004
+ this.instance = indexedDB;
11005
+ return indexedDB;
11006
+ }
11007
+ indexedDB.destroy();
11008
+ }
11009
+ catch (error) {
11010
+ console.warn('[PersistentStorage] IndexedDB failed:', error);
11011
+ }
11012
+ // Fallback to localStorage
11013
+ try {
11014
+ const localStorage = new LocalStorageAdapter();
11015
+ const available = await localStorage.isAvailable();
11016
+ if (available) {
11017
+ console.log('[PersistentStorage] Using localStorage backend (fallback)');
11018
+ this.instance = localStorage;
11019
+ return localStorage;
11020
+ }
11021
+ localStorage.destroy();
11022
+ }
11023
+ catch (error) {
11024
+ console.warn('[PersistentStorage] localStorage failed:', error);
11025
+ }
11026
+ // Final fallback to in-memory (no persistence)
11027
+ console.warn('[PersistentStorage] Using in-memory storage (no persistence)');
11028
+ this.instance = new InMemoryStorage();
11029
+ return this.instance;
11030
+ }
11031
+ static getInstance() {
11032
+ return this.instance;
11033
+ }
11034
+ static reset() {
11035
+ if (this.instance && 'destroy' in this.instance) {
11036
+ this.instance.destroy();
11037
+ }
11038
+ this.instance = null;
11039
+ }
11040
+ }
11041
+ StorageFactory.instance = null;
11042
+ /**
11043
+ * Main storage export - creates singleton storage instance
11044
+ */
11045
+ let storageInstance = null;
11046
+ let storageInitPromise = null;
11047
+ async function getStorage() {
11048
+ if (storageInstance) {
11049
+ return storageInstance;
11050
+ }
11051
+ if (!storageInitPromise) {
11052
+ storageInitPromise = StorageFactory.createStorage();
11053
+ }
11054
+ storageInstance = await storageInitPromise;
11055
+ return storageInstance;
11056
+ }
11057
+ /**
11058
+ * Listen for storage changes from other tabs
11059
+ */
11060
+ function onStorageChange(callback) {
11061
+ if (typeof BroadcastChannel === 'undefined') {
11062
+ console.warn('BroadcastChannel not supported, cross-tab sync disabled');
11063
+ return () => { };
11064
+ }
11065
+ const channel = new BroadcastChannel(STORAGE_CHANNEL);
11066
+ channel.onmessage = (event) => {
11067
+ callback(event.data);
11068
+ };
11069
+ return () => {
11070
+ channel.close();
11071
+ };
11072
+ }
11073
+
11074
+ const TOKEN_KEY = 'token';
11075
+ const USER_KEY = 'user';
11076
+ const ACCOUNT_DATA_KEY = 'account_data';
11077
+ const ACCOUNT_INFO_KEY = 'account_info';
11078
+ const ACCOUNT_INFO_TTL = 5 * 60 * 1000; // 5 minutes default
11079
+ /**
11080
+ * Token Storage Layer
11081
+ *
11082
+ * Manages authentication tokens, user data, and account data using
11083
+ * the persistent storage layer (IndexedDB + localStorage fallback).
11084
+ * All methods are async to support IndexedDB operations.
11085
+ */
10678
11086
  const tokenStorage = {
10679
- saveToken(token, expiresAt) {
11087
+ async saveToken(token, expiresAt) {
11088
+ const storage = await getStorage();
10680
11089
  const authToken = {
10681
11090
  token,
10682
11091
  expiresAt: expiresAt || Date.now() + 3600000, // Default 1 hour
10683
11092
  };
10684
- localStorage.setItem(TOKEN_KEY, JSON.stringify(authToken));
11093
+ await storage.setItem(TOKEN_KEY, authToken);
10685
11094
  },
10686
- getToken() {
10687
- const stored = localStorage.getItem(TOKEN_KEY);
10688
- if (!stored)
11095
+ async getToken() {
11096
+ const storage = await getStorage();
11097
+ const authToken = await storage.getItem(TOKEN_KEY);
11098
+ if (!authToken)
10689
11099
  return null;
10690
- try {
10691
- const authToken = JSON.parse(stored);
10692
- // Check if token is expired
10693
- if (authToken.expiresAt && authToken.expiresAt < Date.now()) {
10694
- this.clearToken();
10695
- return null;
10696
- }
10697
- return authToken;
10698
- }
10699
- catch {
11100
+ // Check if token is expired
11101
+ if (authToken.expiresAt && authToken.expiresAt < Date.now()) {
11102
+ await this.clearToken();
10700
11103
  return null;
10701
11104
  }
11105
+ return authToken;
10702
11106
  },
10703
- clearToken() {
10704
- localStorage.removeItem(TOKEN_KEY);
11107
+ async clearToken() {
11108
+ const storage = await getStorage();
11109
+ await storage.removeItem(TOKEN_KEY);
10705
11110
  },
10706
- saveUser(user) {
10707
- localStorage.setItem(USER_KEY, JSON.stringify(user));
11111
+ async saveUser(user) {
11112
+ const storage = await getStorage();
11113
+ await storage.setItem(USER_KEY, user);
10708
11114
  },
10709
- getUser() {
10710
- const stored = localStorage.getItem(USER_KEY);
10711
- if (!stored)
10712
- return null;
10713
- try {
10714
- return JSON.parse(stored);
10715
- }
10716
- catch {
10717
- return null;
10718
- }
11115
+ async getUser() {
11116
+ const storage = await getStorage();
11117
+ return await storage.getItem(USER_KEY);
10719
11118
  },
10720
- clearUser() {
10721
- localStorage.removeItem(USER_KEY);
11119
+ async clearUser() {
11120
+ const storage = await getStorage();
11121
+ await storage.removeItem(USER_KEY);
10722
11122
  },
10723
- clearAll() {
10724
- this.clearToken();
10725
- this.clearUser();
10726
- this.clearAccountData();
11123
+ async clearAll() {
11124
+ await this.clearToken();
11125
+ await this.clearUser();
11126
+ await this.clearAccountData();
11127
+ await this.clearAccountInfo();
10727
11128
  },
10728
- saveAccountData(data) {
10729
- localStorage.setItem(ACCOUNT_DATA_KEY, JSON.stringify(data));
11129
+ async saveAccountData(data) {
11130
+ const storage = await getStorage();
11131
+ await storage.setItem(ACCOUNT_DATA_KEY, data);
10730
11132
  },
10731
- getAccountData() {
10732
- const stored = localStorage.getItem(ACCOUNT_DATA_KEY);
10733
- if (!stored)
10734
- return null;
10735
- try {
10736
- return JSON.parse(stored);
10737
- }
10738
- catch {
11133
+ async getAccountData() {
11134
+ const storage = await getStorage();
11135
+ return await storage.getItem(ACCOUNT_DATA_KEY);
11136
+ },
11137
+ async clearAccountData() {
11138
+ const storage = await getStorage();
11139
+ await storage.removeItem(ACCOUNT_DATA_KEY);
11140
+ },
11141
+ async saveAccountInfo(accountInfo, ttl = ACCOUNT_INFO_TTL) {
11142
+ const storage = await getStorage();
11143
+ const cachedData = {
11144
+ data: accountInfo,
11145
+ cachedAt: Date.now(),
11146
+ expiresAt: Date.now() + ttl,
11147
+ };
11148
+ await storage.setItem(ACCOUNT_INFO_KEY, cachedData);
11149
+ },
11150
+ async getAccountInfo() {
11151
+ const storage = await getStorage();
11152
+ const cached = await storage.getItem(ACCOUNT_INFO_KEY);
11153
+ if (!cached)
10739
11154
  return null;
10740
- }
11155
+ const isStale = cached.expiresAt < Date.now();
11156
+ return {
11157
+ data: cached.data,
11158
+ isStale,
11159
+ };
10741
11160
  },
10742
- clearAccountData() {
10743
- localStorage.removeItem(ACCOUNT_DATA_KEY);
11161
+ async clearAccountInfo() {
11162
+ const storage = await getStorage();
11163
+ await storage.removeItem(ACCOUNT_INFO_KEY);
10744
11164
  },
10745
11165
  };
10746
11166
 
10747
11167
  const AuthContext = createContext(undefined);
10748
- const AuthProvider = ({ children }) => {
11168
+ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
10749
11169
  const [user, setUser] = useState(null);
10750
11170
  const [token, setToken] = useState(null);
10751
11171
  const [accountData, setAccountData] = useState(null);
11172
+ const [accountInfo, setAccountInfo] = useState(null);
10752
11173
  const [isLoading, setIsLoading] = useState(true);
10753
- // Initialize auth state from localStorage
11174
+ const callbacksRef = useRef(new Set());
11175
+ // Notify all subscribers of auth state changes
11176
+ const notifyAuthStateChange = useCallback((type, currentUser, currentToken, currentAccountData, currentAccountInfo) => {
11177
+ callbacksRef.current.forEach(callback => {
11178
+ try {
11179
+ callback({
11180
+ type,
11181
+ user: currentUser,
11182
+ token: currentToken,
11183
+ accountData: currentAccountData,
11184
+ accountInfo: currentAccountInfo
11185
+ });
11186
+ }
11187
+ catch (error) {
11188
+ console.error('[AuthContext] Error in auth state change callback:', error);
11189
+ }
11190
+ });
11191
+ }, []);
11192
+ // Initialize auth state from persistent storage
11193
+ useEffect(() => {
11194
+ const initializeAuth = async () => {
11195
+ try {
11196
+ const storedToken = await tokenStorage.getToken();
11197
+ const storedUser = await tokenStorage.getUser();
11198
+ const storedAccountData = await tokenStorage.getAccountData();
11199
+ if (storedToken && storedUser) {
11200
+ setToken(storedToken.token);
11201
+ setUser(storedUser);
11202
+ setAccountData(storedAccountData);
11203
+ // Set bearer token in global Smartlinks SDK via auth.verifyToken
11204
+ smartlinks.auth.verifyToken(storedToken.token).catch(err => {
11205
+ console.warn('Failed to restore bearer token on init:', err);
11206
+ });
11207
+ }
11208
+ // Load cached account info if available
11209
+ const cachedAccountInfo = await tokenStorage.getAccountInfo();
11210
+ if (cachedAccountInfo && !cachedAccountInfo.isStale) {
11211
+ setAccountInfo(cachedAccountInfo.data);
11212
+ }
11213
+ }
11214
+ catch (error) {
11215
+ console.error('Failed to initialize auth from storage:', error);
11216
+ }
11217
+ finally {
11218
+ setIsLoading(false);
11219
+ }
11220
+ };
11221
+ initializeAuth();
11222
+ }, []);
11223
+ // Cross-tab synchronization - listen for auth changes in other tabs
10754
11224
  useEffect(() => {
10755
- const storedToken = tokenStorage.getToken();
10756
- const storedUser = tokenStorage.getUser();
10757
- const storedAccountData = tokenStorage.getAccountData();
10758
- if (storedToken && storedUser) {
10759
- setToken(storedToken.token);
10760
- setUser(storedUser);
10761
- setAccountData(storedAccountData);
11225
+ console.log('[AuthContext] Setting up cross-tab synchronization');
11226
+ const unsubscribe = onStorageChange(async (event) => {
11227
+ console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
11228
+ try {
11229
+ if (event.type === 'clear') {
11230
+ // Another tab cleared all storage (logout)
11231
+ console.log('[AuthContext] Detected logout in another tab');
11232
+ setToken(null);
11233
+ setUser(null);
11234
+ setAccountData(null);
11235
+ setAccountInfo(null);
11236
+ smartlinks.auth.logout();
11237
+ notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
11238
+ }
11239
+ else if (event.type === 'remove' && (event.key === 'token' || event.key === 'user')) {
11240
+ // Another tab removed token or user (logout)
11241
+ console.log('[AuthContext] Detected token/user removal in another tab');
11242
+ setToken(null);
11243
+ setUser(null);
11244
+ setAccountData(null);
11245
+ setAccountInfo(null);
11246
+ smartlinks.auth.logout();
11247
+ notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
11248
+ }
11249
+ else if (event.type === 'set' && event.key === 'token') {
11250
+ // Another tab set a new token (login)
11251
+ console.log('[AuthContext] Detected login in another tab');
11252
+ const storedToken = await tokenStorage.getToken();
11253
+ const storedUser = await tokenStorage.getUser();
11254
+ const storedAccountData = await tokenStorage.getAccountData();
11255
+ if (storedToken && storedUser) {
11256
+ setToken(storedToken.token);
11257
+ setUser(storedUser);
11258
+ setAccountData(storedAccountData);
11259
+ // Set bearer token in global Smartlinks SDK
11260
+ smartlinks.auth.verifyToken(storedToken.token).catch(err => {
11261
+ console.warn('[AuthContext] Failed to restore bearer token from cross-tab sync:', err);
11262
+ });
11263
+ notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData);
11264
+ }
11265
+ }
11266
+ else if (event.type === 'set' && event.key === 'account_info') {
11267
+ // Another tab fetched fresh account info
11268
+ const cached = await tokenStorage.getAccountInfo();
11269
+ if (cached && !cached.isStale) {
11270
+ setAccountInfo(cached.data);
11271
+ console.log('[AuthContext] Account info synced from another tab');
11272
+ }
11273
+ }
11274
+ }
11275
+ catch (error) {
11276
+ console.error('[AuthContext] Error handling cross-tab sync:', error);
11277
+ }
11278
+ });
11279
+ return () => {
11280
+ console.log('[AuthContext] Cleaning up cross-tab synchronization');
11281
+ unsubscribe();
11282
+ };
11283
+ }, [notifyAuthStateChange]);
11284
+ const login = useCallback(async (authToken, authUser, authAccountData) => {
11285
+ try {
11286
+ // Store token, user, and account data
11287
+ await tokenStorage.saveToken(authToken);
11288
+ await tokenStorage.saveUser(authUser);
11289
+ if (authAccountData) {
11290
+ await tokenStorage.saveAccountData(authAccountData);
11291
+ }
11292
+ setToken(authToken);
11293
+ setUser(authUser);
11294
+ setAccountData(authAccountData || null);
10762
11295
  // Set bearer token in global Smartlinks SDK via auth.verifyToken
10763
- smartlinks.auth.verifyToken(storedToken.token).catch(err => {
10764
- console.warn('Failed to restore bearer token on init:', err);
11296
+ // This both validates the token and sets it for future API calls
11297
+ smartlinks.auth.verifyToken(authToken).catch(err => {
11298
+ console.warn('Failed to set bearer token on login:', err);
10765
11299
  });
11300
+ notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null);
11301
+ // Optionally preload account info on login
11302
+ if (preloadAccountInfo) {
11303
+ // Preload after login completes (non-blocking)
11304
+ getAccount(true).catch(error => {
11305
+ console.warn('[AuthContext] Failed to preload account info:', error);
11306
+ });
11307
+ }
10766
11308
  }
10767
- setIsLoading(false);
10768
- }, []);
10769
- const login = useCallback((authToken, authUser, authAccountData) => {
10770
- // Store token, user, and account data
10771
- tokenStorage.saveToken(authToken);
10772
- tokenStorage.saveUser(authUser);
10773
- if (authAccountData) {
10774
- tokenStorage.saveAccountData(authAccountData);
10775
- }
10776
- setToken(authToken);
10777
- setUser(authUser);
10778
- setAccountData(authAccountData || null);
10779
- // Set bearer token in global Smartlinks SDK via auth.verifyToken
10780
- // This both validates the token and sets it for future API calls
10781
- smartlinks.auth.verifyToken(authToken).catch(err => {
10782
- console.warn('Failed to set bearer token on login:', err);
10783
- });
10784
- }, []);
11309
+ catch (error) {
11310
+ console.error('Failed to save auth data to storage:', error);
11311
+ throw error;
11312
+ }
11313
+ }, [notifyAuthStateChange, preloadAccountInfo]);
10785
11314
  const logout = useCallback(async () => {
10786
- // Clear local storage
10787
- tokenStorage.clearAll();
10788
- setToken(null);
10789
- setUser(null);
10790
- setAccountData(null);
10791
- // Clear bearer token from global Smartlinks SDK
10792
- smartlinks.auth.logout();
10793
- }, []);
10794
- const getToken = useCallback(() => {
10795
- const storedToken = tokenStorage.getToken();
11315
+ try {
11316
+ // Clear persistent storage
11317
+ await tokenStorage.clearAll();
11318
+ setToken(null);
11319
+ setUser(null);
11320
+ setAccountData(null);
11321
+ setAccountInfo(null);
11322
+ // Clear bearer token from global Smartlinks SDK
11323
+ smartlinks.auth.logout();
11324
+ notifyAuthStateChange('LOGOUT', null, null, null);
11325
+ }
11326
+ catch (error) {
11327
+ console.error('Failed to clear auth data from storage:', error);
11328
+ }
11329
+ }, [notifyAuthStateChange]);
11330
+ const getToken = useCallback(async () => {
11331
+ const storedToken = await tokenStorage.getToken();
10796
11332
  return storedToken ? storedToken.token : null;
10797
11333
  }, []);
10798
11334
  const refreshToken = useCallback(async () => {
10799
11335
  throw new Error('Token refresh must be implemented via your backend API');
10800
11336
  }, []);
11337
+ // Get account with intelligent caching
11338
+ const getAccount = useCallback(async (forceRefresh = false) => {
11339
+ try {
11340
+ // Check if user is authenticated
11341
+ if (!token) {
11342
+ throw new Error('Not authenticated. Please login first.');
11343
+ }
11344
+ // Check cache unless force refresh
11345
+ if (!forceRefresh) {
11346
+ const cached = await tokenStorage.getAccountInfo();
11347
+ if (cached && !cached.isStale) {
11348
+ console.log('[AuthContext] Returning cached account info');
11349
+ return cached.data;
11350
+ }
11351
+ }
11352
+ // Fetch fresh data from API
11353
+ console.log('[AuthContext] Fetching fresh account info from API');
11354
+ const freshAccountInfo = await smartlinks.auth.getAccount();
11355
+ // Cache the fresh data
11356
+ await tokenStorage.saveAccountInfo(freshAccountInfo, accountCacheTTL);
11357
+ setAccountInfo(freshAccountInfo);
11358
+ notifyAuthStateChange('ACCOUNT_REFRESH', user, token, accountData, freshAccountInfo);
11359
+ return freshAccountInfo;
11360
+ }
11361
+ catch (error) {
11362
+ console.error('[AuthContext] Failed to get account info:', error);
11363
+ // Fallback to stale cache if API fails
11364
+ const cached = await tokenStorage.getAccountInfo();
11365
+ if (cached) {
11366
+ console.warn('[AuthContext] Returning stale cached data due to API error');
11367
+ return cached.data;
11368
+ }
11369
+ throw error;
11370
+ }
11371
+ }, [token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
11372
+ // Convenience method for explicit refresh
11373
+ const refreshAccount = useCallback(async () => {
11374
+ return await getAccount(true);
11375
+ }, [getAccount]);
11376
+ // Clear account cache
11377
+ const clearAccountCache = useCallback(async () => {
11378
+ await tokenStorage.clearAccountInfo();
11379
+ setAccountInfo(null);
11380
+ }, []);
11381
+ const onAuthStateChange = useCallback((callback) => {
11382
+ callbacksRef.current.add(callback);
11383
+ // Return unsubscribe function
11384
+ return () => {
11385
+ callbacksRef.current.delete(callback);
11386
+ };
11387
+ }, []);
10801
11388
  const value = {
10802
11389
  user,
10803
11390
  token,
10804
11391
  accountData,
11392
+ accountInfo,
10805
11393
  isAuthenticated: !!token && !!user,
10806
11394
  isLoading,
10807
11395
  login,
10808
11396
  logout,
10809
11397
  getToken,
10810
11398
  refreshToken,
11399
+ getAccount,
11400
+ refreshAccount,
11401
+ clearAccountCache,
11402
+ onAuthStateChange,
10811
11403
  };
10812
11404
  return jsx(AuthContext.Provider, { value: value, children: children });
10813
11405
  };
@@ -10819,7 +11411,7 @@ const useAuth = () => {
10819
11411
  return context;
10820
11412
  };
10821
11413
 
10822
- const AccountManagement = ({ apiEndpoint, clientId, onProfileUpdated, onEmailChangeRequested, onPasswordChanged, onAccountDeleted, onError, theme = 'light', className = '', customization = {}, }) => {
11414
+ const AccountManagement = ({ apiEndpoint, clientId, onProfileUpdated, onEmailChangeRequested, onPasswordChanged, onAccountDeleted, onError, theme = 'light', className = '', customization = {}, minimal = false, }) => {
10823
11415
  const auth = useAuth();
10824
11416
  const [loading, setLoading] = useState(false);
10825
11417
  const [profile, setProfile] = useState(null);
@@ -11102,6 +11694,99 @@ const AccountManagement = ({ apiEndpoint, clientId, onProfileUpdated, onEmailCha
11102
11694
  }, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
11103
11695
  };
11104
11696
 
11697
+ const SmartlinksClaimUI = ({ apiEndpoint, clientId, clientName, collectionId, productId, proofId, onClaimSuccess, onClaimError, additionalFields = [], theme = 'light', className = '', minimal = false, customization = {}, }) => {
11698
+ const auth = useAuth();
11699
+ const [claimStep, setClaimStep] = useState(auth.isAuthenticated ? 'questions' : 'auth');
11700
+ const [claimData, setClaimData] = useState({});
11701
+ const [error, setError] = useState();
11702
+ const [loading, setLoading] = useState(false);
11703
+ const handleAuthSuccess = (token, user, accountData) => {
11704
+ // Authentication successful
11705
+ auth.login(token, user, accountData);
11706
+ // If no additional questions, proceed directly to claim
11707
+ if (additionalFields.length === 0) {
11708
+ executeClaim(user);
11709
+ }
11710
+ else {
11711
+ setClaimStep('questions');
11712
+ }
11713
+ };
11714
+ const handleQuestionSubmit = async (e) => {
11715
+ e.preventDefault();
11716
+ // Validate required fields
11717
+ const missingFields = additionalFields
11718
+ .filter(field => field.required && !claimData[field.name])
11719
+ .map(field => field.label);
11720
+ if (missingFields.length > 0) {
11721
+ setError(`Please fill in: ${missingFields.join(', ')}`);
11722
+ return;
11723
+ }
11724
+ // Execute claim with collected data
11725
+ if (auth.user) {
11726
+ executeClaim(auth.user);
11727
+ }
11728
+ };
11729
+ const executeClaim = async (user) => {
11730
+ setClaimStep('claiming');
11731
+ setLoading(true);
11732
+ setError(undefined);
11733
+ try {
11734
+ // Create attestation to claim the proof
11735
+ const response = await smartlinks.attestation.create(collectionId, productId, proofId, {
11736
+ public: {
11737
+ claimed: true,
11738
+ claimedAt: new Date().toISOString(),
11739
+ claimedBy: user.uid,
11740
+ ...claimData,
11741
+ },
11742
+ private: {},
11743
+ proof: {},
11744
+ });
11745
+ setClaimStep('success');
11746
+ // Call success callback
11747
+ onClaimSuccess({
11748
+ proofId,
11749
+ user,
11750
+ claimData,
11751
+ attestationId: response.id,
11752
+ });
11753
+ }
11754
+ catch (err) {
11755
+ console.error('Claim error:', err);
11756
+ const errorMessage = err instanceof Error ? err.message : 'Failed to claim proof';
11757
+ setError(errorMessage);
11758
+ onClaimError?.(err instanceof Error ? err : new Error(errorMessage));
11759
+ setClaimStep(additionalFields.length > 0 ? 'questions' : 'auth');
11760
+ }
11761
+ finally {
11762
+ setLoading(false);
11763
+ }
11764
+ };
11765
+ const handleFieldChange = (fieldName, value) => {
11766
+ setClaimData(prev => ({
11767
+ ...prev,
11768
+ [fieldName]: value,
11769
+ }));
11770
+ };
11771
+ // Render authentication step
11772
+ if (claimStep === 'auth') {
11773
+ return (jsx("div", { className: className, children: jsx(SmartlinksAuthUI, { apiEndpoint: apiEndpoint, clientId: clientId, clientName: clientName, onAuthSuccess: handleAuthSuccess, onAuthError: onClaimError, theme: theme, minimal: minimal, customization: customization.authConfig }) }));
11774
+ }
11775
+ // Render additional questions step
11776
+ if (claimStep === 'questions') {
11777
+ return (jsxs("div", { className: `claim-questions ${className}`, children: [jsxs("div", { className: "claim-header mb-6", children: [jsx("h2", { className: "text-2xl font-bold mb-2", children: customization.claimTitle || 'Complete Your Claim' }), customization.claimDescription && (jsx("p", { className: "text-muted-foreground", children: customization.claimDescription }))] }), error && (jsx("div", { className: "claim-error bg-destructive/10 text-destructive px-4 py-3 rounded-md mb-4", children: error })), jsxs("form", { onSubmit: handleQuestionSubmit, className: "claim-form space-y-4", children: [additionalFields.map((field) => (jsxs("div", { className: "claim-field", children: [jsxs("label", { htmlFor: field.name, className: "block text-sm font-medium mb-2", children: [field.label, field.required && jsx("span", { className: "text-destructive ml-1", children: "*" })] }), field.type === 'textarea' ? (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' ? (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: [jsx("option", { value: "", children: "Select..." }), field.options?.map((option) => (jsx("option", { value: option, children: option }, option)))] })) : (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))), 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' })] })] }));
11778
+ }
11779
+ // Render claiming step (loading state)
11780
+ if (claimStep === 'claiming') {
11781
+ return (jsxs("div", { className: `claim-loading ${className} flex flex-col items-center justify-center py-12`, children: [jsx("div", { className: "claim-spinner w-12 h-12 border-4 border-primary border-t-transparent rounded-full animate-spin mb-4" }), jsx("p", { className: "text-muted-foreground", children: "Claiming your product..." })] }));
11782
+ }
11783
+ // Render success step
11784
+ if (claimStep === 'success') {
11785
+ return (jsxs("div", { className: `claim-success ${className} text-center py-12`, children: [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" }), jsx("h2", { className: "text-2xl font-bold mb-2", children: "Claim Successful!" }), jsx("p", { className: "text-muted-foreground", children: customization.successMessage || 'Your product has been successfully claimed and registered to your account.' })] }));
11786
+ }
11787
+ return null;
11788
+ };
11789
+
11105
11790
  const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
11106
11791
  const { isAuthenticated, isLoading } = useAuth();
11107
11792
  // Show loading state
@@ -11120,7 +11805,7 @@ const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
11120
11805
  return jsx(Fragment, { children: children });
11121
11806
  };
11122
11807
 
11123
- const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], providerOrder, emailDisplayMode = 'form', theme = 'light', className, }) => {
11808
+ const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', 'phone'], providerOrder, emailDisplayMode = 'form', theme = 'light', className, minimal = false, }) => {
11124
11809
  const showEmail = enabledProviders.includes('email');
11125
11810
  const showGoogle = enabledProviders.includes('google');
11126
11811
  const showPhone = enabledProviders.includes('phone');
@@ -11146,14 +11831,14 @@ const AuthUIPreview = ({ customization, enabledProviders = ['email', 'google', '
11146
11831
  }
11147
11832
  return null;
11148
11833
  };
11149
- return (jsx(AuthContainer, { theme: theme, className: className, config: customization, children: emailDisplayMode === 'button' ? (jsx("div", { className: "auth-provider-buttons", children: orderedProviders.concat(showEmail ? ['email'] : []).map(provider => renderProviderButton(provider)) })) : (
11834
+ return (jsx(AuthContainer, { theme: theme, className: className, config: customization, minimal: minimal, children: emailDisplayMode === 'button' ? (jsx("div", { className: "auth-provider-buttons", children: orderedProviders.concat(showEmail ? ['email'] : []).map(provider => renderProviderButton(provider)) })) : (
11150
11835
  /* Form mode: show email form first, then other providers */
11151
11836
  jsxs(Fragment, { children: [showEmail && (jsxs("div", { className: "auth-form", children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { className: "auth-label", children: "Email" }), jsx("input", { type: "email", className: "auth-input", placeholder: "Enter your email", disabled: true })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { className: "auth-label", children: "Password" }), jsx("input", { type: "password", className: "auth-input", placeholder: "Enter your password", disabled: true })] }), jsx("button", { className: "auth-button auth-button-primary", disabled: true, children: "Sign In" }), jsx("div", { style: { textAlign: 'center', marginTop: '1rem' }, children: jsx("button", { className: "auth-link", disabled: true, children: "Forgot password?" }) }), jsxs("div", { style: { textAlign: 'center', marginTop: '0.5rem', fontSize: '0.875rem', color: 'var(--auth-text-muted, #6B7280)' }, children: ["Don't have an account?", ' ', jsx("button", { className: "auth-link", disabled: true, children: "Sign up" })] })] })), hasOtherProviders && (jsxs(Fragment, { children: [showEmail && (jsx("div", { className: "auth-or-divider", children: jsx("span", { children: "or continue with" }) })), jsx("div", { className: "auth-provider-buttons", children: orderedProviders.map(provider => renderProviderButton(provider)) })] }))] })) }));
11152
11837
  };
11153
11838
 
11154
11839
  // Default Smartlinks Google OAuth Client ID (public - safe to expose)
11155
11840
  const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
11156
- const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, }) => {
11841
+ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, minimal = false, }) => {
11157
11842
  const [mode, setMode] = useState(initialMode);
11158
11843
  const [loading, setLoading] = useState(false);
11159
11844
  const [error, setError] = useState();
@@ -11173,21 +11858,24 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11173
11858
  // Reinitialize Smartlinks SDK when apiEndpoint changes (for test/dev scenarios)
11174
11859
  // IMPORTANT: Preserve bearer token during reinitialization
11175
11860
  useEffect(() => {
11176
- if (apiEndpoint) {
11177
- // Get current token before reinitializing
11178
- const currentToken = auth.getToken();
11179
- smartlinks.initializeApi({
11180
- baseURL: apiEndpoint,
11181
- proxyMode: false, // Direct API calls when custom endpoint is provided
11182
- ngrokSkipBrowserWarning: true,
11183
- });
11184
- // Restore bearer token after reinitialization using auth.verifyToken
11185
- if (currentToken) {
11186
- smartlinks.auth.verifyToken(currentToken).catch(err => {
11187
- console.warn('Failed to restore bearer token after reinit:', err);
11861
+ const reinitializeWithToken = async () => {
11862
+ if (apiEndpoint) {
11863
+ // Get current token before reinitializing
11864
+ const currentToken = await auth.getToken();
11865
+ smartlinks.initializeApi({
11866
+ baseURL: apiEndpoint,
11867
+ proxyMode: false, // Direct API calls when custom endpoint is provided
11868
+ ngrokSkipBrowserWarning: true,
11188
11869
  });
11870
+ // Restore bearer token after reinitialization using auth.verifyToken
11871
+ if (currentToken) {
11872
+ smartlinks.auth.verifyToken(currentToken).catch(err => {
11873
+ console.warn('Failed to restore bearer token after reinit:', err);
11874
+ });
11875
+ }
11189
11876
  }
11190
- }
11877
+ };
11878
+ reinitializeWithToken();
11191
11879
  }, [apiEndpoint, auth]);
11192
11880
  // Get the effective redirect URL (use prop or default to current page)
11193
11881
  const getRedirectUrl = () => {
@@ -11697,9 +12385,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11697
12385
  }
11698
12386
  };
11699
12387
  if (configLoading) {
11700
- return (jsx(AuthContainer, { theme: theme, className: className, children: jsx("div", { style: { textAlign: 'center', padding: '2rem' }, children: jsx("div", { className: "auth-spinner" }) }) }));
12388
+ return (jsx(AuthContainer, { theme: theme, className: className, minimal: minimal || config?.branding?.minimal || false, children: jsx("div", { style: { textAlign: 'center', padding: '2rem' }, children: jsx("div", { className: "auth-spinner" }) }) }));
11701
12389
  }
11702
- return (jsx(AuthContainer, { theme: theme, className: className, config: config, children: authSuccess ? (jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [jsx("div", { style: {
12390
+ return (jsx(AuthContainer, { theme: theme, className: className, config: config, minimal: minimal || config?.branding?.minimal || false, children: authSuccess ? (jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [jsx("div", { style: {
11703
12391
  color: 'var(--auth-primary-color, #4F46E5)',
11704
12392
  fontSize: '3rem',
11705
12393
  marginBottom: '1rem'
@@ -11807,9 +12495,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11807
12495
  setShowResendVerification(false);
11808
12496
  setShowRequestNewReset(false);
11809
12497
  setError(undefined);
11810
- }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
12498
+ }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
11811
12499
  })() })) })) : null }));
11812
12500
  };
11813
12501
 
11814
- export { AccountManagement, AuthProvider, AuthUIPreview, SmartlinksAuthUI as FirebaseAuthUI, ProtectedRoute, SmartlinksAuthUI, tokenStorage, useAuth };
12502
+ export { AccountManagement, AuthProvider, AuthUIPreview, SmartlinksAuthUI as FirebaseAuthUI, ProtectedRoute, SmartlinksAuthUI, SmartlinksClaimUI, tokenStorage, useAuth };
11815
12503
  //# sourceMappingURL=index.esm.js.map