ultimate-jekyll-manager 0.0.278 → 0.0.280
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/css/pages/account/index.scss +4 -0
- package/dist/assets/js/pages/account/sections/billing.js +143 -206
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/account/index.html +57 -9
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/status.html +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
// Libraries
|
|
6
6
|
import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
7
7
|
import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
|
|
8
|
-
import { getPrerenderedIcon } from '__main_assets__/js/libs/prerendered-icons.js';
|
|
9
8
|
|
|
10
9
|
let webManager = null;
|
|
11
10
|
let appData = null;
|
|
@@ -23,6 +22,18 @@ const CANCEL_REASONS = [
|
|
|
23
22
|
'Other',
|
|
24
23
|
];
|
|
25
24
|
|
|
25
|
+
// Status display configuration
|
|
26
|
+
const STATUS_CONFIG = {
|
|
27
|
+
free: { label: 'Free', badgeClass: 'badge bg-secondary' },
|
|
28
|
+
active: { label: 'Active', badgeClass: 'badge bg-success' },
|
|
29
|
+
trialing: { label: 'Active', badgeClass: 'badge bg-success' },
|
|
30
|
+
cancelling: { label: 'Active', badgeClass: 'badge bg-success' },
|
|
31
|
+
suspended: { label: 'Suspended', badgeClass: 'badge bg-danger' },
|
|
32
|
+
cancelled: { label: 'Cancelled', badgeClass: 'badge bg-secondary' },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const FREQUENCY_LABELS = { daily: 'day', weekly: 'week', monthly: 'month', annually: 'year' };
|
|
36
|
+
|
|
26
37
|
// Initialize billing section
|
|
27
38
|
export async function init(wm) {
|
|
28
39
|
webManager = wm;
|
|
@@ -39,11 +50,7 @@ export async function loadData(account, sharedAppData) {
|
|
|
39
50
|
appData = sharedAppData;
|
|
40
51
|
currentAccount = account;
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
updateAlerts(account);
|
|
44
|
-
updateBillingDetails(account);
|
|
45
|
-
updateActionButtons(account);
|
|
46
|
-
updateUsageInfo(account);
|
|
53
|
+
updateUI(account);
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
// Called when section is shown
|
|
@@ -51,197 +58,89 @@ export function onShow() {
|
|
|
51
58
|
// Nothing needed
|
|
52
59
|
}
|
|
53
60
|
|
|
54
|
-
// ───
|
|
55
|
-
|
|
56
|
-
function updatePlanCard(account) {
|
|
57
|
-
const subscription = account.subscription || {};
|
|
58
|
-
const $planStatus = document.getElementById('plan-status');
|
|
59
|
-
const $planDescription = document.getElementById('plan-description');
|
|
60
|
-
|
|
61
|
-
if (!$planStatus || !$planDescription) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const productId = getProductId(subscription);
|
|
66
|
-
const isPaid = productId !== 'basic';
|
|
67
|
-
const displayName = getDisplayName(subscription);
|
|
68
|
-
const status = getEffectiveStatus(subscription, isPaid);
|
|
61
|
+
// ─── UI Update ──────────────────────────────────────────────
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
case 'trialing':
|
|
78
|
-
case 'cancelling':
|
|
79
|
-
case 'active': {
|
|
80
|
-
$planStatus.className = 'badge bg-success';
|
|
81
|
-
$planStatus.textContent = 'Active';
|
|
82
|
-
$planDescription.innerHTML = `You are currently on the <strong>${displayName}</strong> plan.`;
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
case 'suspended': {
|
|
86
|
-
$planStatus.className = 'badge bg-danger';
|
|
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.`;
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case 'cancelled': {
|
|
92
|
-
$planStatus.className = 'badge bg-secondary';
|
|
93
|
-
$planStatus.textContent = 'Cancelled';
|
|
94
|
-
$planDescription.innerHTML = `Your <strong>${displayName}</strong> subscription has ended. Resubscribe to regain access to premium features.`;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
default: {
|
|
98
|
-
$planStatus.className = 'badge bg-secondary';
|
|
99
|
-
$planStatus.textContent = 'Free';
|
|
100
|
-
$planDescription.innerHTML = `You are currently on the <strong>Free</strong> plan.`;
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
63
|
+
/* @dev-only:start */
|
|
64
|
+
{
|
|
65
|
+
window._billing = {
|
|
66
|
+
test: (account) => updateUI(account),
|
|
67
|
+
state: () => buildBillingState(currentAccount),
|
|
68
|
+
restore: () => { if (currentAccount) updateUI(currentAccount); },
|
|
69
|
+
};
|
|
104
70
|
}
|
|
71
|
+
/* @dev-only:end */
|
|
105
72
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const $alerts = document.getElementById('billing-alerts');
|
|
110
|
-
if (!$alerts) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
$alerts.innerHTML = '';
|
|
115
|
-
const subscription = account.subscription || {};
|
|
116
|
-
const productId = getProductId(subscription);
|
|
117
|
-
const isPaid = productId !== 'basic';
|
|
118
|
-
const status = getEffectiveStatus(subscription, isPaid);
|
|
119
|
-
|
|
120
|
-
// Suspended (payment issue) alert
|
|
121
|
-
if (status === 'suspended') {
|
|
122
|
-
$alerts.innerHTML += `
|
|
123
|
-
<div class="alert alert-danger d-flex align-items-start">
|
|
124
|
-
<span class="me-2 flex-shrink-0">${getPrerenderedIcon('triangle-exclamation', 'fa-md')}</span>
|
|
125
|
-
<div>
|
|
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>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Cancellation pending alert
|
|
134
|
-
if (status === 'cancelling') {
|
|
135
|
-
const cancelTimestamp = subscription.cancellation?.date?.timestampUNIX;
|
|
136
|
-
const dateStr = cancelTimestamp && cancelTimestamp > 0
|
|
137
|
-
? new Date(cancelTimestamp * 1000).toLocaleDateString()
|
|
138
|
-
: 'the end of your billing period';
|
|
139
|
-
|
|
140
|
-
$alerts.innerHTML += `
|
|
141
|
-
<div class="alert alert-warning d-flex align-items-start">
|
|
142
|
-
<span class="me-2 flex-shrink-0">${getPrerenderedIcon('clock', 'fa-md')}</span>
|
|
143
|
-
<div>
|
|
144
|
-
<strong>Cancellation scheduled</strong>
|
|
145
|
-
<p class="mb-0 small">Your subscription will end on <strong>${dateStr}</strong>. You'll continue to have full access until then. If you change your mind, you can reactivate through billing management.</p>
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Free trial alert
|
|
152
|
-
if (status === 'trialing') {
|
|
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>
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
`;
|
|
167
|
-
}
|
|
73
|
+
function updateUI(account) {
|
|
74
|
+
webManager.bindings().update(buildBillingState(account));
|
|
75
|
+
updateUsageInfo(account);
|
|
168
76
|
}
|
|
169
77
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
function updateBillingDetails(account) {
|
|
173
|
-
const subscription = account.subscription || {};
|
|
174
|
-
const $details = document.getElementById('billing-details');
|
|
175
|
-
|
|
176
|
-
if (!$details) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
78
|
+
function buildBillingState(account) {
|
|
79
|
+
const subscription = account?.subscription || {};
|
|
180
80
|
const productId = getProductId(subscription);
|
|
181
81
|
const isPaid = productId !== 'basic';
|
|
182
82
|
const status = getEffectiveStatus(subscription, isPaid);
|
|
83
|
+
const displayName = getDisplayName(subscription);
|
|
84
|
+
const config = STATUS_CONFIG[status] || STATUS_CONFIG.free;
|
|
183
85
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
86
|
+
// Pre-format alert dates
|
|
87
|
+
const cancelTimestamp = subscription.cancellation?.date?.timestampUNIX;
|
|
88
|
+
const cancelDate = (cancelTimestamp && cancelTimestamp > 0)
|
|
89
|
+
? new Date(cancelTimestamp * 1000).toLocaleDateString()
|
|
90
|
+
: 'the end of your billing period';
|
|
91
|
+
|
|
92
|
+
const trialEndUnix = subscription.trial?.expires?.timestampUNIX;
|
|
93
|
+
const trialEndDate = (trialEndUnix && trialEndUnix > 0)
|
|
94
|
+
? new Date(trialEndUnix * 1000).toLocaleDateString()
|
|
95
|
+
: null;
|
|
189
96
|
|
|
97
|
+
// Pre-format billing details
|
|
190
98
|
const nextBillingUnix = subscription.expires?.timestampUNIX;
|
|
191
99
|
const amount = subscription.payment?.price;
|
|
192
100
|
const currency = appData?.payment?.currency || 'USD';
|
|
193
101
|
const frequency = subscription.payment?.frequency;
|
|
102
|
+
const hasValidBilling = nextBillingUnix && nextBillingUnix > 0 && amount;
|
|
194
103
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
104
|
+
return {
|
|
105
|
+
billing: {
|
|
106
|
+
plan: {
|
|
107
|
+
name: displayName,
|
|
108
|
+
},
|
|
109
|
+
status: {
|
|
110
|
+
label: config.label,
|
|
111
|
+
badgeClass: config.badgeClass,
|
|
112
|
+
},
|
|
113
|
+
description: {
|
|
114
|
+
free: status === 'free',
|
|
115
|
+
active: status === 'active' || status === 'trialing' || status === 'cancelling',
|
|
116
|
+
suspended: status === 'suspended',
|
|
117
|
+
cancelled: status === 'cancelled',
|
|
118
|
+
},
|
|
119
|
+
alerts: {
|
|
120
|
+
suspended: status === 'suspended',
|
|
121
|
+
cancelling: status === 'cancelling',
|
|
122
|
+
trialing: status === 'trialing',
|
|
123
|
+
cancelDate: cancelDate,
|
|
124
|
+
trialEndDate: trialEndDate || '',
|
|
125
|
+
trialHasEndDate: !!trialEndDate,
|
|
126
|
+
},
|
|
127
|
+
details: {
|
|
128
|
+
visible: isPaid && status !== 'suspended' && status !== 'cancelled' && !!hasValidBilling,
|
|
129
|
+
nextDate: hasValidBilling ? new Date(nextBillingUnix * 1000).toLocaleDateString() : '',
|
|
130
|
+
amount: hasValidBilling ? `${formatCurrency(amount, currency)} / ${FREQUENCY_LABELS[frequency] || 'month'}` : '',
|
|
131
|
+
},
|
|
132
|
+
buttons: {
|
|
133
|
+
upgrade: !isPaid || status === 'cancelled',
|
|
134
|
+
change: isPaid && status !== 'cancelled' && status !== 'suspended',
|
|
135
|
+
manage: isPaid && status !== 'cancelled',
|
|
136
|
+
cancel: isPaid && status !== 'cancelled' && status !== 'suspended',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
};
|
|
216
140
|
}
|
|
217
141
|
|
|
218
142
|
// ─── Action Buttons ──────────────────────────────────────────
|
|
219
143
|
|
|
220
|
-
function updateActionButtons(account) {
|
|
221
|
-
// Bindings handle the primary show/hide based on product.id (basic vs paid).
|
|
222
|
-
// JS only needs to refine for cancelled/suspended edge cases.
|
|
223
|
-
const subscription = account.subscription || {};
|
|
224
|
-
const productId = getProductId(subscription);
|
|
225
|
-
const isPaid = productId !== 'basic';
|
|
226
|
-
const status = getEffectiveStatus(subscription, isPaid);
|
|
227
|
-
|
|
228
|
-
const $upgradeBtn = document.getElementById('upgrade-plan-btn');
|
|
229
|
-
const $changeBtn = document.getElementById('change-plan-btn');
|
|
230
|
-
const $manageBtn = document.getElementById('manage-billing-btn');
|
|
231
|
-
const $cancelTrigger = document.getElementById('cancel-subscription-trigger');
|
|
232
|
-
|
|
233
|
-
if (status === 'cancelled') {
|
|
234
|
-
// Cancelled: show upgrade, hide change/manage/cancel
|
|
235
|
-
if ($upgradeBtn) $upgradeBtn.removeAttribute('hidden');
|
|
236
|
-
if ($changeBtn) $changeBtn.setAttribute('hidden', '');
|
|
237
|
-
if ($manageBtn) $manageBtn.setAttribute('hidden', '');
|
|
238
|
-
if ($cancelTrigger) $cancelTrigger.setAttribute('hidden', '');
|
|
239
|
-
} else if (status === 'suspended') {
|
|
240
|
-
// Suspended: hide change, keep manage + cancel visible (bindings handle)
|
|
241
|
-
if ($changeBtn) $changeBtn.setAttribute('hidden', '');
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
144
|
function setupActionButtons() {
|
|
246
145
|
const $upgradeBtn = document.getElementById('upgrade-plan-btn');
|
|
247
146
|
const $changeBtn = document.getElementById('change-plan-btn');
|
|
@@ -327,6 +226,16 @@ function setupCancellationForm() {
|
|
|
327
226
|
const $selectedReason = document.querySelector('input[name="cancel_reason"]:checked');
|
|
328
227
|
const reason = $selectedReason?.value || '';
|
|
329
228
|
|
|
229
|
+
// Capture status BEFORE the API call — the auth listener may update currentAccount
|
|
230
|
+
// with Firestore data (cancellation.pending=true) before we reach the post-cancel code
|
|
231
|
+
const sub = currentAccount?.subscription;
|
|
232
|
+
const productId = getProductId(sub || {});
|
|
233
|
+
const isPaid = productId !== 'basic';
|
|
234
|
+
const statusBeforeCancel = getEffectiveStatus(sub || {}, isPaid);
|
|
235
|
+
const isTrialCancel = statusBeforeCancel === 'trialing';
|
|
236
|
+
|
|
237
|
+
console.log('[Billing] Cancelling:', { statusBeforeCancel, isTrialCancel, productId });
|
|
238
|
+
|
|
330
239
|
trackBilling('cancel_submit');
|
|
331
240
|
|
|
332
241
|
const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/payments/cancel`, {
|
|
@@ -344,7 +253,13 @@ function setupCancellationForm() {
|
|
|
344
253
|
throw new Error(response.message || 'Failed to cancel subscription. Please try again.');
|
|
345
254
|
}
|
|
346
255
|
|
|
347
|
-
|
|
256
|
+
console.log('[Billing] Cancel complete:', { isTrialCancel, productId });
|
|
257
|
+
|
|
258
|
+
if (isTrialCancel) {
|
|
259
|
+
cancelFormManager.showSuccess('Your trial has been cancelled. You\'ve been moved to the free plan. You can subscribe again anytime.');
|
|
260
|
+
} else {
|
|
261
|
+
cancelFormManager.showSuccess('Your subscription has been cancelled. You\'ll continue to have access until the end of your current billing period.');
|
|
262
|
+
}
|
|
348
263
|
|
|
349
264
|
// Collapse the cancel form after a short delay
|
|
350
265
|
setTimeout(() => {
|
|
@@ -353,22 +268,38 @@ function setupCancellationForm() {
|
|
|
353
268
|
const bsCollapse = bootstrap.Collapse.getInstance($accordion);
|
|
354
269
|
if (bsCollapse) bsCollapse.hide();
|
|
355
270
|
}
|
|
356
|
-
},
|
|
357
|
-
|
|
358
|
-
// Update the UI to reflect cancellation
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
271
|
+
}, 1000);
|
|
272
|
+
|
|
273
|
+
// Update the UI to reflect cancellation
|
|
274
|
+
// Re-read currentAccount.subscription since the auth listener may have replaced it
|
|
275
|
+
const currentSub = currentAccount?.subscription;
|
|
276
|
+
if (currentSub) {
|
|
277
|
+
if (isTrialCancel) {
|
|
278
|
+
// Trial cancellations are immediate — downgrade to basic
|
|
279
|
+
currentSub.status = 'cancelled';
|
|
280
|
+
currentSub.product = { id: 'basic', name: 'Basic' };
|
|
281
|
+
currentSub.cancellation = {
|
|
282
|
+
pending: false,
|
|
283
|
+
date: {
|
|
284
|
+
timestamp: new Date().toISOString(),
|
|
285
|
+
timestampUNIX: Math.floor(Date.now() / 1000),
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
} else {
|
|
289
|
+
// Non-trial cancellations are pending until end of billing period
|
|
290
|
+
const expiresUnix = currentSub.expires?.timestampUNIX || 0;
|
|
291
|
+
currentSub.cancellation = {
|
|
292
|
+
pending: true,
|
|
293
|
+
date: {
|
|
294
|
+
timestamp: new Date(expiresUnix * 1000).toISOString(),
|
|
295
|
+
timestampUNIX: expiresUnix,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log('[Billing] UI update after cancel:', { status: currentSub.status, productId: currentSub.product?.id, cancellationPending: currentSub.cancellation?.pending });
|
|
301
|
+
|
|
302
|
+
updateUI(currentAccount);
|
|
372
303
|
}
|
|
373
304
|
});
|
|
374
305
|
}
|
|
@@ -483,6 +414,9 @@ function getDisplayName(subscription) {
|
|
|
483
414
|
return product?.name || 'Free';
|
|
484
415
|
}
|
|
485
416
|
|
|
417
|
+
// Derives a UI-friendly status from the backend subscription object
|
|
418
|
+
// Backend only has 3 statuses: 'active', 'suspended', 'cancelled'
|
|
419
|
+
// This function adds 'free', 'trialing', and 'cancelling' for UI purposes
|
|
486
420
|
function getEffectiveStatus(subscription, isPaid) {
|
|
487
421
|
if (!isPaid) {
|
|
488
422
|
return 'free';
|
|
@@ -492,21 +426,24 @@ function getEffectiveStatus(subscription, isPaid) {
|
|
|
492
426
|
return 'suspended';
|
|
493
427
|
}
|
|
494
428
|
|
|
495
|
-
if (subscription.
|
|
496
|
-
return '
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
if (subscription.status === 'active' && subscription.trial?.claimed
|
|
500
|
-
&& subscription.trial?.expires?.timestampUNIX > Math.floor(Date.now() / 1000)) {
|
|
501
|
-
return 'trialing';
|
|
429
|
+
if (subscription.status === 'cancelled') {
|
|
430
|
+
return 'cancelled';
|
|
502
431
|
}
|
|
503
432
|
|
|
433
|
+
// For active subscriptions, check trial and cancellation state
|
|
504
434
|
if (subscription.status === 'active') {
|
|
505
|
-
|
|
506
|
-
|
|
435
|
+
// Trial check: claimed and not yet expired
|
|
436
|
+
if (subscription.trial?.claimed
|
|
437
|
+
&& subscription.trial?.expires?.timestampUNIX > Math.floor(Date.now() / 1000)) {
|
|
438
|
+
return 'trialing';
|
|
439
|
+
}
|
|
507
440
|
|
|
508
|
-
|
|
509
|
-
|
|
441
|
+
// Pending cancellation check
|
|
442
|
+
if (subscription.cancellation?.pending) {
|
|
443
|
+
return 'cancelling';
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return 'active';
|
|
510
447
|
}
|
|
511
448
|
|
|
512
449
|
return 'free';
|
|
@@ -970,30 +970,78 @@ badges:
|
|
|
970
970
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
971
971
|
<div>
|
|
972
972
|
<h5 class="card-title mb-1 d-flex align-items-center flex-wrap gap-2">
|
|
973
|
-
<span><span data-wm-bind="@text
|
|
974
|
-
<span
|
|
973
|
+
<span><span data-wm-bind="@text billing.plan.name" class="wm-binding-skeleton">Current</span> plan</span>
|
|
974
|
+
<span data-wm-bind="@attr class billing.status.badgeClass, @text billing.status.label" class="badge bg-secondary wm-binding-skeleton"> </span>
|
|
975
975
|
</h5>
|
|
976
|
-
<p class="card-text mb-0"
|
|
976
|
+
<p class="card-text mb-0" data-wm-bind="@show billing.description.free" hidden>
|
|
977
|
+
You are currently on the <strong data-wm-bind="@text billing.plan.name">Free</strong> plan. Upgrade to unlock premium features.
|
|
978
|
+
</p>
|
|
979
|
+
<p class="card-text mb-0" data-wm-bind="@show billing.description.active" hidden>
|
|
980
|
+
You are currently on the <strong data-wm-bind="@text billing.plan.name">Current</strong> plan.
|
|
981
|
+
</p>
|
|
982
|
+
<p class="card-text mb-0" data-wm-bind="@show billing.description.suspended" hidden>
|
|
983
|
+
Your <strong data-wm-bind="@text billing.plan.name">Current</strong> subscription has been suspended and access has been revoked due to a payment issue. Please update your payment method to restore access.
|
|
984
|
+
</p>
|
|
985
|
+
<p class="card-text mb-0" data-wm-bind="@show billing.description.cancelled" hidden>
|
|
986
|
+
Your <strong data-wm-bind="@text billing.plan.name">Current</strong> subscription has ended. Resubscribe to regain access to premium features.
|
|
987
|
+
</p>
|
|
977
988
|
</div>
|
|
978
989
|
</div>
|
|
979
990
|
|
|
980
991
|
<!-- Billing details (next billing, amount) -->
|
|
981
|
-
<div id="billing-details" class="mb-3
|
|
992
|
+
<div id="billing-details" class="mb-3" data-wm-bind="@show billing.details.visible" hidden>
|
|
993
|
+
<div class="row small text-muted">
|
|
994
|
+
<div class="col-sm-6 mb-1">
|
|
995
|
+
<strong>Next billing:</strong> <span data-wm-bind="@text billing.details.nextDate"></span>
|
|
996
|
+
</div>
|
|
997
|
+
<div class="col-sm-6 mb-1">
|
|
998
|
+
<strong>Amount:</strong> <span data-wm-bind="@text billing.details.amount"></span>
|
|
999
|
+
</div>
|
|
1000
|
+
</div>
|
|
1001
|
+
</div>
|
|
982
1002
|
|
|
983
1003
|
<!-- State-specific alerts -->
|
|
984
|
-
<div
|
|
1004
|
+
<div class="mb-3">
|
|
1005
|
+
<!-- Suspended (payment issue) alert -->
|
|
1006
|
+
<div class="alert alert-danger d-flex align-items-start" data-wm-bind="@show billing.alerts.suspended" hidden>
|
|
1007
|
+
<span class="me-2 flex-shrink-0">{% uj_icon "triangle-exclamation", "fa-md" %}</span>
|
|
1008
|
+
<div>
|
|
1009
|
+
<strong>Payment failed</strong>
|
|
1010
|
+
<p class="mb-0 small">Your payment method was declined. Please update your payment method to keep your subscription active.</p>
|
|
1011
|
+
</div>
|
|
1012
|
+
</div>
|
|
1013
|
+
|
|
1014
|
+
<!-- Cancellation pending alert -->
|
|
1015
|
+
<div class="alert alert-warning d-flex align-items-start" data-wm-bind="@show billing.alerts.cancelling" hidden>
|
|
1016
|
+
<span class="me-2 flex-shrink-0">{% uj_icon "clock", "fa-md" %}</span>
|
|
1017
|
+
<div>
|
|
1018
|
+
<strong>Cancellation scheduled</strong>
|
|
1019
|
+
<p class="mb-0 small">Your subscription will end on <strong data-wm-bind="@text billing.alerts.cancelDate"></strong>. You'll continue to have full access until then. If you change your mind, you can reactivate through billing management.</p>
|
|
1020
|
+
</div>
|
|
1021
|
+
</div>
|
|
1022
|
+
|
|
1023
|
+
<!-- Free trial alert -->
|
|
1024
|
+
<div class="alert alert-success d-flex align-items-start" data-wm-bind="@show billing.alerts.trialing" hidden>
|
|
1025
|
+
<span class="me-2 flex-shrink-0">{% uj_icon "circle-check", "fa-md" %}</span>
|
|
1026
|
+
<div>
|
|
1027
|
+
<strong>Free trial</strong>
|
|
1028
|
+
<p class="mb-0 small" data-wm-bind="@show billing.alerts.trialHasEndDate" hidden>You're on a free trial that ends on <strong data-wm-bind="@text billing.alerts.trialEndDate"></strong>. You won't be charged until your trial ends.</p>
|
|
1029
|
+
<p class="mb-0 small" data-wm-bind="@hide billing.alerts.trialHasEndDate">You're on a free trial. You won't be charged until your trial ends.</p>
|
|
1030
|
+
</div>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
985
1033
|
|
|
986
1034
|
<!-- Action Buttons -->
|
|
987
1035
|
<div class="d-grid d-sm-flex gap-2">
|
|
988
|
-
<button id="upgrade-plan-btn" class="btn btn-success plan-action-btn" data-wm-bind="@show
|
|
1036
|
+
<button id="upgrade-plan-btn" class="btn btn-success plan-action-btn" data-wm-bind="@show billing.buttons.upgrade" hidden>
|
|
989
1037
|
{% uj_icon "crown", "me-2" %}
|
|
990
1038
|
<span class="button-text">Upgrade Plan</span>
|
|
991
1039
|
</button>
|
|
992
|
-
<button id="change-plan-btn" class="btn btn-outline-adaptive plan-action-btn" data-wm-bind="@show
|
|
1040
|
+
<button id="change-plan-btn" class="btn btn-outline-adaptive plan-action-btn" data-wm-bind="@show billing.buttons.change" hidden>
|
|
993
1041
|
{% uj_icon "arrow-right-arrow-left", "me-2" %}
|
|
994
1042
|
<span class="button-text">Change Plan</span>
|
|
995
1043
|
</button>
|
|
996
|
-
<button id="manage-billing-btn" class="btn btn-primary plan-action-btn" data-wm-bind="@show
|
|
1044
|
+
<button id="manage-billing-btn" class="btn btn-primary plan-action-btn" data-wm-bind="@show billing.buttons.manage" hidden>
|
|
997
1045
|
{% uj_icon "credit-card", "me-2" %}
|
|
998
1046
|
<span class="button-text">Manage Billing</span>
|
|
999
1047
|
</button>
|
|
@@ -1012,7 +1060,7 @@ badges:
|
|
|
1012
1060
|
</div>
|
|
1013
1061
|
|
|
1014
1062
|
<!-- Cancel Subscription (only visible for paid users) -->
|
|
1015
|
-
<div id="cancel-subscription-trigger" class="text-center" data-wm-bind="@show
|
|
1063
|
+
<div id="cancel-subscription-trigger" class="text-center" data-wm-bind="@show billing.buttons.cancel" hidden>
|
|
1016
1064
|
<button type="button" class="btn btn-link btn-sm text-muted text-decoration-underline cancel-trigger-link" data-bs-toggle="collapse" data-bs-target="#cancel-subscription-accordion" aria-expanded="false" aria-controls="cancel-subscription-accordion">
|
|
1017
1065
|
Cancel subscription
|
|
1018
1066
|
</button>
|
|
@@ -307,7 +307,7 @@ build_info:
|
|
|
307
307
|
</div>
|
|
308
308
|
<div>
|
|
309
309
|
<div class="text-muted small mb-1">Last Build</div>
|
|
310
|
-
<div class="fw-semibold"><span id="build-time">—</span> <span id="build-time-ago" class="text-muted small"></span></div>
|
|
310
|
+
<div class="fw-semibold"><span id="build-time">—</span> <span id="build-time-ago" class="text-muted small ms-1"></span></div>
|
|
311
311
|
</div>
|
|
312
312
|
</div>
|
|
313
313
|
</div>
|