| |
|
| | 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> |
| | `; |
| | } |
| | } |