timelord7000's picture
bueaty now edit to mvp 3 functional features that save actual human struggle time
7811d64 verified
document.addEventListener('DOMContentLoaded', () => {
loadJobs();
loadSavedJobs();
setupEventListeners();
});
// Feature 1 & 3: Global State & Event Listeners
function setupEventListeners() {
const searchInput = document.getElementById('jobSearch');
const categorySelect = document.getElementById('categoryFilter');
searchInput.addEventListener('input', debounce(() => {
loadJobs(searchInput.value, categorySelect.value);
}, 500));
categorySelect.addEventListener('change', () => {
loadJobs(searchInput.value, categorySelect.value);
});
// Listen for save events from job cards
document.addEventListener('job-saved', (e) => {
saveJob(e.detail);
});
// Listen for remove events
document.addEventListener('job-removed', (e) => {
removeJob(e.detail.id);
});
}
// Feature 3: Save Job Logic
function saveJob(jobData) {
let saved = JSON.parse(localStorage.getItem('warriorSavedJobs')) || [];
// Avoid duplicates
if (saved.some(j => j.url === jobData.url)) {
alert('Target already in Hitlist.');
return;
}
jobData.savedAt = new Date().toISOString();
saved.push(jobData);
localStorage.setItem('warriorSavedJobs', JSON.stringify(saved));
loadSavedJobs(); // Refresh UI
}
function removeJob(url) {
let saved = JSON.parse(localStorage.getItem('warriorSavedJobs')) || [];
saved = saved.filter(j => j.url !== url);
localStorage.setItem('warriorSavedJobs', JSON.stringify(saved));
loadSavedJobs(); // Refresh UI
}
function loadSavedJobs() {
const container = document.getElementById('saved-jobs-list');
const saved = JSON.parse(localStorage.getItem('warriorSavedJobs')) || [];
if (saved.length === 0) {
container.innerHTML = `
<div class="text-center py-12 bg-slate-900/50 rounded-xl border border-dashed border-slate-700">
<p class="text-slate-500">No targets locked yet. Click the heart on job cards to save them.</p>
</div>`;
return;
}
container.innerHTML = '';
saved.forEach(job => {
const el = document.createElement('div');
el.className = 'bg-slate-800 p-4 rounded-lg border border-slate-700 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4';
el.innerHTML = `
<div class="flex-grow">
<h4 class="text-white font-bold">${job.title}</h4>
<p class="text-slate-400 text-sm">${job.company}</p>
</div>
<div class="flex items-center gap-3 w-full sm:w-auto">
<span class="text-xs text-slate-500 hidden sm:block">Added: ${new Date(job.savedAt).toLocaleDateString()}</span>
<a href="${job.url}" target="_blank" class="px-4 py-2 bg-primary-600 hover:bg-primary-500 text-white text-sm rounded-lg font-medium">Apply</a>
<button onclick="removeJob('${job.url}')" class="p-2 text-red-400 hover:bg-red-900/20 rounded-lg">
<i data-feather="trash-2" class="w-4 h-4"></i>
</button>
</div>
`;
container.appendChild(el);
});
feather.replace();
}
// Feature 1: Enhanced Load Jobs with Filtering
async function loadJobs(searchTerm = '', category = '') {
const grid = document.getElementById('job-grid');
// Show loading state if it's empty
if (grid.innerHTML.trim() === '') {
grid.innerHTML = `<div class="col-span-full text-center py-12"><i data-feather="loader" class="animate-spin mx-auto text-primary-500 mb-4"></i><p class="text-slate-500">Scanning market...</p></div>`;
feather.replace();
}
try {
const response = await fetch('https://remotive.com/api/remote-jobs?limit=12');
const data = await response.json();
grid.innerHTML = '';
if(data.jobs && data.jobs.length > 0) {
const filtered = data.jobs.filter(job => {
// Category Filter Logic
if (category && job.category !== category) return false;
// Search Logic
if (searchTerm) {
const term = searchTerm.toLowerCase();
const title = job.title.toLowerCase();
const company = job.company_name.toLowerCase();
return title.includes(term) || company.includes(term);
}
return true;
});
if (filtered.length === 0) {
grid.innerHTML = '<p class="col-span-full text-center text-slate-500 py-10">No targets match your filters.</p>';
return;
}
filtered.forEach(job => {
// Check if already saved
const saved = JSON.parse(localStorage.getItem('warriorSavedJobs')) || [];
const isSaved = saved.some(j => j.url === job.url);
const card = document.createElement('job-card');
card.setAttribute('title', job.title);
card.setAttribute('company', job.company_name);
card.setAttribute('salary', job.salary || 'Competitive');
card.setAttribute('type', job.job_type);
card.setAttribute('url', job.url);
card.setAttribute('category', job.category);
card.setAttribute('saved', isSaved); // Pass state
grid.appendChild(card);
});
}
} catch (error) {
console.error('Error:', error);
grid.innerHTML = `<div class="col-span-full text-center p-6 border border-red-900/50 bg-red-900/10 rounded-lg"><p class="text-red-400">Connection lost.</p></div>`;
}
}
// Utility: Debounce search input
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
async function loadJobs() {
const grid = document.getElementById('job-grid');
try {
// Using Remotive API for remote jobs (Public, No Key needed usually)
const response = await fetch('https://remotive.com/api/remote-jobs?limit=9');
const data = await response.json();
grid.innerHTML = ''; // Clear loading state
if(data.jobs && data.jobs.length > 0) {
data.jobs.forEach(job => {
// Create a job-card element
const card = document.createElement('job-card');
// Set attributes (Passing data to component)
card.setAttribute('title', job.title);
card.setAttribute('company', job.company_name);
card.setAttribute('salary', job.salary || 'Competitive');
card.setAttribute('type', job.job_type);
card.setAttribute('url', job.url);
card.setAttribute('category', job.category);
grid.appendChild(card);
});
} else {
grid.innerHTML = '<p class="col-span-full text-center text-slate-500">No targets found at this moment.</p>';
}
} catch (error) {
console.error('Error fetching jobs:', error);
grid.innerHTML = `
<div class="col-span-full text-center p-6 border border-red-900/50 bg-red-900/10 rounded-lg">
<p class="text-red-400">Connection lost to the network.</p>
<button onclick="location.reload()" class="mt-2 text-sm underline text-slate-400">Retry</button>
</div>
`;
}
}