PageSourceSearch

https://www.vinemapleplace.org/js/form_components.js

js vinemapleplace.org collected 2026-09-29 09:57:38 UTC 120,125 bytes, 2,889 lines download raw bytes

1function removeKeysFromDataString(dataString, keysToRemove) {
2    // Convert the query string into an array of key-value pairs
3    const params = new URLSearchParams(dataString);
4
5    // Remove specified keys
6    keysToRemove.forEach(key => {
7        if (params.has(key)) {
8            params.delete(key);
9        }
10    });
11
12    // Convert back to query string
13    return params.toString();
14}
15
16// --- Signature debug (remove this whole block when finished) ---
17// Toggle: set DEBUG_SIGNATURE_FORM = true below, or in the browser console: window.DEBUG_SIGNATURE_FORM = true
18var DEBUG_SIGNATURE_FORM = true;
19
20function isSignatureDebugEnabled() {
21    return DEBUG_SIGNATURE_FORM === true ||
22        (typeof window !== 'undefined' && window.DEBUG_SIGNATURE_FORM === true);
23}
24
25function debugLogSignatureField(info) {
26    if (!isSignatureDebugEnabled()) {
27        return;
28    }
29    console.log('[BloqForms signature]', info);
30}
31// --- end signature debug ---
32
33// Function to convert query string to JSON
34function queryStringToJson(queryString) {
35    const params = new URLSearchParams(queryString);
36    const jsonObject = {};
37    for (const [key, value] of params.entries()) {
38        // If the key already exists, convert to an array to handle duplicates
39        if (jsonObject[key]) {
40            if (!Array.isArray(jsonObject[key])) {
41                jsonObject[key] = [jsonObject[key]];
42            }
43            jsonObject[key].push(value);
44        } else {
45            jsonObject[key] = value;
46        }
47    }
48    return jsonObject;
49}
50
51// Function to toggle visibility based on rowId
52function toggleVisibility(rowId, action) {
53    const element = document.getElementById(rowId);
54    if (element) {
55        if (action === 'show') {
56            element.classList.remove('hidden');
57            
58            const requiredFields = element.querySelectorAll('[data-required="true"]');
59            requiredFields.forEach(field => {
60                field.setAttribute('required', 'required');
61            });
62
63            restoreChildRows(element);
64
65            // ONLY if signature pad exists
66            const signatureContainers = element.querySelectorAll('.signature-container');
67
68            if (signatureContainers.length > 0) {
69                setTimeout(() => {
70                    reinitializeSignaturePad(element);
71                }, 50);
72            }
73        } else if (action === 'hide') {
74            element.classList.add('hidden');
75            
76            const formFields = element.querySelectorAll('[required]');
77            formFields.forEach(field => {
78                field.removeAttribute('required');
79                
80                if (!field.hasAttribute('data-required')) {
81                    field.setAttribute('data-required', 'true');
82                }
83            });
84
85            hideChildRows(rowId);
86        }
87    }
88}
89
90function hideChildRows(parentRowId, clearReferences = true) {
91    const element = document.getElementById(parentRowId);
92    if (!element) return;
93    
94    const selects = element.querySelectorAll('select');
95    const radios = element.querySelectorAll('input[type="radio"]:checked');
96    const checkboxes = element.querySelectorAll('input[type="checkbox"]:checked');
97    
98    // Handle selects
99    selects.forEach(select => {
100        const prevRowId = select.dataset.prevRowId;
101        if (prevRowId && prevRowId !== 'none') {
102            // Hide this child row
103            const childElement = document.getElementById(prevRowId);
104            if (childElement) {
105                childElement.classList.add('hidden');
106                
107                hideChildRows(prevRowId, clearReferences);
108            }
109        }
110    });
111    
112    // Handle radios
113    radios.forEach(radio => {
114        const prevRowId = radio.dataset.prevRowId;
115        if (prevRowId && prevRowId !== 'none') {
116            // Hide this child row
117            const childElement = document.getElementById(prevRowId);
118            if (childElement) {
119                childElement.classList.add('hidden');
120                
121                hideChildRows(prevRowId, clearReferences);
122            }
123        }
124    });
125    
126    // Handle checkboxes
127    checkboxes.forEach(checkbox => {
128        const prevRowId = checkbox.dataset.prevRowId;
129        if (prevRowId && prevRowId !== 'none') {
130            // Hide this child row
131            const childElement = document.getElementById(prevRowId);
132            if (childElement) {
133                childElement.classList.add('hidden');
134                
135                hideChildRows(prevRowId, clearReferences);
136            }
137        }
138    });
139}
140
141function restoreChildRows(element) {
142    if (!element) return;
143    
144    const selects = element.querySelectorAll('select');
145    const radios = element.querySelectorAll('input[type="radio"]:checked');
146    const checkboxes = element.querySelectorAll('input[type="checkbox"]:checked');
147    
148    selects.forEach(select => {
149        const prevRowId = select.dataset.prevRowId;
150        if (prevRowId && prevRowId !== 'none') {
151            toggleVisibility(prevRowId, 'show');
152        }
153    });
154    
155    radios.forEach(radio => {
156        const prevRowId = radio.dataset.prevRowId;
157        if (prevRowId && prevRowId !== 'none') {
158            toggleVisibility(prevRowId, 'show');
159        }
160    });
161    
162    checkboxes.forEach(checkbox => {
163        const prevRowId = checkbox.dataset.prevRowId;
164        if (prevRowId && prevRowId !== 'none') {
165            toggleVisibility(prevRowId, 'show');
166        }
167    });
168}
169
170// Function to initialize form functionality
171function initializeFormBehavior(formData, bloqFormId = null) {
172    const selectRowIdMappings = {};
173    const radioRowIdMappings = {};
174    const checkboxRowIdMappings = {};
175
176    var containerForm = document.getElementById(`containerForm`);
177    if (bloqFormId) {
178        containerForm = document.getElementById(`containerForm_${bloqFormId}`);
179    }
180
181    if (formData) {
182        let initializedForms = new Set(); // Store processed form IDs
183        // Loop through form data to find 'Drop Down' or 'Radio Button' or 'Checkbox' components
184        formData.forEach(data => {
185            const jsonData = JSON.parse(data.json_data);
186
187            if (data.component_name === 'Drop Down' || data.component_name === 'Radio Button' || data.component_name === 'Checkbox') {
188                jsonData.components.forEach(component => {
189                    if (component.options) {
190                        // Iterate through each option to check the rowId
191                        component.options.forEach(option => {
192                            const rowId = option.rowId;
193                            const element = containerForm.querySelector(`#${rowId}`);
194
195                            if (rowId !== 'none') {
196                                if (element) {
197                                    // Add the hidden class to the element if rowId matches
198                                    element.classList.add('hidden');
199
200                                    const formFields = element.querySelectorAll('[required]');
201                                    formFields.forEach(field => {
202                                        field.removeAttribute('required');
203                                        
204                                        if (!field.hasAttribute('data-required')) {
205                                            field.setAttribute('data-required', 'true');
206                                        }
207                                    });
208                                }
209
210                                if (component.tagName === 'select') {
211                                    if (!selectRowIdMappings[component.name]) {
212                                        selectRowIdMappings[component.name] = [];
213                                    }
214                                    selectRowIdMappings[component.name].push([rowId, option.label]);
215                                } else if (component.tagName === 'input' && component.type === 'radio') {
216                                    radioRowIdMappings[option.name] = rowId;
217                                } else if (component.tagName === 'input' && component.type === 'checkbox') {
218                                    checkboxRowIdMappings[option.name] = rowId;
219                                }
220                            }
221                        });
222                    }
223                });
224            }
225            
226            if (data.component_name === 'Star Rating') {
227                // Initialize star rating for each form component
228                initializeStarRating();
229            }
230            
231            if (data.component_name === 'Range') {
232                // Initialize Range for each form component
233                initializeRange();
234            }
235
236            if (data.component_name === 'File Upload') {
237                // Initialize Range for each form component
238                initializeFileuploader();
239            }
240            
241            if (data.component_name === 'Donation') {
242                let choiceValues = [];
243                jsonData.components.forEach(component => {
244                    // Check if the component is of type 'donation-box'
245                    if (component.type === 'donation-box') {
246                        if (component.options) {
247                            // Extract values from the options array
248                            component.options.forEach(option => {
249                                choiceValues.push(option.value);
250                            });
251                        }
252                    }
253                });
254
255                // Initialize the donation box component
256                initializeDonationBox(choiceValues, data.f_x8_form_id, bloqFormId);
257            }
258            
259            if (data.component_name === 'Signature') {
260                // Initialize the signature pad component
261                initializeSignaturePad();
262            }
263
264            if (data.component_name === 'Item with options' || data.component_name === 'Item with Options (Card Style)' || data.component_name === 'Ticket with options') {
265                // Initialize the signature pad component
266
267                if (!initializedForms.has(data.f_x8_form_id)) {
268                    initializeTicketCard(data.f_x8_form_id, bloqFormId);
269                    initializedForms.add(data.f_x8_form_id); // Mark form as initialized
270                }
271            }
272        });
273    }
274
275    // Add event listeners to checkbox buttons
276    containerForm.querySelectorAll('input[type="checkbox"]').forEach((checkbox, index) => {
277        if (checkbox) {
278            const checkboxId = checkbox.getAttribute('id');
279            const rowId = checkboxRowIdMappings[checkboxId] || 'none';
280
281            checkbox.dataset.prevRowId = rowId;
282
283            if (checkbox.checked && rowId !== 'none') {
284                toggleVisibility(rowId, 'show');
285            }
286
287            // Add event listener for checkbox change
288            checkbox.addEventListener('change', function () {
289                // const rowId = checkboxRowIdMappings[checkboxId] || 'none';
290    
291                // Toggle visibility based on the checkbox state
292                if (this.checked) {
293                    if (rowId !== 'none') {
294                        toggleVisibility(rowId, 'show');
295                    }
296                } else {
297                    if (rowId !== 'none') {
298                        toggleVisibility(rowId, 'hide');
299                    }
300                }
301    
302                // Store the new rowId for future reference
303                // this.dataset.prevRowId = rowId;
304            });
305        }
306    });
307
308    // Add event listeners to radio buttons
309    containerForm.querySelectorAll('input[type="radio"]').forEach(radio => {
310        const radioId = radio.getAttribute('id');
311        const rowId = radioRowIdMappings[radioId] || 'none';
312
313        radio.dataset.prevRowId = rowId;
314
315        radio.addEventListener('change', function() {
316            // const radioId = radio.getAttribute('id');
317            // const rowId = radioRowIdMappings[radioId] || 'none';
318
319            // Hide the previously selected rowId if available
320            const radios = containerForm.querySelectorAll(`[name="${this.name}"]`);
321            radios.forEach(radio => {
322                const prevRowId = radio.dataset.prevRowId || null;
323                if (prevRowId) {
324                    toggleVisibility(prevRowId, 'hide');
325                }
326            });
327
328            // Toggle visibility based on the new selection's rowId
329            if (rowId !== 'none') {
330                toggleVisibility(rowId, 'show');
331            }
332
333            // Store the new rowId for future reference
334            // this.dataset.prevRowId = rowId;
335        });
336    });
337
338    // Add event listeners to select dropdowns
339    containerForm.querySelectorAll('select').forEach(select => {
340        select.addEventListener('change', function() {
341            const selectedOptionLabel = this.options[this.selectedIndex].text;
342            const selectName = select.getAttribute('name');
343
344            // Find the rowId associated with the selected label
345            const mappings = selectRowIdMappings[selectName] || [];
346            const matchedMapping = mappings.find(mapping => mapping[1].trim() === selectedOptionLabel.trim());
347            const rowId = matchedMapping ? matchedMapping[0] : 'none';
348
349            // Hide the previously selected rowId
350            const prevRowId = this.dataset.prevRowId || null;
351            if (prevRowId) {
352                toggleVisibility(prevRowId, 'hide');
353            }
354
355            // Show the new rowId if it's not 'none'
356            if (rowId !== 'none') {
357                toggleVisibility(rowId, 'show');
358            }
359
360            // Store the new rowId for future reference
361            this.dataset.prevRowId = rowId;
362        });
363    });
364
365    containerForm.querySelectorAll('.phone').forEach(function (phoneInput) {
366        phoneInput.addEventListener('input', function (e) {
367            let input = e.target.value;
368            input = input.replace(/\D/g, ''); // Remove all non-numeric characters
369    
370            // Format the input value as (123) 456-7890
371            if (input.length > 0) {
372                input = '(' + input;
373            }
374            if (input.length > 4) {
375                input = input.slice(0, 4) + ') ' + input.slice(4);
376            }
377            if (input.length > 9) {
378                input = input.slice(0, 9) + '-' + input.slice(9);
379            }
380            e.target.value = input.slice(0, 14); // Limit input length
381        });
382    });
383
384    // Select all date and time input elements and show the date or time picker
385    containerForm.querySelectorAll('input[type="date"], input[type="time"]').forEach(input => {
386        input.addEventListener('click', function() {
387            this.showPicker();
388        });
389    });
390
391    $('input[type="number"]').on('keydown input', function(event) {
392        // Prevent 'e', '.', and '-' keys on keydown
393        if (event.type === 'keydown' && (event.key.toLowerCase() === 'e' || event.key === '.' || event.key === '-')) {
394            event.preventDefault();
395        }
396    });
397
398    const formRows = document.querySelectorAll('.form-row');
399
400    if (formRows) {
401        formRows.forEach(formRow => {
402            const formElems = formRow.querySelectorAll('.form-elem');
403
404            if (formRow.classList.contains('sm:grid-cols-2')) {
405                formElems.forEach(elem => {
406
407                    elem.classList.remove('sm:grid-cols-2');
408                    elem.classList.remove('md:grid-cols-3');
409                    elem.classList.remove('md:grid-cols-6');
410                    elem.classList.remove('md:grid-cols-12');
411
412                    const divs = elem.querySelectorAll('.form-ctrl');
413
414                    divs.forEach(div => {
415                        div.className = '';
416                        div.classList.add('w-full', 'mb-4', 'col-span-1');
417                    });
418                });
419            }
420        });
421    }
422}
423
424// Function to initialize star rating functionality
425function initializeStarRating() {
426    document.querySelectorAll('.star-rating').forEach((container,index) => {
427        const stars = container.querySelectorAll('.fa-star');
428        const ratingInput = container.querySelector('#rating');
429
430        // Make the input name dynamic based on index or unique identifier
431        if (ratingInput) {
432            const dynamicName = `rating_${index}`;  // Dynamically generate the name attribute
433            ratingInput.setAttribute('name', dynamicName);  // Set the dynamic name attribute
434        }
435
436        if (stars.length && ratingInput) {
437            stars.forEach((star, index) => {
438                star.addEventListener('click', function() {
439                    ratingInput.value = star.getAttribute('data-value');
440                    
441                    // Reset the color of all stars
442                    stars.forEach(s => s.classList.remove('text-yellow-500'));
443                    stars.forEach(s => s.classList.add('text-gray-300'));
444
445                    // Set the color of the selected stars
446                    for (let i = 0; i <= index; i++) {
447                        stars[i].classList.add('text-yellow-500');
448                    }
449                });
450            });
451        }
452    });
453}
454
455// Function to initialize range functionality
456function initializeRange() {
457    document.querySelectorAll('.range-selector').forEach((container, index) => {
458        const rangeInput = container.querySelector('input[type="range"]');
459        const rangeValueSpan = container.querySelector('#range_value');
460
461        if (rangeInput) {
462            const originalName = rangeInput.getAttribute('name') || ''; // Get the original name if it exists
463            const dynamicName = `${originalName}_${index}`;  // Append the index to the original name
464            rangeInput.setAttribute('name', dynamicName);  // Set the dynamic name attribute
465
466            // Add an event listener to update the displayed value as the range input changes
467            rangeInput.addEventListener('input', function() {
468                rangeValueSpan.textContent = this.value;
469            });
470        }
471    });
472}
473
474// Function to initialize file upload functionality
475function initializeFileuploader() {
476    document.querySelectorAll('input[type="file"]').forEach((fileInput) => {
477        let selectedFiles = [];
478
479        fileInput.addEventListener('change', function () {
480            let newFiles = Array.from(this.files);
481            let validFiles = [];
482            let oversizedFiles = [];
483            let invalidFiles = [];
484            const dataTransfer = new DataTransfer();
485
486            const notAllowedExtensions = /(\.exe)$/i;
487
488            // Validate file sizes (Max: 5MB)
489            newFiles.forEach((file) => {
490                if (file.size > 5 * 1024 * 1024) { // 5MB limit
491                    oversizedFiles.push(`"${file.name}"`);
492                } else if (notAllowedExtensions.test(file.name)) { 
493                    invalidFiles.push(`"${file.name}"`);
494                } else {
495                    validFiles.push(file);
496                    dataTransfer.items.add(file);
497                }
498            });
499
500            // If there are oversized and invalid files
501            if (oversizedFiles.length > 0) {
502                alert(`The following files exceed the 5MB limit and will be removed:\n\n${oversizedFiles.join("\n")}`);
503            }
504            if (invalidFiles.length > 0) {
505                alert(`The following files are not allowed (.exe files) and will be removed:\n\n${invalidFiles.join("\n")}`);
506            }
507
508            selectedFiles = validFiles;
509            this.files = dataTransfer.files; // Update input with valid files
510
511            // Find the closest #fileList
512            const fileListContainer = fileInput.parentElement.querySelector('#fileList');
513            if (!fileListContainer) return;
514
515            updateFileList(selectedFiles, fileListContainer, this);
516        });
517    });
518
519    function updateFileList(files, fileListContainer, fileInput) {
520        fileListContainer.innerHTML = "";
521
522        const ul = document.createElement('ul');
523        ul.style.listStyle = 'none';
524        ul.style.padding = '0';
525
526        files.forEach((file, index) => {
527            const listItem = document.createElement("li");
528            listItem.className = "flex gap-2 mb-2";
529
530            listItem.innerHTML = `
531                <span class="ml-4">${file.name} (${formatFileSize(file.size)})</span>
532                <button type="button" class="cursor-pointer text-red-500 text-xs remove-file" title="Remove file" data-index="${index}">
533                    <i class="fa-solid fa-trash-can"></i>
534                </button>
535            `;
536
537            ul.appendChild(listItem);
538            fileListContainer.appendChild(ul);
539        });
540
541        attachRemoveHandlers(fileListContainer, files, fileInput);
542    }
543
544    function attachRemoveHandlers(fileListContainer, files, fileInput) {
545        fileListContainer.querySelectorAll(".remove-file").forEach(button => {
546            button.addEventListener("click", function () {
547                const index = parseInt(this.getAttribute("data-index"));
548                files.splice(index, 1);
549
550                updateFileList(files, fileListContainer, fileInput);
551
552                const dataTransfer = new DataTransfer();
553                files.forEach(file => dataTransfer.items.add(file));
554                fileInput.files = dataTransfer.files;
555            });
556        });
557    }
558
559    function formatFileSize(bytes) {
560        if (bytes === 0) return '0 Bytes';
561        const k = 1024;
562        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
563        const i = Math.floor(Math.log(bytes) / Math.log(k));
564        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
565    }
566}
567
568// Initialize donation box for preset and custom amounts
569function initializeDonationBox(choiceValues, fX8FormId, bloqFormId) {
570    const donationContainers = document.querySelectorAll('.donation-container');
571
572    donationContainers.forEach((container) => {
573        const presets = container.querySelectorAll('#preset1, #preset2, #preset3');
574        // Adds event listeners to preset buttons to update the amount
575        presets.forEach((preset, index) => {
576            preset.addEventListener('click', () => {
577                updateSelectedAmount(container, choiceValues, choiceValues[index], fX8FormId, bloqFormId);
578            });
579        });
580        initializeCustomAmountHandler(container, choiceValues, fX8FormId, bloqFormId);
581        addAmountToDonate(container, choiceValues, fX8FormId, bloqFormId);
582    });
583}
584
585// Updates the selected amount and highlights the corresponding preset button
586function updateSelectedAmount(container, choiceValues, amount, fX8FormId, bloqFormId) {
587    const otherAmountInput = container.querySelector('#other_amount');
588    const presets = container.querySelectorAll('#preset1, #preset2, #preset3');
589
590    // Set input value to the selected amount
591    otherAmountInput.value = amount;
592
593    // Reset the styling of all preset buttons
594    resetPresetStyles(container);
595
596    // Highlight the corresponding preset based on the selected amount
597    const presetIndex = choiceValues.indexOf(amount.toString());
598    if (presetIndex !== -1) {
599        presets[presetIndex].classList.add('bg-blue-500', 'text-white');
600        presets[presetIndex].classList.remove('bg-gray-200', 'text-gray-700');
601    }
602
603    // Adds event listeners to preset buttons to update the amount
604    presets.forEach((preset, index) => {
605        preset.addEventListener('click', () => {
606            updateSelectedAmount(container, choiceValues, choiceValues[index], fX8FormId, bloqFormId);
607        });
608    });
609}
610
611// Adds event listener to the custom amount input field
612function initializeCustomAmountHandler(container, choiceValues, fX8FormId, bloqFormId) {
613    const otherAmountInput = container.querySelector('#other_amount');
614
615    otherAmountInput.addEventListener('change', function () {
616        const amount = parseInt(this.value, 10);
617
618        if (choiceValues.includes(amount.toString())) {
619            updateSelectedAmount(container, choiceValues, amount, fX8FormId, bloqFormId);
620        } else {
621            resetPresetStyles(container);
622        }
623    });
624}
625
626function addAmountToDonate(container, choiceValues, fX8FormId, bloqFormId) {
627    // console.log(amount ? amount : 'no');
628    const donationBtn = container.querySelector('#donationBtn');
629
630    // Attach event listener for add button
631    if (donationBtn) {
632        donationBtn.addEventListener('click', function () {
633            handleDonationAddToCart(this, container, choiceValues, fX8FormId, bloqFormId);
634        });
635    }
636
637    // Handle adding item to the cart
638    function handleDonationAddToCart(donationBtn, container, choiceValues, fX8FormId, bloqFormId) {
639        let dFormDataId, donationName;
640
641        const presets = container.querySelectorAll('#preset1, #preset2, #preset3');
642        const otherAmountInput = container.querySelector('#other_amount');
643        var amount = otherAmountInput.value;
644
645        // Highlight the corresponding preset based on the selected amount
646        const presetIndex = choiceValues.indexOf(amount.toString());
647        if (presetIndex !== -1) {
648            dFormDataId = presets[presetIndex].getAttribute('data-form-data-id');
649            donationName = presets[presetIndex].getAttribute('data-name');
650        } else {
651            dFormDataId = donationBtn.getAttribute('data-form-data-id');
652            donationName = donationBtn.getAttribute('data-name');
653        }
654
655        if (bloqFormId) {
656            var cartSection = document.getElementById(`cartSection_${bloqFormId}`);
657        } else {
658            var cartSection = document.getElementById('cartSection');
659        }
660        const tableBody = cartSection.querySelector('tbody');
661        
662        amount = Number(amount);
663
664        const errorMsgContainer = container.nextElementSibling;
665        // Check and remove the error message if it exists
666        if (errorMsgContainer && errorMsgContainer.classList.contains('error-message')) {
667            errorMsgContainer.remove();
668        }
669
670        if (amount > 0) {
671            const existingRow = findExistingDonationRow(donationName, amount, tableBody);
672    
673            if (existingRow) {
674                const itemPrice = existingRow.querySelector('.itemPrice');
675                const currentItemPrice = parseFloat(itemPrice.textContent.replace('$', ''));
676                const newItemPrice = (currentItemPrice + amount).toFixed(2);
677                itemPrice.textContent = `$${newItemPrice}`;
678                
679                updateRowTotal(existingRow);
680            } else {
681                createNewRow(donationName, amount, null, 0, dFormDataId, tableBody, fX8FormId, bloqFormId);
682            }
683    
684            var containerForm = bloqFormId ? $(`#containerForm_${fX8FormId}`) : $(`#containerForm`);
685            containerForm.find("#flashMessage").html(
686                '<div class="custom-msg-success" onclick="this.classList.add(\'hidden\')">The donation has been added to the cart.</div>'
687            );
688            hideFlash();
689    
690            updateTotals(bloqFormId);
691        } else {
692            // Add a span with an error message after the item container if it doesn't already exist
693            const errorMessage = document.createElement('span');
694            errorMessage.className = 'error-message text-red-500 text-sm';
695            errorMessage.textContent = `Please enter an amount greater than 0.`;
696            container.after(errorMessage);
697        }
698    }
699
700    // Find an existing row in the cart based on item name and price
701    function findExistingDonationRow(donationName, amount, tableBody) {
702        return [...tableBody.querySelectorAll('tr')].find(row => {
703            const rowItemName = row.querySelector('.itemNameCell').textContent;
704            return rowItemName === donationName;
705        });
706    }
707}
708
709// Resets the styling of preset buttons (if a custom amount is selected)
710function resetPresetStyles(container) {
711    const presets = container.querySelectorAll('#preset1, #preset2, #preset3');
712
713    presets.forEach(preset => {
714        preset.classList.remove('bg-blue-500', 'text-white');
715        preset.classList.add('bg-gray-200', 'text-gray-700');
716    });
717}
718
719function reinitializeSignaturePad(element) {
720    const containers = element.querySelectorAll('.signature-container');
721
722    containers.forEach(container => {
723        const canvas = container.querySelector('.signature-pad');
724        if (!canvas) return;
725
726        const ctx = canvas.getContext('2d');
727
728        // Resize canvas after visible
729        canvas.width = container.offsetWidth;
730        canvas.height = container.offsetHeight;
731
732        // Optional: clear old drawing issue
733        ctx.clearRect(0, 0, canvas.width, canvas.height);
734    });
735}
736
737// Function to initialize signature pad
738function initializeSignaturePad() {
739    document.querySelectorAll('.signature-container').forEach((container, index) => {
740        const canvas = container.querySelector('.signature-pad');
741        const clearBtn = container.querySelector('.clear-signature');
742
743        let signatureInput = container.querySelector('.signature-data');
744
745        // Add a hidden input field to store the signature data if NOT exists
746        if (!signatureInput) {
747            signatureInput = document.createElement('input');
748            signatureInput.type = 'hidden';
749            signatureInput.name = 'signature_' + index;
750            signatureInput.className = 'signature-data';
751
752            if (container.getAttribute('data-required') === '1') {
753                signatureInput.setAttribute('data-required', '1');
754            }
755
756            container.appendChild(signatureInput);
757        }
758
759        // Get context and set up signature pad for each canvas
760        const ctx = canvas.getContext('2d');
761        
762        // Function to set canvas dimensions based on container size
763        function resizeCanvas() {
764            const prevWidth = canvas.width;
765            const prevHeight = canvas.height;
766        
767            let data = null;
768            if (prevWidth > 0 && prevHeight > 0) {
769                try {
770                    data = ctx.getImageData(0, 0, prevWidth, prevHeight);
771                } catch (e) {
772                    console.warn('Canvas data could not be copied:', e);
773                }
774            }
775        
776            canvas.width = container.offsetWidth;
777            canvas.height = container.offsetHeight;
778        
779            if (data) {
780                ctx.putImageData(data, 0, 0);
781            }
782        }        
783
784        // Set the canvas dimensions immediately after initialization
785        resizeCanvas();
786
787        // Adjust the canvas size and maintain drawn content when the window is resized
788        window.addEventListener('resize', resizeCanvas);
789
790        let isDrawing = false;
791        let hasSignature = false;
792
793        // Start drawing when the mouse is pressed down
794        canvas.addEventListener('mousedown', (event) => {
795            isDrawing = true;
796            hasSignature = true;
797            const { x, y } = getCanvasCoordinates(event, canvas);
798            ctx.beginPath();
799            ctx.moveTo(x, y); // Use offsetX and offsetY for correct coordinates
800        });
801
802        // Stop drawing when the mouse is released
803        canvas.addEventListener('mouseup', () => {
804            isDrawing = false;
805            ctx.closePath();
806            if (hasSignature) {
807                updateSignatureData();
808            }
809        });
810
811        // Mouse leaves canvas
812        canvas.addEventListener('mouseleave', () => {
813            if (isDrawing) {
814                isDrawing = false;
815                ctx.closePath();
816                if (hasSignature) {
817                    updateSignatureData();
818                }
819            }
820        });
821
822        // Draw the signature on the canvas
823        canvas.addEventListener('mousemove', (event) => {
824            if (!isDrawing) return;
825            const { x, y } = getCanvasCoordinates(event, canvas);
826            ctx.lineWidth = 2;
827            ctx.lineCap = 'round';
828            ctx.strokeStyle = 'black';
829            ctx.lineTo(x, y); // Use offsetX and offsetY for correct coordinates
830            ctx.stroke();
831        });
832
833        // Touch events support for mobile devices
834        canvas.addEventListener('touchstart', (event) => {
835            event.preventDefault();
836            hasSignature = true;
837            isDrawing = true;
838            const touch = event.touches[0];
839            const rect = canvas.getBoundingClientRect();
840            const scaleX = canvas.width / rect.width;
841            const scaleY = canvas.height / rect.height;
842
843            const touchX = (touch.clientX - rect.left) * scaleX;
844            const touchY = (touch.clientY - rect.top) * scaleY;
845            ctx.beginPath();
846            ctx.moveTo(touchX, touchY);
847        });
848
849        canvas.addEventListener('touchmove', (event) => {
850            event.preventDefault();
851            if (!isDrawing) return;
852            const touch = event.touches[0];
853            const rect = canvas.getBoundingClientRect();
854            const scaleX = canvas.width / rect.width;
855            const scaleY = canvas.height / rect.height;
856
857            const touchX = (touch.clientX - rect.left) * scaleX;
858            const touchY = (touch.clientY - rect.top) * scaleY;
859            ctx.lineWidth = 2;
860            ctx.lineCap = 'round';
861            ctx.strokeStyle = 'black';
862            ctx.lineTo(touchX, touchY);
863            ctx.stroke();
864        });
865
866        canvas.addEventListener('touchend', (event) => {
867            event.preventDefault();
868            isDrawing = false;
869            ctx.closePath();
870            if (hasSignature) {
871                updateSignatureData();
872            }
873        });
874
875        // Clear the signature when the Clear button is clicked
876        clearBtn.addEventListener('click', () => {
877            ctx.clearRect(0, 0, canvas.width, canvas.height);
878            hasSignature = false;
879            signatureInput.value = '';
880        });
881        
882        // Function to update the hidden input with the signature data
883        function updateSignatureData() {
884            const signatureData = canvas.toDataURL('image/png');
885            signatureInput.value = signatureData;
886        }
887
888        function getCanvasCoordinates(event, canvas) {
889            const rect = canvas.getBoundingClientRect();
890
891            const scaleX = canvas.width / rect.width;
892            const scaleY = canvas.height / rect.height;
893
894            return {
895                x: (event.clientX - rect.left) * scaleX,
896                y: (event.clientY - rect.top) * scaleY
897            };
898        }
899    });
900}
901
902// Function to convert base64 to Blob
903function dataURLtoBlob(dataURL) {
904    const parts = dataURL.split(';base64,');
905    const contentType = parts[0].split(':')[1];
906    const raw = window.atob(parts[1]);
907    const rawLength = raw.length;
908    const uInt8Array = new Uint8Array(rawLength);
909    
910    for (let i = 0; i < rawLength; ++i) {
911        uInt8Array[i] = raw.charCodeAt(i);
912    }
913    
914    return new Blob([uInt8Array], { type: contentType });
915}
916
917// Function to initialize Ticket Card
918function initializeTicketCard(fX8FormId, bloqFormId) {
919    document.querySelectorAll('.item').forEach((item, index) => {
920        const select = item.querySelector('#itemOptions');
921        const itemAmountElement = item.querySelector('.itemAmount');
922        const quantityInput = item.querySelector('input[id="itemQuantity"]');
923        const addButton = item.querySelector('button');
924        const itemName = item.querySelector('#itemTitle');
925
926        if (bloqFormId) {
927            var cartSection = document.getElementById(`cartSection_${bloqFormId}`);
928            var errorMsg = document.getElementById(`errorMsg_${bloqFormId}`);
929            var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
930        } else {
931            var cartSection = document.getElementById('cartSection');
932            var errorMsg = document.getElementById('errorMsg');
933            var formPaymentContainer = $(`#formPaymentContainer`);
934        }
935        const tableBody = cartSection.querySelector('tbody');
936        const tableBodyRows = cartSection.querySelectorAll('tbody tr');
937
938        // Check if there are any rows in the cart (tbody)
939        if (cartSection) {
940            if (tableBodyRows.length === 0) {
941                cartSection.classList.add('hidden');
942                errorMsg.classList.remove('hidden');
943                // formPaymentContainer.find('#formPaymentForm #payNowBtn').prop('disabled', true);
944            } else {
945                cartSection.classList.remove('hidden');
946                errorMsg.classList.add('hidden');
947                // formPaymentContainer.find('#formPaymentForm #payNowBtn').prop('disabled', false);
948            }
949        }
950
951        // Set dynamic id for quantity input
952        if (quantityInput) {
953            quantityInput.setAttribute('id', `itemQuantity_${index}`);
954
955            // Get max value from the input's attribute
956            let maxQuantity = parseInt(quantityInput.getAttribute('max')) || Infinity;
957
958            const totalPriceElement = item.querySelector('#totalPrice');
959            let availableQuantity = parseInt(totalPriceElement.textContent);
960
961            // Ensure quantity input does not exceed max value
962            quantityInput.addEventListener('input', function () {
963                let enteredValue = parseInt(this.value);
964
965                const errorMsgContainer = item.nextElementSibling;
966                // Check and remove the error message if it exists
967                if (errorMsgContainer && errorMsgContainer.classList.contains('error-message')) {
968                    errorMsgContainer.remove();
969                }
970
971                if (enteredValue > maxQuantity) {
972                    this.value = maxQuantity;
973
974                    const errorMessage = document.createElement('span');
975                    errorMessage.className = 'error-message text-red-500 text-sm';
976                    errorMessage.textContent = `This item is limited to ${maxQuantity} per transaction.`;
977                    item.after(errorMessage);
978                } else if (enteredValue < 1 || isNaN(enteredValue)) {
979                    this.value = 1;
980                } else if (enteredValue > availableQuantity) {
981                    this.value = availableQuantity;
982
983                    const errorMessage = document.createElement('span');
984                    errorMessage.className = 'error-message text-red-500 text-sm';
985                    errorMessage.textContent = `Only ${availableQuantity} items are available.`;
986                    item.after(errorMessage);
987                }
988            });
989        }
990
991        let currentPriceText = itemAmountElement.textContent;
992        var currentPrice = parseFloat(currentPriceText.replace(/[^0-9.
992-]+/g,""));
993
994        // Attach event listener for select dropdown to update price and reset quantity
995        if (select && itemAmountElement) {
996            select.addEventListener('change', function() {
997                const selectedValue = parseFloat(this.value); // Convert to float for decimal handling
998                itemAmountElement.textContent = `$${selectedValue.toFixed(2)}`; 
999
1000                if (quantityInput) {
1001                    quantityInput.value = 1; // Reset quantity input to 1
1002                }
1003
1004                currentPrice = this.value;
1005            });
1006        }
1007
1008        getAvailableQuantity(item, fX8FormId);
1009        getCartItemsSummary(item, fX8FormId, bloqFormId, 'first-time-load');
1010
1011        // Attach event listener for add button
1012        if (addButton && quantityInput) {
1013            addButton.addEventListener('click', () => handleAddToCart(quantityInput, currentPrice, itemName, tableBody, item, fX8FormId, bloqFormId));
1014        }
1015    });
1016
1017    // Initialize events for rows that already exist in tbody
1018    initializeCartRowEvents(bloqFormId);
1019
1020    function getAvailableQuantity(item, fX8FormId) {
1021        
1022        const isTaxableInput = item.querySelector('input[id="is_taxable"]');
1023    
1024        if (isTaxableInput) {
1025            const formDataId = isTaxableInput.getAttribute('data-form-data-id');
1026            $.ajax({
1027                type: "GET",
1028                url: `/bloqs/bloq-forms/get-available-quantity/${fX8FormId}/${formDataId}/${bloqFormId}`,
1029                success: function(response) {
1030                    const totalPriceElement = item.querySelector('#totalPrice');
1031
1032                    if (response && typeof response.difference !== 'undefined') {
1033                        if (totalPriceElement) {
1034                            totalPriceElement.textContent = response.difference;
1035                        }
1036                    }
1037                },
1038                error: function() {
1039                    console.error("An error occurred while fetching available quantity");
1040                }
1041            });
1042        }
1043    }    
1044
1045    // Handle adding item to the cart
1046    function handleAddToCart(quantityInput, currentPrice, itemName, tableBody, item, fX8FormId, bloqFormId) {
1047        const quantity = parseInt(quantityInput.value, 10);
1048        const itemPrice = parseFloat(currentPrice);
1049        var itemNameText = itemName.textContent;
1050        const isTaxableInput = item.querySelector('input[id="is_taxable"]');
1051        const isTaxable = isTaxableInput.value;
1052        const formDataId = isTaxableInput.getAttribute('data-form-data-id'); 
1053        const itemOptions = item.querySelector('#itemOptions');
1054
1055        if (window.getComputedStyle(itemOptions).display === 'block') {
1056            const selectedText = itemOptions.options[itemOptions.selectedIndex].text;
1057            // itemNameText = `${itemName.textContent} (${selectedText})`;
1058            itemNameText = `${itemName.textContent}: ${selectedText}`;
1059        }
1060
1061        if (quantity > 0) {
1062            const existingRow = findExistingRow(itemNameText, itemPrice, tableBody);
1063
1064            if (existingRow) {
1065                updateExistingRow(existingRow, quantity);
1066            } else {
1067                // Check and remove the error message if it exists
1068                const errorMessage = item.nextElementSibling;
1069                if (errorMessage && errorMessage.classList.contains('error-message')) {
1070                    errorMessage.remove();
1071                }
1072
1073                createNewRow(itemNameText, itemPrice, quantity, isTaxable, formDataId, tableBody, fX8FormId, bloqFormId);
1074            }
1075
1076            var containerForm = bloqFormId ? $(`#containerForm_${fX8FormId}`) : $(`#containerForm`);
1077            containerForm.find("#flashMessage").html(
1078                '<div class="custom-msg-success" onclick="this.classList.add(\'hidden\')">The item has been added to the cart.</div>'
1079            );
1080            hideFlash();
1081
1082            quantityInput.value = 1;
1083
1084            updateTotals(bloqFormId);
1085        } else {
1086            swal("Something went wrong!", "Please enter a quantity greater than 0.")
1087                .then(() => {
1088                    quantityInput.value = 1;
1089                });
1090        }
1091    }
1092
1093    // Find an existing row in the cart based on item name and price
1094    function findExistingRow(itemNameText, itemPrice, tableBody) {
1095        return [...tableBody.querySelectorAll('tr')].find(row => {
1096            const rowItemName = row.querySelector('.itemNameCell').textContent;
1097            const rowItemPrice = parseFloat(row.querySelector('.itemPrice').textContent.replace('$', ''));
1098            return rowItemName === itemNameText && rowItemPrice === itemPrice;
1099        });
1100    }
1101
1102    // Update the quantity and total for an existing row
1103    function updateExistingRow(existingRow, quantity) {
1104        const quantityInput = existingRow.querySelector('.quantityInput');
1105        quantityInput.value = parseInt(quantityInput.value, 10) + quantity;
1106        updateRowTotal(existingRow);
1107    }
1108}
1109
1110// Create a new row in the cart
1111function createNewRow(itemName, itemPrice, quantity = null, isTaxable = 0, formDataId, tableBody, fX8FormId, bloqFormId) {
1112    const newRow = document.createElement('tr');
1113    newRow.id = `cartRow-${formDataId}`;
1114    newRow.innerHTML = `
1115        <td class="p-2 itemNameCell">${itemName}</td>
1116        <td class="p-2 itemPrice">$${itemPrice.toFixed(2)}</td>
1117        <td class="p-2">
1118            ${quantity !== null 
1119                ? `<input type="number" class="quantityInput w-16 !m-0 !py-1 input-standard" value="${quantity}" min="1">`
1120                : `<span>-</span>`}
1121        </td>
1122        <td class="p-2 itemTotal">
1123            $${quantity !== null 
1124                ? (itemPrice * quantity).toFixed(2)
1125                : itemPrice.toFixed(2)}</td>
1126        <td class="p-2 hidden isTaxableCell">${isTaxable}</td>
1127        <td class="p-2 hidden formDataId">${formDataId}</td>
1128        <td class="p-2 hidden fX8FormId">${fX8FormId}</td>
1129        <td>
1130            <button type="button" class="text-red-500 hover:text-red-700 removeRow">
1130<i class="fas fa-trash"></i></button>
1131        </td>
1132    `;
1133    
1134    tableBody.appendChild(newRow);
1135
1136    // Initialize delete and quantity events for the new row
1137    initializeRowEvents(newRow, bloqFormId);
1138}
1139
1140// Initialize row events for existing rows
1141function initializeCartRowEvents(bloqFormId) {
1142    if (bloqFormId) {
1143        var cartSection = document.getElementById(`cartSection_${bloqFormId}`);
1144    } else {
1145        var cartSection = document.getElementById('cartSection');
1146    }
1147    cartSection.querySelectorAll('tbody tr').forEach(row => {
1148        initializeRowEvents(row, bloqFormId);
1149    });
1150}
1151
1152// Initialize delete and quantity update events for a specific row
1153function initializeRowEvents(row, bloqFormId) {
1154    const removeButton = row.querySelector('.removeRow');
1155    const quantityInput = row.querySelector('.quantityInput');
1156
1157    if (removeButton) {
1158        // Attach event listener to remove the row on click
1159        removeButton.addEventListener('click', () => {
1160            row.remove();
1161
1162            const rowId = row.id;
1163            const errorRow = document.getElementById(`${rowId}-error`);
1164
1165            // Remove the error message row if it exists
1166            if (errorRow) {
1167                errorRow.remove();
1168            }
1169
1170            updateTotals(bloqFormId);
1171        });
1172
1173        if (quantityInput) {
1174            // Attach event listener to update the row total when quantity changes
1175            quantityInput.addEventListener('input', () => {
1176                // Ensure the quantity is not less than 1 and not zero
1177                if (quantityInput.value <= 0) {
1178                    quantityInput.value = 1;
1179                }
1180                updateRowTotal(row);
1181                updateTotals(bloqFormId);
1182            });
1183        }
1184    }
1185}
1186
1187// Update the total for a specific row
1188function updateRowTotal(row) {
1189    const itemPrice = parseFloat(row.querySelector('.itemPrice').textContent.replace('$', ''));
1190    // const quantity = parseInt(row.querySelector('.quantityInput').value, 10);
1191    // const total = (itemPrice * quantity).toFixed(2);
1192    const quantityInput = row.querySelector('.quantityInput');
1193    const quantity = quantityInput ? parseInt(quantityInput.value, 10) : null;
1194    const total = (quantity !== null ? itemPrice * quantity : itemPrice).toFixed(2);
1195    row.querySelector('.itemTotal').textContent = `$${total}`;
1196}
1197
1198// Update the total amounts
1199function updateTotals(bloqFormId) {
1200    let totalAmount = 0;
1201    const cartItems = [];
1202
1203    if (bloqFormId) {
1204        var cartSection = document.getElementById(`cartSection_${bloqFormId}`);
1205        var errorMsg = document.getElementById(`errorMsg_${bloqFormId}`);
1206        var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1207    } else {
1208        var cartSection = document.getElementById('cartSection');
1209        var errorMsg = document.getElementById('errorMsg');
1210        var formPaymentContainer = $(`#formPaymentContainer`);
1211    }
1212    const tableBodyRows = cartSection.querySelectorAll('tbody tr');
1213
1214    // Check if there are any rows in the cart (tbody)
1215    if (tableBodyRows.length === 0) {
1216        cartSection.classList.add('hidden');
1217        errorMsg.classList.remove('hidden');
1218        // formPaymentContainer.find('#formPaymentForm #payNowBtn').prop('disabled', true);
1219    } else {
1220        cartSection.classList.remove('hidden');
1221        errorMsg.classList.add('hidden');
1222        // formPaymentContainer.find('#formPaymentForm #payNowBtn').prop('disabled', false);
1223    }
1224    
1225    tableBodyRows.forEach(row => {
1226        if (row.querySelector('.itemNameCell')) {
1227            const itemName = row.querySelector('.itemNameCell').textContent;
1228            const itemPrice = parseFloat(row.querySelector('.itemPrice').textContent.replace('$', ''));
1229            // const quantity = parseInt(row.querySelector('.quantityInput').value, 10);
1230            const quantityInput = row.querySelector('.quantityInput');
1231            const quantity = quantityInput ? parseInt(quantityInput.value, 10) : null;
1232
1233            const total = parseFloat(row.querySelector('.itemTotal').textContent.replace('$', ''));
1234            const isTaxableValue = row.querySelector('.isTaxableCell').textContent;
1235            const isTaxable = isTaxableValue == 1 || isTaxableValue === 'true';
1236            const formDataId = row.querySelector('.formDataId').textContent;
1237            const fX8FormId = row.querySelector('.fX8FormId').textContent;
1238
1239            totalAmount += total;
1240
1241            // Add item details to the cart array
1242            cartItems.push({
1243                form_data_id: formDataId,
1244                name: itemName,
1245                price: itemPrice.toFixed(2),
1246                qty: quantity,
1247                is_taxable: isTaxable,
1248                form_id: fX8FormId
1249            });
1250        }
1251    });
1252
1253    cartSection.querySelector('.subTotal').textContent = `$${totalAmount.toFixed(2)}`;
1254
1255    // Prepare the cart data to be sent to the server
1256    const cartData = {
1257        items: cartItems
1258    };
1259
1260    const fX8FormId = cartItems.length > 0 ? cartItems[0].form_id : null;
1261
1262    // Make the AJAX call to save cart data into the session
1263    $.ajax({
1264        url: '/bloqs/bloq-forms/save-cart-items',
1265        method: 'POST',
1266        data: {
1267            bloq_form_id: bloqFormId,
1268            form_id: cartItems.length > 0 ? cartItems[0].form_id : null,
1269            cart: cartData
1270        },
1271        headers: {
1272            'X-CSRF-Token': csrfToken
1273        },
1274        success: function(response) {
1275            console.log('Cart saved successfully');
1276
1277            document.querySelectorAll('.item').forEach((item, index) => {
1278                getCartItemsSummary(item, fX8FormId, bloqFormId);
1279            });
1280
1281            if (bloqFormId) {
1282                var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1283            } else {
1284                var formPaymentContainer = $(`#formPaymentContainer`);
1285            }
1286
1287            const couponInput = formPaymentContainer.find('input[name="coupon_code"]');
1288
1289            if (couponInput.length > 0) {
1290                const couponCode = couponInput.val().trim();
1291
1292                if (couponCode !== '') {
1293                    checkCoupon(fX8FormId, bloqFormId);
1294                }
1295            }
1296
1297            const cartSessionData = response.cart_items;
1298            
1299            formPaymentContainer.find('#formPaymentForm input[name="cart_data"]').val(cartSessionData);
1300
1301            // Parse cartSessionData to check differences
1302            const cartData = cartSessionData ? JSON.parse(cartSessionData) : null;
1303
1304            let hasNegativeDifference = false;
1305
1306            if (cartData && cartData.items) {
1307                hasNegativeDifference = cartData.items.some(item => item.difference <= 0);
1308            }
1309
1310            // Hide or show the Pay Now button and message
1311            if (hasNegativeDifference) {
1312                formPaymentContainer.find('#formPaymentForm #payNowBtn').hide();
1313                formPaymentContainer.find('#formPaymentForm #payNowMessage').removeClass('hidden');
1314            } else {
1315                formPaymentContainer.find('#formPaymentForm #payNowBtn').show();
1316                formPaymentContainer.find('#formPaymentForm #payNowMessage').addClass('hidden');
1317            }
1318
1319            var zipCodeValue = formPaymentContainer.find('#formPaymentForm #zipCode').val();
1320            if (zipCodeValue.length > 0) {
1321                fetchSalesTax(zipCodeValue, bloqFormId);
1322            } else {
1323                updateSalesTax(0,bloqFormId);
1324            }
1325        },
1326        error: function(xhr, status, error) {
1327            console.error('Error saving cart', error);
1328        }
1329    });
1330}
1331
1332function getCartItemsSummary(item, fX8FormId, bloqFormId, action = null) {
1333    const isTaxableInput = item.querySelector('input[id="is_taxable"]');
1334    const cartSummaryElement = item.querySelector('.cart-summary');
1335
1336    if (isTaxableInput) {
1337
1338        const formDataId = isTaxableInput.getAttribute('data-form-data-id');
1339        $.ajax({
1340            type: "GET",
1341            url: `/bloqs/bloq-forms/get-cart-items-summary/${fX8FormId}/${formDataId}/${bloqFormId}`,
1342            success: function(response) {
1343                if (cartSummaryElement) {
1344                    cartSummaryElement.innerHTML = '';
1345                }
1346
1347                // Cart status for this item
1348                if (response.cart_status && Array.isArray(response.cart_status) && response.cart_status.length > 0) {
1349                    let cart_data = `
1350                    <table class="w-full text-xs mt-4">
1351                        <thead class="bg-gray-50">
1352                            <tr>
1353                                <th class="text-left">Product</th>
1354                                <th class="text-left">Price</th>
1355                                <th class="text-left">Qty</th>
1356                                <th class="text-left">Total</th>
1357                                <th></th>
1358                            </tr>
1359                        </thead>
1360                        <tbody>`;
1361                
1362                    response.cart_status.forEach(item => {
1363                        cart_data += `
1364                        <tr id="cartRow-${item.form_data_id}">
1365                            <td class="">${item.name}</td>
1366                            <td class="">$${item.price}</td>
1367                            <td class="">${item.qty}</td>
1368                            <td class="">$${(item.price * item.qty).toFixed(2)}</td>
1369                            <td class="">
1370                                <button type="button" class="text-red-500 hover:text-red-700 removeRow"><i class="fas fa-trash"></i></button>
1371                            </td>
1372                        </tr>`;
1373                    });
1374                
1375                    cart_data += `</tbody></table>`;
1376                
1377                    // Create a div element and insert the table HTML
1378                    const cartElement = document.createElement("div");
1379                    cartElement.innerHTML = cart_data;
1380
1381                    if (cartSummaryElement) {
1382                        // cartSummaryElement.innerHTML = '';
1383                        cartSummaryElement.appendChild(cartElement);
1384
1385                        cartSummaryElement.querySelectorAll('tbody tr').forEach(row => {
1386                            const removeButton = row.querySelector('.removeRow');
1387                        
1388                            if (removeButton) {
1389                                removeButton.addEventListener('click', () => {
1390                                    const rowId = row.id;
1391                                    row.remove();
1392                        
1393                                    const cartSection = bloqFormId 
1394                                        ? document.getElementById(`cartSection_${bloqFormId}`) 
1395                                        : document.getElementById('cartSection');
1396                        
1397                                    if (cartSection) {
1398                                        const matchingRow = cartSection.querySelector(`tbody tr#${rowId}`);
1399                                        if (matchingRow) {
1400                                            matchingRow.remove();
1401
1402                                            updateTotals(bloqFormId);
1403                                        }
1404                                    }
1405
1406                                    // Check if there are any remaining rows in the cart summary
1407                                    if (cartSummaryElement.querySelectorAll('tbody tr').length === 0) {
1408                                        cartSummaryElement.innerHTML = '';
1409                                    }
1410                                });
1411                            }
1412                        }); 
1413                    }
1414
1415                    if (action) {
1416                        const cartSection = bloqFormId 
1417                            ? document.getElementById(`cartSection_${bloqFormId}`) 
1418                            : document.getElementById('cartSection');
1419
1420                        if (!cartSection) return;
1421
1422                        const tableBody = cartSection.querySelector('tbody');
1423
1424                        response.cart_status.forEach(cartItem => {
1425                            const itemName   = cartItem.name;
1426                            const itemPrice  = parseFloat(cartItem.price);
1427                            const quantity   = parseInt(cartItem.qty, 10);
1428                            const isTaxable  = cartItem.is_taxable;
1429                            const formDataId = cartItem.form_data_id;
1430
1431                            // Prevent duplicate rows
1432                            const existingRow = tableBody.querySelector(`#cartRow-${formDataId}`);
1433                            if (existingRow) return;
1434
1435                            // Reuse existing function
1436                            createNewRow(itemName, itemPrice, quantity, isTaxable, formDataId, tableBody, fX8FormId, bloqFormId);
1437                        });
1438
1439                        // Update totals once after all rows added
1440                        updateTotals(bloqFormId);
1441
1442                        const cartSessionData = {
1443                            items: response.cart_status
1444                        };
1445
1446                        if (bloqFormId) {
1447                            var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1448                        } else {
1449                            var formPaymentContainer = $(`#formPaymentContainer`);
1450                        }
1451
1452                        formPaymentContainer.find('#formPaymentForm input[name="cart_data"]').val(JSON.stringify(cartSessionData));
1453                    }
1454                }
1455
1456                
1457            },
1458            error: function() {
1459                console.error("An error occurred while fetching available quantity");
1460            }
1461        });
1462    }
1463}
1464
1465function checkRequiredItems(targetForm, dataString, formId, type = null) {
1466    var formPaymentContainer = $(`#formPaymentContainer`);
1467    const cartSessionData = formPaymentContainer.find('#formPaymentForm input[name="cart_data"]').val();
1468
1469    if ($('input[name="is_required"]').length > 0) {
1470        const requiredItems = [];
1471        const requiredFields = $('input[name="is_required"]').filter(function() {
1472            // return $(this).val() === '1';
1473            const isRequired = $(this).val() === '1';
1474
1475            const closestRow = $(this).closest('.form-row, [id^="row-"]');
1476            const isRowHidden = closestRow.hasClass('hidden');
1477
1478            return isRequired && !isRowHidden;
1479        });
1480
1481        if (requiredFields.length > 0) {
1482            requiredFields.each(function() {
1483                const isRequired = $(this).val();
1484                const formDataId = $(this).attr('data-form-data-id');
1485
1486                // Add item details to the required items array
1487                requiredItems.push({
1488                    form_data_id: formDataId,
1489                    is_required: isRequired
1490                });
1491            });
1492
1493            $.ajax({
1494                url: '/bloqs/bloq-forms/check-required-items',
1495                method: 'POST',
1496                data: {
1497                    required_items: requiredItems,
1498                    form_id: formId,
1499                    is_bloq_form_id: false
1500                },
1501                headers: {
1502                    'X-CSRF-Token': csrfToken
1503                },
1504                success: function(response) {
1505                    if (response.status == "success") {
1506                        // togglePaymentFormslider(dataString);
1507                        if (type == 'viewForm') {
1508                            if (cartSessionData) {
1509                                togglePaymentFormContainer(targetForm, dataString, null, formId);
1510                            } else {
1511                                saveBloqForm(targetForm, dataString, formId);
1512                            }
1513                        } else if (type == 'previewForm') {
1514                            if (cartSessionData) {
1515                                togglePaymentFormContainer(targetForm, dataString, null, formId, 'previewForm');
1516                            } else {
1517                                formPaymentContainer.html(`<div class="mx-16 my-8 flex flex-col justify-center items-center"><div>Your Transaction Id: XXXXXXXX999 </div>
1518                                <div>You can see your transaction <a href="/payments/plans" class="font-bold text-blue-600"> Here</a></div></div>`);
1519                    
1520                                $(`#containerForm`).addClass('hidden');
1521                                $(`#thankYouContainerForm`).removeClass('hidden');
1522                            }
1523                        }
1524                    } else {
1525                        const missingItems = response.missing_items;
1526                        missingItems.forEach(function(formDataId) {
1527                            const requiredInput = $(`input[name="is_required"][data-form-data-id="${formDataId}"]`);
1528                            const itemContainer = requiredInput.closest('.item');
1529                            
1530                            // Add a span with an error message after the item container if it doesn't already exist
1531                            if (itemContainer.next('.error-message').length === 0) {
1532                                itemContainer.after('<span class="error-message text-red-500 text-sm">This item is required and must be added to the cart.</span>');
1533                            }
1534                        });
1535                    }
1536                },
1537                error: function(xhr, status, error) {
1538                    console.error('Error saving cart', error);
1539                }
1540            });
1541        } else {
1542            // togglePaymentFormslider(dataString);
1543            if (type == 'viewForm') {
1544                if (cartSessionData) {
1545                    togglePaymentFormContainer(targetForm, dataString, null, formId);
1546                } else {
1547                    saveBloqForm(targetForm, dataString, formId);
1548                }
1549            } else if (type == 'previewForm') {
1550                if (cartSessionData) {
1551                    togglePaymentFormContainer(targetForm, dataString, null, formId, 'previewForm');
1552                } else {
1553                    formPaymentContainer.html(`<div class="mx-16 my-8 flex flex-col justify-center items-center"><div>Your Transaction Id: XXXXXXXX999 </div>
1554                    <div>You can see your transaction <a href="/payments/plans" class="font-bold text-blue-600"> Here</a></div></div>`);
1555        
1556                    $(`#containerForm`).addClass('hidden');
1557                    $(`#thankYouContainerForm`).removeClass('hidden');
1558                }
1559            }
1560        }
1561    } else {
1562        // togglePaymentFormslider(dataString);
1563        if (type == 'viewForm') {
1564            if (cartSessionData) {
1565                togglePaymentFormContainer(targetForm, dataString, null, formId);
1566            } else {
1567                saveBloqForm(targetForm, dataString, formId);
1568            }
1569        } else if (type == 'previewForm') {
1570            if (cartSessionData) {
1571                togglePaymentFormContainer(targetForm, dataString, null, formId, 'previewForm');
1572            } else {
1573                formPaymentContainer.html(`<div class="mx-16 my-8 flex flex-col justify-center items-center"><div>Your Transaction Id: XXXXXXXX999 </div>
1574                <div>You can see your transaction <a href="/payments/plans" class="font-bold text-blue-600"> Here</a></div></div>`);
1575    
1576                $(`#containerForm`).addClass('hidden');
1577                $(`#thankYouContainerForm`).removeClass('hidden');
1578            }
1579        }
1580    }
1581}
1582
1583function togglePaymentFormContainer(targetForm, dataString, bloqFormId, formId, type = null) {
1584    if (!validateTotalFileSize(targetForm)) {
1585        return false;
1586    }
1587
1588    if (bloqFormId) {
1589        var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1590        $(`#containerForm_${bloqFormId}`).addClass('hidden');
1591    } else {
1592        var formPaymentContainer = $(`#formPaymentContainer`);
1593        $(`#containerForm`).addClass('hidden');
1594    }
1595    const formData = getFormPostData(targetForm);
1596    displayFormData(formData,formPaymentContainer);
1597
1598    const isSignatureValid = validateSignatureFields(targetForm);
1599
1600    if (!isSignatureValid) {
1601        if (bloqFormId) {
1602            $(`#containerForm_${bloqFormId}`).removeClass('hidden');
1603        } else {
1604            $(`#containerForm`).removeClass('hidden');
1605        }
1606        return false;
1607    }
1608
1609    formPaymentContainer.removeClass('hidden');
1610
1611    $('html, body').animate({
1612        scrollTop: formPaymentContainer.offset().top
1613    }, 100);
1614
1615    formPaymentContainer.find("#formErrors").html('');
1616
1617    if (dataString) {
1618        formPaymentContainer.find('#formPaymentForm input[name="post_data"]').val(JSON.stringify(dataString));
1619
1620        let totalAmount = formPaymentContainer.find('#itemSummary .totalAmount').text();
1621        formPaymentContainer.find('#formPaymentForm #payNowBtn').text(`Pay Now (${totalAmount})`);
1622        formPaymentContainer.find('#formPaymentForm input[name="total"]').val(`${totalAmount}`);
1623
1624        const cartSessionData = formPaymentContainer.find('#formPaymentForm input[name="cart_data"]').val();
1625
1626        const cartData = cartSessionData ? JSON.parse(cartSessionData) : null;
1627
1628        let hasNegativeDifference = false;
1629
1630        if (cartData && cartData.items) {
1631            hasNegativeDifference = cartData.items.some(item => item.difference <= 0);
1632        }
1633
1634        if (hasNegativeDifference) {
1635            formPaymentContainer.find('#formPaymentForm #payNowBtn').hide();
1636            formPaymentContainer.find('#formPaymentForm #payNowMessage').removeClass('hidden');
1637        } else {
1638            formPaymentContainer.find('#formPaymentForm #payNowBtn').show();
1639            formPaymentContainer.find('#formPaymentForm #payNowMessage').addClass('hidden');
1640        }
1641
1642        var zipCodeValue = formPaymentContainer.find('#formPaymentForm #zipCode').val();
1643        if (zipCodeValue.length > 0) {
1644            fetchSalesTax(zipCodeValue, bloqFormId);
1645        } else {
1646            updateSalesTax(0, bloqFormId);
1647        }
1648    }
1649
1650    formPaymentContainer.find('#formPaymentForm #zipCode').blur(function() {
1651        var zipcode = $(this).val();
1652
1653        if (zipcode.length > 0) {
1654            fetchSalesTax(zipcode, bloqFormId);
1655        } else {
1656            updateSalesTax(0, bloqFormId);
1657        }
1658    });
1659
1660    formPaymentContainer.find('#formPaymentForm #cvv').inputmask('999');
1661    formPaymentContainer.find('#formPaymentForm #cardNumber').inputmask("9999 9999 9999 9999");
1662    formPaymentContainer.find('#formPaymentForm #phone').inputmask("(999) 999-9999");
1663    formPaymentContainer.find('#formPaymentForm #zipCode').inputmask("99999");
1664
1665    formPaymentContainer.find('#formPaymentForm #expiry').inputmask('99/99', {
1666        clearIncomplete: true,
1667        onincomplete: () => formPaymentContainer.find('#formPaymentForm #expiry-error').addClass('hidden')
1668    });
1669
1670    formPaymentContainer.find('#formPaymentForm #expiry').on('input', function () {
1671        const month = $(this).val().substring(0, 2).replace(/\D/g, '');
1672
1673        // Auto-format single digit months to two digits
1674        if (month.length === 1 && month > 1) {
1675            $(this).val('0' + month);
1676        }
1677
1678        if (month.length === 2) {
1679            const monthNum = parseInt(month, 10);
1680            formPaymentContainer.find('#formPaymentForm #expiry-error').toggleClass('hidden', monthNum >= 1 && monthNum <= 12);
1681        }
1682    });
1683
1684    formPaymentContainer.find('#applyBtn').off('click').on('click', function () {
1685        checkCoupon(formId, bloqFormId);
1686    });
1687
1688    formPaymentContainer.find('#formPaymentForm').off('submit').on('submit', function(e) {
1689        e.preventDefault();
1690        var dataString = $(this).serialize();
1691
1692        if (type) {
1693            formPaymentContainer.html(`<div class="mx-16 my-8 flex flex-col justify-center items-center"><div>Your Transaction Id: XXXXXXXX999 </div>
1694            <div>You can see your transaction <a href="/payments/plans" class="font-bold text-blue-600"> Here</a></div></div>`);
1695
1696            if (bloqFormId) {
1697                $(`#containerForm_${bloqFormId}`).addClass('hidden');
1698                $(`#thankYouContainerForm_${bloqFormId}`).removeClass('hidden');
1699            } else {
1700                $(`#containerForm`).addClass('hidden');
1701                $(`#thankYouContainerForm`).removeClass('hidden');
1702            }
1703        } else {
1704            const newFormData = new FormData();
1705
1706            // Append form data
1707            newFormData.append('form_data', JSON.stringify(dataString));
1708
1709            // Append files separately
1710            targetForm.find('input[type="file"]').each(function () {
1711                const files = this.files;
1712                const fieldName = $(this).attr("name");
1713
1714                // Check if the input has the 'multiple' attribute
1715                const isMultiple = $(this).attr("multiple") !== undefined;
1716
1717                for (let i = 0; i < files.length; i++) {
1718                    // If it's a single file upload, enforce array format manually
1719                    const formattedFieldName = isMultiple ? `${fieldName}[]` : `${fieldName}[][]`;
1720
1721                    newFormData.append(formattedFieldName, files[i]); // Ensure uniform structure
1722                }
1723            });
1724
1725            // Add signature data as files
1726            targetForm.find('.signature-data').each(function(index) {
1727                const signatureData = $(this).val();
1728                if (signatureData) {
1729                    // Convert base64 signature to Blob
1730                    const signatureBlob = dataURLtoBlob(signatureData);
1731                    // Create a file from the blob
1732                    const signatureFile = new File([signatureBlob], `signature_${index}.png`, { type: 'image/png' });
1733                    // Append to FormData
1734                    newFormData.append('signature[][]', signatureFile);
1735                }
1736            });
1737
1738            let isAnyCheckboxChecked = false;
1739            targetForm.find('input[name^="subscribe"]').each(function(index) {
1740                const checkbox = $(this);
1741
1742                if (checkbox.is(':checkbox') && checkbox.is(':checked')) {
1743                    isAnyCheckboxChecked = true;
1744                }
1745            });
1746
1747            if (isAnyCheckboxChecked) {
1748                newFormData.append('subscribe_checked', 'yes');
1749            }
1750
1751            $.ajax({
1752                type: "POST",
1753                url: $('#formPaymentPayFormArea #formPaymentForm').attr('action'),
1754                // data: dataString,
1755                data: newFormData,
1756                processData: false,
1757                contentType: false,
1758                headers: {
1759                    'X-CSRF-Token': $('meta[name="csrfToken"]').attr('content')
1760                },
1761                beforeSend: function () {
1762                    const submitBtn = formPaymentContainer.find("#payNowBtn");
1763                    submitBtn.prop('disabled', true);
1764                    submitBtn.html(`<svg aria-hidden="true" role="status" class="inline w-4 h-4 text-white animate-spin"  viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
1765                    <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="#E5E7EB"></path>
1766                    <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.
17661071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentColor"></path>
1767                    </svg>`);
1768                },
1769                dataType: "json",
1770                success: function(response) {
1771    
1772                    if(response.status == 'success'){
1773                        formPaymentContainer.html(`<div class="mx-16 my-8 flex flex-col justify-center items-center"><div>Your Transaction Id: ${response.transaction_id} </div>
1774                        <div>You can see your transaction <a href="/payments/plans" class="font-bold text-blue-600"> Here</a></div></div>`);
1775    
1776                        if (bloqFormId) {
1777                            $(`#containerForm_${bloqFormId}`).addClass('hidden');
1778                            $(`#thankYouContainerForm_${bloqFormId}`).removeClass('hidden');
1779                        } else {
1780                            $(`#containerForm`).addClass('hidden');
1781                            $(`#thankYouContainerForm`).removeClass('hidden');
1782                        }
1783                    }else{
1784                        formPaymentContainer.find("#formErrors").html(response.message);
1785                        var payAmount = $("#formPaymentForm input[name='total']").val();
1786                        formPaymentContainer.find("#formPaymentForm #payNowBtn").html('Pay Now ($' + payAmount + ')');
1787                        const submitBtn = formPaymentContainer.find('#payNowBtn');
1788                        submitBtn.prop('disabled', false);
1789                    }
1790                }
1791            });
1792        }
1793    });
1794}
1795
1796function checkCoupon(formId, bloqFormId) {
1797    if (bloqFormId) {
1798        var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1799    } else {
1800        var formPaymentContainer = $(`#formPaymentContainer`);
1801    }
1802
1803    let couponInput = formPaymentContainer.find('input[name="coupon_code"]');
1804    let couponCode = couponInput.val().trim();
1805    let errorMsg = formPaymentContainer.find('#couponErrorMsg');
1806
1807    if (couponCode === '') {
1808        errorMsg.text('Please enter a coupon code.').removeClass('hidden');
1809        return;
1810    } else {
1811        errorMsg.addClass('hidden');
1812    }
1813
1814
1815    $.ajax({
1816        url: '/bloqs/bloq-forms/check-coupon',
1817        type: 'POST',
1818        data: {
1819            coupon_code: couponCode,
1820            form_id: formId
1821        },
1822        headers: {
1823            'X-CSRF-Token': $('meta[name="csrfToken"]').attr('content')
1824        },
1825        success: function (response) {
1826            if (response.status == 'success') {
1827                formPaymentContainer.find('#formPaymentForm input[name="coupon_id"]').val(response.coupon.id);
1828
1829                errorMsg.addClass('hidden');
1830                formPaymentContainer.find('#applyBtn').addClass('hidden');
1831                formPaymentContainer.find('#removeCoupon').removeClass('hidden');
1832                formPaymentContainer.find('#discountContainer').removeClass('hidden');
1833
1834                couponInput.attr('readonly', true);
1835
1836                const subTotalElement = formPaymentContainer.find('#itemSummary .subTotal');
1837                const currentSubTotalAmount = parseFloat(subTotalElement.text().replace('$', ''));
1838
1839                var couponText = `Discount`;
1840                var discountAmount = response.coupon.value;
1841
1842                if (response.coupon.type == 'percent') {
1843                    couponText = `Discount (${response.coupon.value}%)`;
1844                    discountAmount = (currentSubTotalAmount * response.coupon.value) / 100;
1845                }
1846
1847                formPaymentContainer.find('.discountText').text(couponText);
1848                formPaymentContainer.find('.discountPrice').text(`- $${discountAmount.toFixed(2)}`);
1849
1850                calculateTax(bloqFormId);
1851
1852                formPaymentContainer.find('#removeCoupon').off('click').on('click', function () {
1853                    removeCoupon(discountAmount, bloqFormId);
1854                });
1855            } else {
1856                errorMsg.text(response.message).removeClass('hidden');
1857            }
1858        },
1859        error: function () {
1860            errorMsg.text('Something went wrong. Please try again.').removeClass('hidden');
1861        }
1862    });
1863}
1864
1865function removeCoupon(discountAmount, bloqFormId) {
1866    if (bloqFormId) {
1867        var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1868    } else {
1869        var formPaymentContainer = $(`#formPaymentContainer`);
1870    }
1871
1872    formPaymentContainer.find('input[name="coupon_code"]').val('');
1873    formPaymentContainer.find('#discountContainer').addClass('hidden');
1874    formPaymentContainer.find('.discountText').text('Discount');
1875    formPaymentContainer.find('.discountPrice').text('');
1876    formPaymentContainer.find('#formPaymentForm input[name="coupon_id"]').val('');
1877
1878    const totalAmountElement = formPaymentContainer.find('#itemSummary .totalAmount');
1879    const currentTotalAmount = parseFloat(totalAmountElement.text().replace('$', ''));
1880    const originalTotalAmount = (currentTotalAmount + discountAmount);
1881
1882    totalAmountElement.text(`$${originalTotalAmount.toFixed(2)}`);
1883
1884    formPaymentContainer.find('#formPaymentForm #discountAmount').text('').parent().addClass('hidden');
1885    formPaymentContainer.find('#applyBtn').removeClass('hidden');
1886    formPaymentContainer.find('#removeCoupon').addClass('hidden');
1887
1888    formPaymentContainer.find('input[name="coupon_code"]').removeAttr('readonly');
1889
1890    calculateTax(bloqFormId);
1891}
1892
1893function fetchSalesTax(zipcode, bloqFormId) {
1894    $.ajax({
1895        url: '/payments/getsalesTax/' + zipcode,
1896        method: 'GET',
1897        dataType: "json",
1898        success: function(response) {
1899            if (response.status == 'success' && response.tax_data) {
1900                salesTax = response.tax_data.total_rate;
1901
1902                if (bloqFormId) {
1903                    var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1904                } else {
1905                    var formPaymentContainer = $(`#formPaymentContainer`);
1906                }
1907                formPaymentContainer.find('#formPaymentForm input[name="tax_data"]').val(JSON.stringify(response));
1908                updateSalesTax(salesTax, bloqFormId);
1909            } else {
1910                updateSalesTax(0,bloqFormId);
1911            }
1912        },
1913        error: function(xhr, status, error) {
1914            console.log("Error: " + error);
1915            updateSalesTax(0, bloqFormId);
1916        }
1917    });
1918}
1919
1920function updateSalesTax(taxRate, bloqFormId) {
1921    salesTax = taxRate;
1922    const salesTaxPercent = (salesTax * 100).toFixed(2);
1923
1924    if (bloqFormId) {
1925        var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1926    } else {
1927        var formPaymentContainer = $(`#formPaymentContainer`);
1928    }
1929    formPaymentContainer.find("#itemSummary .taxRate").text(`(${salesTaxPercent}%)`);
1930    calculateTax(bloqFormId);
1931}
1932
1933function calculateTax(bloqFormId) {
1934    if (bloqFormId) {
1935        var formPaymentContainer = $(`#formPaymentContainer_${bloqFormId}`);
1936    } else {
1937        var formPaymentContainer = $(`#formPaymentContainer`);
1938    }
1939    const itemSummary = formPaymentContainer.find('#itemSummary');
1940    const tableRows = itemSummary.find('tbody tr');
1941    let taxAmount = 0.00;
1942
1943    // Get Transaction Fee Settings
1944    const transactionFeeElement = formPaymentContainer.find('#showTransactionFee');
1945
1946    let includeTransactionFee = false;
1947    let transactionFeePercent = 0;
1948
1949    if (transactionFeeElement.length > 0) {
1950        includeTransactionFee = transactionFeeElement.data('include') == 1;
1951        transactionFeePercent = parseFloat(transactionFeeElement.data('fee-percent')) || 0;
1952    }
1953
1954    tableRows.each(function() {
1955        const row = $(this);
1956        const totalElement = row.find('.itemTotal');
1957        const isTaxableCell = row.find('.isTaxableCell');
1958
1959        if (totalElement && isTaxableCell) {
1960            const total = parseFloat(totalElement.text().replace('$', ''));
1961            const isTaxableValue = isTaxableCell.text().trim();
1962            const isTaxable = isTaxableValue == 1 || isTaxableValue === 'true';
1963
1964            if (isTaxable && salesTax > 0) {
1965                taxAmount += total * salesTax;
1966            }
1967        }
1968    });
1969
1970    taxAmount = parseFloat(taxAmount.toFixed(2));
1971    formPaymentContainer.find("#itemSummary .taxAmount").text(`$${taxAmount.toFixed(2)}`);
1972    formPaymentContainer.find('#formPaymentForm input[name="tax"]').val(taxAmount);
1973
1974    const subTotalElement = itemSummary.find('.subTotal');
1975    const totalAmountElement = itemSummary.find('.totalAmount');
1976    const currentSubTotalAmount = parseFloat(subTotalElement.text().replace('$', ''));
1977
1978    // var newTotalAmount = (currentSubTotalAmount + taxAmount).toFixed(2);
1979    var newTotalAmount = currentSubTotalAmount;
1980
1981    const discountPriceElement = itemSummary.find('.discountPrice');
1982    if (discountPriceElement.length !== 0 && discountPriceElement.text().trim() !== '') {
1983        const currentdiscountPriceAmount = parseFloat(discountPriceElement.text().replace('- $', ''));
1984        formPaymentContainer.find('#formPaymentForm #discountAmount').text(discountPriceElement.text()).parent().removeClass('hidden');
1985
1986        const totalAmountAfterDiscount = (currentSubTotalAmount - currentdiscountPriceAmount);
1987        // newTotalAmount = (totalAmountAfterDiscount + taxAmount).toFixed(2);
1988        newTotalAmount = totalAmountAfterDiscount;
1989
1990        if (newTotalAmount < 1) {
1991            // Remove the coupon and show error
1992            removeCoupon(currentdiscountPriceAmount, bloqFormId);
1993            formPaymentContainer.find('#couponErrorMsg').text('This coupon cannot be applied as it would reduce the total amount below $1.00.').removeClass('hidden');
1994            return;
1995        }
1996    }
1997
1998    newTotalAmount = newTotalAmount + taxAmount;
1999    let summaryTotalAmount = newTotalAmount + taxAmount;
2000    // Apply Transaction Fee
2001    let transactionFeeAmount = 0;
2002
2003    if (includeTransactionFee && transactionFeePercent > 0) {
2004        transactionFeeAmount = (newTotalAmount * transactionFeePercent) / 100;
2005    }
2006
2007    newTotalAmount = newTotalAmount + transactionFeeAmount;
2008
2009    newTotalAmount = parseFloat(newTotalAmount.toFixed(2));
2010    summaryTotalAmount = parseFloat(summaryTotalAmount.toFixed(2));
2011    transactionFeeAmount = parseFloat(transactionFeeAmount.toFixed(2));
2012
2013    if (includeTransactionFee && transactionFeePercent > 0) {
2014        transactionFeeElement.removeClass('hidden');
2015        formPaymentContainer.find('#TransactionFeeTotal')
2016            .text(`$${transactionFeeAmount.toFixed(2)}`);
2017    } else {
2018        transactionFeeElement.addClass('hidden');
2019    }
2020
2021    totalAmountElement.text(`$${summaryTotalAmount}`);
2022
2023    formPaymentContainer.find('#formPaymentForm #subTotal').text(`$${currentSubTotalAmount.toFixed(2)}`);
2024    formPaymentContainer.find('#formPaymentForm #totalTax').text(`$${taxAmount}`);
2025    formPaymentContainer.find('#formPaymentForm #totalAmount').text(`$${newTotalAmount}`);
2026
2027    formPaymentContainer.find('#formPaymentForm #payNowBtn').text(`Pay Now ($${newTotalAmount})`);
2028    formPaymentContainer.find('#formPaymentForm input[name="total"]').val(newTotalAmount);
2029}
2030
2031/* ------------------------------------------------------------------
2032 * Anti-abuse helpers for Form Builder submissions.
2033 * The server (BloqFormsController::saveFormInfo) requires:
2034 *   - form_token      : signed token issued when the form page was rendered
2035 *   - recaptcha_token : reCAPTCHA Enterprise token (only when a site key is configured)
2036 * Templates expose window.bloqsFormTokens[formId] and window.bloqsRecaptchaSiteKey.
2037 * ------------------------------------------------------------------ */
2038const BLOQS_FORM_RECAPTCHA_ACTION = 'form_builder_submit';
2039
2040function bloqsGetFormToken(formId) {
2041    if (window.bloqsFormTokens && formId && window.bloqsFormTokens[formId]) {
2042        return window.bloqsFormTokens[formId];
2043    }
2044    return '';
2045}
2046
2047function bloqsGetFormIdFromAction(targetForm) {
2048    const action = (targetForm && targetForm.attr) ? (targetForm.attr('action') || '') : '';
2049    const match = action.match(/save-form-info\/(\d+)/);
2050    return match ? match[1] : null;
2051}
2052
2053function bloqsGetRecaptchaToken() {
2054    return new Promise(function(resolve) {
2055        const siteKey = window.bloqsRecaptchaSiteKey || '';
2056        if (!siteKey || typeof grecaptcha === 'undefined' || !grecaptcha.enterprise) {
2057            resolve(null);
2058            return;
2059        }
2060        // Never leave the submit button stuck if Google is slow/unreachable
2061        let settled = false;
2062        const finish = function(value) {
2063            if (!settled) {
2064                settled = true;
2065                resolve(value);
2066            }
2067        };
2068        setTimeout(function() { finish(null); }, 10000);
2069        try {
2070            grecaptcha.enterprise.ready(function() {
2071                grecaptcha.enterprise.execute(siteKey, { action: BLOQS_FORM_RECAPTCHA_ACTION })
2072                    .then(finish)
2073                    .catch(function(err) {
2074                        console.error('reCAPTCHA token generation failed', err);
2075                        finish(null);
2076                    });
2077            });
2078        } catch (e) {
2079            console.error('reCAPTCHA error', e);
2080            finish(null);
2081        }
2082    });
2083}
2084
2085// Appends form_token + recaptcha_token to a FormData; resolves when ready to send.
2086function bloqsAppendAntiAbuseFields(formData, formId) {
2087    formData.append('form_token', bloqsGetFormToken(formId));
2088    return bloqsGetRecaptchaToken().then(function(token) {
2089        if (token) {
2090            formData.append('recaptcha_token', token);
2091        }
2092        return formData;
2093    });
2094}
2095
2096function postSaveFormInfoRequest(targetForm, dataString, formId, bloqFormId) {
2097    const newFormData = new FormData();
2098
2099    newFormData.append('form_data', JSON.stringify(dataString));
2100
2101    targetForm.find('input[type="file"]').each(function () {
2102        const files = this.files;
2103        const fieldName = $(this).attr('name');
2104        const isMultiple = $(this).attr('multiple') !== undefined;
2105
2106        for (let i = 0; i < files.length; i++) {
2107            const formattedFieldName = isMultiple ? `${fieldName}[]` : `${fieldName}[][]`;
2108            newFormData.append(formattedFieldName, files[i]);
2109        }
2110    });
2111
2112    targetForm.find('.signature-data').each(function(index) {
2113        const signatureData = $(this).val();
2114        if (signatureData) {
2115            const signatureBlob = dataURLtoBlob(signatureData);
2116            const signatureFile = new File([signatureBlob], `signature_${index}.png`, { type: 'image/png' });
2117            newFormData.append('signature[][]', signatureFile);
2118        }
2119    });
2120
2121    var websiteValue = targetForm.find('input[name="website"]').val();
2122    if (websiteValue === undefined || websiteValue === null) {
2123        websiteValue = $('#toEmailContainer input[name="website"]').val();
2124    }
2125    newFormData.append('website', websiteValue || '');
2126
2127    const submitBtn = targetForm.find('button[type="submit"]');
2128
2129    if (submitBtn.data('bloqsOrigHtml') === undefined) {
2130        submitBtn.data('bloqsOrigHtml', submitBtn.html());
2131    }
2132    submitBtn.prop('disabled', true);
2133
2134    bloqsAppendAntiAbuseFields(newFormData, formId).then(function() {
2135        $.ajax({
2136            type: "POST",
2137            url: '/bloqs/bloq-forms/save-form-info/' + formId,
2138            data: newFormData,
2139            processData: false,
2140            contentType: false,
2141            dataType: "json",
2142            headers: {
2143                'X-CSRF-Token': csrfToken
2144            },
2145            beforeSend: function () {
2146                submitBtn.html(`<svg aria-hidden="true" role="status" class="inline w-4 h-4 text-white animate-spin"  viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
2147                <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="#E5E7EB"></path>
2148                <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentColor"></path>
2149                </svg>`);
2150            },
2151            success: function(response) {
2152                if (response.status == "success") {
2153                    if (bloqFormId) {
2154                        $(`#containerForm_${bloqFormId}`).addClass('hidden');
2155                        $(`#thankYouContainerForm_${bloqFormId}`).removeClass('hidden');
2156                    } else {
2157                        $(`#containerForm`).addClass('hidden');
2158                        $(`#thankYouContainerForm`).removeClass('hidden');
2159                    }
2160                } else {
2161                    submitBtn.prop('disabled', false).html(submitBtn.data('bloqsOrigHtml'));
2162                    alert(response.message);
2163                }
2164            },
2165            error: function() {
2166                submitBtn.prop('disabled', false).html(submitBtn.data('bloqsOrigHtml'));
2167                alert("An error occurred. Please try again.");
2168            }
2169        });
2170    });
2171}
2172
2173function saveBloqForm(targetForm, dataString, formId, bloqFormId = null) {
2174    if (formId && dataString) {
2175        if (!validateTotalFileSize(targetForm)) {
2176            return false;
2177        }
2178        if (!validateSignatureFields(targetForm)) {
2179            return false;
2180        }
2181        postSaveFormInfoRequest(targetForm, dataString, formId, bloqFormId);
2182    }
2183}
2184
2185function hideFlash() {
2186    const successMessage = document.querySelector('.custom-msg-success');
2187    if (successMessage) {
2188        // Hide the message after 5 seconds
2189        setTimeout(() => {
2190            setTimeout(() => successMessage.style.display = 'none', successMessage.style.opacity = 0, 500);
2191        }, 2000);
2192    }
2193}
2194
2195function getFormPostData(targetForm, formId = null, bloqFormId = null) {
2196    try {
2197        var formData = {};
2198
2199        if (formId) {
2200            formData.form_id = formId;
2201        }
2202
2203        if (bloqFormId) {
2204            formData.bloq_form_id = bloqFormId;
2205        }
2206
2207        // List of fields to exclude
2208        const excludedFields = [
2209            'is_required',
2210            'is_taxable',
2211            'other_amount',
2212            'quantity'
2213        ];
2214        
2215        // Process each form element
2216        targetForm.find('input, select, textarea').each(function() {
2217            var element = $(this);
2218            var name = element.attr('name');
2219
2220            var type = element.attr('type');
2221            var tag = element.prop('tagName').toLowerCase();
2222
2223            var isCheckedOrSelected = false;
2224
2225            // Check if the element is checked or selected
2226            if ((type === 'checkbox' || type === 'radio') && element.is(':checked')) {
2227                isCheckedOrSelected = true;
2228            } else if (tag === 'select' && element.val()) {
2229                isCheckedOrSelected = true;
2230            }
2231
2232            // If it's checked/selected and has a prevRowId
2233            if (!isCheckedOrSelected) {
2234                var prevRowId = element.data('prev-row-id');
2235
2236                if (prevRowId) {
2237                    var fieldNames = [];
2238
2239                    $('#' + prevRowId).find('input[name], select[name], textarea[name]').each(function () {
2240                        var fieldName = $(this).attr('name');
2241                        if (fieldName) {
2242                            fieldNames.push(fieldName);
2243                            excludedFields.push(fieldName);
2244                        }
2245                    });
2246                }
2247            }
2248
2249            var subscribeField = [];
2250
2251            $('input[name^="subscribe"]').each(function () {
2252                var fieldName = $(this).attr('name');
2253                if (fieldName) {
2254                    subscribeField.push(fieldName);
2255                    excludedFields.push(fieldName);
2256                }
2257            });
2258
2259            // Skip if no name attribute or hidden CSRF token
2260            if (!name || name === '_token' || excludedFields.includes(name)) return;
2261            
2262            // Remove array notation from checkbox names
2263            var cleanName = name.replace('[]', '');
2264            
2265            // Find the label - first try to find group label
2266            var label = '';
2267
2268            if (element.attr('id') && element.attr('type') !== 'checkbox' && element.attr('type') !== 'radio' && !element.is('select')) {
2269                label = $('label[for="' + element.attr('id') + '"]').first().text().trim();
2270            }
2271
2272            var container = element.closest('.mb-6, .form-group');
2273            if (container.length) {
2274                if (name.includes('agree')) {
2275                    label = 'agree';
2276                } else {
2277                    if (!label) {
2278                        // First, try to find a direct label element
2279                        label = container.find('> label, > span').first().text().trim();
2280                    }
2281                    
2282                    if (!label) {
2283                        label = container.find('label:first, span:first').text().trim();
2284                    }
2285                }
2286            }
2287            
2288            // If still no label found, try finding by input id
2289            // if (!label) {
2290            //     label = $('label[for="' + element.attr('id') + '"]').first().text().trim();
2291            // }
2292
2293            // Skip if no label found
2294            if (!label) {
2295                return;
2296            }
2297            
2298            // Remove the asterisk from required fields
2299            label = label.replace('*', '').trim();
2300            
2301            // Initialize the data structure if it doesn't exist
2302            if (!formData[cleanName]) {
2303                formData[cleanName] = {
2304                    label: label,
2305                    value: null
2306                };
2307            }
2308            
2309            switch(element.attr('type')) {
2310                case 'file':
2311                    // Handle multiple files
2312                    var fileList = [];
2313                    var files = element[0].files;
2314                    for (var i = 0; i < files.length; i++) {
2315                        fileList.push(files[i].name);
2316                    }
2317                    formData[cleanName].value = fileList;
2318                    formData[cleanName].type = 'file';
2319                    break;
2320
2321                case 'checkbox':
2322                    if (name.includes('agree')) {
2323                        // Handle agree checkboxes
2324                        formData[cleanName].value = element.is(':checked') ? "Yes" : "No";
2325                    } else if (!name.includes('switch')) {
2326                        // Handle normal checkboxes
2327                        if (!Array.isArray(formData[cleanName].value)) {
2328                            formData[cleanName].value = [];
2329                            formData[cleanName].options = [];
2330                        }
2331                        
2332                        // For checkboxes, collect all options in the same container
2333                        var checkboxContainer = element.closest('.mb-6, .form-group');
2334                        if (!formData[cleanName].options.length) {
2335                            checkboxContainer.find('input[type="checkbox"]').each(function() {
2336                                var optionLabel = $('label[for="' + $(this).attr('id') + '"]').text().trim();
2337                                formData[cleanName].options.push(optionLabel);
2338                            });
2339                        }
2340                        
2341                        // Add to values if checked
2342                        if (element.is(':checked')) {
2343                            formData[cleanName].value.push(element.val());
2344                        }
2345                    }
2346                    // Handle switch type checkbox
2347                    else {
2348                        if (!formData[cleanName].options) {
2349                            formData[cleanName].options = [];
2350                            // Find switch labels
2351                            var switchContainer = element.closest('label');
2352                            switchContainer.find('label').each(function() {
2353                                if ($(this).text().trim() !== '') {
2354                                    formData[cleanName].options.push($(this).text().trim());
2355                                }
2356                            });
2357                        }
2358                        formData[cleanName].value = element.is(':checked') ? 
2359                            formData[cleanName].options[1] : 
2360                            formData[cleanName].options[0];
2361                    }
2362                    break;
2363                    
2364                case 'radio':
2365                    if (!formData[cleanName].options) {
2366                        formData[cleanName].options = [];
2367                    }
2368                    
2369                    // For radio buttons, collect all options in the same container
2370                    var radioContainer = element.closest('.mb-6, .form-group');
2371                    if (!formData[cleanName].options.length) {
2372                        radioContainer.find('input[type="radio"]').each(function() {
2373                            var optionLabel = $('label[for="' + $(this).attr('id') + '"]').text().trim();
2374                            formData[cleanName].options.push(optionLabel);
2375                        });
2376                    }
2377                    
2378                    // Set value if checked
2379                    if (element.is(':checked')) {
2380                        formData[cleanName].value = element.val();
2381                    }
2382                    break;
2383                    
2384                case 'hidden':
2385                    // Skip hidden inputs for switches as they're handled by the checkbox
2386                    if (!element.next('input[type="checkbox"]').length) {
2387                        formData[cleanName].value = element.val();
2388                    }
2389                    break;
2390                    
2391                case 'textarea':
2392                    formData[cleanName].value = element.val().trim();
2393                    break;
2394
2395                case 'time':
2396                    // Convert 24-hour time to 12-hour format with AM/PM
2397                    const timeVal = element.val();
2398                    if (timeVal) {
2399                        const [hours, minutes] = timeVal.split(':');
2400                        const hour = parseInt(hours, 10);
2401                        const ampm = hour >= 12 ? 'PM' : 'AM';
2402                        const hour12 = hour % 12 || 12;
2403                        formData[cleanName].value = `${hour12}:${minutes} ${ampm}`;
2404                    } else {
2405                        formData[cleanName].value = timeVal;
2406                    }
2407                    break;
2408
2409                default:
2410                    formData[cleanName].value = element.val();
2411            }
2412        });
2413        
2414        // Convert checkbox arrays to single values if only one selection
2415        Object.keys(formData).forEach(key => {
2416            if (Array.isArray(formData[key].value) && formData[key].value.length === 1) {
2417                formData[key].value = formData[key].value[0];
2418            }
2419        });
2420
2421        return formData;
2422    } catch (error) {
2423        console.error('Error processing form data:', error);
2424        return null;
2425    }
2426}
2427
2428function collectMemberData(targetForm) {
2429    const result = [];
2430
2431    const containers = targetForm.find('.subscribe-container');
2432    if (containers.length === 0) {
2433        return false;
2434    }
2435
2436    containers.each(function () {
2437        const container = $(this);
2438        const firstName = container.find('[name^="first_name"]').val() || '';
2439        const lastName = container.find('[name^="last_name"]').val() || '';
2440        const phone = container.find('[name^="phone_number"]').val() || '';
2441        const email = container.find('[name^="email"]').val() || '';
2442
2443        if (container.find('.subscribe-phone input[type="checkbox"]').is(':checked')) {
2444            result.push({
2445                first_name: firstName,
2446                last_name: lastName,
2447                phone_number: phone,
2448                email: email,
2449                type: 'phone',
2450                sms_opt_in: 1
2451            });
2452        }
2453
2454        if (container.find('.subscribe-email input[type="checkbox"]').is(':checked')) {
2455            result.push({
2456                first_name: firstName,
2457                last_name: lastName,
2458                phone_number: phone,
2459                email: email,
2460                type: 'email',
2461                email_opt_in: 1
2462            });
2463        }
2464    });
2465
2466    return result;
2467}
2468
2469function saveStandardFormData(targetForm, bloqFormId = null, type = null) {
2470    const formData = getFormPostData(targetForm);
2471    const memberData = collectMemberData(targetForm);
2472
2473    if (!validateTotalFileSize(targetForm)) {
2474        return false;
2475    }
2476
2477    if (bloqFormId) {
2478        var toEmailContainer = $(`#toEmailContainer_${bloqFormId}`);
2479        var containerForm = $(`#containerForm_${bloqFormId}`);
2480    } else {
2481        var toEmailContainer = $(`#toEmailContainer`);
2482        var containerForm = $(`#containerForm`);
2483    }
2484
2485    const isSignatureValid = validateSignatureFields(targetForm);
2486
2487    if (isSignatureValid) {
2488        containerForm.addClass('hidden');
2489        toEmailContainer.removeClass('hidden');
2490    
2491        var emailValue= '';
2492        // Loop through all email inputs and pick the first non-empty one
2493        targetForm.find('input[type="email"]').each(function () {
2494            const val = $(this).val().trim();
2495            if (val !== '') {
2496                emailValue = val;
2497                return false;
2498            }
2499        });
2500    
2501        // Scroll to the toEmailContainer smoothly
2502        $('html, body').animate({
2503            scrollTop: toEmailContainer.offset().top
2504        }, 100);
2505    
2506        toEmailContainer.find('input[name="to_email"]').each(function () {
2507            $(this).val(emailValue);
2508        });
2509    
2510        displayFormData(formData, toEmailContainer);
2511    }
2512
2513    toEmailContainer.find('#toEmailForm').off('submit').on('submit', function(e) {
2514        e.preventDefault();
2515
2516        var dataString = $(this).serialize();
2517
2518        if (type) {
2519            toEmailContainer.addClass('hidden');
2520
2521            if (bloqFormId) {
2522                $(`#thankYouContainerForm_${bloqFormId}`).removeClass('hidden');
2523            } else {
2524                $(`#thankYouContainerForm`).removeClass('hidden');
2525            }
2526        } else {
2527            const newFormData = new FormData();
2528
2529            // Append form data
2530            newFormData.append('form_data', JSON.stringify(formData));
2531            newFormData.append('data_string', dataString);
2532
2533            // Append files separately
2534            targetForm.find('input[type="file"]').each(function () {
2535                const files = this.files;
2536                const fieldName = $(this).attr("name");
2537
2538                // Check if the input has the 'multiple' attribute
2539                const isMultiple = $(this).attr("multiple") !== undefined;
2540
2541                for (let i = 0; i < files.length; i++) {
2542                    // If it's a single file upload, enforce array format manually
2543                    const formattedFieldName = isMultiple ? `${fieldName}[]` : `${fieldName}[][]`;
2544
2545                    newFormData.append(formattedFieldName, files[i]); // Ensure uniform structure
2546                }
2547            });
2548
2549            // Add signature data as files
2550            targetForm.find('.signature-data').each(function(index) {
2551                const signatureData = $(this).val();
2552                if (signatureData) {
2553                    // Convert base64 signature to Blob
2554                    const signatureBlob = dataURLtoBlob(signatureData);
2555                    // Create a file from the blob
2556                    const signatureFile = new File([signatureBlob], `signature_${index}.png`, { type: 'image/png' });
2557                    // Append to FormData
2558                    newFormData.append('signature[][]', signatureFile);
2559                }
2560            });
2561
2562            if (memberData) {
2563                newFormData.append('member_data', JSON.stringify(memberData));
2564            }
2565
2566            const fx8FormId = bloqsGetFormIdFromAction(targetForm);
2567            const submitBtn = toEmailContainer.find(".submitBtn");
2568            if (submitBtn.data('bloqsOrigHtml') === undefined) {
2569                submitBtn.data('bloqsOrigHtml', submitBtn.html());
2570            }
2571            submitBtn.prop('disabled', true);
2572
2573            bloqsAppendAntiAbuseFields(newFormData, fx8FormId).then(function() {
2574                $.ajax({
2575                    type: "POST",
2576                    url: targetForm.attr('action'),
2577                    data: newFormData,
2578                    processData: false,
2579                    contentType: false,
2580                    dataType: "json",
2581                    headers: {
2582                        'X-CSRF-Token': csrfToken
2583                    },
2584                    beforeSend: function () {
2585                        submitBtn.html(`<svg aria-hidden="true" role="status" class="inline w-4 h-4 text-white animate-spin"  viewB
2585ox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
2586                        <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="#E5E7EB"></path>
2587                        <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentColor"></path>
2588                        </svg>`);
2589                    },
2590                    success: function(response) {
2591                        if (response.status == "success") {
2592                            toEmailContainer.addClass('hidden');
2593
2594                            if (bloqFormId) {
2595                                $(`#thankYouContainerForm_${bloqFormId}`).removeClass('hidden');
2596                            } else {
2597                                $(`#thankYouContainerForm`).removeClass('hidden');
2598                            }
2599                        } else {
2600                            submitBtn.prop('disabled', false).html(submitBtn.data('bloqsOrigHtml'));
2601                            alert(response.message);
2602                        }
2603                    },
2604                    error: function() {
2605                        submitBtn.prop('disabled', false).html(submitBtn.data('bloqsOrigHtml'));
2606                        alert("An error occurred. Please try again.");
2607                    }
2608                });
2609            });
2610        }
2611    });
2612}
2613
2614function displayFormData(formData, targetContainer = null) {
2615    // Create container
2616    const container = document.createElement('div');
2617    container.classList.add('text-sm');
2618
2619    // Iterate through form data
2620    for (const [key, value] of Object.entries(formData)) {
2621        // Create section for each field
2622        const section = document.createElement('div');
2623        section.classList.add('mb-5', 'border-b-2', 'border-dotted');
2624        
2625        // Create label
2626        const label = document.createElement('div');
2627        label.textContent = value.label;
2628        label.classList.add('block', 'font-medium', 'text-gray-700', 'mb-1');
2629        
2630        // Create value
2631        const valueElement = document.createElement('div');
2632        valueElement.classList.add('text-gray-500', 'pb-2', 'ml-2');
2633
2634        if (Array.isArray(value.value) && value.value.length > 0) {
2635            value.value.forEach(item => {
2636                const line = document.createElement('div'); // Create a new div for each item
2637                line.textContent = item;
2638                valueElement.appendChild(line);
2639            });
2640        } else {
2641            valueElement.textContent = value.value;
2642        }
2643        
2644        // Assemble section
2645        section.appendChild(label);
2646        section.appendChild(valueElement);
2647        container.appendChild(section);
2648    }
2649    
2650    // Clear any existing content and append new display
2651    if(targetContainer){
2652        const targetElement = targetContainer.find('#formSummary');
2653        targetElement.empty();
2654        targetElement.append(container);
2655    }
2656}
2657
2658function openInModal(object) {
2659    var url = object.getAttribute('data-url');
2660    openModalWithAjax(url);
2661}
2662
2663function validateSignatureFields(targetForm) {
2664    let signatureError = false;
2665
2666    targetForm.find('.signature-data').each(function (index) {
2667        if($(this).closest('.hidden').length === 0){
2668        const $input = $(this);
2669        const inputValue = $input.val();
2670        const isRequired = $input.attr('data-required') === '1';
2671
2672        $input.next('.signature-error').remove();
2673
2674        const fieldInvalid = !inputValue && isRequired;
2675
2676        debugLogSignatureField({
2677            index: index,
2678            name: $input.attr('name') || '(no name)',
2679            id: $input.attr('id') || '(no id)',
2680            required: isRequired,
2681            isEmpty: !inputValue,
2682            isValid: !fieldInvalid,
2683        });
2684
2685        if (fieldInvalid) {
2686            $('<div class="signature-error text-red-500 text-sm mt-1">Signature is required.</div>').insertAfter($input);
2687            signatureError = true;
2688        }
2689    }
2690    });
2691
2692    return signatureError ? false : true;
2693}
2694
2695function validateTotalFileSize(targetForm, maxSize = 5 * 1024 * 1024) {
2696    let totalSize = 0;
2697
2698    targetForm.find('input[type="file"]').each(function () {
2699        const files = this.files;
2700        for (let i = 0; i < files.length; i++) {
2701            totalSize += files[i].size;
2702        }
2703    });
2704
2705    if (totalSize > maxSize) {
2706        alert(`Cannot submit form: Total file size (${formatFileSize(totalSize)}) exceeds the maximum limit of ${formatFileSize(maxSize)}.\n\nPlease remove some files and try again.`);
2707        return false;
2708    }
2709
2710    return true;
2711}
2712
2713function formatFileSize(bytes) {
2714    if (bytes === 0) return '0 Bytes';
2715    const k = 1024;
2716    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
2717    const i = Math.floor(Math.log(bytes) / Math.log(k));
2718    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
2719}
2720
2721function closeModal(id){
2722    document.getElementById(id).classList.add('hidden');
2723}
2724
2725function handleSubscribeCheckbox(checkbox, formId) {
2726    // const chechboxId = checkbox.id;
2727    const parentElement = checkbox.closest('.subscribe-email, .subscribe-phone');
2728    let subscribeType = '';
2729    if (parentElement) {
2730        if (parentElement.classList.contains('subscribe-email')) {
2731            subscribeType = 'email';
2732        } else if (parentElement.classList.contains('subscribe-phone')) {
2733            subscribeType = 'phone';
2734        }
2735    }
2736
2737    document.querySelectorAll('.phone-container .phone').forEach(input => {
2738        if (input) {
2739            if (input.hasAttribute('required')) {
2740                input.dataset.originalRequired = 'true';
2741            }
2742        }
2743    });
2744
2745    if (checkbox.checked) {
2746        $.ajax({
2747            type: "GET",
2748            url: '/bloqs/bloq-forms/member-details/' + subscribeType,
2749            headers: {
2750                'X-CSRF-Token': $('meta[name="csrfToken"]').attr('content')
2751            },
2752            success: function(response) {
2753                $("#subscribeDetailsModal").removeClass('hidden').html(response);
2754
2755                setTimeout(() => {
2756                    const cancelButton = document.querySelector('#subscribeDetailsModal .cancel-btn');
2757                    if (cancelButton) {
2758                        cancelButton.onclick = function () {
2759                            $('#subscribeDetailsModal').addClass('hidden');
2760                            checkbox.checked = false;
2761
2762                            if (subscribeType == 'phone') {
2763                                const phoneContainer = checkbox.closest('.phone-container');
2764                                if (phoneContainer) {
2765                                    const phoneInput = phoneContainer.querySelector('.phone');
2766                                    if (phoneInput && !phoneInput.dataset.originalRequired) {
2767                                        phoneInput.removeAttribute('required');
2768                                    }
2769                                }
2770                            }
2771                        };
2772                    }
2773
2774                    const agreeButton = document.querySelector('#subscribeDetailsModal .agree-btn');
2775                    if (agreeButton) {
2776                        agreeButton.onclick = function () {
2777                            $('#subscribeDetailsModal').addClass('hidden');
2778
2779                            if (subscribeType == 'phone') {
2780                                const phoneContainer = checkbox.closest('.phone-container');
2781                                if (phoneContainer) {
2782                                    const phoneInput = phoneContainer.querySelector('.phone');
2783                                    if (phoneInput && !phoneInput.dataset.originalRequired) {
2784                                        phoneInput.setAttribute('required', 'required');
2785                                    }
2786                                }
2787                            }
2788                        };
2789                    }
2790
2791                    // const form = document.getElementById('member-details-form');
2792                    // const saveBtn = document.getElementById('save-details');
2793
2794                    // if (form && saveBtn) {
2795                    //     saveBtn.addEventListener('click', function (e) {
2796                    //         e.preventDefault();
2797
2798                    //         if (form.checkValidity()) {
2799                    //             const parentDiv = checkbox.closest('.subscribe-email, .subscribe-phone');
2800                    //             let type = '';
2801                    //             if (parentDiv) {
2802                    //                 if (parentDiv.classList.contains('subscribe-email')) {
2803                    //                     type = 'email';
2804                    //                 } else if (parentDiv.classList.contains('subscribe-phone')) {
2805                    //                     type = 'phone';
2806                    //                 }
2807                    //             }
2808
2809                    //             const formData = new FormData(form);
2810                    //             formData.append('clicked_checkbox_id', chechboxId);
2811                    //             formData.append('type', type);
2812                    //             formData.append('form_id', formId);
2813
2814                    //             $.ajax({
2815                    //                 url: '/bloqs/bloq-forms/save-member-details',
2816                    //                 method: 'POST',
2817                    //                 data: formData,
2818                    //                 processData: false,
2819                    //                 contentType: false,
2820                    //                 headers: {
2821                    //                     'X-CSRF-Token': $('meta[name="csrfToken"]').attr('content')
2822                    //                 },
2823                    //                 success: function(res) {
2824                    //                     $('#subscribeDetailsModal').addClass('hidden');
2825                    //                 },
2826                    //                 error: function(xhr) {
2827                    //                     console.error('Save failed:', xhr.responseText);
2828                    //                 }
2829                    //             });
2830
2831                    //         } else {
2832                    //             form.reportValidity();
2833                    //         }
2834                    //     });
2835                    // }
2836                }, 0); 
2837            },
2838            error: function(xhr) {
2839                console.error('AJAX error:', xhr.responseText);
2840            }
2841        });
2842    } else {
2843        if (subscribeType == 'phone') {
2844            const phoneContainer = checkbox.closest('.phone-container');
2845            if (phoneContainer) {
2846                const phoneInput = phoneContainer.querySelector('.phone');
2847                if (phoneInput && !phoneInput.dataset.originalRequired) {
2848                    phoneInput.removeAttribute('required');
2849                }
2850            }
2851        }
2852        // removeSessionData(chechboxId, formId);
2853    }
2854}
2855
2856function toggleAgreeModal(checkbox, type) {
2857    if (checkbox.checked) {
2858        if (type == 'email') {
2859            document.getElementById('emailConsentModal').classList.remove('hidden');
2860        } else {
2861            document.getElementById('smsConsentModal').classList.remove('hidden');
2862        }
2863    }
2864}
2865
2866function cancelConsent(type) {
2867    document.getElementById('iAgreeCheckbox').checked = false;
2868    document.getElementById(`${type}ConsentModal`).classList.add('hidden');
2869}
2870
2871function removeSessionData(checkboxId, formId) {
2872    $.ajax({
2873        url: '/bloqs/bloq-forms/remove-member-details',
2874        method: 'POST',
2875        data: { 
2876            checkbox_id: checkboxId,
2877            form_id: formId
2878        },
2879        headers: {
2880            'X-CSRF-Token': $('meta[name="csrfToken"]').attr('content')
2881        },
2882        success: function (res) {
2883            console.log('Session cleared for:', checkboxId);
2884        },
2885        error: function (xhr) {
2886            console.error('Session removal failed:', xhr.responseText);
2887        }
2888    });
2889}

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.