|
|
| document.addEventListener('DOMContentLoaded', () => { |
| loadJobs(); |
| loadSavedJobs(); |
| setupEventListeners(); |
| }); |
|
|
| |
| 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); |
| }); |
|
|
| |
| document.addEventListener('job-saved', (e) => { |
| saveJob(e.detail); |
| }); |
|
|
| |
| document.addEventListener('job-removed', (e) => { |
| removeJob(e.detail.id); |
| }); |
| } |
|
|
| |
| function saveJob(jobData) { |
| let saved = JSON.parse(localStorage.getItem('warriorSavedJobs')) || []; |
| |
| |
| 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(); |
| } |
|
|
| function removeJob(url) { |
| let saved = JSON.parse(localStorage.getItem('warriorSavedJobs')) || []; |
| saved = saved.filter(j => j.url !== url); |
| localStorage.setItem('warriorSavedJobs', JSON.stringify(saved)); |
| loadSavedJobs(); |
| } |
|
|
| 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(); |
| } |
|
|
| |
| async function loadJobs(searchTerm = '', category = '') { |
| const grid = document.getElementById('job-grid'); |
| |
| |
| 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 => { |
| |
| if (category && job.category !== category) return false; |
| |
| |
| 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 => { |
| |
| 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); |
| |
| 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>`; |
| } |
| } |
|
|
| |
| 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 { |
| |
| const response = await fetch('https://remotive.com/api/remote-jobs?limit=9'); |
| const data = await response.json(); |
| |
| grid.innerHTML = ''; |
|
|
| if(data.jobs && data.jobs.length > 0) { |
| data.jobs.forEach(job => { |
| |
| 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); |
| |
| 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> |
| `; |
| } |
| } |