diff --git a/api/index.js b/api/index.js index d84d94a..fdf3bc2 100644 --- a/api/index.js +++ b/api/index.js @@ -7,8 +7,17 @@ const getAsync = promisify(client.get).bind(client); const app = express(); const port = 5000; +app.use(function(req, res, next) { + res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); // update to match the domain you will make the request from + res.header( + 'Access-Control-Allow-Headers', + 'Origin, X-Requested-With, Content-Type, Accept' + ); + next(); +}); + app.get('/', (req, res) => res.send('Jobs API!')); -app.get('/jobs', async (req, res) => { +app.get('/api/jobs', async (req, res) => { const jobs = await getAsync('github'); return res.send(jobs); }); diff --git a/client/package.json b/client/package.json index c93adbf..8e7f12c 100644 --- a/client/package.json +++ b/client/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@material-ui/core": "4.8.0", + "@material-ui/icons": "4.5.1", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", @@ -20,6 +21,7 @@ "eslintConfig": { "extends": "react-app" }, + "proxy": "http://localhost:5000", "browserslist": { "production": [ ">0.2%", @@ -32,4 +34,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css index 6cfd7e5..2be5e9f 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -1,46 +1,30 @@ -.App { - text-align: center; +body { + margin: 20 40px; } -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } +.job { + width: 80%; + display: flex; + justify-content: space-between; + margin: 20px 0; + padding: 20px 10px; } -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; +.job:hover { + cursor: pointer; + color: #666666; } -.App-link { - color: #61dafb; +.detail-logo { + height: 30px; + float: right; } -.job { - width: 80%; +.flex-align-mid { display: flex; - justify-content: space-around; - margin: 10px 0; - padding: 10px 0; + align-items: center; } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +.job-title-location { + margin-left: 20px; } diff --git a/client/src/App.js b/client/src/App.js index 41372e1..4f30d81 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -3,16 +3,25 @@ import './App.css'; import Jobs from './Jobs'; -const mockJobs = [ - {title: 'SME 1', company: 'Facebook'}, - {title: 'SME 1', company: 'Google'}, - {title: 'SME 1', company: 'Apple'}, -]; +const JOB_API_URL = '/api/jobs'; + +const fetchJobs = async updateCb => { + const res = await fetch(JOB_API_URL); + const json = await res.json(); + + updateCb(json); +}; function App() { + const [jobList, updateJobs] = React.useState([]); + + React.useEffect(() => { + fetchJobs(updateJobs); + }, []); + return (
- +
); } diff --git a/client/src/Job.js b/client/src/Job.js index a63fdb3..2a68cb0 100644 --- a/client/src/Job.js +++ b/client/src/Job.js @@ -1,10 +1,47 @@ import React from 'react'; +import Paper from '@material-ui/core/Paper'; +import {Typography} from '@material-ui/core'; -export default function Job({job}) { +const ONE_DAY_MS = 24 * 3600 * 1000; + +// returns a date like Fri Jun 14 +function getMDY(ts) { + return ts + .toDateString() + .split(' ') + .slice(0, 3) + .join(' '); +} + +// makeDate takes a TS and returns a date like Fri Jun 14 +// if it's today or yesterday, it returns that instead +function makeDate(timestamp) { + const date = new Date(timestamp); + const dateStr = getMDY(date); + const todayStr = getMDY(new Date()); + const yesterdayStr = getMDY(new Date(Date.now() - ONE_DAY_MS)); + if (dateStr === todayStr) { + return 'today'; + } else if (dateStr === yesterdayStr) { + return 'yesterday'; + } else { + return dateStr; + } +} + +export default function Job({job, onClick}) { return ( -
- {job.title} - {job.company} -
+ +
+
+ {job.title} + {job.company} + {job.location} +
+
+
+ {makeDate(job.created_at)} +
+
); } diff --git a/client/src/JobModal.js b/client/src/JobModal.js new file mode 100644 index 0000000..abba91d --- /dev/null +++ b/client/src/JobModal.js @@ -0,0 +1,49 @@ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Slide from '@material-ui/core/Slide'; + +const Transition = React.forwardRef(function Transition(props, ref) { + return ; +}); + +export default function JobModal({job, open, handleClose}) { + if (!job.title) { + return
; + } + return ( +
+ + + {job.title} - {job.company} + + + + + + + + + + + + +
+ ); +} diff --git a/client/src/Jobs.js b/client/src/Jobs.js index b08e6df..8afdea9 100644 --- a/client/src/Jobs.js +++ b/client/src/Jobs.js @@ -1,15 +1,95 @@ import React from 'react'; import {Typography} from '@material-ui/core'; +import MobileStepper from '@material-ui/core/MobileStepper'; +import Button from '@material-ui/core/Button'; +import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; +import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; import Job from './Job'; +import JobModal from './JobModal'; export default function Jobs({jobs}) { + // modal + const [open, setOpen] = React.useState(false); + const [selectedJob, selectJob] = React.useState([]); + const handleClickOpen = () => { + setOpen(true); + }; + const handleClose = () => { + setOpen(false); + }; + + // pagination + const numJobs = jobs.length; + const numPages = Math.ceil(numJobs / 50); + const [activeStep, setActiveStep] = React.useState(0); + const jobsOnPage = jobs.slice(activeStep * 50, activeStep * 50 + 50); + + // step == 0, show 0-49 + // step == 1, show 50-99 + + function scrollToTop() { + const c = document.documentElement.scrollTop || document.body.scrollTop; + if (c > 0) { + window.requestAnimationFrame(scrollToTop); + window.scrollTo(0, c - c / 8); + } + } + + const handleNext = () => { + setActiveStep(prevActiveStep => prevActiveStep + 1); + scrollToTop(); + }; + + const handleBack = () => { + setActiveStep(prevActiveStep => prevActiveStep - 1); + scrollToTop(); + }; + return (
- Entry Level Software Jobs - {jobs.map(job => ( - + + + Entry Level Software Jobs + + + Found {numJobs} Jobs + + {jobsOnPage.map((job, i) => ( + { + handleClickOpen(); + selectJob(job); + }} + /> ))} +
+ Page {activeStep + 1} of {numPages} +
+ + Next + + + } + backButton={ + + } + />
); } diff --git a/client/yarn.lock b/client/yarn.lock index 8a6827b..da80d82 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1091,6 +1091,13 @@ react-is "^16.8.0" react-transition-group "^4.3.0" +"@material-ui/icons@4.5.1": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.5.1.tgz#6963bad139e938702ece85ca43067688018f04f8" + integrity sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/styles@^4.7.1": version "4.7.1" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.7.1.tgz#48fa70f06441c35e301a9c4b6c825526a97b7a29" @@ -9204,7 +9211,7 @@ serialize-javascript@^1.7.0: resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A== -serialize-javascript@^2.1.1: +serialize-javascript@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== diff --git a/worker/tasks/fetch-github.js b/worker/tasks/fetch-github.js index 191eeda..39a4b2e 100644 --- a/worker/tasks/fetch-github.js +++ b/worker/tasks/fetch-github.js @@ -43,6 +43,4 @@ const fetchGithub = async () => { console.log(successs); }; -fetchGithub(); - module.exports = fetchGithub;