mirror of
https://github.com/dyzulk/twinpath-hotspot-themes.git
synced 2026-01-25 21:18:47 +07:00
chore: release v1.1.0
This commit is contained in:
@@ -584,7 +584,7 @@ footer {
|
||||
transform: translateY(-50%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.5;
|
||||
opacity: 0.9;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
52
js/script.js
52
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 = `
|
||||
<div class="confirm-item" style="margin-bottom: 12px; border-bottom: 1px solid var(--border); padding-bottom: 8px;">
|
||||
<span class="confirm-label" data-i18n="user_label">${getTranslation('user_label')}</span>
|
||||
@@ -269,9 +303,9 @@ function checkVoucher(forceCode = null) {
|
||||
<div style="color: #fff; font-weight: 600;">${data.data_left}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 15px; background: rgba(255,77,77,0.1); padding: 8px; border-radius: 4px; border: 1px solid rgba(255,77,77,0.2);">
|
||||
<div class="confirm-label" style="color: #ff4d4d;">EXPIRED AT</div>
|
||||
<div style="color: #ff4d4d; font-family: monospace; font-weight: bold;">${data.expired_at}</div>
|
||||
<div style="margin-top: 15px; background: ${statusBg}; padding: 8px; border-radius: 4px; border: 1px solid ${statusBorder};">
|
||||
<div class="confirm-label" style="color: ${statusColor};" data-i18n="status_label">STATUS</div>
|
||||
<div style="color: ${statusColor}; font-family: monospace; font-weight: bold;">${statusText}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -361,6 +395,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Enter key for Voucher Check
|
||||
const infoInput = document.getElementById('info-input');
|
||||
if (infoInput) {
|
||||
infoInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
checkVoucher(); // No form to prevent anymore
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function togglePassword(inputId, button) {
|
||||
|
||||
67
login.html
67
login.html
@@ -59,9 +59,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<!-- Login Form (Voucher & Member Only) -->
|
||||
<form name="login" action="$(link-login-only)" method="post" onsubmit="return doLogin()">
|
||||
<input type="hidden" name="dst" value="$(link-orig)">
|
||||
<input type="hidden" name="popup" value="true">
|
||||
<!-- Hidden submit to capture Enter key on inputs -->
|
||||
<input type="submit" style="display:none" />
|
||||
|
||||
<!-- Voucher Mode -->
|
||||
<div id="voucher-mode">
|
||||
@@ -96,38 +99,40 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info/Check Mode -->
|
||||
<div id="info-mode" class="hidden">
|
||||
<div class="input-group">
|
||||
<label class="input-label" data-i18n="info_label">Check Validity</label>
|
||||
<div class="input-wrapper">
|
||||
<img src="svg/search.svg" class="input-icon-img" alt="" data-asset="icon_search">
|
||||
<input type="text" id="info-input" class="input-field input-with-icon" data-i18n="voucher_placeholder" placeholder="Enter code to check...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div style="display: grid; gap: 0.75rem;">
|
||||
<button type="submit" id="login-btn" class="btn btn-primary" data-i18n="login_voucher">Use Voucher</button>
|
||||
<button type="button" id="check-btn" class="btn btn-primary hidden" onclick="checkVoucher()" data-i18n="check_btn">Check Status</button>
|
||||
|
||||
<button type="button" class="btn btn-outline" id="scan-btn" onclick="openQR(getActiveMode())">
|
||||
<img src="svg/scan-line.svg" width="16" height="16" alt="" data-asset="icon_scan" style="margin-right: 0.5rem; vertical-align: text-bottom;">
|
||||
<span data-i18n="scan_btn">Scan QR Code</span>
|
||||
</button>
|
||||
|
||||
$(if trial == 'yes')
|
||||
<div style="text-align: center; margin-top: 0.5rem; font-size: 0.8rem; color: var(--fg-secondary);">
|
||||
<span data-i18n="or_text">Or</span>
|
||||
<button type="button" onclick="location.href='$(link-login-only)?dst=$(link-orig-esc)&username=T-$(mac-esc)'" class="btn btn-outline" style="margin-top: 0.5rem" data-i18n="trial_btn">
|
||||
Free Trial Access
|
||||
</button>
|
||||
</div>
|
||||
$(endif)
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Info/Check Mode (Outside Form) -->
|
||||
<div id="info-mode" class="hidden">
|
||||
<div class="input-group">
|
||||
<label class="input-label" data-i18n="info_label">Check Validity</label>
|
||||
<div class="input-wrapper">
|
||||
<img src="svg/search.svg" class="input-icon-img" alt="" data-asset="icon_search">
|
||||
<input type="text" id="info-input" class="input-field input-with-icon" data-i18n="voucher_placeholder" placeholder="Enter code to check...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons (Shared Grid) -->
|
||||
<div style="display: grid; gap: 0.75rem;">
|
||||
<!-- Login Button: Triggers Form Submit via JS -->
|
||||
<button type="button" id="login-btn" class="btn btn-primary" data-i18n="login_voucher" onclick="if(doLogin()) document.login.submit()">Use Voucher</button>
|
||||
|
||||
<button type="button" id="check-btn" class="btn btn-primary hidden" onclick="checkVoucher()" data-i18n="check_btn">Check Status</button>
|
||||
|
||||
<button type="button" class="btn btn-outline" id="scan-btn" onclick="openQR(getActiveMode())">
|
||||
<img src="svg/scan-line.svg" width="16" height="16" alt="" data-asset="icon_scan" style="margin-right: 0.5rem; vertical-align: text-bottom;">
|
||||
<span data-i18n="scan_btn">Scan QR Code</span>
|
||||
</button>
|
||||
|
||||
$(if trial == 'yes')
|
||||
<div id="trial-container" style="text-align: center; margin-top: 0.5rem; font-size: 0.8rem; color: var(--fg-secondary);">
|
||||
<span data-i18n="or_text">Or</span>
|
||||
<button type="button" onclick="location.href='$(link-login-only)?dst=$(link-orig-esc)&username=T-$(mac-esc)'" class="btn btn-outline" style="margin-top: 0.5rem" data-i18n="trial_btn">
|
||||
Free Trial Access
|
||||
</button>
|
||||
</div>
|
||||
$(endif)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pricing Section -->
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ededed" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 273 B |
Reference in New Issue
Block a user