ultimate-jekyll-manager 0.0.119 → 0.0.120
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/CLAUDE.md +102 -2
- package/README.md +171 -2
- package/TODO.md +10 -2
- package/_backup/form-manager.backup.js +1020 -0
- package/dist/assets/js/libs/auth/pages.js +64 -136
- package/dist/assets/js/libs/form-manager.js +643 -775
- package/dist/assets/js/pages/account/sections/api-keys.js +37 -52
- package/dist/assets/js/pages/account/sections/connections.js +37 -46
- package/dist/assets/js/pages/account/sections/delete.js +46 -66
- package/dist/assets/js/pages/account/sections/profile.js +37 -56
- package/dist/assets/js/pages/account/sections/security.js +100 -126
- package/dist/assets/js/pages/admin/notifications/new/index.js +72 -157
- package/dist/assets/js/pages/blog/index.js +29 -51
- package/dist/assets/js/pages/contact/index.js +110 -144
- package/dist/assets/js/pages/download/index.js +38 -86
- package/dist/assets/js/pages/oauth2/index.js +17 -17
- package/dist/assets/js/pages/payment/checkout/index.js +23 -36
- package/dist/assets/js/pages/test/libraries/form-manager/index.js +194 -0
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signin.html +2 -2
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signup.html +2 -2
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html +10 -37
- package/dist/defaults/dist/pages/test/libraries/form-manager.html +181 -0
- package/dist/gulp/tasks/serve.js +18 -0
- package/dist/lib/logger.js +1 -1
- package/firebase-debug.log +392 -0
- package/package.json +6 -6
- package/.playwright-mcp/page-2025-10-22T19-11-27-666Z.png +0 -0
- package/.playwright-mcp/page-2025-10-22T19-11-57-357Z.png +0 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact Page JavaScript
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
// Libraries
|
|
2
6
|
import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
3
7
|
import fetch from 'wonderful-fetch';
|
|
8
|
+
|
|
4
9
|
let webManager = null;
|
|
5
10
|
|
|
6
11
|
// Module
|
|
@@ -13,46 +18,96 @@ export default (Manager) => {
|
|
|
13
18
|
await webManager.dom().ready();
|
|
14
19
|
|
|
15
20
|
setupForm();
|
|
21
|
+
setupFormScrolling();
|
|
16
22
|
|
|
17
23
|
// Resolve after initialization
|
|
18
24
|
return resolve();
|
|
19
25
|
});
|
|
20
26
|
};
|
|
21
27
|
|
|
22
|
-
// Global variables
|
|
23
|
-
let formManager = null;
|
|
24
|
-
|
|
25
28
|
// Setup form handling
|
|
26
29
|
function setupForm() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
autoDisable: true,
|
|
30
|
-
showSpinner: true,
|
|
31
|
-
validateOnSubmit: true,
|
|
32
|
-
allowMultipleSubmissions: false,
|
|
30
|
+
const formManager = new FormManager('#contact-form', {
|
|
31
|
+
allowResubmit: false,
|
|
33
32
|
resetOnSuccess: true,
|
|
34
|
-
submitButtonLoadingText: 'Sending...',
|
|
35
|
-
submitButtonSuccessText: 'Message Sent!',
|
|
36
33
|
});
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
formManager.on('submit', async ({ data }) => {
|
|
36
|
+
const slapformId = webManager.config.brand.contact['slapform-form-id'];
|
|
37
|
+
|
|
38
|
+
console.log('Contact form submission:', data);
|
|
39
|
+
|
|
40
|
+
// Check honeypot fields (anti-spam)
|
|
41
|
+
if (data.url_check || data.slap_honey) {
|
|
42
|
+
console.warn('Honeypot field filled - potential spam');
|
|
43
|
+
trackContactSpam();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check if slapformId is missing
|
|
48
|
+
if (!slapformId) {
|
|
49
|
+
webManager.sentry().captureException(new Error('Contact form is not configured - missing slapform ID'));
|
|
50
|
+
throw new Error('Contact form is not configured properly. Please try again later.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
trackContactFormSubmit(data.subject || 'general');
|
|
54
|
+
|
|
55
|
+
// Prepare data for API
|
|
56
|
+
const requestData = {
|
|
57
|
+
first_name: data.first_name,
|
|
58
|
+
last_name: data.last_name,
|
|
59
|
+
email: data.email,
|
|
60
|
+
company: data.company || '',
|
|
61
|
+
subject: data.subject,
|
|
62
|
+
message: data.message,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Get API endpoint from site config or use default
|
|
66
|
+
const apiEndpoint = `https://api.slapform.com/${slapformId}`;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Send request using wonderful-fetch
|
|
70
|
+
await fetch(apiEndpoint, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
body: requestData,
|
|
73
|
+
response: 'json',
|
|
74
|
+
timeout: 30000,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
formManager.showSuccess('Thank you for your message! We\'ll get back to you within 24 hours.');
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// Only capture technical errors to Sentry (network, timeout, API errors)
|
|
80
|
+
if (error.message?.includes('network') || error.message?.includes('timeout') || !error.message?.includes('Failed')) {
|
|
81
|
+
webManager.sentry().captureException(new Error('Contact form submission error', { cause: error }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Show user-friendly error message
|
|
85
|
+
let errorMessage = 'An error occurred while sending your message. ';
|
|
86
|
+
|
|
87
|
+
if (error.message?.includes('network')) {
|
|
88
|
+
errorMessage += 'Please check your internet connection and try again.';
|
|
89
|
+
} else if (error.message?.includes('timeout')) {
|
|
90
|
+
errorMessage += 'The request timed out. Please try again.';
|
|
91
|
+
} else if (error.message?.includes('Failed')) {
|
|
92
|
+
errorMessage = error.message;
|
|
93
|
+
} else {
|
|
94
|
+
errorMessage += 'Please try again or contact us directly.';
|
|
95
|
+
}
|
|
41
96
|
|
|
42
|
-
|
|
43
|
-
|
|
97
|
+
throw new Error(errorMessage);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
44
100
|
}
|
|
45
101
|
|
|
46
102
|
// Setup smooth scrolling to form and chat handler
|
|
47
103
|
function setupFormScrolling() {
|
|
48
|
-
// Find all contact method links
|
|
49
104
|
const $contactLinks = document.querySelectorAll('a[href="#form"], a[href="#chat"]');
|
|
50
105
|
|
|
51
|
-
$contactLinks.forEach(link => {
|
|
52
|
-
link.addEventListener('click', (e) => {
|
|
106
|
+
$contactLinks.forEach($link => {
|
|
107
|
+
$link.addEventListener('click', (e) => {
|
|
53
108
|
e.preventDefault();
|
|
54
109
|
|
|
55
|
-
const href = link.getAttribute('href');
|
|
110
|
+
const href = $link.getAttribute('href');
|
|
56
111
|
|
|
57
112
|
if (href === '#chat') {
|
|
58
113
|
// Open chat window
|
|
@@ -62,151 +117,62 @@ function setupFormScrolling() {
|
|
|
62
117
|
webManager.sentry().captureException(new Error('Error opening chat', { cause: error }));
|
|
63
118
|
webManager.utilities().showNotification('Chat is currently unavailable. Please try again later.', 'danger');
|
|
64
119
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const $formSection = document.getElementById('form');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
68
122
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
block: 'start'
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Focus the first input immediately and after scroll completes
|
|
77
|
-
const focusFirstInput = () => {
|
|
78
|
-
// Find the first input field in the form
|
|
79
|
-
const $firstInput = document.querySelector('#contact-form input');
|
|
80
|
-
if (!$firstInput) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
$firstInput.focus();
|
|
85
|
-
// Don't select text on mobile devices
|
|
86
|
-
if (!('ontouchstart' in window) && $firstInput.select) {
|
|
87
|
-
$firstInput.select();
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// Try focusing immediately
|
|
92
|
-
focusFirstInput();
|
|
93
|
-
|
|
94
|
-
// Also try after scroll animation completes
|
|
95
|
-
setTimeout(focusFirstInput, 800);
|
|
123
|
+
if (href === '#form') {
|
|
124
|
+
const $formSection = document.getElementById('form');
|
|
125
|
+
if (!$formSection) {
|
|
126
|
+
return;
|
|
96
127
|
}
|
|
128
|
+
|
|
129
|
+
// Smooth scroll to the form section
|
|
130
|
+
$formSection.scrollIntoView({
|
|
131
|
+
behavior: 'smooth',
|
|
132
|
+
block: 'start',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Focus the first input after scroll completes
|
|
136
|
+
const focusFirstInput = () => {
|
|
137
|
+
const $firstInput = document.querySelector('#contact-form input');
|
|
138
|
+
if (!$firstInput) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
$firstInput.focus();
|
|
143
|
+
// Don't select text on mobile devices
|
|
144
|
+
if (!('ontouchstart' in window) && $firstInput.select) {
|
|
145
|
+
$firstInput.select();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Try focusing immediately and after scroll animation
|
|
150
|
+
focusFirstInput();
|
|
151
|
+
setTimeout(focusFirstInput, 800);
|
|
97
152
|
}
|
|
98
153
|
});
|
|
99
154
|
});
|
|
100
155
|
}
|
|
101
156
|
|
|
102
|
-
//
|
|
103
|
-
async function handleFormSubmit(event) {
|
|
104
|
-
// Prevent default FormManager submission
|
|
105
|
-
event.preventDefault();
|
|
106
|
-
|
|
107
|
-
const formData = event.detail.data;
|
|
108
|
-
const slapformId = webManager.config.brand.contact['slapform-form-id'];
|
|
109
|
-
|
|
110
|
-
console.log('Contact form submission:', formData);
|
|
111
|
-
|
|
112
|
-
// Check honeypot field (anti-spam)
|
|
113
|
-
if (formData.url_check) {
|
|
114
|
-
console.warn('Honeypot field filled - potential spam');
|
|
115
|
-
trackContactSpam();
|
|
116
|
-
formManager.setFormState('ready');
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Check if slapformId is missing
|
|
121
|
-
if (!slapformId) {
|
|
122
|
-
webManager.sentry().captureException(new Error('Contact form is not configured - missing slapform ID'));
|
|
123
|
-
formManager.showError('Contact form is not configured properly. Please try again later.');
|
|
124
|
-
formManager.setFormState('ready');
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
trackContactFormSubmit(formData.subject || 'general');
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
// Prepare data for API
|
|
132
|
-
const requestData = {
|
|
133
|
-
first_name: formData.first_name,
|
|
134
|
-
last_name: formData.last_name,
|
|
135
|
-
email: formData.email,
|
|
136
|
-
company: formData.company || '',
|
|
137
|
-
subject: formData.subject,
|
|
138
|
-
message: formData.message,
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Get API endpoint from site config or use default
|
|
142
|
-
const apiEndpoint = `https://api.slapform.com/${slapformId}`;
|
|
143
|
-
|
|
144
|
-
// Send request using wonderful-fetch
|
|
145
|
-
await fetch(apiEndpoint, {
|
|
146
|
-
method: 'POST',
|
|
147
|
-
body: requestData,
|
|
148
|
-
response: 'json',
|
|
149
|
-
timeout: 30000 // 30 second timeout
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Handle successful response
|
|
153
|
-
formManager.showSuccess('Thank you for your message! We\'ll get back to you within 24 hours.');
|
|
154
|
-
formManager.setFormState('submitted');
|
|
155
|
-
} catch (error) {
|
|
156
|
-
// Only capture technical errors to Sentry (network, timeout, API errors)
|
|
157
|
-
if (error.message?.includes('network') || error.message?.includes('timeout') || !error.message?.includes('Failed')) {
|
|
158
|
-
webManager.sentry().captureException(new Error('Contact form submission error', { cause: error }));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Show user-friendly error message
|
|
162
|
-
let errorMessage = 'An error occurred while sending your message. ';
|
|
163
|
-
|
|
164
|
-
if (error.message?.includes('network')) {
|
|
165
|
-
errorMessage += 'Please check your internet connection and try again.';
|
|
166
|
-
} else if (error.message?.includes('timeout')) {
|
|
167
|
-
errorMessage += 'The request timed out. Please try again.';
|
|
168
|
-
} else if (error.message?.includes('Failed')) {
|
|
169
|
-
errorMessage = error.message;
|
|
170
|
-
} else {
|
|
171
|
-
errorMessage += 'Please try again or contact us directly.';
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
formManager.showError(errorMessage);
|
|
175
|
-
formManager.setFormState('ready');
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Handle field changes
|
|
180
|
-
function handleFieldChange(event) {
|
|
181
|
-
const { field, fieldName } = event.detail;
|
|
182
|
-
|
|
183
|
-
// Clear field-specific error message if it exists
|
|
184
|
-
if (field.dataset.invalidField) {
|
|
185
|
-
const $feedback = field.parentElement.querySelector('.invalid-feedback');
|
|
186
|
-
if ($feedback) {
|
|
187
|
-
$feedback.textContent = '';
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
157
|
+
// Tracking functions
|
|
192
158
|
function trackContactSpam() {
|
|
193
159
|
gtag('event', 'contact_form_spam', {
|
|
194
|
-
content_type: 'honeypot'
|
|
160
|
+
content_type: 'honeypot',
|
|
195
161
|
});
|
|
196
162
|
}
|
|
197
163
|
|
|
198
164
|
function trackContactFormSubmit(subject) {
|
|
199
165
|
gtag('event', 'generate_lead', {
|
|
200
166
|
lead_source: 'contact_form',
|
|
201
|
-
subject: subject
|
|
167
|
+
subject: subject,
|
|
202
168
|
});
|
|
203
169
|
fbq('track', 'Lead', {
|
|
204
170
|
content_name: 'Contact Form',
|
|
205
|
-
content_category: subject
|
|
171
|
+
content_category: subject,
|
|
206
172
|
});
|
|
207
173
|
ttq.track('Contact', {
|
|
208
174
|
content_id: 'contact-form',
|
|
209
175
|
content_type: 'product',
|
|
210
|
-
content_name: 'Contact Form'
|
|
176
|
+
content_name: 'Contact Form',
|
|
211
177
|
});
|
|
212
178
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download Page JavaScript
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
// Libraries
|
|
2
6
|
import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
3
7
|
import fetch from 'wonderful-fetch';
|
|
8
|
+
|
|
4
9
|
let webManager = null;
|
|
5
|
-
let mobileEmailForms = {};
|
|
6
10
|
|
|
7
11
|
// Module
|
|
8
12
|
export default (Manager) => {
|
|
@@ -37,8 +41,8 @@ const config = {
|
|
|
37
41
|
selectors: {
|
|
38
42
|
platformButtons: '.platform-btn',
|
|
39
43
|
platformDownloads: '[data-platform]',
|
|
40
|
-
downloadButtons: '.tab-pane[data-platform] .btn-primary:not([type="submit"])'
|
|
41
|
-
}
|
|
44
|
+
downloadButtons: '.tab-pane[data-platform] .btn-primary:not([type="submit"])',
|
|
45
|
+
},
|
|
42
46
|
};
|
|
43
47
|
|
|
44
48
|
// Setup platform detection and auto-select
|
|
@@ -128,55 +132,26 @@ function showOnboardingModal(platform) {
|
|
|
128
132
|
modal.show();
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
|
|
132
135
|
// Tracking functions
|
|
133
|
-
// function trackPlatformDetection(platform) {
|
|
134
|
-
// gtag('event', 'platform_detected', {
|
|
135
|
-
// platform: platform
|
|
136
|
-
// });
|
|
137
|
-
// fbq('track', 'ViewContent', {
|
|
138
|
-
// content_name: 'Download Page',
|
|
139
|
-
// content_category: platform
|
|
140
|
-
// });
|
|
141
|
-
// ttq.track('ViewContent', {
|
|
142
|
-
// content_name: 'Download Page',
|
|
143
|
-
// content_type: platform
|
|
144
|
-
// });
|
|
145
|
-
// }
|
|
146
|
-
|
|
147
|
-
// function trackPlatformSwitch(platform) {
|
|
148
|
-
// gtag('event', 'platform_switch', {
|
|
149
|
-
// platform: platform
|
|
150
|
-
// });
|
|
151
|
-
// fbq('track', 'CustomizeProduct', {
|
|
152
|
-
// content_name: 'Platform Selection',
|
|
153
|
-
// content_category: platform
|
|
154
|
-
// });
|
|
155
|
-
// ttq.track('ClickButton', {
|
|
156
|
-
// content_name: 'Platform Switch',
|
|
157
|
-
// content_type: platform
|
|
158
|
-
// });
|
|
159
|
-
// }
|
|
160
|
-
|
|
161
136
|
function trackDownloadClick(platform, downloadName, downloadUrl) {
|
|
162
137
|
console.log('Download clicked:', platform, downloadName, downloadUrl);
|
|
163
138
|
|
|
164
139
|
gtag('event', 'download', {
|
|
165
140
|
platform: platform,
|
|
166
141
|
download_name: downloadName,
|
|
167
|
-
download_url: downloadUrl
|
|
142
|
+
download_url: downloadUrl,
|
|
168
143
|
});
|
|
169
144
|
|
|
170
145
|
fbq('trackCustom', 'Download', {
|
|
171
146
|
content_name: downloadName,
|
|
172
147
|
content_category: platform,
|
|
173
|
-
content_type: 'download'
|
|
148
|
+
content_type: 'download',
|
|
174
149
|
});
|
|
175
150
|
|
|
176
151
|
ttq.track('Download', {
|
|
177
152
|
content_id: `download-${platform}`,
|
|
178
153
|
content_type: 'product',
|
|
179
|
-
content_name: downloadName
|
|
154
|
+
content_name: downloadName,
|
|
180
155
|
});
|
|
181
156
|
}
|
|
182
157
|
|
|
@@ -188,7 +163,9 @@ function setupCopyButtons() {
|
|
|
188
163
|
$button.addEventListener('click', async function() {
|
|
189
164
|
const $input = this.closest('.input-group').querySelector('input');
|
|
190
165
|
|
|
191
|
-
if (!$input || !$input.value)
|
|
166
|
+
if (!$input || !$input.value) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
192
169
|
|
|
193
170
|
try {
|
|
194
171
|
await webManager.utilities().clipboardCopy($input);
|
|
@@ -221,57 +198,32 @@ function setupMobileEmailForms() {
|
|
|
221
198
|
const formId = `#mobile-email-form-${platform}`;
|
|
222
199
|
|
|
223
200
|
const formManager = new FormManager(formId, {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
allowMultipleSubmissions: false,
|
|
228
|
-
submitButtonLoadingText: 'Sending...',
|
|
229
|
-
submitButtonSuccessText: 'Sent!',
|
|
201
|
+
allowResubmit: false,
|
|
202
|
+
submittingText: 'Sending...',
|
|
203
|
+
submittedText: 'Email Sent!',
|
|
230
204
|
});
|
|
231
205
|
|
|
232
|
-
formManager.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const apiEndpoint = webManager.getApiUrl() + '/backend-manager';
|
|
254
|
-
|
|
255
|
-
// Send request using wonderful-fetch
|
|
256
|
-
await fetch(apiEndpoint, {
|
|
257
|
-
method: 'POST',
|
|
258
|
-
body: {
|
|
259
|
-
command: 'general:send-email',
|
|
260
|
-
payload: {
|
|
261
|
-
id: 'general:download-app-link',
|
|
262
|
-
email: email,
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
|
-
response: 'json',
|
|
266
|
-
timeout: 30000
|
|
206
|
+
formManager.on('submit', async ({ data }) => {
|
|
207
|
+
console.log('Mobile email form submitted:', { platform, email: data.email });
|
|
208
|
+
|
|
209
|
+
// Get API endpoint
|
|
210
|
+
const apiEndpoint = webManager.getApiUrl() + '/backend-manager';
|
|
211
|
+
|
|
212
|
+
// Send request using wonderful-fetch
|
|
213
|
+
await fetch(apiEndpoint, {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
body: {
|
|
216
|
+
command: 'general:send-email',
|
|
217
|
+
payload: {
|
|
218
|
+
id: 'general:download-app-link',
|
|
219
|
+
email: data.email,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
response: 'json',
|
|
223
|
+
timeout: 30000,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
formManager.showSuccess('Success! Please check your email for the download link.');
|
|
267
227
|
});
|
|
268
|
-
|
|
269
|
-
// Handle successful response
|
|
270
|
-
formManager.showSuccess('Success! Please check your email for the download link.');
|
|
271
|
-
formManager.setFormState('submitted');
|
|
272
|
-
} catch (error) {
|
|
273
|
-
webManager.sentry().captureException(new Error('Mobile email form submission error', { cause: error }));
|
|
274
|
-
formManager.showError(error.message || 'Failed to send email');
|
|
275
|
-
formManager.setFormState('ready');
|
|
276
|
-
}
|
|
228
|
+
});
|
|
277
229
|
}
|
|
@@ -33,7 +33,7 @@ async function handleOAuthCallback() {
|
|
|
33
33
|
// Get URL parameters
|
|
34
34
|
const urlParams = new URLSearchParams(window.location.search);
|
|
35
35
|
const code = urlParams.get('code');
|
|
36
|
-
const
|
|
36
|
+
const state = urlParams.get('state');
|
|
37
37
|
const error = urlParams.get('error');
|
|
38
38
|
const errorDescription = urlParams.get('error_description');
|
|
39
39
|
|
|
@@ -47,47 +47,47 @@ async function handleOAuthCallback() {
|
|
|
47
47
|
throw new Error('Missing authorization code');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
if (!
|
|
50
|
+
if (!state) {
|
|
51
51
|
throw new Error('Missing state parameter');
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// Parse state
|
|
55
|
-
let
|
|
55
|
+
let stateParsed;
|
|
56
56
|
try {
|
|
57
|
-
|
|
57
|
+
stateParsed = JSON.parse(decodeURIComponent(state));
|
|
58
58
|
} catch (e) {
|
|
59
59
|
console.error('Failed to parse state:', e);
|
|
60
60
|
throw new Error('Invalid state parameter');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
console.log('OAuth callback state:',
|
|
63
|
+
console.log('OAuth callback state:', stateParsed);
|
|
64
64
|
|
|
65
65
|
// Validate state
|
|
66
|
-
if (!
|
|
66
|
+
if (!stateParsed.provider) {
|
|
67
67
|
throw new Error('Missing provider in state');
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
if (!
|
|
70
|
+
if (!stateParsed.authenticationToken) {
|
|
71
71
|
throw new Error('Missing authentication token');
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
if (!
|
|
74
|
+
if (!stateParsed.serverUrl) {
|
|
75
75
|
throw new Error('Missing server URL');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// Update provider name
|
|
79
|
-
const providerName = capitalizeFirstLetter(
|
|
79
|
+
const providerName = capitalizeFirstLetter(stateParsed.provider);
|
|
80
80
|
$provider.textContent = providerName;
|
|
81
81
|
|
|
82
82
|
// Validate redirect URL
|
|
83
|
-
if (
|
|
83
|
+
if (stateParsed.redirectUrl && !isValidRedirectUrl(stateParsed.redirectUrl)) {
|
|
84
84
|
throw new Error('Invalid redirect URL');
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// Build tokenize payload
|
|
88
88
|
const payload = {
|
|
89
89
|
state: 'tokenize',
|
|
90
|
-
provider:
|
|
90
|
+
provider: stateParsed.provider,
|
|
91
91
|
code: code
|
|
92
92
|
};
|
|
93
93
|
|
|
@@ -101,13 +101,13 @@ async function handleOAuthCallback() {
|
|
|
101
101
|
console.log('Tokenize payload:', payload);
|
|
102
102
|
|
|
103
103
|
// Call server to complete OAuth flow
|
|
104
|
-
const response = await fetch(
|
|
104
|
+
const response = await fetch(stateParsed.serverUrl, {
|
|
105
105
|
method: 'POST',
|
|
106
106
|
timeout: 60000,
|
|
107
107
|
response: 'json',
|
|
108
108
|
tries: 2,
|
|
109
109
|
body: {
|
|
110
|
-
authenticationToken:
|
|
110
|
+
authenticationToken: stateParsed.authenticationToken,
|
|
111
111
|
command: 'user:oauth2',
|
|
112
112
|
payload: payload
|
|
113
113
|
}
|
|
@@ -120,7 +120,7 @@ async function handleOAuthCallback() {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// Show success
|
|
123
|
-
showSuccess(
|
|
123
|
+
showSuccess(stateParsed.redirectUrl || stateParsed.referrer || '/account#connections');
|
|
124
124
|
|
|
125
125
|
} catch (error) {
|
|
126
126
|
console.error('OAuth callback error:', error);
|
|
@@ -163,11 +163,11 @@ function showError(message) {
|
|
|
163
163
|
|
|
164
164
|
// Set return button href
|
|
165
165
|
const urlParams = new URLSearchParams(window.location.search);
|
|
166
|
-
const
|
|
166
|
+
const state = urlParams.get('state');
|
|
167
167
|
|
|
168
|
-
if (
|
|
168
|
+
if (state) {
|
|
169
169
|
try {
|
|
170
|
-
const state = JSON.parse(decodeURIComponent(
|
|
170
|
+
const state = JSON.parse(decodeURIComponent(state));
|
|
171
171
|
if (state.referrer) {
|
|
172
172
|
$returnButton.href = state.referrer;
|
|
173
173
|
}
|