(function () { // ============================================================ // CONFIG // ============================================================ const PROD_URL = 'https://www.modernanimal.com/v3/public/'; const STAGING_URL = 'https://staging.modernanimal.com/v3/public/'; const CORS_PROXY = 'https://ma-cors-proxy.info-e9b.workers.dev'; const AUTH_TOKEN = '10dd78c8441ed4079f2b21e8e17690e7c3122151'; const HOURS_LABEL = '8am – 6pm'; const NO_AVAIL = 'No availability this week'; const ALL_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; const JS_DAY_TO_IDX = [6, 0, 1, 2, 3, 4, 5]; // ============================================================ // URL ROUTING // ============================================================ const hostname = window.location.hostname; let targetURL; if (hostname === 'www.modernanimal.com' || hostname === 'modernanimal.com') { targetURL = PROD_URL; } else if (hostname === 'staging.modernanimal.com') { targetURL = STAGING_URL; } else { targetURL = CORS_PROXY; } // ============================================================ // ENTRY // ============================================================ document.querySelectorAll('[data-clinic-hours]').forEach(initWidget); function initWidget(el) { const locationId = el.dataset.locationId; const hoursEl = el.querySelector('[data-hours-days-time]'); if (!locationId || !hoursEl) return; const { startGte, endLte } = getCurrentWeekRange(); fetchOpenDays(locationId, startGte, endLte) .then(function (days) { if (days.length === 0) { hoursEl.innerHTML = NO_AVAIL; } else { hoursEl.innerHTML = formatDays(days) + '
' + HOURS_LABEL; } }) .catch(function () { // fail silently — static HTML left untouched }); } // ============================================================ // API // ============================================================ async function fetchOpenDays(locationId, startGte, endLte) { const payload = { query: ` query GetSlots($startGte: DateTime!, $endLte: DateTime!, $locationIds: [ID!]) { slots( filters: { start: { gte: $startGte } end: { lte: $endLte } location: { id: { inList: $locationIds } } published: true } order: { start: ASC } ) { start } } `, variables: { startGte, endLte, locationIds: [locationId] } }; const res = await fetch(targetURL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': '*/*', 'Authorization': `Bearer ${AUTH_TOKEN}` }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error('API error ' + res.status); const json = await res.json(); const slots = json?.data?.slots ?? []; const daySet = new Set(); slots.forEach(function (s) { daySet.add(JS_DAY_TO_IDX[new Date(s.start).getDay()]); }); return ALL_DAYS.filter(function (_, i) { return daySet.has(i); }); } // ============================================================ // DATE HELPERS // ============================================================ function getCurrentWeekRange() { const now = new Date(); const jsDay = now.getDay(); const toMon = jsDay === 0 ? -6 : 1 - jsDay; const mon = new Date(now); mon.setDate(now.getDate() + toMon); mon.setHours(0, 0, 0, 0); const sun = new Date(mon); sun.setDate(mon.getDate() + 6); sun.setHours(23, 59, 59, 999); return { startGte: toISOWithOffset(mon), endLte: toISOWithOffset(sun) }; } function toISOWithOffset(date) { const pad = n => String(n).padStart(2, '0'); const off = -date.getTimezoneOffset(); const sign = off >= 0 ? '+' : '-'; const absOff = Math.abs(off); return ( date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()) + sign + pad(Math.floor(absOff / 60)) + ':' + pad(absOff % 60) ); } // ============================================================ // DAY FORMATTING // // 3+ consecutive → dash: Mon–Fri // pair → comma: Mon, Tue // singles → comma list, & before last (no Oxford comma) // // Mon–Fri → Mon–Fri // Mon Tue Wed Fri → Mon–Wed & Fri // Mon Tue Thu Fri → Mon, Tue, Thu & Fri // Mon Tue Thu Sat Sun → Mon, Tue, Thu, Sat & Sun // ============================================================ function formatDays(days) { if (!days || days.length === 0) return ''; const indices = days.map(function (d) { return ALL_DAYS.indexOf(d); }); const runs = []; let s = indices[0], e = indices[0]; for (let i = 1; i < indices.length; i++) { if (indices[i] === e + 1) { e = indices[i]; } else { runs.push([s, e]); s = e = indices[i]; } } runs.push([s, e]); const tokens = runs.map(function ([s, e]) { const len = e - s + 1; if (len >= 3) return ALL_DAYS[s] + ' – ' + ALL_DAYS[e]; if (len === 2) return ALL_DAYS[s] + ', ' + ALL_DAYS[e]; return ALL_DAYS[s]; }); if (tokens.length === 1) return tokens[0]; return tokens.slice(0, -1).join(', ') + ' & ' + tokens[tokens.length - 1]; } })();