/** * NETRA UI Utilities * Enhanced interactivity and utility functions for frontend */ // Toast Notification System class Toast { constructor() { this.container = this.ensureContainer(); } ensureContainer() { let container = document.getElementById("toast-container"); if (!container) { container = document.createElement("div"); container.id = "toast-container"; document.body.appendChild(container); } return container; } show(message, type = "info", duration = 3000) { const toast = document.createElement("div"); toast.className = `toast ${type}`; toast.textContent = message; this.container.appendChild(toast); // Auto remove after duration setTimeout(() => { toast.style.animation = "slideInRight 0.3s ease reverse"; setTimeout(() => toast.remove(), 300); }, duration); return toast; } success(message, duration = 3000) { return this.show(message, "success", duration); } error(message, duration = 3000) { return this.show(message, "error", duration); } warning(message, duration = 3000) { return this.show(message, "warning", duration); } info(message, duration = 3000) { return this.show(message, "info", duration); } } // Modal Dialog System class Modal { constructor(title, content, options = {}) { this.title = title; this.content = content; this.options = { closeButton: true, footer: true, size: "medium", onConfirm: null, onCancel: null, ...options, }; this.element = null; this.overlay = null; } create() { // Create overlay this.overlay = document.createElement("div"); this.overlay.className = "modal-overlay"; // Create modal this.element = document.createElement("div"); this.element.className = `modal modal-${this.options.size}`; // Create header const header = document.createElement("div"); header.className = "modal-header"; header.innerHTML = ``; if (this.options.closeButton) { const closeBtn = document.createElement("button"); closeBtn.className = "modal-close"; closeBtn.innerHTML = "×"; closeBtn.onclick = () => this.close(); header.appendChild(closeBtn); } // Create body const body = document.createElement("div"); body.className = "modal-body"; if (typeof this.content === "string") { body.innerHTML = this.content; } else { body.appendChild(this.content); } // Create footer let footer = ""; if (this.options.footer) { footer = ` `; } this.element.appendChild(header); this.element.appendChild(body); if (footer) { this.element.innerHTML += footer; } this.overlay.appendChild(this.element); document.body.appendChild(this.overlay); // Setup click handler for overlay (close on outside click) this.overlay.addEventListener("click", (e) => { if (e.target === this.overlay) { this.close(); } }); return this; } show() { if (!this.element) this.create(); this.overlay.classList.add("open"); return this; } close() { if (this.overlay) { this.overlay.classList.remove("open"); setTimeout(() => { this.overlay?.remove(); this.element = null; this.overlay = null; }, 300); } return this; } static confirm(title, message, onConfirm, onCancel) { const modal = new Modal(title, `

${message}

`, { footer: true, onConfirm, onCancel, }); modal.show(); return modal; } static alert(title, message) { const modal = new Modal(title, `

${message}

`, { footer: false, }); modal.show(); return modal; } } // Loading State Manager class LoadingState { static setLoading(element, isLoading = true) { if (!element) return; if (isLoading) { element.classList.add("loading"); element.disabled = true; const originalText = element.textContent; element.setAttribute("data-original-text", originalText); element.innerHTML = ` Loading...`; } else { element.classList.remove("loading"); element.disabled = false; const originalText = element.getAttribute("data-original-text") || element.textContent; element.textContent = originalText; } } static setLoadingMultiple(elements, isLoading = true) { elements.forEach((el) => this.setLoading(el, isLoading)); } } // Form Validation class FormValidator { static validate(form) { const errors = {}; const inputs = form.querySelectorAll("input, textarea, select"); inputs.forEach((input) => { const error = this.validateInput(input); if (error) { errors[input.name] = error; } }); return { isValid: Object.keys(errors).length === 0, errors, }; } static validateInput(input) { if (input.hasAttribute("required") && !input.value.trim()) { return `${input.name || "This field"} is required`; } if (input.type === "email" && input.value) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(input.value)) { return "Please enter a valid email address"; } } if (input.minLength && input.value.length < input.minLength) { return `Minimum length is ${input.minLength} characters`; } if (input.maxLength && input.value.length > input.maxLength) { return `Maximum length is ${input.maxLength} characters`; } return null; } static showErrors(form, errors) { // Clear previous errors form.querySelectorAll(".error-message").forEach((el) => el.remove()); Object.entries(errors).forEach(([name, message]) => { const input = form.querySelector(`[name="${name}"]`); if (input) { input.classList.add("error"); const errorEl = document.createElement("div"); errorEl.className = "error-message"; errorEl.textContent = message; input.parentNode.insertBefore(errorEl, input.nextSibling); } }); } static clearErrors(form) { form.querySelectorAll(".error-message").forEach((el) => el.remove()); form .querySelectorAll(".error") .forEach((el) => el.classList.remove("error")); } } // Scroll Animation Observer class ScrollAnimation { static init() { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.style.opacity = "1"; entry.target.style.transform = "translateY(0)"; observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: "0px 0px -50px 0px", }, ); document.querySelectorAll(".reveal").forEach((el) => { el.style.opacity = "0"; el.style.transform = "translateY(20px)"; el.style.transition = "opacity 0.6s ease, transform 0.6s ease"; observer.observe(el); }); } } // Network Request Helper class ApiClient { static async request(url, options = {}) { const defaultOptions = { method: "GET", headers: { "Content-Type": "application/json", }, ...options, }; try { const response = await fetch(url, defaultOptions); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error("API request failed:", error); throw error; } } static get(url) { return this.request(url, { method: "GET" }); } static post(url, data) { return this.request(url, { method: "POST", body: JSON.stringify(data), }); } static put(url, data) { return this.request(url, { method: "PUT", body: JSON.stringify(data), }); } static delete(url) { return this.request(url, { method: "DELETE" }); } } // Page Transitions class PageTransition { static fadeOut(element, duration = 300) { return new Promise((resolve) => { element.style.opacity = "0"; element.style.transition = `opacity ${duration}ms ease`; setTimeout(resolve, duration); }); } static fadeIn(element, duration = 300) { return new Promise((resolve) => { element.style.opacity = "0"; setTimeout(() => { element.style.transition = `opacity ${duration}ms ease`; element.style.opacity = "1"; setTimeout(resolve, duration); }, 10); }); } } // Initialize utilities on DOM ready document.addEventListener("DOMContentLoaded", () => { ScrollAnimation.init(); }); // Export for use in other scripts window.Toast = Toast; window.Modal = Modal; window.LoadingState = LoadingState; window.FormValidator = FormValidator; window.ApiClient = ApiClient; window.PageTransition = PageTransition; // Create global toast instance window.toast = new Toast();