refactoring by responsibility
This commit is contained in:
1 parent
f6cd518b00
commit
a3cb7f058a
5 files changed
+807
-805
No files matched your search
+165
@@ -0,0 +1,165 @@
|
||||
const translations = window.translations;
|
||||
|
||||
const languageLocales = {
|
||||
vi: 'vi_VN',
|
||||
ru: 'ru_RU',
|
||||
en: 'en_US',
|
||||
zh: 'zh_CN',
|
||||
de: 'de_DE',
|
||||
fr: 'fr_FR'
|
||||
};
|
||||
|
||||
let currentLang = 'vi';
|
||||
let currentWeatherCondition = 'weather_loading';
|
||||
|
||||
function getWeatherPresentation(code) {
|
||||
if (code === 0) return { key: 'weather_clear', icon: 'fa-sun', color: 'text-amber-500' };
|
||||
if (code === 1) return { key: 'weather_mainly_clear', icon: 'fa-cloud-sun', color: 'text-amber-500' };
|
||||
if (code === 2) return { key: 'weather_partly_cloudy', icon: 'fa-cloud-sun', color: 'text-amber-500' };
|
||||
if (code === 3) return { key: 'weather_overcast', icon: 'fa-cloud', color: 'text-gray-500' };
|
||||
if ([45, 48].includes(code)) return { key: 'weather_fog', icon: 'fa-smog', color: 'text-gray-500' };
|
||||
if ([51, 53, 55, 56, 57].includes(code)) return { key: 'weather_drizzle', icon: 'fa-cloud-rain', color: 'text-sky-500' };
|
||||
if ([61, 63, 65, 66, 67].includes(code)) return { key: 'weather_rain', icon: 'fa-cloud-rain', color: 'text-sky-600' };
|
||||
if ([71, 73, 75, 77].includes(code)) return { key: 'weather_snow', icon: 'fa-snowflake', color: 'text-sky-500' };
|
||||
if ([80, 81, 82, 85, 86].includes(code)) return { key: 'weather_showers', icon: 'fa-cloud-showers-heavy', color: 'text-sky-600' };
|
||||
if ([95, 96, 99].includes(code)) return { key: 'weather_thunderstorm', icon: 'fa-cloud-bolt', color: 'text-indigo-500' };
|
||||
return { key: 'weather_partly_cloudy', icon: 'fa-cloud-sun', color: 'text-amber-500' };
|
||||
}
|
||||
|
||||
function renderWeatherCondition() {
|
||||
const condition = document.getElementById('weather-condition');
|
||||
if (condition) condition.textContent = translations[currentLang][currentWeatherCondition] || translations.en[currentWeatherCondition];
|
||||
}
|
||||
|
||||
async function loadCurrentWeather() {
|
||||
const temperature = document.getElementById('weather-temperature');
|
||||
const icon = document.getElementById('weather-icon');
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.open-meteo.com/v1/forecast?latitude=12.2388&longitude=109.1967¤t=temperature_2m,weather_code&timezone=Asia%2FHo_Chi_Minh');
|
||||
if (!response.ok) throw new Error('Weather request failed');
|
||||
|
||||
const data = await response.json();
|
||||
const presentation = getWeatherPresentation(data.current.weather_code);
|
||||
temperature.textContent = `${Math.round(data.current.temperature_2m)}°C`;
|
||||
icon.className = `fa-solid ${presentation.icon} text-2xl ${presentation.color}`;
|
||||
currentWeatherCondition = presentation.key;
|
||||
} catch {
|
||||
temperature.textContent = '--°C';
|
||||
icon.className = 'fa-solid fa-cloud text-2xl text-gray-400';
|
||||
currentWeatherCondition = 'weather_unavailable';
|
||||
}
|
||||
|
||||
renderWeatherCondition();
|
||||
}
|
||||
|
||||
function getPreferredLanguage() {
|
||||
const supported = Object.keys(translations);
|
||||
const preferences = navigator.languages?.length
|
||||
? navigator.languages
|
||||
: [navigator.language];
|
||||
|
||||
for (const locale of preferences) {
|
||||
const language = locale.toLowerCase().split(/[-_]/)[0];
|
||||
if (supported.includes(language)) return language;
|
||||
}
|
||||
|
||||
return 'vi';
|
||||
}
|
||||
|
||||
function getRequestedLanguage() {
|
||||
const requested = new URLSearchParams(window.location.search).get('lang');
|
||||
return translations[requested] ? requested : null;
|
||||
}
|
||||
|
||||
function setLanguage(lang) {
|
||||
if (!translations[lang]) lang = 'vi';
|
||||
currentLang = lang;
|
||||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||
const key = el.getAttribute('data-i18n');
|
||||
if (translations[lang] && translations[lang][key]) {
|
||||
el.textContent = translations[lang][key];
|
||||
}
|
||||
});
|
||||
|
||||
// Reset language button styles
|
||||
const langs = ['vi', 'ru', 'en', 'zh', 'de', 'fr'];
|
||||
langs.forEach(l => {
|
||||
const btn = document.getElementById(`btn-${l}`);
|
||||
if (btn) {
|
||||
if (l === lang) {
|
||||
btn.className = "px-2.5 py-1.5 font-bold rounded-lg transition bg-white text-brand-600 shadow-sm";
|
||||
} else {
|
||||
btn.className = "px-2.5 py-1.5 font-bold rounded-lg transition text-gray-600 hover:text-brand-600";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.documentElement.lang = lang;
|
||||
const languageUrl = new URL(window.location.href);
|
||||
languageUrl.searchParams.set('lang', lang);
|
||||
history.replaceState(null, '', `${languageUrl.pathname}${languageUrl.search}${languageUrl.hash}`);
|
||||
|
||||
const canonicalUrl = new URL(`/?lang=${lang}`, window.location.origin).href;
|
||||
document.getElementById('canonical-url').href = canonicalUrl;
|
||||
document.getElementById('meta-description').content = translations[lang].meta_description;
|
||||
document.getElementById('og-title').content = translations[lang].page_title;
|
||||
document.getElementById('og-description').content = translations[lang].meta_description;
|
||||
document.getElementById('og-url').content = canonicalUrl;
|
||||
document.getElementById('og-locale').content = languageLocales[lang];
|
||||
renderWeatherCondition();
|
||||
}
|
||||
|
||||
function toggleMobileMenu() {
|
||||
const menu = document.getElementById('mobile-menu');
|
||||
menu.classList.toggle('hidden');
|
||||
document.querySelector('[aria-controls="mobile-menu"]').setAttribute('aria-expanded', !menu.classList.contains('hidden'));
|
||||
}
|
||||
|
||||
function filterAttractions(category, event) {
|
||||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.className = "filter-btn px-5 py-2 rounded-xl text-sm font-bold bg-white text-gray-600 hover:bg-gray-100 shadow-sm";
|
||||
});
|
||||
event.currentTarget.className = "filter-btn active px-5 py-2 rounded-xl text-sm font-bold bg-brand-500 text-white shadow";
|
||||
|
||||
document.querySelectorAll('.attraction-card').forEach(card => {
|
||||
if (category === 'all' || card.getAttribute('data-category') === category) {
|
||||
card.style.display = 'flex';
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function handleBooking(event) {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const submitButton = form.querySelector('[type="submit"]');
|
||||
const status = document.getElementById('form-status');
|
||||
status.classList.add('hidden');
|
||||
submitButton.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(form.action, {
|
||||
method: form.method,
|
||||
body: new FormData(form),
|
||||
headers: { Accept: 'application/json' }
|
||||
});
|
||||
if (!response.ok) throw new Error('Form submission failed');
|
||||
|
||||
form.reset();
|
||||
document.getElementById('success-modal').classList.remove('hidden');
|
||||
} catch {
|
||||
status.classList.remove('hidden');
|
||||
} finally {
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('success-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
setLanguage(getRequestedLanguage() || getPreferredLanguage());
|
||||
loadCurrentWeather();
|
||||
setInterval(loadCurrentWeather, 30 * 60 * 1000);
|
||||
Reference in new issue
Block a user