diff --git a/features/dating-autopilot/extensions/firefox-tryst/popup/popup.js b/features/dating-autopilot/extensions/firefox-tryst/popup/popup.js new file mode 100644 index 000000000..21ec8c33b --- /dev/null +++ b/features/dating-autopilot/extensions/firefox-tryst/popup/popup.js @@ -0,0 +1,158 @@ +// Popup script - display boost state, manual controls, webhook config + +const elements = { + toggleEnabled: document.getElementById('toggleEnabled'), + status: document.getElementById('status'), + errorText: document.getElementById('errorText'), + boostStatus: document.getElementById('boostStatus'), + boostExpires: document.getElementById('boostExpires'), + nextRefresh: document.getElementById('nextRefresh'), + statCycles: document.getElementById('statCycles'), + statUptime: document.getElementById('statUptime'), + lastRefresh: document.getElementById('lastRefresh'), + lastCheck: document.getElementById('lastCheck'), + webhookUrl: document.getElementById('webhookUrl'), + btnRefresh: document.getElementById('btnRefresh'), + btnReset: document.getElementById('btnReset'), +}; + +function formatTime(isoString) { + if (!isoString) return '--'; + const d = new Date(isoString); + const now = new Date(); + const diffMs = d.getTime() - now.getTime(); + + // If in the future, show relative time + if (diffMs > 0) { + const hours = Math.floor(diffMs / 3600000); + const mins = Math.floor((diffMs % 3600000) / 60000); + if (hours > 0) return `${hours}h ${mins}m`; + return `${mins}m`; + } + + // If in the past, show relative + const ago = Math.abs(diffMs); + const mins = Math.floor(ago / 60000); + if (mins < 1) return 'just now'; + if (mins < 60) return `${mins}m ago`; + const hours = Math.floor(mins / 60); + if (hours < 24) return `${hours}h ${mins % 60}m ago`; + return d.toLocaleDateString(); +} + +function formatUptime(startTime) { + if (!startTime) return '0h'; + const diff = Date.now() - new Date(startTime).getTime(); + const hours = Math.floor(diff / 3600000); + if (hours < 1) { + const mins = Math.floor(diff / 60000); + return `${mins}m`; + } + if (hours >= 24) { + const days = Math.floor(hours / 24); + return `${days}d ${hours % 24}h`; + } + return `${hours}h`; +} + +const statusLabels = { + idle: 'Idle', + monitoring: 'Monitoring', + refreshing: 'Refreshing...', + cooldown: 'Cooldown', + error: 'Error', + disabled: 'Disabled', +}; + +function updateUI(state) { + if (!state) return; + + // Toggle + elements.toggleEnabled.checked = state.enabled; + + // Status badge + elements.status.className = 'status ' + (state.status || 'idle'); + elements.status.textContent = statusLabels[state.status] || state.status || 'Idle'; + + // Error display + if (state.lastError && state.status === 'error') { + elements.errorText.textContent = state.lastError; + elements.errorText.style.display = 'block'; + } else { + elements.errorText.style.display = 'none'; + } + + // Boost info + elements.boostStatus.textContent = state.boostActive ? 'Active' : 'Inactive'; + elements.boostExpires.textContent = formatTime(state.boostExpiresAt); + elements.nextRefresh.textContent = formatTime(state.nextRefreshAt); + + // Stats + elements.statCycles.textContent = state.cycleCount || 0; + elements.statUptime.textContent = formatUptime(state.lastRefreshAt ? state.lastRefreshAt : null); + + elements.lastRefresh.textContent = formatTime(state.lastRefreshAt); + elements.lastCheck.textContent = formatTime(state.lastCheckAt); + + // Webhook + if (state.webhookUrl && !elements.webhookUrl.matches(':focus')) { + elements.webhookUrl.value = state.webhookUrl; + } + + // Button state + elements.btnRefresh.disabled = !state.enabled || state.status === 'refreshing'; +} + +async function loadState() { + const state = await browser.runtime.sendMessage({ action: 'getBoostState' }); + updateUI(state); +} + +// ============== EVENT LISTENERS ============== + +elements.toggleEnabled.addEventListener('change', async () => { + const state = await browser.runtime.sendMessage({ + action: 'setEnabled', + enabled: elements.toggleEnabled.checked, + }); + updateUI(state); +}); + +elements.btnRefresh.addEventListener('click', async () => { + elements.btnRefresh.disabled = true; + elements.status.className = 'status refreshing'; + elements.status.textContent = 'Refreshing...'; + + const state = await browser.runtime.sendMessage({ action: 'refreshNow' }); + updateUI(state); +}); + +elements.btnReset.addEventListener('click', async () => { + const state = await browser.runtime.sendMessage({ action: 'resetState' }); + updateUI(state); +}); + +let webhookDebounce; +elements.webhookUrl.addEventListener('input', () => { + clearTimeout(webhookDebounce); + webhookDebounce = setTimeout(async () => { + await browser.runtime.sendMessage({ + action: 'setWebhookUrl', + url: elements.webhookUrl.value.trim(), + }); + }, 500); +}); + +// ============== LIVE UPDATES ============== + +browser.runtime.onMessage.addListener((msg) => { + if (msg.action === 'stateUpdate' || msg.action === 'statsUpdate') { + loadState(); + } +}); + +// Refresh display every 30 seconds for time formatting +setInterval(loadState, 30000); + +// Initial load +loadState();