| 1 |
/** |
| 2 |
* Cancellation feedback modal. |
| 3 |
* |
| 4 |
* Intercepts the Cancel action link on the single-subscription page, shows a |
| 5 |
* feedback modal, records the customer's reason + comment via AJAX, then follows |
| 6 |
* the original secure cancel URL. Recording is best-effort: if the request fails, |
| 7 |
* the cancellation still proceeds. |
| 8 |
* |
| 9 |
* Backing out is reported too. Dismissing the modal - Keep subscription, the X, |
| 10 |
* the overlay or Escape - is a retention event the store owner wants to hear |
| 11 |
* about, so it posts a save before hiding. Also fire-and-forget: a failed report |
| 12 |
* must never trap the customer in a modal they asked to close. |
| 13 |
*/ |
| 14 |
(function () { |
| 15 |
"use strict"; |
| 16 |
|
| 17 |
document.addEventListener("DOMContentLoaded", function () { |
| 18 |
var modal = document.getElementById("subscrpt-feedback-modal"); |
| 19 |
if (!modal) { |
| 20 |
return; |
| 21 |
} |
| 22 |
|
| 23 |
var cancelLink = document.querySelector(".subscrpt_action_buttons a.cancel"); |
| 24 |
if (!cancelLink) { |
| 25 |
return; |
| 26 |
} |
| 27 |
|
| 28 |
var cancelUrl = cancelLink.getAttribute("href"); |
| 29 |
var confirmBtn = document.getElementById("subscrpt-feedback-confirm"); |
| 30 |
var reported = false; |
| 31 |
|
| 32 |
/** |
| 33 |
* Open the modal. |
| 34 |
* |
| 35 |
* @param {Event} e Click event. |
| 36 |
*/ |
| 37 |
function openModal(e) { |
| 38 |
e.preventDefault(); |
| 39 |
modal.hidden = false; |
| 40 |
document.body.style.overflow = "hidden"; |
| 41 |
var firstRadio = modal.querySelector('input[name="subscrpt_feedback_reason"]'); |
| 42 |
if (firstRadio) firstRadio.focus(); |
| 43 |
} |
| 44 |
|
| 45 |
/** |
| 46 |
* Post the modal's current state to a wp-ajax action. |
| 47 |
* |
| 48 |
* @param {string} action The wp_ajax action name. |
| 49 |
* @param {Object} [extra] Extra key/value pairs to send. |
| 50 |
* @return {Promise} Resolves when the request settles. |
| 51 |
*/ |
| 52 |
function post(action, extra) { |
| 53 |
var checked = modal.querySelector('input[name="subscrpt_feedback_reason"]:checked'); |
| 54 |
var commentEl = document.getElementById("subscrpt-feedback-comment"); |
| 55 |
|
| 56 |
var params = new URLSearchParams(); |
| 57 |
params.append("action", action); |
| 58 |
params.append("nonce", subscrptCancellationFeedback.nonce); |
| 59 |
params.append("subscription_id", modal.getAttribute("data-subscription") || ""); |
| 60 |
params.append("reason_key", checked ? checked.value : ""); |
| 61 |
params.append("comment", commentEl ? commentEl.value : ""); |
| 62 |
|
| 63 |
if (extra) { |
| 64 |
Object.keys(extra).forEach(function (key) { |
| 65 |
params.append(key, extra[key]); |
| 66 |
}); |
| 67 |
} |
| 68 |
|
| 69 |
return fetch(subscrptCancellationFeedback.ajaxUrl, { |
| 70 |
method: "POST", |
| 71 |
credentials: "same-origin", |
| 72 |
headers: { "Content-Type": "application/x-www-form-urlencoded" }, |
| 73 |
body: params.toString(), |
| 74 |
}); |
| 75 |
} |
| 76 |
|
| 77 |
/** |
| 78 |
* Close the modal without cancelling, reporting the save on the way out. |
| 79 |
* |
| 80 |
* Reported once per page view: a customer who opens and closes the modal |
| 81 |
* repeatedly is one save, not several. |
| 82 |
*/ |
| 83 |
function closeModal() { |
| 84 |
if (!reported) { |
| 85 |
reported = true; |
| 86 |
post("subscrpt_record_cancellation_save").catch(function () {}); |
| 87 |
} |
| 88 |
modal.hidden = true; |
| 89 |
document.body.style.overflow = ""; |
| 90 |
} |
| 91 |
|
| 92 |
/** |
| 93 |
* Navigate to the original cancel URL. |
| 94 |
*/ |
| 95 |
function proceed() { |
| 96 |
window.location.href = cancelUrl; |
| 97 |
} |
| 98 |
|
| 99 |
/** |
| 100 |
* Swap the offer step for the reasons step. |
| 101 |
*/ |
| 102 |
function showReasons() { |
| 103 |
modal.querySelectorAll("[data-subscrpt-offer-step]").forEach(function (el) { |
| 104 |
el.hidden = true; |
| 105 |
}); |
| 106 |
modal.querySelectorAll("[data-subscrpt-reason-step]").forEach(function (el) { |
| 107 |
el.hidden = false; |
| 108 |
}); |
| 109 |
} |
| 110 |
|
| 111 |
var claimBtn = modal.querySelector("[data-subscrpt-offer-claim]"); |
| 112 |
var declineBtn = modal.querySelector("[data-subscrpt-offer-decline]"); |
| 113 |
var offerResult = modal.querySelector("[data-subscrpt-offer-result]"); |
| 114 |
|
| 115 |
if (declineBtn) { |
| 116 |
declineBtn.addEventListener("click", showReasons); |
| 117 |
} |
| 118 |
|
| 119 |
if (claimBtn) { |
| 120 |
claimBtn.addEventListener("click", function () { |
| 121 |
claimBtn.disabled = true; |
| 122 |
|
| 123 |
// A claimed offer is a save, reported by the endpoint itself. |
| 124 |
reported = true; |
| 125 |
|
| 126 |
post("subscrpt_claim_cancellation_offer") |
| 127 |
.then(function (res) { |
| 128 |
return res.json(); |
| 129 |
}) |
| 130 |
.then(function (data) { |
| 131 |
if (!data || !data.success || !data.data || !data.data.code) { |
| 132 |
throw new Error("no offer"); |
| 133 |
} |
| 134 |
if (offerResult) { |
| 135 |
offerResult.textContent = data.data.code; |
| 136 |
offerResult.hidden = false; |
| 137 |
} |
| 138 |
claimBtn.hidden = true; |
| 139 |
if (declineBtn) { |
| 140 |
declineBtn.textContent = subscrptCancellationFeedback.doneLabel || "Done"; |
| 141 |
declineBtn.removeEventListener("click", showReasons); |
| 142 |
declineBtn.addEventListener("click", function () { |
| 143 |
window.location.reload(); |
| 144 |
}); |
| 145 |
} |
| 146 |
}) |
| 147 |
.catch(function () { |
| 148 |
// No offer could be issued - never strand the customer on a step |
| 149 |
// that cannot complete. |
| 150 |
claimBtn.disabled = false; |
| 151 |
reported = false; |
| 152 |
showReasons(); |
| 153 |
}); |
| 154 |
}); |
| 155 |
} |
| 156 |
|
| 157 |
cancelLink.addEventListener("click", openModal); |
| 158 |
|
| 159 |
var dismissEls = modal.querySelectorAll("[data-subscrpt-feedback-dismiss]"); |
| 160 |
for (var i = 0; i < dismissEls.length; i++) { |
| 161 |
dismissEls[i].addEventListener("click", closeModal); |
| 162 |
} |
| 163 |
|
| 164 |
// Close on Escape, but only while this modal is open. |
| 165 |
document.addEventListener("keydown", function (e) { |
| 166 |
if (e.key === "Escape" && !modal.hidden) { |
| 167 |
closeModal(); |
| 168 |
} |
| 169 |
}); |
| 170 |
|
| 171 |
if (confirmBtn) { |
| 172 |
confirmBtn.addEventListener("click", function () { |
| 173 |
confirmBtn.disabled = true; |
| 174 |
|
| 175 |
// Cancelling is not a save: stop closeModal reporting one if the page |
| 176 |
// tears down mid-navigation. |
| 177 |
reported = true; |
| 178 |
|
| 179 |
post("subscrpt_record_cancellation_feedback").then(proceed).catch(proceed); |
| 180 |
}); |
| 181 |
} |
| 182 |
}); |
| 183 |
})(); |
| 184 |
|