diff --git a/css/style.css b/css/style.css index c354fc2..212ec50 100644 --- a/css/style.css +++ b/css/style.css @@ -584,7 +584,7 @@ footer { transform: translateY(-50%); width: 16px; height: 16px; - opacity: 0.5; + opacity: 0.9; pointer-events: none; } diff --git a/js/languages.js b/js/languages.js index 7eafe63..48c06f0 100644 --- a/js/languages.js +++ b/js/languages.js @@ -62,7 +62,8 @@ const translations = { check_not_found: "Voucher tidak ditemukan atau belum aktif.", check_expired: "Voucher sudah kadaluarsa.", check_valid_until: "Aktif sampai", - check_quota_remaining: "Sisa Kuota" + check_quota_remaining: "Sisa Kuota", + status_label: "STATUS" }, en: { lang_name: "English", @@ -127,7 +128,8 @@ const translations = { check_not_found: "Voucher not found or not active.", check_expired: "Voucher has expired.", check_valid_until: "Valid until", - check_quota_remaining: "Quota Remaining" + check_quota_remaining: "Quota Remaining", + status_label: "STATUS" } }; diff --git a/js/qr-scanner.js b/js/qr-scanner.js index 5540024..b094786 100644 --- a/js/qr-scanner.js +++ b/js/qr-scanner.js @@ -24,13 +24,33 @@ function safeResume() { function handleDecodedText(decodedText) { console.log(`Scan result: ${decodedText}`); + // 1. Fast Path for CHECK MODE: Allow plain text and skip confirmation + if (activeScannerMode === 'check' || activeScannerMode === 'info') { + let code = decodedText; + try { + if (decodedText.startsWith('http://') || decodedText.startsWith('https://')) { + const url = new URL(decodedText); + code = url.searchParams.get('username') || decodedText; + } + } catch (e) { + code = decodedText; + } + + if (code) { + console.log("Check/Info mode: Direct completion for", code); + closeQR(); + checkVoucher(code); // Trigger the API check modal + return; + } + } + let username = ""; let password = ""; let isUnauthorized = false; let blockReason = ""; // Track why it was blocked scannedUrl = ""; - // Check if result is a URL + // Check if result is a URL (STRICT for Login Mode) try { if (decodedText.startsWith('http://') || decodedText.startsWith('https://')) { const url = new URL(decodedText); @@ -75,15 +95,8 @@ function handleDecodedText(decodedText) { blockReason = getTranslation('qr_err_parse'); } - // Fill inputs (only if authorized) + // Fill inputs (only if authorized - Login Mode) if (!isUnauthorized && username) { - if (activeScannerMode === 'check') { - console.log("Check mode: Direct fetch for", username); - closeQR(); - checkVoucher(username); - return; - } - const voucherInput = document.getElementById('voucher-input'); const passField = document.getElementById('voucher-pass'); if (voucherInput) voucherInput.value = username; diff --git a/js/script.js b/js/script.js index 4bd3c06..fc645d1 100644 --- a/js/script.js +++ b/js/script.js @@ -5,10 +5,11 @@ function setMode(mode) { const tabs = document.querySelectorAll('.tab-btn'); const loginBtn = document.getElementById('login-btn'); const checkBtn = document.getElementById('check-btn'); + const trialContainer = document.getElementById('trial-container'); const form = document.login; // Reset visibility - [voucherMode, memberMode, infoMode, loginBtn, checkBtn].forEach(el => { + [voucherMode, memberMode, infoMode, loginBtn, checkBtn, trialContainer].forEach(el => { if (el) el.classList.add('hidden'); }); tabs.forEach(t => t.classList.remove('active')); @@ -16,6 +17,7 @@ function setMode(mode) { if (mode === 'voucher') { if (voucherMode) voucherMode.classList.remove('hidden'); if (loginBtn) loginBtn.classList.remove('hidden'); + if (trialContainer) trialContainer.classList.remove('hidden'); tabs[0].classList.add('active'); if (form) { const code = document.getElementById('voucher-input').value; @@ -25,6 +27,7 @@ function setMode(mode) { } else if (mode === 'member') { if (memberMode) memberMode.classList.remove('hidden'); if (loginBtn) loginBtn.classList.remove('hidden'); + if (trialContainer) trialContainer.classList.remove('hidden'); tabs[1].classList.add('active'); if (form) { form.username.value = document.getElementById('member-user').value; @@ -33,6 +36,7 @@ function setMode(mode) { } else if (mode === 'info') { if (infoMode) infoMode.classList.remove('hidden'); if (checkBtn) checkBtn.classList.remove('hidden'); + // trialContainer remains hidden in info mode tabs[2].classList.add('active'); } @@ -252,6 +256,36 @@ function checkVoucher(forceCode = null) { .then(response => response.json()) .then(data => { if (data.status === 'active') { + const lang = localStorage.getItem('twinpath_lang') || 'en'; + let statusText = data.expired_at; + let statusColor = '#ff4d4d'; // Default red for expiration dates + let statusBg = 'rgba(255,77,77,0.1)'; + let statusBorder = 'rgba(255,77,77,0.2)'; + + // Format logic + if (statusText === 'Active') { + statusColor = '#50e3c2'; // Green + statusBg = 'rgba(80, 227, 194, 0.1)'; + statusBorder = 'rgba(80, 227, 194, 0.2)'; + } else { + // Try to parse date: "jan/15/2026 00:37:47" + const dateParts = statusText.match(/([a-z]+)\/(\d+)\/(\d+)\s+(\d+:\d+:\d+)/i); + if (dateParts) { + try { + const [_, mStr, d, y, time] = dateParts; + const months = { 'jan': 0, 'feb': 1, 'mar': 2, 'apr': 3, 'may': 4, 'jun': 5, 'jul': 6, 'aug': 7, 'sep': 8, 'oct': 9, 'nov': 10, 'dec': 11 }; + const dateObj = new Date(y, months[mStr.toLowerCase()], d); + // Set time components manually if needed or just use date + const [hh, mm, ss] = time.split(':'); + dateObj.setHours(hh, mm, ss); + + const options = { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }; + const formattedDate = new Intl.DateTimeFormat(lang === 'id' ? 'id-ID' : 'en-US', options).format(dateObj); + statusText = formattedDate; // Just the date + } catch (e) { console.error("Date parse error", e); } + } + } + infoContent.innerHTML = `