mirror of
https://github.com/dyzulk/twinpath-hotspot-themes.git
synced 2026-01-26 05:25:40 +07:00
chore: release v1.1.0
This commit is contained in:
@@ -584,7 +584,7 @@ footer {
|
|||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
opacity: 0.5;
|
opacity: 0.9;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ const translations = {
|
|||||||
check_not_found: "Voucher tidak ditemukan atau belum aktif.",
|
check_not_found: "Voucher tidak ditemukan atau belum aktif.",
|
||||||
check_expired: "Voucher sudah kadaluarsa.",
|
check_expired: "Voucher sudah kadaluarsa.",
|
||||||
check_valid_until: "Aktif sampai",
|
check_valid_until: "Aktif sampai",
|
||||||
check_quota_remaining: "Sisa Kuota"
|
check_quota_remaining: "Sisa Kuota",
|
||||||
|
status_label: "STATUS"
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
lang_name: "English",
|
lang_name: "English",
|
||||||
@@ -127,7 +128,8 @@ const translations = {
|
|||||||
check_not_found: "Voucher not found or not active.",
|
check_not_found: "Voucher not found or not active.",
|
||||||
check_expired: "Voucher has expired.",
|
check_expired: "Voucher has expired.",
|
||||||
check_valid_until: "Valid until",
|
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) {
|
function handleDecodedText(decodedText) {
|
||||||
console.log(`Scan result: ${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 username = "";
|
||||||
let password = "";
|
let password = "";
|
||||||
let isUnauthorized = false;
|
let isUnauthorized = false;
|
||||||
let blockReason = ""; // Track why it was blocked
|
let blockReason = ""; // Track why it was blocked
|
||||||
scannedUrl = "";
|
scannedUrl = "";
|
||||||
|
|
||||||
// Check if result is a URL
|
// Check if result is a URL (STRICT for Login Mode)
|
||||||
try {
|
try {
|
||||||
if (decodedText.startsWith('http://') || decodedText.startsWith('https://')) {
|
if (decodedText.startsWith('http://') || decodedText.startsWith('https://')) {
|
||||||
const url = new URL(decodedText);
|
const url = new URL(decodedText);
|
||||||
@@ -75,15 +95,8 @@ function handleDecodedText(decodedText) {
|
|||||||
blockReason = getTranslation('qr_err_parse');
|
blockReason = getTranslation('qr_err_parse');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill inputs (only if authorized)
|
// Fill inputs (only if authorized - Login Mode)
|
||||||
if (!isUnauthorized && username) {
|
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 voucherInput = document.getElementById('voucher-input');
|
||||||
const passField = document.getElementById('voucher-pass');
|
const passField = document.getElementById('voucher-pass');
|
||||||
if (voucherInput) voucherInput.value = username;
|
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 tabs = document.querySelectorAll('.tab-btn');
|
||||||
const loginBtn = document.getElementById('login-btn');
|
const loginBtn = document.getElementById('login-btn');
|
||||||
const checkBtn = document.getElementById('check-btn');
|
const checkBtn = document.getElementById('check-btn');
|
||||||
|
const trialContainer = document.getElementById('trial-container');
|
||||||
const form = document.login;
|
const form = document.login;
|
||||||
|
|
||||||
// Reset visibility
|
// Reset visibility
|
||||||
[voucherMode, memberMode, infoMode, loginBtn, checkBtn].forEach(el => {
|
[voucherMode, memberMode, infoMode, loginBtn, checkBtn, trialContainer].forEach(el => {
|
||||||
if (el) el.classList.add('hidden');
|
if (el) el.classList.add('hidden');
|
||||||
});
|
});
|
||||||
tabs.forEach(t => t.classList.remove('active'));
|
tabs.forEach(t => t.classList.remove('active'));
|
||||||
@@ -16,6 +17,7 @@ function setMode(mode) {
|
|||||||
if (mode === 'voucher') {
|
if (mode === 'voucher') {
|
||||||
if (voucherMode) voucherMode.classList.remove('hidden');
|
if (voucherMode) voucherMode.classList.remove('hidden');
|
||||||
if (loginBtn) loginBtn.classList.remove('hidden');
|
if (loginBtn) loginBtn.classList.remove('hidden');
|
||||||
|
if (trialContainer) trialContainer.classList.remove('hidden');
|
||||||
tabs[0].classList.add('active');
|
tabs[0].classList.add('active');
|
||||||
if (form) {
|
if (form) {
|
||||||
const code = document.getElementById('voucher-input').value;
|
const code = document.getElementById('voucher-input').value;
|
||||||
@@ -25,6 +27,7 @@ function setMode(mode) {
|
|||||||
} else if (mode === 'member') {
|
} else if (mode === 'member') {
|
||||||
if (memberMode) memberMode.classList.remove('hidden');
|
if (memberMode) memberMode.classList.remove('hidden');
|
||||||
if (loginBtn) loginBtn.classList.remove('hidden');
|
if (loginBtn) loginBtn.classList.remove('hidden');
|
||||||
|
if (trialContainer) trialContainer.classList.remove('hidden');
|
||||||
tabs[1].classList.add('active');
|
tabs[1].classList.add('active');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.username.value = document.getElementById('member-user').value;
|
form.username.value = document.getElementById('member-user').value;
|
||||||
@@ -33,6 +36,7 @@ function setMode(mode) {
|
|||||||
} else if (mode === 'info') {
|
} else if (mode === 'info') {
|
||||||
if (infoMode) infoMode.classList.remove('hidden');
|
if (infoMode) infoMode.classList.remove('hidden');
|
||||||
if (checkBtn) checkBtn.classList.remove('hidden');
|
if (checkBtn) checkBtn.classList.remove('hidden');
|
||||||
|
// trialContainer remains hidden in info mode
|
||||||
tabs[2].classList.add('active');
|
tabs[2].classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +256,36 @@ function checkVoucher(forceCode = null) {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.status === 'active') {
|
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 = `
|
infoContent.innerHTML = `
|
||||||
<div class="confirm-item" style="margin-bottom: 12px; border-bottom: 1px solid var(--border); padding-bottom: 8px;">
|
<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>
|
<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 style="color: #fff; font-weight: 600;">${data.data_left}</div>
|
||||||
</div>
|
</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 style="margin-top: 15px; background: ${statusBg}; padding: 8px; border-radius: 4px; border: 1px solid ${statusBorder};">
|
||||||
<div class="confirm-label" style="color: #ff4d4d;">EXPIRED AT</div>
|
<div class="confirm-label" style="color: ${statusColor};" data-i18n="status_label">STATUS</div>
|
||||||
<div style="color: #ff4d4d; font-family: monospace; font-weight: bold;">${data.expired_at}</div>
|
<div style="color: ${statusColor}; font-family: monospace; font-weight: bold;">${statusText}</div>
|
||||||
</div>
|
</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) {
|
function togglePassword(inputId, button) {
|
||||||
|
|||||||
67
login.html
67
login.html
@@ -59,9 +59,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Login Form -->
|
<!-- Login Form -->
|
||||||
|
<!-- Login Form (Voucher & Member Only) -->
|
||||||
<form name="login" action="$(link-login-only)" method="post" onsubmit="return doLogin()">
|
<form name="login" action="$(link-login-only)" method="post" onsubmit="return doLogin()">
|
||||||
<input type="hidden" name="dst" value="$(link-orig)">
|
<input type="hidden" name="dst" value="$(link-orig)">
|
||||||
<input type="hidden" name="popup" value="true">
|
<input type="hidden" name="popup" value="true">
|
||||||
|
<!-- Hidden submit to capture Enter key on inputs -->
|
||||||
|
<input type="submit" style="display:none" />
|
||||||
|
|
||||||
<!-- Voucher Mode -->
|
<!-- Voucher Mode -->
|
||||||
<div id="voucher-mode">
|
<div id="voucher-mode">
|
||||||
@@ -96,38 +99,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Pricing Section -->
|
<!-- 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