@proveanything/smartlinks-auth-ui 0.1.27 → 0.1.29

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
@@ -3,6 +3,7 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
5
  var smartlinks = require('@proveanything/smartlinks');
6
+ var http = require('@proveanything/smartlinks/dist/http');
6
7
 
7
8
  function _interopNamespaceDefault(e) {
8
9
  var n = Object.create(null);
@@ -78,22 +79,145 @@ const AuthContainer = ({ children, theme = 'light', className = '', config, mini
78
79
  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" })] }))] }) }));
79
80
  };
80
81
 
81
- const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, additionalFields = [], }) => {
82
+ /**
83
+ * Renders a form field based on schema definition
84
+ * Used in both registration and account management forms
85
+ */
86
+ const SchemaFieldRenderer = ({ field, value, onChange, disabled = false, error, className = '', }) => {
87
+ const handleChange = (newValue) => {
88
+ onChange(field.key, newValue);
89
+ };
90
+ const inputId = `field-${field.key}`;
91
+ const commonProps = {
92
+ id: inputId,
93
+ className: `auth-input ${error ? 'auth-input-error' : ''} ${className}`,
94
+ disabled: disabled || field.readOnly,
95
+ 'aria-describedby': error ? `${inputId}-error` : undefined,
96
+ };
97
+ const renderField = () => {
98
+ switch (field.widget) {
99
+ case 'select':
100
+ return (jsxRuntime.jsxs("select", { ...commonProps, value: value || '', onChange: (e) => handleChange(e.target.value), required: field.required, children: [jsxRuntime.jsxs("option", { value: "", children: ["Select ", field.label, "..."] }), field.options?.map((option) => {
101
+ const optionValue = typeof option === 'string' ? option : option.value;
102
+ const optionLabel = typeof option === 'string' ? option : option.label;
103
+ return (jsxRuntime.jsx("option", { value: optionValue, children: optionLabel }, optionValue));
104
+ })] }));
105
+ case 'checkbox':
106
+ return (jsxRuntime.jsxs("label", { className: "auth-checkbox-label", style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [jsxRuntime.jsx("input", { type: "checkbox", id: inputId, checked: !!value, onChange: (e) => handleChange(e.target.checked), disabled: disabled || field.readOnly, required: field.required, className: "auth-checkbox" }), jsxRuntime.jsx("span", { children: field.description || field.label })] }));
107
+ case 'textarea':
108
+ return (jsxRuntime.jsx("textarea", { ...commonProps, value: value || '', onChange: (e) => handleChange(e.target.value), required: field.required, placeholder: field.placeholder, rows: 3, style: { minHeight: '80px', resize: 'vertical' }, maxLength: field.validation?.maxLength }));
109
+ case 'number':
110
+ return (jsxRuntime.jsx("input", { ...commonProps, type: "number", value: value ?? '', onChange: (e) => handleChange(e.target.value ? Number(e.target.value) : undefined), required: field.required, placeholder: field.placeholder, min: field.validation?.min, max: field.validation?.max }));
111
+ case 'date':
112
+ return (jsxRuntime.jsx("input", { ...commonProps, type: "date", value: value || '', onChange: (e) => handleChange(e.target.value), required: field.required }));
113
+ case 'tel':
114
+ return (jsxRuntime.jsx("input", { ...commonProps, type: "tel", value: value || '', onChange: (e) => handleChange(e.target.value), required: field.required, placeholder: field.placeholder || '+1 (555) 000-0000', autoComplete: "tel" }));
115
+ case 'email':
116
+ return (jsxRuntime.jsx("input", { ...commonProps, type: "email", value: value || '', onChange: (e) => handleChange(e.target.value), required: field.required, placeholder: field.placeholder || 'email@example.com', autoComplete: "email" }));
117
+ case 'text':
118
+ default:
119
+ return (jsxRuntime.jsx("input", { ...commonProps, type: "text", value: value || '', onChange: (e) => handleChange(e.target.value), required: field.required, placeholder: field.placeholder, minLength: field.validation?.minLength, maxLength: field.validation?.maxLength, pattern: field.validation?.pattern }));
120
+ }
121
+ };
122
+ // Checkbox has its own label
123
+ if (field.widget === 'checkbox') {
124
+ return (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [renderField(), error && (jsxRuntime.jsx("div", { id: `${inputId}-error`, className: "auth-field-error", role: "alert", children: error }))] }));
125
+ }
126
+ return (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsxs("label", { htmlFor: inputId, className: "auth-label", children: [field.label, field.required && jsxRuntime.jsx("span", { style: { color: 'var(--auth-error-color, #ef4444)' }, children: " *" })] }), field.description && (jsxRuntime.jsx("p", { className: "auth-field-description", style: { fontSize: '0.85em', color: 'var(--auth-text-muted)', marginBottom: '4px' }, children: field.description })), renderField(), error && (jsxRuntime.jsx("div", { id: `${inputId}-error`, className: "auth-field-error", role: "alert", children: error }))] }));
127
+ };
128
+ /**
129
+ * Helper to get all editable fields from schema
130
+ */
131
+ const getEditableFields = (schema) => {
132
+ if (!schema)
133
+ return [];
134
+ const editableKeys = new Set(schema.settings.publicEditableFields);
135
+ const coreEditable = schema.fields.filter(f => f.editable && f.visible && editableKeys.has(f.key));
136
+ const customEditable = schema.customFields.filter(f => f.editable && f.visible);
137
+ return [...coreEditable, ...customEditable];
138
+ };
139
+ /**
140
+ * Helper to get registration fields based on config
141
+ */
142
+ const getRegistrationFields = (schema, registrationConfig) => {
143
+ if (!schema || !registrationConfig.length)
144
+ return [];
145
+ const configMap = new Map(registrationConfig.map(c => [c.key, c]));
146
+ const allFields = [...schema.fields, ...schema.customFields];
147
+ return allFields
148
+ .filter(field => {
149
+ const config = configMap.get(field.key);
150
+ return config?.showDuringRegistration && field.visible;
151
+ })
152
+ .map(field => {
153
+ const config = configMap.get(field.key);
154
+ // Allow registration config to override required status
155
+ if (config?.required !== undefined) {
156
+ return { ...field, required: config.required };
157
+ }
158
+ return field;
159
+ });
160
+ };
161
+ /**
162
+ * Sort fields by placement (inline first, then post-credentials)
163
+ */
164
+ const sortFieldsByPlacement = (fields, registrationConfig) => {
165
+ const configMap = new Map(registrationConfig.map(c => [c.key, c]));
166
+ const inline = [];
167
+ const postCredentials = [];
168
+ fields.forEach(field => {
169
+ const config = configMap.get(field.key);
170
+ if (config?.placement === 'post-credentials') {
171
+ postCredentials.push(field);
172
+ }
173
+ else {
174
+ inline.push(field);
175
+ }
176
+ });
177
+ return { inline, postCredentials };
178
+ };
179
+
180
+ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, schema, registrationFieldsConfig = [], additionalFields = [], }) => {
82
181
  const [formData, setFormData] = React.useState({
83
182
  email: '',
84
183
  password: '',
85
184
  displayName: '',
86
185
  });
186
+ // Custom field values (separate from core AuthFormData)
187
+ const [customFieldValues, setCustomFieldValues] = React.useState({});
188
+ // Get schema-driven registration fields
189
+ const schemaFields = React.useMemo(() => {
190
+ if (!schema || !registrationFieldsConfig.length)
191
+ return { inline: [], postCredentials: [] };
192
+ const fields = getRegistrationFields(schema, registrationFieldsConfig);
193
+ return sortFieldsByPlacement(fields, registrationFieldsConfig);
194
+ }, [schema, registrationFieldsConfig]);
195
+ // Check if we have any additional fields to show
196
+ const hasSchemaFields = schemaFields.inline.length > 0 || schemaFields.postCredentials.length > 0;
197
+ const hasLegacyFields = additionalFields.length > 0 && !hasSchemaFields;
87
198
  const handleSubmit = async (e) => {
88
199
  e.preventDefault();
89
- await onSubmit(formData);
200
+ // Merge custom field values into accountData for registration
201
+ const submitData = {
202
+ ...formData,
203
+ accountData: mode === 'register'
204
+ ? { ...(formData.accountData || {}), customFields: customFieldValues }
205
+ : undefined,
206
+ };
207
+ await onSubmit(submitData);
90
208
  };
91
209
  const handleChange = (field, value) => {
92
210
  setFormData(prev => ({ ...prev, [field]: value }));
93
211
  };
212
+ const handleCustomFieldChange = (key, value) => {
213
+ setCustomFieldValues(prev => ({ ...prev, [key]: value }));
214
+ };
215
+ const renderSchemaField = (field) => (jsxRuntime.jsx(SchemaFieldRenderer, { field: field, value: customFieldValues[field.key], onChange: handleCustomFieldChange, disabled: loading }, field.key));
216
+ // Legacy field renderer (for backward compatibility)
217
+ const renderLegacyField = (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));
94
218
  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'
95
219
  ? 'Welcome back! Please enter your credentials.'
96
- : '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' })] })] }));
220
+ : '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' && hasSchemaFields && schemaFields.inline.map(renderSchemaField), mode === 'register' && hasLegacyFields && additionalFields.map(renderLegacyField), mode === 'register' && hasSchemaFields && schemaFields.postCredentials.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsxRuntime.jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), 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' })] })] }));
97
221
  };
98
222
 
99
223
  const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
@@ -10661,6 +10785,8 @@ class AuthAPI {
10661
10785
  return smartlinks__namespace.authKit.login(this.clientId, email, password);
10662
10786
  }
10663
10787
  async register(data) {
10788
+ // Note: redirectUrl is not passed to register - verification email is sent separately
10789
+ // via sendEmailVerification() after registration for verify-then-* modes
10664
10790
  return smartlinks__namespace.authKit.register(this.clientId, {
10665
10791
  email: data.email,
10666
10792
  password: data.password,
@@ -10684,6 +10810,18 @@ class AuthAPI {
10684
10810
  // Pass token to SDK - backend verifies with Google
10685
10811
  return smartlinks__namespace.authKit.googleLogin(this.clientId, token);
10686
10812
  }
10813
+ async loginWithGoogleCode(code, redirectUri) {
10814
+ this.log.log('loginWithGoogleCode called:', {
10815
+ codeLength: code?.length,
10816
+ redirectUri,
10817
+ });
10818
+ // Exchange authorization code for tokens via backend
10819
+ // Use direct HTTP call since SDK may not have this method in authKit namespace yet
10820
+ return http.post(`/api/v1/authkit/${this.clientId}/google-code`, {
10821
+ code,
10822
+ redirectUri,
10823
+ });
10824
+ }
10687
10825
  async sendPhoneCode(phoneNumber) {
10688
10826
  return smartlinks__namespace.authKit.sendPhoneCode(this.clientId, phoneNumber);
10689
10827
  }
@@ -11167,7 +11305,7 @@ const tokenStorage = {
11167
11305
  const storage = await getStorage();
11168
11306
  const authToken = {
11169
11307
  token,
11170
- expiresAt: expiresAt || Date.now() + 3600000, // Default 1 hour
11308
+ expiresAt: expiresAt || Date.now() + (7 * 24 * 60 * 60 * 1000), // Default 7 days (matches backend JWT)
11171
11309
  };
11172
11310
  await storage.setItem(TOKEN_KEY, authToken);
11173
11311
  },
@@ -11178,7 +11316,8 @@ const tokenStorage = {
11178
11316
  return null;
11179
11317
  // Check if token is expired
11180
11318
  if (authToken.expiresAt && authToken.expiresAt < Date.now()) {
11181
- await this.clearToken();
11319
+ console.log('[TokenStorage] Token expired at:', new Date(authToken.expiresAt).toISOString(), '- clearing all auth data');
11320
+ await this.clearAll(); // Clear ALL auth data to prevent orphaned state
11182
11321
  return null;
11183
11322
  }
11184
11323
  return authToken;
@@ -11258,6 +11397,9 @@ const tokenStorage = {
11258
11397
 
11259
11398
  const AuthContext = React.createContext(undefined);
11260
11399
  const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false,
11400
+ // Token refresh settings
11401
+ enableAutoRefresh = true, refreshThresholdPercent = 75, // Refresh when 75% of token lifetime has passed
11402
+ refreshCheckInterval = 60 * 1000, // Check every minute
11261
11403
  // Contact & Interaction features
11262
11404
  collectionId, enableContactSync, enableInteractionTracking, interactionAppId, interactionConfig, }) => {
11263
11405
  const [user, setUser] = React.useState(null);
@@ -11678,11 +11820,11 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11678
11820
  unsubscribe();
11679
11821
  };
11680
11822
  }, [proxyMode, notifyAuthStateChange]);
11681
- const login = React.useCallback(async (authToken, authUser, authAccountData, isNewUser) => {
11823
+ const login = React.useCallback(async (authToken, authUser, authAccountData, isNewUser, expiresAt) => {
11682
11824
  try {
11683
11825
  // Only persist to storage in standalone mode
11684
11826
  if (!proxyMode) {
11685
- await tokenStorage.saveToken(authToken);
11827
+ await tokenStorage.saveToken(authToken, expiresAt);
11686
11828
  await tokenStorage.saveUser(authUser);
11687
11829
  if (authAccountData) {
11688
11830
  await tokenStorage.saveAccountData(authAccountData);
@@ -11765,9 +11907,67 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11765
11907
  const storedToken = await tokenStorage.getToken();
11766
11908
  return storedToken ? storedToken.token : null;
11767
11909
  }, [proxyMode, token]);
11910
+ // Get token with expiration info
11911
+ const getTokenInfo = React.useCallback(async () => {
11912
+ if (proxyMode) {
11913
+ // In proxy mode, we don't have expiration info
11914
+ if (token) {
11915
+ return { token, expiresAt: 0, expiresIn: 0 };
11916
+ }
11917
+ return null;
11918
+ }
11919
+ const storedToken = await tokenStorage.getToken();
11920
+ if (!storedToken?.token || !storedToken.expiresAt) {
11921
+ return null;
11922
+ }
11923
+ return {
11924
+ token: storedToken.token,
11925
+ expiresAt: storedToken.expiresAt,
11926
+ expiresIn: Math.max(0, storedToken.expiresAt - Date.now()),
11927
+ };
11928
+ }, [proxyMode, token]);
11929
+ // Refresh token - validates current token and extends session if backend supports it
11768
11930
  const refreshToken = React.useCallback(async () => {
11769
- throw new Error('Token refresh must be implemented via your backend API');
11770
- }, []);
11931
+ if (proxyMode) {
11932
+ console.log('[AuthContext] Proxy mode: token refresh handled by parent');
11933
+ throw new Error('Token refresh in proxy mode is handled by the parent application');
11934
+ }
11935
+ const storedToken = await tokenStorage.getToken();
11936
+ if (!storedToken?.token) {
11937
+ throw new Error('No token to refresh. Please login first.');
11938
+ }
11939
+ try {
11940
+ console.log('[AuthContext] Refreshing token...');
11941
+ // Verify current token is still valid
11942
+ const verifyResult = await smartlinks__namespace.auth.verifyToken(storedToken.token);
11943
+ if (!verifyResult.valid) {
11944
+ console.warn('[AuthContext] Token is no longer valid, clearing session');
11945
+ await logout();
11946
+ throw new Error('Token expired or invalid. Please login again.');
11947
+ }
11948
+ // Token is valid - extend its expiration locally
11949
+ // Backend JWT remains valid, we just update our local tracking
11950
+ const newExpiresAt = Date.now() + (7 * 24 * 60 * 60 * 1000); // 7 days from now
11951
+ await tokenStorage.saveToken(storedToken.token, newExpiresAt);
11952
+ console.log('[AuthContext] Token verified and expiration extended to:', new Date(newExpiresAt).toISOString());
11953
+ // Update verified state
11954
+ setIsVerified(true);
11955
+ pendingVerificationRef.current = false;
11956
+ notifyAuthStateChange('TOKEN_REFRESH', user, storedToken.token, accountData, accountInfo, true, contact, contactId);
11957
+ return storedToken.token;
11958
+ }
11959
+ catch (error) {
11960
+ console.error('[AuthContext] Token refresh failed:', error);
11961
+ // If it's a network error, don't logout
11962
+ if (isNetworkError(error)) {
11963
+ console.warn('[AuthContext] Network error during refresh, keeping session');
11964
+ throw error;
11965
+ }
11966
+ // Auth error - clear session
11967
+ await logout();
11968
+ throw new Error('Token refresh failed. Please login again.');
11969
+ }
11970
+ }, [proxyMode, user, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
11771
11971
  const getAccount = React.useCallback(async (forceRefresh = false) => {
11772
11972
  try {
11773
11973
  if (proxyMode) {
@@ -11875,6 +12075,54 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11875
12075
  window.removeEventListener('offline', handleOffline);
11876
12076
  };
11877
12077
  }, [proxyMode, token, user, retryVerification]);
12078
+ // Automatic background token refresh
12079
+ React.useEffect(() => {
12080
+ if (proxyMode || !enableAutoRefresh || !token || !user) {
12081
+ return;
12082
+ }
12083
+ console.log('[AuthContext] Setting up automatic token refresh (interval:', refreshCheckInterval, 'ms, threshold:', refreshThresholdPercent, '%)');
12084
+ const checkAndRefresh = async () => {
12085
+ try {
12086
+ const storedToken = await tokenStorage.getToken();
12087
+ if (!storedToken?.expiresAt) {
12088
+ console.log('[AuthContext] No token expiration info, skipping refresh check');
12089
+ return;
12090
+ }
12091
+ const now = Date.now();
12092
+ const tokenLifetime = storedToken.expiresAt - (storedToken.expiresAt - (7 * 24 * 60 * 60 * 1000)); // Assume 7-day lifetime
12093
+ const tokenAge = now - (storedToken.expiresAt - (7 * 24 * 60 * 60 * 1000));
12094
+ const percentUsed = (tokenAge / tokenLifetime) * 100;
12095
+ // Calculate time remaining
12096
+ const timeRemaining = storedToken.expiresAt - now;
12097
+ const hoursRemaining = Math.round(timeRemaining / (60 * 60 * 1000));
12098
+ if (percentUsed >= refreshThresholdPercent) {
12099
+ console.log(`[AuthContext] Token at ${Math.round(percentUsed)}% lifetime (${hoursRemaining}h remaining), refreshing...`);
12100
+ try {
12101
+ await refreshToken();
12102
+ console.log('[AuthContext] Automatic token refresh successful');
12103
+ }
12104
+ catch (refreshError) {
12105
+ console.warn('[AuthContext] Automatic token refresh failed:', refreshError);
12106
+ // Don't logout on refresh failure - user can still use the app until token actually expires
12107
+ }
12108
+ }
12109
+ else {
12110
+ console.log(`[AuthContext] Token at ${Math.round(percentUsed)}% lifetime (${hoursRemaining}h remaining), no refresh needed`);
12111
+ }
12112
+ }
12113
+ catch (error) {
12114
+ console.error('[AuthContext] Error checking token for refresh:', error);
12115
+ }
12116
+ };
12117
+ // Check immediately on mount
12118
+ checkAndRefresh();
12119
+ // Set up periodic check
12120
+ const intervalId = setInterval(checkAndRefresh, refreshCheckInterval);
12121
+ return () => {
12122
+ console.log('[AuthContext] Cleaning up automatic token refresh timer');
12123
+ clearInterval(intervalId);
12124
+ };
12125
+ }, [proxyMode, enableAutoRefresh, refreshCheckInterval, refreshThresholdPercent, token, user, refreshToken]);
11878
12126
  const value = {
11879
12127
  user,
11880
12128
  token,
@@ -11892,6 +12140,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11892
12140
  login,
11893
12141
  logout,
11894
12142
  getToken,
12143
+ getTokenInfo,
11895
12144
  refreshToken,
11896
12145
  getAccount,
11897
12146
  refreshAccount,
@@ -11909,6 +12158,14 @@ const useAuth = () => {
11909
12158
  return context;
11910
12159
  };
11911
12160
 
12161
+ // Helper to calculate expiration from AuthResponse
12162
+ const getExpirationFromResponse = (response) => {
12163
+ if (response.expiresAt)
12164
+ return response.expiresAt;
12165
+ if (response.expiresIn)
12166
+ return Date.now() + response.expiresIn;
12167
+ return undefined; // Will use 7-day default in tokenStorage
12168
+ };
11912
12169
  // Default Smartlinks Google OAuth Client ID (public - safe to expose)
11913
12170
  const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
11914
12171
  // Default auth UI configuration when no clientId is provided
@@ -11952,6 +12209,28 @@ const loadGoogleIdentityServices = () => {
11952
12209
  document.head.appendChild(script);
11953
12210
  });
11954
12211
  };
12212
+ // Helper to detect WebView environments (Android/iOS)
12213
+ const detectWebView = () => {
12214
+ const ua = navigator.userAgent;
12215
+ // Android WebView detection
12216
+ if (/Android/i.test(ua)) {
12217
+ // Modern Android WebViews include "wv" in UA string
12218
+ if (/\bwv\b/i.test(ua))
12219
+ return true;
12220
+ // Check for legacy Android bridge
12221
+ if (typeof window.Android !== 'undefined')
12222
+ return true;
12223
+ }
12224
+ // iOS WKWebView detection
12225
+ if (/iPhone|iPad|iPod/i.test(ua)) {
12226
+ const hasWebKitHandlers = !!window.webkit?.messageHandlers;
12227
+ const isSafari = !!window.safari;
12228
+ // WKWebView has webkit handlers but no safari object
12229
+ if (hasWebKitHandlers && !isSafari)
12230
+ return true;
12231
+ }
12232
+ return false;
12233
+ };
11955
12234
  // Helper to convert generic SDK errors to user-friendly messages
11956
12235
  const getFriendlyErrorMessage = (errorMessage) => {
11957
12236
  // Check for common HTTP status codes in the error message
@@ -11979,7 +12258,7 @@ const getFriendlyErrorMessage = (errorMessage) => {
11979
12258
  // Return original message if no pattern matches
11980
12259
  return errorMessage;
11981
12260
  };
11982
- const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'auto', className, customization, skipConfigFetch = false, minimal = false, logger, proxyMode = false, }) => {
12261
+ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'auto', className, customization, skipConfigFetch = false, minimal = false, logger, proxyMode = false, collectionId, }) => {
11983
12262
  const [mode, setMode] = React.useState(initialMode);
11984
12263
  const [loading, setLoading] = React.useState(false);
11985
12264
  const [error, setError] = React.useState();
@@ -11996,6 +12275,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11996
12275
  const [configLoading, setConfigLoading] = React.useState(!skipConfigFetch);
11997
12276
  const [showEmailForm, setShowEmailForm] = React.useState(false); // Track if email form should be shown when emailDisplayMode is 'button'
11998
12277
  const [sdkReady, setSdkReady] = React.useState(false); // Track SDK initialization state
12278
+ const [contactSchema, setContactSchema] = React.useState(null); // Schema for registration fields
11999
12279
  const log = React.useMemo(() => createLoggerWrapper(logger), [logger]);
12000
12280
  const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
12001
12281
  const auth = useAuth();
@@ -12170,6 +12450,24 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12170
12450
  };
12171
12451
  fetchConfig();
12172
12452
  }, [apiEndpoint, clientId, customization, skipConfigFetch, sdkReady, proxyMode, log]);
12453
+ // Fetch contact schema for registration fields when collectionId is provided
12454
+ React.useEffect(() => {
12455
+ if (!collectionId || !sdkReady)
12456
+ return;
12457
+ const fetchSchema = async () => {
12458
+ try {
12459
+ console.log('[SmartlinksAuthUI] 📋 Fetching contact schema for collection:', collectionId);
12460
+ const schema = await smartlinks__namespace.contact.publicGetSchema(collectionId);
12461
+ console.log('[SmartlinksAuthUI] ✅ Schema loaded:', schema);
12462
+ setContactSchema(schema);
12463
+ }
12464
+ catch (err) {
12465
+ console.warn('[SmartlinksAuthUI] ⚠️ Failed to fetch schema (non-fatal):', err);
12466
+ // Non-fatal - registration will work without schema fields
12467
+ }
12468
+ };
12469
+ fetchSchema();
12470
+ }, [collectionId, sdkReady]);
12173
12471
  // Reset showEmailForm when mode changes away from login/register
12174
12472
  React.useEffect(() => {
12175
12473
  if (mode !== 'login' && mode !== 'register') {
@@ -12194,8 +12492,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12194
12492
  const params = getUrlParams();
12195
12493
  const urlMode = params.get('mode');
12196
12494
  const token = params.get('token');
12197
- log.log('URL params detected:', { urlMode, token, hash: window.location.hash, search: window.location.search });
12198
- if (urlMode && token) {
12495
+ // Check for Google OAuth redirect callback
12496
+ const authCode = params.get('code');
12497
+ const state = params.get('state');
12498
+ log.log('URL params detected:', { urlMode, token, authCode: !!authCode, state: !!state, hash: window.location.hash, search: window.location.search });
12499
+ if (authCode && state) {
12500
+ // Google OAuth redirect callback
12501
+ handleGoogleAuthCodeCallback(authCode, state);
12502
+ }
12503
+ else if (urlMode && token) {
12199
12504
  handleURLBasedAuth(urlMode, token);
12200
12505
  }
12201
12506
  }, []);
@@ -12210,18 +12515,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12210
12515
  const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
12211
12516
  if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
12212
12517
  // Auto-login modes: Log the user in immediately if token is provided
12213
- auth.login(response.token, response.user, response.accountData, true);
12518
+ auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response));
12214
12519
  setAuthSuccess(true);
12215
12520
  setSuccessMessage('Email verified successfully! You are now logged in.');
12216
12521
  onAuthSuccess(response.token, response.user, response.accountData);
12217
12522
  // Clear the URL parameters
12218
12523
  const cleanUrl = window.location.href.split('?')[0];
12219
12524
  window.history.replaceState({}, document.title, cleanUrl);
12220
- // Redirect after a brief delay to show success message
12525
+ // For email verification deep links, redirect immediately if configured
12526
+ // (user came from an email link, so redirect is expected behavior)
12221
12527
  if (redirectUrl) {
12222
- setTimeout(() => {
12223
- window.location.href = redirectUrl;
12224
- }, 2000);
12528
+ window.location.href = redirectUrl;
12225
12529
  }
12226
12530
  }
12227
12531
  else {
@@ -12253,18 +12557,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12253
12557
  const response = await api.verifyMagicLink(token);
12254
12558
  // Auto-login with magic link if token is provided
12255
12559
  if (response.token) {
12256
- auth.login(response.token, response.user, response.accountData, true);
12560
+ auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response));
12257
12561
  setAuthSuccess(true);
12258
12562
  setSuccessMessage('Magic link verified! You are now logged in.');
12259
12563
  onAuthSuccess(response.token, response.user, response.accountData);
12260
12564
  // Clear the URL parameters
12261
12565
  const cleanUrl = window.location.href.split('?')[0];
12262
12566
  window.history.replaceState({}, document.title, cleanUrl);
12263
- // Redirect after a brief delay to show success message
12567
+ // For magic link deep links, redirect immediately if configured
12568
+ // (user came from an email link, so redirect is expected behavior)
12264
12569
  if (redirectUrl) {
12265
- setTimeout(() => {
12266
- window.location.href = redirectUrl;
12267
- }, 2000);
12570
+ window.location.href = redirectUrl;
12268
12571
  }
12269
12572
  }
12270
12573
  else {
@@ -12310,6 +12613,43 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12310
12613
  setLoading(false);
12311
12614
  }
12312
12615
  };
12616
+ // Handle Google OAuth authorization code callback (from redirect flow)
12617
+ const handleGoogleAuthCodeCallback = async (code, stateParam) => {
12618
+ setLoading(true);
12619
+ setError(undefined);
12620
+ try {
12621
+ // Parse state to get context
12622
+ const state = JSON.parse(decodeURIComponent(stateParam));
12623
+ log.log('Google OAuth redirect callback:', { clientId: state.clientId, returnPath: state.returnPath });
12624
+ // Determine the redirect URI that was used (must match exactly)
12625
+ const redirectUri = state.redirectUri || window.location.origin + window.location.pathname;
12626
+ // Exchange authorization code for tokens
12627
+ const response = await api.loginWithGoogleCode(code, redirectUri);
12628
+ if (response.token) {
12629
+ auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response));
12630
+ setAuthSuccess(true);
12631
+ setSuccessMessage('Google login successful!');
12632
+ onAuthSuccess(response.token, response.user, response.accountData);
12633
+ }
12634
+ else {
12635
+ throw new Error('Authentication failed - no token received');
12636
+ }
12637
+ // Clean URL parameters
12638
+ const cleanUrl = window.location.origin + window.location.pathname + (state.returnPath?.includes('#') ? state.returnPath.split('?')[0] : window.location.hash.split('?')[0]);
12639
+ window.history.replaceState({}, document.title, cleanUrl);
12640
+ }
12641
+ catch (err) {
12642
+ const errorMessage = err instanceof Error ? err.message : 'Google login failed';
12643
+ setError(getFriendlyErrorMessage(errorMessage));
12644
+ onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
12645
+ // Clean URL parameters even on error
12646
+ const cleanUrl = window.location.origin + window.location.pathname + window.location.hash.split('?')[0];
12647
+ window.history.replaceState({}, document.title, cleanUrl);
12648
+ }
12649
+ finally {
12650
+ setLoading(false);
12651
+ }
12652
+ };
12313
12653
  const handleEmailAuth = async (data) => {
12314
12654
  setLoading(true);
12315
12655
  setError(undefined);
@@ -12329,7 +12669,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12329
12669
  // Handle different verification modes
12330
12670
  if (verificationMode === 'immediate' && response.token) {
12331
12671
  // Immediate mode: Log in right away if token is provided (isNewUser=true for registration)
12332
- auth.login(response.token, response.user, response.accountData, true);
12672
+ auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response));
12333
12673
  setAuthSuccess(true);
12334
12674
  const deadline = response.emailVerificationDeadline
12335
12675
  ? new Date(response.emailVerificationDeadline).toLocaleString()
@@ -12338,19 +12678,37 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12338
12678
  if (response.token) {
12339
12679
  onAuthSuccess(response.token, response.user, response.accountData);
12340
12680
  }
12341
- if (redirectUrl) {
12342
- setTimeout(() => {
12343
- window.location.href = redirectUrl;
12344
- }, 2000);
12345
- }
12681
+ // Note: No automatic redirect - app controls navigation via onAuthSuccess callback
12346
12682
  }
12347
12683
  else if (verificationMode === 'verify-then-auto-login') {
12348
12684
  // Verify-then-auto-login mode: Don't log in yet, but will auto-login after email verification
12685
+ // Send the verification email since backend register may not send it automatically
12686
+ if (response.user?.uid && data.email) {
12687
+ try {
12688
+ await api.sendEmailVerification(response.user.uid, data.email, getRedirectUrl());
12689
+ log.log('Verification email sent after registration');
12690
+ }
12691
+ catch (verifyError) {
12692
+ log.warn('Failed to send verification email after registration:', verifyError);
12693
+ // Don't fail the registration, just log the warning
12694
+ }
12695
+ }
12349
12696
  setAuthSuccess(true);
12350
12697
  setSuccessMessage('Account created! Please check your email and click the verification link to complete your registration.');
12351
12698
  }
12352
12699
  else {
12353
12700
  // verify-then-manual-login mode: Traditional flow
12701
+ // Send the verification email since backend register may not send it automatically
12702
+ if (response.user?.uid && data.email) {
12703
+ try {
12704
+ await api.sendEmailVerification(response.user.uid, data.email, getRedirectUrl());
12705
+ log.log('Verification email sent after registration');
12706
+ }
12707
+ catch (verifyError) {
12708
+ log.warn('Failed to send verification email after registration:', verifyError);
12709
+ // Don't fail the registration, just log the warning
12710
+ }
12711
+ }
12354
12712
  setAuthSuccess(true);
12355
12713
  setSuccessMessage('Account created successfully! Please check your email to verify your account, then log in.');
12356
12714
  }
@@ -12365,15 +12723,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12365
12723
  if (response.requiresEmailVerification) {
12366
12724
  throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
12367
12725
  }
12368
- auth.login(response.token, response.user, response.accountData, false);
12726
+ auth.login(response.token, response.user, response.accountData, false, getExpirationFromResponse(response));
12369
12727
  setAuthSuccess(true);
12370
12728
  setSuccessMessage('Login successful!');
12371
12729
  onAuthSuccess(response.token, response.user, response.accountData);
12372
- if (redirectUrl) {
12373
- setTimeout(() => {
12374
- window.location.href = redirectUrl;
12375
- }, 2000);
12376
- }
12730
+ // Note: No automatic redirect - app controls navigation via onAuthSuccess callback
12377
12731
  }
12378
12732
  else {
12379
12733
  throw new Error('Authentication failed - please verify your email before logging in.');
@@ -12448,11 +12802,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12448
12802
  // Use custom client ID from config, or fall back to default Smartlinks client ID
12449
12803
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
12450
12804
  // Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
12451
- const oauthFlow = config?.googleOAuthFlow || 'oneTap';
12805
+ const configuredFlow = config?.googleOAuthFlow || 'oneTap';
12806
+ // For oneTap, automatically use redirect flow in WebView environments
12807
+ const isWebView = detectWebView();
12808
+ const oauthFlow = (configuredFlow === 'oneTap' && isWebView) ? 'redirect' : configuredFlow;
12452
12809
  // Log Google Auth configuration for debugging
12453
12810
  log.log('Google Auth initiated:', {
12454
12811
  googleClientId,
12455
- oauthFlow,
12812
+ configuredFlow,
12813
+ effectiveFlow: oauthFlow,
12814
+ isWebView,
12456
12815
  currentOrigin: window.location.origin,
12457
12816
  currentHref: window.location.href,
12458
12817
  configGoogleClientId: config?.googleClientId,
@@ -12468,7 +12827,37 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12468
12827
  throw new Error('Google Identity Services failed to initialize');
12469
12828
  }
12470
12829
  log.log('Google Identity Services loaded, using flow:', oauthFlow);
12471
- if (oauthFlow === 'popup') {
12830
+ if (oauthFlow === 'redirect') {
12831
+ // Use OAuth2 redirect flow (works in WebViews and everywhere)
12832
+ if (!google.accounts.oauth2) {
12833
+ throw new Error('Google OAuth2 not available');
12834
+ }
12835
+ // Build the redirect URI - use current URL without query params
12836
+ const redirectUri = getRedirectUrl();
12837
+ // Build state parameter to preserve context across redirect
12838
+ const state = encodeURIComponent(JSON.stringify({
12839
+ clientId,
12840
+ returnPath: window.location.hash || window.location.pathname,
12841
+ redirectUri,
12842
+ }));
12843
+ log.log('Initializing Google OAuth2 redirect flow:', {
12844
+ client_id: googleClientId,
12845
+ scope: 'openid email profile',
12846
+ redirect_uri: redirectUri,
12847
+ state,
12848
+ });
12849
+ const client = google.accounts.oauth2.initCodeClient({
12850
+ client_id: googleClientId,
12851
+ scope: 'openid email profile',
12852
+ ux_mode: 'redirect',
12853
+ redirect_uri: redirectUri,
12854
+ state,
12855
+ });
12856
+ // This will navigate away from the page
12857
+ client.requestCode();
12858
+ return; // Don't set loading to false - we're navigating away
12859
+ }
12860
+ else if (oauthFlow === 'popup') {
12472
12861
  // Use OAuth2 popup flow (works in iframes but requires popup permission)
12473
12862
  if (!google.accounts.oauth2) {
12474
12863
  throw new Error('Google OAuth2 not available');
@@ -12526,7 +12915,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12526
12915
  });
12527
12916
  if (authResponse.token) {
12528
12917
  // Google OAuth can be login or signup - use isNewUser flag from backend if available
12529
- auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser);
12918
+ auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
12530
12919
  setAuthSuccess(true);
12531
12920
  setSuccessMessage('Google login successful!');
12532
12921
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
@@ -12534,11 +12923,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12534
12923
  else {
12535
12924
  throw new Error('Authentication failed - no token received');
12536
12925
  }
12537
- if (redirectUrl) {
12538
- setTimeout(() => {
12539
- window.location.href = redirectUrl;
12540
- }, 2000);
12541
- }
12926
+ // Note: No automatic redirect - app controls navigation via onAuthSuccess callback
12542
12927
  }
12543
12928
  catch (apiError) {
12544
12929
  const errorMessage = apiError instanceof Error ? apiError.message : 'Google login failed';
@@ -12563,7 +12948,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12563
12948
  client.requestAccessToken();
12564
12949
  }
12565
12950
  else {
12566
- // Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
12951
+ // Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes or WebViews)
12567
12952
  log.log('Initializing Google OneTap flow:', {
12568
12953
  client_id: googleClientId,
12569
12954
  origin: window.location.origin,
@@ -12576,7 +12961,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12576
12961
  const authResponse = await api.loginWithGoogle(idToken);
12577
12962
  if (authResponse.token) {
12578
12963
  // Google OAuth can be login or signup - use isNewUser flag from backend if available
12579
- auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser);
12964
+ auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
12580
12965
  setAuthSuccess(true);
12581
12966
  setSuccessMessage('Google login successful!');
12582
12967
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
@@ -12584,11 +12969,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12584
12969
  else {
12585
12970
  throw new Error('Authentication failed - no token received');
12586
12971
  }
12587
- if (redirectUrl) {
12588
- setTimeout(() => {
12589
- window.location.href = redirectUrl;
12590
- }, 2000);
12591
- }
12972
+ // Note: No automatic redirect - app controls navigation via onAuthSuccess callback
12592
12973
  setLoading(false);
12593
12974
  }
12594
12975
  catch (err) {
@@ -12600,8 +12981,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12600
12981
  },
12601
12982
  auto_select: false,
12602
12983
  cancel_on_tap_outside: true,
12603
- // Note: use_fedcm_for_prompt omitted - requires Permissions-Policy header on hosting server
12604
- // Will be needed when FedCM becomes mandatory in the future
12984
+ use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
12605
12985
  });
12606
12986
  // Use timeout fallback instead of deprecated notification methods
12607
12987
  // (isNotDisplayed/isSkippedMoment will stop working when FedCM becomes mandatory)
@@ -12638,7 +13018,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12638
13018
  // Update auth context with account data if token is provided
12639
13019
  if (response.token) {
12640
13020
  // Phone auth can be login or signup - use isNewUser flag from backend if available
12641
- auth.login(response.token, response.user, response.accountData, response.isNewUser);
13021
+ auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response));
12642
13022
  onAuthSuccess(response.token, response.user, response.accountData);
12643
13023
  if (redirectUrl) {
12644
13024
  window.location.href = redirectUrl;
@@ -12811,36 +13191,39 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12811
13191
  setShowResendVerification(false);
12812
13192
  setShowRequestNewReset(false);
12813
13193
  setError(undefined);
12814
- }, 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 }))] }));
13194
+ }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, 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 }))] }));
12815
13195
  })() })) })) : null }));
12816
13196
  };
12817
13197
 
12818
- const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', customization = {}, }) => {
13198
+ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, className = '', customization = {}, }) => {
12819
13199
  const auth = useAuth();
12820
13200
  const [loading, setLoading] = React.useState(false);
12821
13201
  const [profile, setProfile] = React.useState(null);
12822
13202
  const [error, setError] = React.useState();
12823
13203
  const [success, setSuccess] = React.useState();
13204
+ // Schema state
13205
+ const [schema, setSchema] = React.useState(null);
13206
+ const [schemaLoading, setSchemaLoading] = React.useState(false);
12824
13207
  // Track which section is being edited
12825
13208
  const [editingSection, setEditingSection] = React.useState(null);
12826
- // Profile form state
13209
+ // Form state for core fields
12827
13210
  const [displayName, setDisplayName] = React.useState('');
12828
- // Email change state
12829
13211
  const [newEmail, setNewEmail] = React.useState('');
12830
13212
  const [emailPassword, setEmailPassword] = React.useState('');
12831
- // Password change state
12832
13213
  const [currentPassword, setCurrentPassword] = React.useState('');
12833
13214
  const [newPassword, setNewPassword] = React.useState('');
12834
13215
  const [confirmPassword, setConfirmPassword] = React.useState('');
12835
- // Phone change state (reuses existing sendPhoneCode flow)
12836
13216
  const [newPhone, setNewPhone] = React.useState('');
12837
13217
  const [phoneCode, setPhoneCode] = React.useState('');
12838
13218
  const [phoneCodeSent, setPhoneCodeSent] = React.useState(false);
13219
+ // Custom fields form state
13220
+ const [customFieldValues, setCustomFieldValues] = React.useState({});
12839
13221
  // Account deletion state
12840
13222
  const [deletePassword, setDeletePassword] = React.useState('');
12841
13223
  const [deleteConfirmText, setDeleteConfirmText] = React.useState('');
12842
13224
  const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
12843
- const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, } = customization;
13225
+ const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, showCustomFields = true, // New: show schema-driven custom fields
13226
+ } = customization;
12844
13227
  // Reinitialize Smartlinks SDK when apiEndpoint changes
12845
13228
  React.useEffect(() => {
12846
13229
  if (apiEndpoint) {
@@ -12851,10 +13234,32 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
12851
13234
  });
12852
13235
  }
12853
13236
  }, [apiEndpoint]);
12854
- // Load user profile on mount
13237
+ // Load schema when collectionId is available
13238
+ React.useEffect(() => {
13239
+ if (!collectionId)
13240
+ return;
13241
+ const loadSchema = async () => {
13242
+ setSchemaLoading(true);
13243
+ try {
13244
+ console.log('[AccountManagement] Loading schema for collection:', collectionId);
13245
+ const schemaResult = await smartlinks__namespace.contact.publicGetSchema(collectionId);
13246
+ console.log('[AccountManagement] Schema loaded:', schemaResult);
13247
+ setSchema(schemaResult);
13248
+ }
13249
+ catch (err) {
13250
+ console.warn('[AccountManagement] Failed to load schema:', err);
13251
+ // Non-fatal - component works without schema
13252
+ }
13253
+ finally {
13254
+ setSchemaLoading(false);
13255
+ }
13256
+ };
13257
+ loadSchema();
13258
+ }, [collectionId]);
13259
+ // Load user profile and contact data on mount
12855
13260
  React.useEffect(() => {
12856
13261
  loadProfile();
12857
- }, [clientId]);
13262
+ }, [clientId, collectionId]);
12858
13263
  const loadProfile = async () => {
12859
13264
  if (!auth.isAuthenticated) {
12860
13265
  setError('You must be logged in to manage your account');
@@ -12863,10 +13268,7 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
12863
13268
  setLoading(true);
12864
13269
  setError(undefined);
12865
13270
  try {
12866
- // TODO: Backend implementation required
12867
- // Endpoint: GET /api/v1/authkit/:clientId/account/profile
12868
- // SDK method: smartlinks.authKit.getProfile(clientId)
12869
- // Temporary mock data for UI testing
13271
+ // Get base profile from auth context
12870
13272
  const profileData = {
12871
13273
  uid: auth.user?.uid || '',
12872
13274
  email: auth.user?.email,
@@ -12878,6 +13280,17 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
12878
13280
  };
12879
13281
  setProfile(profileData);
12880
13282
  setDisplayName(profileData.displayName || '');
13283
+ // Load contact custom fields if collectionId provided
13284
+ if (collectionId && auth.contact) {
13285
+ setCustomFieldValues(auth.contact.customFields || {});
13286
+ }
13287
+ else if (collectionId) {
13288
+ // Try to fetch contact
13289
+ const contact = await auth.getContact?.();
13290
+ if (contact?.customFields) {
13291
+ setCustomFieldValues(contact.customFields);
13292
+ }
13293
+ }
12881
13294
  }
12882
13295
  catch (err) {
12883
13296
  const errorMessage = err instanceof Error ? err.message : 'Failed to load profile';
@@ -12888,28 +13301,39 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
12888
13301
  setLoading(false);
12889
13302
  }
12890
13303
  };
13304
+ const cancelEdit = React.useCallback(() => {
13305
+ setEditingSection(null);
13306
+ setDisplayName(profile?.displayName || '');
13307
+ setNewEmail('');
13308
+ setEmailPassword('');
13309
+ setCurrentPassword('');
13310
+ setNewPassword('');
13311
+ setConfirmPassword('');
13312
+ setNewPhone('');
13313
+ setPhoneCode('');
13314
+ setPhoneCodeSent(false);
13315
+ setError(undefined);
13316
+ setSuccess(undefined);
13317
+ // Reset custom fields to original values
13318
+ if (auth.contact?.customFields) {
13319
+ setCustomFieldValues(auth.contact.customFields);
13320
+ }
13321
+ }, [profile, auth.contact]);
13322
+ // Get editable custom fields from schema
13323
+ const editableCustomFields = getEditableFields(schema).filter(f => !['email', 'displayName', 'phone', 'phoneNumber'].includes(f.key));
12891
13324
  const handleUpdateProfile = async (e) => {
12892
13325
  e.preventDefault();
12893
13326
  setLoading(true);
12894
13327
  setError(undefined);
12895
13328
  setSuccess(undefined);
12896
13329
  try {
12897
- // TODO: Backend implementation required
12898
- // Endpoint: POST /api/v1/authkit/:clientId/account/update-profile
12899
- // SDK method: smartlinks.authKit.updateProfile(clientId, updateData)
12900
- setError('Backend API not yet implemented. See console for required endpoint.');
12901
- console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-profile');
12902
- console.log('Update data:', { displayName });
12903
- // Uncomment when backend is ready:
12904
- // const updateData: ProfileUpdateData = {
12905
- // displayName: displayName || undefined,
12906
- // photoURL: photoURL || undefined,
12907
- // };
12908
- // const updatedProfile = await smartlinks.authKit.updateProfile(clientId, updateData);
12909
- // setProfile(updatedProfile);
12910
- // setSuccess('Profile updated successfully!');
12911
- // setEditingSection(null);
12912
- // onProfileUpdated?.(updatedProfile);
13330
+ await smartlinks__namespace.authKit.updateProfile(clientId, { displayName });
13331
+ setSuccess('Profile updated successfully!');
13332
+ setEditingSection(null);
13333
+ // Update local state
13334
+ if (profile) {
13335
+ setProfile({ ...profile, displayName });
13336
+ }
12913
13337
  }
12914
13338
  catch (err) {
12915
13339
  const errorMessage = err instanceof Error ? err.message : 'Failed to update profile';
@@ -12920,19 +13344,29 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
12920
13344
  setLoading(false);
12921
13345
  }
12922
13346
  };
12923
- const cancelEdit = () => {
12924
- setEditingSection(null);
12925
- setDisplayName(profile?.displayName || '');
12926
- setNewEmail('');
12927
- setEmailPassword('');
12928
- setCurrentPassword('');
12929
- setNewPassword('');
12930
- setConfirmPassword('');
12931
- setNewPhone('');
12932
- setPhoneCode('');
12933
- setPhoneCodeSent(false);
13347
+ const handleUpdateCustomFields = async (e) => {
13348
+ e.preventDefault();
13349
+ if (!collectionId) {
13350
+ setError('Collection ID is required to update custom fields');
13351
+ return;
13352
+ }
13353
+ setLoading(true);
12934
13354
  setError(undefined);
12935
13355
  setSuccess(undefined);
13356
+ try {
13357
+ console.log('[AccountManagement] Updating custom fields:', customFieldValues);
13358
+ await auth.updateContactCustomFields?.(customFieldValues);
13359
+ setSuccess('Profile updated successfully!');
13360
+ setEditingSection(null);
13361
+ }
13362
+ catch (err) {
13363
+ const errorMessage = err instanceof Error ? err.message : 'Failed to update profile';
13364
+ setError(errorMessage);
13365
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
13366
+ }
13367
+ finally {
13368
+ setLoading(false);
13369
+ }
12936
13370
  };
12937
13371
  const handleChangeEmail = async (e) => {
12938
13372
  e.preventDefault();
@@ -12940,21 +13374,12 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
12940
13374
  setError(undefined);
12941
13375
  setSuccess(undefined);
12942
13376
  try {
12943
- // TODO: Backend implementation required
12944
- // Endpoint: POST /api/v1/authkit/:clientId/account/change-email
12945
- // SDK method: smartlinks.authKit.changeEmail(clientId, newEmail, password)
12946
- // Note: No verification flow for now - direct email update
12947
- setError('Backend API not yet implemented. See console for required endpoint.');
12948
- console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-email');
12949
- console.log('Data:', { newEmail });
12950
- // Uncomment when backend is ready:
12951
- // await smartlinks.authKit.changeEmail(clientId, newEmail, emailPassword);
12952
- // setSuccess('Email changed successfully!');
12953
- // setEditingSection(null);
12954
- // setNewEmail('');
12955
- // setEmailPassword('');
12956
- // onEmailChangeRequested?.();
12957
- // await loadProfile(); // Reload to show new email
13377
+ const redirectUrl = window.location.href;
13378
+ await smartlinks__namespace.authKit.changeEmail(clientId, newEmail, emailPassword, redirectUrl);
13379
+ setSuccess('Email change requested. Please check your new email for verification.');
13380
+ setEditingSection(null);
13381
+ setNewEmail('');
13382
+ setEmailPassword('');
12958
13383
  }
12959
13384
  catch (err) {
12960
13385
  const errorMessage = err instanceof Error ? err.message : 'Failed to change email';
@@ -12979,20 +13404,12 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
12979
13404
  setError(undefined);
12980
13405
  setSuccess(undefined);
12981
13406
  try {
12982
- // TODO: Backend implementation required
12983
- // Endpoint: POST /api/v1/authkit/:clientId/account/change-password
12984
- // SDK method: smartlinks.authKit.changePassword(clientId, currentPassword, newPassword)
12985
- setError('Backend API not yet implemented. See console for required endpoint.');
12986
- console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/change-password');
12987
- console.log('Data: currentPassword and newPassword provided');
12988
- // Uncomment when backend is ready:
12989
- // await smartlinks.authKit.changePassword(clientId, currentPassword, newPassword);
12990
- // setSuccess('Password changed successfully!');
12991
- // setEditingSection(null);
12992
- // setCurrentPassword('');
12993
- // setNewPassword('');
12994
- // setConfirmPassword('');
12995
- // onPasswordChanged?.();
13407
+ await smartlinks__namespace.authKit.changePassword(clientId, currentPassword, newPassword);
13408
+ setSuccess('Password changed successfully!');
13409
+ setEditingSection(null);
13410
+ setCurrentPassword('');
13411
+ setNewPassword('');
13412
+ setConfirmPassword('');
12996
13413
  }
12997
13414
  catch (err) {
12998
13415
  const errorMessage = err instanceof Error ? err.message : 'Failed to change password';
@@ -13026,20 +13443,13 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
13026
13443
  setError(undefined);
13027
13444
  setSuccess(undefined);
13028
13445
  try {
13029
- // TODO: Backend implementation required
13030
- // Endpoint: POST /api/v1/authkit/:clientId/account/update-phone
13031
- // SDK method: smartlinks.authKit.updatePhone(clientId, phoneNumber, verificationCode)
13032
- setError('Backend API not yet implemented. See console for required endpoint.');
13033
- console.log('Required API endpoint: POST /api/v1/authkit/:clientId/account/update-phone');
13034
- console.log('Data:', { phoneNumber: newPhone, code: phoneCode });
13035
- // Uncomment when backend is ready:
13036
- // await smartlinks.authKit.updatePhone(clientId, newPhone, phoneCode);
13037
- // setSuccess('Phone number updated successfully!');
13038
- // setEditingSection(null);
13039
- // setNewPhone('');
13040
- // setPhoneCode('');
13041
- // setPhoneCodeSent(false);
13042
- // await loadProfile();
13446
+ await smartlinks__namespace.authKit.updatePhone(clientId, newPhone, phoneCode);
13447
+ setSuccess('Phone number updated successfully!');
13448
+ setEditingSection(null);
13449
+ setNewPhone('');
13450
+ setPhoneCode('');
13451
+ setPhoneCodeSent(false);
13452
+ await loadProfile();
13043
13453
  }
13044
13454
  catch (err) {
13045
13455
  const errorMessage = err instanceof Error ? err.message : 'Failed to update phone number';
@@ -13062,19 +13472,9 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
13062
13472
  setLoading(true);
13063
13473
  setError(undefined);
13064
13474
  try {
13065
- // TODO: Backend implementation required
13066
- // Endpoint: DELETE /api/v1/authkit/:clientId/account/delete
13067
- // SDK method: smartlinks.authKit.deleteAccount(clientId, password, confirmText)
13068
- // Note: This performs a SOFT DELETE (marks as deleted, obfuscates email)
13069
- setError('Backend API not yet implemented. See console for required endpoint.');
13070
- console.log('Required API endpoint: DELETE /api/v1/authkit/:clientId/account/delete');
13071
- console.log('Data: password and confirmText="DELETE" provided');
13072
- console.log('Note: Backend should soft delete (mark deleted, obfuscate email, disable account)');
13073
- // Uncomment when backend is ready:
13074
- // await smartlinks.authKit.deleteAccount(clientId, deletePassword, deleteConfirmText);
13075
- // setSuccess('Account deleted successfully');
13076
- // onAccountDeleted?.();
13077
- // await auth.logout();
13475
+ await smartlinks__namespace.authKit.deleteAccount(clientId, deletePassword, deleteConfirmText);
13476
+ setSuccess('Account deleted successfully');
13477
+ await auth.logout();
13078
13478
  }
13079
13479
  catch (err) {
13080
13480
  const errorMessage = err instanceof Error ? err.message : 'Failed to delete account';
@@ -13085,120 +13485,24 @@ const AccountManagement = ({ apiEndpoint, clientId, onError, className = '', cus
13085
13485
  setLoading(false);
13086
13486
  }
13087
13487
  };
13488
+ const handleCustomFieldChange = (key, value) => {
13489
+ setCustomFieldValues(prev => ({ ...prev, [key]: value }));
13490
+ };
13088
13491
  if (!auth.isAuthenticated) {
13089
13492
  return (jsxRuntime.jsx("div", { className: `account-management ${className}`, children: jsxRuntime.jsx("p", { className: "text-muted-foreground", children: "Please log in to manage your account" }) }));
13090
13493
  }
13091
- if (loading && !profile) {
13494
+ if ((loading || schemaLoading) && !profile) {
13092
13495
  return (jsxRuntime.jsx("div", { className: `account-management ${className}`, children: jsxRuntime.jsx("p", { className: "text-muted-foreground", children: "Loading..." }) }));
13093
13496
  }
13094
- 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: () => {
13497
+ 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" })] })] }))] })), showCustomFields && collectionId && editableCustomFields.length > 0 && (jsxRuntime.jsxs("section", { className: "account-section", children: [jsxRuntime.jsx("h3", { className: "section-title", style: { fontSize: '0.9rem', fontWeight: 600, marginBottom: '12px' }, children: "Additional Information" }), editingSection !== 'customFields' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [editableCustomFields.map((field) => (jsxRuntime.jsx("div", { className: "account-field", children: jsxRuntime.jsxs("div", { className: "field-info", children: [jsxRuntime.jsx("label", { className: "field-label", children: field.label }), jsxRuntime.jsx("div", { className: "field-value", children: customFieldValues[field.key] !== undefined && customFieldValues[field.key] !== ''
13498
+ ? String(customFieldValues[field.key])
13499
+ : 'Not set' })] }) }, field.key))), jsxRuntime.jsx("button", { type: "button", onClick: () => setEditingSection('customFields'), className: "auth-button button-secondary", style: { marginTop: '8px' }, children: "Edit Information" })] })) : (jsxRuntime.jsxs("form", { onSubmit: handleUpdateCustomFields, className: "edit-form", children: [editableCustomFields.map((field) => (jsxRuntime.jsx(SchemaFieldRenderer, { field: field, value: customFieldValues[field.key], onChange: handleCustomFieldChange, disabled: loading }, field.key))), 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: () => {
13095
13500
  setShowDeleteConfirm(false);
13096
13501
  setDeletePassword('');
13097
13502
  setDeleteConfirmText('');
13098
13503
  }, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] }));
13099
13504
  };
13100
13505
 
13101
- const SmartlinksClaimUI = (props) => {
13102
- // Destructure AFTER logging raw props to debug proxyMode issue
13103
- const { apiEndpoint, clientId, clientName, collectionId, productId, proofId, onClaimSuccess, onClaimError, additionalFields = [], theme = 'light', className = '', minimal = false, proxyMode = false, customization = {}, } = props;
13104
- // Debug logging for proxyMode - log RAW props first
13105
- console.log('[SmartlinksClaimUI] 🔍 RAW props received:', props);
13106
- console.log('[SmartlinksClaimUI] 🔍 props.proxyMode value:', props.proxyMode);
13107
- console.log('[SmartlinksClaimUI] 🔍 typeof props.proxyMode:', typeof props.proxyMode);
13108
- console.log('[SmartlinksClaimUI] 🎯 Destructured proxyMode:', proxyMode);
13109
- const auth = useAuth();
13110
- const [claimStep, setClaimStep] = React.useState(auth.isAuthenticated ? 'questions' : 'auth');
13111
- const [claimData, setClaimData] = React.useState({});
13112
- const [error, setError] = React.useState();
13113
- const [loading, setLoading] = React.useState(false);
13114
- const handleAuthSuccess = (token, user, accountData) => {
13115
- // Authentication successful
13116
- auth.login(token, user, accountData);
13117
- // If no additional questions, proceed directly to claim
13118
- if (additionalFields.length === 0) {
13119
- executeClaim(user);
13120
- }
13121
- else {
13122
- setClaimStep('questions');
13123
- }
13124
- };
13125
- const handleQuestionSubmit = async (e) => {
13126
- e.preventDefault();
13127
- // Validate required fields
13128
- const missingFields = additionalFields
13129
- .filter(field => field.required && !claimData[field.name])
13130
- .map(field => field.label);
13131
- if (missingFields.length > 0) {
13132
- setError(`Please fill in: ${missingFields.join(', ')}`);
13133
- return;
13134
- }
13135
- // Execute claim with collected data
13136
- if (auth.user) {
13137
- executeClaim(auth.user);
13138
- }
13139
- };
13140
- const executeClaim = async (user) => {
13141
- setClaimStep('claiming');
13142
- setLoading(true);
13143
- setError(undefined);
13144
- try {
13145
- // Create attestation to claim the proof
13146
- const response = await smartlinks__namespace.attestation.create(collectionId, productId, proofId, {
13147
- public: {
13148
- claimed: true,
13149
- claimedAt: new Date().toISOString(),
13150
- claimedBy: user.uid,
13151
- ...claimData,
13152
- },
13153
- private: {},
13154
- proof: {},
13155
- });
13156
- setClaimStep('success');
13157
- // Call success callback
13158
- onClaimSuccess({
13159
- proofId,
13160
- user,
13161
- claimData,
13162
- attestationId: response.id,
13163
- });
13164
- }
13165
- catch (err) {
13166
- console.error('Claim error:', err);
13167
- const errorMessage = err instanceof Error ? err.message : 'Failed to claim proof';
13168
- setError(errorMessage);
13169
- onClaimError?.(err instanceof Error ? err : new Error(errorMessage));
13170
- setClaimStep(additionalFields.length > 0 ? 'questions' : 'auth');
13171
- }
13172
- finally {
13173
- setLoading(false);
13174
- }
13175
- };
13176
- const handleFieldChange = (fieldName, value) => {
13177
- setClaimData(prev => ({
13178
- ...prev,
13179
- [fieldName]: value,
13180
- }));
13181
- };
13182
- // Render authentication step
13183
- if (claimStep === 'auth') {
13184
- console.log('[SmartlinksClaimUI] 🔑 Rendering auth step with proxyMode:', proxyMode);
13185
- return (jsxRuntime.jsx("div", { className: className, children: jsxRuntime.jsx(SmartlinksAuthUI, { apiEndpoint: apiEndpoint, clientId: clientId, clientName: clientName, onAuthSuccess: handleAuthSuccess, onAuthError: onClaimError, theme: theme, minimal: minimal, proxyMode: proxyMode, customization: customization.authConfig }) }));
13186
- }
13187
- // Render additional questions step
13188
- if (claimStep === 'questions') {
13189
- 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' })] })] }));
13190
- }
13191
- // Render claiming step (loading state)
13192
- if (claimStep === 'claiming') {
13193
- 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..." })] }));
13194
- }
13195
- // Render success step
13196
- if (claimStep === 'success') {
13197
- 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.' })] }));
13198
- }
13199
- return null;
13200
- };
13201
-
13202
13506
  const ProtectedRoute = ({ children, fallback, redirectTo, }) => {
13203
13507
  const { isAuthenticated, isLoading } = useAuth();
13204
13508
  // Show loading state
@@ -13253,8 +13557,11 @@ exports.AuthProvider = AuthProvider;
13253
13557
  exports.AuthUIPreview = AuthUIPreview;
13254
13558
  exports.FirebaseAuthUI = SmartlinksAuthUI;
13255
13559
  exports.ProtectedRoute = ProtectedRoute;
13560
+ exports.SchemaFieldRenderer = SchemaFieldRenderer;
13256
13561
  exports.SmartlinksAuthUI = SmartlinksAuthUI;
13257
- exports.SmartlinksClaimUI = SmartlinksClaimUI;
13562
+ exports.getEditableFields = getEditableFields;
13563
+ exports.getRegistrationFields = getRegistrationFields;
13564
+ exports.sortFieldsByPlacement = sortFieldsByPlacement;
13258
13565
  exports.tokenStorage = tokenStorage;
13259
13566
  exports.useAuth = useAuth;
13260
13567
  //# sourceMappingURL=index.js.map