ultimate-jekyll-manager 0.0.277 → 0.0.279

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.
@@ -74,6 +74,10 @@
74
74
  user-select: none;
75
75
  }
76
76
 
77
+ .progress {
78
+ background-color: var(--bs-tertiary-bg);
79
+ }
80
+
77
81
  .cancel-trigger-link {
78
82
  font-size: 0.8125rem;
79
83
  opacity: 0.7;
@@ -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
- updatePlanCard(account);
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,196 +58,89 @@ export function onShow() {
51
58
  // Nothing needed
52
59
  }
53
60
 
54
- // ─── Plan Card ───────────────────────────────────────────────
61
+ // ─── UI Update ──────────────────────────────────────────────
55
62
 
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);
69
-
70
- switch (status) {
71
- case 'free': {
72
- $planStatus.className = 'badge bg-secondary';
73
- $planStatus.textContent = 'Free';
74
- $planDescription.innerHTML = `You are currently on the <strong>${displayName}</strong> plan. Upgrade to unlock premium features.`;
75
- break;
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
- // ─── Alerts ──────────────────────────────────────────────────
107
-
108
- function updateAlerts(account) {
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
- // ─── Billing Details ─────────────────────────────────────────
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
- // Only show billing details for paid, non-suspended, non-cancelled subscriptions
185
- if (!isPaid || status === 'suspended' || status === 'cancelled') {
186
- $details.classList.add('d-none');
187
- return;
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';
189
91
 
92
+ const trialEndUnix = subscription.trial?.expires?.timestampUNIX;
93
+ const trialEndDate = (trialEndUnix && trialEndUnix > 0)
94
+ ? new Date(trialEndUnix * 1000).toLocaleDateString()
95
+ : null;
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
- if (!nextBillingUnix || nextBillingUnix <= 0 || !amount) {
196
- $details.classList.add('d-none');
197
- return;
198
- }
199
-
200
- const nextDate = new Date(nextBillingUnix * 1000).toLocaleDateString();
201
- const formattedAmount = formatCurrency(amount, currency);
202
- const frequencyLabel = frequency === 'annually' || frequency === 'yearly' ? 'year' : 'month';
203
-
204
- $details.innerHTML = `
205
- <div class="row small text-muted">
206
- <div class="col-sm-6 mb-1">
207
- <strong>Next billing:</strong> ${nextDate}
208
- </div>
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');
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
+ };
215
140
  }
216
141
 
217
142
  // ─── Action Buttons ──────────────────────────────────────────
218
143
 
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.
222
- const subscription = account.subscription || {};
223
- const productId = getProductId(subscription);
224
- const isPaid = productId !== 'basic';
225
- const status = getEffectiveStatus(subscription, isPaid);
226
-
227
- const $upgradeBtn = document.getElementById('upgrade-plan-btn');
228
- const $changeBtn = document.getElementById('change-plan-btn');
229
- const $manageBtn = document.getElementById('manage-billing-btn');
230
- const $cancelTrigger = document.getElementById('cancel-subscription-trigger');
231
-
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', '');
241
- }
242
- }
243
-
244
144
  function setupActionButtons() {
245
145
  const $upgradeBtn = document.getElementById('upgrade-plan-btn');
246
146
  const $changeBtn = document.getElementById('change-plan-btn');
@@ -343,7 +243,16 @@ function setupCancellationForm() {
343
243
  throw new Error(response.message || 'Failed to cancel subscription. Please try again.');
344
244
  }
345
245
 
346
- cancelFormManager.showSuccess('Your subscription has been cancelled. You\'ll continue to have access until the end of your current billing period.');
246
+ // Detect trial cancellation: trial was active and trial period equals subscription period
247
+ const sub = currentAccount?.subscription;
248
+ const isTrialCancel = sub?.trial?.claimed === true
249
+ && sub?.trial?.expires?.timestampUNIX === sub?.expires?.timestampUNIX;
250
+
251
+ if (isTrialCancel) {
252
+ cancelFormManager.showSuccess('Your trial has been cancelled. You\'ve been moved to the free plan. You can subscribe again anytime.');
253
+ } else {
254
+ cancelFormManager.showSuccess('Your subscription has been cancelled. You\'ll continue to have access until the end of your current billing period.');
255
+ }
347
256
 
348
257
  // Collapse the cancel form after a short delay
349
258
  setTimeout(() => {
@@ -352,22 +261,32 @@ function setupCancellationForm() {
352
261
  const bsCollapse = bootstrap.Collapse.getInstance($accordion);
353
262
  if (bsCollapse) bsCollapse.hide();
354
263
  }
355
- }, 3000);
264
+ }, 1000);
356
265
 
357
266
  // Update the UI to reflect cancellation (using backend structure)
358
- if (currentAccount?.subscription) {
359
- const expiresUnix = currentAccount.subscription.expires?.timestampUNIX || 0;
360
- currentAccount.subscription.cancellation = {
361
- pending: true,
362
- date: {
363
- timestamp: new Date(expiresUnix * 1000).toISOString(),
364
- timestampUNIX: expiresUnix,
365
- },
366
- };
367
- updatePlanCard(currentAccount);
368
- updateAlerts(currentAccount);
369
- updateBillingDetails(currentAccount);
370
- updateActionButtons(currentAccount);
267
+ if (sub) {
268
+ if (isTrialCancel) {
269
+ // Trial cancellations are immediate — set status to cancelled
270
+ sub.status = 'cancelled';
271
+ sub.cancellation = {
272
+ pending: false,
273
+ date: {
274
+ timestamp: new Date().toISOString(),
275
+ timestampUNIX: Math.floor(Date.now() / 1000),
276
+ },
277
+ };
278
+ } else {
279
+ // Non-trial cancellations are pending until end of billing period
280
+ const expiresUnix = sub.expires?.timestampUNIX || 0;
281
+ sub.cancellation = {
282
+ pending: true,
283
+ date: {
284
+ timestamp: new Date(expiresUnix * 1000).toISOString(),
285
+ timestampUNIX: expiresUnix,
286
+ },
287
+ };
288
+ }
289
+ updateUI(currentAccount);
371
290
  }
372
291
  });
373
292
  }
@@ -7,9 +7,8 @@ function getBasePrice(state) {
7
7
  if (!product) return 0;
8
8
 
9
9
  if (product.type === 'subscription') {
10
- return state.frequency === 'monthly'
11
- ? (product.prices?.monthly?.amount || 0)
12
- : (product.prices?.annually?.amount || 0);
10
+ const entry = product.prices?.[state.frequency];
11
+ return (typeof entry === 'object' ? entry?.amount : entry) || 0;
13
12
  }
14
13
 
15
14
  return product.prices?.amount
@@ -21,16 +21,16 @@ export const state = {
21
21
  // Returns a fresh object every time -- no mutation of shared references
22
22
  export function buildBindingsState() {
23
23
  const isSubscription = !!state.frequency;
24
- const billingCycleText = state.frequency === 'monthly'
25
- ? 'monthly'
26
- : state.frequency === 'annually'
27
- ? 'annually'
28
- : '';
29
- const billingPeriodText = state.frequency === 'monthly'
30
- ? 'month'
31
- : state.frequency === 'annually'
32
- ? 'year'
33
- : '';
24
+
25
+ const FREQUENCY_MAP = {
26
+ daily: { cycle: 'daily', period: 'day' },
27
+ weekly: { cycle: 'weekly', period: 'week' },
28
+ monthly: { cycle: 'monthly', period: 'month' },
29
+ annually: { cycle: 'annually', period: 'year' },
30
+ };
31
+ const freq = FREQUENCY_MAP[state.frequency] || { cycle: '', period: '' };
32
+ const billingCycleText = freq.cycle;
33
+ const billingPeriodText = freq.period;
34
34
 
35
35
  // Build subscription info text
36
36
  let subscriptionInfoText = '';
@@ -345,7 +345,7 @@ function updateTimeAgo($element, timestamp) {
345
345
  const totalSeconds = Math.floor(diffMs / 1000);
346
346
 
347
347
  if (totalSeconds <= 0) {
348
- $element.textContent = 'Just now';
348
+ $element.textContent = '(Just now)';
349
349
  return;
350
350
  }
351
351
 
@@ -383,7 +383,7 @@ function updateTimeAgo($element, timestamp) {
383
383
  }
384
384
  parts.push(`${seconds}s`);
385
385
 
386
- $element.textContent = parts.join(' ') + ' ago';
386
+ $element.textContent = '(' + parts.join(' ') + ' ago)';
387
387
  }
388
388
 
389
389
  // Fetch status data from API (if configured)
@@ -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 auth.account.subscription.product.name">Current</span> plan</span>
974
- <span id="plan-status" class="badge bg-secondary">Loading...</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">&nbsp;</span>
975
975
  </h5>
976
- <p class="card-text mb-0" id="plan-description">Loading plan details...</p>
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 d-none"></div>
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 id="billing-alerts" class="mb-3"></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 auth.account.subscription.product.id === basic" hidden>
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 auth.account.subscription.product.id !== basic" hidden>
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 auth.account.subscription.product.id !== basic" hidden>
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 auth.account.subscription.product.id !== basic" hidden>
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,8 +307,7 @@ build_info:
307
307
  </div>
308
308
  <div>
309
309
  <div class="text-muted small mb-1">Last Build</div>
310
- <div id="build-time" class="fw-semibold">—</div>
311
- <div id="build-time-ago" class="text-muted small">—</div>
310
+ <div class="fw-semibold"><span id="build-time">—</span> <span id="build-time-ago" class="text-muted small ms-1"></span></div>
312
311
  </div>
313
312
  </div>
314
313
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "0.0.277",
3
+ "version": "0.0.279",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {