@proveanything/smartlinks-auth-ui 0.1.3 → 0.1.5

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