1import Swiper from "https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.esm.browser.min.js"; 2 3// config 4const studioBaseApi = true 5 ? "https://apis.studio.marketersmedia.com" 6 : "http://localhost:8080"; 7 8const fsCheckoutSecureApi = studioBaseApi + "/billing/fs-checkout"; 9 10// globals 11let plan = "executive"; 12let prx = 5; 13let planChosen = false; 14let fsGeneratedPackageId = null; 15let fsPurchaseCompleted = false; 16 17// packageIdMapper[plan][prx] 18const packageIdMapper = { 19 professional: { 20 1: 966, 21 5: 2996, 22 25: 2999, 23 }, 24 executive: { 25 1: 967, 26 5: 2997, 27 25: 3000, 28 }, 29 director: { 30 1: 968, 31 5: 2998, 32 25: 3001, 33 }, 34}; 35 36let hostedFieldInstance; 37 38// setup 39const firebaseApp = firebase.initializeApp({ 40 apiKey: "AIzaSyAScvAditJVKqnfvjDSXrxRL9nqUwAxHUo", 41 authDomain: "marketersmedia-studio.firebaseapp.com", 42 projectId: "marketersmedia-studio", 43 appId: "1:459236083887:web:e88b0a3fd283b9b27dec5a", 44}); 45 46const auth = firebaseApp.auth(); 47 48// common 49const setLoader = (text, loading = true) => { 50 if (loading) { 51 document.getElementById("process-loader").classList.remove("hidden"); 52 document.getElementById("loader-text").innerHTML = text; 53 } else { 54 document.getElementById("process-loader").classList.add("hidden"); 55 } 56}; 57 58const setAuthorizedState = async (result) => { 59 if (result.additionalUserInfo.isNewUser) { 60 const idToken = await auth.currentUser.getIdToken(); 61 62 $.ajax({ 63 url: studioBaseApi + "/auth/create-client", 64 type: "POST", 65 contentType: "application/json", 66 beforeSend: (xhr) => { 67 xhr.setRequestHeader("Authorization", "Bearer " + idToken); 68 }, 69 data: JSON.stringify({ 70 fname: result.user?.displayName || result.user?.email, 71 lname: "", 72 invitationId: "", 73 }), 74 success: (resp) => { 75 console.log({ resp }); 76 }, 77 error: (err) => { 78 console.error(err); 79 alert("Something went wrong!"); 80 }, 81 }); 82 } 83}; 84 85// FastSpring callbacks 86window.dataPopupWebhookReceived = function (data) { 87 if ( 88 data && 89 (data.completed === true || 90 data.status === "success" || 91 data.orderComplete === true) 92 ) { 93 fsPurchaseCompleted = true; 94 window.location.href = "https://studio.marketersmedia.com/"; 95 } else if (data && data.error) { 96 alert("There was a problem processing your payment. Please try again."); 97 } 98}; 99 100window.dataPopupClosed = function () { 101 if (!fsGeneratedPackageId || fsPurchaseCompleted) return; 102 auth.currentUser?.getIdToken().then((idToken) => { 103 if (!idToken) return; 104 $.ajax({ 105 url: studioBaseApi + "/billing/remove-package", 106 type: "POST", 107 beforeSend: (xhr) => 108 xhr.setRequestHeader("Authorization", "Bearer " + idToken), 109 contentType: "application/json", 110 data: JSON.stringify({ packageId: fsGeneratedPackageId }), 111 }); 112 }); 113}; 114 115const triggerFastSpring = async (idToken) => { 116 const packageId = packageIdMapper[plan][prx]; 117 const fsProductPath = `credit-${plan}`; 118 119 let payload = "", 120 key = ""; 121 122 await new Promise((resolve) => { 123 $.ajax({ 124 url: fsCheckoutSecureApi, 125 type: "POST", 126 beforeSend: (xhr) => 127 xhr.setRequestHeader("Authorization", "Bearer " + idToken), 128 contentType: "application/json", 129 data: JSON.stringify({ 130 packages: [{ id: packageId, quantity: 1 }], 131 productPath: fsProductPath, 132 exclusiveDealCid: packageId, 133 }), 134 success: (result) => { 135 console.log("[FS] fs-checkout raw response:", result); 136 if (result.secure?.payload) { 137 try { 138 console.log( 139 "[FS] payload decoded:", 140 JSON.parse(atob(result.secure.payload)), 141 ); 142 } catch (e) { 143 console.log("[FS] payload (raw):", result.secure.payload); 144 } 145 } 146 if (result.secure?.payload && result.secure?.key) { 147 payload = result.secure.payload; 148 key = result.secure.key; 149 fsGeneratedPackageId = result.packageId; 150 } 151 resolve(); 152 }, 153 error: () => { 154 alert("Something went wrong. Please try again."); 155 resolve(); 156 }, 157 }); 158 }); 159
160 if (!payload || !key) { 161 return; 162 } 163 164 await new Promise((resolve, reject) => { 165 console.log({ payload, key }); 166 fastspring.builder.secure(payload, key, (data) => { 167 if (data.error) return reject(data.error); 168 resolve(); 169 }); 170 }); 171 172 fastspring.builder.checkout(); 173 planChosen = false; 174}; 175 176// account 177const setupAccountAndBraintree = () => { 178 $("#btn-sell, .btn-checkout").on("click", () => { 179 $("html, body").animate( 180 { scrollTop: $("#first-pp-table").offset().top }, 181 600, 182 ); 183 }); 184 185 auth.onAuthStateChanged(async (authState) => { 186 if (authState && authState.uid && planChosen) { 187 const idToken = await auth.currentUser.getIdToken(); 188 $("#popup-container").addClass("hidden"); 189 await triggerFastSpring(idToken); 190 } 191 }); 192 193 $(".table-add-to-cart-btn").on("click", async function () { 194 plan = $(this).attr("data-plan"); 195 planChosen = true; 196 197 const priceInfo = PR_PACKAGES[prx][plan]; 198 const originalPrice = PACKAGE_ORI_PRICE_PR[plan]; 199 const totalPrice = priceInfo.total; 200 const fullOriPrice = originalPrice * prx; 201 202 $(".pp-package").text(plan.charAt(0).toUpperCase() + plan.slice(1)); 203 $(".pp-ori-total-price").text("$" + fullOriPrice.toLocaleString()); 204 $(".pp-prx").text(prx); 205 $(".pp-discount").text(function () { 206 const savedAmount = fullOriPrice - totalPrice; 207 return "$" + (savedAmount > 0 ? savedAmount.toLocaleString() : 0); 208 }); 209 $(".pp-total").text("$" + totalPrice.toLocaleString()); 210 211 const currentUser = auth.currentUser; 212 if (currentUser) { 213 const idToken = await currentUser.getIdToken(); 214 await triggerFastSpring(idToken); 215 } else { 216 $("#popup-container").removeClass("hidden"); 217 } 218 }); 219 220 // Close popup when close button is clicked 221 $(".close-btn").on("click", function () { 222 $("#popup-container").addClass("hidden"); 223 }); 224 225 // Close popup when clicking on the overlay 226 $("#popup-container").on("click", function (event) { 227 console.log(""); 228 // Only close if clicking directly on the overlay (not on popup content) 229 if (event.target == this) { 230 $(this).addClass("hidden"); 231 } 232 }); 233 234 // - switch panel 235 $("#bi-switch-signin").on("click", () => { 236 $("#signup").addClass("hidden"); 237 $("#signin").removeClass("hidden"); 238 }); 239 $("#bi-switch-signup").on("click", () => { 240 $("#signin").addClass("hidden"); 241 $("#signup").removeClass("hidden"); 242 }); 243 244 // google-signin 245 $(".bi-google-button").on("click", async () => { 246 try { 247 setLoader("Signing in...", true); 248 249 const provider = new firebase.auth.GoogleAuthProvider(); 250 const userCredential = await auth.signInWithPopup(provider); 251 setAuthorizedState(userCredential); 252 } catch (error) { 253 console.error(error); 254 alert("Something went wrong"); 255 } finally { 256 setLoader("", false); 257 } 258 }); 259 260 // signup 261 $("#bi-signup-btn").on("click", async () => { 262 try { 263 setLoader("Signing up...", true); 264 265 const email = $("#bi-signup-email").val(); 266 const password = $("#bi-signup-password").val(); 267 const confirmPassword = $("#bi-signup-confirm-password").val(); 268 269 if (!email || !password) { 270 alert("Please fill in required fields."); 271 return; 272 } 273 274 if (password !== confirmPassword) { 275 alert( 276 "Please make sure the password and confirm password are the same. ", 277 ); 278 return; 279 } 280 281 const result = await auth.createUserWithEmailAndPassword(email, password); 282 283 const isNewUser = 284 result.user.metadata.creationTime === 285 result.user.metadata.lastSignInTime; 286 287 if (isNewUser) { 288 dataLayer.push({ 289 event: "new_user_signup_pp", 290 email: email, 291 }); 292 } 293 294 setAuthorizedState(result); 295 } catch (error) { 296 console.error(error); 297 298 alert("Something went wrong"); 299 setLoader("", false); 300 } 301 }); 302 303 // login 304 window.loginStudio = (recaptchaResponse) => { 305 setLoader("Signing in...", true); 306 307 const email = $("#bi-signin-email").val().trim(); 308 const password = $("#bi-signin-password").val(); 309 310 if (!email || !password) { 311 alert("Please fill in required fields."); 312 return; 313 } 314 315 $.ajax({ 316 url: studioBaseApi + "/auth/login", 317 type: "POST", 318 contentType: "application/json", 319 data: JSON.stringify({ recaptchaResponse, email, password }), 320 success: (result) => { 321 if (!result.success && result.error) { 322 grecaptcha.reset(1);
323 setLoader("", false); 324 325 switch (result.error) { 326 case "auth/user-not-found": 327 alert("Email not found. Please sign up with a new account. "); 328 break; 329 330 default: 331 alert("Error: " + result.error); 332 break; 333 } 334 } 335 336 if (result.success) { 337 auth 338 .signInWithEmailAndPassword(email, password) 339 .then((result) => { 340 setAuthorizedState(result); 341 }) 342 .catch((error) => { 343 console.error(error); 344 grecaptcha.reset(1); 345 setLoader("", false); 346 347 if (error.code) { 348 switch (error.code) { 349 case "auth/wrong-password": 350 alert("Password is wrong. Please try again."); 351 break; 352 353 default: 354 alert( 355 `Something went wrong. Please contact us. (${error.code})`, 356 ); 357 break; 358 } 359 } 360 }); 361 } 362 }, 363 error: (error) => { 364 grecaptcha.reset(1); 365 alert("Something went wrong"); 366 }, 367 }); 368 }; 369 370 // signout 371 // $('#signout-btn').on('click', async () => { 372 // setLoader('Signing out...', true); 373 // await auth.signOut(); 374 // setLoader('', false); 375 // }); 376}; 377 378function addAgencySolutionOnClickListener() { 379 document 380 .getElementById("agency-solution-btn") 381 .addEventListener("click", () => { 382 window.location.href = "https://marketersmedia.com/why-agency"; 383 }); 384} 385 386// Logistic to extend FAQ card 387function addFaqButtonOnClickListener() { 388 const btns = document.getElementsByClassName("faq-header-btn"); 389 390 for (let i = 0; i < btns.length; i++) { 391 btns.item(i).addEventListener("click", () => extendFAQ(i + 1)); 392 } 393} 394 395function getFirstElmentByClassName(object, className) { 396 return object.getElementsByClassName(className).item(0); 397} 398 399function extendFAQ(faq_no) { 400 let card = getFirstElmentByClassName(document, "faq" + String(faq_no)); 401 // Changing style of the card 402 card.classList.remove("faq-card-minimize"); 403 card.classList.add("faq-card-extend"); 404 405 // Change visibility of components 406 let icons = getFirstElmentByClassName( 407 card, 408 "faq-header-icon", 409 ).getElementsByTagName("img"); 410 icons.item(0).classList.add("hidden"); 411 icons.item(1).classList.remove("hidden"); 412 getFirstElmentByClassName(card, "faq-card-desc").style.display = "block"; 413 414 // Remove onclickListener and add new one to 415 let btn = getFirstElmentByClassName(card, "faq-header-btn"); 416 let newBtn = btn.cloneNode(true); 417 newBtn.addEventListener("click", () => minimizeFAQ(faq_no)); 418 card.replaceChild(newBtn, btn); 419} 420 421function minimizeFAQ(faq_no) { 422 let card = getFirstElmentByClassName(document, "faq" + String(faq_no)); 423 // Changing style of the card 424 card.classList.remove("faq-card-extend"); 425 card.classList.add("faq-card-minimize"); 426 427 // Change visibility of components 428 let icons = getFirstElmentByClassName( 429 card, 430 "faq-header-icon", 431 ).getElementsByTagName("img"); 432 icons.item(0).classList.remove("hidden"); 433 icons.item(1).classList.add("hidden"); 434 getFirstElmentByClassName(card, "faq-card-desc").style.display = "none"; 435 436 // Remove onclickListener and add new one to 437 let btn = getFirstElmentByClassName(card, "faq-header-btn"); 438 let newBtn = btn.cloneNode(true); 439 newBtn.addEventListener("click", () => extendFAQ(faq_no)); 440 card.replaceChild(newBtn, btn); 441} 442 443// // Swiper 444const swiper = new Swiper(".swiper", { 445 // Optional parameters 446 direction: "horizontal", 447 loop: true, 448 navigation: { 449 nextEl: ".slideshow-right-btn", 450 prevEl: ".slideshow-left-btn", 451 }, 452 breakpoints: { 453 // when window width is >= 320px 454 320: { 455 slidesPerView: 1, 456 }, 457 1024: { 458 slidesPerView: 2, 459 }, 460 }, 461}); 462 463// Logistic for number of PR switch 464const PACKAGE_ORI_PRICE_PR = { 465 professional: 189, 466 executive: 389, 467 director: 989, 468}; 469 470const PACKAGE_1_PR = { 471 professional: { 472 new_price: 189, 473 total: 189, 474 }, 475 executive: { 476 new_price: 389, 477 total: 389, 478 }, 479 director: { 480 new_price: 989, 481 total: 989, 482 }, 483}; 484
485const PACKAGE_5_PR = { 486 professional: { 487 new_price: 169, 488 total: 845, 489 }, 490 executive: { 491 new_price: 350, 492 total: 1750, 493 }, 494 director: { 495 new_price: 929, 496 total: 4645, 497 }, 498}; 499 500// TO-DO Update the price to real one 501const PACKAGE_25_PR = { 502 professional: { 503 new_price: 139, 504 total: 3475, 505 }, 506 executive: { 507 new_price: 300, 508 total: 7500, 509 }, 510 director: { 511 new_price: 890, 512 total: 22250, 513 }, 514}; 515 516const PR_PACKAGES = { 517 0: PACKAGE_ORI_PRICE_PR, 518 1: PACKAGE_1_PR, 519 5: PACKAGE_5_PR, 520 25: PACKAGE_25_PR, 521}; 522 523const NUM_PR_BUTTON = []; 524 525function loadPRSwitch() { 526 const buttons = document 527 .getElementById("num-PR-switch") 528 .getElementsByTagName("button"); 529 for (var i = 0; i < buttons.length; i++) { 530 // Get important values 531 let button = buttons.item(i); 532 533 // Add on click listener 534 button.addEventListener("click", () => onClickPRSwitch(button)); 535 536 // Add to constant 537 NUM_PR_BUTTON.push(button); 538 } 539 540 // Select the 1st button 541 onClickPRSwitch(NUM_PR_BUTTON[2]); 542} 543 544function updateSwitchColour(button) { 545 // Update everything to unselected style 546 for (let i = 0; i < NUM_PR_BUTTON.length; i++) { 547 NUM_PR_BUTTON[i].classList.remove("pr-num-button-selected"); 548 NUM_PR_BUTTON[i].classList.add("pr-num-button-not-selected"); 549 } 550 551 // Update selected button 552 button.classList.remove("pr-num-button-not-selected"); 553 button.classList.add("pr-num-button-selected"); 554} 555 556function onClickPRSwitch(button) { 557 var num_of_PR = Number(button.textContent); 558 console.log({ num_of_PR }); 559 setPackagePrice(num_of_PR); 560 561 prx = num_of_PR; 562 563 updateSwitchColour(button); 564} 565 566function setPackagePrice(n) { 567 const packages = ["professional", "executive", "director"]; 568 569 for (var i = 0; i < packages.length; i++) { 570 // Calculations for values to be displayed 571 let name = packages[i]; 572 let new_price = PR_PACKAGES[n][name].new_price; 573 let total = PR_PACKAGES[n][name].total; 574 const oriTotal = PACKAGE_ORI_PRICE_PR[name] * n; 575 let save = n > 1 ? Math.round(((oriTotal - total) / oriTotal) * 100) : 0; 576 577 // Get UI components required 578 let save_box = document.getElementById(name + "-save-box"); 579 let save_text = document.getElementById(name + "-save-price"); 580 let ori_price_text = document.getElementById(name + "-ori-price"); 581 let new_price_text = document.getElementById(name + "-new-price"); 582 let total_text = document.getElementById(name + "-total-price"); 583 584 // Change UI based on saved amount 585 if (save === 0) { 586 // Hide elements and remove stroke 587 save_box.style.visibility = "hidden"; 588 new_price_text.style.visibility = "hidden"; 589 ori_price_text.style.textDecoration = "none"; 590 } else { 591 // Make elements visible and add stroke 592 save_box.style.visibility = "visible"; 593 new_price_text.style.visibility = "visible"; 594 ori_price_text.style.textDecoration = "line-through"; 595 596 // Update new values 597 // save_text.innerHTML = '$' + save.toLocaleString(); 598 save_text.innerHTML = save.toLocaleString() + "%"; 599 new_price_text.innerHTML = "$" + new_price.toLocaleString(); 600 } 601 602 total_text.innerHTML = "$" + total.toLocaleString(); 603 } 604} 605 606function contactFormTimer(contactSaleLink) { 607 const now = new Date();
608 const month = now.getMonth(); 609 const day = now.getDate(); 610 const year = now.getFullYear(); 611 612 // Sales Enquiry form redirection time 613 const startHour = "00"; // 12.01 am 614 const startMin = "01"; 615 const endHour = "07"; // 7.59 am 616 const endMin = "59"; 617 618 // Start and end datetime 619 const startTimeDate = new Date(year, month, day, startHour, startMin, "00"); 620 const endTimeDate = new Date(year, month, day, endHour, endMin, "00"); 621 622 // Start date and time down to millisec 623 const startTimeDateMsecs = startTimeDate.getTime(); 624 const endTimeDateMsecs = endTimeDate.getTime(); 625 626 // Current date time 627 const currentTimeDateMsecs = now.getTime(); 628 629 // If current date time between start and end date time, redirect to sales enquiry form 630 // Else open the Tawkto dialog 631 if ( 632 currentTimeDateMsecs >= startTimeDateMsecs && 633 currentTimeDateMsecs <= endTimeDateMsecs 634 ) { 635 contactSaleLink.href = "https://marketersmedia.com/sales-enquiry-form"; 636 } else { 637 contactSaleLink.href = "javascript:void(Tawk_API.toggle())"; 638 } 639} 640 641const contactSaleLinks = document.getElementsByClassName("contact-sales-link"); 642 643Array.from(contactSaleLinks).forEach((contactSaleLink) => { 644 contactSaleLink.addEventListener("click", () => { 645 contactFormTimer(contactSaleLink); 646 }); 647}); 648 649function main() { 650 addAgencySolutionOnClickListener(); 651 setupAccountAndBraintree(); 652 loadPRSwitch(); 653 addFaqButtonOnClickListener(); 654} 655 656main();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.