From 77a66a643aab11245bfd1ec4e65640c420d6f001 Mon Sep 17 00:00:00 2001 From: dyzulk <66510723+dyzulk@users.noreply.github.com> Date: Mon, 12 Jan 2026 09:47:24 +0700 Subject: [PATCH] Feature: Implement Voucher Check (Opsi A) with 3-tab UI --- js/config.js | 9 +++-- js/languages.js | 20 +++++++++- js/qr-scanner.js | 27 ++++++++++++- js/script.js | 100 ++++++++++++++++++++++++++++++++++++++--------- login.html | 17 +++++++- svg/search.svg | 1 + 6 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 svg/search.svg diff --git a/js/config.js b/js/config.js index 33d448d..73ced8f 100644 --- a/js/config.js +++ b/js/config.js @@ -2,12 +2,12 @@ const brandConfig = { brandName: "TwinpathNet", portalUrl: "http://welcome.dyzulk.com/login", allowedDomains: [ - "welcome.dyzulk.com", // Main Portal - "10.0.0.1", // Default Gateway (Local IP) - "dyzulk.com" // Custom Domain + "welcome.dyzulk.com" ], creditName: "dyzulk.com", creditUrl: "https://dyzulk.com", + mikhmonUrl: "https://mikhmon.dyzulk.com", + mikhmonSession: "Twinpath-Net", assets: { logo: "img/logo-twinpath.svg", icon_ticket: "svg/ticket.svg", @@ -23,7 +23,8 @@ const brandConfig = { icon_clock: "svg/clock.svg", icon_upload: "svg/upload-cloud.svg", icon_download: "svg/download-cloud.svg", - icon_wifi: "svg/wifi.svg" + icon_wifi: "svg/wifi.svg", + icon_search: "svg/search.svg" } }; diff --git a/js/languages.js b/js/languages.js index 8a844e7..7eafe63 100644 --- a/js/languages.js +++ b/js/languages.js @@ -54,7 +54,15 @@ const translations = { qr_err_unauthorized: "Domain Tidak Sah", qr_err_invalid_url: "URL Login Hotspot Tidak Sah", qr_err_invalid_content: "Konten Tidak Sah (Hanya URL)", - qr_err_parse: "Gagal Membaca QR" + qr_err_parse: "Gagal Membaca QR", + tab_info: "Info", + info_label: "Cek Masa Aktif", + check_btn: "Cek Status", + check_loading: "Mengecek voucher...", + check_not_found: "Voucher tidak ditemukan atau belum aktif.", + check_expired: "Voucher sudah kadaluarsa.", + check_valid_until: "Aktif sampai", + check_quota_remaining: "Sisa Kuota" }, en: { lang_name: "English", @@ -111,7 +119,15 @@ const translations = { qr_err_unauthorized: "Unauthorized Domain", qr_err_invalid_url: "Invalid Hotspot Login URL", qr_err_invalid_content: "Invalid Content (Only URL allowed)", - qr_err_parse: "QR Parse Error" + qr_err_parse: "QR Parse Error", + tab_info: "Info", + info_label: "Check Validity", + check_btn: "Check Status", + check_loading: "Checking voucher...", + check_not_found: "Voucher not found or not active.", + check_expired: "Voucher has expired.", + check_valid_until: "Valid until", + check_quota_remaining: "Quota Remaining" } }; diff --git a/js/qr-scanner.js b/js/qr-scanner.js index d1473c1..6591379 100644 --- a/js/qr-scanner.js +++ b/js/qr-scanner.js @@ -87,14 +87,28 @@ function handleDecodedText(decodedText) { const overlay = document.getElementById('qr-confirm-overlay'); const confirmUser = document.getElementById('confirm-user'); const connectBtn = document.querySelector('button[onclick="proceedSubmit()"]'); + const confirmMsg = document.querySelector('[data-i18n="confirm_msg"]'); + // Determine mode from active tab + const activeTab = document.querySelector('.tab-btn.active'); + const isInfoMode = activeTab && activeTab.onclick.toString().includes('info'); + if (overlay && confirmUser) { if (isUnauthorized) { confirmUser.innerHTML = `Blocked: ${blockReason}`; if (connectBtn) connectBtn.style.display = 'none'; } else { confirmUser.innerText = username; - if (connectBtn) connectBtn.style.display = 'block'; + if (connectBtn) { + connectBtn.style.display = 'block'; + if (isInfoMode) { + connectBtn.innerText = getTranslation('check_btn'); + if (confirmMsg) confirmMsg.innerText = getTranslation('info_label'); + } else { + connectBtn.innerText = getTranslation('connect_btn'); + if (confirmMsg) confirmMsg.innerText = getTranslation('confirm_msg'); + } + } } overlay.classList.remove('hidden'); } @@ -111,6 +125,17 @@ function cancelConfirm() { } function proceedSubmit() { + // Determine mode from active tab + const activeTab = document.querySelector('.tab-btn.active'); + const isInfoMode = activeTab && activeTab.onclick.toString().includes('info'); + + if (isInfoMode) { + const username = document.getElementById('confirm-user').innerText; + checkVoucher(username); + closeQR(); + return; + } + // If it's a URL, redirect directly if (scannedUrl) { console.log("Redirecting to scanned URL:", scannedUrl); diff --git a/js/script.js b/js/script.js index b3d2146..8d5e3d5 100644 --- a/js/script.js +++ b/js/script.js @@ -1,38 +1,43 @@ function setMode(mode) { const voucherMode = document.getElementById('voucher-mode'); const memberMode = document.getElementById('member-mode'); - const voucherTab = document.querySelector('.tab-btn:nth-child(1)'); - const memberTab = document.querySelector('.tab-btn:nth-child(2)'); + const infoMode = document.getElementById('info-mode'); + const tabs = document.querySelectorAll('.tab-btn'); + const loginBtn = document.getElementById('login-btn'); + const checkBtn = document.getElementById('check-btn'); const form = document.login; + // Reset visibility + [voucherMode, memberMode, infoMode, loginBtn, checkBtn].forEach(el => { + if (el) el.classList.add('hidden'); + }); + tabs.forEach(t => t.classList.remove('active')); + if (mode === 'voucher') { - voucherMode.classList.remove('hidden'); - memberMode.classList.add('hidden'); - voucherTab.classList.add('active'); - memberTab.classList.remove('active'); - - // Sync to form immediately + if (voucherMode) voucherMode.classList.remove('hidden'); + if (loginBtn) loginBtn.classList.remove('hidden'); + tabs[0].classList.add('active'); if (form) { const code = document.getElementById('voucher-input').value; form.username.value = code; form.password.value = document.getElementById('voucher-pass').value || code; } - } else { - voucherMode.classList.add('hidden'); - memberMode.classList.remove('hidden'); - voucherTab.classList.remove('active'); - memberTab.classList.add('active'); - - // Sync to form immediately + } else if (mode === 'member') { + if (memberMode) memberMode.classList.remove('hidden'); + if (loginBtn) loginBtn.classList.remove('hidden'); + tabs[1].classList.add('active'); if (form) { form.username.value = document.getElementById('member-user').value; form.password.value = document.getElementById('member-pass').value; } + } else if (mode === 'info') { + if (infoMode) infoMode.classList.remove('hidden'); + if (checkBtn) checkBtn.classList.remove('hidden'); + tabs[2].classList.add('active'); } - // Update login button text based on mode - const loginBtn = document.getElementById('login-btn'); - if (loginBtn) { + // Update login button text based on mode (only for voucher/member) + if (loginBtn && (mode === 'voucher' || mode === 'member')) { const lang = localStorage.getItem('twinpath_lang') || 'en'; const key = mode === 'voucher' ? 'login_voucher' : 'login_member'; loginBtn.setAttribute('data-i18n', key); @@ -176,6 +181,65 @@ function updateProgressBars() { } } +function checkVoucher(forceCode = null) { + const input = document.getElementById('info-input'); + const code = forceCode || (input ? input.value.trim() : ""); + if (!code) return; + + const overlay = document.getElementById('qr-confirm-overlay'); + const confirmUser = document.getElementById('confirm-user'); + const confirmMsg = document.querySelector('[data-i18n="confirm_msg"]'); + const connectBtn = document.querySelector('button[onclick="proceedSubmit()"]'); + + // Show loading state in overlay + if (overlay && confirmUser) { + confirmUser.innerText = getTranslation('check_loading'); + if (connectBtn) connectBtn.style.display = 'none'; + overlay.classList.remove('hidden'); + } + + const mikhmonUrl = brandConfig.mikhmonUrl; + const session = brandConfig.mikhmonSession; + const url = `${mikhmonUrl}/status/index.php?session=${session}&nama=${code}&json=true`; + + fetch(url) + .then(response => response.json()) + .then(data => { + if (data.status === 'active') { + confirmUser.innerHTML = ` +