PageSourceSearch

https://zentoagenda.com/assets/js/ux.js?v=20260908

js zentoagenda.com collected 2026-10-02 01:51:14 UTC 6,356 bytes, 134 lines download raw bytes

1(function () {
2  'use strict';
3  const english = document.documentElement.lang === 'en';
4  const copy = {
5    invalid: english ? 'Check the highlighted fields.' : 'Revisa los campos indicados.',
6    sending: english ? 'Sending… Please wait.' : 'Enviando… Espera un momento.',
7  };
8
9  function initForms() {
10    document.querySelectorAll('form[data-zento-form]').forEach(function (form) {
11      const status = document.createElement('div');
12      status.className = 'zento-form-status';
13      status.setAttribute('role', 'status');
14      status.setAttribute('aria-live', 'polite');
15      form.appendChild(status);
16      let busy = false;
17      const markedButtons = [];
18      function feedback(field) {
19        if (!field.id) field.id = 'zento-field-' + Math.random().toString(36).slice(2);
20        const described = (field.getAttribute('aria-describedby') || '').split(/\s+/);
21        let message = described.map(id => document.getElementById(id))
22          .find(el => el && el.classList.contains('invalid-feedback'));
23        if (!message) {
24          message = document.createElement('div');
25          message.id = field.id + '-feedback';
26          message.className = 'invalid-feedback';
27          const group = field.closest('.input-group');
28          (group || field).insertAdjacentElement('afterend', message);
29          field.setAttribute('aria-describedby', described.filter(Boolean).concat(message.id).join(' '));
30        }
31        return message;
32      }
33      function mark(field, message) {
34        const el = feedback(field);
35        el.textContent = message;
36        el.classList.toggle('d-block', !!message);
37        field.setAttribute('aria-invalid', message ? 'true' : 'false');
38      }
39      form.addEventListener('invalid', function (event) {
40        mark(event.target, event.target.validationMessage);
41        status.textContent = copy.invalid;
42      }, true);
43      form.addEventListener('input', function (event) {
44        if (event.target.matches('input, select, textarea') && event.target.getAttribute('aria-invalid') === 'true') {
45          mark(event.target, event.target.validity.valid ? '' : event.target.validationMessage);
46        }
47      });
48      form.addEventListener('change', function (event) {
49        if (event.target.matches('input, select, textarea') && event.target.validity.valid) mark(event.target, '');
50      });
51      let firstError = null;
52      form.querySelectorAll('input, select, textarea').forEach(function (field) {
53        const ids = (field.getAttribute('aria-describedby') || '').split(/\s+/);
54        const error = ids.map(id => document.getElementById(id))
55          .find(el => el && el.classList.contains('invalid-feedback') && el.textContent.trim());
56        if (error) { field.setAttribute('aria-invalid', 'true'); firstError = firstError || field; }
57      });
58      if (firstError) firstError.focus();
59      form.addEventListener('submit', function (event) {
60        if (busy) { event.preventDefault(); return; }
61        // Run after other handlers. Payment modals and AJAX flows retain their own lifecycle.
62        queueMicrotask(function () {
63          if (event.defaultPrevented || !form.hasAttribute('data-zento-submit-lock')) return;
64          busy = true;
65          form.setAttribute('aria-busy', 'true');
66          status.textContent = copy.sending;
67          form.querySelectorAll('button[type="submit"], input[type="submit"]').forEach(function (button) {
68            button.setAttribute('aria-disabled', 'true');
69            markedButtons.push(button);
70          });
71        });
72      });
73      window.addEventListener('pageshow', function () {
74        busy = false;
75        form.removeAttribute('aria-busy');
76        status.textContent = '';
77        markedButtons.forEach(button => button.removeAttribute('aria-disabled'));
78      });
79    });
80  }
81
82  function initNavigation() {
83    function sync() {
84      const links = Array.from(document.querySelectorAll('.ab-mobile-bottom-nav a'));
85      const exact = links.find(link => {
86        const url = new URL(link.href, location.href);
87        return url.pathname === location.pathname && url.hash === location.hash && !!url.hash;
88      });
89      if (!exact && !document.getElementById('mis-citas')) return;
90      links.forEach(function (link) {
91        const active = exact ? link === exact : new URL(link.href).pathname === location.pathname && !new URL(link.href).hash;
92        link.classList.toggle('active', active);
93        link.classList.toggle('ab-mobile-nav-active', active);
94        if (active) link.setAttribute('aria-current', 'page'); else link.removeAttribute('aria-current');
95      });
96    }
97    sync();
98    window.addEventListener('hashchange', sync);
99  }
100
101  function initOnboarding() {
102    const config = window.ZentoUX || {};
103    if (!config.employeeId) return;
104    const key = 'zento:onboarding:' + config.employeeId;
105    let saved = null;
106    try {
107      saved = JSON.parse(localStorage.getItem(key) || 'null');
108      if (!saved || !Number.isInteger(saved.step) || saved.step < 1 || saved.step > 6 || !Number.isFinite(saved.expires) || saved.expires < Date.now()) saved = null;
109      if (config.complete) { localStorage.removeItem(key); saved = null; }
110      if (config.step) {
111        saved = { step: config.step, expires: Date.now() + 30 * 86400000 };
112        localStorage.setItem(key, JSON.stringify(saved));
113      }
114    } catch (_) { /* Server session remains the fallback when local storage is unavailable. */ }
115    const step = config.resumeStep || (saved ? saved.step : 0);
116    if (step && !config.complete && !config.step) {
117      try { localStorage.setItem(key, JSON.stringify({ step: step, expires: Date.now() + 30 * 86400000 })); } catch (_) {}
118    }
119    document.querySelectorAll('[data-zento-resume]').forEach(function (link) {
120      if (step && !config.complete) {
121        const url = new URL(link.href, location.href);
122        url.searchParams.set('step', String(step));
123        link.href = url.href;
124        const card = link.closest('[data-zento-resume-card]');
125        if (card) card.hidden = false;
126      }
127    });
128    // Store only the checkpoint. Names, phones, passwords and form drafts never go here.
129  }
130
131  function init() { initForms(); initNavigation(); initOnboarding(); }
132  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
133  else init();
134})();

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.