ultimate-jekyll-manager 0.0.265 → 0.0.266

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.
@@ -1,6 +1,7 @@
1
- // Libraries
2
- import { state } from './modules/state.js';
3
- import { initializeConfirmationUI, updateAllUI } from './modules/bindings.js';
1
+ // Payment Confirmation Page
2
+ import { state, buildBindingsState } from './modules/state.js';
3
+ import { trackPurchaseIfNeeded } from './modules/tracking.js';
4
+ import { triggerCelebration } from './modules/celebration.js';
4
5
 
5
6
  let webManager = null;
6
7
 
@@ -8,215 +9,47 @@ let webManager = null;
8
9
  https://localhost:3000/payment/confirmation?orderId=ORD-TRIAL-123&productId=pro&productName=Pro%20Plan&amount=0&currency=USD&frequency=annually&paymentMethod=stripe&trial=true&track=true
9
10
  */
10
11
 
11
- // Module
12
- export default (Manager, options) => {
12
+ // Module export
13
+ export default (Manager) => {
13
14
  return new Promise(async function (resolve) {
14
- // Set webManager
15
15
  webManager = Manager.webManager;
16
-
17
- // Initialize when DOM is ready
18
16
  await webManager.dom().ready();
19
-
20
- // Initialize UI with loading states
21
- initializeConfirmationUI();
22
-
23
- // Load order data and update bindings
24
- loadOrderData();
25
-
26
- // Trigger celebration
27
- await triggerCelebration();
28
-
29
- // Resolve after initialization
17
+ await initializeConfirmation();
30
18
  return resolve();
31
19
  });
32
20
  };
33
21
 
34
- // Load order data from URL parameters
35
- function loadOrderData() {
36
- const urlParams = new URLSearchParams(window.location.search);
37
-
38
- // Parse raw data from URL
39
- const orderId = urlParams.get('orderId') || generateOrderNumber();
40
- const productId = urlParams.get('productId');
41
- const productName = urlParams.get('productName');
42
- const total = parseFloat(urlParams.get('amount') || 0);
43
- const currency = urlParams.get('currency') || 'USD';
44
- const billingCycle = urlParams.get('frequency');
45
- const paymentMethod = urlParams.get('paymentMethod');
46
- const hasFreeTrial = urlParams.get('trial') === 'true';
47
-
48
- // Build billing cycle text
49
- const billingCycleText = billingCycle === 'monthly' ? 'monthly' : billingCycle === 'annually' ? 'annually' : '';
50
- const billingPeriodText = billingCycle === 'monthly' ? 'month' : billingCycle === 'annually' ? 'year' : '';
51
-
52
- // Build subscription info text
53
- let subscriptionInfoText = '';
54
- if (billingCycle) {
55
- if (hasFreeTrial) {
56
- subscriptionInfoText = `Free Trial Active! Your trial period has begun. You'll be charged ${billingCycleText} after the trial ends.`;
57
- } else {
58
- subscriptionInfoText = `Your ${billingCycleText} subscription is now active. You'll be charged automatically each ${billingPeriodText}.`;
59
- }
60
- }
61
-
62
- // Update state directly in bindings format
63
- state.confirmation.order.id = orderId;
64
- state.confirmation.order.productName = productName || productId || 'Product';
65
- state.confirmation.order.total = `$${total.toFixed(2)}`;
66
- state.confirmation.order.currency = currency;
67
- state.confirmation.subscription.show = !!billingCycle;
68
- state.confirmation.subscription.hasFreeTrial = hasFreeTrial;
69
- state.confirmation.subscription.billingCycle = billingCycleText;
70
- state.confirmation.subscription.infoText = subscriptionInfoText;
71
- state.confirmation.loaded = true;
72
-
73
- // Update UI with complete bindings
74
- updateAllUI(webManager);
75
-
76
- // Track analytics only if not already tracked
77
- trackPurchaseIfNotTracked({
78
- orderId,
79
- productId,
80
- productName,
81
- total,
82
- currency,
83
- billingCycle,
84
- paymentMethod,
85
- hasFreeTrial
86
- });
87
- }
88
-
89
- // Generate a mock order number
90
- function generateOrderNumber() {
91
- const timestamp = Date.now();
92
- const random = Math.floor(Math.random() * 1000);
93
- return `ORD-${timestamp}-${random}`;
22
+ // Update UI via bindings (single source of truth)
23
+ function updateUI() {
24
+ webManager.bindings().update(buildBindingsState());
94
25
  }
95
26
 
96
- // Trigger celebration animation
97
- async function triggerCelebration() {
98
- try {
99
- // Load confetti library using webManager
100
- await webManager.dom().loadScript({
101
- src: 'https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js'
102
- });
103
-
104
- // Fire confetti
105
- window.confetti({
106
- particleCount: 100,
107
- spread: 70,
108
- origin: { y: 0.6 },
109
- colors: ['#5b6fff', '#8b5cf6', '#22d3ee', '#34d399', '#fb923c']
110
- });
27
+ // Initialize confirmation page
28
+ async function initializeConfirmation() {
29
+ // Parse URL params into state
30
+ parseUrlParams();
111
31
 
112
- // Optional: Fire from multiple angles for more celebration
113
- setTimeout(() => {
114
- window.confetti({
115
- particleCount: 50,
116
- angle: 60,
117
- spread: 55,
118
- origin: { x: 0 },
119
- colors: ['#5b6fff', '#8b5cf6', '#22d3ee']
120
- });
121
- }, 250);
32
+ // Update UI with loaded data
33
+ updateUI();
122
34
 
123
- setTimeout(() => {
124
- window.confetti({
125
- particleCount: 50,
126
- angle: 120,
127
- spread: 55,
128
- origin: { x: 1 },
129
- colors: ['#34d399', '#fb923c', '#5b6fff']
130
- });
131
- }, 400);
35
+ // Track purchase (if track=true param present)
36
+ // trackPurchaseIfNeeded(state, webManager);
132
37
 
133
- } catch (error) {
134
- console.log('Confetti library failed to load:', error);
135
- }
38
+ // Trigger celebration animation
39
+ await triggerCelebration(webManager);
136
40
  }
137
41
 
138
- // Track purchase only if not already tracked for this order
139
- function trackPurchaseIfNotTracked(orderData) {
42
+ // Parse URL parameters into minimal state
43
+ function parseUrlParams() {
140
44
  const urlParams = new URLSearchParams(window.location.search);
141
- const shouldTrack = urlParams.get('track') === 'true';
142
45
 
143
- // Only track if the track parameter is present and true
144
- if (!shouldTrack) {
145
- console.log('Tracking parameter not present or false, skipping tracking');
146
- return;
147
- }
148
-
149
- // Track the purchase
150
- trackPurchase(orderData);
151
-
152
- // Remove only the 'track' parameter from URL to prevent re-tracking on refresh
153
- urlParams.delete('track');
154
- const newUrl = urlParams.toString()
155
- ? `${window.location.pathname}?${urlParams.toString()}`
156
- : window.location.pathname;
157
-
158
- window.history.replaceState({}, document.title, newUrl);
159
- console.log('Removed track parameter from URL to prevent duplicate tracking');
160
-
161
- // Also store in localStorage as backup (in case user navigates back with track=true)
162
- const trackedOrders = JSON.parse(localStorage.getItem('trackedPurchases') || '[]');
163
- if (!trackedOrders.includes(orderData.orderId)) {
164
- trackedOrders.push(orderData.orderId);
165
-
166
- // Keep only last 50 orders
167
- if (trackedOrders.length > 50) {
168
- trackedOrders.shift();
169
- }
170
-
171
- localStorage.setItem('trackedPurchases', JSON.stringify(trackedOrders));
172
- }
173
- }
174
-
175
- // Track purchase event to all analytics providers
176
- function trackPurchase(orderData) {
177
- // Prepare items array for consistent tracking
178
- const items = [{
179
- item_id: orderData.productId,
180
- item_name: orderData.productName || orderData.productId,
181
- item_category: orderData.billingCycle ? 'subscription' : 'one-time',
182
- item_variant: orderData.billingCycle,
183
- price: orderData.total,
184
- quantity: 1
185
- }];
186
-
187
- // Google Analytics 4
188
- gtag('event', 'purchase', {
189
- transaction_id: orderData.orderId,
190
- value: orderData.total,
191
- currency: orderData.currency,
192
- items: items
193
- });
194
-
195
- // Facebook Pixel
196
- fbq('track', 'Purchase', {
197
- content_ids: [orderData.productId],
198
- content_name: orderData.productName || orderData.productId,
199
- content_type: 'product',
200
- currency: orderData.currency,
201
- value: orderData.total,
202
- num_items: 1
203
- });
204
-
205
- // TikTok Pixel
206
- ttq.track('CompletePayment', {
207
- content_id: orderData.productId,
208
- content_type: 'product',
209
- content_name: orderData.productName || orderData.productId,
210
- price: orderData.total,
211
- quantity: 1,
212
- currency: orderData.currency,
213
- value: orderData.total
214
- });
215
-
216
- console.log('Tracked purchase event:', {
217
- orderId: orderData.orderId,
218
- productId: orderData.productId,
219
- total: orderData.total,
220
- currency: orderData.currency
221
- });
46
+ state.orderId = urlParams.get('orderId') || '';
47
+ state.productId = urlParams.get('productId') || '';
48
+ state.productName = urlParams.get('productName') || '';
49
+ state.amount = parseFloat(urlParams.get('amount') || 0);
50
+ state.currency = urlParams.get('currency') || 'USD';
51
+ state.frequency = urlParams.get('frequency') || '';
52
+ state.paymentMethod = urlParams.get('paymentMethod') || '';
53
+ state.hasFreeTrial = urlParams.get('trial') === 'true';
54
+ state.loaded = true;
222
55
  }
@@ -0,0 +1,42 @@
1
+ // Celebration animation for confirmation page
2
+
3
+ // Trigger confetti celebration
4
+ export async function triggerCelebration(webManager) {
5
+ try {
6
+ await webManager.dom().loadScript({
7
+ src: 'https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js'
8
+ });
9
+
10
+ // Center burst
11
+ window.confetti({
12
+ particleCount: 100,
13
+ spread: 70,
14
+ origin: { y: 0.6 },
15
+ colors: ['#5b6fff', '#8b5cf6', '#22d3ee', '#34d399', '#fb923c'],
16
+ });
17
+
18
+ // Left side burst
19
+ setTimeout(() => {
20
+ window.confetti({
21
+ particleCount: 50,
22
+ angle: 60,
23
+ spread: 55,
24
+ origin: { x: 0 },
25
+ colors: ['#5b6fff', '#8b5cf6', '#22d3ee'],
26
+ });
27
+ }, 250);
28
+
29
+ // Right side burst
30
+ setTimeout(() => {
31
+ window.confetti({
32
+ particleCount: 50,
33
+ angle: 120,
34
+ spread: 55,
35
+ origin: { x: 1 },
36
+ colors: ['#34d399', '#fb923c', '#5b6fff'],
37
+ });
38
+ }, 400);
39
+ } catch (error) {
40
+ console.log('Confetti library failed to load:', error);
41
+ }
42
+ }
@@ -1,19 +1,68 @@
1
- // Confirmation state management
2
- // This is the SINGLE source of truth - structure matches bindings exactly
1
+ // State management for confirmation page
2
+ // Minimal mutable state -- everything else is derived by buildBindingsState()
3
+
4
+ // Minimal mutable state
3
5
  export const state = {
4
- confirmation: {
5
- order: {
6
- id: 'Loading...',
7
- productName: 'Product',
8
- total: '$0.00',
9
- currency: 'USD'
10
- },
11
- subscription: {
12
- show: false,
13
- hasFreeTrial: false,
14
- billingCycle: '',
15
- infoText: ''
16
- },
17
- loaded: false
6
+ // Raw values from URL params (never formatted)
7
+ orderId: '',
8
+ productId: '',
9
+ productName: '',
10
+ amount: 0,
11
+ currency: 'USD',
12
+ frequency: '',
13
+ paymentMethod: '',
14
+ hasFreeTrial: false,
15
+
16
+ // UI state
17
+ loaded: false,
18
+ };
19
+
20
+ // Build the complete bindings state from minimal state
21
+ // Returns a fresh object every time -- no mutation of shared references
22
+ export function buildBindingsState() {
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
+ : '';
34
+
35
+ // Build subscription info text
36
+ let subscriptionInfoText = '';
37
+ if (isSubscription) {
38
+ if (state.hasFreeTrial) {
39
+ subscriptionInfoText = `Free Trial Active! Your trial period has begun. You'll be charged ${billingCycleText} after the trial ends.`;
40
+ } else {
41
+ subscriptionInfoText = `Your ${billingCycleText} subscription is now active. You'll be charged automatically each ${billingPeriodText}.`;
42
+ }
18
43
  }
19
- };
44
+
45
+ return {
46
+ confirmation: {
47
+ order: {
48
+ id: state.orderId || 'Loading...',
49
+ productName: state.productName || state.productId || 'Product',
50
+ total: formatCurrency(state.amount),
51
+ currency: state.currency,
52
+ },
53
+ subscription: {
54
+ show: isSubscription,
55
+ hasFreeTrial: state.hasFreeTrial,
56
+ billingCycle: billingCycleText,
57
+ infoText: subscriptionInfoText,
58
+ },
59
+ loaded: state.loaded,
60
+ },
61
+ };
62
+ }
63
+
64
+ // Format a number as currency, or return placeholder
65
+ function formatCurrency(amount) {
66
+ if (amount == null || isNaN(amount)) return '$--';
67
+ return `$${Number(amount).toFixed(2)}`;
68
+ }
@@ -0,0 +1,83 @@
1
+ // Purchase analytics tracking for confirmation page
2
+ // All three platforms tracked together, no conditional checks
3
+
4
+ // Build common item array for tracking
5
+ function buildItems(state) {
6
+ return [{
7
+ item_id: state.productId,
8
+ item_name: state.productName || state.productId,
9
+ item_category: state.frequency ? 'subscription' : 'one-time',
10
+ item_variant: state.frequency,
11
+ price: state.amount,
12
+ quantity: 1,
13
+ }];
14
+ }
15
+
16
+ // Track purchase event to all analytics providers
17
+ function trackPurchase(state) {
18
+ const items = buildItems(state);
19
+
20
+ // Google Analytics 4
21
+ gtag('event', 'purchase', {
22
+ transaction_id: state.orderId,
23
+ value: state.amount,
24
+ currency: state.currency,
25
+ items: items,
26
+ });
27
+
28
+ // Facebook Pixel
29
+ fbq('track', 'Purchase', {
30
+ content_ids: [state.productId],
31
+ content_name: state.productName || state.productId,
32
+ content_type: 'product',
33
+ currency: state.currency,
34
+ value: state.amount,
35
+ num_items: 1,
36
+ });
37
+
38
+ // TikTok Pixel
39
+ ttq.track('CompletePayment', {
40
+ content_id: state.productId,
41
+ content_type: 'product',
42
+ content_name: state.productName || state.productId,
43
+ price: state.amount,
44
+ quantity: 1,
45
+ currency: state.currency,
46
+ value: state.amount,
47
+ });
48
+ }
49
+
50
+ // Track purchase only if the track=true URL param is present
51
+ // Removes the param after tracking to prevent duplicates on refresh
52
+ // Also stores orderId in webManager storage as a backup guard
53
+ export function trackPurchaseIfNeeded(state, webManager) {
54
+ const urlParams = new URLSearchParams(window.location.search);
55
+ const shouldTrack = urlParams.get('track') === 'true';
56
+
57
+ if (!shouldTrack) {
58
+ return;
59
+ }
60
+
61
+ // Track the purchase
62
+ trackPurchase(state);
63
+
64
+ // Remove 'track' param from URL to prevent re-tracking on refresh
65
+ urlParams.delete('track');
66
+ const newUrl = urlParams.toString()
67
+ ? `${window.location.pathname}?${urlParams.toString()}`
68
+ : window.location.pathname;
69
+ window.history.replaceState({}, document.title, newUrl);
70
+
71
+ // Backup: store orderId in storage
72
+ const trackedOrders = webManager.storage().get('trackedPurchases', []);
73
+ if (!trackedOrders.includes(state.orderId)) {
74
+ trackedOrders.push(state.orderId);
75
+
76
+ // Keep only last 50 orders
77
+ if (trackedOrders.length > 50) {
78
+ trackedOrders.shift();
79
+ }
80
+
81
+ webManager.storage().set('trackedPurchases', trackedOrders);
82
+ }
83
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "0.0.265",
3
+ "version": "0.0.266",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -17,7 +17,9 @@
17
17
  },
18
18
  "bin": {
19
19
  "uj": "bin/ultimate-jekyll",
20
- "ultimate-jekyll": "bin/ultimate-jekyll"
20
+ "ujm": "bin/ultimate-jekyll",
21
+ "ultimate-jekyll": "bin/ultimate-jekyll",
22
+ "mgr": "bin/ultimate-jekyll"
21
23
  },
22
24
  "preparePackage": {
23
25
  "input": "./src",
@@ -75,7 +77,7 @@
75
77
  "cheerio": "^1.2.0",
76
78
  "chrome-launcher": "^1.2.1",
77
79
  "dotenv": "^17.3.1",
78
- "fast-xml-parser": "^5.3.9",
80
+ "fast-xml-parser": "^5.4.1",
79
81
  "fs-jetpack": "^5.1.0",
80
82
  "glob": "^13.0.6",
81
83
  "gulp-clean-css": "^4.3.0",
@@ -91,7 +93,7 @@
91
93
  "json5": "^2.2.3",
92
94
  "libsodium-wrappers": "^0.8.2",
93
95
  "lodash": "^4.17.23",
94
- "minimatch": "^10.2.3",
96
+ "minimatch": "^10.2.4",
95
97
  "node-powertools": "^2.3.2",
96
98
  "npm-api": "^1.0.1",
97
99
  "postcss": "^8.5.6",
@@ -99,7 +101,7 @@
99
101
  "sass": "^1.97.3",
100
102
  "spellchecker": "^3.7.1",
101
103
  "through2": "^4.0.2",
102
- "web-manager": "^4.1.13",
104
+ "web-manager": "^4.1.14",
103
105
  "webpack": "^5.105.2",
104
106
  "wonderful-fetch": "^1.3.4",
105
107
  "wonderful-version": "^1.3.2",
@@ -1,28 +0,0 @@
1
- // Confirmation Bindings Module
2
- import { state } from './state.js';
3
-
4
- // Get the COMPLETE confirmation bindings object
5
- // State IS the bindings - no transformation needed
6
- export function getCompleteConfirmationBindings() {
7
- // Just return state directly - it's already structured correctly
8
- return state;
9
- }
10
-
11
- // Update all UI through bindings
12
- // ALWAYS passes the COMPLETE confirmation object
13
- export function updateAllUI(webManager) {
14
- const bindingsData = getCompleteConfirmationBindings();
15
-
16
- console.log('🔄 Updating confirmation bindings with data:', bindingsData);
17
-
18
- webManager.bindings().update(bindingsData);
19
- }
20
-
21
- // Initialize confirmation UI with loading states
22
- export function initializeConfirmationUI() {
23
- console.log('🚀 Initializing confirmation UI');
24
-
25
- // State is already initialized with default values
26
- // We'll update bindings once after data is loaded
27
- state.confirmation.loaded = false;
28
- }