377 lines
11 KiB
JavaScript
377 lines
11 KiB
JavaScript
// Optimized app.js
|
|
const passiveOptions = { passive: true };
|
|
|
|
document.addEventListener('livewire:init', () => {
|
|
Livewire.hook('request', ({ fail }) => {
|
|
fail(({ status, preventDefault }) => {
|
|
if (status === 419) {
|
|
window.location.reload();
|
|
preventDefault();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
Alpine.store('notifications', {
|
|
notifications: [],
|
|
|
|
init() {
|
|
Livewire.on('notify', (e) => {
|
|
this.addNotification(e);
|
|
});
|
|
},
|
|
|
|
addNotification(notification) {
|
|
notification = notification[0];
|
|
notification.show = false;
|
|
notification.id = Date.now() + Math.floor(Math.random() * 1000);
|
|
this.notifications.push(notification);
|
|
|
|
requestAnimationFrame(() => {
|
|
this.notifications = this.notifications.map(n => {
|
|
if (n.id === notification.id) {
|
|
n.show = true;
|
|
}
|
|
return n;
|
|
});
|
|
});
|
|
|
|
setTimeout(() => {
|
|
this.removeNotification(notification.id);
|
|
}, notification.timeout || 5000);
|
|
},
|
|
|
|
removeNotification(id) {
|
|
this.notifications = this.notifications.filter(n => {
|
|
if (n.id === id) {
|
|
n.show = false;
|
|
}
|
|
return n.show;
|
|
});
|
|
}
|
|
});
|
|
|
|
let timeUpdateInterval;
|
|
const laravelLocale = document.documentElement.lang || 'en';
|
|
|
|
const localeMapping = {
|
|
'ar': 'ar-SA', // Arabic (Saudi Arabia)
|
|
'de': 'de-DE', // German (Germany)
|
|
'en': 'en-US', // English (US)
|
|
'es': 'es-ES', // Spanish (Spain)
|
|
'fi': 'fi-FI', // Finnish (Finland)
|
|
'fr': 'fr-FR', // French (France)
|
|
'it': 'it-IT', // Italian (Italy)
|
|
'sv': 'sv-SE', // Swedish (Sweden)
|
|
'uk': 'uk-UA', // Ukrainian (Ukraine)
|
|
'ko': 'ko-KR', // Korean (South Korea)
|
|
'lv': 'lv-LV', // Latvian (Latvia)
|
|
'nl': 'nl-NL', // Dutch (Netherlands)
|
|
'no': 'no-NO', // Norwegian (Norway)
|
|
'pt': 'pt-PT', // Portuguese (Portugal)
|
|
'sr': 'sr-RS', // Serbian (Serbia)
|
|
'id': 'id-ID' // Indonesian (Bahasa Indonesia)
|
|
};
|
|
|
|
function updateDateTime() {
|
|
const now = new Date();
|
|
const timeElement = document.getElementById('current-time');
|
|
const dateElement = document.getElementById('current-date');
|
|
|
|
const jsLocale = localeMapping[laravelLocale] || 'en-US'; // Default to 'id-ID' if not mapped
|
|
|
|
if (timeElement) {
|
|
timeElement.textContent = now.toLocaleTimeString(jsLocale, {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: true
|
|
});
|
|
}
|
|
|
|
if (dateElement) {
|
|
dateElement.textContent = now.toLocaleDateString(jsLocale, {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
});
|
|
}
|
|
}
|
|
|
|
const debounce = (func, wait) => {
|
|
let timeout;
|
|
return (...args) => {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
};
|
|
};
|
|
|
|
const throttle = (func, limit) => {
|
|
let inThrottle;
|
|
return function(...args) {
|
|
if (!inThrottle) {
|
|
func.apply(this, args);
|
|
inThrottle = true;
|
|
setTimeout(() => inThrottle = false, limit);
|
|
}
|
|
};
|
|
};
|
|
|
|
let revealObserver;
|
|
function initRevealAnimations() {
|
|
const revealElements = document.querySelectorAll('.reveal-on-scroll');
|
|
if (revealElements.length === 0) return;
|
|
|
|
const observerOptions = {
|
|
root: null,
|
|
rootMargin: '0px 0px -10% 0px',
|
|
threshold: 0.1
|
|
};
|
|
|
|
revealObserver = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.style.transform = 'translateY(0)';
|
|
entry.target.style.opacity = '1';
|
|
entry.target.classList.add('revealed');
|
|
revealObserver.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, observerOptions);
|
|
|
|
revealElements.forEach(el => {
|
|
el.style.transform = 'translateY(20px)';
|
|
el.style.opacity = '0';
|
|
el.style.transition = 'transform 0.6s ease-out, opacity 0.6s ease-out';
|
|
revealObserver.observe(el);
|
|
});
|
|
}
|
|
|
|
const buttonClickStates = new WeakMap();
|
|
function preventDoubleClick(selector) {
|
|
const buttons = document.querySelectorAll(selector);
|
|
|
|
buttons.forEach(button => {
|
|
if (buttonClickStates.has(button)) return;
|
|
|
|
const clickHandler = function(e) {
|
|
if (buttonClickStates.get(button)) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
|
|
buttonClickStates.set(button, true);
|
|
button.classList.add('btn-loading');
|
|
|
|
const resetButton = () => {
|
|
buttonClickStates.set(button, false);
|
|
button.classList.remove('btn-loading');
|
|
};
|
|
|
|
if (window.requestIdleCallback) {
|
|
setTimeout(() => requestIdleCallback(resetButton), 2000);
|
|
} else {
|
|
setTimeout(resetButton, 2000);
|
|
}
|
|
};
|
|
|
|
button.addEventListener('click', clickHandler, passiveOptions);
|
|
buttonClickStates.set(button, false);
|
|
});
|
|
}
|
|
|
|
let imageObserver;
|
|
function initLazyLoading() {
|
|
const images = document.querySelectorAll('img[data-src]');
|
|
if (images.length === 0) return;
|
|
|
|
imageObserver = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const img = entry.target;
|
|
img.src = img.dataset.src;
|
|
img.classList.remove('lazy');
|
|
img.removeAttribute('data-src');
|
|
imageObserver.unobserve(img);
|
|
}
|
|
});
|
|
}, { rootMargin: '50px' });
|
|
|
|
images.forEach(img => imageObserver.observe(img));
|
|
}
|
|
|
|
let devicePerformanceLevel = null;
|
|
function getDevicePerformance() {
|
|
if (devicePerformanceLevel !== null) return devicePerformanceLevel;
|
|
|
|
const factors = [];
|
|
|
|
if (navigator.hardwareConcurrency) {
|
|
factors.push(navigator.hardwareConcurrency >= 4 ? 1 : 0);
|
|
}
|
|
if (navigator.deviceMemory) {
|
|
factors.push(navigator.deviceMemory >= 4 ? 1 : 0);
|
|
}
|
|
|
|
if (navigator.connection) {
|
|
const conn = navigator.connection;
|
|
factors.push(conn.effectiveType === '4g' ? 1 : 0);
|
|
}
|
|
|
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
factors.push(isMobile ? 0 : 1);
|
|
|
|
const score = factors.reduce((a, b) => a + b, 0) / factors.length;
|
|
devicePerformanceLevel = score > 0.5 ? 'high' : 'low';
|
|
|
|
return devicePerformanceLevel;
|
|
}
|
|
|
|
function optimizeFormInteractions() {
|
|
const forms = document.querySelectorAll('form');
|
|
|
|
forms.forEach(form => {
|
|
const submitBtn = form.querySelector('button[type="submit"], input[type="submit"]');
|
|
if (!submitBtn) return;
|
|
|
|
const submitHandler = function(e) {
|
|
submitBtn.disabled = true;
|
|
submitBtn.classList.add('btn-loading');
|
|
|
|
setTimeout(() => {
|
|
submitBtn.disabled = false;
|
|
submitBtn.classList.remove('btn-loading');
|
|
}, 8000);
|
|
};
|
|
|
|
form.addEventListener('submit', submitHandler, passiveOptions);
|
|
});
|
|
}
|
|
|
|
function optimizeForMobile() {
|
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
|
|
if (isMobile) {
|
|
const viewport = document.querySelector('meta[name=viewport]');
|
|
const inputs = document.querySelectorAll('input, select, textarea');
|
|
|
|
inputs.forEach(input => {
|
|
input.addEventListener('focus', () => {
|
|
if (viewport) {
|
|
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0');
|
|
}
|
|
}, passiveOptions);
|
|
|
|
input.addEventListener('blur', () => {
|
|
if (viewport) {
|
|
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0');
|
|
}
|
|
}, passiveOptions);
|
|
});
|
|
|
|
document.body.classList.add('mobile-optimized');
|
|
}
|
|
}
|
|
|
|
function setupErrorHandling() {
|
|
window.addEventListener('error', (e) => {
|
|
console.error('Error:', e.error);
|
|
});
|
|
|
|
window.addEventListener('unhandledrejection', (e) => {
|
|
console.error('Unhandled promise rejection:', e.reason);
|
|
});
|
|
}
|
|
|
|
function showErrorNotification(message) {
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.className = 'fixed top-4 right-4 bg-red-500 text-white px-4 py-2 rounded shadow-lg z-50 transition-opacity duration-300';
|
|
errorDiv.textContent = message;
|
|
|
|
document.body.appendChild(errorDiv);
|
|
|
|
requestAnimationFrame(() => {
|
|
errorDiv.style.opacity = '1';
|
|
});
|
|
|
|
setTimeout(() => {
|
|
errorDiv.style.opacity = '0';
|
|
setTimeout(() => errorDiv.remove(), 300);
|
|
}, 4000);
|
|
}
|
|
|
|
function setupNetworkHandling() {
|
|
let offlineNotification = null;
|
|
|
|
window.addEventListener('offline', () => {
|
|
if (offlineNotification) return;
|
|
|
|
offlineNotification = document.createElement('div');
|
|
offlineNotification.className = 'fixed top-4 right-4 bg-yellow-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
|
offlineNotification.textContent = 'You are offline. Some features may not work.';
|
|
document.body.appendChild(offlineNotification);
|
|
});
|
|
|
|
window.addEventListener('online', () => {
|
|
if (offlineNotification) {
|
|
offlineNotification.remove();
|
|
offlineNotification = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
function initializeApp() {
|
|
updateDateTime();
|
|
timeUpdateInterval = setInterval(updateDateTime, 1000);
|
|
|
|
const performance = getDevicePerformance();
|
|
|
|
if (performance === 'high') {
|
|
initRevealAnimations();
|
|
initLazyLoading();
|
|
} else {
|
|
document.body.classList.add('low-performance');
|
|
document.querySelectorAll('.reveal-on-scroll').forEach(el => {
|
|
el.classList.add('revealed');
|
|
});
|
|
}
|
|
|
|
preventDoubleClick('.btn-primary, [href*="checkout"], [href*="add-to-cart"]');
|
|
optimizeFormInteractions();
|
|
optimizeForMobile();
|
|
setupErrorHandling();
|
|
setupNetworkHandling();
|
|
|
|
if (performance === 'low') {
|
|
const heavyElements = document.querySelectorAll('.animate-pulse-slowest, .animate-pulse-slow, .animate-pulse-fast');
|
|
heavyElements.forEach(el => {
|
|
el.style.animation = 'none';
|
|
el.style.opacity = '0.1';
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener('touchstart', () => {}, passiveOptions);
|
|
document.addEventListener('touchmove', () => {}, passiveOptions);
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initializeApp);
|
|
} else {
|
|
initializeApp();
|
|
}
|
|
|
|
window.addEventListener('beforeunload', () => {
|
|
if (timeUpdateInterval) clearInterval(timeUpdateInterval);
|
|
if (revealObserver) revealObserver.disconnect();
|
|
if (imageObserver) imageObserver.disconnect();
|
|
});
|
|
|
|
window.AppOptimizer = {
|
|
debounce,
|
|
throttle,
|
|
getDevicePerformance,
|
|
showErrorNotification
|
|
};
|
|
|