PageSourceSearch

https://www.zendrop.com/wp-content/plugins/zendrop-elementor-blocks/assets/js/multistep.js?ver=3.5.5

js zendrop.com collected 2026-10-02 14:40:19 UTC 39,397 bytes, 1,150 lines download raw bytes

1(function ($) {
2  $(document).ready(function () {
3    // Get user information from Zendrop account API
4    async function getUserInfo() {
5      try {
6        const response = await fetch("https://account.zendrop.com/api/user", {
7          method: "GET",
8          credentials: "include",
9          redirect: "error",
10        });
11
12        if (!response.ok) {
13          throw new Error("Network response was not ok");
14        }
15        return await response.json();
16      } catch (error) {
17        // Return fallback values for local development
18        return {
19          // id: "test_user_123",
20          // email: "[email protected]",
21          // name: "Test User",
22        };
23      }
24    }
25
26    // Check if user has completed multistep form
27    function checkMultistepFormStatus(userEmail) {
28      return new Promise((resolve, reject) => {
29        // Check if zendrop_ajax is available
30        if (typeof zendrop_ajax === "undefined") {
31          resolve({ completed: false, declined: false }); // Default to false if AJAX not available
32          return;
33        }
34
35        if (!userEmail) {
36          resolve({ completed: false, declined: false });
37          return;
38        }
39
40        const ajaxData = {
41          action: "zendrop_check_multistep_status",
42          security: zendrop_ajax.nonce,
43          user_email: userEmail,
44        };
45
46        $.ajax({
47          url: '/wp-admin/admin-ajax.php',
48          type: "POST",
49          data: ajaxData,
50          success: function (response) {
51            if (response.success) {
52              resolve(response.data);
53            } else {
54              resolve({ completed: false, declined: false }); // Default to false on error
55            }
56          },
57          error: function () {
58            resolve({ completed: false, declined: false }); // Default to false on error
59          },
60        });
61      });
62    }
63
64    // Check upsell container and handle form redirection
65    async function checkUpsellContainerAndRedirect() {
66      // Check if upsell-container element exists
67      const upsellContainer = document.getElementById("upsell-container");
68
69      if (!upsellContainer) {
70        // Element doesn't exist, do nothing
71        return;
72      }
73
74      try {
75        // First request: Get user information
76        const userInfo = await getUserInfo();
77
78        if (!userInfo || !userInfo.email) {
79          const upsellLoader = document.getElementById("upsell-loader");
80
81          if (upsellLoader) {
82            upsellLoader.style.display = "none";
83          }
84
85          if (upsellContainer) {
86            upsellContainer.style.display = "flex";
87          }
88
89          return;
90        }
91
92        // Second request: Check if user has filled multistep form
93        const formStatus = await checkMultistepFormStatus(userInfo.email);
94
95        if (!formStatus || (!formStatus.completed && !formStatus.declined)) {
96          // User hasn't filled or declined the form, redirect to /onboarding page
97          window.location.href = "/onboarding";
98        } else {
99          const upsellLoader = document.getElementById("upsell-loader");
100          // User has completed or declined the form, show the upsell container
101          if (upsellLoader) {
102            upsellLoader.style.display = "none";
103          }
104
105          if (upsellContainer) {
106            upsellContainer.style.display = "flex";
107          }
108        }
109      } catch (error) {
110        // Silently handle error
111      }
112    }
113
114    // Run the upsell check when page loads
115    checkUpsellContainerAndRedirect();
116
117    // Initialize Multi-Step Forms
118    $(".zendrop-multistep-form-container").each(function () {
119      initMultiStepForm($(this));
120    });
121
122    function initMultiStepForm($container) {
123      const $form = $container.find(".zendrop-multistep-form");
124      const $steps = $form.find(".zendrop-form-step");
125      const $nextBtn = $form.find(".zendrop-next-btn");
126      const $prevBtn = $form.find(".zendrop-prev-btn");
127      const $submitBtn = $form.find(".zendrop-submit-btn");
128      const $progressSteps = $container.find(".zendrop-progress-step");
129      const $currentStepText = $container.find(".current-step");
130      const totalSteps = parseInt($container.data("total-steps"));
131
132      let currentStep = 1;
133      let storedAnswerArray = []; // Store answer arrays from all steps: [hubspot_field, value, question]
134      let userEmail = ""; // Store user email for step saving
135
136      // Store original button text
137      $submitBtn.data("original-text", $submitBtn.text());
138
139      // Function to collect data from ALL steps and convert to answer array format
140      function collectAllStepsData() {
141        const allAnswers = [];
142
143        // Loop through ALL steps, not just current step
144        $steps.each(function (stepIndex) {
145          const $stepDiv = $(this);
146          const stepAnswers = [];
147
148          collectStepData($stepDiv, stepAnswers);
149          allAnswers.push(...stepAnswers);
150        });
151
152        // Update stored answer array with all collected data
153        storedAnswerArray = allAnswers;
154        return allAnswers;
155      }
156
157      // Function to save current step progress to server
158      async function saveCurrentStepProgress() {
159        const $currentStepDiv = $steps.eq(currentStep - 1);
160        const stepAnswers = [];
161
162        // Collect data from current step
163        collectStepData($currentStepDiv, stepAnswers);
164
165        // Convert answer array to simple key-value format for saving
166        // Only save HubSpot field name and value, nothing else
167        const stepData = {};
168        stepAnswers.forEach((answer) => {
169          const [hubspotField, value] = answer;
170          if (hubspotField) {
171            stepData[hubspotField] = value;
172          }
173        });
174
175        // Get form ID
176        const formId = $form.data("form-id");
177
178        // Get user email if not already fetched
179        if (!userEmail) {
180          try {
181            const userInfo = await getUserInfo();
182            if (userInfo && userInfo.email) {
183              userEmail = userInfo.email;
184            }
185          } catch (error) {
186            return false;
187          }
188        }
189
190        // Check if zendrop_ajax is available
191        if (typeof zendrop_ajax === "undefined") {
192          return false;
193        }
194
195        // Save via AJAX
196        return new Promise((resolve) => {
197          $.ajax({
198            url: '/wp-admin/admin-ajax.php',
199            type: "POST",
200            data: {
201              action: "zendrop_save_step_progress",
202              security: zendrop_ajax.nonce,
203              form_id: formId,
204              step_number: currentStep,
205              step_data: stepData,
206              user_email: userEmail,
207            },
208            success: function (response) {
209              if (response.success) {
210                resolve(true);
211              } else {
212                resolve(false);
213              }
214            },
215            error: function () {
216              resolve(false);
217            },
218          });
219        });
220      }
221
222      // Helper function to collect data from a specific step div
223      function collectStepData($stepDiv, stepAnswers) {
224        // Helper function to check if field should be excluded
225        function shouldExcludeField(fieldName) {
226          if (!fieldName) return true;
227          if (fieldName.includes("widget_settings[")) return true;
228          if (fieldName.includes("form_steps[")) return true;
229          // DON'T exclude _visual fields - they contain user selections!
230          // Only exclude specific Elementor/widget configuration fields, not user answers
231          if (fieldName.startsWith("widget_settings")) return true;
232          if (fieldName.startsWith("form_steps")) return true;
233          if (fieldName.startsWith("elementor_")) return true;
234          return false;
235        }
236
237        // Helper function to get field info (hubspot mapping and question text)
238        function getFieldInfo($input) {
239          const $fieldGroup = $input.closest(".zendrop-field-group");
240          const $label = $fieldGroup.find(".zendrop-field-label");
241          const hubspotField =
242            $fieldGroup.data("hubspot-field") ||
243            $fieldGroup.attr("data-hubspot-field") ||
244            "";
245          const questionText =
246            $label.length > 0 ? $label.text().replace(/\*$/, "").trim() : "";
247
248          return { hubspotField, questionText };
249        }
250
251        // Collect ALL inputs from this step (including selects and hidden fields)
252        const $allInputs = $stepDiv.find("input, textarea, select");
253
254        $allInputs.each(function () {
255          const $input = $(this);
256          const name = $input.attr("name");
257          const type = $input.attr("type");
258          let value = $input.val();
259
260          if (shouldExcludeField(name)) {
261            return;
262          }
263
264          // Handle different input types
265          if ($input.is("select")) {
266            // Handle select elements (including hidden ones)
267            if (value && value !== "" && value !== "-- Select --") {
268              if (Array.isArray(value)) {
269                value = value.filter(
270                  (v) => v && v !== "" && v !== "-- Select --"
271                );
272                if (value.length > 0) {
273                  const { hubspotField, questionText } = getFieldInfo($input);
274                  stepAnswers.push([
275                    hubspotField,
276                    value.join(", "),
277                    questionText,
278                  ]);
279                }
280              } else {
281                const { hubspotField, questionText } = getFieldInfo($input);
282                stepAnswers.push([hubspotField, value, questionText]);
283              }
284            }
285          } else if (type === "radio") {
286            // Handle radio buttons (only if checked)
287            if ($input.is(":checked")) {
288              const { hubspotField, questionText } = getFieldInfo($input);
289              stepAnswers.push([hubspotField, value, questionText]);
290            }
291          } else if (type === "checkbox") {
292            // Handle checkboxes (only if checked)
293            if ($input.is(":checked")) {
294              const { hubspotField, questionText } = getFieldInfo($input);
295
296              // If checkbox has empty value but has a label, use the label text as value
297              let checkboxValue = value;
298              if (!checkboxValue || checkboxValue.trim() === "") {
299                const $label = $input.closest(".zendrop-option-label");
300                if ($label.length > 0) {
301                  checkboxValue = $label.text().trim();
302                }
303              }
304
305              if (checkboxValue && checkboxValue.trim() !== "") {
306                stepAnswers.push([hubspotField, checkboxValue, questionText]);
307              }
308            }
309          } else {
310            // Handle regular text inputs, textareas, etc.
311            if (value && value.trim() !== "") {
312              const { hubspotField, questionText } = getFieldInfo($input);
313              stepAnswers.push([hubspotField, value.trim(), questionText]);
314            }
315          }
316        });
317      }
318
319      // Handle conditional fields
320      function handleConditionalFields() {
321        $form.find(".conditional-field").each(function () {
322          const $field = $(this);
323          const parentFieldId = $field.data("parent-field");
324          const triggerValues = $field
325            .data("trigger-value")
326            .toString()
327            .split(",")
328            .map((v) => v.trim());
329          // Look for parent field - check hidden select first, then visual inputs, then regular inputs
330          let $parentField = $form.find(`#${parentFieldId}_hidden_select`);
331
332          if ($parentField.length === 0) {
333            $parentField = $form.find(`select[name="${parentFieldId}"]`);
334          }
335
336          if ($parentField.length === 0) {
337            $parentField = $form.find(`input[name="${parentFieldId}_visual"]`);
338          }
339
340          if ($parentField.length === 0) {
341            $parentField = $form.find(
342              `input[name="${parentFieldId}"], #${parentFieldId}`
343            );
344          }
345
346          if ($parentField.length === 0) return;
347
348          function checkVisibility() {
349            let parentValue = "";
350
351            if ($parentField.is("select")) {
352              // Hidden select field
353              parentValue = $parentField.val();
354              if (Array.isArray(parentValue)) {
355                parentValue = parentValue.join(",");
356              }
357            } else if ($parentField.is('input[type="radio"]')) {
358              // Visual radio buttons
359              parentValue = $parentField.filter(":checked").val() || "";
360            } else if ($parentField.is('input[type="checkbox"]')) {
361              // Visual checkboxes
362              const checkedValues = $parentField
363                .filter(":checked")
364                .map(function () {
365                  return $(this).val();
366                })
367                .get();
368              parentValue = checkedValues.join(",");
369            } else {
370              // Regular text inputs
371              parentValue = $parentField.val() || "";
372            }
373
374            const shouldShow = triggerValues.some((triggerValue) => {
375              if (parentValue.includes(",")) {
376                return parentValue.split(",").includes(triggerValue);
377              }
378              return parentValue === triggerValue;
379            });
380
381            if (shouldShow) {
382              $field.slideDown(300, function () {
383                updateButtonState(); // Update button state when field becomes visible
384
385                // Capture data from newly visible fields that have values
386                $field
387                  .find(
388                    "input:checked, input[value!=''], textarea[value!=''], select"
389                  )
390                  .each(function () {
391                    const $input = $(this);
392                    const name = $input.attr("name");
393                    let value = "";
394
395                    if ($input.is(":radio") || $input.is(":checkbox")) {
396                      if ($input.is(":checked")) {
397                        value = $input.val();
398                      }
399                    } else if ($input.is("select")) {
400                      value = $input.val();
401                      if (Array.isArray(value)) {
402                        value = value.filter(
403                          (v) => v && v !== "" && v !== "-- Select --"
404                        );
405                      }
406                    } else {
407                      value = $input.val();
408                    }
409
410                    // If field has value, add it to stored answers
411                    if (
412                      value &&
413                      (Array.isArray(value)
414                        ? value.length > 0
415                        : value.toString().trim() !== "")
416                    ) {
417                      const $fieldGroup = $input.closest(
418                        ".zendrop-field-group"
419                      );
420                      const $label = $fieldGroup.find(".zendrop-field-label");
421                      const hubspotField =
422                        $fieldGroup.data("hubspot-field") ||
423                        $fieldGroup.attr("data-hubspot-field") ||
424                        "";
425                      const questionText =
426                        $label.length > 0
427                          ? $label.text().replace(/\*$/, "").trim()
428                          : "";
429
430                      const processedValue = Array.isArray(value)
431                        ? value.join(", ")
432                        : value.toString().trim();
433                      const newAnswer = [
434                        hubspotField,
435                        processedValue,
436                        questionText,
437                      ];
438
439                      // Check if this answer already exists
440                      const exists = storedAnswerArray.some(
441                        (stored) =>
442                          stored[0] === newAnswer[0] &&
443                          stored[2] === newAnswer[2]
444                      );
445                      if (!exists) {
446                        storedAnswerArray.push(newAnswer);
447                      }
448                    }
449                  });
450              });
451            } else {
452              $field.slideUp(300, function () {
453                updateButtonState(); // Update button state when field becomes hidden
454              });
455              // Clear field values when hidden
456              $field.find("input, select, textarea").val("");
457              $field
458                .find('input[type="checkbox"], input[type="radio"]')
459                .prop("checked", false);
460            }
461          }
462
463          // Initial check
464          checkVisibility();
465
466          // Listen for changes on both visible and hidden fields
467          $parentField.on("change input", checkVisibility);
468
469          // Also listen to hidden select if we found visual inputs
470          if ($parentField.is("input")) {
471            const hiddenSelectId = parentFieldId + "_hidden_select";
472            const $hiddenSelect = $form.find(`#${hiddenSelectId}`);
473            if ($hiddenSelect.length > 0) {
474              $hiddenSelect.on("change", checkVisibility);
475            }
476          }
477        });
478      }
479
480      // Initialize conditional fields
481      handleConditionalFields();
482
483      // Function to update hidden select fields based on visual input changes
484      function updateHiddenSelect($input, $targetSelect, inputType) {
485        const value = $input.val();
486        const isChecked = $input.is(":checked");
487
488        if (inputType === "radio") {
489          // For radio buttons, set the select value directly
490          if (isChecked) {
491            $targetSelect.val(value);
492          }
493        } else if (inputType === "checkbox") {
494          // For checkboxes, handle multiple selections
495          let currentValues = $targetSelect.val() || [];
496          if (!Array.isArray(currentValues)) {
497            currentValues = currentValues ? [currentValues] : [];
498          }
499
500          if (isChecked) {
501            // Add value if not already present
502            if (!currentValues.includes(value)) {
503              currentValues.push(value);
504            }
505          } else {
506            // Remove value if present
507            currentValues = currentValues.filter((v) => v !== value);
508          }
509
510          $targetSelect.val(currentValues);
511        }
512
513        // Trigger change event on the hidden select to update any dependent logic
514        $targetSelect.trigger("change");
515      }
516
517      // Handle custom radio/checkbox styling and hidden field updates
518      function handleCustomOptions() {
519        // Add visual styling for selected options and update hidden fields
520        $form.on(
521          "change",
522          "input[type='radio'], input[type='checkbox']",
523          function () {
524            const $input = $(this);
525            const $label = $input.closest(".zendrop-option-label");
526            const inputType = $input.attr("type");
527            const targetSelectId = $input.data("target-select");
528
529            // Update hidden select field if this is a visual input
530            if (targetSelectId) {
531              const $targetSelect = $form.find(`#${targetSelectId}`);
532              if ($targetSelect.length > 0) {
533                updateHiddenSelect($input, $targetSelect, inputType);
534              }
535            }
536
537            if (inputType === "radio") {
538              // Clear other radio buttons in the same group
539              const inputName = $input.attr("name");
540              const $radioGroup = $form.find(`input[name="${inputName}"]`);
541
542              $radioGroup
543                .closest(".zendrop-option-label")
544                .removeClass("selected");
545
546              // If this input has a target select, clear other radio buttons' target selects in the same group
547              if (targetSelectId) {
548                $radioGroup.not($input).each(function () {
549                  const otherTargetSelectId = $(this).data("target-select");
550                  if (
551                    otherTargetSelectId &&
552                    otherTargetSelectId !== targetSelectId
553                  ) {
554                    const $otherTargetSelect = $form.find(
555                      `#${otherTargetSelectId}`
556                    );
557                    if ($otherTargetSelect.length > 0) {
558                      $otherTargetSelect.val("").trigger("change");
559                    }
560                  }
561                });
562              }
563
564              // Select this one if checked
565              if ($input.is(":checked")) {
566                $label.addClass("selected");
567              }
568            } else if (inputType === "checkbox") {
569              // Toggle checkbox styling
570              $label.toggleClass("selected", $input.is(":checked"));
571            }
572          }
573        );
574
575        // Initialize selected states and hidden field values
576        $form
577          .find("input:checked")
578          .closest(".zendrop-option-label")
579          .addClass("selected");
580
581        // Initialize hidden select values based on pre-checked inputs
582        $form
583          .find("input[type='radio']:checked, input[type='checkbox']:checked")
584          .each(function () {
585            const $input = $(this);
586            const targetSelectId = $input.data("target-select");
587            if (targetSelectId) {
588              const $targetSelect = $form.find(`#${targetSelectId}`);
589              if ($targetSelect.length > 0) {
590                updateHiddenSelect($input, $targetSelect, $input.attr("type"));
591              }
592            }
593          });
594      }
595
596      // Initialize custom options
597      handleCustomOptions();
598
599      // Check if current step has required fields and update button state
600      function updateButtonState() {
601        const isValid = validateCurrentStep();
602
603        if (currentStep === totalSteps) {
604          // Last step - update submit button
605          $submitBtn.prop("disabled", !isValid);
606        } else {
607          // Not last step - update next button
608          $nextBtn.prop("disabled", !isValid);
609        }
610      }
611
612      // Listen for changes in form fields to update button state
613      function initButtonStateTracking() {
614        $form.on("input change", "input, textarea, select", function () {
615          updateButtonState();
616        });
617
618        // Also listen for custom option changes
619        $form.on("click", ".zendrop-option-label", function () {
620          setTimeout(updateButtonState, 10);
621        });
622      }
623
624      // Initialize button state tracking
625      initButtonStateTracking();
626
627      // Validate current step
628      function validateCurrentStep() {
629        const $currentStepDiv = $steps.eq(currentStep - 1);
630        let isValid = true;
631
632        // Check regular required fields (visible inputs, textareas)
633        const $requiredFields = $currentStepDiv
634          .find("input[required], textarea[required]")
635          .filter(":visible");
636
637        $requiredFields.each(function () {
638          const $field = $(this);
639          const fieldType = $field.attr("type");
640
641          // Clear previous error styling
642          $field.removeClass("error");
643
644          if (fieldType === "radio") {
645            const name = $field.attr("name");
646            const $radioGroup = $currentStepDiv.find(`input[name="${name}"]`);
647            if (!$radioGroup.is(":checked")) {
648              $radioGroup.addClass("error");
649              isValid = false;
650            }
651          } else if (fieldType === "checkbox") {
652            const name = $field.attr("name");
653            const $checkboxGroup = $currentStepDiv.find(
654              `input[name="${name}"]`
655            );
656            if (!$checkboxGroup.is(":checked")) {
657              $checkboxGroup.addClass("error");
658              isValid = false;
659            }
660          } else {
661            if (!$field.val().trim()) {
662              $field.addClass("error");
663              isValid = false;
664            }
665          }
666        });
667
668        // Check required radio and checkbox groups
669        $currentStepDiv
670          .find(".zendrop-field-options[data-field-type]")
671          .each(function () {
672            const $fieldOptions = $(this);
673            const fieldType = $fieldOptions.data("field-type");
674            const $requiredInputs = $fieldOptions.find("input[required]");
675
676            if ($requiredInputs.length > 0) {
677              const $allInputs = $fieldOptions.find("input");
678
679              if (fieldType === "multiselect") {
680                // For multiselect, check if at least one checkbox is checked
681                if (!$allInputs.is(":checked")) {
682                  $allInputs.closest(".zendrop-option-label").addClass("error");
683                  isValid = false;
684                }
685              } else if (fieldType === "select") {
686                // For single select, check if a radio is selected
687                if (!$allInputs.is(":checked")) {
688                  $allInputs.closest(".zendrop-option-label").addClass("error");
689                  isValid = false;
690                }
691              }
692            }
693          });
694
695        return isValid;
696      }
697
698      // Update progress bar
699      function updateProgress() {
700        $currentStepText.text(currentStep);
701
702        // Update progress steps
703        $progressSteps.each(function (index) {
704          const $step = $(this);
705          const stepNumber = index + 1;
706
707          $step.removeClass("active completed");
708
709          if (stepNumber < currentStep) {
710            $step.addClass("completed");
711          } else if (stepNumber === currentStep) {
712            $step.addClass("active");
713          }
714        });
715      }
716
717      // Show specific step
718      function showStep(stepNumber) {
719        $steps.removeClass("active");
720        $steps.eq(stepNumber - 1).addClass("active");
721
722        // Update button visibility (previous button always hidden/
722disabled)
723        $prevBtn.hide();
724
725        if (stepNumber === totalSteps) {
726          $nextBtn.hide();
727          $submitBtn.show();
728        } else {
729          $nextBtn.show();
730          $submitBtn.hide();
731        }
732
733        updateProgress();
734        handleConditionalFields(); // Re-check conditional fields
735        handleCustomOptions(); // Re-initialize custom option styling
736        updateButtonState(); // Update button state for new step
737      }
738
739      // Next button click
740      $nextBtn.on("click", async function (e) {
741        e.preventDefault();
742
743        if (validateCurrentStep()) {
744          // Collect data from all steps before moving to next step
745          collectAllStepsData();
746
747          // Save current step progress
748          await saveCurrentStepProgress();
749
750          if (currentStep < totalSteps) {
751            currentStep++;
752            showStep(currentStep);
753          }
754        } else {
755          // Show validation message
756          showValidationMessage("Please fill in all required fields.");
757        }
758      });
759
760      // Function to decline form
761      async function declineForm() {
762        // Collect data from all steps first
763        collectAllStepsData();
764
765        // Get user email if not already fetched
766        if (!userEmail) {
767          try {
768            const userInfo = await getUserInfo();
769            if (userInfo && userInfo.email) {
770              userEmail = userInfo.email;
771            }
772          } catch (error) {
773            return false;
774          }
775        }
776
777        // Check if zendrop_ajax is available
778        if (typeof zendrop_ajax === "undefined") {
779          return false;
780        }
781
782        // Get form ID
783        const formId = $form.data("form-id");
784
785        // Convert answer array to simple key-value format for saving
786        const stepData = {};
787        storedAnswerArray.forEach((answer) => {
788          const [hubspotField, value] = answer;
789          if (hubspotField) {
790            stepData[hubspotField] = value;
791          }
792        });
793
794        // Save partial data and declined status via AJAX
795        return new Promise((resolve) => {
796          $.ajax({
797            url: '/wp-admin/admin-ajax.php',
798            type: "POST",
799            data: {
800              action: "zendrop_save_step_progress",
801              security: zendrop_ajax.nonce,
802              form_id: formId,
803              step_number: currentStep,
804              step_data: stepData,
805              user_email: userEmail,
806            },
807            success: function (response) {
808              // After saving partial data, mark as declined
809              $.ajax({
810                url: '/wp-admin/admin-ajax.php',
811                type: "POST",
812                data: {
813                  action: "zendrop_decline_form",
814                  security: zendrop_ajax.nonce,
815                  user_email: userEmail,
816                },
817                success: function (response) {
818                  resolve(response.success);
819                },
820                error: function () {
821                  resolve(false);
822                },
823              });
824            },
825            error: function () {
826              $.ajax({
827                url: '/wp-admin/admin-ajax.php',
828                type: "POST",
829                data: {
830                  action: "zendrop_decline_form",
831                  security: zendrop_ajax.nonce,
832                  user_email: userEmail,
833                },
834                success: function (response) {
835                  resolve(response.success);
836                },
837                error: function () {
838                  resolve(false);
839                },
840              });
841            },
842          });
843        });
844      }
845
846      // Close button click (if exists)
847      const $closeBtn = $container.find(".zendrop-form-close-btn");
848      $closeBtn.on("click", async function (e) {
849        e.preventDefault();
850
851        // Save declined status before redirecting
852        await declineForm();
853
854        // Get widget settings for close button URL
855        let widgetSettings = {};
856        try {
857          const settingsData = $container.data("widget-settings");
858          if (settingsData) {
859            widgetSettings =
860              typeof settingsData === "string"
861                ? JSON.parse(settingsData)
862                : settingsData;
863          }
864        } catch (e) {
865          // If parsing fails, use history.back()
866          window.history.back();
867          return;
868        }
869
870        const closeButtonUrl = widgetSettings.close_button_url;
871
872        if (closeButtonUrl && closeButtonUrl.trim() !== "") {
873          // Redirect to custom URL
874          window.location.href = closeButtonUrl;
875        } else {
876          // Redirect back to previous page
877          window.history.back();
878        }
879      });
880
881      // Previous button click (disabled)
882      $prevBtn.on("click", function (e) {
883        e.preventDefault();
884        // Previous button is disabled - no functionality
885        return false;
886      });
887
888      // Form submission
889      $form.on("submit", async function (e) {
890        e.preventDefault(); // Always prevent default form submission
891
892        if (!validateCurrentStep()) {
893          showValidationMessage("Please fill in all required fields.");
894          return false;
895        }
896
897        // Show loading state
898        $submitBtn.prop("disabled", true).text("Submitting...");
899
900        try {
901          // Get user information from Zendrop API
902          const userInfo = await getUserInfo();
903
904          // Collect data from all steps for final submission
905          collectAllStepsData();
906
907          // Add Zendrop user information to answer array
908          if (userInfo) {
909            // Add user ID
910            const userIdAnswer = [
911              "zendrop_user_id",
912              userInfo.id,
913              "Zendrop user id",
914            ];
915            const emailAnswer = ["email", userInfo.email, "Email"];
916
917            // Check if these don't already exist
918            const userIdExists = storedAnswerArray.some(
919              (stored) => stored[0] === "zendrop_user_id"
920            );
921            const emailExists = storedAnswerArray.some(
922              (stored) => stored[0] === "email"
923            );
924
925            if (!userIdExists) {
926              storedAnswerArray.push(userIdAnswer);
927            }
928            if (!emailExists) {
929              storedAnswerArray.push(emailAnswer);
930            }
931          }
932
933          // Deduplicate answers by HubSpot field name (keep the last occurrence)
934          const deduplicatedAnswers = [];
935          const seenFields = new Map();
936
937          // Process in reverse to keep last occurrence
938          for (let i = storedAnswerArray.length - 1; i >= 0; i--) {
939            const [hubspotField, value, questionText] = storedAnswerArray[i];
940
941            // Use hubspot field as key, or question text if no hubspot field
942            const key = hubspotField || questionText;
943
944            if (key && !seenFields.has(key)) {
945              seenFields.set(key, true);
946              deduplicatedAnswers.unshift([hubspotField, value, questionText]);
947            }
948          }
949
950          // Submit via AJAX
951          submitFormAjax(deduplicatedAnswers);
952        } catch (error) {
953          // Restore button state
954          $submitBtn
955            .prop("disabled", false)
956            .text($submitBtn.data("original-text") || "Submit");
957          showValidationMessage(
958            "Error retrieving user information. Please try again."
959          );
960        }
961      });
962
963      // AJAX form submission
964      function submitFormAjax(answerArray) {
965        // Check if zendrop_ajax is available
966        if (typeof zendrop_ajax === "undefined") {
967          showValidationMessage(
968            "Form configuration error. Please refresh the page and try again."
969          );
970          $submitBtn
971            .prop("disabled", false)
972            .text($submitBtn.data("original-text") || "Submit");
973          return;
974        }
975
976        // Get form ID
977        const formId = $form.data("form-id");
978
979        // Process the answer array (it's already in the correct format)
980        // Just apply auto-mapping for fields without HubSpot mapping
981
982        // Auto-map common fields if no HubSpot field is specified
983        const processedAnswers = answerArray.map((answer) => {
984          let [hubspotField, value, questionText] = answer;
985
986          // Auto-map common fields if no HubSpot field is specified
987          if (!hubspotField) {
988            if (
989              questionText.toLowerCase().includes("email") ||
990              (typeof value === "string" && value.includes("@"))
991            ) {
992              hubspotField = "email";
993            } else if (
994              questionText.includes("zendrop_user_id") ||
995              questionText.includes("Zendrop user id")
996            ) {
997              hubspotField = "zendrop_user_id";
998            } else if (
999              questionText.toLowerCase().includes("name") &&
1000              !questionText.toLowerCase().includes("company")
1001            ) {
1002              hubspotField = "firstname";
1003            } else if (questionText.toLowerCase().includes("company")) {
1004              hubspotField = "company";
1005            } else if (questionText.toLowerCase().includes("phone")) {
1006              hubspotField = "phone";
1007            }
1008          }
1009
1010          return [hubspotField, value, questionText];
1011        });
1012
1013        // Create clean submission data with processed answer array
1014        const submissionData = {
1015          form_id: formId,
1016          answers: processedAnswers,
1017        };
1018
1019        $.ajax({
1020          url: '/wp-admin/admin-ajax.php',
1021          type: "POST",
1022          data: {
1023            action: "zendrop_submit_questionare",
1024            security: zendrop_ajax.nonce,
1025            submission: submissionData,
1026          },
1027          success: function (response) {
1028            if (response.success) {
1029              let message =
1030                response.data.message || "Form submitted successfully!";
1031
1032              // Add HubSpot sync status to message
1033              if (response.data.hubspot) {
1034                if (response.data.hubspot.success) {
1035                  message += " Contact synced to HubSpot.";
1036                }
1037              }
1038
1039              // Handle redirect if configured, otherwise show success message
1040              const hasRedirect = checkForRedirect();
1041              if (hasRedirect) {
1042                handleSuccessRedirect();
1043              } else {
1044                showSuccessMessage(message);
1045              }
1046            } else {
1047              showValidationMessage(
1048                response.data.message ||
1049                  "Form submission failed. Please try again."
1050              );
1051            }
1052          },
1053          error: function () {
1054            showValidationMessage("Form submission failed. Please try again.");
1055          },
1056          complete: function () {
1057            // Restore button state
1058            $submitBtn
1059              .prop("disabled", false)
1060              .text($submitBtn.data("original-text") || "Submit");
1061          },
1062        });
1063      }
1064
1065      // Check if redirect is configured
1066      function checkForRedirect() {
1067        let widgetSettings = {};
1068        try {
1069          const settingsData = $container.data("widget-settings");
1070          if (settingsData) {
1071            widgetSettings =
1072              typeof settingsData === "string"
1073                ? JSON.parse(settingsData)
1074                : settingsData;
1075          }
1076        } catch (e) {
1077          return false;
1078        }
1079
1080        const redirectUrl = widgetSettings.success_redirect_url;
1081        return redirectUrl && redirectUrl.trim() !== "";
1082      }
1083
1084      // Handle success redirect
1085      function handleSuccessRedirect() {
1086        // Get widget settings for redirect configuration
1087        let widgetSettings = {};
1088        try {
1089          const settingsData = $container.data("widget-settings");
1090          if (settingsData) {
1091            widgetSettings =
1092              typeof settingsData === "string"
1093                ? JSON.parse(settingsData)
1094                : settingsData;
1095          }
1096        } catch (e) {
1097          return;
1098        }
1099
1100        const redirectUrl = widgetSettings.success_redirect_url;
1101
1102        if (redirectUrl && redirectUrl.trim() !== "") {
1103          // Always redirect immediately
1104          window.location.href = redirectUrl;
1105        }
1106      }
1107
1108      // Show validation message
1109      function showValidationMessage(message) {
1110        // Remove existing messages
1111        $container.find(".zendrop-form-message").remove();
1112
1113        const $message = $(
1114          `<div class="zendrop-form-message error">${message}</div>`
1115        );
1116        $form.prepend($message);
1117
1118        setTimeout(function () {
1119          $message.fadeOut(function () {
1120            $message.remove();
1121          });
1122        }, 3000);
1123      }
1124
1125      // Show success message
1126      function showSuccessMessage(message) {
1127        // Remove existing messages
1128        $container.find(".zendrop-form-message").remove();
1129
1130        const $message = $(
1131          `<div class="zendrop-form-message success">${message}</div>`
1132        );
1133        $form.prepend($message);
1134
1135        // Don't reset form automatically - let user navigate away or manually reset
1136        setTimeout(function () {
1137          $message.fadeOut(function () {
1138            $message.remove();
1139          });
1140        }, 3000);
1141      }
1142
1143      // Initialize first step
1144      showStep(currentStep);
1145
1146      // Set initial button state
1147      updateButtonState();
1148    }
1149  });
1150})(jQuery);

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.