PluginProbe
MaxiBlocks Builder | 17,000+ Design Assets, Patterns, Icons & Starter Sites / trunk
MaxiBlocks Builder | 17,000+ Design Assets, Patterns, Icons & Starter Sites vtrunk
2.2.3 2.2.2 2.2.1 2.2.0 trunk 1.0.0 1.0.1 1.1.0 1.1.1 1.2.0 1.2.1 1.3 1.3.1 1.4 1.4.1 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 71 releases
maxi-blocks / core / admin / admin.js

admin.js in MaxiBlocks Builder | 17,000+ Design Assets, Patterns, Icons & Starter Sites trunk, at core/admin/admin.js

1,816 lines 47.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // Keep track of active polling to prevent multiple instances
2 let activePollingEmail = null;
3
4 document.addEventListener('DOMContentLoaded', function maxiAdmin() {
5 // &panel=documentation-support will open the tab in the accordion
6 const urlStr = window.location.href;
7 const url = new URL(urlStr);
8 const toCheck = url.searchParams.get('panel');
9 const checkBox = document.getElementById(toCheck);
10 if (checkBox) checkBox.checked = true;
11
12 // Hide the "Pro" tab if the user is not logged in
13 const proDiv = document.getElementById('maxi-dashboard_main-content_pro');
14 if (proDiv) proDiv.style.display = 'none';
15
16 const notProDiv = document.getElementById(
17 'maxi-dashboard_main-content_not-pro'
18 );
19 if (notProDiv) {
20 notProDiv.style.display = 'block';
21 const proNotProDiv = document.getElementById(
22 'maxi-dashboard_main-content_pro-not-pro'
23 );
24 if (proNotProDiv) proNotProDiv.style.display = 'block';
25 }
26
27 // save new breakpoints to the hidden input
28 const inputs = document.getElementsByClassName(
29 'maxi-dashboard_main-content_accordion-item-input'
30 );
31
32 const breakpointsInput = document.getElementById('maxi-breakpoints');
33
34 if (inputs && breakpointsInput) {
35 const breakpoints = breakpointsInput?.value;
36 const breakpointsArray = JSON.parse(breakpoints);
37
38 Array.from(inputs)?.forEach(input => {
39 const inputId = input.id;
40 const breakpoint = inputId.replace('maxi-breakpoint-', '');
41
42 input.addEventListener('input', function updateBreakpoints() {
43 const inputValue = input.value;
44 breakpointsArray[breakpoint] = parseInt(inputValue);
45 breakpointsInput.value = JSON.stringify(breakpointsArray);
46 });
47 });
48 }
49
50 const select = document.getElementById('maxi-versions');
51 const version = document.getElementById('maxi-rollback-version');
52
53 select?.addEventListener('change', function updateBreakpoints() {
54 const { value } = select;
55 version.value = value;
56 });
57
58 const dropdowns = document.querySelectorAll(
59 '.maxi-dashboard_main-content_accordion-item-content-switcher__dropdown select'
60 );
61
62 if (dropdowns) {
63 Array.from(dropdowns).forEach(dropdown => {
64 const dropdownInput = document.querySelector(
65 `input#${dropdown.id}`
66 );
67
68 dropdownInput.value = dropdown.value;
69
70 dropdown.addEventListener('change', function updateInputs() {
71 dropdownInput.value = dropdown.value;
72 });
73 });
74 }
75
76 // test map for google api key
77 // Initialize and add the map
78 const initTestMap = () => {
79 // The location of Uluru
80 const uluru = { lat: -25.344, lng: 131.031 };
81 // The map, centered at Uluru
82 // eslint-disable-next-line no-undef
83 const map = new google.maps.Map(
84 document.getElementById('maxi-api-test'),
85 {
86 zoom: 4,
87 center: uluru,
88 }
89 );
90 // The marker, positioned at Uluru
91 // eslint-disable-next-line no-undef, no-unused-vars
92 const marker = new google.maps.Marker({
93 position: uluru,
94 map,
95 });
96 };
97
98 const customValidation = (
99 type,
100 getKey,
101 hiddenInput,
102 validationDiv,
103 validationLoadingClass = 'api-validation-loading',
104 errorClass = 'api-error'
105 ) => {
106 const key = getKey();
107 let validationMessage = '';
108
109 // Use window.localization directly for consistency
110
111 if (type === 'validating') {
112 validationMessage = window.localization.loading_status_message;
113 validationDiv.classList.add(validationLoadingClass);
114 validationDiv.classList.remove(errorClass);
115 } else {
116 validationDiv.classList.remove(validationLoadingClass);
117
118 if (key === '' || type === 'EmptyKeyError') {
119 hiddenInput.value = '';
120 validationDiv.classList.add(errorClass);
121 validationMessage = window.localization.please_add_api_key;
122 } else {
123 validationDiv.classList.add(errorClass);
124 switch (type) {
125 case 'InvalidKeyError':
126 validationMessage = window.localization.invalid_api_key;
127 break;
128 case 'RefererNotAllowedError':
129 validationMessage =
130 window.localization.referer_not_allowed;
131 break;
132 case 'InvalidCharactersError':
133 validationMessage =
134 window.localization.invalid_characters;
135 break;
136 case 'ServerError':
137 validationMessage = window.localization.server_error;
138 break;
139 case true:
140 hiddenInput.value = key;
141 validationDiv.classList.remove(errorClass);
142 break;
143 default:
144 break;
145 }
146 }
147 }
148
149 validationDiv.innerHTML = validationMessage;
150 };
151
152 const makeInputPasswordVisible = input => {
153 if (!input) return;
154
155 input.addEventListener('focus', () => {
156 input.type = 'text';
157 });
158
159 input.addEventListener('blur', () => {
160 input.type = 'password';
161 });
162 };
163
164 // Google API Key validation
165 const googleApiKeyVisibleInput = document.querySelector(
166 '.google-api-key-option-visible-input'
167 );
168 const googleApiKeyHiddenInput = document.getElementById(
169 'google_api_key_option'
170 );
171 const googleValidationDiv = document.getElementById(
172 'maxi-api-test__validation-message'
173 );
174
175 makeInputPasswordVisible(googleApiKeyVisibleInput);
176
177 const head = document.getElementsByTagName('head')[0];
178
179 const getGoogleApiKey = () => googleApiKeyVisibleInput.value;
180
181 const checkForInvalidCharactersError = googleApiKey =>
182 !googleApiKey.match(/^[a-zA-Z0-9_$.[\]]+$/);
183
184 const googleMapsCustomValidation = type => {
185 customValidation(
186 type,
187 getGoogleApiKey,
188 googleApiKeyHiddenInput,
189 googleValidationDiv
190 );
191 };
192
193 const testGoogleApiKey = () => {
194 const googleApiKey = getGoogleApiKey();
195
196 if (checkForInvalidCharactersError(googleApiKey)) {
197 googleMapsCustomValidation('InvalidCharactersError');
198 return;
199 }
200
201 const oldScript = document.getElementById(
202 'maxi-test-google-map-script'
203 );
204 if (oldScript) head.removeChild(oldScript);
205
206 const script = document.createElement('script');
207 script.src = `https://maps.googleapis.com/maps/api/js?key=${googleApiKey}&callback=initMap`;
208 script.id = 'maxi-test-google-map-script';
209 script.defer = true;
210 script.async = true;
211
212 head.appendChild(script);
213 window.initMap = initTestMap;
214 googleValidationDiv.innerHTML = '';
215 googleValidationDiv.classList.remove('api-error');
216 };
217
218 const catchGoogleMapsApiErrors = () => {
219 // based on http://tobyho.com/2012/07/27/taking-over-console-log/
220 const { console } = window;
221 if (!console) return;
222
223 const intercept = method => {
224 const original = console[method];
225 console[method] = function () {
226 // eslint-disable-next-line prefer-rest-params
227 if (arguments[0]) {
228 if (
229 // eslint-disable-next-line prefer-rest-params
230 arguments[0].includes('InvalidKeyMapError') ||
231 // eslint-disable-next-line prefer-rest-params
232 arguments[0].includes('InvalidKeyError') ||
233 // eslint-disable-next-line prefer-rest-params
234 arguments[0].includes('API multiple times')
235 ) {
236 googleMapsCustomValidation('InvalidKeyError');
237 } else if (
238 // eslint-disable-next-line prefer-rest-params
239 arguments[0].includes('RefererNotAllowedMapError') ||
240 // eslint-disable-next-line prefer-rest-params
241 arguments[0].includes('RefererNotAllowedError')
242 ) {
243 googleMapsCustomValidation('RefererNotAllowedError');
244 } else if (
245 // eslint-disable-next-line prefer-rest-params
246 arguments[0].includes('API without a key') ||
247 // eslint-disable-next-line prefer-rest-params
248 arguments[0].includes('EmptyKeyError')
249 ) {
250 googleMapsCustomValidation('EmptyKeyError');
251 } else {
252 googleMapsCustomValidation(true);
253 }
254 }
255
256 if (original.apply) {
257 // eslint-disable-next-line prefer-rest-params
258 original.apply(console, arguments);
259 }
260 };
261 };
262 intercept(['error']);
263 };
264
265 // eslint-disable-next-line func-names
266 googleApiKeyVisibleInput?.addEventListener('input', function () {
267 testGoogleApiKey();
268 catchGoogleMapsApiErrors();
269 });
270
271 // OpenAI API Key validation
272 const openAIApiKeyVisibleInput = document.querySelector(
273 '.openai-api-key-option-visible-input'
274 );
275 const openAIApiKeyHiddenInput = document.getElementById(
276 'openai_api_key_option'
277 );
278 const openAIValidationDiv = document.getElementById(
279 'maxi-api-test__validation-message'
280 );
281
282 makeInputPasswordVisible(openAIApiKeyVisibleInput);
283
284 const getOpenAIApiKey = () => openAIApiKeyVisibleInput.value;
285
286 const openAIApiKeyCustomValidation = type => {
287 customValidation(
288 type,
289 getOpenAIApiKey,
290 openAIApiKeyHiddenInput,
291 openAIValidationDiv
292 );
293 };
294
295 const fetchOpenAIModels = async apiKey => {
296 try {
297 const response = await fetch('https://api.openai.com/v1/models', {
298 method: 'GET',
299 headers: {
300 Authorization: `Bearer ${apiKey}`,
301 'Content-Type': 'application/json',
302 },
303 });
304
305 if (!response.ok) {
306 throw new Error('Failed to fetch models');
307 }
308
309 const data = await response.json();
310
311 const excludedPatterns = [
312 'audio',
313 'gpt-3.5-turbo-instruct',
314 'gpt-4o-mini-realtime-preview',
315 'gpt-4o-realtime-preview',
316 'gpt-image',
317 'gpt-realtime',
318 'transcribe',
319 'tts',
320 'search-preview',
321 'o1-pro',
322 ];
323
324 const includedPatterns = ['o1', 'o3', 'gpt'];
325
326 return data.data
327 .filter(model => {
328 const modelId = model.id;
329 const isExcluded = excludedPatterns.some(pattern =>
330 modelId.includes(pattern)
331 );
332 const isIncluded = includedPatterns.some(pattern =>
333 modelId.includes(pattern)
334 );
335
336 return !isExcluded && isIncluded;
337 })
338 .map(model => model.id)
339 .sort();
340 } catch (error) {
341 console.error('Error fetching OpenAI models:', error);
342 return [];
343 }
344 };
345
346 let isUpdatingDropdown = false;
347
348 const updateModelDropdown = async apiKey => {
349 if (isUpdatingDropdown) return;
350 isUpdatingDropdown = true;
351
352 const modelSelect = document.getElementById('maxi_ai_model');
353 const modelInput = document.querySelector('input#maxi_ai_model');
354
355 if (!modelSelect || !modelInput) {
356 isUpdatingDropdown = false;
357 return;
358 }
359
360 // Only show loading message if we have a valid API key
361 if (apiKey) {
362 modelSelect.innerHTML = `<option value="">${window.localization.loading_available_models}</option>`;
363 } else {
364 modelSelect.innerHTML = `<option value="">${window.localization.please_add_api_key}</option>`;
365 modelInput.value = '';
366 isUpdatingDropdown = false;
367 return;
368 }
369
370 try {
371 const models = await fetchOpenAIModels(apiKey);
372
373 // Clear existing options
374 modelSelect.innerHTML = '';
375
376 if (models.length === 0) {
377 const option = document.createElement('option');
378 option.value = '';
379 option.textContent = window.localization.no_models_available;
380 modelSelect.appendChild(option);
381 modelInput.value = '';
382 isUpdatingDropdown = false;
383 return;
384 }
385
386 // Add available models
387 models.forEach(modelId => {
388 const option = document.createElement('option');
389 option.value = modelId;
390 option.textContent = modelId;
391 modelSelect.appendChild(option);
392 });
393
394 // Get the saved value from WordPress options via localized script
395 const currentValue =
396 window.maxiAiSettings?.defaultModel || 'gpt-3.5-turbo';
397 modelInput.value = currentValue;
398
399 // Try to restore previous selection if available
400 if (models.includes(currentValue)) {
401 modelSelect.value = currentValue;
402 } else {
403 // If previous selection not available, use first model
404 // eslint-disable-next-line prefer-destructuring
405 modelSelect.value = models[0];
406 // eslint-disable-next-line prefer-destructuring
407 modelInput.value = models[0];
408 }
409 } catch (error) {
410 console.error('Error updating model dropdown:', error);
411 modelSelect.innerHTML = `<option value="">${window.localization.error_loading_models}</option>`;
412 modelInput.value = '';
413 } finally {
414 isUpdatingDropdown = false;
415 }
416 };
417
418 const testOpenAIApiKey = () => {
419 const openAIApiKey = getOpenAIApiKey();
420
421 if (openAIApiKey === '') {
422 openAIApiKeyCustomValidation('');
423 return;
424 }
425
426 openAIApiKeyCustomValidation('validating');
427
428 // Test the API key and update models
429 Promise.all([
430 fetch('https://api.openai.com/v1/chat/completions', {
431 method: 'POST',
432 headers: {
433 'Content-Type': 'application/json',
434 Authorization: `Bearer ${openAIApiKey}`,
435 },
436 body: JSON.stringify({
437 messages: [{ role: 'user', content: 'Hello' }],
438 model: 'gpt-3.5-turbo',
439 max_tokens: 1,
440 }),
441 }),
442 updateModelDropdown(openAIApiKey),
443 ])
444 .then(([response]) => {
445 if (response.ok) {
446 openAIApiKeyCustomValidation(true);
447 } else {
448 openAIApiKeyCustomValidation('InvalidKeyError');
449 }
450 })
451 .catch(error => {
452 console.error(error);
453 openAIApiKeyCustomValidation('ServerError');
454 });
455 };
456
457 // Check if openAIApiKeyVisibleInput exists before executing related code
458 if (openAIApiKeyVisibleInput) {
459 const openAIApiKey = getOpenAIApiKey();
460 if (openAIApiKey) {
461 updateModelDropdown(openAIApiKey);
462 }
463
464 // Handle select changes
465 const modelSelect = document.getElementById('maxi_ai_model');
466 if (modelSelect) {
467 modelSelect.addEventListener('change', function () {
468 const modelInput = document.querySelector(
469 'input#maxi_ai_model'
470 );
471 if (modelInput) {
472 modelInput.value = this.value;
473 }
474 });
475 }
476
477 // Handle API key changes
478 openAIApiKeyVisibleInput.addEventListener('input', () => {
479 testOpenAIApiKey();
480 });
481 }
482
483 function autoResize(textarea) {
484 const maxHeight = 300; // Set this to your preferred maximum height, e.g., 200, 300, or 400 px
485
486 textarea.style.height = 'auto';
487
488 if (textarea.scrollHeight > maxHeight) {
489 textarea.style.height = `${maxHeight}px`;
490 textarea.style.overflowY = 'scroll'; // Enable vertical scrolling
491 } else {
492 textarea.style.height = `${textarea.scrollHeight}px`;
493 textarea.style.overflowY = 'hidden'; // Hide vertical scrollbar
494 }
495 }
496
497 // Get all textareas with the given class
498 const textareas = document.querySelectorAll(
499 'textarea.maxi-dashboard_main-content_accordion-item-input'
500 );
501 Array.from(textareas).forEach(textarea => {
502 // Initialize the height
503 autoResize(textarea);
504
505 // Add the auto-resizing functionality on input event
506 textarea.addEventListener('input', () => {
507 autoResize(textarea);
508 });
509 });
510
511 // Handle admin menu active states
512 function setAdminMenuActive() {
513 const adminMenu = document.querySelector(
514 '#toplevel_page_maxi-blocks-dashboard'
515 );
516 if (!adminMenu) return;
517
518 // Get the current tab from URL
519 const urlParams = new URLSearchParams(window.location.search);
520 const currentTab = urlParams.get('tab');
521 const currentPage = urlParams.get('page');
522
523 // Remove all current classes first
524 const allMenuItems = adminMenu.querySelectorAll('li');
525 allMenuItems.forEach(item => {
526 item.classList.remove('current');
527 const anchor = item.querySelector('a');
528 if (anchor) {
529 anchor.classList.remove('current');
530 anchor.setAttribute('aria-current', 'false');
531 }
532 });
533
534 // Find and set the active menu item
535 const menuItems = adminMenu.querySelectorAll('a');
536 menuItems.forEach(link => {
537 let isActive = false;
538
539 // Special case for Quick Start
540 if (
541 currentPage === 'maxi-blocks-quick-start' &&
542 link.href.includes('maxi-blocks-quick-start')
543 ) {
544 isActive = true;
545 }
546 // Handle other menu items
547 else if (currentTab) {
548 // Check if the link contains the current tab
549 const tabInUrl = link.href.match(/tab=([^&]*)/);
550 if (tabInUrl) {
551 isActive = tabInUrl[1] === currentTab;
552 }
553 }
554 // Handle Welcome page (no tab)
555 else if (
556 currentPage === 'maxi-blocks-dashboard' &&
557 link.href.includes('maxi-blocks-dashboard') &&
558 !link.href.includes('tab=')
559 ) {
560 isActive = true;
561 }
562
563 if (isActive) {
564 // Set active state
565 const menuItem = link.closest('li');
566 if (menuItem) {
567 menuItem.classList.add('current');
568 link.classList.add('current');
569 link.setAttribute('aria-current', 'page');
570 }
571 }
572 });
573 }
574
575 // Call the function on page load
576 setAdminMenuActive();
577 });
578
579 // License page functionality
580 document.addEventListener('DOMContentLoaded', function () {
581 // Handle license validation (email or purchase code)
582 const validateButton = document.getElementById('maxi-validate-license');
583 const licenseInput = document.getElementById('maxi-license-input');
584 const validationMessage = document.getElementById(
585 'maxi-license-validation-message'
586 );
587 const currentStatus = document.getElementById('current-license-status');
588 const currentUser = document.getElementById('current-license-user');
589 const logoutButton = document.getElementById('maxi-license-logout');
590
591 // Click count for email show/hide functionality
592 let clickCount = 0;
593
594 /**
595 * Initialize email show/hide functionality for existing user
596 */
597 function initializeEmailToggle() {
598 if (currentUser && currentUser.textContent) {
599 const userName = currentUser.textContent.trim();
600
601 if (isValidEmail(userName)) {
602 currentUser.style.cursor = 'pointer';
603 currentUser.title = window.localization.click_to_show;
604
605 // Add click handler for email show/hide
606 currentUser.onclick = function () {
607 clickCount += 1;
608 if (clickCount % 2 !== 0) {
609 currentUser.textContent = userName;
610 currentUser.title = window.localization.click_to_hide;
611 } else {
612 currentUser.textContent = '******@***.***';
613 currentUser.title = window.localization.click_to_show;
614 }
615 };
616
617 // Set initial masked display
618 currentUser.textContent = '******@***.***';
619 }
620 }
621 }
622
623 // Initialize email toggle functionality on page load
624 initializeEmailToggle();
625
626 /**
627 * Show validation message
628 */
629 function showMessage(message, isError = false) {
630 if (validationMessage) {
631 validationMessage.style.display = 'block';
632 validationMessage.textContent = message;
633 validationMessage.className = `maxi-license-message ${
634 isError ? 'error' : 'success'
635 }`;
636 }
637 }
638
639 /**
640 * Update license status display
641 */
642 function updateLicenseStatus(status, userName = '') {
643 if (currentStatus) {
644 currentStatus.textContent = status;
645 }
646
647 if (currentUser) {
648 if (userName) {
649 currentUser.textContent =
650 userName === 'Maxiblocks' ? 'MaxiBlocks' : userName;
651 currentUser.parentElement.style.display = 'block';
652
653 // Add email show/hide functionality if it's an email
654 if (isValidEmail(userName)) {
655 currentUser.style.cursor = 'pointer';
656 currentUser.title =
657 clickCount % 2 !== 0
658 ? window.localization.click_to_hide
659 : window.localization.click_to_show;
660
661 // Remove any existing click handlers
662 currentUser.onclick = null;
663
664 // Add click handler for email show/hide
665 currentUser.onclick = function () {
666 clickCount += 1;
667 if (clickCount % 2 !== 0) {
668 currentUser.textContent = userName;
669 currentUser.title =
670 window.localization.click_to_hide;
671 } else {
672 currentUser.textContent = '******@***.***';
673 currentUser.title =
674 window.localization.click_to_show;
675 }
676 };
677
678 // Set initial display
679 currentUser.textContent =
680 clickCount % 2 !== 0 ? userName : '******@***.***';
681 } else {
682 // For purchase codes, show as-is without click functionality
683 currentUser.style.cursor = 'default';
684 currentUser.title = '';
685 currentUser.onclick = null;
686 currentUser.textContent = userName;
687 }
688 } else {
689 currentUser.parentElement.style.display = 'none';
690 }
691 }
692
693 // Since we're combining sections, we need to reload the page to show the correct UI
694 // This ensures the proper elements (logout button vs input form) are displayed
695 if (status === 'Active' || status === 'Not activated') {
696 setTimeout(() => {
697 window.location.reload();
698 }, 300); // Give time to show the success message
699 }
700 }
701
702 /**
703 * Helper functions for authentication
704 */
705
706 /**
707 * Detects if input is an email or purchase code
708 * @param {string} input - The input string to check
709 * @returns {string} - 'email' or 'code'
710 */
711 function detectInputType(input) {
712 if (!input || typeof input !== 'string') return 'email';
713
714 const trimmedInput = input.trim();
715
716 // If it contains @ or . (dot), it's likely an email
717 const hasAtSymbol = trimmedInput.includes('@');
718 const hasDot = trimmedInput.includes('.');
719
720 if (hasAtSymbol || hasDot) {
721 return 'email';
722 }
723
724 // Purchase codes are typically alphanumeric strings without @ or . symbols
725 // and are usually longer than 6 characters
726 const isAlphanumeric = /^[a-zA-Z0-9\-_]+$/.test(trimmedInput);
727 const isLongEnough = trimmedInput.length >= 6;
728
729 // If it doesn't have @ or . and looks like a code, treat as purchase code
730 if (isAlphanumeric && isLongEnough) {
731 return 'code';
732 }
733
734 // Default to email for other cases
735 return 'email';
736 }
737
738 /**
739 * Validates email format
740 * @param {string} email - Email to validate
741 * @returns {boolean} - True if valid email
742 */
743 function isValidEmail(email) {
744 const emailPattern =
745 /^(?![.])(([^<>()[\]\\.,;:\s@"']+(\.[^<>()[\]\\.,;:\s@"']+)*|"(.+?)")|(".+?"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
746 return emailPattern.test(email);
747 }
748
749 /**
750 * Validate license (email or purchase code)
751 */
752 function validateLicense() {
753 const inputValue = licenseInput ? licenseInput.value.trim() : '';
754
755 if (!inputValue) {
756 showMessage(window.localization.please_enter_email_or_code, true);
757 return;
758 }
759
760 const inputType = detectInputType(inputValue);
761
762 // Show loading state
763 if (validateButton) {
764 validateButton.disabled = true;
765 validateButton.textContent = window.localization.validating;
766 }
767
768 if (inputType === 'email') {
769 // Handle email authentication
770 if (!isValidEmail(inputValue)) {
771 showMessage(window.localization.the_email_is_not_valid, true);
772 resetValidateButton();
773 return;
774 }
775
776 // For email authentication, send to WordPress backend and open login page
777
778 const formData = new FormData();
779 formData.append('action', 'maxi_validate_license');
780 // eslint-disable-next-line no-undef
781 formData.append('nonce', maxiLicenseSettings.nonce);
782 formData.append('license_input', inputValue);
783 formData.append('license_action', 'activate');
784
785 // eslint-disable-next-line no-undef
786 fetch(maxiLicenseSettings.ajaxUrl, {
787 method: 'POST',
788 body: formData,
789 })
790 .then(response => {
791 return response.json();
792 })
793 .then(data => {
794 if (data.success && data.data.auth_type === 'email') {
795 // Open the login URL in a new tab
796 window.open(data.data.login_url, '_blank')?.focus();
797 showMessage(
798 window.localization.email_authentication_started,
799 false
800 );
801
802 // Start smart email authentication checking
803 startSmartAuthCheck(inputValue);
804 } else {
805 console.error(
806 JSON.stringify({
807 message:
808 'MaxiBlocks Email Auth JS INIT: Email auth failed',
809 errorMessage:
810 data.data?.message || 'Unknown error',
811 })
812 );
813 showMessage(
814 data.data.message || 'Email authentication failed',
815 true
816 );
817 }
818 })
819 .catch(error => {
820 console.error(
821 JSON.stringify({
822 message:
823 'MaxiBlocks Email Auth JS INIT: Request failed',
824 error: error.message,
825 })
826 );
827 showMessage(
828 window.localization.failed_to_initiate_email_auth,
829 true
830 );
831 })
832 .finally(() => {
833 resetValidateButton();
834 });
835 } else {
836 // Handle purchase code authentication
837 const formData = new FormData();
838 formData.append('action', 'maxi_validate_license');
839 // eslint-disable-next-line no-undef
840 formData.append('nonce', maxiLicenseSettings.nonce);
841 formData.append('license_input', inputValue);
842 formData.append('license_action', 'activate');
843
844 // eslint-disable-next-line no-undef
845 fetch(maxiLicenseSettings.ajaxUrl, {
846 method: 'POST',
847 body: formData,
848 })
849 .then(response => response.json())
850 .then(data => {
851 if (data.success) {
852 showMessage(data.data.message);
853 updateLicenseStatus(
854 data.data.status,
855 data.data.user_name
856 );
857 // Reload page to show activated state
858 setTimeout(() => {
859 window.location.reload();
860 }, 1500);
861 } else {
862 showMessage(
863 data.data.message || 'Validation failed',
864 true
865 );
866 }
867 })
868 .catch(error => {
869 showMessage(
870 window.localization.failed_to_validate_license,
871 true
872 );
873 })
874 .finally(() => {
875 resetValidateButton();
876 });
877 }
878 }
879
880 /**
881 * Start smart authentication checking using Page Visibility API and focus events
882 * This is much more efficient than constant polling - only checks when user returns to tab
883 */
884 function startSmartAuthCheck(email) {
885 // Set active polling email to prevent duplicates
886 activePollingEmail = email;
887
888 let fallbackTimeout;
889 let isCheckingAuth = false;
890 let handleVisibilityChange;
891 let handleWindowFocus;
892
893 const stopAuthCheck = () => {
894 activePollingEmail = null;
895 if (fallbackTimeout) {
896 clearTimeout(fallbackTimeout);
897 }
898 // Remove event listeners
899 if (handleVisibilityChange) {
900 document.removeEventListener(
901 'visibilitychange',
902 handleVisibilityChange
903 );
904 }
905 if (handleWindowFocus) {
906 window.removeEventListener('focus', handleWindowFocus);
907 }
908 };
909
910 const checkAuth = async (trigger = 'unknown') => {
911 if (isCheckingAuth) return false; // Prevent multiple simultaneous checks
912
913 isCheckingAuth = true;
914
915 try {
916 const authResult = await checkEmailAuthentication(email);
917
918 if (authResult && authResult.success) {
919 // User is fully authenticated (both subscription valid and logged into Appwrite)
920 stopAuthCheck();
921
922 showMessage(window.localization.successfully_authenticated);
923 updateLicenseStatus('Active ✓', authResult.user_name);
924 setTimeout(() => {
925 window.location.reload();
926 }, 1500);
927
928 return true;
929 }
930
931 if (
932 authResult &&
933 authResult.subscription_valid &&
934 !authResult.appwrite_login_verified
935 ) {
936 // Subscription is valid but user hasn't logged into Appwrite yet
937 // Don't stop checking - keep polling until they log in
938 showMessage(
939 window.localization.please_log_into_maxiblocks,
940 false
941 );
942 return false;
943 }
944
945 if (authResult && authResult.error) {
946 // Handle specific errors like seat limit
947 console.error(
948 JSON.stringify({
949 message:
950 'MaxiBlocks Email Auth JS: Authentication error',
951 email,
952 trigger,
953 errorMessage: authResult.error_message,
954 errorCode: authResult.error_code,
955 })
956 );
957
958 // Stop auth checking on error
959 stopAuthCheck();
960
961 showMessage(authResult.error_message, true);
962 return false;
963 }
964
965 // If we get here, authentication failed for unknown reasons
966 } catch (error) {
967 console.error(
968 JSON.stringify({
969 message:
970 'MaxiBlocks Email Auth JS: Auth check exception',
971 email,
972 trigger,
973 error: error.message,
974 stack: error.stack,
975 })
976 );
977 } finally {
978 isCheckingAuth = false;
979 }
980
981 return false;
982 };
983
984 // Define event handlers
985 handleVisibilityChange = () => {
986 if (
987 document.visibilityState === 'visible' &&
988 activePollingEmail === email
989 ) {
990 checkAuth('visibility-change');
991 }
992 };
993
994 handleWindowFocus = () => {
995 if (activePollingEmail === email) {
996 checkAuth('window-focus');
997 }
998 };
999
1000 // Add event listeners
1001 document.addEventListener('visibilitychange', handleVisibilityChange);
1002 window.addEventListener('focus', handleWindowFocus);
1003
1004 // Check immediately
1005 checkAuth('initial');
1006
1007 // Fallback: Check once every 60 seconds as a safety net (much less frequent than before)
1008 const fallbackCheck = () => {
1009 if (activePollingEmail === email) {
1010 // Only check if tab is visible to avoid unnecessary API calls
1011 if (document.visibilityState === 'visible') {
1012 checkAuth('fallback-timer');
1013 }
1014 fallbackTimeout = setTimeout(fallbackCheck, 60000); // 60 seconds
1015 }
1016 };
1017 fallbackTimeout = setTimeout(fallbackCheck, 60000);
1018
1019 // Stop checking after 10 minutes
1020 setTimeout(() => {
1021 if (activePollingEmail === email) {
1022 stopAuthCheck();
1023 }
1024 }, 600000); // 10 minutes
1025 }
1026
1027 /**
1028 * Check email authentication directly (similar to toolbar)
1029 */
1030 async function checkEmailAuthentication(email) {
1031 try {
1032 // Get the auth key from cookie
1033 const cookies = document.cookie.split(';');
1034 let authKey = null;
1035
1036 for (const cookie of cookies) {
1037 const [name, value] = cookie.trim().split('=');
1038 if (name === 'maxi_blocks_key') {
1039 try {
1040 const cookieData = JSON.parse(value);
1041 authKey = cookieData[email];
1042
1043 break;
1044 } catch (e) {
1045 console.error(
1046 JSON.stringify({
1047 message:
1048 'MaxiBlocks Email Auth JS: Error parsing cookie',
1049 email,
1050 error: e.message,
1051 cookieValue: value,
1052 })
1053 );
1054 }
1055 }
1056 }
1057
1058 if (!authKey) {
1059 console.error(
1060 JSON.stringify({
1061 message:
1062 'MaxiBlocks Email Auth JS: No auth key found for email',
1063 email,
1064 })
1065 );
1066 return false;
1067 }
1068
1069 // Call WordPress AJAX endpoint to check authentication status
1070 const formData = new FormData();
1071 formData.append('action', 'maxi_check_auth_status');
1072 // eslint-disable-next-line no-undef
1073 formData.append('nonce', maxiLicenseSettings.nonce);
1074
1075 // eslint-disable-next-line no-undef
1076 const endpoint = maxiLicenseSettings.ajaxUrl;
1077
1078 // eslint-disable-next-line no-undef
1079 const response = await fetch(endpoint, {
1080 method: 'POST',
1081 body: formData,
1082 });
1083
1084 if (!response.ok) {
1085 console.error(
1086 JSON.stringify({
1087 message:
1088 'MaxiBlocks Email Auth JS: API response not ok',
1089 email,
1090 status: response.status,
1091 statusText: response.statusText,
1092 })
1093 );
1094 return false;
1095 }
1096
1097 const data = await response.json();
1098
1099 if (data && data.success) {
1100 if (data.data.is_authenticated) {
1101 // User is fully authenticated (both subscription valid and logged into Appwrite)
1102
1103 return {
1104 success: true,
1105 user_name: data.data.user_name,
1106 };
1107 }
1108
1109 // Check for the new intermediate state: subscription valid but not logged into Appwrite
1110 if (
1111 data.data.subscription_valid &&
1112 !data.data.appwrite_login_verified
1113 ) {
1114 return {
1115 success: false,
1116 subscription_valid: true,
1117 appwrite_login_verified: false,
1118 message:
1119 data.data.message ||
1120 window.localization.please_log_into_maxiblocks,
1121 };
1122 }
1123
1124 if (data.data.error && data.data.error_message) {
1125 // Handle specific errors like seat limit
1126 console.error(
1127 JSON.stringify({
1128 message:
1129 'MaxiBlocks Email Auth JS: ERROR response from server',
1130 email,
1131 errorCode: data.data.error_code,
1132 errorMessage: data.data.error_message,
1133 })
1134 );
1135 return {
1136 success: false,
1137 error: true,
1138 error_message: data.data.error_message,
1139 error_code: data.data.error_code,
1140 };
1141 }
1142 } else {
1143 console.error(
1144 JSON.stringify({
1145 message:
1146 'MaxiBlocks Email Auth JS: Response unsuccessful or malformed',
1147 email,
1148 responseSuccess: data?.success,
1149 responseData: data,
1150 })
1151 );
1152 }
1153
1154 return false;
1155 } catch (error) {
1156 console.error(
1157 JSON.stringify({
1158 message:
1159 'MaxiBlocks Email Auth JS: Exception caught in checkEmailAuthentication',
1160 email,
1161 error: error.message,
1162 stack: error.stack,
1163 })
1164 );
1165 return false;
1166 }
1167 }
1168
1169 /**
1170 * Reset validate button state
1171 */
1172 function resetValidateButton() {
1173 if (validateButton) {
1174 validateButton.disabled = false;
1175 validateButton.textContent = window.localization.activate;
1176 }
1177 }
1178
1179 /**
1180 * Handle logout
1181 */
1182 function handleLogout() {
1183 if (logoutButton) {
1184 logoutButton.disabled = true;
1185 logoutButton.textContent = window.localization.signing_out;
1186 }
1187
1188 // Check if this is an email logout (check for the maxi_blocks_key cookie)
1189 const isEmailLogout = document.cookie
1190 .split(';')
1191 .some(cookie => cookie.trim().startsWith('maxi_blocks_key='));
1192
1193 // If email logout, open logout page
1194 if (isEmailLogout) {
1195 const logoutUrl = 'https://my.maxiblocks.com/log-out?plugin';
1196 window.open(logoutUrl, '_blank')?.focus();
1197 }
1198
1199 const formData = new FormData();
1200 formData.append('action', 'maxi_validate_license');
1201 // eslint-disable-next-line no-undef
1202 formData.append('nonce', maxiLicenseSettings.nonce);
1203 formData.append('license_action', 'logout');
1204
1205 // eslint-disable-next-line no-undef
1206 fetch(maxiLicenseSettings.ajaxUrl, {
1207 method: 'POST',
1208 body: formData,
1209 })
1210 .then(response => response.json())
1211 .then(data => {
1212 if (data.success) {
1213 showMessage(data.data.message);
1214 updateLicenseStatus(data.data.status, data.data.user_name);
1215 } else {
1216 showMessage(
1217 data.data.message ||
1218 window.localization.failed_to_sign_out,
1219 true
1220 );
1221 }
1222 })
1223 .catch(error => {
1224 console.error('Logout error:', error);
1225 showMessage(window.localization.failed_to_sign_out, true);
1226 })
1227 .finally(() => {
1228 if (logoutButton) {
1229 logoutButton.disabled = false;
1230 logoutButton.textContent = window.localization.sign_out;
1231 }
1232 });
1233 }
1234
1235 // Event listeners
1236 if (validateButton) {
1237 validateButton.addEventListener('click', validateLicense);
1238 }
1239
1240 if (logoutButton) {
1241 logoutButton.addEventListener('click', handleLogout);
1242 }
1243
1244 if (licenseInput) {
1245 // Store original placeholder
1246 const originalPlaceholder = licenseInput.placeholder;
1247
1248 // Hide placeholder on focus
1249 // eslint-disable-next-line func-names
1250 licenseInput.addEventListener('focus', function () {
1251 this.placeholder = '';
1252 });
1253
1254 // Restore placeholder on blur if input is empty
1255 // eslint-disable-next-line func-names
1256 licenseInput.addEventListener('blur', function () {
1257 if (!this.value.trim()) {
1258 this.placeholder = originalPlaceholder;
1259 }
1260 });
1261
1262 // eslint-disable-next-line func-names
1263 licenseInput.addEventListener('keypress', function (e) {
1264 if (e.key === 'Enter') {
1265 e.preventDefault();
1266 validateLicense();
1267 }
1268 });
1269 }
1270 });
1271
1272 // Custom Fonts Manager with AJAX
1273 document.addEventListener('DOMContentLoaded', () => {
1274 const submitBtn = document.getElementById('maxi-custom-font-submit');
1275
1276 if (!submitBtn) {
1277 return;
1278 }
1279
1280 // Check if wp.apiFetch is available
1281 if (!window.wp || !window.wp.apiFetch) {
1282 console.error('wp.apiFetch is not available');
1283 return;
1284 }
1285
1286 const noticeContainer = document.getElementById('maxi-custom-fonts-notice');
1287 const fontsListContainer = document.querySelector(
1288 '.maxi-custom-fonts-list'
1289 );
1290
1291 /**
1292 * Display a notice message using DOM-safe element creation
1293 *
1294 * @param {string} message - The message to display (will be escaped)
1295 * @param {string} type - Notice type: 'success' or 'error'
1296 */
1297 const showNotice = (message, type = 'success') => {
1298 if (!noticeContainer) {
1299 return;
1300 }
1301
1302 // Determine notice classes based on type
1303 const noticeClass =
1304 type === 'success'
1305 ? 'notice notice-success'
1306 : 'notice notice-error';
1307
1308 // Create wrapper div with notice classes
1309 const wrapperDiv = document.createElement('div');
1310 wrapperDiv.className = `${noticeClass} is-dismissible`;
1311
1312 // Create paragraph element and set text content (XSS-safe)
1313 const paragraph = document.createElement('p');
1314 paragraph.textContent = message;
1315
1316 // Append paragraph to wrapper
1317 wrapperDiv.appendChild(paragraph);
1318
1319 // Clear and update notice container
1320 noticeContainer.innerHTML = '';
1321 noticeContainer.appendChild(wrapperDiv);
1322 };
1323
1324 const refreshFontsList = async () => {
1325 if (!fontsListContainer) {
1326 return;
1327 }
1328
1329 try {
1330 const fonts = await wp.apiFetch({
1331 path: '/maxi-blocks/v1.0/fonts/custom',
1332 });
1333
1334 const fontsArray = Object.values(fonts);
1335
1336 // Find or create the container element
1337 let container = document.querySelector(
1338 '.maxi-custom-fonts-list-container'
1339 );
1340 if (!container) {
1341 container = fontsListContainer.parentElement;
1342 }
1343
1344 if (!fontsArray.length) {
1345 container.innerHTML =
1346 '<p>No custom fonts have been uploaded yet.</p>';
1347 return;
1348 }
1349
1350 let html = '';
1351
1352 fontsArray.forEach(font => {
1353 const family = font.value || '';
1354 const variants = font.variants || [];
1355
1356 // Collect unique weights and styles
1357 const weights = [];
1358 const styles = [];
1359
1360 variants.forEach(v => {
1361 const weight = v.weight || '';
1362 const style = v.style || '';
1363
1364 if (weight && !weights.includes(weight)) {
1365 weights.push(weight);
1366 }
1367 if (style && !styles.includes(style)) {
1368 styles.push(style);
1369 }
1370 });
1371
1372 // Sort weights numerically
1373 weights.sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
1374
1375 const weightsHtml = weights.length
1376 ? `<span class="maxi-font-weights">${weights.join(
1377 ', '
1378 )}</span>`
1379 : '—';
1380 const stylesHtml = styles.length
1381 ? `<span class="maxi-font-styles">${styles.join(
1382 ', '
1383 )}</span>`
1384 : '—';
1385
1386 html += '<tr>';
1387 html += `<td><strong>${family}</strong></td>`;
1388 html += `<td>${weightsHtml}</td>`;
1389 html += `<td>${stylesHtml}</td>`;
1390 html += '<td>';
1391 if (font.id) {
1392 html += `<button type="button" class="button-link-delete maxi-delete-custom-font" data-font-id="${font.id}">Remove</button>`;
1393 }
1394 html += '</td></tr>';
1395 });
1396
1397 // Only update tbody to preserve event listeners on delete buttons
1398 const tbody = fontsListContainer.querySelector('tbody');
1399 if (tbody) {
1400 tbody.innerHTML = html;
1401 } else {
1402 // If table doesn't exist yet, create it
1403 container.innerHTML = `
1404 <table class="widefat striped maxi-custom-fonts-list">
1405 <thead>
1406 <tr>
1407 <th>Font family</th>
1408 <th>Weights</th>
1409 <th>Styles</th>
1410 <th>Actions</th>
1411 </tr>
1412 </thead>
1413 <tbody>${html}</tbody>
1414 </table>
1415 `;
1416 }
1417
1418 // Reattach handlers to new buttons
1419 attachDeleteHandlers();
1420 } catch (error) {
1421 console.error('Error refreshing fonts list:', error);
1422 }
1423 };
1424
1425 const handleDelete = async event => {
1426 const { target: btn } = event;
1427 const { fontId } = btn.dataset;
1428
1429 if (!fontId) {
1430 return;
1431 }
1432
1433 // eslint-disable-next-line no-alert
1434 if (!window.confirm('Are you sure you want to remove this font?')) {
1435 return;
1436 }
1437
1438 btn.disabled = true;
1439 btn.textContent = 'Removing...';
1440
1441 try {
1442 await wp.apiFetch({
1443 path: `/maxi-blocks/v1.0/fonts/custom/${fontId}`,
1444 method: 'DELETE',
1445 });
1446
1447 showNotice('Custom font removed successfully.', 'success');
1448 await refreshFontsList();
1449 } catch (error) {
1450 showNotice(error.message || 'Failed to remove font.', 'error');
1451 btn.disabled = false;
1452 btn.textContent = 'Remove';
1453 }
1454 };
1455
1456 function attachDeleteHandlers() {
1457 document.querySelectorAll('.maxi-delete-custom-font').forEach(btn => {
1458 btn.addEventListener('click', handleDelete);
1459 });
1460 }
1461
1462 // Handle button click
1463 submitBtn.addEventListener('click', async () => {
1464 const familyInput = document.getElementById('maxi-custom-font-family');
1465 const fileInput = document.getElementById('maxi-custom-font-file');
1466
1467 if (!familyInput || !fileInput || !fileInput.files[0]) {
1468 showNotice('Please fill in all required fields.', 'error');
1469 return;
1470 }
1471
1472 const family = familyInput.value.trim();
1473 const file = fileInput.files[0];
1474
1475 if (!family) {
1476 showNotice('Font family name is required.', 'error');
1477 return;
1478 }
1479
1480 // Disable submit button
1481 submitBtn.disabled = true;
1482 submitBtn.textContent = 'Uploading...';
1483
1484 try {
1485 // First upload the file to media library
1486 const formData = new FormData();
1487 formData.append('file', file);
1488
1489 const attachment = await wp.apiFetch({
1490 path: '/wp/v2/media',
1491 method: 'POST',
1492 body: formData,
1493 });
1494
1495 // Then add it as a custom font
1496 try {
1497 await wp.apiFetch({
1498 path: '/maxi-blocks/v1.0/fonts/custom',
1499 method: 'POST',
1500 data: {
1501 family,
1502 attachment_id: attachment.id,
1503 },
1504 });
1505
1506 showNotice('Custom font added successfully!', 'success');
1507
1508 // Clear inputs
1509 familyInput.value = '';
1510 fileInput.value = '';
1511
1512 await refreshFontsList();
1513 } catch (fontError) {
1514 // Delete the uploaded attachment if font creation fails
1515 await wp.apiFetch({
1516 path: `/wp/v2/media/${attachment.id}`,
1517 method: 'DELETE',
1518 });
1519 throw fontError;
1520 }
1521 } catch (error) {
1522 showNotice(error.message || 'Failed to upload font.', 'error');
1523 } finally {
1524 submitBtn.disabled = false;
1525 submitBtn.textContent = 'Add custom font';
1526 }
1527 });
1528
1529 // Initial attachment of delete handlers
1530 attachDeleteHandlers();
1531 });
1532
1533 // Network License Management for Multisite
1534 // eslint-disable-next-line func-names
1535 document.addEventListener('DOMContentLoaded', function () {
1536 // Check if we're in network admin and have the necessary elements
1537 if (
1538 typeof maxiNetworkLicenseSettings !== 'undefined' &&
1539 // eslint-disable-next-line no-undef
1540 maxiNetworkLicenseSettings.isNetworkAdmin
1541 ) {
1542 initNetworkLicenseHandlers();
1543 }
1544
1545 // Also initialize for regular site admin
1546 if (typeof maxiLicenseSettings !== 'undefined') {
1547 // Existing license handlers are already in place
1548 // Just ensure they work with network license context
1549 initSiteLicenseHandlers();
1550 }
1551 });
1552
1553 /**
1554 * Initialize network license handlers
1555 */
1556 function initNetworkLicenseHandlers() {
1557 const validateButton = document.getElementById(
1558 'maxi-validate-network-license'
1559 );
1560 const logoutButton = document.getElementById('maxi-network-license-logout');
1561 const licenseInput = document.getElementById('maxi-network-license-input');
1562
1563 if (validateButton) {
1564 // eslint-disable-next-line func-names
1565 validateButton.addEventListener('click', function () {
1566 const licenseValue = licenseInput ? licenseInput.value.trim() : '';
1567
1568 if (!licenseValue) {
1569 showNetworkMessage(
1570 window.localization.please_enter_purchase_code,
1571 'error'
1572 );
1573 return;
1574 }
1575
1576 // Show loading state
1577 validateButton.disabled = true;
1578 validateButton.textContent = window.localization.activating;
1579
1580 // Send AJAX request for network license validation
1581 sendNetworkLicenseRequest('validate', licenseValue);
1582 });
1583 }
1584
1585 if (logoutButton) {
1586 // eslint-disable-next-line func-names
1587 logoutButton.addEventListener('click', function () {
1588 if (
1589 // eslint-disable-next-line no-undef, no-undef, no-restricted-globals, no-alert
1590 confirm(window.localization.deactivate_network_license_confirm)
1591 ) {
1592 logoutButton.disabled = true;
1593 logoutButton.textContent = window.localization.deactivating;
1594
1595 sendNetworkLicenseRequest('logout', '');
1596 }
1597 });
1598 }
1599
1600 // Check initial network license status
1601 checkNetworkAuthStatus();
1602 }
1603
1604 /**
1605 * Initialize site license handlers with network awareness
1606 */
1607 function initSiteLicenseHandlers() {
1608 // The existing license handlers should already be working
1609 // We just need to ensure they understand network license context
1610
1611 // If there's a network license input restriction, handle it
1612 const licenseInput = document.getElementById('maxi-license-input');
1613 const validateButton = document.getElementById('maxi-validate-license');
1614
1615 if (licenseInput && validateButton) {
1616 // Check if we're in a network-restricted mode (email only)
1617 const emailOnlyForm = document.querySelector('.maxi-email-only');
1618 if (emailOnlyForm) {
1619 // Modify validation to only allow email format
1620 // eslint-disable-next-line func-names, consistent-return
1621 validateButton.addEventListener('click', function (e) {
1622 const inputValue = licenseInput.value.trim();
1623 if (inputValue && !isValidEmail(inputValue)) {
1624 e.preventDefault();
1625 // eslint-disable-next-line no-undef
1626 showMessage(
1627 window.localization.only_email_authentication_allowed,
1628 'error'
1629 );
1630 return false;
1631 }
1632 });
1633 }
1634 }
1635 }
1636
1637 /**
1638 * Send network license AJAX request
1639 */
1640 function sendNetworkLicenseRequest(action, licenseInput) {
1641 const data = new FormData();
1642 data.append('action', 'maxi_network_validate_license');
1643 // eslint-disable-next-line no-undef
1644 data.append('nonce', maxiNetworkLicenseSettings.nonce);
1645 data.append('license_action', action);
1646 if (licenseInput) {
1647 data.append('license_input', licenseInput);
1648 }
1649
1650 // eslint-disable-next-line no-undef
1651 fetch(maxiNetworkLicenseSettings.ajaxUrl, {
1652 method: 'POST',
1653 body: data,
1654 })
1655 .then(response => response.json())
1656 .then(data => {
1657 if (data.success) {
1658 handleNetworkLicenseSuccess(data.data, action);
1659 } else {
1660 handleNetworkLicenseError(
1661 data.data.message || 'An error occurred',
1662 action
1663 );
1664 }
1665 })
1666 .catch(error => {
1667 console.error('Network license request failed:', error);
1668 handleNetworkLicenseError(
1669 window.localization.network_error_occurred,
1670 action
1671 );
1672 });
1673 }
1674
1675 /**
1676 * Handle successful network license response
1677 */
1678 function handleNetworkLicenseSuccess(data, action) {
1679 if (action === 'validate') {
1680 showNetworkMessage(data.message, 'success');
1681 // Refresh the page to show new status
1682 setTimeout(() => {
1683 window.location.reload();
1684 }, 1500);
1685 } else if (action === 'logout') {
1686 showNetworkMessage(data.message, 'success');
1687 // Refresh the page to show new status
1688 setTimeout(() => {
1689 window.location.reload();
1690 }, 1500);
1691 }
1692 }
1693
1694 /**
1695 * Handle network license error response
1696 */
1697 function handleNetworkLicenseError(message, action) {
1698 showNetworkMessage(message, 'error');
1699
1700 // Reset button states
1701 const validateButton = document.getElementById(
1702 'maxi-validate-network-license'
1703 );
1704 const logoutButton = document.getElementById('maxi-network-license-logout');
1705
1706 if (action === 'validate' && validateButton) {
1707 validateButton.disabled = false;
1708 validateButton.textContent =
1709 window.localization.activate_network_license;
1710 } else if (action === 'logout' && logoutButton) {
1711 logoutButton.disabled = false;
1712 logoutButton.textContent =
1713 window.localization.deactivate_network_license;
1714 }
1715 }
1716
1717 /**
1718 * Show network license message
1719 */
1720 function showNetworkMessage(message, type) {
1721 const messageDiv = document.getElementById(
1722 'maxi-network-license-validation-message'
1723 );
1724 if (!messageDiv) return;
1725
1726 messageDiv.style.display = 'block';
1727 messageDiv.textContent = message;
1728 messageDiv.className = `maxi-license-message maxi-license-${type}`;
1729
1730 // Auto-hide success messages
1731 if (type === 'success') {
1732 setTimeout(() => {
1733 messageDiv.style.display = 'none';
1734 }, 5000);
1735 }
1736 }
1737
1738 /**
1739 * Check network authentication status
1740 */
1741 function checkNetworkAuthStatus() {
1742 const data = new FormData();
1743 data.append('action', 'maxi_network_check_auth_status');
1744 // eslint-disable-next-line no-undef
1745 data.append('nonce', maxiNetworkLicenseSettings.nonce);
1746
1747 // eslint-disable-next-line no-undef
1748 fetch(maxiNetworkLicenseSettings.ajaxUrl, {
1749 method: 'POST',
1750 body: data,
1751 })
1752 .then(response => response.json())
1753 .then(data => {
1754 if (data.success && data.data.is_authenticated) {
1755 // Update UI to reflect current status
1756 updateNetworkLicenseUI(data.data);
1757 }
1758 })
1759 .catch(error => {
1760 console.error('Network auth status check failed:', error);
1761 });
1762 }
1763
1764 /**
1765 * Update network license UI
1766 */
1767 function updateNetworkLicenseUI(data) {
1768 const statusElement = document.getElementById(
1769 'current-network-license-status'
1770 );
1771 const userElement = document.getElementById('current-network-license-user');
1772
1773 if (statusElement) {
1774 statusElement.textContent = data.status;
1775 statusElement.className = data.is_authenticated
1776 ? 'maxi-license-active'
1777 : 'maxi-license-inactive';
1778 }
1779
1780 if (userElement) {
1781 userElement.textContent = data.user_name;
1782 }
1783 }
1784
1785 /**
1786 * Validate email format
1787 */
1788 function isValidEmail(email) {
1789 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1790 return emailRegex.test(email);
1791 }
1792
1793 /**
1794 * Show regular site license message (fallback for existing functionality)
1795 */
1796 function showMessage(message, type) {
1797 const messageDiv = document.getElementById(
1798 'maxi-license-validation-message'
1799 );
1800 if (!messageDiv) {
1801 console.error(message);
1802 return;
1803 }
1804
1805 messageDiv.style.display = 'block';
1806 messageDiv.textContent = message;
1807 messageDiv.className = `maxi-license-message maxi-license-${type}`;
1808
1809 // Auto-hide success messages
1810 if (type === 'success') {
1811 setTimeout(() => {
1812 messageDiv.style.display = 'none';
1813 }, 5000);
1814 }
1815 }
1816