ultimate-jekyll-manager 0.0.118 → 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 +409 -23
- package/README.md +171 -2
- package/TODO.md +10 -2
- package/_backup/form-manager.backup.js +1020 -0
- package/dist/assets/js/core/auth.js +5 -4
- package/dist/assets/js/core/cookieconsent.js +24 -17
- package/dist/assets/js/core/exit-popup.js +15 -12
- package/dist/assets/js/core/social-sharing.js +8 -4
- package/dist/assets/js/libs/auth/pages.js +78 -149
- package/dist/assets/js/libs/dev.js +192 -129
- package/dist/assets/js/libs/form-manager.js +643 -775
- package/dist/assets/js/pages/account/index.js +3 -2
- 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 +57 -78
- package/dist/assets/js/pages/account/sections/profile.js +37 -56
- package/dist/assets/js/pages/account/sections/security.js +102 -125
- package/dist/assets/js/pages/admin/notifications/new/index.js +73 -151
- package/dist/assets/js/pages/blog/index.js +33 -53
- package/dist/assets/js/pages/contact/index.js +112 -173
- package/dist/assets/js/pages/download/index.js +39 -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/pricing/index.js +5 -2
- package/dist/assets/js/pages/test/libraries/form-manager/index.js +194 -0
- package/dist/assets/themes/classy/css/components/_cards.scss +2 -2
- package/dist/defaults/_.env +6 -0
- package/dist/defaults/_.gitignore +7 -1
- package/dist/defaults/dist/_includes/core/body.html +5 -13
- package/dist/defaults/dist/_includes/core/foot.html +1 -0
- package/dist/defaults/dist/_includes/themes/classy/frontend/sections/nav.html +51 -36
- package/dist/defaults/dist/_layouts/blueprint/admin/notifications/new.html +13 -2
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/about.html +84 -42
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +26 -21
- 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/blog/index.html +72 -58
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/post.html +46 -29
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html +46 -53
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/download.html +111 -73
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/index.html +111 -56
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +127 -81
- package/dist/defaults/dist/pages/test/libraries/form-manager.html +181 -0
- package/dist/defaults/dist/pages/test/libraries/lazy-loading.html +1 -1
- package/dist/gulp/tasks/defaults.js +210 -1
- package/dist/gulp/tasks/serve.js +18 -0
- package/dist/lib/logger.js +1 -1
- package/firebase-debug.log +770 -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,50 +18,98 @@ 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
|
+
}
|
|
41
83
|
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
}
|
|
96
|
+
|
|
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
|
-
trackChatClick();
|
|
59
|
-
|
|
60
113
|
// Open chat window
|
|
61
114
|
try {
|
|
62
115
|
chatsy.open();
|
|
@@ -64,176 +117,62 @@ function setupFormScrolling() {
|
|
|
64
117
|
webManager.sentry().captureException(new Error('Error opening chat', { cause: error }));
|
|
65
118
|
webManager.utilities().showNotification('Chat is currently unavailable. Please try again later.', 'danger');
|
|
66
119
|
}
|
|
67
|
-
|
|
68
|
-
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
69
122
|
|
|
70
|
-
|
|
123
|
+
if (href === '#form') {
|
|
71
124
|
const $formSection = document.getElementById('form');
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Smooth scroll to the form section
|
|
75
|
-
$formSection.scrollIntoView({
|
|
76
|
-
behavior: 'smooth',
|
|
77
|
-
block: 'start'
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Focus the first input immediately and after scroll completes
|
|
81
|
-
const focusFirstInput = () => {
|
|
82
|
-
// Find the first input field in the form
|
|
83
|
-
const $firstInput = document.querySelector('#contact-form input');
|
|
84
|
-
if (!$firstInput) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
$firstInput.focus();
|
|
89
|
-
// Don't select text on mobile devices
|
|
90
|
-
if (!('ontouchstart' in window) && $firstInput.select) {
|
|
91
|
-
$firstInput.select();
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// Try focusing immediately
|
|
96
|
-
focusFirstInput();
|
|
97
|
-
|
|
98
|
-
// Also try after scroll animation completes
|
|
99
|
-
setTimeout(focusFirstInput, 800);
|
|
125
|
+
if (!$formSection) {
|
|
126
|
+
return;
|
|
100
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);
|
|
101
152
|
}
|
|
102
153
|
});
|
|
103
154
|
});
|
|
104
155
|
}
|
|
105
156
|
|
|
106
|
-
// Handle form submission event from FormManager
|
|
107
|
-
async function handleFormSubmit(event) {
|
|
108
|
-
// Prevent default FormManager submission
|
|
109
|
-
event.preventDefault();
|
|
110
|
-
|
|
111
|
-
const formData = event.detail.data;
|
|
112
|
-
const slapformId = webManager.config.brand.contact['slapform-form-id'];
|
|
113
|
-
|
|
114
|
-
console.log('Contact form submission:', formData);
|
|
115
|
-
|
|
116
|
-
// Check honeypot field (anti-spam)
|
|
117
|
-
if (formData.url_check) {
|
|
118
|
-
console.warn('Honeypot field filled - potential spam');
|
|
119
|
-
trackContactSpam();
|
|
120
|
-
formManager.setFormState('ready');
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Check if slapformId is missing
|
|
125
|
-
if (!slapformId) {
|
|
126
|
-
webManager.sentry().captureException(new Error('Contact form is not configured - missing slapform ID'));
|
|
127
|
-
formManager.showError('Contact form is not configured properly. Please try again later.');
|
|
128
|
-
formManager.setFormState('ready');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
trackContactFormSubmit(formData.subject || 'general');
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
// Prepare data for API
|
|
136
|
-
const requestData = {
|
|
137
|
-
first_name: formData.first_name,
|
|
138
|
-
last_name: formData.last_name,
|
|
139
|
-
email: formData.email,
|
|
140
|
-
company: formData.company || '',
|
|
141
|
-
subject: formData.subject,
|
|
142
|
-
message: formData.message,
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
// Get API endpoint from site config or use default
|
|
146
|
-
const apiEndpoint = `https://api.slapform.com/${slapformId}`;
|
|
147
|
-
|
|
148
|
-
// Send request using wonderful-fetch
|
|
149
|
-
await fetch(apiEndpoint, {
|
|
150
|
-
method: 'POST',
|
|
151
|
-
body: requestData,
|
|
152
|
-
response: 'json',
|
|
153
|
-
timeout: 30000 // 30 second timeout
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// Handle successful response
|
|
157
|
-
formManager.showSuccess('Thank you for your message! We\'ll get back to you within 24 hours.');
|
|
158
|
-
formManager.setFormState('submitted');
|
|
159
|
-
} catch (error) {
|
|
160
|
-
// Only capture technical errors to Sentry (network, timeout, API errors)
|
|
161
|
-
if (error.message?.includes('network') || error.message?.includes('timeout') || !error.message?.includes('Failed')) {
|
|
162
|
-
webManager.sentry().captureException(new Error('Contact form submission error', { cause: error }));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Show user-friendly error message
|
|
166
|
-
let errorMessage = 'An error occurred while sending your message. ';
|
|
167
|
-
|
|
168
|
-
if (error.message?.includes('network')) {
|
|
169
|
-
errorMessage += 'Please check your internet connection and try again.';
|
|
170
|
-
} else if (error.message?.includes('timeout')) {
|
|
171
|
-
errorMessage += 'The request timed out. Please try again.';
|
|
172
|
-
} else if (error.message?.includes('Failed')) {
|
|
173
|
-
errorMessage = error.message;
|
|
174
|
-
} else {
|
|
175
|
-
errorMessage += 'Please try again or contact us directly.';
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
formManager.showError(errorMessage);
|
|
179
|
-
formManager.setFormState('ready');
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Handle field changes
|
|
184
|
-
function handleFieldChange(event) {
|
|
185
|
-
const { field, fieldName } = event.detail;
|
|
186
|
-
|
|
187
|
-
// Clear field-specific error message if it exists
|
|
188
|
-
if (field.dataset.invalidField) {
|
|
189
|
-
const $feedback = field.parentElement.querySelector('.invalid-feedback');
|
|
190
|
-
if ($feedback) {
|
|
191
|
-
$feedback.textContent = '';
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
157
|
// Tracking functions
|
|
197
|
-
function trackChatClick() {
|
|
198
|
-
// gtag('event', 'contact_method_click', {
|
|
199
|
-
// method: 'chat'
|
|
200
|
-
// });
|
|
201
|
-
// fbq('track', 'Contact', {
|
|
202
|
-
// content_name: 'Chat'
|
|
203
|
-
// });
|
|
204
|
-
// ttq.track('ClickButton', {
|
|
205
|
-
// content_name: 'Chat'
|
|
206
|
-
// });
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function trackFormClick() {
|
|
210
|
-
// gtag('event', 'contact_method_click', {
|
|
211
|
-
// method: 'form'
|
|
212
|
-
// });
|
|
213
|
-
// fbq('track', 'Contact', {
|
|
214
|
-
// content_name: 'Form'
|
|
215
|
-
// });
|
|
216
|
-
// ttq.track('ClickButton', {
|
|
217
|
-
// content_name: 'Contact Form'
|
|
218
|
-
// });
|
|
219
|
-
}
|
|
220
|
-
|
|
221
158
|
function trackContactSpam() {
|
|
222
159
|
gtag('event', 'contact_form_spam', {
|
|
223
|
-
content_type: 'honeypot'
|
|
160
|
+
content_type: 'honeypot',
|
|
224
161
|
});
|
|
225
162
|
}
|
|
226
163
|
|
|
227
164
|
function trackContactFormSubmit(subject) {
|
|
228
|
-
gtag('event', '
|
|
229
|
-
|
|
165
|
+
gtag('event', 'generate_lead', {
|
|
166
|
+
lead_source: 'contact_form',
|
|
167
|
+
subject: subject,
|
|
230
168
|
});
|
|
231
169
|
fbq('track', 'Lead', {
|
|
232
170
|
content_name: 'Contact Form',
|
|
233
|
-
content_category: subject
|
|
171
|
+
content_category: subject,
|
|
234
172
|
});
|
|
235
|
-
ttq.track('
|
|
173
|
+
ttq.track('Contact', {
|
|
174
|
+
content_id: 'contact-form',
|
|
175
|
+
content_type: 'product',
|
|
236
176
|
content_name: 'Contact Form',
|
|
237
|
-
content_type: subject
|
|
238
177
|
});
|
|
239
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,54 +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', {
|
|
152
|
+
content_id: `download-${platform}`,
|
|
153
|
+
content_type: 'product',
|
|
177
154
|
content_name: downloadName,
|
|
178
|
-
content_type: platform
|
|
179
155
|
});
|
|
180
156
|
}
|
|
181
157
|
|
|
@@ -187,7 +163,9 @@ function setupCopyButtons() {
|
|
|
187
163
|
$button.addEventListener('click', async function() {
|
|
188
164
|
const $input = this.closest('.input-group').querySelector('input');
|
|
189
165
|
|
|
190
|
-
if (!$input || !$input.value)
|
|
166
|
+
if (!$input || !$input.value) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
191
169
|
|
|
192
170
|
try {
|
|
193
171
|
await webManager.utilities().clipboardCopy($input);
|
|
@@ -220,57 +198,32 @@ function setupMobileEmailForms() {
|
|
|
220
198
|
const formId = `#mobile-email-form-${platform}`;
|
|
221
199
|
|
|
222
200
|
const formManager = new FormManager(formId, {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
allowMultipleSubmissions: false,
|
|
227
|
-
submitButtonLoadingText: 'Sending...',
|
|
228
|
-
submitButtonSuccessText: 'Sent!',
|
|
201
|
+
allowResubmit: false,
|
|
202
|
+
submittingText: 'Sending...',
|
|
203
|
+
submittedText: 'Email Sent!',
|
|
229
204
|
});
|
|
230
205
|
|
|
231
|
-
formManager.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const apiEndpoint = webManager.getApiUrl() + '/backend-manager';
|
|
253
|
-
|
|
254
|
-
// Send request using wonderful-fetch
|
|
255
|
-
await fetch(apiEndpoint, {
|
|
256
|
-
method: 'POST',
|
|
257
|
-
body: {
|
|
258
|
-
command: 'general:send-email',
|
|
259
|
-
payload: {
|
|
260
|
-
id: 'general:download-app-link',
|
|
261
|
-
email: email,
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
response: 'json',
|
|
265
|
-
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.');
|
|
266
227
|
});
|
|
267
|
-
|
|
268
|
-
// Handle successful response
|
|
269
|
-
formManager.showSuccess('Success! Please check your email for the download link.');
|
|
270
|
-
formManager.setFormState('submitted');
|
|
271
|
-
} catch (error) {
|
|
272
|
-
webManager.sentry().captureException(new Error('Mobile email form submission error', { cause: error }));
|
|
273
|
-
formManager.showError(error.message || 'Failed to send email');
|
|
274
|
-
formManager.setFormState('ready');
|
|
275
|
-
}
|
|
228
|
+
});
|
|
276
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
|
}
|