ultimate-jekyll-manager 0.0.272 → 0.0.273
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/README.md +6 -1
- package/dist/assets/js/libs/form-manager.js +11 -0
- package/dist/assets/js/pages/account/index.js +32 -7
- package/dist/assets/js/pages/account/sections/billing.js +95 -182
- package/dist/assets/js/pages/account/sections/data-request.js +1 -17
- package/dist/assets/js/pages/account/sections/delete.js +1 -18
- package/dist/assets/js/pages/account/sections/profile.js +2 -4
- package/dist/assets/js/pages/account/test-subscriptions/active.js +14 -73
- package/dist/assets/js/pages/account/test-subscriptions/cancellation-requested.js +19 -75
- package/dist/assets/js/pages/account/test-subscriptions/cancelled.js +13 -75
- package/dist/assets/js/pages/account/test-subscriptions/suspended.js +10 -84
- package/dist/assets/js/pages/account/test-subscriptions/trialing.js +16 -74
- package/dist/defaults/dist/_layouts/blueprint/admin/notifications/new.html +2 -3
- package/dist/defaults/dist/_layouts/blueprint/admin/users/new.html +16 -16
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +16 -12
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/blog/index.html +2 -5
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/status.html +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -374,7 +374,12 @@ Raw pixel values also accepted: `vert-size="300"` → 300px max-height. Omit `ve
|
|
|
374
374
|
#### Testing Parameters
|
|
375
375
|
|
|
376
376
|
##### Account Page (`/account`)
|
|
377
|
-
* `_dev_subscription`: Override subscription data for testing
|
|
377
|
+
* `_dev_subscription`: Override subscription data for testing billing states. The product ID is automatically patched to match a real product from the backend. Available values:
|
|
378
|
+
- `_dev_subscription=active`: Active paid subscription
|
|
379
|
+
- `_dev_subscription=trialing`: Free trial in progress
|
|
380
|
+
- `_dev_subscription=suspended`: Payment failed, access revoked
|
|
381
|
+
- `_dev_subscription=cancellation-requested`: Active but cancellation pending
|
|
382
|
+
- `_dev_subscription=cancelled`: Subscription ended
|
|
378
383
|
* `_dev_prefill=true`: Adds fake test data for development:
|
|
379
384
|
- Inserts fake referral data in the Referrals section
|
|
380
385
|
- Inserts fake session data in the Security section (active sessions)
|
|
@@ -113,6 +113,17 @@ export class FormManager {
|
|
|
113
113
|
// Initialize file drop zones
|
|
114
114
|
this._initFileDropZones();
|
|
115
115
|
|
|
116
|
+
// Warn about fields missing name attributes (they will be invisible to validation and getData)
|
|
117
|
+
/* @dev-only:start */
|
|
118
|
+
{
|
|
119
|
+
this.$form.querySelectorAll('input, select, textarea').forEach(($field) => {
|
|
120
|
+
if (!$field.name && !$field.matches(HONEYPOT_SELECTOR) && $field.type !== 'hidden') {
|
|
121
|
+
console.warn('[Form-manager] Field missing "name" attribute — will be skipped by validation and getData():', $field);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/* @dev-only:end */
|
|
126
|
+
|
|
116
127
|
// Auto-transition to initialState when DOM is ready
|
|
117
128
|
if (this.config.autoReady) {
|
|
118
129
|
domReady().then(() => this._setInitialState());
|
|
@@ -83,6 +83,10 @@ async function initializeAccount() {
|
|
|
83
83
|
webManager.auth().listen({ account: true }, async (state) => {
|
|
84
84
|
console.log('Auth state with account data:', state);
|
|
85
85
|
|
|
86
|
+
// Load user data with the account information
|
|
87
|
+
// Wait for app data to be fetched before loading section data
|
|
88
|
+
await fetchAppData();
|
|
89
|
+
|
|
86
90
|
/* @dev-only:start */
|
|
87
91
|
{
|
|
88
92
|
// Check for test subscription parameter
|
|
@@ -94,10 +98,16 @@ async function initializeAccount() {
|
|
|
94
98
|
console.log(`Loading test subscription: ${testSubscription}`);
|
|
95
99
|
const testModule = await import(`./test-subscriptions/${testSubscription}.js`);
|
|
96
100
|
|
|
97
|
-
//
|
|
101
|
+
// Merge test fields INTO the real subscription (preserves real product, payment, etc.)
|
|
98
102
|
if (state.account) {
|
|
99
|
-
state.account.subscription
|
|
100
|
-
|
|
103
|
+
const real = state.account.subscription || {};
|
|
104
|
+
const test = testModule.default;
|
|
105
|
+
const merged = deepMerge(real, test);
|
|
106
|
+
|
|
107
|
+
// Write back so both JS and WM bindings see the same data
|
|
108
|
+
state.account.subscription = merged;
|
|
109
|
+
|
|
110
|
+
console.log('Test subscription merged:', merged);
|
|
101
111
|
}
|
|
102
112
|
} catch (error) {
|
|
103
113
|
console.error(`Failed to load test subscription '${testSubscription}':`, error);
|
|
@@ -106,10 +116,6 @@ async function initializeAccount() {
|
|
|
106
116
|
}
|
|
107
117
|
/* @dev-only:end */
|
|
108
118
|
|
|
109
|
-
// Load user data with the account information
|
|
110
|
-
// Wait for app data to be fetched before loading section data
|
|
111
|
-
await fetchAppData();
|
|
112
|
-
|
|
113
119
|
loadAllSectionData(state);
|
|
114
120
|
|
|
115
121
|
// Hide loading section and show the appropriate section
|
|
@@ -359,6 +365,25 @@ function showSection(sectionId) {
|
|
|
359
365
|
}
|
|
360
366
|
}
|
|
361
367
|
|
|
368
|
+
// Deep merge utility (target fields are overwritten by source fields)
|
|
369
|
+
function deepMerge(target, source) {
|
|
370
|
+
const result = { ...target };
|
|
371
|
+
|
|
372
|
+
Object.keys(source).forEach(key => {
|
|
373
|
+
const sourceVal = source[key];
|
|
374
|
+
const targetVal = target[key];
|
|
375
|
+
|
|
376
|
+
if (sourceVal && typeof sourceVal === 'object' && !Array.isArray(sourceVal)
|
|
377
|
+
&& targetVal && typeof targetVal === 'object' && !Array.isArray(targetVal)) {
|
|
378
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
379
|
+
} else {
|
|
380
|
+
result[key] = sourceVal;
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
|
|
362
387
|
// Tracking functions
|
|
363
388
|
function trackAccountSectionView(sectionId) {
|
|
364
389
|
gtag('event', 'account_section_view', {
|
|
@@ -58,16 +58,13 @@ function updatePlanCard(account) {
|
|
|
58
58
|
const $planStatus = document.getElementById('plan-status');
|
|
59
59
|
const $planDescription = document.getElementById('plan-description');
|
|
60
60
|
|
|
61
|
-
// Determine product info
|
|
62
|
-
const productId = getProductId(subscription);
|
|
63
|
-
const isPaid = productId !== 'basic';
|
|
64
|
-
const displayName = getDisplayName(productId, isPaid);
|
|
65
|
-
|
|
66
|
-
// Set status badge and description
|
|
67
61
|
if (!$planStatus || !$planDescription) {
|
|
68
62
|
return;
|
|
69
63
|
}
|
|
70
64
|
|
|
65
|
+
const productId = getProductId(subscription);
|
|
66
|
+
const isPaid = productId !== 'basic';
|
|
67
|
+
const displayName = getDisplayName(subscription);
|
|
71
68
|
const status = getEffectiveStatus(subscription, isPaid);
|
|
72
69
|
|
|
73
70
|
switch (status) {
|
|
@@ -77,30 +74,18 @@ function updatePlanCard(account) {
|
|
|
77
74
|
$planDescription.innerHTML = `You are currently on the <strong>${displayName}</strong> plan. Upgrade to unlock premium features.`;
|
|
78
75
|
break;
|
|
79
76
|
}
|
|
77
|
+
case 'trialing':
|
|
78
|
+
case 'cancelling':
|
|
80
79
|
case 'active': {
|
|
81
80
|
$planStatus.className = 'badge bg-success';
|
|
82
81
|
$planStatus.textContent = 'Active';
|
|
83
82
|
$planDescription.innerHTML = `You are currently on the <strong>${displayName}</strong> plan.`;
|
|
84
83
|
break;
|
|
85
84
|
}
|
|
86
|
-
case '
|
|
87
|
-
const daysLeft = getTrialDaysLeft(subscription);
|
|
88
|
-
$planStatus.className = 'badge bg-info';
|
|
89
|
-
$planStatus.textContent = daysLeft > 0 ? `Trial (${daysLeft} days left)` : 'Trial';
|
|
90
|
-
$planDescription.innerHTML = `You're trialing the <strong>${displayName}</strong> plan.`;
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
case 'cancelling': {
|
|
94
|
-
const daysLeft = getCancellationDaysLeft(subscription);
|
|
95
|
-
$planStatus.className = 'badge bg-warning text-body';
|
|
96
|
-
$planStatus.textContent = daysLeft > 0 ? `Cancelling (${daysLeft} days left)` : 'Cancelling';
|
|
97
|
-
$planDescription.innerHTML = `Your <strong>${displayName}</strong> plan is set to cancel. You still have access until the end of your billing period.`;
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
case 'payment_issue': {
|
|
85
|
+
case 'suspended': {
|
|
101
86
|
$planStatus.className = 'badge bg-danger';
|
|
102
|
-
$planStatus.textContent = '
|
|
103
|
-
$planDescription.innerHTML = `Your <strong>${displayName}</strong>
|
|
87
|
+
$planStatus.textContent = 'Suspended';
|
|
88
|
+
$planDescription.innerHTML = `Your <strong>${displayName}</strong> subscription has been suspended and access has been revoked due to a payment issue. Please update your payment method to restore access.`;
|
|
104
89
|
break;
|
|
105
90
|
}
|
|
106
91
|
case 'cancelled': {
|
|
@@ -109,12 +94,6 @@ function updatePlanCard(account) {
|
|
|
109
94
|
$planDescription.innerHTML = `Your <strong>${displayName}</strong> subscription has ended. Resubscribe to regain access to premium features.`;
|
|
110
95
|
break;
|
|
111
96
|
}
|
|
112
|
-
case 'expired': {
|
|
113
|
-
$planStatus.className = 'badge bg-secondary';
|
|
114
|
-
$planStatus.textContent = 'Expired';
|
|
115
|
-
$planDescription.innerHTML = `Your <strong>${displayName}</strong> subscription has expired. Subscribe again to regain access.`;
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
97
|
default: {
|
|
119
98
|
$planStatus.className = 'badge bg-secondary';
|
|
120
99
|
$planStatus.textContent = 'Free';
|
|
@@ -138,22 +117,14 @@ function updateAlerts(account) {
|
|
|
138
117
|
const isPaid = productId !== 'basic';
|
|
139
118
|
const status = getEffectiveStatus(subscription, isPaid);
|
|
140
119
|
|
|
141
|
-
//
|
|
142
|
-
if (status === '
|
|
143
|
-
const message = subscription.paymentIssue?.message || 'Your payment method was declined.';
|
|
144
|
-
const attempts = subscription.paymentIssue?.attempts || 0;
|
|
145
|
-
const nextRetry = subscription.paymentIssue?.nextRetry;
|
|
146
|
-
let retryText = '';
|
|
147
|
-
if (nextRetry) {
|
|
148
|
-
retryText = ` Next retry: ${new Date(nextRetry).toLocaleDateString()}.`;
|
|
149
|
-
}
|
|
150
|
-
|
|
120
|
+
// Suspended (payment issue) alert
|
|
121
|
+
if (status === 'suspended') {
|
|
151
122
|
$alerts.innerHTML += `
|
|
152
123
|
<div class="alert alert-danger d-flex align-items-start">
|
|
153
124
|
<span class="me-2 flex-shrink-0">${getPrerenderedIcon('triangle-exclamation', 'fa-md')}</span>
|
|
154
125
|
<div>
|
|
155
|
-
<strong>Payment failed</strong>
|
|
156
|
-
<p class="mb-0 small"
|
|
126
|
+
<strong>Payment failed</strong>
|
|
127
|
+
<p class="mb-0 small">Your payment method was declined. Please update your payment method to keep your subscription active.</p>
|
|
157
128
|
</div>
|
|
158
129
|
</div>
|
|
159
130
|
`;
|
|
@@ -161,9 +132,9 @@ function updateAlerts(account) {
|
|
|
161
132
|
|
|
162
133
|
// Cancellation pending alert
|
|
163
134
|
if (status === 'cancelling') {
|
|
164
|
-
const
|
|
165
|
-
const dateStr =
|
|
166
|
-
? new Date(
|
|
135
|
+
const cancelTimestamp = subscription.cancellation?.date?.timestampUNIX;
|
|
136
|
+
const dateStr = cancelTimestamp && cancelTimestamp > 0
|
|
137
|
+
? new Date(cancelTimestamp * 1000).toLocaleDateString()
|
|
167
138
|
: 'the end of your billing period';
|
|
168
139
|
|
|
169
140
|
$alerts.innerHTML += `
|
|
@@ -177,20 +148,22 @@ function updateAlerts(account) {
|
|
|
177
148
|
`;
|
|
178
149
|
}
|
|
179
150
|
|
|
180
|
-
//
|
|
151
|
+
// Free trial alert
|
|
181
152
|
if (status === 'trialing') {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
153
|
+
const trialEndUnix = subscription.trial?.expires?.timestampUNIX;
|
|
154
|
+
const endDate = trialEndUnix && trialEndUnix > 0
|
|
155
|
+
? new Date(trialEndUnix * 1000).toLocaleDateString()
|
|
156
|
+
: null;
|
|
157
|
+
|
|
158
|
+
$alerts.innerHTML += `
|
|
159
|
+
<div class="alert alert-success d-flex align-items-start">
|
|
160
|
+
<span class="me-2 flex-shrink-0">${getPrerenderedIcon('circle-check', 'fa-md')}</span>
|
|
161
|
+
<div>
|
|
162
|
+
<strong>Free trial</strong>
|
|
163
|
+
<p class="mb-0 small">You're on a free trial${endDate ? ` that ends on <strong>${endDate}</strong>` : ''}. You won't be charged until your trial ends.</p>
|
|
191
164
|
</div>
|
|
192
|
-
|
|
193
|
-
|
|
165
|
+
</div>
|
|
166
|
+
`;
|
|
194
167
|
}
|
|
195
168
|
}
|
|
196
169
|
|
|
@@ -206,106 +179,65 @@ function updateBillingDetails(account) {
|
|
|
206
179
|
|
|
207
180
|
const productId = getProductId(subscription);
|
|
208
181
|
const isPaid = productId !== 'basic';
|
|
182
|
+
const status = getEffectiveStatus(subscription, isPaid);
|
|
209
183
|
|
|
210
|
-
// Only show billing details for paid
|
|
211
|
-
if (!isPaid ||
|
|
184
|
+
// Only show billing details for paid, non-suspended, non-cancelled subscriptions
|
|
185
|
+
if (!isPaid || status === 'suspended' || status === 'cancelled') {
|
|
212
186
|
$details.classList.add('d-none');
|
|
213
187
|
return;
|
|
214
188
|
}
|
|
215
189
|
|
|
216
|
-
const
|
|
217
|
-
const amount = subscription.
|
|
218
|
-
const currency =
|
|
219
|
-
const frequency = subscription.frequency
|
|
190
|
+
const nextBillingUnix = subscription.expires?.timestampUNIX;
|
|
191
|
+
const amount = subscription.payment?.price;
|
|
192
|
+
const currency = appData?.payment?.currency || 'USD';
|
|
193
|
+
const frequency = subscription.payment?.frequency;
|
|
220
194
|
|
|
221
|
-
if (!
|
|
195
|
+
if (!nextBillingUnix || nextBillingUnix <= 0 || !amount) {
|
|
222
196
|
$details.classList.add('d-none');
|
|
223
197
|
return;
|
|
224
198
|
}
|
|
225
199
|
|
|
226
|
-
const nextDate = new Date(
|
|
200
|
+
const nextDate = new Date(nextBillingUnix * 1000).toLocaleDateString();
|
|
227
201
|
const formattedAmount = formatCurrency(amount, currency);
|
|
228
202
|
const frequencyLabel = frequency === 'annually' || frequency === 'yearly' ? 'year' : 'month';
|
|
229
203
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
html = '';
|
|
235
|
-
} else {
|
|
236
|
-
html = `
|
|
237
|
-
<div class="row small text-muted">
|
|
238
|
-
<div class="col-sm-6 mb-1">
|
|
239
|
-
<strong>Next billing:</strong> ${nextDate}
|
|
240
|
-
</div>
|
|
241
|
-
<div class="col-sm-6 mb-1">
|
|
242
|
-
<strong>Amount:</strong> ${formattedAmount} / ${frequencyLabel}
|
|
243
|
-
</div>
|
|
204
|
+
$details.innerHTML = `
|
|
205
|
+
<div class="row small text-muted">
|
|
206
|
+
<div class="col-sm-6 mb-1">
|
|
207
|
+
<strong>Next billing:</strong> ${nextDate}
|
|
244
208
|
</div>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} else {
|
|
252
|
-
$details.classList.add('d-none');
|
|
253
|
-
}
|
|
209
|
+
<div class="col-sm-6 mb-1">
|
|
210
|
+
<strong>Amount:</strong> ${formattedAmount} / ${frequencyLabel}
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
`;
|
|
214
|
+
$details.classList.remove('d-none');
|
|
254
215
|
}
|
|
255
216
|
|
|
256
217
|
// ─── Action Buttons ──────────────────────────────────────────
|
|
257
218
|
|
|
258
219
|
function updateActionButtons(account) {
|
|
220
|
+
// Bindings handle the primary show/hide based on product.id (basic vs paid).
|
|
221
|
+
// JS only needs to refine for cancelled/suspended edge cases.
|
|
259
222
|
const subscription = account.subscription || {};
|
|
260
223
|
const productId = getProductId(subscription);
|
|
261
224
|
const isPaid = productId !== 'basic';
|
|
262
225
|
const status = getEffectiveStatus(subscription, isPaid);
|
|
263
226
|
|
|
264
|
-
// Hide all action buttons first
|
|
265
|
-
document.querySelectorAll('.plan-action-btn').forEach(btn => btn.classList.add('d-none'));
|
|
266
|
-
|
|
267
227
|
const $upgradeBtn = document.getElementById('upgrade-plan-btn');
|
|
268
228
|
const $changeBtn = document.getElementById('change-plan-btn');
|
|
269
229
|
const $manageBtn = document.getElementById('manage-billing-btn');
|
|
270
230
|
const $cancelTrigger = document.getElementById('cancel-subscription-trigger');
|
|
271
231
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if ($cancelTrigger) $cancelTrigger.classList.remove('d-none');
|
|
282
|
-
break;
|
|
283
|
-
|
|
284
|
-
case 'trialing':
|
|
285
|
-
if ($upgradeBtn) $upgradeBtn.classList.remove('d-none');
|
|
286
|
-
if ($cancelTrigger) $cancelTrigger.classList.remove('d-none');
|
|
287
|
-
break;
|
|
288
|
-
|
|
289
|
-
case 'cancelling':
|
|
290
|
-
if ($manageBtn) $manageBtn.classList.remove('d-none');
|
|
291
|
-
if ($cancelTrigger) $cancelTrigger.classList.add('d-none');
|
|
292
|
-
break;
|
|
293
|
-
|
|
294
|
-
case 'payment_issue':
|
|
295
|
-
if ($manageBtn) $manageBtn.classList.remove('d-none');
|
|
296
|
-
if ($cancelTrigger) $cancelTrigger.classList.remove('d-none');
|
|
297
|
-
break;
|
|
298
|
-
|
|
299
|
-
case 'cancelled':
|
|
300
|
-
case 'expired':
|
|
301
|
-
if ($upgradeBtn) $upgradeBtn.classList.remove('d-none');
|
|
302
|
-
if ($cancelTrigger) $cancelTrigger.classList.add('d-none');
|
|
303
|
-
break;
|
|
304
|
-
|
|
305
|
-
default:
|
|
306
|
-
if ($upgradeBtn) $upgradeBtn.classList.remove('d-none');
|
|
307
|
-
if ($cancelTrigger) $cancelTrigger.classList.add('d-none');
|
|
308
|
-
break;
|
|
232
|
+
if (status === 'cancelled') {
|
|
233
|
+
// Cancelled: show upgrade, hide change/manage/cancel
|
|
234
|
+
if ($upgradeBtn) $upgradeBtn.removeAttribute('hidden');
|
|
235
|
+
if ($changeBtn) $changeBtn.setAttribute('hidden', '');
|
|
236
|
+
if ($manageBtn) $manageBtn.setAttribute('hidden', '');
|
|
237
|
+
if ($cancelTrigger) $cancelTrigger.setAttribute('hidden', '');
|
|
238
|
+
} else if (status === 'suspended') {
|
|
239
|
+
// Suspended: hide change, keep manage + cancel visible (bindings handle)
|
|
240
|
+
if ($changeBtn) $changeBtn.setAttribute('hidden', '');
|
|
309
241
|
}
|
|
310
242
|
}
|
|
311
243
|
|
|
@@ -375,10 +307,7 @@ async function openStripePortal() {
|
|
|
375
307
|
|
|
376
308
|
function setupCancellationForm() {
|
|
377
309
|
const $form = document.getElementById('cancel-subscription-form');
|
|
378
|
-
|
|
379
|
-
const $submitBtn = document.getElementById('cancel-subscription-btn');
|
|
380
|
-
|
|
381
|
-
if (!$form || !$checkbox || !$submitBtn) {
|
|
310
|
+
if (!$form) {
|
|
382
311
|
return;
|
|
383
312
|
}
|
|
384
313
|
|
|
@@ -392,16 +321,7 @@ function setupCancellationForm() {
|
|
|
392
321
|
submittedText: 'Subscription Cancelled',
|
|
393
322
|
});
|
|
394
323
|
|
|
395
|
-
// Enable/disable submit based on checkbox
|
|
396
|
-
$checkbox.addEventListener('change', () => {
|
|
397
|
-
$submitBtn.disabled = !$checkbox.checked;
|
|
398
|
-
});
|
|
399
|
-
|
|
400
324
|
cancelFormManager.on('submit', async ({ data }) => {
|
|
401
|
-
if (!$checkbox.checked) {
|
|
402
|
-
throw new Error('Please confirm the cancellation terms before proceeding.');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
325
|
// Get selected reason
|
|
406
326
|
const $selectedReason = document.querySelector('input[name="cancel_reason"]:checked');
|
|
407
327
|
const reason = $selectedReason?.value || '';
|
|
@@ -434,13 +354,15 @@ function setupCancellationForm() {
|
|
|
434
354
|
}
|
|
435
355
|
}, 3000);
|
|
436
356
|
|
|
437
|
-
// Update the UI to reflect cancellation
|
|
357
|
+
// Update the UI to reflect cancellation (using backend structure)
|
|
438
358
|
if (currentAccount?.subscription) {
|
|
359
|
+
const expiresUnix = currentAccount.subscription.expires?.timestampUNIX || 0;
|
|
439
360
|
currentAccount.subscription.cancellation = {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
361
|
+
pending: true,
|
|
362
|
+
date: {
|
|
363
|
+
timestamp: new Date(expiresUnix * 1000).toISOString(),
|
|
364
|
+
timestampUNIX: expiresUnix,
|
|
365
|
+
},
|
|
444
366
|
};
|
|
445
367
|
updatePlanCard(currentAccount);
|
|
446
368
|
updateAlerts(currentAccount);
|
|
@@ -476,9 +398,14 @@ function updateUsageInfo(account) {
|
|
|
476
398
|
return;
|
|
477
399
|
}
|
|
478
400
|
|
|
479
|
-
//
|
|
401
|
+
// Determine effective product for usage limits
|
|
402
|
+
// If subscription isn't actively paid, use basic plan limits
|
|
480
403
|
const productId = getProductId(subscription);
|
|
481
|
-
const
|
|
404
|
+
const isPaid = productId !== 'basic';
|
|
405
|
+
const status = getEffectiveStatus(subscription, isPaid);
|
|
406
|
+
const hasActiveAccess = status === 'active' || status === 'trialing' || status === 'cancelling';
|
|
407
|
+
const effectiveProductId = (isPaid && hasActiveAccess) ? productId : 'basic';
|
|
408
|
+
const product = appData?.payment?.products?.find(p => p.id === effectiveProductId);
|
|
482
409
|
const limits = product?.limits || {};
|
|
483
410
|
|
|
484
411
|
// Clear container
|
|
@@ -538,13 +465,19 @@ function updateUsageInfo(account) {
|
|
|
538
465
|
// ─── Helpers ─────────────────────────────────────────────────
|
|
539
466
|
|
|
540
467
|
function getProductId(subscription) {
|
|
541
|
-
return subscription.product?.id ||
|
|
468
|
+
return subscription.product?.id || 'basic';
|
|
542
469
|
}
|
|
543
470
|
|
|
544
|
-
function getDisplayName(
|
|
471
|
+
function getDisplayName(subscription) {
|
|
472
|
+
// Use backend-provided product name first
|
|
473
|
+
if (subscription.product?.name && subscription.product.name !== 'Basic') {
|
|
474
|
+
return subscription.product.name;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Fall back to appData product name
|
|
478
|
+
const productId = subscription.product?.id || 'basic';
|
|
545
479
|
const product = appData?.payment?.products?.find(p => p.id === productId);
|
|
546
|
-
return product?.name
|
|
547
|
-
|| (isPaid ? productId.charAt(0).toUpperCase() + productId.slice(1) : 'Free');
|
|
480
|
+
return product?.name || 'Free';
|
|
548
481
|
}
|
|
549
482
|
|
|
550
483
|
function getEffectiveStatus(subscription, isPaid) {
|
|
@@ -552,55 +485,35 @@ function getEffectiveStatus(subscription, isPaid) {
|
|
|
552
485
|
return 'free';
|
|
553
486
|
}
|
|
554
487
|
|
|
555
|
-
if (subscription.status === 'suspended'
|
|
556
|
-
return '
|
|
488
|
+
if (subscription.status === 'suspended') {
|
|
489
|
+
return 'suspended';
|
|
557
490
|
}
|
|
558
491
|
|
|
559
|
-
if (subscription.cancellation?.
|
|
492
|
+
if (subscription.cancellation?.pending && subscription.status === 'active') {
|
|
560
493
|
return 'cancelling';
|
|
561
494
|
}
|
|
562
495
|
|
|
563
|
-
if (subscription.status === 'active'
|
|
564
|
-
|
|
496
|
+
if (subscription.status === 'active' && subscription.trial?.claimed
|
|
497
|
+
&& subscription.trial?.expires?.timestampUNIX > Math.floor(Date.now() / 1000)) {
|
|
498
|
+
return 'trialing';
|
|
565
499
|
}
|
|
566
500
|
|
|
567
|
-
if (subscription.status === '
|
|
568
|
-
return '
|
|
501
|
+
if (subscription.status === 'active') {
|
|
502
|
+
return 'active';
|
|
569
503
|
}
|
|
570
504
|
|
|
571
505
|
if (subscription.status === 'cancelled') {
|
|
572
506
|
return 'cancelled';
|
|
573
507
|
}
|
|
574
508
|
|
|
575
|
-
if (subscription.status === 'expired') {
|
|
576
|
-
return 'expired';
|
|
577
|
-
}
|
|
578
|
-
|
|
579
509
|
return 'free';
|
|
580
510
|
}
|
|
581
511
|
|
|
582
|
-
function
|
|
583
|
-
const trialEnd = subscription.trial?.endedAt || subscription.trial?.expires?.timestamp;
|
|
584
|
-
if (!trialEnd) {
|
|
585
|
-
return 0;
|
|
586
|
-
}
|
|
587
|
-
return Math.max(0, Math.ceil((new Date(trialEnd) - new Date()) / (1000 * 60 * 60 * 24)));
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
function getCancellationDaysLeft(subscription) {
|
|
591
|
-
const effectiveAt = subscription.cancellation?.effectiveAt;
|
|
592
|
-
if (!effectiveAt) {
|
|
593
|
-
return 0;
|
|
594
|
-
}
|
|
595
|
-
// effectiveAt is in Unix seconds
|
|
596
|
-
return Math.max(0, Math.ceil((effectiveAt * 1000 - Date.now()) / (1000 * 60 * 60 * 24)));
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function formatCurrency(amountInCents, currency) {
|
|
512
|
+
function formatCurrency(amount, currency) {
|
|
600
513
|
return new Intl.NumberFormat('en-US', {
|
|
601
514
|
style: 'currency',
|
|
602
|
-
currency: currency.toUpperCase(),
|
|
603
|
-
}).format(
|
|
515
|
+
currency: (currency || 'USD').toUpperCase(),
|
|
516
|
+
}).format(amount); // amount is already in display dollars
|
|
604
517
|
}
|
|
605
518
|
|
|
606
519
|
function formatMetricName(metricId) {
|
|
@@ -22,11 +22,7 @@ export async function init(wm) {
|
|
|
22
22
|
// Setup data request form
|
|
23
23
|
function setupDataRequestForm() {
|
|
24
24
|
const $form = document.getElementById('data-request-form');
|
|
25
|
-
|
|
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) {
|
|
25
|
+
if (!$form) {
|
|
30
26
|
return;
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -37,19 +33,7 @@ function setupDataRequestForm() {
|
|
|
37
33
|
submittedText: 'Request Submitted',
|
|
38
34
|
});
|
|
39
35
|
|
|
40
|
-
// Enable/disable submit button based on both checkboxes
|
|
41
|
-
function updateSubmitState() {
|
|
42
|
-
$submitBtn.disabled = !($checkbox1.checked && $checkbox2.checked);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
$checkbox1.addEventListener('change', updateSubmitState);
|
|
46
|
-
$checkbox2.addEventListener('change', updateSubmitState);
|
|
47
|
-
|
|
48
36
|
formManager.on('submit', async ({ data }) => {
|
|
49
|
-
if (!$checkbox1.checked || !$checkbox2.checked) {
|
|
50
|
-
throw new Error('Please confirm all acknowledgments before submitting.');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
37
|
trackDataRequest('submit');
|
|
54
38
|
|
|
55
39
|
const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user/data-request`, {
|
|
@@ -18,11 +18,7 @@ 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
|
-
|
|
22
|
-
const $checkbox2 = document.getElementById('delete-data-request-checkbox');
|
|
23
|
-
const $deleteBtn = document.getElementById('delete-account-btn');
|
|
24
|
-
|
|
25
|
-
if (!$form || !$checkbox1 || !$checkbox2 || !$deleteBtn) {
|
|
21
|
+
if (!$form) {
|
|
26
22
|
return;
|
|
27
23
|
}
|
|
28
24
|
|
|
@@ -33,20 +29,7 @@ function setupDeleteAccountForm() {
|
|
|
33
29
|
submittedText: 'Account Deleted!',
|
|
34
30
|
});
|
|
35
31
|
|
|
36
|
-
// Enable/disable delete button based on both checkboxes
|
|
37
|
-
function updateDeleteState() {
|
|
38
|
-
$deleteBtn.disabled = !($checkbox1.checked && $checkbox2.checked);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
$checkbox1.addEventListener('change', updateDeleteState);
|
|
42
|
-
$checkbox2.addEventListener('change', updateDeleteState);
|
|
43
|
-
|
|
44
32
|
formManager.on('submit', async ({ data }) => {
|
|
45
|
-
// Check if both checkboxes are checked
|
|
46
|
-
if (!$checkbox1.checked || !$checkbox2.checked) {
|
|
47
|
-
throw new Error('Please confirm all acknowledgments before proceeding.');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
33
|
// 1ms wait for dialog to appear properly
|
|
51
34
|
await new Promise(resolve => setTimeout(resolve, 1));
|
|
52
35
|
|
|
@@ -228,11 +228,9 @@ function updateRoleBadges(account) {
|
|
|
228
228
|
// Show premium badge if subscription is active AND on a paid plan
|
|
229
229
|
const $premiumBadge = document.getElementById('badge-premium');
|
|
230
230
|
if ($premiumBadge) {
|
|
231
|
-
const productId = subscription?.product?.id ||
|
|
231
|
+
const productId = subscription?.product?.id || 'basic';
|
|
232
232
|
const isPaid = productId !== 'basic';
|
|
233
|
-
const isActive = subscription?.status === 'active'
|
|
234
|
-
|| subscription?.status === 'trialing'
|
|
235
|
-
|| subscription?.access === true;
|
|
233
|
+
const isActive = subscription?.status === 'active';
|
|
236
234
|
|
|
237
235
|
if (isActive && isPaid) {
|
|
238
236
|
$premiumBadge.classList.remove('d-none');
|