feat(dating-autopilot): Introduce interactive popup elements with enhanced event handling in popup.js

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-20 06:02:36 -07:00
parent 7c8d10c593
commit ef79f814d5

View file

@ -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();