PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.10.4
Subscriptions for WooCommerce with Stripe Recurring Payments v1.10.4
2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
subscription / assets / js / admin / onboarding-wizard.js

onboarding-wizard.js in Subscriptions for WooCommerce with Stripe Recurring Payments 1.10.4, at assets/js/admin/onboarding-wizard.js

578 lines 19.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Onboarding Wizard — SPA-style
3 * All pages rendered at once; JS controls section visibility
4 * PHP values passed via localized script data (see Menu::enqueue_admin_assets)
5 */
6 (function ($) {
7 "use strict";
8
9 var Wizard = {
10 currentPage: 1,
11 ajaxUrl: "",
12 subscriptionsUrl: "",
13
14 init: function () {
15 this.ajaxUrl = subscrpt_wizard.ajax_url;
16 this.subscriptionsUrl = subscrpt_wizard.subscriptions_url;
17 this.bindEvents();
18 this.showRelevantProductSection();
19 this.initPageIndicator();
20 this.initLivePreview();
21 this.initStepperState();
22 },
23
24 initPageIndicator: function () {
25 this.currentPage = parseInt($("#subscrpt-current-page").text(), 10) || 1;
26 },
27
28 initStepperState: function () {
29 var page = parseInt($("#subscrpt-wizard-page").val(), 10) || 1;
30 if (page > 1) {
31 $("#subscrpt-stepper").show();
32 $(".wpsubs-wizard-stepper__step").removeClass("active done");
33 $(".wpsubs-wizard-stepper__step").each(function () {
34 var step = parseInt($(this).data("step"), 10);
35 if (step < page) {
36 $(this).addClass("done");
37 } else if (step === page) {
38 $(this).addClass("active");
39 }
40 });
41 }
42 },
43
44 bindEvents: function () {
45 // Page 1
46 $(document).on("click", "#subscrpt-btn-start", $.proxy(this.goToPage2, this));
47 $(document).on("click", "#subscrpt-btn-skip", $.proxy(this.skip, this));
48
49 // Page 2: product toggle
50 $(document).on("click", "#subscrpt-btn-create-new", $.proxy(this.showNewProduct, this));
51 $(document).on("click", "#subscrpt-btn-use-existing", $.proxy(this.showExistingProduct, this));
52
53 // Page 2: product search
54 $(document).on("focus", "#subscrpt-product-search-input", $.proxy(this.openProductSearch, this));
55 $(document).on("input", "#subscrpt-product-search-input", $.proxy(this.filterProducts, this));
56 $(document).on("click", ".p2-product-search__item", $.proxy(this.onProductItemClick, this));
57 $(document).on("click", function (e) {
58 if (!$(e.target).closest(".p2-product-search").length) {
59 $("#subscrpt-product-search-dropdown").hide();
60 }
61 });
62 $(document).on("click", "#subscrpt-btn-clear-product", $.proxy(this.clearProductSelection, this));
63 $(document).on("click", ".p2-variation-item", $.proxy(this.onVariationSelect, this));
64
65 // Page 2: navigation
66 $(document).on("click", "#subscrpt-btn-back", $.proxy(this.goToPage1, this));
67 $(document).on("click", "#subscrpt-btn-save", $.proxy(this.savePage2, this));
68
69 // Page 3
70 $(document).on("click", "#subscrpt-btn-add-another", $.proxy(this.restart, this));
71 $(document).on("click", "#subscrpt-btn-start-over", $.proxy(this.restart, this));
72 },
73
74 // ----- Page transitions -----
75
76 goToPage1: function () {
77 this.switchSection(1);
78 },
79
80 goToPage2: function () {
81 this.switchSection(2);
82 },
83
84 goToPage3: function (productId) {
85 $("#subscrpt-product-id").val(productId || 0);
86 this.switchSection(3);
87 },
88
89 switchSection: function (pageNum) {
90 this.currentPage = pageNum;
91
92 $("#subscrpt-wizard-page").val(pageNum);
93
94 if (pageNum === 1) {
95 $("#subscrpt-stepper").hide();
96 } else {
97 $("#subscrpt-stepper").show();
98 $(".wpsubs-wizard-stepper__step").removeClass("active done");
99 $(".wpsubs-wizard-stepper__step").each(function () {
100 var step = parseInt($(this).data("step"), 10);
101 if (step < pageNum) {
102 $(this).addClass("done");
103 }
104 });
105 $('.wpsubs-wizard-stepper__step[data-step="' + pageNum + '"]').addClass("active");
106 }
107
108 $(".wizard-section").removeClass("active");
109 $("#subscrpt-section-" + pageNum).addClass("active");
110 },
111
112 // ----- Page 1 actions -----
113
114 skip: function (e) {
115 e.preventDefault();
116 window.location.href = this.subscriptionsUrl;
117 },
118
119 // ----- Page 2 actions -----
120
121 showNewProduct: function (e) {
122 e.preventDefault();
123 $(".p2-option-card, .product-toggle-btn").removeClass("active");
124 $(e.currentTarget).addClass("active");
125 $("#subscrpt-existing-product-fields").hide();
126 // Restore name field editability
127 $("#subscrpt_product_name").prop("readonly", false).val("");
128 $("#subscrpt_product_price").val("");
129 $("#subscrpt-btn-save").html("Create product &rsaquo;");
130 this.updatePreview();
131 },
132
133 showExistingProduct: function (e) {
134 e.preventDefault();
135 $(".p2-option-card, .product-toggle-btn").removeClass("active");
136 $(e.currentTarget).addClass("active");
137 $("#subscrpt-existing-product-fields").show();
138 $("#subscrpt-btn-save").html("Update product &rsaquo;");
139 // If already has a selection, re-show chip
140 var selectedVal = $("#subscrpt_existing_product").val();
141 if (selectedVal) {
142 this.showProductChip($("#subscrpt_existing_product option:selected"));
143 }
144 },
145
146 openProductSearch: function () {
147 $(".p2-product-search__item").show();
148 $(".p2-product-search__empty").hide();
149 $("#subscrpt-product-search-dropdown").show();
150 },
151
152 filterProducts: function (e) {
153 var q = $(e.target).val().toLowerCase().trim();
154 $("#subscrpt-product-search-dropdown").show();
155 var visible = 0;
156 $(".p2-product-search__item").each(function () {
157 var name = $(this).data("name") ? $(this).data("name").toLowerCase() : "";
158 var sku = $(this).data("sku") ? String($(this).data("sku")).toLowerCase() : "";
159 if (!q || name.indexOf(q) >= 0 || sku.indexOf(q) >= 0) {
160 $(this).show();
161 visible++;
162 } else {
163 $(this).hide();
164 }
165 });
166 $(".p2-product-search__empty").toggle(visible === 0);
167 },
168
169 onProductItemClick: function (e) {
170 var item = $(e.currentTarget);
171 if (item.hasClass("p2-product-search__item--locked")) {
172 return;
173 }
174 var id = String(item.data("id"));
175 var name = item.data("name") || "";
176 var price = item.data("price") != null ? String(item.data("price")) : "";
177 var type = item.data("type") || "";
178 var sku = item.data("sku") ? String(item.data("sku")) : "";
179 var productType = item.data("product-type") || "";
180
181 $("#subscrpt-existing-product-hidden").val(id);
182 $("#subscrpt-product-search-dropdown").hide();
183 this.showProductChip(name, price, type, sku);
184 $("#subscrpt_product_name").val(name);
185 $("#subscrpt_product_price").val(price);
186
187 // Reset variation picker state
188 $("#subscrpt-variation-id-hidden").val("");
189
190 if (productType === "variable") {
191 // Variation picker handles subscription field autofill
192 this.fetchAndShowVariations(id);
193 } else {
194 $("#subscrpt-variation-picker-wrap").hide();
195
196 // Autofill subscription fields from product meta
197 var billingPeriod = item.data("billing-period");
198 var billingPer = item.data("billing-per");
199 var trialPer = item.data("trial-per");
200 var signupFee = item.data("signup-fee");
201
202 if (billingPeriod) {
203 this.setAdvSelectValue("#subscrpt-billing-period-select", String(billingPeriod));
204 }
205 if (billingPer) {
206 $("#subscrpt_billing_per").val(billingPer);
207 $("#subscrpt_billing_per_visible").val(billingPer);
208 }
209 if (trialPer !== undefined && trialPer !== "") {
210 $("#subscrpt_trial_timing_per").val(trialPer);
211 }
212 if (signupFee !== undefined && signupFee !== "") {
213 $("#subscrpt_signup_fee").val(signupFee);
214 }
215 }
216
217 this.updatePreview();
218 },
219
220 fetchAndShowVariations: function (productId) {
221 var self = this;
222 var wrap = $("#subscrpt-variation-picker-wrap");
223 var list = $("#subscrpt-variation-picker-list");
224
225 wrap.show();
226 list.html(
227 '<p class="p2-variation-picker__loading">' +
228 (window.subscrpt_wizard_i18n ? subscrpt_wizard_i18n.loading : "Loading variations…") +
229 "</p>",
230 );
231
232 $.post(
233 this.ajaxUrl,
234 {
235 action: "subscrpt_get_product_variations",
236 nonce: $("#subscrpt_wizard_nonce").val(),
237 product_id: productId,
238 },
239 function (response) {
240 if (response.success && response.data && response.data.length) {
241 self.renderVariationPicker(response.data);
242 } else {
243 wrap.hide();
244 }
245 },
246 ).fail(function () {
247 wrap.hide();
248 });
249 },
250
251 renderVariationPicker: function (variations) {
252 var self = this;
253 var symbol = (window.subscrpt_wizard && subscrpt_wizard.currency_symbol) || "$";
254 var html = '<p class="p2-variation-picker__label">Select a variation</p>';
255 html += '<div class="p2-variation-picker__list">';
256
257 variations.forEach(function (v) {
258 var meta = v.sku ? "SKU " + v.sku : "";
259 var priceDisplay =
260 v.price !== "" && v.price !== null && v.price !== undefined ? symbol + parseFloat(v.price).toFixed(2) : "";
261
262 html +=
263 '<div class="p2-variation-item"' +
264 ' data-id="' +
265 self.escAttr(String(v.id)) +
266 '"' +
267 ' data-label="' +
268 self.escAttr(v.label) +
269 '"' +
270 ' data-price="' +
271 self.escAttr(String(v.price !== null && v.price !== undefined ? v.price : "")) +
272 '"' +
273 ' data-sku="' +
274 self.escAttr(v.sku || "") +
275 '"' +
276 ' data-billing-period="' +
277 self.escAttr(v.billing_period || "") +
278 '"' +
279 ' data-billing-per="' +
280 self.escAttr(String(v.billing_per || "1")) +
281 '"' +
282 ' data-trial-per="' +
283 self.escAttr(String(v.trial_per !== null && v.trial_per !== undefined ? v.trial_per : "")) +
284 '"' +
285 ' data-signup-fee="' +
286 self.escAttr(String(v.signup_fee || "")) +
287 '">' +
288 '<div class="p2-variation-item__check">&#10003;</div>' +
289 '<div class="p2-variation-item__info">' +
290 '<p class="p2-variation-item__name">' +
291 self.escHtml(v.label) +
292 "</p>" +
293 (meta ? '<p class="p2-variation-item__meta">' + self.escHtml(meta) + "</p>" : "") +
294 "</div>" +
295 (priceDisplay ? '<span class="p2-variation-item__price">' + self.escHtml(priceDisplay) + "</span>" : "") +
296 "</div>";
297 });
298
299 html += "</div>";
300 $("#subscrpt-variation-picker-list").html(html);
301 $("#subscrpt-variation-picker-wrap").show();
302 },
303
304 onVariationSelect: function (e) {
305 var item = $(e.currentTarget);
306 $(".p2-variation-item").removeClass("selected");
307 item.addClass("selected");
308
309 $("#subscrpt-variation-id-hidden").val(String(item.data("id")));
310
311 // Autofill subscription fields and price from variation meta
312 var price = item.data("price");
313 var billingPeriod = item.data("billing-period");
314 var billingPer = item.data("billing-per");
315 var trialPer = item.data("trial-per");
316 var signupFee = item.data("signup-fee");
317
318 if (price !== undefined && price !== "") {
319 $("#subscrpt_product_price").val(price);
320 }
321 if (billingPeriod) {
322 this.setAdvSelectValue("#subscrpt-billing-period-select", String(billingPeriod));
323 }
324 if (billingPer) {
325 $("#subscrpt_billing_per").val(billingPer);
326 $("#subscrpt_billing_per_visible").val(billingPer);
327 }
328 if (trialPer !== undefined && trialPer !== "") {
329 $("#subscrpt_trial_timing_per").val(trialPer);
330 }
331 if (signupFee !== undefined && signupFee !== "") {
332 $("#subscrpt_signup_fee").val(signupFee);
333 }
334
335 this.updatePreview();
336 },
337
338 escHtml: function (str) {
339 return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
340 },
341
342 escAttr: function (str) {
343 return String(str)
344 .replace(/&/g, "&amp;")
345 .replace(/"/g, "&quot;")
346 .replace(/'/g, "&#39;")
347 .replace(/</g, "&lt;")
348 .replace(/>/g, "&gt;");
349 },
350
351 setAdvSelectValue: function (selector, value) {
352 var el = $(selector);
353 if (!el.length) return;
354 var menuItem = el.find('.wpsubs-adv-select__item[data-value="' + value + '"]');
355 var label = menuItem.length ? menuItem.find(".wpsubs-adv-select__item-label").text().trim() : value;
356 el.find('input[type="hidden"]').val(value);
357 el.find(".wpsubs-adv-select__label").text(label);
358 },
359
360 showProductChip: function (name, price, type, sku) {
361 var initials = name
362 .split(" ")
363 .filter(Boolean)
364 .slice(0, 2)
365 .map(function (w) {
366 return w[0].toUpperCase();
367 })
368 .join("");
369
370 var meta = [sku ? "SKU " + sku : null, type, price ? "$" + parseFloat(price).toFixed(2) : null]
371 .filter(Boolean)
372 .join(" · ");
373
374 $("#p2-chip-avatar").text(initials);
375 $("#p2-chip-name").text(name);
376 $("#p2-chip-meta").text(meta);
377 $("#subscrpt-product-select-wrap").hide();
378 $("#subscrpt-selected-product-chip").show();
379 },
380
381 clearProductSelection: function () {
382 $("#subscrpt-existing-product-hidden").val("");
383 $("#subscrpt-product-search-input").val("");
384 $(".p2-product-search__item").show();
385 $(".p2-product-search__empty").hide();
386 $("#subscrpt-product-search-dropdown").hide();
387 $("#subscrpt-selected-product-chip").hide();
388 $("#subscrpt-product-select-wrap").show();
389 $("#subscrpt_product_name").val("");
390 $("#subscrpt_product_price").val("");
391 // Reset variation picker
392 $("#subscrpt-variation-picker-wrap").hide();
393 $("#subscrpt-variation-picker-list").empty();
394 $("#subscrpt-variation-id-hidden").val("");
395 this.updatePreview();
396 },
397
398 showRelevantProductSection: function () {
399 var activeBtn = $(".product-toggle-btn.active");
400 var mode = activeBtn.length ? activeBtn.data("mode") : "new";
401
402 if (mode === "existing") {
403 $("#subscrpt-existing-product-fields").show();
404 var selectedId = $("#subscrpt-existing-product-hidden").val();
405 if (selectedId) {
406 var item = $(".p2-product-search__item[data-id='" + selectedId + "']");
407 if (item.length) {
408 var name = item.data("name") || "";
409 var price = item.data("price") != null ? String(item.data("price")) : "";
410 var type = item.data("type") || "";
411 var sku = item.data("sku") ? String(item.data("sku")) : "";
412 this.showProductChip(name, price, type, sku);
413 }
414 }
415 } else {
416 $("#subscrpt-existing-product-fields").hide();
417 }
418 },
419
420 // ----- Live preview -----
421
422 initLivePreview: function () {
423 var self = this;
424 $(document).on("input", "#subscrpt_product_name", function () {
425 self.updatePreview();
426 });
427 $(document).on("input", "#subscrpt_product_price", function () {
428 self.updatePreview();
429 });
430 $(document).on("wpsubs:select", "#subscrpt-billing-period-select", function () {
431 self.updatePreview();
432 });
433 },
434
435 updatePreview: function () {
436 var name = $("#subscrpt_product_name").val() || "Your product";
437 var price = $("#subscrpt_product_price").val() || "0.00";
438 var period = $("input[name='subscrpt_billing_period']").val() || "months";
439
440 $("#p2-preview-name").text(name);
441 $("#p2-preview-price").text(parseFloat(price).toFixed(2));
442 $("#p2-preview-period").text(period);
443 },
444
445 // ----- Save & validate -----
446
447 savePage2: function (e) {
448 e.preventDefault();
449
450 if (!this.validatePage2()) {
451 return;
452 }
453
454 var productMode = $(".product-toggle-btn.active").data("mode") || "new";
455 var confirmMsg =
456 productMode === "new"
457 ? "Create this subscription product?"
458 : "Apply these subscription settings to the selected product?";
459
460 if (!confirm(confirmMsg)) {
461 return;
462 }
463
464 var data = {
465 action: "subscrpt_save_wizard_page2",
466 nonce: $("#subscrpt_wizard_nonce").val(),
467 product_mode: $(".product-toggle-btn.active").data("mode") || "new",
468 product_name: $("#subscrpt_product_name").val(),
469 product_price: $("#subscrpt_product_price").val(),
470 existing_product_id: $("#subscrpt-existing-product-hidden").val(),
471 variation_id: $("#subscrpt-variation-id-hidden").val(),
472 timing_option: $("#subscrpt_timing_option").val(),
473 billing_per: $("#subscrpt_billing_per").val(),
474 billing_period: $("input[name='subscrpt_billing_period']").val(),
475 trial_timing_per: $("#subscrpt_trial_timing_per").val(),
476 signup_fee: $("#subscrpt_signup_fee").val(),
477 // hidden compat fields
478 trial_enabled: 0,
479 trial_timing_option: $("input[name='subscrpt_trial_timing_option']").val() || "days",
480 length_enabled: 0,
481 length_per: "",
482 length_option: "months",
483 };
484
485 var self = this;
486
487 $.post(this.ajaxUrl, data, function (response) {
488 if (response.success) {
489 window.location.reload();
490 } else {
491 alert(response.data.message || "Something went wrong. Please try again.");
492 }
493 }).fail(function () {
494 alert("Server error. Please try again.");
495 });
496 },
497
498 validatePage2: function () {
499 var isValid = true;
500 var messages = [];
501 var productMode = $(".product-toggle-btn.active").data("mode") || "new";
502 var productName = $("#subscrpt_product_name").val().trim();
503 var price = $("#subscrpt_product_price").val().trim();
504 var existing = $("#subscrpt-existing-product-hidden").val();
505 var billing = $("input[name='subscrpt_billing_period']").val();
506
507 if (productMode === "new") {
508 if (!productName) {
509 messages.push("Product name is required.");
510 isValid = false;
511 }
512 } else {
513 if (!existing) {
514 messages.push("Please select an existing product.");
515 isValid = false;
516 }
517 if (
518 existing &&
519 $("#subscrpt-variation-picker-wrap").is(":visible") &&
520 !$("#subscrpt-variation-id-hidden").val()
521 ) {
522 messages.push("Please select a variation.");
523 isValid = false;
524 }
525 }
526
527 if (!price || isNaN(parseFloat(price)) || parseFloat(price) < 0) {
528 messages.push("Please enter a valid price.");
529 isValid = false;
530 }
531
532 if (!billing) {
533 messages.push("Please select a billing period.");
534 isValid = false;
535 }
536
537 if (!isValid) {
538 alert(messages.join("\n"));
539 }
540
541 return isValid;
542 },
543
544 // ----- Page 3 actions -----
545
546 restart: function (e) {
547 e.preventDefault();
548 var self = this;
549 $.post(
550 this.ajaxUrl,
551 {
552 action: "subscrpt_reset_wizard",
553 nonce: $("#subscrpt_wizard_nonce").val(),
554 },
555 function () {
556 self.switchSection(1);
557 $("#subscrpt-current-page").text("1");
558 $("#subscrpt_product_name").val("");
559 $("#subscrpt_product_price").val("");
560 $("#subscrpt_timing_option").val("never");
561 $("#subscrpt_billing_per").val("1");
562 $("input[name='subscrpt_billing_period']").val("months");
563 $("#subscrpt_trial_timing_per").val("0");
564 $("#subscrpt_signup_fee").val("");
565 // Reset chip
566 $("#subscrpt-selected-product-chip").hide();
567 $("#subscrpt-product-select-wrap").show();
568 $("#subscrpt_existing_product").val("");
569 },
570 );
571 },
572 };
573
574 $(document).ready(function () {
575 Wizard.init();
576 });
577 })(jQuery);
578