ultimate-jekyll-manager 0.0.269 → 0.0.271
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/CHANGELOG.md +13 -0
- package/CLAUDE.md +36 -0
- package/README.md +10 -0
- package/dist/assets/css/pages/account/index.scss +23 -6
- package/dist/assets/js/libs/auth.js +3 -0
- package/dist/assets/js/pages/account/index.js +35 -2
- package/dist/assets/js/pages/account/sections/data-request.js +253 -0
- package/dist/assets/js/pages/account/sections/delete.js +12 -17
- package/dist/assets/js/pages/payment/checkout/index.js +12 -0
- package/dist/build.js +6 -0
- package/dist/commands/clean.js +10 -0
- package/dist/commands/setup.js +13 -0
- package/dist/defaults/dist/_includes/core/body.html +2 -4
- package/dist/defaults/dist/_layouts/blueprint/legal/privacy.md +22 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +227 -50
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/reset.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signin.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/auth/signup.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/index.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/contact.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/download.html +1 -1
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/index.html +2 -2
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/payment/checkout.html +5 -3
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/status.html +1 -1
- package/dist/gulp/tasks/jekyll.js +9 -0
- package/dist/gulp/tasks/sass.js +10 -0
- package/dist/gulp/tasks/webpack.js +10 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
14
14
|
- `Fixed` for any bug fixes.
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
|
+
---
|
|
18
|
+
## [Unreleased]
|
|
19
|
+
### Added
|
|
20
|
+
- Quick boot mode (`UJ_QUICK=true`) for faster dev server startup (~5s vs ~20s) by skipping clean, slow setup operations, and deferring webpack/sass compilation until after Jekyll's first build
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Add `onsubmit="return false"` to all JS-managed forms as a safety net against native submission before FormManager loads
|
|
24
|
+
- Checkout payment method buttons start hidden and are revealed via `data-wm-bind` when payment methods load
|
|
25
|
+
- Remove development-only guard from click prevention logging in body.html
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- Add dev-only artificial pre-delay support to checkout page for testing form protection timing
|
|
29
|
+
|
|
17
30
|
---
|
|
18
31
|
## [1.0.0] - 2024-06-19
|
|
19
32
|
### Added
|
package/CLAUDE.md
CHANGED
|
@@ -595,6 +595,42 @@ Selectively protect non-standard elements that trigger important actions:
|
|
|
595
595
|
- **Click Prevention:** `src/defaults/dist/_includes/core/body.html` - Inline script
|
|
596
596
|
- **State Removal:** `src/assets/js/core/complete.js` - Removes loading state
|
|
597
597
|
|
|
598
|
+
### Form Protection Standards
|
|
599
|
+
|
|
600
|
+
All JS-managed forms use a layered protection strategy to prevent native form submission before JavaScript takes control:
|
|
601
|
+
|
|
602
|
+
#### Layer 1: `onsubmit="return false"` on ALL JS-managed forms
|
|
603
|
+
|
|
604
|
+
Every `<form>` that will be managed by FormManager MUST include `onsubmit="return false"`:
|
|
605
|
+
|
|
606
|
+
```html
|
|
607
|
+
<form id="my-form" onsubmit="return false">
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
This is a zero-cost safety net that prevents native form submission if a user clicks submit before FormManager attaches its `e.preventDefault()` handler. FormManager's own submit handling overrides this — there is no conflict.
|
|
611
|
+
|
|
612
|
+
**Exception:** Traditional forms with an `action` attribute that intentionally navigate (e.g., search forms, external form submissions) should NOT include this.
|
|
613
|
+
|
|
614
|
+
#### Layer 2: Button initial state based on use case
|
|
615
|
+
|
|
616
|
+
| Use Case | Initial State | Mechanism |
|
|
617
|
+
|----------|---------------|-----------|
|
|
618
|
+
| Buttons dependent on async data (checkout payment methods) | `hidden` | `data-wm-bind="@show ..."` reveals when data loads |
|
|
619
|
+
| Buttons on auth/sensitive forms | `disabled` | FormManager's `ready()` removes `disabled` |
|
|
620
|
+
| Buttons on simple forms (contact, newsletter) | Default (visible) | FormManager's `autoReady: true` enables quickly |
|
|
621
|
+
|
|
622
|
+
#### Layer 3: FormManager `autoReady` configuration
|
|
623
|
+
|
|
624
|
+
| Scenario | `autoReady` | `ready()` call |
|
|
625
|
+
|----------|-------------|----------------|
|
|
626
|
+
| No async work before form init | `true` (default) | Automatic on DOM ready |
|
|
627
|
+
| Async work before form init (API calls, redirects) | `false` | Explicit call after async completes |
|
|
628
|
+
|
|
629
|
+
**Reference implementations:**
|
|
630
|
+
- Simple form: `src/assets/js/pages/contact/index.js`
|
|
631
|
+
- Auth form: `src/assets/js/libs/auth.js`
|
|
632
|
+
- Async data form: `src/assets/js/pages/payment/checkout/index.js`
|
|
633
|
+
|
|
598
634
|
## Lazy Loading System
|
|
599
635
|
|
|
600
636
|
Ultimate Jekyll uses a custom lazy loading system powered by web-manager.
|
package/README.md
CHANGED
|
@@ -314,6 +314,16 @@ Add the `.btn-action` class to protect custom elements that trigger important ac
|
|
|
314
314
|
**Use `.btn-action` for:** API calls, form submissions, data modifications, payments, destructive actions
|
|
315
315
|
**Don't use for:** Navigation, UI toggles, modals, accordions, harmless interactions
|
|
316
316
|
|
|
317
|
+
#### Form Protection Standards
|
|
318
|
+
|
|
319
|
+
All JS-managed forms use a layered protection strategy:
|
|
320
|
+
|
|
321
|
+
1. **`onsubmit="return false"`** on every `<form>` managed by FormManager — prevents native submission before JS loads
|
|
322
|
+
2. **Button initial state** — buttons dependent on async data start `hidden` (revealed by `data-wm-bind`); auth buttons start `disabled` (enabled by FormManager's `ready()`)
|
|
323
|
+
3. **FormManager `autoReady`** — use `autoReady: false` when async work happens before form init, call `ready()` explicitly after
|
|
324
|
+
|
|
325
|
+
**Exception:** Traditional forms with an `action` attribute that intentionally navigate should NOT include `onsubmit="return false"`.
|
|
326
|
+
|
|
317
327
|
### Ad Units (Verts)
|
|
318
328
|
|
|
319
329
|
UJ provides ad unit includes that display Google AdSense ads with automatic fallback to in-house promo-server ads when AdSense is blocked or unfilled.
|
|
@@ -64,21 +64,38 @@
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// Shared styles for data-request and delete sections
|
|
68
|
+
#data-request-section,
|
|
68
69
|
#delete-section {
|
|
69
|
-
.card
|
|
70
|
+
.card-form-border {
|
|
70
71
|
border-width: 2px;
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
.form-check-label {
|
|
75
|
+
user-select: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.accordion-trigger {
|
|
79
|
+
background: none;
|
|
80
|
+
border: none;
|
|
81
|
+
padding: 0;
|
|
82
|
+
font: inherit;
|
|
83
|
+
color: inherit;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
|
|
86
|
+
&:focus {
|
|
87
|
+
outline: none;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Delete account section styles
|
|
93
|
+
#delete-section {
|
|
73
94
|
#delete-account-btn {
|
|
74
95
|
&:disabled {
|
|
75
96
|
opacity: 0.5;
|
|
76
97
|
cursor: not-allowed;
|
|
77
98
|
}
|
|
78
99
|
}
|
|
79
|
-
|
|
80
|
-
.form-check-label {
|
|
81
|
-
user-select: none;
|
|
82
|
-
}
|
|
83
100
|
}
|
|
84
101
|
|
|
@@ -114,6 +114,7 @@ export default function (Manager) {
|
|
|
114
114
|
formManager = new FormManager('#auth-form', {
|
|
115
115
|
autoReady: false, // We'll call ready() after checking redirect result
|
|
116
116
|
allowResubmit: false,
|
|
117
|
+
warnOnUnsavedChanges: false,
|
|
117
118
|
submittingText: 'Signing in...',
|
|
118
119
|
submittedText: 'Signed In!',
|
|
119
120
|
});
|
|
@@ -128,6 +129,7 @@ export default function (Manager) {
|
|
|
128
129
|
formManager = new FormManager('#auth-form', {
|
|
129
130
|
autoReady: false, // We'll call ready() after checking redirect result
|
|
130
131
|
allowResubmit: false,
|
|
132
|
+
warnOnUnsavedChanges: false,
|
|
131
133
|
submittingText: 'Creating account...',
|
|
132
134
|
submittedText: 'Account Created!',
|
|
133
135
|
});
|
|
@@ -142,6 +144,7 @@ export default function (Manager) {
|
|
|
142
144
|
formManager = new FormManager('#auth-form', {
|
|
143
145
|
autoReady: false, // We'll call ready() after checking redirect result
|
|
144
146
|
allowResubmit: false,
|
|
147
|
+
warnOnUnsavedChanges: false,
|
|
145
148
|
submittingText: 'Sending...',
|
|
146
149
|
submittedText: 'Email Sent!',
|
|
147
150
|
});
|
|
@@ -8,6 +8,7 @@ import * as teamSection from './sections/team.js';
|
|
|
8
8
|
import * as referralsSection from './sections/referrals.js';
|
|
9
9
|
import * as apiKeysSection from './sections/api-keys.js';
|
|
10
10
|
import * as deleteSection from './sections/delete.js';
|
|
11
|
+
import * as dataRequestSection from './sections/data-request.js';
|
|
11
12
|
import * as connectionsSection from './sections/connections.js';
|
|
12
13
|
let webManager = null;
|
|
13
14
|
|
|
@@ -47,6 +48,7 @@ const sectionModules = {
|
|
|
47
48
|
referrals: referralsSection,
|
|
48
49
|
'api-keys': apiKeysSection,
|
|
49
50
|
delete: deleteSection,
|
|
51
|
+
'data-request': dataRequestSection,
|
|
50
52
|
connections: connectionsSection
|
|
51
53
|
};
|
|
52
54
|
|
|
@@ -62,10 +64,13 @@ async function initializeAccount() {
|
|
|
62
64
|
// Setup navigation
|
|
63
65
|
setupNavigation();
|
|
64
66
|
|
|
65
|
-
// Check if delete hash is present on initial load
|
|
67
|
+
// Check if delete/data-request hash is present on initial load
|
|
66
68
|
if (window.location.hash === '#delete') {
|
|
67
69
|
showDeleteOption();
|
|
68
70
|
}
|
|
71
|
+
if (window.location.hash === '#data-request') {
|
|
72
|
+
showDataRequestOption();
|
|
73
|
+
}
|
|
69
74
|
|
|
70
75
|
// Initialize all section modules
|
|
71
76
|
Object.values(sectionModules).forEach(module => {
|
|
@@ -180,6 +185,10 @@ function loadAllSectionData(authState) {
|
|
|
180
185
|
sectionModules.delete.loadData(account);
|
|
181
186
|
}
|
|
182
187
|
|
|
188
|
+
if (sectionModules['data-request'] && sectionModules['data-request'].loadData) {
|
|
189
|
+
sectionModules['data-request'].loadData(account);
|
|
190
|
+
}
|
|
191
|
+
|
|
183
192
|
if (sectionModules.connections.loadData) {
|
|
184
193
|
sectionModules.connections.loadData(account, appData);
|
|
185
194
|
}
|
|
@@ -219,10 +228,13 @@ function setupNavigation() {
|
|
|
219
228
|
function handleHashChange() {
|
|
220
229
|
const hash = window.location.hash.slice(1);
|
|
221
230
|
|
|
222
|
-
// Show
|
|
231
|
+
// Show hidden nav items based on hash
|
|
223
232
|
if (hash === 'delete') {
|
|
224
233
|
showDeleteOption();
|
|
225
234
|
}
|
|
235
|
+
if (hash === 'data-request') {
|
|
236
|
+
showDataRequestOption();
|
|
237
|
+
}
|
|
226
238
|
|
|
227
239
|
if (hash) {
|
|
228
240
|
// Check if the section exists
|
|
@@ -264,6 +276,27 @@ function showDeleteOption() {
|
|
|
264
276
|
}
|
|
265
277
|
}
|
|
266
278
|
|
|
279
|
+
// Show data request option in navigation
|
|
280
|
+
function showDataRequestOption() {
|
|
281
|
+
// Show desktop nav item
|
|
282
|
+
const $dataRequestNavItem = document.getElementById('data-request-nav-item');
|
|
283
|
+
if ($dataRequestNavItem) {
|
|
284
|
+
$dataRequestNavItem.classList.remove('d-none');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Add mobile dropdown option if not exists
|
|
288
|
+
const $mobileNavSelect = document.getElementById('mobile-nav-select');
|
|
289
|
+
if ($mobileNavSelect) {
|
|
290
|
+
const dataRequestOption = $mobileNavSelect.querySelector('option[value="data-request"]');
|
|
291
|
+
if (!dataRequestOption) {
|
|
292
|
+
const option = document.createElement('option');
|
|
293
|
+
option.value = 'data-request';
|
|
294
|
+
option.textContent = 'Data Request';
|
|
295
|
+
$mobileNavSelect.appendChild(option);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
267
300
|
// Hide all sections except loading
|
|
268
301
|
function hideAllSectionsExceptLoading() {
|
|
269
302
|
$sections.forEach(section => {
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Request Section JavaScript
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Libraries
|
|
6
|
+
import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
7
|
+
import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
|
|
8
|
+
|
|
9
|
+
let webManager = null;
|
|
10
|
+
let formManager = null;
|
|
11
|
+
let downloadFormManager = null;
|
|
12
|
+
let cancelFormManager = null;
|
|
13
|
+
|
|
14
|
+
// Initialize data-request section
|
|
15
|
+
export async function init(wm) {
|
|
16
|
+
webManager = wm;
|
|
17
|
+
setupDataRequestForm();
|
|
18
|
+
setupDownloadButton();
|
|
19
|
+
setupCancelButton();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Setup data request form
|
|
23
|
+
function setupDataRequestForm() {
|
|
24
|
+
const $form = document.getElementById('data-request-form');
|
|
25
|
+
const $checkbox1 = document.getElementById('data-request-confirm-checkbox');
|
|
26
|
+
const $checkbox2 = document.getElementById('data-request-deletion-checkbox');
|
|
27
|
+
const $submitBtn = document.getElementById('data-request-submit-btn');
|
|
28
|
+
|
|
29
|
+
if (!$form || !$checkbox1 || !$checkbox2 || !$submitBtn) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
formManager = new FormManager('#data-request-form', {
|
|
34
|
+
allowResubmit: false,
|
|
35
|
+
submittingText: 'Submitting request...',
|
|
36
|
+
submittedText: 'Request Submitted',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Enable/disable submit button based on both checkboxes
|
|
40
|
+
function updateSubmitState() {
|
|
41
|
+
$submitBtn.disabled = !($checkbox1.checked && $checkbox2.checked);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
$checkbox1.addEventListener('change', updateSubmitState);
|
|
45
|
+
$checkbox2.addEventListener('change', updateSubmitState);
|
|
46
|
+
|
|
47
|
+
formManager.on('submit', async ({ data }) => {
|
|
48
|
+
if (!$checkbox1.checked || !$checkbox2.checked) {
|
|
49
|
+
throw new Error('Please confirm all acknowledgments before submitting.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
trackDataRequest('submit');
|
|
53
|
+
|
|
54
|
+
const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user/data-request`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
timeout: 30000,
|
|
57
|
+
response: 'json',
|
|
58
|
+
tries: 2,
|
|
59
|
+
body: {
|
|
60
|
+
confirmed: true,
|
|
61
|
+
reason: data.reason || '',
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (response.error) {
|
|
66
|
+
throw new Error(response.message || 'Failed to submit data request. Please try again later.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
formManager.showSuccess('Your data request has been submitted. Please check back in up to 14 business days.');
|
|
70
|
+
|
|
71
|
+
showRequestStatus(response.request);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Setup download form
|
|
76
|
+
function setupDownloadButton() {
|
|
77
|
+
const $form = document.getElementById('data-request-download-form');
|
|
78
|
+
|
|
79
|
+
if (!$form) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
downloadFormManager = new FormManager('#data-request-download-form', {
|
|
84
|
+
allowResubmit: false,
|
|
85
|
+
submittingText: 'Downloading...',
|
|
86
|
+
submittedText: 'Downloaded!',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
downloadFormManager.on('submit', async () => {
|
|
90
|
+
const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user/data-request?action=download`, {
|
|
91
|
+
method: 'GET',
|
|
92
|
+
timeout: 60000,
|
|
93
|
+
response: 'json',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (response.error || !response.data) {
|
|
97
|
+
throw new Error(response.message || 'Failed to download data.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
trackDataRequest('download');
|
|
101
|
+
|
|
102
|
+
// Trigger file download
|
|
103
|
+
const blob = new Blob([JSON.stringify(response.data, null, 2)], { type: 'application/json' });
|
|
104
|
+
const url = URL.createObjectURL(blob);
|
|
105
|
+
const $a = document.createElement('a');
|
|
106
|
+
$a.href = url;
|
|
107
|
+
$a.download = `my-data-${new Date().toISOString().split('T')[0]}.json`;
|
|
108
|
+
document.body.appendChild($a);
|
|
109
|
+
$a.click();
|
|
110
|
+
document.body.removeChild($a);
|
|
111
|
+
URL.revokeObjectURL(url);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Setup cancel form
|
|
116
|
+
function setupCancelButton() {
|
|
117
|
+
const $form = document.getElementById('data-request-cancel-form');
|
|
118
|
+
|
|
119
|
+
if (!$form) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
cancelFormManager = new FormManager('#data-request-cancel-form', {
|
|
124
|
+
allowResubmit: true,
|
|
125
|
+
submittingText: 'Withdrawing...',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
cancelFormManager.on('submit', async () => {
|
|
129
|
+
const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user/data-request`, {
|
|
130
|
+
method: 'DELETE',
|
|
131
|
+
timeout: 30000,
|
|
132
|
+
response: 'json',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (response.error) {
|
|
136
|
+
throw new Error(response.message || 'Failed to withdraw request.');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
trackDataRequest('cancel');
|
|
140
|
+
|
|
141
|
+
showRequestForm();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Load data (called when auth state resolves)
|
|
146
|
+
export async function loadData() {
|
|
147
|
+
// Status is checked lazily in onShow
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Called when section is shown
|
|
151
|
+
export async function onShow() {
|
|
152
|
+
await checkRequestStatus();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check request status from backend
|
|
156
|
+
async function checkRequestStatus() {
|
|
157
|
+
try {
|
|
158
|
+
const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user/data-request`, {
|
|
159
|
+
method: 'GET',
|
|
160
|
+
timeout: 30000,
|
|
161
|
+
response: 'json',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (response.request) {
|
|
165
|
+
showRequestStatus(response.request);
|
|
166
|
+
} else {
|
|
167
|
+
showRequestForm();
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// No active request or error — show form
|
|
171
|
+
showRequestForm();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Show request status UI
|
|
176
|
+
function showRequestStatus(request) {
|
|
177
|
+
const $status = document.getElementById('data-request-status');
|
|
178
|
+
const $statusTitle = document.getElementById('data-request-status-title');
|
|
179
|
+
const $statusMessage = document.getElementById('data-request-status-message');
|
|
180
|
+
const $download = document.getElementById('data-request-download');
|
|
181
|
+
const $cancel = document.getElementById('data-request-cancel');
|
|
182
|
+
const $formContainer = document.getElementById('data-request-form-container');
|
|
183
|
+
const $formTrigger = document.getElementById('data-request-form-trigger');
|
|
184
|
+
|
|
185
|
+
if (!$status) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (request.status === 'expired') {
|
|
190
|
+
showRequestForm();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
$status.classList.remove('d-none');
|
|
195
|
+
$formContainer.classList.add('d-none');
|
|
196
|
+
if ($formTrigger) {
|
|
197
|
+
$formTrigger.classList.add('d-none');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const createdDate = new Date(request.createdAt).toLocaleDateString();
|
|
201
|
+
|
|
202
|
+
if (request.status === 'complete') {
|
|
203
|
+
$statusTitle.textContent = 'Your data is ready';
|
|
204
|
+
$statusMessage.textContent = `Your data request from ${createdDate} has been processed. Click below to download your data package. This download will expire 30 days after your data became available.`;
|
|
205
|
+
$download.classList.remove('d-none');
|
|
206
|
+
if (downloadFormManager) {
|
|
207
|
+
downloadFormManager.reset();
|
|
208
|
+
}
|
|
209
|
+
$cancel.classList.add('d-none');
|
|
210
|
+
|
|
211
|
+
trackDataRequest('download_available');
|
|
212
|
+
} else {
|
|
213
|
+
$statusTitle.textContent = 'Request pending';
|
|
214
|
+
$statusMessage.textContent = `Your data request was submitted on ${createdDate}. Processing may take up to 14 business days. Please check back later.`;
|
|
215
|
+
$download.classList.add('d-none');
|
|
216
|
+
$cancel.classList.remove('d-none');
|
|
217
|
+
if (cancelFormManager) {
|
|
218
|
+
cancelFormManager.reset();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Show request form UI
|
|
224
|
+
function showRequestForm() {
|
|
225
|
+
const $status = document.getElementById('data-request-status');
|
|
226
|
+
const $formContainer = document.getElementById('data-request-form-container');
|
|
227
|
+
const $formTrigger = document.getElementById('data-request-form-trigger');
|
|
228
|
+
|
|
229
|
+
if (!$status || !$formContainer) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
$status.classList.add('d-none');
|
|
234
|
+
$formContainer.classList.remove('d-none');
|
|
235
|
+
if ($formTrigger) {
|
|
236
|
+
$formTrigger.classList.remove('d-none');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Tracking
|
|
241
|
+
function trackDataRequest(action) {
|
|
242
|
+
gtag('event', 'data_request', {
|
|
243
|
+
action: action,
|
|
244
|
+
});
|
|
245
|
+
fbq('trackCustom', 'DataRequest', {
|
|
246
|
+
action: action,
|
|
247
|
+
});
|
|
248
|
+
ttq.track('ViewContent', {
|
|
249
|
+
content_id: `data-request-${action}`,
|
|
250
|
+
content_type: 'product',
|
|
251
|
+
content_name: `Data Request ${action}`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
@@ -18,11 +18,11 @@ export async function init(wm) {
|
|
|
18
18
|
// Setup delete account form
|
|
19
19
|
function setupDeleteAccountForm() {
|
|
20
20
|
const $form = document.getElementById('delete-account-form');
|
|
21
|
-
const $
|
|
21
|
+
const $checkbox1 = document.getElementById('delete-confirm-checkbox');
|
|
22
|
+
const $checkbox2 = document.getElementById('delete-data-request-checkbox');
|
|
22
23
|
const $deleteBtn = document.getElementById('delete-account-btn');
|
|
23
|
-
const $cancelBtn = document.getElementById('cancel-delete-btn');
|
|
24
24
|
|
|
25
|
-
if (!$form || !$
|
|
25
|
+
if (!$form || !$checkbox1 || !$checkbox2 || !$deleteBtn) {
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -32,23 +32,18 @@ function setupDeleteAccountForm() {
|
|
|
32
32
|
submittedText: 'Account Deleted!',
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
// Enable/disable delete button based on
|
|
36
|
-
|
|
37
|
-
$deleteBtn.disabled = !
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Handle cancel button
|
|
41
|
-
if ($cancelBtn) {
|
|
42
|
-
$cancelBtn.addEventListener('click', () => {
|
|
43
|
-
// Navigate back to profile
|
|
44
|
-
window.location.hash = 'profile';
|
|
45
|
-
});
|
|
35
|
+
// Enable/disable delete button based on both checkboxes
|
|
36
|
+
function updateDeleteState() {
|
|
37
|
+
$deleteBtn.disabled = !($checkbox1.checked && $checkbox2.checked);
|
|
46
38
|
}
|
|
47
39
|
|
|
40
|
+
$checkbox1.addEventListener('change', updateDeleteState);
|
|
41
|
+
$checkbox2.addEventListener('change', updateDeleteState);
|
|
42
|
+
|
|
48
43
|
formManager.on('submit', async ({ data }) => {
|
|
49
|
-
// Check if
|
|
50
|
-
if (!$
|
|
51
|
-
throw new Error('Please confirm
|
|
44
|
+
// Check if both checkboxes are checked
|
|
45
|
+
if (!$checkbox1.checked || !$checkbox2.checked) {
|
|
46
|
+
throw new Error('Please confirm all acknowledgments before proceeding.');
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
// 1ms wait for dialog to appear properly
|
|
@@ -73,6 +73,18 @@ async function initializeCheckout() {
|
|
|
73
73
|
initializeRecaptcha(webManager.config?.recaptcha?.['site-key'], webManager),
|
|
74
74
|
]);
|
|
75
75
|
|
|
76
|
+
/* @dev-only:start */
|
|
77
|
+
{
|
|
78
|
+
const _dev_preDelay = urlParams.get('_dev_preDelay');
|
|
79
|
+
if (_dev_preDelay) {
|
|
80
|
+
const delayMs = parseInt(_dev_preDelay, 10) || 5000;
|
|
81
|
+
console.warn(`[Checkout Dev] Artificial pre-delay: ${delayMs}ms`);
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
83
|
+
console.warn('[Checkout Dev] Pre-delay complete');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/* @dev-only:end */
|
|
87
|
+
|
|
76
88
|
// App config is required
|
|
77
89
|
if (appConfigResult.status === 'rejected') {
|
|
78
90
|
const reason = appConfigResult.reason?.message || appConfigResult.reason || 'Unknown error';
|
package/dist/build.js
CHANGED
|
@@ -98,6 +98,12 @@ Manager.isBuildMode = function () {
|
|
|
98
98
|
}
|
|
99
99
|
Manager.prototype.isBuildMode = Manager.isBuildMode;
|
|
100
100
|
|
|
101
|
+
// isQuickMode
|
|
102
|
+
Manager.isQuickMode = function () {
|
|
103
|
+
return process.env.UJ_QUICK === 'true';
|
|
104
|
+
}
|
|
105
|
+
Manager.prototype.isQuickMode = Manager.isQuickMode;
|
|
106
|
+
|
|
101
107
|
// actLikeProduction - determines if we should act like production mode
|
|
102
108
|
Manager.actLikeProduction = function () {
|
|
103
109
|
return Boolean(Manager.isBuildMode() || process.env.UJ_AUDIT_FORCE === 'true');
|
package/dist/commands/clean.js
CHANGED
|
@@ -18,6 +18,16 @@ const dirs = [
|
|
|
18
18
|
module.exports = async function (options) {
|
|
19
19
|
options = options || {};
|
|
20
20
|
|
|
21
|
+
// Quick mode: skip clean to reuse existing build artifacts
|
|
22
|
+
if (Manager.isQuickMode()) {
|
|
23
|
+
if (!jetpack.exists('dist') || !jetpack.exists('_site')) {
|
|
24
|
+
logger.log('Quick mode: No existing build, running full clean');
|
|
25
|
+
} else {
|
|
26
|
+
logger.log('Quick mode: Skipping clean');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
// Build list of directories to clean
|
|
22
32
|
const dirsToClean = [...dirs];
|
|
23
33
|
|
package/dist/commands/setup.js
CHANGED
|
@@ -41,6 +41,19 @@ module.exports = async function (options) {
|
|
|
41
41
|
options.deduplicatePosts = options.deduplicatePosts !== 'false';
|
|
42
42
|
options.migrate = options.migrate !== 'false';
|
|
43
43
|
|
|
44
|
+
// Quick mode: skip slow/network operations
|
|
45
|
+
if (Manager.isQuickMode()) {
|
|
46
|
+
logger.log('Quick mode: Skipping slow setup operations');
|
|
47
|
+
options.checkManager = false;
|
|
48
|
+
options.checkNode = false;
|
|
49
|
+
options.checkRuby = false;
|
|
50
|
+
options.checkBundle = false;
|
|
51
|
+
options.checkPeerDependencies = false;
|
|
52
|
+
options.fetchFirebaseAuth = false;
|
|
53
|
+
options.publishGitHubToken = false;
|
|
54
|
+
options.deduplicatePosts = false;
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
// Log
|
|
45
58
|
logger.log(`Welcome to ${package.name} v${package.version}!`);
|
|
46
59
|
logger.log(`options`, options);
|
|
@@ -101,10 +101,8 @@
|
|
|
101
101
|
&& target.closest('button, input[type="submit"], input[type="button"], input[type="reset"], .btn-action')
|
|
102
102
|
)
|
|
103
103
|
) {
|
|
104
|
-
// Log
|
|
105
|
-
|
|
106
|
-
console.log('Click prevented (disabled):', target);
|
|
107
|
-
}
|
|
104
|
+
// Log the click for debugging
|
|
105
|
+
console.log('Click prevented (disabled):', target);
|
|
108
106
|
|
|
109
107
|
// Prevent all actions
|
|
110
108
|
e.preventDefault();
|
|
@@ -70,6 +70,7 @@ In the event of a change of control, if we sell or otherwise transfer part or th
|
|
|
70
70
|
|
|
71
71
|
### Your Choices About Your Information:
|
|
72
72
|
Your account information and profile privacy settings can be updated or changed by visiting your account profile at [{{ site.url }}/account]({{ site.url }}/account) or by contacting {{ brand }} directly at [{{ site.url }}/contact]({{ site.url }}/contact).
|
|
73
|
+
- You may request a copy of your personal data by visiting [{{ site.url }}/account]({{ site.url }}/account#data-request).
|
|
73
74
|
- You may request to have your account deleted by visiting your account profile at [{{ site.url }}/account]({{ site.url }}/account#delete).
|
|
74
75
|
- You may request to unsubscribe from emails by clicking the "unsubscribe" link inside the email.
|
|
75
76
|
|
|
@@ -80,7 +81,27 @@ When it comes to the collection of personal information from children under 13,
|
|
|
80
81
|
By opting in to receive SMS communications from {{ brand }}, you agree to receive marketing text messages, such as promotions and cart reminders, from us. Consent to receive marketing text messages is not a condition of any purchase. Message and data rates may apply, and the frequency of messages may vary. You may unsubscribe from receiving SMS messages at any time by replying “STOP” to any message or by clicking the unsubscribe link provided in our communications. For more information about our privacy practices, please refer to this Privacy Policy or our [Terms of Service]({{ site.url }}/terms/).
|
|
81
82
|
|
|
82
83
|
## Request Your Data to Be Removed or Deleted
|
|
83
|
-
|
|
84
|
+
Under applicable data protection regulations including the General Data Protection Regulation (GDPR) and the California Consumer Privacy Act (CCPA), you have the right to request the deletion of the personal data we hold about you. Account deletion is a permanent, irreversible action that cannot be undone under any circumstances.
|
|
85
|
+
|
|
86
|
+
To request deletion, visit your account page here: [{{ site.url }}/account]({{ site.url }}/account#delete). Please read the following important information before proceeding:
|
|
87
|
+
|
|
88
|
+
* **Permanent and irreversible:** Once your account is deleted, all personal data associated with your account will be permanently removed from our systems. This includes your profile information, subscription and billing history, activity logs, API keys, OAuth2 connections, referral data, and all other stored information. There is no mechanism to recover any data after deletion.
|
|
89
|
+
* **Active subscriptions:** You must cancel any active paid subscriptions before deleting your account. Accounts with active or suspended paid subscriptions cannot be deleted until the subscription is cancelled or expires.
|
|
90
|
+
* **Pending data requests:** If you have a pending data request (Subject Access Request), it will be permanently cancelled upon account deletion. We will not be able to fulfill data requests after your account has been deleted. If you wish to obtain a copy of your data, you must complete the data request and download process **before** initiating account deletion.
|
|
91
|
+
* **Immediate effect:** Account deletion takes effect immediately. You will be signed out of all active sessions and will no longer be able to access any services associated with your account.
|
|
92
|
+
* **Third-party services:** While we will remove your data from our systems, we cannot guarantee the removal of data that has already been shared with or collected by third-party services you may have used in connection with your account. Please refer to the privacy policies of those services for information about their data retention practices.
|
|
93
|
+
|
|
94
|
+
## Request a Copy of Your Data
|
|
95
|
+
Under applicable data protection regulations including the General Data Protection Regulation (GDPR), the California Consumer Privacy Act (CCPA), and other similar legislative frameworks, you may have the right to request a copy of the personal data we hold about you. This is commonly referred to as a Subject Access Request (SAR) or a Data Portability Request.
|
|
96
|
+
|
|
97
|
+
To submit a data request, visit your account page here: [{{ site.url }}/account]({{ site.url }}/account#data-request). Please read the following important information before submitting your request:
|
|
98
|
+
|
|
99
|
+
* **Processing time:** Data requests may take up to **14 business days** to process. The compilation and verification of your data is performed manually by our data processing team to ensure accuracy, completeness, and security.
|
|
100
|
+
* **Download only — no email delivery:** For security purposes, your data will **not** be sent via email or any other communication channel. You must return to your account page to download your data once it is ready. We do not consider email to be a sufficiently secure medium for the transmission of personally identifiable information.
|
|
101
|
+
* **Data unavailable after account deletion:** If you delete your account before downloading your data, all personal information will be permanently and irreversibly removed from our systems. We will not be able to fulfill any pending data requests after account deletion. If you wish to both request your data and delete your account, you must download your data **before** initiating account deletion.
|
|
102
|
+
* **Download expiration:** Completed data requests will be available for download for 30 days after processing is complete. After this period, the request will expire and you will need to submit a new request.
|
|
103
|
+
* **Request limits:** You are limited to one active data request at a time. After a completed request, there is a 30-day cooldown period before a new request can be submitted.
|
|
104
|
+
* **Data format:** Your data will be provided as a downloadable JSON file containing your account information, authentication records, subscription history, activity data, and other personally identifiable information associated with your account.
|
|
84
105
|
|
|
85
106
|
## Acceptance of This Policy
|
|
86
107
|
Use of our site signifies your acceptance of this policy. If you do not accept the policy then please do not use this site. When registering, we will further request your explicit acceptance of the privacy policy.
|
|
@@ -48,6 +48,9 @@ sections:
|
|
|
48
48
|
- id: "api-keys"
|
|
49
49
|
name: "API Keys"
|
|
50
50
|
icon: "key"
|
|
51
|
+
- id: "data-request"
|
|
52
|
+
name: "Data Request"
|
|
53
|
+
icon: "file-export"
|
|
51
54
|
- id: "delete"
|
|
52
55
|
name: "Delete Account"
|
|
53
56
|
icon: "trash"
|
|
@@ -133,7 +136,7 @@ badges:
|
|
|
133
136
|
<div class="d-flex align-items-center justify-content-between py-3">
|
|
134
137
|
<!-- Left: Brand -->
|
|
135
138
|
<a href="/" class="d-flex align-items-center text-decoration-none text-body">
|
|
136
|
-
<span class="avatar avatar-md
|
|
139
|
+
<span class="avatar avatar-md me-3">
|
|
137
140
|
<img src="{{ site.brand.images.brandmark }}?cb={{ site.uj.cache_breaker }}" alt="{{ site.brand.name }}"/>
|
|
138
141
|
</span>
|
|
139
142
|
<h1 class="h5 mb-0 fw-semibold">{{ site.brand.name }} Account</h1>
|
|
@@ -153,13 +156,13 @@ badges:
|
|
|
153
156
|
<div class="d-flex gap-2">
|
|
154
157
|
<select class="form-select flex-grow-1" id="mobile-nav-select" aria-label="Select account section">
|
|
155
158
|
{% for section in page.resolved.sections %}
|
|
156
|
-
{% unless section.id == 'delete' %}
|
|
159
|
+
{% unless section.id == 'delete' or section.id == 'data-request' %}
|
|
157
160
|
<option value="{{ section.id }}" {% if forloop.first %}selected{% endif %}>
|
|
158
161
|
{{ section.name }}
|
|
159
162
|
</option>
|
|
160
163
|
{% endunless %}
|
|
161
164
|
{% endfor %}
|
|
162
|
-
<!-- Delete
|
|
165
|
+
<!-- Delete and Data Request options will be added dynamically by JavaScript when needed -->
|
|
163
166
|
</select>
|
|
164
167
|
<button class="btn btn-danger auth-signout-btn">
|
|
165
168
|
{% uj_icon "arrow-right-from-bracket", "fa-sm" %}
|
|
@@ -181,7 +184,7 @@ badges:
|
|
|
181
184
|
<div class="card-body p-3">
|
|
182
185
|
<ul class="nav nav-pills flex-column" id="account-nav" role="tablist" aria-label="Account sections">
|
|
183
186
|
{% for section in page.resolved.sections %}
|
|
184
|
-
<li class="nav-item {% if section.id == 'delete' %}d-none{% endif %}" id="{{ section.id }}-nav-item">
|
|
187
|
+
<li class="nav-item {% if section.id == 'delete' or section.id == 'data-request' %}d-none{% endif %}" id="{{ section.id }}-nav-item">
|
|
185
188
|
<a class="nav-link d-flex align-items-center {% if forloop.first %}active{% endif %}"
|
|
186
189
|
href="#{{ section.id }}"
|
|
187
190
|
data-section="{{ section.id }}"
|
|
@@ -222,7 +225,7 @@ badges:
|
|
|
222
225
|
<section id="profile-section" class="account-section d-none">
|
|
223
226
|
<h2 class="h3 mb-4" >My profile</h2>
|
|
224
227
|
|
|
225
|
-
<form id="profile-form" novalidate>
|
|
228
|
+
<form id="profile-form" novalidate onsubmit="return false">
|
|
226
229
|
<!-- Avatar and Edit Button -->
|
|
227
230
|
<div class="mb-4">
|
|
228
231
|
<div class="d-flex align-items-center">
|
|
@@ -817,7 +820,7 @@ badges:
|
|
|
817
820
|
</div>
|
|
818
821
|
</div>
|
|
819
822
|
<div class="flex-shrink-0">
|
|
820
|
-
<form id="signin-method-{{ method.id }}-form" class="d-grid d-sm-inline-block" novalidate>
|
|
823
|
+
<form id="signin-method-{{ method.id }}-form" class="d-grid d-sm-inline-block" novalidate onsubmit="return false">
|
|
821
824
|
<input type="hidden" name="method" value="{{ method.id }}">
|
|
822
825
|
{% if method.id == "password" %}
|
|
823
826
|
<button type="submit" class="btn btn-primary btn-sm" data-action="change">
|
|
@@ -859,7 +862,7 @@ badges:
|
|
|
859
862
|
<div class="card-body">
|
|
860
863
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
861
864
|
<h5 class="card-title mb-0">Active sessions</h5>
|
|
862
|
-
<form id="signout-all-sessions-form" class="d-inline" novalidate>
|
|
865
|
+
<form id="signout-all-sessions-form" class="d-inline" novalidate onsubmit="return false">
|
|
863
866
|
<input type="hidden" name="action" value="signout-all">
|
|
864
867
|
<button type="submit" class="btn btn-outline-danger btn-sm">
|
|
865
868
|
{% uj_icon "right-from-bracket", "me-1" %}
|
|
@@ -919,7 +922,7 @@ badges:
|
|
|
919
922
|
</div>
|
|
920
923
|
<div class="text-start text-sm-end flex-shrink-0">
|
|
921
924
|
<div>
|
|
922
|
-
<form id="connection-form-{{ connection.id }}" class="d-grid d-sm-inline-block" novalidate>
|
|
925
|
+
<form id="connection-form-{{ connection.id }}" class="d-grid d-sm-inline-block" novalidate onsubmit="return false">
|
|
923
926
|
<input type="hidden" name="provider" value="{{ connection.id }}">
|
|
924
927
|
<!-- Connect button -->
|
|
925
928
|
<button type="submit" class="btn btn-sm btn-primary" data-action="connect">
|
|
@@ -1090,7 +1093,7 @@ badges:
|
|
|
1090
1093
|
<div class="card-body">
|
|
1091
1094
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1092
1095
|
<h5 class="card-title mb-0">Your API key</h5>
|
|
1093
|
-
<form id="reset-api-key-form" class="d-inline" novalidate>
|
|
1096
|
+
<form id="reset-api-key-form" class="d-inline" novalidate onsubmit="return false">
|
|
1094
1097
|
<button type="submit" class="btn btn-outline-danger btn-sm" id="reset-api-key-btn">
|
|
1095
1098
|
{% uj_icon "rotate", "me-1" %}
|
|
1096
1099
|
<span class="button-text">
|
|
@@ -1126,54 +1129,228 @@ badges:
|
|
|
1126
1129
|
</div>
|
|
1127
1130
|
</section>
|
|
1128
1131
|
|
|
1132
|
+
<!-- Data Request Section (Hidden by default) -->
|
|
1133
|
+
<section id="data-request-section" class="account-section d-none">
|
|
1134
|
+
<h2 class="h3 mb-4">Request your data</h2>
|
|
1135
|
+
|
|
1136
|
+
<!-- Info card (always shown) -->
|
|
1137
|
+
<div class="card mb-4">
|
|
1138
|
+
<div class="card-body">
|
|
1139
|
+
<h5 class="card-title text-primary">
|
|
1140
|
+
{% uj_icon "file-shield", "fa-lg me-2" %}
|
|
1141
|
+
About Data Subject Access Requests
|
|
1142
|
+
</h5>
|
|
1143
|
+
<p class="card-text small text-muted">
|
|
1144
|
+
Under applicable data protection regulations including the General Data Protection Regulation (GDPR), the California Consumer Privacy Act (CCPA), and other similar legislative frameworks enacted by governmental bodies around the world, you may have the right to request a copy of the personal data we hold about you. This process is commonly referred to as a Subject Access Request (SAR) or a Data Portability Request. It is important that you understand the full scope, limitations, and procedural requirements of this request before proceeding. Please read the following information carefully and in its entirety before submitting a request.
|
|
1145
|
+
</p>
|
|
1146
|
+
<p class="card-text small text-muted">
|
|
1147
|
+
When you submit a data request, our data processing team will begin the process of compiling the personal information associated with your account. This may include, but is not limited to: your name, email address, account creation date, account activity logs, subscription and billing history (including payment processor identifiers and transaction records), geolocation data collected during your use of our services (such as IP address, approximate location derived from IP, continent, country, region, and city), device and browser information (including user agent strings, platform identifiers, and viewport characteristics), API usage statistics and rate limiting data, referral and affiliate information, OAuth2 connection records, and any other data points stored in connection with your account in our systems.
|
|
1148
|
+
</p>
|
|
1149
|
+
<p class="card-text small text-muted">
|
|
1150
|
+
The compilation and verification process is performed by our data processing team to ensure the accuracy, completeness, and security of the information provided to you. Due to the manual verification steps involved in this process and the need to ensure that data is being released to the correct account holder, this process may take up to <strong>14 business days</strong> from the date of your request submission to complete. We appreciate your patience during this time and ask that you do not submit duplicate requests, as this will not expedite the process and may in fact delay it.
|
|
1151
|
+
</p>
|
|
1152
|
+
<p class="card-text small text-muted">
|
|
1153
|
+
Once your data has been compiled, verified, and is ready for retrieval, you will need to <strong>return to this page</strong> to download it. For security purposes, we do not transmit personal data via email, SMS, or any other communication channel, as these methods are not considered sufficiently secure mediums for the transmission of personally identifiable information. The data will be made available exclusively through this authenticated account interface to ensure that only the verified account holder can access it. Your data package will be provided as a JSON file containing all of the aforementioned data categories.
|
|
1154
|
+
</p>
|
|
1155
|
+
<p class="card-text small text-muted">
|
|
1156
|
+
<strong>Important notice regarding account deletion:</strong> If you choose to delete your account at any point, all personal data associated with your account will be permanently and irreversibly removed from our systems in accordance with our data retention policy. Once your account has been deleted, we will no longer be able to process any pending data requests, nor will we be able to provide you with a copy of your data retroactively. There is no mechanism to recover data after account deletion. If you are considering both requesting your data and deleting your account, you must complete the data request process in full and download your data package <strong>before</strong> initiating the account deletion process. We strongly recommend downloading and verifying your data before taking any irreversible actions with your account.
|
|
1157
|
+
</p>
|
|
1158
|
+
<p class="card-text small text-muted">
|
|
1159
|
+
You are limited to one active data request at a time. If you have previously submitted a request that is still being processed, you will not be able to submit a new request until the current one has been completed or has expired. Additionally, after a completed data request, there is a 30-day cooldown period before a new request can be submitted. These limitations exist to prevent abuse of the system and to ensure that our data processing team can handle all requests in a timely and thorough manner. Excessive or abusive use of this feature may result in additional restrictions being applied to your account in accordance with our Terms of Service.
|
|
1160
|
+
</p>
|
|
1161
|
+
|
|
1162
|
+
<hr>
|
|
1163
|
+
|
|
1164
|
+
<h6 class="text-muted">Summary of key points:</h6>
|
|
1165
|
+
<ul class="small text-muted">
|
|
1166
|
+
<li>Processing time: up to <strong>14 business days</strong></li>
|
|
1167
|
+
<li>You must return to this page to download your data</li>
|
|
1168
|
+
<li>Data is <strong>not</strong> sent via email for security reasons</li>
|
|
1169
|
+
<li>Data is permanently unavailable after account deletion</li>
|
|
1170
|
+
<li>Only one active request at a time</li>
|
|
1171
|
+
<li>30-day cooldown between completed requests</li>
|
|
1172
|
+
<li>Data is provided as a downloadable JSON file</li>
|
|
1173
|
+
</ul>
|
|
1174
|
+
|
|
1175
|
+
<p id="data-request-form-trigger" class="card-text small text-muted mb-0">
|
|
1176
|
+
If you have read and understood all of the information above and still wish to proceed with your data subject access request, you may proceed to <button type="button" class="accordion-trigger small text-muted text-decoration-underline" data-bs-toggle="collapse" data-bs-target="#data-request-form-accordion" aria-expanded="false" aria-controls="data-request-form-accordion">submit your request →</button>.
|
|
1177
|
+
</p>
|
|
1178
|
+
</div>
|
|
1179
|
+
</div>
|
|
1180
|
+
|
|
1181
|
+
<!-- Status area (shown when a request exists) -->
|
|
1182
|
+
<div id="data-request-status" class="d-none mb-4">
|
|
1183
|
+
<div class="card">
|
|
1184
|
+
<div class="card-body">
|
|
1185
|
+
<h5 class="card-title">
|
|
1186
|
+
{% uj_icon "clock", "fa-lg me-2" %}
|
|
1187
|
+
<span id="data-request-status-title">Request Status</span>
|
|
1188
|
+
</h5>
|
|
1189
|
+
<p id="data-request-status-message" class="card-text text-muted"></p>
|
|
1190
|
+
<div id="data-request-download" class="d-none">
|
|
1191
|
+
<form id="data-request-download-form" onsubmit="return false">
|
|
1192
|
+
<button type="submit" class="btn btn-primary btn-sm" id="data-request-download-btn">
|
|
1193
|
+
{% uj_icon "download", "fa-sm me-2" %}
|
|
1194
|
+
<span class="button-text">Download your data</span>
|
|
1195
|
+
</button>
|
|
1196
|
+
</form>
|
|
1197
|
+
</div>
|
|
1198
|
+
<div id="data-request-cancel" class="d-none">
|
|
1199
|
+
<form id="data-request-cancel-form" onsubmit="return false">
|
|
1200
|
+
<button type="submit" class="btn btn-primary btn-sm" id="data-request-cancel-btn">
|
|
1201
|
+
{% uj_icon "rotate-left", "fa-sm me-2" %}
|
|
1202
|
+
<span class="button-text">Request withdrawal</span>
|
|
1203
|
+
</button>
|
|
1204
|
+
</form>
|
|
1205
|
+
</div>
|
|
1206
|
+
</div>
|
|
1207
|
+
</div>
|
|
1208
|
+
</div>
|
|
1209
|
+
|
|
1210
|
+
<!-- Request form (hidden when a request is pending) -->
|
|
1211
|
+
<div id="data-request-form-container">
|
|
1212
|
+
<div class="collapse" id="data-request-form-accordion">
|
|
1213
|
+
<div class="card card-form-border border-primary mt-4">
|
|
1214
|
+
<div class="card-body">
|
|
1215
|
+
<h5 class="card-title text-primary">Confirm data request</h5>
|
|
1216
|
+
|
|
1217
|
+
<form id="data-request-form" novalidate onsubmit="return false">
|
|
1218
|
+
<div class="mb-3">
|
|
1219
|
+
<div class="form-check">
|
|
1220
|
+
<input class="form-check-input" type="checkbox" id="data-request-confirm-checkbox" name="confirm_processing_time" required>
|
|
1221
|
+
<label class="form-check-label small" for="data-request-confirm-checkbox">
|
|
1222
|
+
I understand that this request may take up to 14 business days to process and that I must return to this page to download my data. I acknowledge that data will not be sent to me via email or any other channel.
|
|
1223
|
+
</label>
|
|
1224
|
+
</div>
|
|
1225
|
+
</div>
|
|
1226
|
+
|
|
1227
|
+
<div class="mb-3">
|
|
1228
|
+
<div class="form-check">
|
|
1229
|
+
<input class="form-check-input" type="checkbox" id="data-request-deletion-checkbox" name="confirm_deletion_warning" required>
|
|
1230
|
+
<label class="form-check-label small" for="data-request-deletion-checkbox">
|
|
1231
|
+
I understand that if I delete my account before downloading my data, the data will be permanently lost and cannot be recovered under any circumstances.
|
|
1232
|
+
</label>
|
|
1233
|
+
</div>
|
|
1234
|
+
</div>
|
|
1235
|
+
|
|
1236
|
+
<div class="mb-3">
|
|
1237
|
+
<label for="data-request-reason" class="form-label">Reason for request</label>
|
|
1238
|
+
<textarea class="form-control" id="data-request-reason" name="reason" rows="3" placeholder="Help us understand why you're requesting your data..."></textarea>
|
|
1239
|
+
</div>
|
|
1240
|
+
|
|
1241
|
+
<div class="row g-2">
|
|
1242
|
+
<div class="col-12 col-md-6 order-md-2">
|
|
1243
|
+
<button type="submit" class="btn btn-outline-adaptive w-100" id="data-request-submit-btn" disabled>
|
|
1244
|
+
{% uj_icon "file-export", "fa-sm me-2" %}
|
|
1245
|
+
<span class="button-text">Submit Data Request</span>
|
|
1246
|
+
</button>
|
|
1247
|
+
</div>
|
|
1248
|
+
<div class="col-12 col-md-6 order-md-1">
|
|
1249
|
+
<a href="/account" class="btn btn-primary w-100">
|
|
1250
|
+
{% uj_icon "arrow-left", "fa-sm me-2" %}
|
|
1251
|
+
<span>Return to Profile</span>
|
|
1252
|
+
</a>
|
|
1253
|
+
</div>
|
|
1254
|
+
</div>
|
|
1255
|
+
</form>
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
</div>
|
|
1259
|
+
</div>
|
|
1260
|
+
</section>
|
|
1261
|
+
|
|
1129
1262
|
<!-- Delete Account Section (Hidden by default) -->
|
|
1130
1263
|
<section id="delete-section" class="account-section d-none">
|
|
1131
|
-
<h2 class="h3 mb-4
|
|
1132
|
-
|
|
1133
|
-
<div class="alert alert-danger">
|
|
1134
|
-
<h5 class="alert-heading">
|
|
1135
|
-
{% uj_icon "triangle-exclamation", "fa-lg" %}
|
|
1136
|
-
Warning: This action cannot be undone
|
|
1137
|
-
</h5>
|
|
1138
|
-
<p>Deleting your account will permanently remove:</p>
|
|
1139
|
-
<ul class="mb-2">
|
|
1140
|
-
<li>All your personal information</li>
|
|
1141
|
-
<li>Your subscription and billing history</li>
|
|
1142
|
-
<li>Access to all services and data</li>
|
|
1143
|
-
<li>Any stored content or settings</li>
|
|
1144
|
-
</ul>
|
|
1145
|
-
<p class="mb-0"><strong>This action is immediate and irreversible.</strong></p>
|
|
1146
|
-
</div>
|
|
1264
|
+
<h2 class="h3 mb-4">Delete account</h2>
|
|
1147
1265
|
|
|
1148
|
-
<div class="card
|
|
1266
|
+
<div class="card mb-4">
|
|
1149
1267
|
<div class="card-body">
|
|
1150
|
-
<h5 class="card-title text-danger">
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1268
|
+
<h5 class="card-title text-danger">
|
|
1269
|
+
{% uj_icon "triangle-exclamation", "fa-lg me-2" %}
|
|
1270
|
+
About Account Deletion
|
|
1271
|
+
</h5>
|
|
1272
|
+
<p class="card-text small text-muted">
|
|
1273
|
+
Under applicable data protection regulations including the General Data Protection Regulation (GDPR) and the California Consumer Privacy Act (CCPA), you have the right to request the deletion of the personal data we hold about you. Account deletion is a permanent, irreversible action that cannot be undone under any circumstances. Please read the following important information carefully before proceeding.
|
|
1274
|
+
</p>
|
|
1275
|
+
<p class="card-text small text-muted">
|
|
1276
|
+
<strong>Permanent and irreversible:</strong> Once your account is deleted, all personal data associated with your account will be permanently removed from our systems. This includes, but is not limited to, your profile information, subscription and billing history, activity logs, API keys, OAuth2 connections, referral data, saved preferences, and all other stored information. There is no mechanism to recover any data after deletion. Our support team cannot restore deleted accounts or retrieve any associated data, regardless of the circumstances.
|
|
1277
|
+
</p>
|
|
1278
|
+
<p class="card-text small text-muted">
|
|
1279
|
+
<strong>Active subscriptions:</strong> You must cancel any active paid subscriptions before deleting your account. Accounts with active or suspended paid subscriptions cannot be deleted until the subscription is cancelled or expires. If you have a subscription that is currently in a billing cycle, you will need to wait for the cycle to complete or cancel the subscription first. No refunds will be issued for unused portions of cancelled subscriptions in connection with account deletion.
|
|
1280
|
+
</p>
|
|
1281
|
+
<p class="card-text small text-muted">
|
|
1282
|
+
<strong>Pending data requests:</strong> If you have a pending data request (Subject Access Request), it will be permanently cancelled upon account deletion. We will not be able to fulfill data requests after your account has been deleted. If you wish to obtain a copy of your data, you must complete the data request and download process <strong>before</strong> initiating account deletion. You can request a copy of your data from the <a href="#data-request">data request section</a>.
|
|
1283
|
+
</p>
|
|
1284
|
+
<p class="card-text small text-muted">
|
|
1285
|
+
<strong>Immediate effect:</strong> Account deletion takes effect immediately upon confirmation. You will be signed out of all active sessions across all devices and platforms, and will no longer be able to access any services, features, content, or data associated with your account. Any active API integrations or third-party connections will cease to function immediately.
|
|
1286
|
+
</p>
|
|
1287
|
+
<p class="card-text small text-muted">
|
|
1288
|
+
<strong>Third-party services:</strong> While we will remove your data from our systems, we cannot guarantee the removal of data that has already been shared with or collected by third-party services you may have used in connection with your account. This includes, but is not limited to, analytics providers, payment processors, authentication providers, and any external services you authorized. Please refer to the privacy policies of those services for information about their data retention and deletion practices.
|
|
1289
|
+
</p>
|
|
1290
|
+
|
|
1291
|
+
<hr>
|
|
1292
|
+
|
|
1293
|
+
<h6 class="text-muted">Summary of consequences:</h6>
|
|
1294
|
+
<ul class="small text-muted">
|
|
1295
|
+
<li>All personal information, account data, and stored content will be permanently erased</li>
|
|
1296
|
+
<li>Your subscription and complete billing history will be permanently removed</li>
|
|
1297
|
+
<li>All API keys, OAuth2 connections, and integrations will be revoked</li>
|
|
1298
|
+
<li>Access to all services, features, and platforms will be terminated immediately</li>
|
|
1299
|
+
<li>Any pending data requests will be cancelled and cannot be fulfilled</li>
|
|
1300
|
+
<li>This action cannot be reversed, appealed, or undone by our support team</li>
|
|
1301
|
+
</ul>
|
|
1302
|
+
|
|
1303
|
+
<p class="card-text small text-muted mb-0">
|
|
1304
|
+
If you have read and understood all of the information above and still wish to proceed with deleting your account, you may proceed to <button type="button" class="accordion-trigger small text-muted text-decoration-underline" data-bs-toggle="collapse" data-bs-target="#delete-form-accordion" aria-expanded="false" aria-controls="delete-form-accordion">confirm account deletion →</button>.
|
|
1305
|
+
</p>
|
|
1306
|
+
</div>
|
|
1307
|
+
</div>
|
|
1308
|
+
|
|
1309
|
+
<div class="collapse" id="delete-form-accordion">
|
|
1310
|
+
<div class="card card-form-border border-danger mt-4">
|
|
1311
|
+
<div class="card-body">
|
|
1312
|
+
<h5 class="card-title text-danger">Confirm account deletion</h5>
|
|
1313
|
+
|
|
1314
|
+
<form id="delete-account-form" novalidate onsubmit="return false">
|
|
1315
|
+
<div class="mb-3">
|
|
1316
|
+
<div class="form-check">
|
|
1317
|
+
<input class="form-check-input" type="checkbox" id="delete-confirm-checkbox" required>
|
|
1318
|
+
<label class="form-check-label" for="delete-confirm-checkbox">
|
|
1319
|
+
I understand that deleting my account is permanent and cannot be reversed. All my data, including personal information, subscription history, API keys, and stored content, will be permanently and irreversibly erased.
|
|
1320
|
+
</label>
|
|
1321
|
+
</div>
|
|
1159
1322
|
</div>
|
|
1160
|
-
</div>
|
|
1161
1323
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1324
|
+
<div class="mb-3">
|
|
1325
|
+
<div class="form-check">
|
|
1326
|
+
<input class="form-check-input" type="checkbox" id="delete-data-request-checkbox" required>
|
|
1327
|
+
<label class="form-check-label" for="delete-data-request-checkbox">
|
|
1328
|
+
I acknowledge that any pending data requests will be cancelled and I will not be able to request or download a copy of my data after my account is deleted.
|
|
1329
|
+
</label>
|
|
1330
|
+
</div>
|
|
1331
|
+
</div>
|
|
1166
1332
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
<
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1333
|
+
<div class="mb-3">
|
|
1334
|
+
<label for="delete-reason" class="form-label">Reason for leaving</label>
|
|
1335
|
+
<textarea class="form-control" id="delete-reason" name="reason" rows="3" placeholder="Help us improve by sharing why you're leaving..."></textarea>
|
|
1336
|
+
</div>
|
|
1337
|
+
|
|
1338
|
+
<div class="row g-2">
|
|
1339
|
+
<div class="col-12 col-md-6 order-md-2">
|
|
1340
|
+
<button type="submit" class="btn btn-outline-adaptive w-100" id="delete-account-btn" disabled>
|
|
1341
|
+
{% uj_icon "trash", "fa-sm me-2" %}
|
|
1342
|
+
<span class="button-text">Delete My Account Permanently</span>
|
|
1343
|
+
</button>
|
|
1344
|
+
</div>
|
|
1345
|
+
<div class="col-12 col-md-6 order-md-1">
|
|
1346
|
+
<a href="/account" class="btn btn-danger w-100" id="cancel-delete-btn">
|
|
1347
|
+
{% uj_icon "arrow-left", "fa-sm me-2" %}
|
|
1348
|
+
<span>Return to Profile</span>
|
|
1349
|
+
</a>
|
|
1350
|
+
</div>
|
|
1351
|
+
</div>
|
|
1352
|
+
</form>
|
|
1353
|
+
</div>
|
|
1177
1354
|
</div>
|
|
1178
1355
|
</div>
|
|
1179
1356
|
</section>
|
|
@@ -25,7 +25,7 @@ layout: themes/[ site.theme.id ]/frontend/core/cover
|
|
|
25
25
|
{% capture initializing_spinner %}<span class="spinner-border spinner-border-sm me-2 form-initializing-spinner"></span>{% endcapture %}
|
|
26
26
|
|
|
27
27
|
<!-- Reset Form -->
|
|
28
|
-
<form id="auth-form" autocomplete="on" novalidate>
|
|
28
|
+
<form id="auth-form" autocomplete="on" novalidate onsubmit="return false">
|
|
29
29
|
<div class="mb-4 text-start">
|
|
30
30
|
<label for="email" class="form-label fw-semibold">
|
|
31
31
|
Email <span class="text-danger">*</span>
|
|
@@ -40,7 +40,7 @@ social_signin:
|
|
|
40
40
|
{% capture initializing_spinner %}<span class="spinner-border spinner-border-sm me-2 form-initializing-spinner"></span>{% endcapture %}
|
|
41
41
|
|
|
42
42
|
<!-- Sign In Form -->
|
|
43
|
-
<form id="auth-form" autocomplete="on" novalidate>
|
|
43
|
+
<form id="auth-form" autocomplete="on" novalidate onsubmit="return false">
|
|
44
44
|
<!-- Hidden default submit button for Enter key -->
|
|
45
45
|
<button type="submit" class="d-none" data-provider="email" aria-hidden="true" tabindex="-1"></button>
|
|
46
46
|
|
|
@@ -40,7 +40,7 @@ social_signup:
|
|
|
40
40
|
{% capture initializing_spinner %}<span class="spinner-border spinner-border-sm me-2 form-initializing-spinner"></span>{% endcapture %}
|
|
41
41
|
|
|
42
42
|
<!-- Sign Up Form -->
|
|
43
|
-
<form id="auth-form" autocomplete="on" novalidate>
|
|
43
|
+
<form id="auth-form" autocomplete="on" novalidate onsubmit="return false">
|
|
44
44
|
<!-- Hidden default submit button for Enter key -->
|
|
45
45
|
<button type="submit" class="d-none" data-provider="email" aria-hidden="true" tabindex="-1"></button>
|
|
46
46
|
|
|
@@ -340,7 +340,7 @@ newsletter_cta:
|
|
|
340
340
|
{% endiftruthy %}
|
|
341
341
|
|
|
342
342
|
<!-- Newsletter Form -->
|
|
343
|
-
<form id="newsletter-form" class="newsletter-form">
|
|
343
|
+
<form id="newsletter-form" class="newsletter-form" onsubmit="return false">
|
|
344
344
|
<div class="row g-3 justify-content-center">
|
|
345
345
|
<div class="col-md-7">
|
|
346
346
|
<input
|
|
@@ -178,7 +178,7 @@ faqs:
|
|
|
178
178
|
{% endiftruthy %}
|
|
179
179
|
</div>
|
|
180
180
|
|
|
181
|
-
<form id="contact-form" autocomplete="on">
|
|
181
|
+
<form id="contact-form" autocomplete="on" onsubmit="return false">
|
|
182
182
|
<div class="row g-3 mb-4">
|
|
183
183
|
<div class="col-md-6">
|
|
184
184
|
<label for="first_name" class="form-label fw-semibold">First Name <span class="text-danger">*</span></label>
|
|
@@ -368,7 +368,7 @@ cta:
|
|
|
368
368
|
{% else %}
|
|
369
369
|
{% if platform.id == "android" or platform.id == "ios" %}
|
|
370
370
|
<div class="mb-4 text-start">
|
|
371
|
-
<form id="mobile-email-form-{{ platform.id }}" class="mobile-email-form" data-platform="{{ platform.id }}">
|
|
371
|
+
<form id="mobile-email-form-{{ platform.id }}" class="mobile-email-form" data-platform="{{ platform.id }}" onsubmit="return false">
|
|
372
372
|
<label for="email-{{ platform.id }}" class="form-label">Email Address</label>
|
|
373
373
|
<div class="row g-3">
|
|
374
374
|
<div class="col-12 col-md-8">
|
|
@@ -225,7 +225,7 @@ cta:
|
|
|
225
225
|
<!-- Input Demo: Single input with button -->
|
|
226
226
|
<div class="card bg-glassy border-0 {% unless is_side_layout %}mx-auto{% endunless %} {{ demo.options.class }}">
|
|
227
227
|
<div class="card-body">
|
|
228
|
-
<form id="hero-demo-form" class="d-flex flex-column {% unless is_side_layout %}flex-sm-row justify-content-center{% endunless %} gap-3" {% iftruthy demo.options.redirect %}data-redirect="{{ demo.options.redirect }}"{% endiftruthy %}>
|
|
228
|
+
<form id="hero-demo-form" class="d-flex flex-column {% unless is_side_layout %}flex-sm-row justify-content-center{% endunless %} gap-3" {% iftruthy demo.options.redirect %}data-redirect="{{ demo.options.redirect }}"{% endiftruthy %} onsubmit="return false">
|
|
229
229
|
<input
|
|
230
230
|
type="{{ demo.options.input_type | default: 'text' }}"
|
|
231
231
|
name="{{ demo.options.name | default: 'input' }}"
|
|
@@ -249,7 +249,7 @@ cta:
|
|
|
249
249
|
{% elsif demo.type == "form" %}
|
|
250
250
|
<!-- Form Demo: Multiple fields with card styling -->
|
|
251
251
|
<div class="{% unless is_side_layout %}mx-auto{% endunless %} {{ demo.options.class }}">
|
|
252
|
-
<form id="hero-demo-form" {% iftruthy demo.options.redirect %}data-redirect="{{ demo.options.redirect }}"{% endiftruthy %}>
|
|
252
|
+
<form id="hero-demo-form" {% iftruthy demo.options.redirect %}data-redirect="{{ demo.options.redirect }}"{% endiftruthy %} onsubmit="return false">
|
|
253
253
|
<div class="row g-3 {% unless is_side_layout %}justify-content-center{% endunless %}">
|
|
254
254
|
{% assign autofocus_set = false %}
|
|
255
255
|
{% for field in demo.options.fields %}
|
|
@@ -24,7 +24,8 @@ web_manager:
|
|
|
24
24
|
<button type="submit" class="btn btn-adaptive btn-md d-flex align-items-center justify-content-center payment-button"
|
|
25
25
|
data-payment-method="card"
|
|
26
26
|
data-action="pay-card"
|
|
27
|
-
data-wm-bind="@show checkout.paymentMethods.card"
|
|
27
|
+
data-wm-bind="@show checkout.paymentMethods.card"
|
|
28
|
+
hidden>
|
|
28
29
|
{% uj_icon "credit-card", "me-2 fa-3xl" %}
|
|
29
30
|
<span class="fw-semibold">Credit/Debit</span>
|
|
30
31
|
</button>
|
|
@@ -33,7 +34,8 @@ web_manager:
|
|
|
33
34
|
<button type="submit" class="btn btn-paypal btn-md d-flex align-items-center justify-content-center payment-button"
|
|
34
35
|
data-payment-method="paypal"
|
|
35
36
|
data-action="pay-paypal"
|
|
36
|
-
data-wm-bind="@show checkout.paymentMethods.paypal"
|
|
37
|
+
data-wm-bind="@show checkout.paymentMethods.paypal"
|
|
38
|
+
hidden>
|
|
37
39
|
<img src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-200px.png"
|
|
38
40
|
alt="PayPal"
|
|
39
41
|
height="28"
|
|
@@ -152,7 +154,7 @@ web_manager:
|
|
|
152
154
|
</div> -->
|
|
153
155
|
|
|
154
156
|
<!-- Main Checkout Form -->
|
|
155
|
-
<form id="checkout-form" autocomplete="on" novalidate>
|
|
157
|
+
<form id="checkout-form" autocomplete="on" novalidate onsubmit="return false">
|
|
156
158
|
<!-- Content Row -->
|
|
157
159
|
<div class="row d-lg-flex">
|
|
158
160
|
<!-- Section 1: Billing Cycle & Customer Info -->
|
|
@@ -255,7 +255,7 @@ build_info:
|
|
|
255
255
|
{% endiftruthy %}
|
|
256
256
|
</div>
|
|
257
257
|
<div class="col-lg-5">
|
|
258
|
-
<form id="status-subscribe-form" class="d-flex flex-column flex-sm-row gap-2">
|
|
258
|
+
<form id="status-subscribe-form" class="d-flex flex-column flex-sm-row gap-2" onsubmit="return false">
|
|
259
259
|
<input
|
|
260
260
|
type="email"
|
|
261
261
|
class="form-control flex-grow-1"
|
|
@@ -148,6 +148,15 @@ async function jekyll(complete) {
|
|
|
148
148
|
launchBrowserSync();
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// Quick mode: trigger deferred asset compilation after first build
|
|
152
|
+
if (Manager.isQuickMode() && index === 0) {
|
|
153
|
+
logger.log('Quick mode: Triggering deferred asset compilation...');
|
|
154
|
+
Manager.triggerRebuild([
|
|
155
|
+
'src/assets/js/main.js',
|
|
156
|
+
'src/assets/css/main.scss',
|
|
157
|
+
], logger);
|
|
158
|
+
}
|
|
159
|
+
|
|
151
160
|
// Reload browser
|
|
152
161
|
if (global.browserSync) {
|
|
153
162
|
global.browserSync.reload();
|
package/dist/gulp/tasks/sass.js
CHANGED
|
@@ -77,6 +77,9 @@ const output = 'dist/assets/css';
|
|
|
77
77
|
const delay = 250;
|
|
78
78
|
const compiled = {};
|
|
79
79
|
|
|
80
|
+
// Flags
|
|
81
|
+
let deferred = false;
|
|
82
|
+
|
|
80
83
|
// Configuration
|
|
81
84
|
const MAIN_BUNDLE_PAGE_PARTIALS = false; // Set to true to merge pages into _page-specific.scss, false to compile separately
|
|
82
85
|
// Enable PurgeCSS via environment variable or in production mode
|
|
@@ -86,6 +89,13 @@ const ujmConfig = Manager.getUJMConfig();
|
|
|
86
89
|
|
|
87
90
|
// SASS Compilation Task
|
|
88
91
|
function sass(complete) {
|
|
92
|
+
// Quick mode: skip initial compilation, use stale cached bundles
|
|
93
|
+
if (Manager.isQuickMode() && !deferred) {
|
|
94
|
+
deferred = true;
|
|
95
|
+
logger.log('Quick mode: Skipping initial SASS compilation (using cached bundles)');
|
|
96
|
+
return complete();
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
// Log
|
|
90
100
|
logger.log('Starting...');
|
|
91
101
|
Manager.logMemory(logger, 'Start');
|
|
@@ -72,6 +72,9 @@ const copy = [
|
|
|
72
72
|
|
|
73
73
|
const delay = 250;
|
|
74
74
|
|
|
75
|
+
// Flags
|
|
76
|
+
let deferred = false;
|
|
77
|
+
|
|
75
78
|
// Bundle naming configuration
|
|
76
79
|
const bundleNaming = {
|
|
77
80
|
// Files that should have stable (non-hashed) names
|
|
@@ -275,6 +278,13 @@ function getSettings() {
|
|
|
275
278
|
|
|
276
279
|
// Task
|
|
277
280
|
function webpack(complete) {
|
|
281
|
+
// Quick mode: skip initial compilation, use stale cached bundles
|
|
282
|
+
if (Manager.isQuickMode() && !deferred) {
|
|
283
|
+
deferred = true;
|
|
284
|
+
logger.log('Quick mode: Skipping initial webpack compilation (using cached bundles)');
|
|
285
|
+
return complete();
|
|
286
|
+
}
|
|
287
|
+
|
|
278
288
|
// Get settings (loads config at runtime)
|
|
279
289
|
const settings = getSettings();
|
|
280
290
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultimate-jekyll-manager",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.271",
|
|
4
4
|
"description": "Ultimate Jekyll dependency manager",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"sass": "^1.97.3",
|
|
102
102
|
"spellchecker": "^3.7.1",
|
|
103
103
|
"through2": "^4.0.2",
|
|
104
|
-
"web-manager": "^4.1.
|
|
104
|
+
"web-manager": "^4.1.15",
|
|
105
105
|
"webpack": "^5.105.2",
|
|
106
106
|
"wonderful-fetch": "^1.3.4",
|
|
107
107
|
"wonderful-version": "^1.3.2",
|