diff --git a/package.json b/package.json index 6af3dbc..01c2069 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1" }, "dependencies": { - "dompurify": "2.2.2", - "gatsby": "^5.14.0", + "dompurify": "^2.5.8", + "gatsby": "^3.3.1", "marked": "^4.1.1", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/src/filter-jobs.mjs b/src/filter-jobs.mjs index 623b1ca..8439d22 100644 --- a/src/filter-jobs.mjs +++ b/src/filter-jobs.mjs @@ -1,31 +1,42 @@ -import { isJobExpired } from "./utils.mjs"; +const filterJobs = (jobs, filter) => { -const filterJobs = (jobs, filter) => { const today = new Date(); + const minOSS = filter.ossTimeGt ? parseInt(filter.ossTimeGt, 10) : null; - // Filter out expired jobs if requested. - if (!filter.showExpired) { - jobs = jobs.filter((job) => !isJobExpired(job, today)); - } - - if (filter.fullTime) { - jobs = jobs.filter((job) => job.percentTime === 100); - } - - if (filter.ossTimeGt) { - // Ensure we're comparing numbers - const minOSS = parseInt(filter.ossTimeGt, 10); - jobs = jobs.filter((job) => job.percentOSS >= minOSS); - } - - if (filter.remote) { - // Matches "remote", "Remote (US)", "London / Remote", etc. - jobs = jobs.filter((job) => - (job.location || "remote").toLowerCase().includes("remote") - ); - } - - return jobs; + return jobs.filter((job) => { + if (!filter.showExpired && isJobExpired(job, today)) { + return false; + } + + if (filter.fullTime && job.percentTime !== 100) { + return false; + } + + if (minOSS !== null && job.percentOSS < minOSS) { + return false; + } + + if (filter.remote) { + const location = (job.location || "").toLowerCase(); + return location.includes("remote"); + } + + return true; + }); +}; + + +// here we consider a job expired if its posted date is more than 30 days old +const isJobExpired = (job, today) => { + if (!job.postedDate) return false; // If no posted date, assume it's not expired + + const postedDate = new Date(job.postedDate); + const diffTime = Math.abs(today - postedDate); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + return diffDays > 30; }; -export default filterJobs; +export default filterJobs; +// we can use this code for mordern js and it will work in node and browser both. + diff --git a/src/styles/global.css b/src/styles/global.css index f6549a2..4d08a4f 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -28,8 +28,7 @@ a { color: #2c3e50; font-weight: bold; } -.page { -} + .content { padding-left: 1rem; padding-right: 1rem; @@ -137,34 +136,171 @@ a { font-weight: bold; font-size: 0.9rem; } -.job a { -} + .job .meta a { - font-weight: normal; + +* { + box-sizing: border-box; } -.badges { - float: right; + +html { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; } -.badge { + +body { + margin: 0; + background: #f7f9fc; + color: #2c3e50; +} + + +.content { + max-width: 1200px; + margin: auto; + padding: 2rem; +} + +.jobsLayout { + display: flex; + gap: 2rem; +} + + +.leftColumn { + width: 250px; + flex-shrink: 0; } -.badge .tooltip { - visibility: hidden; - background-color: #007d8a; + +.filter { + background: white; + padding: 1.5rem; + border-radius: 10px; + box-shadow: 0 4px 10px rgba(0,0,0,0.05); +} + +.section { + font-weight: 600; + margin-top: 1rem; + margin-bottom: 0.5rem; +} + +.filter label { + display: block; + margin: 0.5rem 0; + font-size: 0.9rem; +} + +.reset { + margin-top: 1.5rem; + background: #007d8a; color: white; - text-align: center; - padding-left: 0.5rem; - padding-right: 0.5rem; - padding-top: 0.25rem; - padding-bottom: 0.25rem; + border: none; + padding: 0.6rem; border-radius: 6px; + cursor: pointer; + transition: 0.3s; +} + +.reset:hover { + background: #005f68; +} + +/* ========== Jobs Section ========== */ +.jobs { + flex-grow: 1; +} + +.jobList { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 1.5rem; +} + +/* ========== Job Card ========== */ +.job { + background: white; + padding: 1.5rem; + border-radius: 12px; + box-shadow: 0 4px 15px rgba(0,0,0,0.05); + transition: 0.3s ease; + position: relative; +} + +.job:hover { + transform: translateY(-5px); + box-shadow: 0 8px 20px rgba(0,0,0,0.1); +} + +.job .title { + font-size: 1.3rem; + font-weight: 600; + color: #007d8a; + margin-bottom: 0.5rem; +} + +.job .meta { + font-family: monospace; + font-size: 0.9rem; + color: #e1523d; +} + +.job .description { + margin-top: 1rem; + font-size: 0.95rem; + line-height: 1.5; +} + +.job .permalink { + margin-top: 1rem; + font-size: 0.85rem; + text-align: right; +} + + +.expired-banner { + background: #ffe5e8; + color: #c0392b; + padding: 0.4rem; + border-radius: 6px; + text-align: center; + font-size: 0.85rem; + margin-bottom: 0.75rem; +} + + +.badges { position: absolute; - z-index: 1; + top: 1rem; + right: 1rem; } -.badge:hover .tooltip { - visibility: visible; + +.badge { + background: #007d8a; + color: white; + padding: 0.3rem 0.6rem; + border-radius: 20px; + font-size: 0.75rem; + margin-left: 0.3rem; } + + .noJobs { - padding: 2rem; - font-size: 200%; + text-align: center; + padding: 3rem; + font-size: 1.5rem; color: gray; } + + +@media (max-width: 900px) { + .jobsLayout { + flex-direction: column; + } + + .leftColumn { + width: 100%; + } +} +} \ No newline at end of file