Scanning marketplaces now
Never miss
a deal again.
MotoHawk is an AI agent that hunts motorcycle deals across every marketplace 24/7. It watches so you don't have to.
Failed to load hunts.
';
}
}
function renderHunts() {
var el = document.getElementById('huntsContainer');
if (!hunts.length) {
el.innerHTML = '' + (criteria.length ? criteria.map(function(c){return ''+esc(c)+'';}).join('') : 'All motorcycles') + '
' + lastScraped + '' + (h.alert_count||0) + ' alerts
';
}
function toggleNewHuntForm() {
var form = document.getElementById('newHuntForm');
var btn = document.getElementById('newHuntBtn');
var open = form.style.display === 'none';
form.style.display = open ? '' : 'none';
btn.textContent = open ? '\u2715 Cancel' : '+ New Hunt';
}
async function handleCreateHunt() {
var name = document.getElementById('huntName').value.trim();
if (!name) { toast('Hunt name is required', 'error'); return; }
var body = {
name: name,
make: document.getElementById('huntMake').value.trim() || null,
model: document.getElementById('huntModel').value.trim() || null,
yearMin: parseInt(document.getElementById('huntYearMin').value) || null,
yearMax: parseInt(document.getElementById('huntYearMax').value) || null,
maxPrice: parseInt(document.getElementById('huntMaxPrice').value) || null,
location: document.getElementById('huntLocation').value.trim() || null,
radiusMiles: parseInt(document.getElementById('huntRadius').value) || 100
};
try {
var res = await fetch('/api/hunts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
var data = await res.json();
if (!res.ok) { toast(data.error || 'Failed to create hunt', 'error'); return; }
hunts.unshift(data.hunt);
renderHunts();
toggleNewHuntForm();
clearHuntForm();
toast("Hunt created! We'll start checking shortly.", 'success');
} catch(e) { toast('Network error', 'error'); }
}
function clearHuntForm() {
['huntName','huntMake','huntModel','huntYearMin','huntYearMax','huntMaxPrice','huntLocation','huntRadius']
.forEach(function(id) { var el = document.getElementById(id); if(el) el.value=''; });
}
async function toggleHuntActive(id, active) {
try {
var res = await fetch('/api/hunts/' + id, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ active: active })
});
if (!res.ok) throw new Error('update failed');
var hunt = hunts.find(function(h){return h.id===id;});
if (hunt) hunt.active = active;
renderHunts();
toast(active ? 'Hunt resumed' : 'Hunt paused');
} catch(e) { toast('Failed to update hunt', 'error'); }
}
async function deleteHunt(id) {
if (!confirm('Delete this hunt? All associated alerts will also be deleted.')) return;
try {
var res = await fetch('/api/hunts/' + id, { method: 'DELETE' });
if (!res.ok) throw new Error('delete failed');
hunts = hunts.filter(function(h){return h.id!==id;});
renderHunts();
toast('Hunt deleted');
} catch(e) { toast('Failed to delete hunt', 'error'); }
}
async function loadAlerts() {
try {
var res = await fetch('/api/hunts/alerts');
var data = await res.json();
alerts = data.alerts || [];
renderAlerts();
var badge = document.getElementById('alertBadge');
if (alerts.length > 0) badge.textContent = '(' + alerts.length + ')';
} catch(e) {
document.getElementById('alertsContainer').innerHTML = 'Failed to load alerts.
';
}
}
function renderAlerts() {
var el = document.getElementById('alertsContainer');
if (!alerts.length) {
el.innerHTML = '-' + pct.toFixed(0) + '%below
' + esc(title) + '
' + tags.map(function(t){return ''+esc(String(t))+'';}).join('') + '
Hunt: ' + esc(a.hunt_name) + ' · ' + timeAgo(a.created_at) + '
$' + Number(a.price).toLocaleString() + '' + (a.market_avg_price ? '
$' + Number(a.market_avg_price).toLocaleString() + '
' : '') + '
View → ';
}
function esc(s) {
if (!s && s !== 0) return '';
return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');
}
function timeAgo(iso) {
var ms = Date.now() - new Date(iso).getTime();
var mins = Math.floor(ms / 60000);
if (mins < 2) return 'just now';
if (mins < 60) return mins + 'm ago';
var hrs = Math.floor(mins / 60);
if (hrs < 24) return hrs + 'h ago';
return Math.floor(hrs / 24) + 'd ago';
}
var toastTimeout;
function toast(msg, type) {
var el = document.getElementById('toast');
el.textContent = msg;
el.className = 'toast show' + (type ? ' ' + type : '');
clearTimeout(toastTimeout);
toastTimeout = setTimeout(function(){ el.classList.remove('show'); }, 3000);
}
document.getElementById('signupPassword').addEventListener('keydown', function(e){ if(e.key==='Enter') handleSignup(); });
document.getElementById('loginPassword').addEventListener('keydown', function(e){ if(e.key==='Enter') handleLogin(); });