🏠 Home 

Freshdesk Enhancement

add buttons: open user's stripe account, mixpanel page

// ==UserScript==
// @name        Freshdesk Enhancement
// @scriptURL   https://greasyfork.org/en/scripts/486895-freshdesk-enhancement
// @namespace   Violentmonkey Scripts
// @match       https://*.freshdesk.com/a/tickets/*
// @grant       none
// @version     1.5
// @author      GorvGoyl
// @supportURL  https://github.com/gorvGoyl/
// @description add buttons: open user's stripe account, mixpanel page
// @license MIT
// ==/UserScript==
async function init() {
console.log("adding buttons");
const existingButton = await getElement(
'button[data-test-actions="tktDelete"]'
);
console.log("got reference btn");
addStripeBtn(existingButton);
addMixpanelBtn(existingButton);
copyEmailBtn(existingButton);
const styles = `.ticket_note>div>.gmail_quote>blockquote.gmail_quote {
max-height: 200px;
overflow: auto;
background: #f5f5f4;
border-radius: 6px;
}`;
addCSS(styles);
}
function addStripeBtn(existingButton) {
const id = "stripeAccountBtn";
const btn = document.getElementById(id);
if (btn) return;
const newButton = document.createElement("button");
newButton.id = id;
newButton.innerHTML = "Stripe";
newButton.style.border = "1px solid gray";
newButton.style.padding = "4px 8px";
newButton.style.borderRadius = "4px";
newButton.style.margin = "0px 4px";
newButton.addEventListener("click", openStripeAccount);
newButton.setAttribute("title", "Open Stripe Account");
existingButton.parentNode.insertBefore(newButton, existingButton.nextSibling);
}
function addMixpanelBtn(existingButton) {
const id = "mixpanelUserPageBtn";
const btn = document.getElementById(id);
if (btn) return;
const newButton = document.createElement("button");
newButton.id = id;
newButton.innerHTML = "Mixpanel";
newButton.style.border = "1px solid gray";
newButton.style.padding = "4px 8px";
newButton.style.borderRadius = "4px";
newButton.style.margin = "0px 4px";
newButton.addEventListener("click", openMixpanelPage);
newButton.setAttribute("title", "Open mixpanel users page");
existingButton.parentNode.insertBefore(newButton, existingButton.nextSibling);
}
function copyEmailBtn(existingButton) {
const id = "copyEmailBtn";
const btn = document.getElementById(id);
if (btn) return;
const newButton = document.createElement("button");
newButton.id = id;
newButton.innerHTML = "Copy Email";
newButton.style.border = "1px solid gray";
newButton.style.padding = "4px 8px";
newButton.style.borderRadius = "4px";
newButton.style.margin = "0px 4px";
newButton.addEventListener("click", copyEmail);
newButton.setAttribute("title", "Copy email");
existingButton.parentNode.insertBefore(newButton, existingButton.nextSibling);
}
async function openStripeAccount(e) {
console.log("opening stripe account");
e.preventDefault();
const email = parseEmail();
showToast("email copied: " + email);
const url =
`https://dashboard.stripe.com/search?query=` + encodeURIComponent(email);
window.open(url, "stripeWindow");
}
async function copyEmail(e) {
e.preventDefault();
const email = parseEmail();
await navigator.clipboard.writeText(email);
showToast("email copied: " + email);
}
async function openMixpanelPage(e) {
console.log("opening mixpanel page...");
e.preventDefault();
await copyEmail(e);
const url = `https://mixpanel.com/project/2952645/view/3474349/app/users#CRgQ4fCX6zKj`;
window.open(url, "mixpanelWindow");
}
function parseEmail() {
const email = document
.querySelector(
"div.info-details-content.text__content.text--semibold._ar_redact_"
)
.innerText.trim();
console.log("email", email);
return email;
}
window.addEventListener("urlchange", function (e) {
console.log(
"URL changed to:",
e.detail.url,
"with state:",
e.detail.state,
"via:",
e.detail.type
);
if (e.detail.url.includes("/tickets")) {
init();
}
});
init();
//-----------------------------HRLPER METHODS------------------------------//
function addCSS(css) {
const styleSheet = document.createElement("style")
styleSheet.innerText = css
document.head.appendChild(styleSheet)
}
// helper menthod: get element whenever it becomes available
function getElement(selector) {
return new Promise((resolve, reject) => {
// Check if the element already exists
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
// Create a MutationObserver to listen for changes in the DOM
const observer = new MutationObserver((mutations, observer) => {
// Check for the element again within each mutation
const element = document.querySelector(selector);
if (element) {
observer.disconnect(); // Stop observing
resolve(element);
}
});
// Start observing the document body for child list changes
observer.observe(document.body, { childList: true, subtree: true });
// Set a timeout to reject the promise if the element isn't found within 10 seconds
const timeoutId = setTimeout(() => {
observer.disconnect(); // Ensure to disconnect the observer to prevent memory leaks
resolve(null); // Resolve with null instead of rejecting to indicate the timeout without throwing an error
}, 10000); // 10 seconds
// Ensure that if the element is found and the observer is disconnected, we also clear the timeout
observer.takeRecords().forEach((record) => {
clearTimeout(timeoutId);
});
});
}
// helper menthod: detect url changes in SPA sites
(function (history) {
var originalPushState = history.pushState;
var originalReplaceState = history.replaceState;
history.pushState = function (state, title, url) {
var fullUrl = url ? window.location.origin + url : window.location.href;
var returnValue = originalPushState.apply(history, [state, title, url]);
window.dispatchEvent(
new CustomEvent("urlchange", {
detail: {
state: state,
url: fullUrl,
type: "pushState",
},
})
);
return returnValue;
};
history.replaceState = function (state, title, url) {
var fullUrl = url ? window.location.origin + url : window.location.href;
var returnValue = originalReplaceState.apply(history, [state, title, url]);
window.dispatchEvent(
new CustomEvent("urlchange", {
detail: {
state: state,
url: fullUrl,
type: "replaceState",
},
})
);
return returnValue;
};
})(window.history);
// helper function: show toast
function showToast(text) {
// Create the toast container div
const toast = document.createElement("div");
toast.textContent = text;
toast.style.position = "fixed";
toast.style.left = "50%";
toast.style.bottom = "100px";
toast.style.transform = "translateX(-50%)";
toast.style.backgroundColor = "#000000c4";
toast.style.color = "white";
toast.style.padding = "10px";
toast.style.borderRadius = "5px";
toast.style.textAlign = "center";
toast.style.zIndex = "9999";
toast.style.pointerEvents = "none"; // Make it click-through
document.body.appendChild(toast);
// Remove the toast after 3 seconds
setTimeout(() => {
document.body.removeChild(toast);
}, 3000);
}