{"id":21288457,"url":"https://github.com/jannickpepe/mern-waves","last_synced_at":"2026-04-06T01:35:15.341Z","repository":{"id":254325467,"uuid":"846116693","full_name":"JannickPepe/MERN-Waves","owner":"JannickPepe","description":"A fullstack MERN project with JWT, ApiLimiter, cookies, actions, reducers and apiContext","archived":false,"fork":false,"pushed_at":"2024-08-26T12:39:43.000Z","size":1840,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-03T14:38:55.198Z","etag":null,"topics":["cookies","express","jwt","mongodb","nodejs","reactjs","tailwindcss"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JannickPepe.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-22T15:04:51.000Z","updated_at":"2024-08-26T12:39:46.000Z","dependencies_parsed_at":"2024-08-26T13:58:15.682Z","dependency_job_id":null,"html_url":"https://github.com/JannickPepe/MERN-Waves","commit_stats":null,"previous_names":["jannickpepe/mern-waves"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/JannickPepe/MERN-Waves","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JannickPepe%2FMERN-Waves","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JannickPepe%2FMERN-Waves/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JannickPepe%2FMERN-Waves/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JannickPepe%2FMERN-Waves/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JannickPepe","download_url":"https://codeload.github.com/JannickPepe/MERN-Waves/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JannickPepe%2FMERN-Waves/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31456662,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"ssl_error","status_checked_at":"2026-04-05T21:22:51.943Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cookies","express","jwt","mongodb","nodejs","reactjs","tailwindcss"],"created_at":"2024-11-21T12:21:18.904Z","updated_at":"2026-04-06T01:35:15.325Z","avatar_url":"https://github.com/JannickPepe.png","language":"JavaScript","funding_links":["https://www.buymeacoffee.com/johnsmilga"],"categories":[],"sub_categories":[],"readme":"\n# Jobify\n\n#### Track Your Job Search\n\nProject in Action - [Jobify](https://www.jobify.live/)\n\n#### Support\n\nFind the App Useful? [You can always buy me a coffee](https://www.buymeacoffee.com/johnsmilga)\n\n#### Run The App Locally\n\n```sh\nnpm run install-dependencies\n```\n\n- rename .env.temp to .env\n- setup values for - MONGO_URL, JWT_SECRET, JWT_LIFETIME\n\n```sh\nnpm start\n```\n\n- visit url http://localhost:3000/\n\n#### Setup React App\n\n- create \u003cb\u003eclient\u003c/b\u003e folder\n- open terminal\n\n```sh\ncd client\n```\n\n```sh\nnpx create-react-app .\n```\n\n```sh\nnpm start\n```\n\n- set editor/browser side by side\n- copy/paste assets from complete project\n\n#### Spring Cleaning\n\n- in src remove\n- App.css\n- App.test.js\n- logo.svg\n- reportWebVitals.js\n- setupTests.js\n- fix App.js and index.js\n\n#### Title and Favicon\n\n- change title in public/index.html\n- replace favicon.ico in public\n- resource [Generate Favicons](https://favicon.io/)\n\n#### Normalize.css and Global Styles\n\n- CSS in JS (styled-components)\n- saves times on the setup\n- less lines of css\n- speeds up the development\n- normalize.css\n- small CSS file that provides cross-browser consistency in the default styling of HTML elements.\n- [normalize docs](https://necolas.github.io/normalize.css/)\n\n```sh\nnpm install normalize.css\n```\n\n- import 'normalize.css' in index.js\n- SET BEFORE 'index.css'\n- replace contents of index.css\n- if any questions about normalize or specific styles\n- Coding Addict - [Default Starter Video](https://youtu.be/UDdyGNlQK5w)\n- Repo - [Default Starter Repo](https://github.com/john-smilga/default-starter)\n\n#### Landing Page\n\n- zoom level 175%\n- markdown preview extension\n- get something on the screen\n- react router and styled components right after\n- create pages directory in the source\n- for now Landing.js\n- create component (snippets extension)\n- setup basic return\n\n```js\n\u003ch4\u003eLanding Page\u003ch4\u003e\n```\n\n- import logo.svg and main.svg\n- import Landing in App.js and render\n\n#### Styled Components\n\n- CSS in JS\n- Styled Components\n- have logic and styles in component\n- no name collisions\n- apply javascript logic\n- [Styled Components Docs](https://styled-components.com/)\n- [Styled Components Course](https://www.udemy.com/course/styled-components-tutorial-and-project-course/?referralCode=9DABB172FCB2625B663F)\n\n```sh\nnpm install styled-components\n```\n\n```js\nimport styled from 'styled-components';\n\nconst El = styled.el`\n  // styles go here\n`;\n```\n\n- no name collisions, since unique class\n- vscode-styled-components extension\n- colors and bugs\n- style entire react component\n\n```js\nconst Wrapper = styled.el``;\n\nconst Component = () =\u003e {\n  return (\n    \u003cWrapper\u003e\n      \u003ch1\u003e Component\u003c/h1\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n```\n\n- only responsible for styling\n- wrappers folder in assets\n\n#### Logo and Images\n\n- logo built in Figma\n- [Cool Images](https://undraw.co/)\n\n#### Logo\n\n- create \u003cb\u003ecomponents\u003c/b\u003e folder in source\n- create Logo.js\n- move import and image logic\n- export as default\n- utilize index.js\n\n#### React Router\n\n- Version 6\n- [React Router Docs](https://reactrouter.com/docs/en/v6)\n\n```sh\nnpm install history@5 react-router-dom@6\n```\n\n- import four components\n\n```js\nimport { BrowserRouter, Routes, Route, Link } from 'react-router-dom';\n```\n\n- Connect to browser's URL with BrowserRouter\n- Routes instead of Switch\n\n```js\n\n\u003cBrowserRouter\u003e\n    \u003cRoutes\u003e\n      \u003cRoute path=\"/\" element={\u003cdiv\u003eDashboard\u003c/div\u003e} /\u003e\n      \u003cRoute path=\"/register\" element={\u003cdiv\u003eRegister\u003c/div\u003e} /\u003e\n      \u003cRoute path=\"/landing\" element={\u003cLanding /\u003e} /\u003e\n      \u003cRoute path=\"*\" element={\u003cdiv\u003eError\u003c/div\u003e}\u003e\n    \u003c/Routes\u003e\n\u003c/BrowserRouter\u003e\n\n```\n\n```js\n\u003cnav\u003e\n  \u003cLink to='/'\u003eDashboard\u003c/Link\u003e\n  \u003cLink to='/register'\u003eRegister\u003c/Link\u003e\n  \u003cLink to='/landing'\u003eHome\u003c/Link\u003e\n\u003c/nav\u003e\n```\n\n- go to Landing.js\n\n```js\nimport { Link } from 'react-router-dom';\n\nreturn (\n  \u003cLink to='/register' className='btn btn-hero'\u003e\n    Login / Register\n  \u003c/Link\u003e\n);\n```\n\n#### Setup Pages\n\n- create Error, Register, Dashboard pages\n- basic return\n- create index.js\n- import all the pages\n- export one by one\n- basically the same, as in components\n- import App.js\n- add to element={}\n- remove temp navbar\n\n#### Error Page\n\n```js\nimport { Link } from 'react-router-dom';\nimport img from '../assets/images/not-found.svg';\nimport Wrapper from '../assets/wrappers/ErrorPage';\n\nreturn (\n  \u003cWrapper className='full-page'\u003e\n    \u003cdiv\u003e\n      \u003cimg src={img} alt='not found' /\u003e\n      \u003ch3\u003etext\u003c/h3\u003e\n      \u003cp\u003etext\u003c/p\u003e\n      \u003cLink to='/'\u003eback home\u003c/Link\u003e\n    \u003c/div\u003e\n  \u003c/Wrapper\u003e\n);\n```\n\n#### Auto Imports\n\n- use while developing\n- only sparingly while recording\n- better picture\n- messes with flow\n- just my preference\n- still use them, just not all the time\n\n#### Register Page - Setup\n\n- show preview in Browser and themes\n\n```js\nimport { useState, useEffect } from 'react';\nimport { Logo } from '../components';\nimport Wrapper from '../assets/wrappers/RegisterPage';\n// global context and useNavigate later\n\nconst initialState = {\n  name: '',\n  email: '',\n  password: '',\n  isMember: true,\n};\n// if possible prefer local state\n// global state\n\nfunction Register() {\n  const [values, setValues] = useState(initialState);\n\n  // global context and useNavigate later\n\n  const handleChange = (e) =\u003e {\n    console.log(e.target);\n  };\n\n  const onSubmit = (e) =\u003e {\n    e.preventDefault();\n    console.log(e.target);\n  };\n  return (\n    \u003cWrapper className='full-page'\u003e\n      \u003cform className='form' onSubmit={onSubmit}\u003e\n        \u003cLogo /\u003e\n        \u003ch3\u003eLogin\u003c/h3\u003e\n\n        {/* name field */}\n        \u003cdiv className='form-row'\u003e\n          \u003clabel htmlFor='name' className='form-label'\u003e\n            name\n          \u003c/label\u003e\n\n          \u003cinput\n            type='text'\n            value={values.name}\n            name='name'\n            onChange={handleChange}\n            className='form-input'\n          /\u003e\n        \u003c/div\u003e\n\n        \u003cbutton type='submit' className='btn btn-block'\u003e\n          submit\n        \u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n}\n```\n\n#### FormRow Component\n\n- create FormRow.js in \u003cb\u003ecomponents\u003c/b\u003e\n- setup import/export\n- setup one for email and password\n- hint \"type,name,value\"\n\n```js\nconst FormRow = ({ type, name, value, handleChange, labelText }) =\u003e {\n  return (\n    \u003cdiv className='form-row'\u003e\n      \u003clabel htmlFor={name} className='form-label'\u003e\n        {labelText || name}\n      \u003c/label\u003e\n\n      \u003cinput\n        type={type}\n        value={value}\n        name={name}\n        onChange={handleChange}\n        className='form-input'\n      /\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default FormRow;\n```\n\n#### Alert Component\n\n- right away setup as component\n- create Alert.js in \u003cb\u003ecomponents\u003c/b\u003e\n\n```js\nconst Alert = () =\u003e {\n  return \u003cdiv className='alert alert-danger'\u003ealert goes here\u003c/div\u003e;\n};\n\nexport default Alert;\n```\n\n- setup import/export\n- alert-danger or alert-success\n- eventually setup in global context\n- showAlert in initialState (true || false)\n- right after h3 login\n\n```js\nvalues.showAlert \u0026\u0026 \u003cAlert /\u003e;\n```\n\n#### Toggle Member\n\n```js\nconst toggleMember = () =\u003e {\n  setValues({ ...values, isMember: !values.isMember });\n};\n\nreturn (\n  \u003cWrapper\u003e\n    {/* control h3 */}\n\n    \u003ch3\u003e{values.isMember ? 'Login' : 'Register'}\u003c/h3\u003e\n\n    {/* toggle name */}\n\n    {!values.isMember \u0026\u0026 (\n      \u003cFormRow\n        type='text'\n        name='name'\n        value={values.name}\n        handleChange={handleChange}\n      /\u003e\n    )}\n\n    {/* right after submit btn */}\n    {/* toggle button */}\n\n    \u003cp\u003e\n      {values.isMember ? 'Not a member yet?' : 'Already a member?'}\n\n      \u003cbutton type='button' onClick={toggleMember} className='member-btn'\u003e\n        {values.isMember ? 'Register' : 'Login'}\n      \u003c/button\u003e\n    \u003c/p\u003e\n  \u003c/Wrapper\u003e\n);\n```\n\n#### Global Context\n\n- in src create \u003cb\u003econtext\u003c/b\u003e directory\n- actions.js\n- reducer.js\n- appContext.js\n\n```js\nimport React, { useState, useReducer, useContext } from 'react';\n\nexport const initialState = {\n  isLoading: false,\n  showAlert: false,\n  alertText: '',\n  alertType: '',\n};\nconst AppContext = React.createContext();\nconst AppProvider = ({ children }) =\u003e {\n  const [state, setState] = useState(initialState);\n\n  return (\n    \u003cAppContext.Provider\n      value={{\n        ...state,\n      }}\n    \u003e\n      {children}\n    \u003c/AppContext.Provider\u003e\n  );\n};\n// make sure use\nexport const useAppContext = () =\u003e {\n  return useContext(AppContext);\n};\n\nexport { AppProvider };\n```\n\n- index.js\n\n```js\nimport { AppProvider } from './context/appContext';\n\nReactDOM.render(\n  \u003cReact.StrictMode\u003e\n    \u003cAppProvider\u003e\n      \u003cApp /\u003e\n    \u003c/AppProvider\u003e\n  \u003c/React.StrictMode\u003e,\n  document.getElementById('root')\n);\n```\n\n- Register.js\n\n```js\nimport { useAppContext } from '../context/appContext';\n\nconst { isLoading, showAlert } = useAppContext();\n```\n\n- switch to global showAlert\n\n#### useReducer\n\n- [React Tutorial](https://youtu.be/iZhV0bILFb0)\n- useReducer vs Redux\n- multiple reducers vs one\n\n#### Wire Up Reducer\n\n```js\nreducer.js;\n\nconst reducer = (state, action) =\u003e {\n  throw new Error(`no such action :${action.type}`);\n};\nexport default reducer;\n```\n\n```js\nappContext.js;\n\nimport reducer from './reducer';\n\nconst [state, dispatch] = useReducer(reducer, initialState);\n```\n\n#### Display Alert\n\n```js\nactions.js;\n\nexport const DISPLAY_ALERT = 'SHOW_ALERT';\n```\n\n- setup imports (reducer and appContext)\n\n```js\nappContext.js\n\nconst displayAlert() =\u003e{\n  dispatch({type:DISPLAY_ALERT})\n}\n\n```\n\n```js\nreducer.js;\n\nif (action.type === DISPLAY_ALERT) {\n  return {\n    ...state,\n    showAlert: true,\n    alertType: 'danger',\n    alertText: 'Please provide all values!',\n  };\n}\n```\n\n```js\nAlert.js in Components;\n\nimport { useAppContext } from '../context/appContext';\n\nconst Alert = () =\u003e {\n  const { alertType, alertText } = useAppContext();\n  return \u003cdiv className={`alert alert-${alertType}`}\u003e{alertText}\u003c/div\u003e;\n};\n```\n\n#### Display Alert\n\n- [JS Nuggets - Dynamic Object Keys](https://youtu.be/_qxCYtWm0tw)\n\n```js\nappContext.js;\n\nconst handleChange = (e) =\u003e {\n  setValues({ ...values, [e.target.name]: e.target.value });\n};\n```\n\n- get displayAlert function\n\n```js\nappContext.js;\n\nconst onSubmit = (e) =\u003e {\n  e.preventDefault();\n  const { name, email, password, isMember } = values;\n  if (!email || !password || (!isMember \u0026\u0026 !name)) {\n    displayAlert();\n    return;\n  }\n  console.log(values);\n};\n```\n\n#### Clear Alert\n\n- technically optional\n\n```js\nactions.js;\n\nexport const CLEAR_ALERT = 'CLEAR_ALERT';\n```\n\n- setup imports (reducer and appContext)\n\n```js\nreducer.js;\n\nif (action.type === CLEAR_ALERT) {\n  return {\n    ...state,\n    showAlert: false,\n    alertType: '',\n    alertText: '',\n  };\n}\n```\n\n```js\nappContext.js;\n\nconst displayAlert = () =\u003e {\n  dispatch({\n    type: DISPLAY_ALERT,\n  });\n  clearAlert();\n};\n\nconst clearAlert = () =\u003e {\n  setTimeout(() =\u003e {\n    dispatch({\n      type: CLEAR_ALERT,\n    });\n  }, 3000);\n};\n```\n\n#### Setup Server\n\n- stop the dev server in client\n- cd ..\n- start setting up our server\n- setup package.json\n\n```sh\nnpm init -y\n```\n\n- create server.js\n- console.log('server running...')\n\n```sh\nnode server\n```\n\n#### ES6 vs CommonJS\n\n```js\nCommonJS;\n\nconst express = require('express');\nconst app = express();\n```\n\n```js\nES6;\n\nimport express from 'express';\nconst app = express();\n```\n\n- file extension .mjs\n\n```js\npackage.json\n\n\"type\":\"module\"\n```\n\n#### Nodemon and Basic Express Server\n\n```sh\nnpm install nodemon --save-dev\n```\n\n```js\npackage.json\n\n\"start\":\"nodemon server\"\n\n```\n\n```sh\nnpm install express\n```\n\n```js\nimport express from 'express';\nconst app = express();\n\napp.get('/', (req, res) =\u003e {\n  res.send('Welcome!');\n});\n\nconst port = process.env.PORT || 5000;\n\napp.listen(port, () =\u003e console.log(`Server is listening on port ${port}...`));\n```\n\n#### Not Found Middleware\n\n- in the root create \u003cb\u003emiddleware\u003c/b\u003e folder\n- not-found.js\n- setup function\n- return 404 with message 'Route does not exist'\n- import in server.js\n- make sure to use .js extension\n- place after home route\n\n#### Error Middleware\n\n- in the middleware create error-handler.js\n- setup function\n- accept 4 parameters, first one error\n- log error\n- return 500\n- json({msg:'there was an error'})\n- import in the server.js\n- make sure to use .js extension\n- place it last\n- eventually handle Mongoose Errors, just like in the node-express\n- showcase with async errors\n\n#### ENV Variables\n\n```sh\nnpm install dotenv\n```\n\n- import dotenv from 'dotenv'\n- dotenv.config()\n\n- create .env\n- PORT=4000\n- .gitignore\n- /node_modules\n- .env\n\n#### Connect to MongoDB\n\n- switched back to PORT=5000\n- remove Error from '/'\n\n- existing MongoDB Atlas Account\n\n```sh\nnpm install mongoose\n```\n\n- create \u003cb\u003edb\u003c/b\u003e folder\n- create connect.js\n- setup connectDB(url)\n- in server.js create start() function\n- get connection string\n- setup as MONGO_URL in .env\n- provide credentials and DB Name\n\n#### Auth Controller and Route Structure\n\n- create \u003cb\u003econtrollers\u003c/b\u003e\n- authController.js\n- create async functions\n\n```js\nexport { register, login, updateUser };\n```\n\n- return res.send('function name')\n- create \u003cb\u003eroutes\u003c/b\u003e folder\n- authRoutes.js\n- setup express router\n- import functions from authController.js\n\n```js\nrouter.route('/register').post(register);\nrouter.route('/login').post(login);\nrouter.route('/updateUser').patch(updateUser);\n\nexport default router;\n```\n\n- import authRouter in server.js\n\n```js\napp.use('/api/v1/auth', authRouter);\n```\n\n#### Jobs Controller and Route Structure\n\n- jobsController.js\n- create async functions\n\n```js\nexport { createJob, deleteJob, getAllJobs, updateJob, showStats };\n```\n\n- return res.send('function name')\n\n- jobsRoutes.js\n- setup express router\n- import functions from jobsController.js\n\n```js\nrouter.route('/').post(createJob).get(getAllJobs);\n// place before :id\nrouter.route('/stats').get(showStats);\nrouter.route('/:id').delete(deleteJob).patch(updateJob);\n\nexport default router;\n```\n\n- in server.js jobsRouter\n\n```js\napp.use('/api/v1/jobs', jobsRouter);\n```\n\n#### Postman\n\n- URL global var\n- JOBIFY Collection\n- auth and jobs folders\n- setup routes\n\n#### User Model\n\n- \u003cb\u003emodels\u003c/b\u003e folder\n- User.js\n- setup schema\n- name, email, password, lastName, location\n- all {type:String}\n\n#### Validate Email\n\n```js\nvalidate:{\n  validator:(field)=\u003e {return 2 \u003e 1},\n  message:'Please provide valid email'\n  }\n```\n\n- [Validator Package](https://www.npmjs.com/package/validator)\n\n```sh\nnpm install validator\n```\n\n- import in User.js\n- validator.isEmail\n\n#### Register User - Initial Setup\n\n- authController\n- import User model\n- setup temporary try/catch\n- await User.create(req.body)\n- if success 201 with json({user}) (temp)\n- if error 500 with json({msg:'there was an error'})\n\n#### Pass Error to Error Handler\n\n- next(error)\n\n#### Express-Async-Errors Package\n\n- remove try/catch\n- [Express-Async-Errors](https://www.npmjs.com/package/express-async-errors)\n\n```sh\nnpm install express-async-errors\n\n```\n\n- in server.js\n- import 'express-async-errors'\n\n- use throw Error('error') instead of next(error)\n\n#### Http Status Codes\n\n- constants for status codes\n- personal preference\n- provides consistency\n- less bugs\n- easier to read/manage\n\n- [Http Status Codes](https://www.npmjs.com/package/http-status-codes)\n\n```sh\nnpm install http-status-codes\n```\n\n- import/setup in authController and error-handler\n- setup defaultError\n\n#### Custom Errors\n\n#### Refactor Errors\n\n- create errors folder\n- create custom-api, bad-request, not-found, index.js files\n- add proper imports\n- setup index.js just like in the front-end\n- import {BadRequestError} in authController\n- gotcha \"errors/index.js\"\n\n#### Hash Passwords\n\n- one way street, only compare hashed values\n- [bcrypt.js](https://www.npmjs.com/package/bcryptjs)\n\n```sh\nnpm install bcryptjs\n```\n\n- User Model\n- import bcrypt from 'bcryptjs'\n- await genSalt(10)\n- await hash(password , salt)\n- await compare(requestPassword , currentPassword)\n- [mongoose middleware](https://mongoosejs.com/docs/middleware.html)\n- UserSchema.pre('save',async function(){\n  \"this\" points to instance created by UserSchema\n  })\n\n#### Mongoose - Custom Instance Methods\n\n[Custom Instance Methods](https://mongoosejs.com/docs/guide.html#methods)\n\n- UserSchema.methods.createJWT = function(){console.log(this)}\n- register controller\n- right after User.create()\n- invoke user.createJWT()\n\n#### JWT\n\n- token\n- [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken)\n\n```sh\nnpm install jsonwebtoken\n```\n\n- User Model\n- import jwt from 'jsonwebtoken'\n- jwt.sign(payload,secret,options)\n- createJWT\n\n```js\nreturn jwt.sign({ userId: this._id }, 'jwtSecret', { expiresIn: '1d' });\n```\n\n```js\nreturn jwt.sign({ userId: this._id }, process.env.JWT_SECRET, {\n  expiresIn: process.env.JWT_LIFETIME,\n});\n```\n\n#### JWT_SECRET and JWT_LIFETIME\n\n- [Keys Generator](https://www.allkeysgenerator.com/)\n- RESTART SERVER!!!!\n\n#### Complete Register\n\n- password : {select:false}\n- complete response\n\n#### Concurrently\n\n- front-end and backend (server)\n- run separate terminals\n- [concurrently](https://www.npmjs.com/package/concurrently)\n\n```sh\nnpm install concurrently --save-dev\n\n```\n\n- package.json\n\n```js\n// --kill-others switch, all commands are killed if one dies\n// --prefix client - folder\n// cd client \u0026\u0026 npm start\n// escape quotes\n\n\"scripts\": {\n    \"server\": \"nodemon server --ignore client\",\n    \"client\": \"npm start --prefix client\",\n    \"start\": \"concurrently --kill-others-on-fail \\\"npm run server\\\" \\\" npm run client\\\"\"\n  },\n```\n\n#### Cors Error\n\n[Cors Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)\n\n- two fixes (cors package and proxy)\n\n#### Cors Package\n\n[cors package](https://www.npmjs.com/package/cors)\n\n```sh\nnpm install cors\n```\n\n```js\nimport cors from 'cors';\n\napp.use(cors());\n```\n\n#### Proxy\n\n- access from anywhere\n- don't want to use full url\n\n[cra proxy](https://create-react-app.dev/docs/proxying-api-requests-in-development/)\n\n```js\n\"proxy\":\"http://localhost:5000\"\n```\n\n- my preference to remove trailing slash /\n- restart app\n\n#### Register User - Setup\n\n```js\nappContext.js;\n\nconst initialState = {\n  user: null,\n  token: null,\n  userLocation: '',\n};\n```\n\n- actions.js REGISTER_USER_BEGIN,SUCCESS,ERROR\n- import reducer,appContext\n\n```js\nappContext.js;\nconst registerUser = async (currentUser) =\u003e {\n  console.log(currentUser);\n};\n```\n\n- import in Register.js\n\n```js\nRegister.js;\n\nconst currentUser = { name, email, password };\nif (isMember) {\n  console.log('already a member');\n} else {\n  registerUser(currentUser);\n}\n\nreturn (\n  \u003cbutton type='submit' className='btn btn-block' disabled={isLoading}\u003e\n    submit\n  \u003c/button\u003e\n);\n```\n\n#### Axios\n\n- [axios docs](https://axios-http.com/docs/intro)\n- stop app\n- cd client\n\n```sh\nnpm install axios\n```\n\n- cd ..\n- restart app\n\n#### Register User - Complete\n\n```js\nappContext.js;\n\nimport axios from 'axios';\n\nconst registerUser = async (currentUser) =\u003e {\n  dispatch({ type: REGISTER_USER_BEGIN });\n  try {\n    const response = await axios.post('/api/v1/auth/register', currentUser);\n    console.log(response);\n    const { user, token, location } = response.data;\n    dispatch({\n      type: REGISTER_USER_SUCCESS,\n      payload: {\n        user,\n        token,\n        location,\n      },\n    });\n\n    // will add later\n    // addUserToLocalStorage({\n    //   user,\n    //   token,\n    //   location,\n    // })\n  } catch (error) {\n    console.log(error.response);\n    dispatch({\n      type: REGISTER_USER_ERROR,\n      payload: { msg: error.response.data.msg },\n    });\n  }\n  clearAlert();\n};\n```\n\n```js\nreducer.js;\nif (action.type === REGISTER_USER_BEGIN) {\n  return { ...state, isLoading: true };\n}\nif (action.type === REGISTER_USER_SUCCESS) {\n  return {\n    ...state,\n    user: action.payload.user,\n    token: action.payload.token,\n    userLocation: action.payload.location,\n    jobLocation: action.payload.location,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'success',\n    alertText: 'User Created! Redirecting...',\n  };\n}\nif (action.type === REGISTER_USER_ERROR) {\n  return {\n    ...state,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'danger',\n    alertText: action.payload.msg,\n  };\n}\n```\n\n#### Navigate To Dashboard\n\n```js\nRegister.js;\nimport { useEffect } from 'react';\nimport { useNavigate } from 'react-router-dom';\n\nconst Register = () =\u003e {\n  const { user } = useAppContext();\n  const navigate = useNavigate();\n\n  useEffect(() =\u003e {\n    if (user) {\n      setTimeout(() =\u003e {\n        navigate('/');\n      }, 3000);\n    }\n  }, [user, navigate]);\n};\n```\n\n#### Local Storage\n\n```js\nappContext.js;\nconst addUserToLocalStorage = ({ user, token, location }) =\u003e {\n  localStorage.setItem('user', JSON.stringify(user));\n  localStorage.setItem('token', token);\n  localStorage.setItem('location', location);\n};\n\nconst removeUserFromLocalStorage = () =\u003e {\n  localStorage.removeItem('token');\n  localStorage.removeItem('user');\n  localStorage.removeItem('location');\n};\n\nconst registerUser = async (currentUser) =\u003e {\n  // in try block\n  addUserToLocalStorage({\n    user,\n    token,\n    location,\n  });\n};\n\n// set as default\nconst token = localStorage.getItem('token');\nconst user = localStorage.getItem('user');\nconst userLocation = localStorage.getItem('location');\n\nconst initialState = {\n  user: user ? JSON.parse(user) : null,\n  token: token,\n  userLocation: userLocation || '',\n  jobLocation: userLocation || '',\n};\n```\n\n#### Morgan Package\n\n- http logger middleware for node.js\n- [morgan docs](https://www.npmjs.com/package/morgan)\n\n```sh\nnpm install morgan\n```\n\n```js\nimport morgan from 'morgan';\n\nif (process.env.NODE_ENV !== 'production') {\n  app.use(morgan('dev'));\n}\n```\n\n#### UnauthenticatedError\n\n- unauthenticated.js in errors\n- import/export\n\n```js\nimport { StatusCodes } from 'http-status-codes';\nimport CustomAPIError from './custom-api.js';\n\nclass UnauthenticatedError extends CustomAPIError {\n  constructor(message) {\n    super(message);\n    this.statusCode = StatusCodes.UNAUTHORIZED;\n  }\n}\n```\n\n#### Compare Password\n\n```js\nUser.js in models;\n\nUserSchema.methods.comparePassword = async function (candidatePassword) {\n  const isMatch = await bcrypt.compare(candidatePassword, this.password);\n  return isMatch;\n};\n```\n\n```js\nauthController.js;\nconst login = async (req, res) =\u003e {\n  const { email, password } = req.body;\n  if (!email || !password) {\n    throw new BadRequestError('Please provide all values');\n  }\n  const user = await User.findOne({ email }).select('+password');\n\n  if (!user) {\n    throw new UnauthenticatedError('Invalid Credentials');\n  }\n  const isPasswordCorrect = await user.comparePassword(password);\n  if (!isPasswordCorrect) {\n    throw new UnauthenticatedError('Invalid Credentials');\n  }\n  const token = user.createJWT();\n  user.password = undefined;\n  res.status(StatusCodes.OK).json({ user, token, location: user.location });\n};\n```\n\n- test in Postman\n\n#### Login User - Setup\n\n- actions.js LOGIN_USER_BEGIN,SUCCESS,ERROR\n- import reducer,appContext\n\n```js\nappContext.js;\nconst loginUser = async (currentUser) =\u003e {\n  console.log(currentUser);\n};\n```\n\n- import in Register.js\n\n```js\nRegister.js;\n\nif (isMember) {\n  loginUser(currentUser);\n} else {\n  registerUser(currentUser);\n}\n```\n\n#### Login User - Complete\n\n```js\nappContext.js;\nconst loginUser = async (currentUser) =\u003e {\n  dispatch({ type: LOGIN_USER_BEGIN });\n  try {\n    const { data } = await axios.post('/api/v1/auth/login', currentUser);\n    const { user, token, location } = data;\n\n    dispatch({\n      type: LOGIN_USER_SUCCESS,\n      payload: { user, token, location },\n    });\n\n    addUserToLocalStorage({ user, token, location });\n  } catch (error) {\n    dispatch({\n      type: LOGIN_USER_ERROR,\n      payload: { msg: error.response.data.msg },\n    });\n  }\n  clearAlert();\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === LOGIN_USER_BEGIN) {\n  return {\n    ...state,\n    isLoading: true,\n  };\n}\nif (action.type === LOGIN_USER_SUCCESS) {\n  return {\n    ...state,\n    isLoading: false,\n    user: action.payload.user,\n    token: action.payload.token,\n    userLocation: action.payload.location,\n    jobLocation: action.payload.location,\n    showAlert: true,\n    alertType: 'success',\n    alertText: 'Login Successful! Redirecting...',\n  };\n}\nif (action.type === LOGIN_USER_ERROR) {\n  return {\n    ...state,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'danger',\n    alertText: action.payload.msg,\n  };\n}\n```\n\n#### Refactor\n\n```js\nactions.js;\nexport const SETUP_USER_BEGIN = 'SETUP_USER_BEGIN';\nexport const SETUP_USER_SUCCESS = 'SETUP_USER_SUCCESS';\nexport const SETUP_USER_ERROR = 'SETUP_USER_ERROR';\n```\n\n```js\nappContext.js;\n\nconst setupUser = async ({ currentUser, endPoint, alertText }) =\u003e {\n  dispatch({ type: SETUP_USER_BEGIN });\n  try {\n    const { data } = await axios.post(`/api/v1/auth/${endPoint}`, currentUser);\n\n    const { user, token, location } = data;\n    dispatch({\n      type: SETUP_USER_SUCCESS,\n      payload: { user, token, location, alertText },\n    });\n    addUserToLocalStorage({ user, token, location });\n  } catch (error) {\n    dispatch({\n      type: SETUP_USER_ERROR,\n      payload: { msg: error.response.data.msg },\n    });\n  }\n  clearAlert();\n};\n```\n\n```js\nreducer.js;\nif (action.type === SETUP_USER_BEGIN) {\n  return { ...state, isLoading: true };\n}\nif (action.type === SETUP_USER_SUCCESS) {\n  return {\n    ...state,\n    isLoading: false,\n    token: action.payload.token,\n    user: action.payload.user,\n    userLocation: action.payload.location,\n    jobLocation: action.payload.location,\n    showAlert: true,\n    alertType: 'success',\n    alertText: action.payload.alertText,\n  };\n}\nif (action.type === SETUP_USER_ERROR) {\n  return {\n    ...state,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'danger',\n    alertText: action.payload.msg,\n  };\n}\n```\n\n- import/export\n\n```js\nRegister.js;\n\nconst onSubmit = (e) =\u003e {\n  e.preventDefault();\n  const { name, email, password, isMember } = values;\n  if (!email || !password || (!isMember \u0026\u0026 !name)) {\n    displayAlert();\n    return;\n  }\n  const currentUser = { name, email, password };\n  if (isMember) {\n    setupUser({\n      currentUser,\n      endPoint: 'login',\n      alertText: 'Login Successful! Redirecting...',\n    });\n  } else {\n    setupUser({\n      currentUser,\n      endPoint: 'register',\n      alertText: 'User Created! Redirecting...',\n    });\n  }\n};\n```\n\n#### Nested Pages in React Router 6\n\n#### Dashboard pages\n\n- delete Dashboard.js\n- fix imports/exports\n- replace in home route\n\n```js\n\u003cRoute path='/' element={\u003cdiv\u003edashboard\u003c/div\u003e} /\u003e\n```\n\n- create \u003cb\u003edashboard\u003c/b\u003e directory in pages\n- create AddJob,AllJobs,Profile,Stats,SharedLayout, index.js\n- setup basic returns\n\n```js\nreturn \u003ch1\u003eAdd Job Page\u003c/h1\u003e;\n```\n\n- export all with index.js (just like components)\n- import all pages in App.js\n\n#### Nested Structure\n\n```js\nApp.js\n\n\u003cRoute path='/' \u003e\n  \u003cRoute path=\"stats\" element={\u003cStats /\u003e} /\u003e\n  \u003cRoute path='all-jobs' element={\u003cAllJobs /\u003e}\u003e\u003c/Route\u003e\n  \u003cRoute path='add-job' element={\u003cAddJob /\u003e}\u003e\u003c/Route\u003e\n  \u003cRoute path='profile' element={\u003cProfile /\u003e}\u003e\u003c/Route\u003e\n\u003c/Route\u003e\n```\n\n#### Shared Layout\n\n```js\nApp.js\n\n\u003cRoute path='/' element={\u003cSharedLayout/\u003e} \u003e\n```\n\n```js\nSharedLayout.js;\n\nimport { Outlet, Link } from 'react-router-dom';\nimport Wrapper from '../../assets/wrappers/SharedLayout';\n\nconst SharedLayout = () =\u003e {\n  return (\n    \u003cWrapper\u003e\n      \u003cnav\u003e\n        \u003cLink to='all-jobs'\u003eall jobs\u003c/Link\u003e\n        \u003cLink to='add-job'\u003eall jobs\u003c/Link\u003e\n      \u003c/nav\u003e\n      \u003cOutlet /\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default SharedLayout;\n```\n\n```js\nApp.js\n\n\u003cRoute index element={\u003cStats/\u003e} \u003e\n```\n\n#### Protected Route\n\n- create ProtectedRoute.js in pages\n- import/export\n- wrap SharedLayout in App.js\n\n```js\n\u003cRoute\n  path='/'\n  element={\n    \u003cProtectedRoute\u003e\n      \u003cSharedLayout /\u003e\n    \u003c/ProtectedRoute\u003e\n  }\n/\u003e\n```\n\n```js\nProtectedRoute.js;\n\nimport { Navigate } from 'react-router-dom';\nimport { useAppContext } from '../context/appContext';\n\nconst ProtectedRoute = ({ children }) =\u003e {\n  const { user } = useAppContext();\n  if (!user) {\n    return \u003cNavigate to='/landing' /\u003e;\n  }\n  return children;\n};\n```\n\n#### Navbar, SmallSidebar, BigSidebar\n\n- create Navbar, SmallSidebar, BigSidebar in components\n- import Wrappers from assets/wrappers\n- simple return\n- import/export\n\n```js\nSharedLayout.js;\n\nimport { Outlet } from 'react-router-dom';\nimport { Navbar, SmallSidebar, BigSidebar } from '../../components';\nimport Wrapper from '../../assets/wrappers/SharedLayout';\n\nconst SharedLayout = () =\u003e {\n  const { user } = useAppContext();\n  return (\n    \u003c\u003e\n      \u003cWrapper\u003e\n        \u003cmain className='dashboard'\u003e\n          \u003cSmallSidebar /\u003e\n          \u003cBigSidebar /\u003e\n          \u003cdiv\u003e\n            \u003cNavbar /\u003e\n            \u003cdiv className='dashboard-page'\u003e\n              \u003cOutlet /\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/main\u003e\n      \u003c/Wrapper\u003e\n    \u003c/\u003e\n  );\n};\n\nexport default SharedLayout;\n```\n\n#### React Icons\n\n[React Icons](https://react-icons.github.io/react-icons/)\n\n```sh\nnpm install react-icons\n```\n\n```js\nNavbar.js\n\nimport Wrapper from '../assets/wrappers/Navbar'\nimport {FaHome} from 'react-icons/fa'\nconst Navbar = () =\u003e {\n  return (\n    \u003cWrapper\u003e\n      \u003ch4\u003enavbar\u003c/h4\u003e\n      \u003cFaHome\u003e\n    \u003c/Wrapper\u003e\n  )\n}\n\nexport default Navbar\n\n```\n\n#### Navbar Setup\n\n```js\nNavbar.js;\n\nimport { useState } from 'react';\nimport { FaAlignLeft, FaUserCircle, FaCaretDown } from 'react-icons/fa';\nimport { useAppContext } from '../context/appContext';\nimport Logo from './Logo';\nimport Wrapper from '../assets/wrappers/Navbar';\nconst Navbar = () =\u003e {\n  return (\n    \u003cWrapper\u003e\n      \u003cdiv className='nav-center'\u003e\n        \u003cbutton\n          className='toggle-btn'\n          onClick={() =\u003e console.log('toggle sidebar')}\n        \u003e\n          \u003cFaAlignLeft /\u003e\n        \u003c/button\u003e\n\n        \u003cdiv\u003e\n          \u003cLogo /\u003e\n          \u003ch3 className='logo-text'\u003edashboard\u003c/h3\u003e\n        \u003c/div\u003e\n\n        \u003cdiv className='btn-container'\u003e\n          \u003cbutton className='btn' onClick={() =\u003e console.log('show logout')}\u003e\n            \u003cFaUserCircle /\u003e\n            john\n            \u003cFaCaretDown /\u003e\n          \u003c/button\u003e\n          \u003cdiv className='dropdown show-dropdown'\u003e\n            \u003cbutton\n              onClick={() =\u003e console.log('logout user')}\n              className='dropdown-btn'\n            \u003e\n              logout\n            \u003c/button\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default Navbar;\n```\n\n#### Toggle Sidebar\n\n```js\nactions.js;\n\nexport const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR';\n```\n\n- import/export\n\n```js\nappContext.js;\n\nconst initialState = {\n  showSidebar: false,\n};\n\nconst toggleSidebar = () =\u003e {\n  dispatch({ type: TOGGLE_SIDEBAR });\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === TOGGLE_SIDEBAR) {\n  return { ...state, showSidebar: !state.showSidebar };\n}\n```\n\n```js\nNavbar.js;\n\nconst { toggleSidebar } = useAppContext();\n\nreturn (\n  \u003cbutton className='toggle-btn' onClick={toggleSidebar}\u003e\n    \u003cFaAlignLeft /\u003e\n  \u003c/button\u003e\n);\n```\n\n#### Toggle Dropdown\n\n```js\nNavbar.js\n\nconst [showLogout, setShowLogout] = useState(false)\n\n\u003cdiv className='btn-container'\u003e\n  \u003cbutton className='btn' onClick={() =\u003e setShowLogout(!showLogout)}\u003e\n    \u003cFaUserCircle /\u003e\n      {user.name}\n    \u003cFaCaretDown /\u003e\n  \u003c/button\u003e\n  \u003cdiv className={showLogout ? 'dropdown show-dropdown' : 'dropdown'}\u003e\n    \u003cbutton onClick={() =\u003e logoutUser()} className='dropdown-btn'\u003e\n      logout\n    \u003c/button\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n```\n\n#### Logout User\n\n```js\nactions.js;\n\nexport const LOGOUT_USER = 'LOGOUT_USER';\n```\n\n- import/export\n\n```js\nappContext.js\n\nconst logoutUser = () =\u003e {\n  dispatch({ type: LOGOUT_USER })\n  removeUserFromLocalStorage()\n}\n\nvalue={{logoutUser}}\n```\n\n```js\nreducer.js;\n\nimport { initialState } from './appContext';\n\nif (action.type === LOGOUT_USER) {\n  return {\n    ...initialState,\n    user: null,\n    token: null,\n    userLocation: '',\n    jobLocation: '',\n  };\n}\n```\n\n```js\nNavbar.js;\n\nconst { user, logoutUser, toggleSidebar } = useAppContext();\n\nreturn (\n  \u003cdiv className='btn-container'\u003e\n    \u003cbutton className='btn' onClick={() =\u003e setShowLogout(!showLogout)}\u003e\n      \u003cFaUserCircle /\u003e\n      {user.name}\n      {user \u0026\u0026 user.name}\n      {user?.name} // optional chaining\n      \u003cFaCaretDown /\u003e\n    \u003c/button\u003e\n    \u003cdiv className={showLogout ? 'dropdown show-dropdown' : 'dropdown'}\u003e\n      \u003cbutton onClick={logoutUser} className='dropdown-btn'\u003e\n        logout\n      \u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n);\n```\n\n#### Setup Links\n\n- create \u003cb\u003eutils\u003c/b\u003ein the \u003cb\u003esrc\u003c/b\u003e\n- setup links.js\n\n```js\nimport { IoBarChartSharp } from 'react-icons/io5';\nimport { MdQueryStats } from 'react-icons/md';\nimport { FaWpforms } from 'react-icons/fa';\nimport { ImProfile } from 'react-icons/im';\n\nconst links = [\n  {\n    id: 1,\n    text: 'stats',\n    path: '/',\n    icon: \u003cIoBarChartSharp /\u003e,\n  },\n  {\n    id: 2,\n    text: 'all jobs',\n    path: 'all-jobs',\n    icon: \u003cMdQueryStats /\u003e,\n  },\n  {\n    id: 3,\n    text: 'add job',\n    path: 'add-job',\n    icon: \u003cFaWpforms /\u003e,\n  },\n  {\n    id: 4,\n    text: 'profile',\n    path: 'profile',\n    icon: \u003cImProfile /\u003e,\n  },\n];\n\nexport default links;\n```\n\n#### Small Sidebar - Setup\n\n```js\nSmallSidebar.js;\n\nimport Wrapper from '../assets/wrappers/SmallSidebar';\nimport { FaTimes } from 'react-icons/fa';\nimport { useAppContext } from '../context/appContext';\nimport links from '../utils/links';\nimport { NavLink } from 'react-router-dom';\nimport Logo from './Logo';\n\nexport const SmallSidebar = () =\u003e {\n  return (\n    \u003cWrapper\u003e\n      \u003cdiv className='sidebar-container show-sidebar'\u003e\n        \u003cdiv className='content'\u003e\n          \u003cbutton className='close-btn' onClick={() =\u003e console.log('toggle')}\u003e\n            \u003cFaTimes /\u003e\n          \u003c/button\u003e\n          \u003cheader\u003e\n            \u003cLogo /\u003e\n          \u003c/header\u003e\n          \u003cdiv className='nav-links'\u003enav links\u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default SmallSidebar;\n```\n\n#### Small Sidebar - Toggle\n\n```js\nSmallSidebar.js;\n\nconst { showSidebar, toggleSidebar } = useAppContext();\n```\n\n```js\nSmallSidebar.js;\n\nreturn (\n  \u003cdiv\n    className={\n      showSidebar ? 'sidebar-container show-sidebar' : 'sidebar-container'\n    }\n  \u003e\u003c/div\u003e\n);\n```\n\n```js\nSmallSidebar.js;\n\nreturn (\n  \u003cbutton className='close-btn' onClick={toggleSidebar}\u003e\n    \u003cFaTimes /\u003e\n  \u003c/button\u003e\n);\n```\n\n#### Small Sidebar - Nav Links\n\n```js\nSmallSidebar.js;\n\nimport { NavLink } from 'react-router-dom';\n\nreturn (\n  \u003cdiv className='nav-links'\u003e\n    {links.map((link) =\u003e {\n      const { text, path, id, icon } = link;\n\n      return (\n        \u003cNavLink\n          to={path}\n          className={({ isActive }) =\u003e\n            isActive ? 'nav-link active' : 'nav-link'\n          }\n          key={id}\n          onClick={toggleSidebar}\n        \u003e\n          \u003cspan className='icon'\u003e{icon}\u003c/span\u003e\n          {text}\n        \u003c/NavLink\u003e\n      );\n    })}\n  \u003c/div\u003e\n);\n```\n\n#### Nav Links Component\n\n- in \u003cb\u003ecomponents\u003c/b\u003e create NavLinks.js\n- styles still set from Wrapper\n- also can setup in links.js, preference\n\n```js\nimport { NavLink } from 'react-router-dom';\nimport links from '../utils/links';\n\nconst NavLinks = ({ toggleSidebar }) =\u003e {\n  return (\n    \u003cdiv className='nav-links'\u003e\n      {links.map((link) =\u003e {\n        const { text, path, id, icon } = link;\n\n        return (\n          \u003cNavLink\n            to={path}\n            key={id}\n            onClick={toggleSidebar}\n            className={({ isActive }) =\u003e\n              isActive ? 'nav-link active' : 'nav-link'\n            }\n          \u003e\n            \u003cspan className='icon'\u003e{icon}\u003c/span\u003e\n            {text}\n          \u003c/NavLink\u003e\n        );\n      })}\n    \u003c/div\u003e\n  );\n};\n\nexport default NavLinks;\n```\n\n```js\nSmallSidebar.js\n\nimport NavLinks from './NavLinks'\n\nreturn \u003cNavLinks toggleSidebar={toggleSidebar}\u003e\n```\n\n#### Big Sidebar\n\n```js\nimport { useAppContext } from '../context/appContext';\nimport NavLinks from './NavLinks';\nimport Logo from '../components/Logo';\nimport Wrapper from '../assets/wrappers/BigSidebar';\n\nconst BigSidebar = () =\u003e {\n  const { showSidebar } = useAppContext();\n  return (\n    \u003cWrapper\u003e\n      \u003cdiv\n        className={\n          showSidebar ? 'sidebar-container ' : 'sidebar-container show-sidebar'\n        }\n      \u003e\n        \u003cdiv className='content'\u003e\n          \u003cheader\u003e\n            \u003cLogo /\u003e\n          \u003c/header\u003e\n          \u003cNavLinks /\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default BigSidebar;\n```\n\n#### REACT ROUTER UPDATE !!!\n\n- [Stack Overflow](https://stackoverflow.com/questions/70644361/react-router-dom-v6-shows-active-for-index-as-well-as-other-subroutes)\n\n```js\n\u003cNavLink\nto={path}\nkey={id}\nonClick={toggleSidebar}\nclassName={({ isActive }) =\u003e\nisActive ? 'nav-link active' : 'nav-link'}\n\n\nend\n\u003e\n```\n\n#### Authenticate User Setup\n\n- create auth.js in \u003cb\u003emiddleware\u003c/b\u003e\n\n```js\nconst auth = async (req, res, next) =\u003e {\n  console.log('authenticate user');\n  next();\n};\n\nexport default auth;\n```\n\n```js\nauthRoutes.js;\n\nimport authenticateUser from '../middleware/auth.js';\n\nrouter.route('/updateUser').patch(authenticateUser, updateUser);\n```\n\n- two options\n\n```js\nserver.js;\n\nimport authenticateUser from './middleware/auth.js';\napp.use('/api/v1/jobs', authenticateUser, jobsRouter);\n```\n\n```js\njobsRoutes.js;\n\nimport authenticateUser from './middleware/auth.js';\n\n// all routes !!!!\n\nrouter.route('/stats').get(authenticateUser, showStats);\n```\n\n#### Auth - Bearer Schema\n\n```js\nPostman\n\nHeaders\n\nAuthorization: Bearer \u003ctoken\u003e\n\n```\n\n```js\nauth.js;\n\nconst auth = async (req, res, next) =\u003e {\n  const headers = req.headers;\n  const authHeader = req.headers.authorization;\n  console.log(headers);\n  console.log(authHeader);\n  next();\n};\n```\n\n#### Postman - Set Token Programmatically\n\n- register and login routes\n- Tests\n\n```js\nconst jsonData = pm.response.json();\npm.globals.set('token', jsonData.token);\n\nType: Bearer;\n\nToken: {\n  {\n    token;\n  }\n}\n```\n\n#### Unauthenticated Error\n\n```js\nauth.js;\n\nimport { UnAuthenticatedError } from '../errors/index.js';\n\nconst auth = async (req, res, next) =\u003e {\n  const authHeader = req.headers.authorization;\n\n  if (!authHeader) {\n    // why, well is it 400 or 404?\n    // actually 401\n    throw new UnAuthenticatedError('Authentication Invalid');\n  }\n\n  next();\n};\n```\n\n#### Auth Middleware\n\n```js\nimport jwt from 'jsonwebtoken';\nimport { UnAuthenticatedError } from '../errors/index.js';\n\nconst auth = async (req, res, next) =\u003e {\n  // check header\n  const authHeader = req.headers.authorization;\n  if (!authHeader || !authHeader.startsWith('Bearer')) {\n    throw new UnauthenticatedError('Authentication invalid');\n  }\n  const token = authHeader.split(' ')[1];\n\n  try {\n    const payload = jwt.verify(token, process.env.JWT_SECRET);\n    // console.log(payload)\n    // attach the user request object\n    // req.user = payload\n    req.user = { userId: payload.userId };\n    next();\n  } catch (error) {\n    throw new UnauthenticatedError('Authentication invalid');\n  }\n};\n\nexport default auth;\n```\n\n#### Update User\n\n```js\nconst updateUser = async (req, res) =\u003e {\n  const { email, name, lastName, location } = req.body;\n  if (!email || !name || !lastName || !location) {\n    throw new BadRequestError('Please provide all values');\n  }\n\n  const user = await User.findOne({ _id: req.user.userId });\n\n  user.email = email;\n  user.name = name;\n  user.lastName = lastName;\n  user.location = location;\n\n  await user.save();\n\n  // various setups\n  // in this case only id\n  // if other properties included, must re-generate\n\n  const token = user.createJWT();\n  res.status(StatusCodes.OK).json({\n    user,\n    token,\n    location: user.location,\n  });\n};\n```\n\n#### Modified Paths\n\n- user.save() vs User.findOneAndUpdate\n\n```js\nUser.js;\n\nUserSchema.pre('save', async function () {\n  console.log(this.modifiedPaths());\n  console.log(this.isModified('name'));\n\n  // if (!this.isModified('password')) return\n  // const salt = await bcrypt.genSalt(10)\n  // this.password = await bcrypt.hash(this.password, salt)\n});\n```\n\n#### Profile Page\n\n```js\nappContext.js\n\nconst updateUser = async (currentUser) =\u003e {\n  console.log(currentUser)\n}\n\nvalue={{updateUser}}\n```\n\n```js\nProfile.js;\n\nimport { useState } from 'react';\nimport { FormRow, Alert } from '../../components';\nimport { useAppContext } from '../../context/appContext';\nimport Wrapper from '../../assets/wrappers/DashboardFormPage';\n\nconst Profile = () =\u003e {\n  const { user, showAlert, displayAlert, updateUser, isLoading } =\n    useAppContext();\n  const [name, setName] = useState(user?.name);\n  const [email, setEmail] = useState(user?.email);\n  const [lastName, setLastName] = useState(user?.lastName);\n  const [location, setLocation] = useState(user?.location);\n\n  const handleSubmit = (e) =\u003e {\n    e.preventDefault();\n    if (!name || !email || !lastName || !location) {\n      // test and remove temporary\n      displayAlert();\n      return;\n    }\n\n    updateUser({ name, email, lastName, location });\n  };\n  return (\n    \u003cWrapper\u003e\n      \u003cform className='form' onSubmit={handleSubmit}\u003e\n        \u003ch3\u003eprofile \u003c/h3\u003e\n        {showAlert \u0026\u0026 \u003cAlert /\u003e}\n\n        {/* name */}\n        \u003cdiv className='form-center'\u003e\n          \u003cFormRow\n            type='text'\n            name='name'\n            value={name}\n            handleChange={(e) =\u003e setName(e.target.value)}\n          /\u003e\n          \u003cFormRow\n            labelText='last name'\n            type='text'\n            name='lastName'\n            value={lastName}\n            handleChange={(e) =\u003e setLastName(e.target.value)}\n          /\u003e\n          \u003cFormRow\n            type='email'\n            name='email'\n            value={email}\n            handleChange={(e) =\u003e setEmail(e.target.value)}\n          /\u003e\n\n          \u003cFormRow\n            type='text'\n            name='location'\n            value={location}\n            handleChange={(e) =\u003e setLocation(e.target.value)}\n          /\u003e\n          \u003cbutton className='btn btn-block' type='submit' disabled={isLoading}\u003e\n            {isLoading ? 'Please Wait...' : 'save changes'}\n          \u003c/button\u003e\n        \u003c/div\u003e\n      \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default Profile;\n```\n\n#### Bearer Token - Manual Approach\n\n```js\nappContext.js;\n\nconst updaterUser = async (currentUser) =\u003e {\n  try {\n    const { data } = await axios.patch('/api/v1/auth/updateUser', currentUser, {\n      headers: {\n        Authorization: `Bearer ${state.token}`,\n      },\n    });\n    console.log(data);\n  } catch (error) {\n    console.log(error.response);\n  }\n};\n```\n\n#### Axios - Global Setup\n\n\u003c!-- IMPORTANT  --\u003e\n\nIn current axios version,\ncommon property returns undefined,\nso we don't use it anymore!!!\n\n```js\nappContext.js;\n\naxios.defaults.headers['Authorization'] = `Bearer ${state.token}`;\n```\n\n#### Axios - Setup Instance\n\n```js\nAppContext.js;\n\nconst authFetch = axios.create({\n  baseURL: '/api/v1',\n  headers: {\n    Authorization: `Bearer ${state.token}`,\n  },\n});\n\nconst updaterUser = async (currentUser) =\u003e {\n  try {\n    const { data } = await authFetch.patch('/auth/updateUser', currentUser);\n  } catch (error) {\n    console.log(error.response);\n  }\n};\n```\n\n#### Axios - Interceptors\n\n- will use instance, but can use axios instead\n\n\u003c!-- IMPORTANT  --\u003e\n\nIn current axios version,\ncommon property returns undefined,\nso we don't use it anymore!!!\n\n```js\nappContext.js;\n\n// response interceptor\nauthFetch.interceptors.request.use(\n  (config) =\u003e {\n    config.headers['Authorization'] = `Bearer ${state.token}`;\n    return config;\n  },\n  (error) =\u003e {\n    return Promise.reject(error);\n  }\n);\n// response interceptor\nauthFetch.interceptors.response.use(\n  (response) =\u003e {\n    return response;\n  },\n  (error) =\u003e {\n    console.log(error.response);\n    if (error.response.status === 401) {\n      console.log('AUTH ERROR');\n    }\n    return Promise.reject(error);\n  }\n);\n```\n\n#### Update User\n\n```js\nactions.js;\nexport const UPDATE_USER_BEGIN = 'UPDATE_USER_BEGIN';\nexport const UPDATE_USER_SUCCESS = 'UPDATE_USER_SUCCESS';\nexport const UPDATE_USER_ERROR = 'UPDATE_USER_ERROR';\n```\n\n```js\nappContext.js;\n\nconst updateUser = async (currentUser) =\u003e {\n  dispatch({ type: UPDATE_USER_BEGIN });\n  try {\n    const { data } = await authFetch.patch('/auth/updateUser', currentUser);\n\n    // no token\n    const { user, location, token } = data;\n\n    dispatch({\n      type: UPDATE_USER_SUCCESS,\n      payload: { user, location, token },\n    });\n\n    addUserToLocalStorage({ user, location, token });\n  } catch (error) {\n    dispatch({\n      type: UPDATE_USER_ERROR,\n      payload: { msg: error.response.data.msg },\n    });\n  }\n  clearAlert();\n};\n```\n\n```js\nreducer.js\nif (action.type === UPDATE_USER_BEGIN) {\n  return { ...state, isLoading: true }\n}\n\nif (action.type === UPDATE_USER_SUCCESS) {\n  return {\n    ...state,\n    isLoading: false,\n    token:action.payload.token\n    user: action.payload.user,\n    userLocation: action.payload.location,\n    jobLocation: action.payload.location,\n    showAlert: true,\n    alertType: 'success',\n    alertText: 'User Profile Updated!',\n  }\n}\nif (action.type === UPDATE_USER_ERROR) {\n  return {\n    ...state,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'danger',\n    alertText: action.payload.msg,\n  }\n}\n```\n\n#### 401 Error - Logout User\n\n```js\nappContext.js;\n// response interceptor\nauthFetch.interceptors.response.use(\n  (response) =\u003e {\n    return response;\n  },\n  (error) =\u003e {\n    if (error.response.status === 401) {\n      logoutUser();\n    }\n    return Promise.reject(error);\n  }\n);\n\nconst updateUser = async (currentUser) =\u003e {\n  dispatch({ type: UPDATE_USER_BEGIN });\n  try {\n    const { data } = await authFetch.patch('/auth/updateUser', currentUser);\n\n    // no token\n    const { user, location } = data;\n\n    dispatch({\n      type: UPDATE_USER_SUCCESS,\n      payload: { user, location, token },\n    });\n\n    addUserToLocalStorage({ user, location, token: initialState.token });\n  } catch (error) {\n    if (error.response.status !== 401) {\n      dispatch({\n        type: UPDATE_USER_ERROR,\n        payload: { msg: error.response.data.msg },\n      });\n    }\n  }\n  clearAlert();\n};\n```\n\n#### Job Model\n\n- Job Model\n\n```js\nJob.js;\n\nimport mongoose from 'mongoose';\n\nconst JobSchema = new mongoose.Schema(\n  {\n    company: {\n      type: String,\n      required: [true, 'Please provide company name'],\n      maxlength: 50,\n    },\n    position: {\n      type: String,\n      required: [true, 'Please provide position'],\n      maxlength: 100,\n    },\n    status: {\n      type: String,\n      enum: ['interview', 'declined', 'pending'],\n      default: 'pending',\n    },\n\n    jobType: {\n      type: String,\n      enum: ['full-time', 'part-time', 'remote', 'internship'],\n      default: 'full-time',\n    },\n    jobLocation: {\n      type: String,\n      default: 'my city',\n      required: true,\n    },\n    createdBy: {\n      type: mongoose.Types.ObjectId,\n      ref: 'User',\n      required: [true, 'Please provide user'],\n    },\n  },\n  { timestamps: true }\n);\n\nexport default mongoose.model('Job', JobSchema);\n```\n\n#### Create Job\n\n```js\njobsController.js;\n\nimport Job from '../models/Job.js';\nimport { StatusCodes } from 'http-status-codes';\nimport { BadRequestError, NotFoundError } from '../errors/index.js';\n\nconst createJob = async (req, res) =\u003e {\n  const { position, company } = req.body;\n\n  if (!position || !company) {\n    throw new BadRequestError('Please Provide All Values');\n  }\n\n  req.body.createdBy = req.user.userId;\n\n  const job = await Job.create(req.body);\n  res.status(StatusCodes.CREATED).json({ job });\n};\n```\n\n#### Job State Values\n\n```js\nappContext.js;\nconst initialState = {\n  isEditing: false,\n  editJobId: '',\n  position: '',\n  company: '',\n  // jobLocation\n  jobTypeOptions: ['full-time', 'part-time', 'remote', 'internship'],\n  jobType: 'full-time',\n  statusOptions: ['pending', 'interview', 'declined'],\n  status: 'pending',\n};\n```\n\n#### AddJob Page - Setup\n\n```js\nimport { FormRow, Alert } from '../../components';\nimport { useAppContext } from '../../context/appContext';\nimport Wrapper from '../../assets/wrappers/DashboardFormPage';\nconst AddJob = () =\u003e {\n  const {\n    isEditing,\n    showAlert,\n    displayAlert,\n    position,\n    company,\n    jobLocation,\n    jobType,\n    jobTypeOptions,\n    status,\n    statusOptions,\n  } = useAppContext();\n\n  const handleSubmit = (e) =\u003e {\n    e.preventDefault();\n\n    if (!position || !company || !jobLocation) {\n      displayAlert();\n      return;\n    }\n    console.log('create job');\n  };\n\n  const handleJobInput = (e) =\u003e {\n    const name = e.target.name;\n    const value = e.target.value;\n    console.log(`${name}:${value}`);\n  };\n\n  return (\n    \u003cWrapper\u003e\n      \u003cform className='form'\u003e\n        \u003ch3\u003e{isEditing ? 'edit job' : 'add job'} \u003c/h3\u003e\n        {showAlert \u0026\u0026 \u003cAlert /\u003e}\n\n        {/* position */}\n        \u003cdiv className='form-center'\u003e\n          \u003cFormRow\n            type='text'\n            name='position'\n            value={position}\n            handleChange={handleJobInput}\n          /\u003e\n          {/* company */}\n          \u003cFormRow\n            type='text'\n            name='company'\n            value={company}\n            handleChange={handleJobInput}\n          /\u003e\n          {/* location */}\n          \u003cFormRow\n            type='text'\n            labelText='location'\n            name='jobLocation'\n            value={jobLocation}\n            handleChange={handleJobInput}\n          /\u003e\n          {/* job type */}\n\n          {/* job status */}\n\n          \u003cdiv className='btn-container'\u003e\n            \u003cbutton\n              className='btn btn-block submit-btn'\n              type='submit'\n              onClick={handleSubmit}\n            \u003e\n              submit\n            \u003c/button\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default AddJob;\n```\n\n#### Select Input\n\n```js\nreturn (\n  // job type\n  \u003cdiv className='form-row'\u003e\n    \u003clabel htmlFor='jobType' className='form-label'\u003e\n      job type\n    \u003c/label\u003e\n\n    \u003cselect\n      name='jobType'\n      value={jobType}\n      onChange={handleJobInput}\n      className='form-select'\n    \u003e\n      {jobTypeOptions.map((itemValue, index) =\u003e {\n        return (\n          \u003coption key={index} value={itemValue}\u003e\n            {itemValue}\n          \u003c/option\u003e\n        );\n      })}\n    \u003c/select\u003e\n  \u003c/div\u003e\n);\n```\n\n#### FormRowSelect\n\n- create FormRowSelect in components\n- setup import/export\n\n```js\nconst FormRowSelect = ({ labelText, name, value, handleChange, list }) =\u003e {\n  return (\n    \u003cdiv className='form-row'\u003e\n      \u003clabel htmlFor={name} className='form-label'\u003e\n        {labelText || name}\n      \u003c/label\u003e\n\n      \u003cselect\n        name={name}\n        value={value}\n        onChange={handleChange}\n        className='form-select'\n      \u003e\n        {list.map((itemValue, index) =\u003e {\n          return (\n            \u003coption key={index} value={itemValue}\u003e\n              {itemValue}\n            \u003c/option\u003e\n          );\n        })}\n      \u003c/select\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default FormRowSelect;\n```\n\n```js\nAddJob.js;\n\nreturn (\n  \u003c\u003e\n    {/* job status */}\n\n    \u003cFormRowSelect\n      name='status'\n      value={status}\n      handleChange={handleJobInput}\n      list={statusOptions}\n    /\u003e\n\n    {/* job type */}\n    \u003cFormRowSelect\n      labelText='type'\n      name='jobType'\n      value={jobType}\n      handleChange={handleJobInput}\n      list={jobTypeOptions}\n    /\u003e\n  \u003c/\u003e\n);\n```\n\n#### Change State Values With Handle Change\n\n- [JS Nuggets Dynamic Object Keys](https://youtu.be/_qxCYtWm0tw)\n\n```js\nactions.js;\n\nexport const HANDLE_CHANGE = 'HANDLE_CHANGE';\n```\n\n```js\nappContext.js\n\nconst handleChange = ({ name, value }) =\u003e {\n  dispatch({\n    type: HANDLE_CHANGE,\n    payload: { name, value },\n  })\n}\n\nvalue={{handleChange}}\n```\n\n```js\nreducer.js;\n\nif (action.type === HANDLE_CHANGE) {\n  return { ...state, [action.payload.name]: action.payload.value };\n}\n```\n\n```js\nAddJob.js;\n\nconst { handleChange } = useAppContext();\n\nconst handleJobInput = (e) =\u003e {\n  handleChange({ name: e.target.name, value: e.target.value });\n};\n```\n\n#### Clear Values\n\n```js\nactions.js;\n\nexport const CLEAR_VALUES = 'CLEAR_VALUES';\n```\n\n```js\nappContext.js\n\nconst clearValues = () =\u003e {\n    dispatch({ type: CLEAR_VALUES })\n  }\n\nvalue={{clearValues}}\n```\n\n```js\nreducer.js;\n\nif (action.type === CLEAR_VALUES) {\n  const initialState = {\n    isEditing: false,\n    editJobId: '',\n    position: '',\n    company: '',\n    jobLocation: state.userLocation,\n    jobType: 'full-time',\n    status: 'pending',\n  };\n  return { ...state, ...initialState };\n}\n```\n\n```js\nAddJob.js;\n\nconst { clearValues } = useAppContext();\n\nreturn (\n  \u003cdiv className='btn-container'\u003e\n    {/* submit button */}\n\n    \u003cbutton\n      className='btn btn-block clear-btn'\n      onClick={(e) =\u003e {\n        e.preventDefault();\n        clearValues();\n      }}\n    \u003e\n      clear\n    \u003c/button\u003e\n  \u003c/div\u003e\n);\n```\n\n#### Create Job\n\n```js\nactions.js;\n\nexport const CREATE_JOB_BEGIN = 'CREATE_JOB_BEGIN';\nexport const CREATE_JOB_SUCCESS = 'CREATE_JOB_SUCCESS';\nexport const CREATE_JOB_ERROR = 'CREATE_JOB_ERROR';\n```\n\n```js\nappContext.js;\n\nconst createJob = async () =\u003e {\n  dispatch({ type: CREATE_JOB_BEGIN });\n  try {\n    const { position, company, jobLocation, jobType, status } = state;\n\n    await authFetch.post('/jobs', {\n      company,\n      position,\n      jobLocation,\n      jobType,\n      status,\n    });\n    dispatch({\n      type: CREATE_JOB_SUCCESS,\n    });\n    // call function instead clearValues()\n    dispatch({ type: CLEAR_VALUES });\n  } catch (error) {\n    if (error.response.status === 401) return;\n    dispatch({\n      type: CREATE_JOB_ERROR,\n      payload: { msg: error.response.data.msg },\n    });\n  }\n  clearAlert();\n};\n```\n\n```js\nAddJob.js;\n\nconst { createJob } = useAppContext();\n\nconst handleSubmit = (e) =\u003e {\n  e.preventDefault();\n  // while testing\n\n  // if (!position || !company || !jobLocation) {\n  //   displayAlert()\n  //   return\n  // }\n  if (isEditing) {\n    // eventually editJob()\n    return;\n  }\n  createJob();\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === CREATE_JOB_BEGIN) {\n  return { ...state, isLoading: true };\n}\nif (action.type === CREATE_JOB_SUCCESS) {\n  return {\n    ...state,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'success',\n    alertText: 'New Job Created!',\n  };\n}\nif (action.type === CREATE_JOB_ERROR) {\n  return {\n    ...state,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'danger',\n    alertText: action.payload.msg,\n  };\n}\n```\n\n#### Get All Jobs\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n  const jobs = await Job.find({ createdBy: req.user.userId });\n\n  res\n    .status(StatusCodes.OK)\n    .json({ jobs, totalJobs: jobs.length, numOfPages: 1 });\n};\n```\n\n#### Jobs State Values\n\n```js\nappContext.js;\n\nconst initialState = {\n  jobs: [],\n  totalJobs: 0,\n  numOfPages: 1,\n  page: 1,\n};\n```\n\n#### Get All Jobs Request\n\n```js\nactions.js;\nexport const GET_JOBS_BEGIN = 'GET_JOBS_BEGIN';\nexport const GET_JOBS_SUCCESS = 'GET_JOBS_SUCCESS';\n```\n\n```js\nappContext.js\n\nimport React, { useReducer, useContext, useEffect } from 'react'\n\nconst getJobs = async () =\u003e {\n  let url = `/jobs`\n\n  dispatch({ type: GET_JOBS_BEGIN })\n  try {\n    const { data } = await authFetch(url)\n    const { jobs, totalJobs, numOfPages } = data\n    dispatch({\n      type: GET_JOBS_SUCCESS,\n      payload: {\n        jobs,\n        totalJobs,\n        numOfPages,\n      },\n    })\n  } catch (error) {\n    console.log(error.response)\n    logoutUser()\n  }\n  clearAlert()\n}\n\nuseEffect(() =\u003e {\n  getJobs()\n}, [])\n\nvalue={{getJobs}}\n\n```\n\n```js\nreducer.js;\n\nif (action.type === GET_JOBS_BEGIN) {\n  return { ...state, isLoading: true, showAlert: false };\n}\nif (action.type === GET_JOBS_SUCCESS) {\n  return {\n    ...state,\n    isLoading: false,\n    jobs: action.payload.jobs,\n    totalJobs: action.payload.totalJobs,\n    numOfPages: action.payload.numOfPages,\n  };\n}\n```\n\n#### AllJobs Page Setup\n\n- create\n- SearchContainer export\n- JobsContainer export\n- Job\n- JobInfo\n\n```js\nAllJobs.js;\n\nimport { JobsContainer, SearchContainer } from '../../components';\nconst AllJobs = () =\u003e {\n  return (\n    \u003c\u003e\n      \u003cSearchContainer /\u003e\n      \u003cJobsContainer /\u003e\n    \u003c/\u003e\n  );\n};\n\nexport default AllJobs;\n```\n\n```js\nJobsContainer.js;\nimport { useAppContext } from '../context/appContext';\nimport { useEffect } from 'react';\nimport Loading from './Loading';\nimport Job from './Job';\nimport Wrapper from '../assets/wrappers/JobsContainer';\n\nconst JobsContainer = () =\u003e {\n  const { getJobs, jobs, isLoading, page, totalJobs } = useAppContext();\n  useEffect(() =\u003e {\n    getJobs();\n  }, []);\n\n  if (isLoading) {\n    return \u003cLoading center /\u003e;\n  }\n  if (jobs.length === 0) {\n    return (\n      \u003cWrapper\u003e\n        \u003ch2\u003eNo jobs to display...\u003c/h2\u003e\n      \u003c/Wrapper\u003e\n    );\n  }\n  return (\n    \u003cWrapper\u003e\n      \u003ch5\u003e\n        {totalJobs} job{jobs.length \u003e 1 \u0026\u0026 's'} found\n      \u003c/h5\u003e\n      \u003cdiv className='jobs'\u003e\n        {jobs.map((job) =\u003e {\n          return \u003cJob key={job._id} {...job} /\u003e;\n        })}\n      \u003c/div\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default JobsContainer;\n```\n\n```js\nJob.js;\n\nimport moment from 'moment';\n\nconst Job = ({ company }) =\u003e {\n  return \u003ch5\u003e{company}\u003c/h5\u003e;\n};\n\nexport default Job;\n```\n\n#### Moment.js\n\n- Format Dates\n- [moment.js](https://momentjs.com/)\n\n- stop server\n- cd client\n\n```sh\nnpm install moment\n\n```\n\n```js\nJob.js;\n\nimport moment from 'moment';\n\nconst Job = ({ company, createdAt }) =\u003e {\n  let date = moment(createdAt);\n  date = date.format('MMM Do, YYYY');\n  return (\n    \u003cdiv\u003e\n      \u003ch5\u003e{company}\u003c/h5\u003e\n      \u003ch5\u003e{date}\u003c/h5\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default Job;\n```\n\n#### Job Component - Setup\n\n```js\nappContext.js\n\nconst setEditJob = (id) =\u003e {\n  console.log(`set edit job : ${id}`)\n}\nconst deleteJob = (id) =\u003e{\n  console.log(`delete : ${id}`)\n}\nvalue={{setEditJob,deleteJob}}\n```\n\n```js\nJob.js;\n\nimport { FaLocationArrow, FaBriefcase, FaCalendarAlt } from 'react-icons/fa';\nimport { Link } from 'react-router-dom';\nimport { useAppContext } from '../context/appContext';\nimport Wrapper from '../assets/wrappers/Job';\nimport JobInfo from './JobInfo';\n\nconst Job = ({\n  _id,\n  position,\n  company,\n  jobLocation,\n  jobType,\n  createdAt,\n  status,\n}) =\u003e {\n  const { setEditJob, deleteJob } = useAppContext();\n\n  let date = moment(createdAt);\n  date = date.format('MMM Do, YYYY');\n\n  return (\n    \u003cWrapper\u003e\n      \u003cheader\u003e\n        \u003cdiv className='main-icon'\u003e{company.charAt(0)}\u003c/div\u003e\n        \u003cdiv className='info'\u003e\n          \u003ch5\u003e{position}\u003c/h5\u003e\n          \u003cp\u003e{company}\u003c/p\u003e\n        \u003c/div\u003e\n      \u003c/header\u003e\n      \u003cdiv className='content'\u003e\n        {/* content center later */}\n        \u003cfooter\u003e\n          \u003cdiv className='actions'\u003e\n            \u003cLink\n              to='/add-job'\n              onClick={() =\u003e setEditJob(_id)}\n              className='btn edit-btn'\n            \u003e\n              Edit\n            \u003c/Link\u003e\n            \u003cbutton\n              type='button'\n              className='btn delete-btn'\n              onClick={() =\u003e deleteJob(_id)}\n            \u003e\n              Delete\n            \u003c/button\u003e\n          \u003c/div\u003e\n        \u003c/footer\u003e\n      \u003c/div\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default Job;\n```\n\n#### JobInfo\n\n```js\nJobInfo.js;\n\nimport Wrapper from '../assets/wrappers/JobInfo';\n\nconst JobInfo = ({ icon, text }) =\u003e {\n  return (\n    \u003cWrapper\u003e\n      \u003cspan className='icon'\u003e{icon}\u003c/span\u003e\n      \u003cspan className='text'\u003e{text}\u003c/span\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default JobInfo;\n```\n\n```js\nJob.js;\nreturn (\n  \u003cdiv className='content'\u003e\n    \u003cdiv className='content-center'\u003e\n      \u003cJobInfo icon={\u003cFaLocationArrow /\u003e} text={jobLocation} /\u003e\n      \u003cJobInfo icon={\u003cFaCalendarAlt /\u003e} text={date} /\u003e\n      \u003cJobInfo icon={\u003cFaBriefcase /\u003e} text={jobType} /\u003e\n      \u003cdiv className={`status ${status}`}\u003e{status}\u003c/div\u003e\n    \u003c/div\u003e\n    {/* footer content */}\n  \u003c/div\u003e\n);\n```\n\n#### SetEditJob\n\n```js\nactions.js;\nexport const SET_EDIT_JOB = 'SET_EDIT_JOB';\n```\n\n```js\nappContext.js\n\nconst setEditJob = (id) =\u003e {\n  dispatch({ type: SET_EDIT_JOB, payload: { id } })\n}\nconst editJob = () =\u003e {\n  console.log('edit job')\n}\nvalue={{editJob}}\n```\n\n```js\nreducer.js;\n\nif (action.type === SET_EDIT_JOB) {\n  const job = state.jobs.find((job) =\u003e job._id === action.payload.id);\n  const { _id, position, company, jobLocation, jobType, status } = job;\n  return {\n    ...state,\n    isEditing: true,\n    editJobId: _id,\n    position,\n    company,\n    jobLocation,\n    jobType,\n    status,\n  };\n}\n```\n\n```js\nAddJob.js;\nconst { isEditing, editJob } = useAppContext();\nconst handleSubmit = (e) =\u003e {\n  e.preventDefault();\n\n  if (!position || !company || !jobLocation) {\n    displayAlert();\n    return;\n  }\n  if (isEditing) {\n    editJob();\n    return;\n  }\n  createJob();\n};\n```\n\n#### Edit Job - Server\n\n```js\njobsController.js;\n\nconst updateJob = async (req, res) =\u003e {\n  const { id: jobId } = req.params;\n\n  const { company, position } = req.body;\n\n  if (!company || !position) {\n    throw new BadRequestError('Please Provide All Values');\n  }\n\n  const job = await Job.findOne({ _id: jobId });\n\n  if (!job) {\n    throw new NotFoundError(`No job with id ${jobId}`);\n  }\n\n  // check permissions\n\n  const updatedJob = await Job.findOneAndUpdate({ _id: jobId }, req.body, {\n    new: true,\n    runValidators: true,\n  });\n\n  res.status(StatusCodes.OK).json({ updatedJob });\n};\n```\n\n#### Alternative Approach\n\n- optional\n- multiple approaches\n- different setups\n- course Q\u0026A\n\n```js\njobsController.js;\nconst updateJob = async (req, res) =\u003e {\n  const { id: jobId } = req.params;\n  const { company, position, jobLocation } = req.body;\n\n  if (!position || !company) {\n    throw new BadRequestError('Please provide all values');\n  }\n  const job = await Job.findOne({ _id: jobId });\n\n  if (!job) {\n    throw new NotFoundError(`No job with id :${jobId}`);\n  }\n\n  // check permissions\n\n  // alternative approach\n\n  job.position = position;\n  job.company = company;\n  job.jobLocation = jobLocation;\n\n  await job.save();\n  res.status(StatusCodes.OK).json({ job });\n};\n```\n\n#### Check Permissions\n\n```js\njobsController.js;\n\nconst updateJob = async (req, res) =\u003e {\n  const { id: jobId } = req.params;\n  const { company, position, status } = req.body;\n\n  if (!position || !company) {\n    throw new BadRequestError('Please provide all values');\n  }\n  const job = await Job.findOne({ _id: jobId });\n\n  if (!job) {\n    throw new NotFoundError(`No job with id :${jobId}`);\n  }\n\n  // check permissions\n  // req.user.userId (string) === job.createdBy(object)\n  // throw new UnAuthenticatedError('Not authorized to access this route')\n\n  // console.log(typeof req.user.userId)\n  // console.log(typeof job.createdBy)\n\n  checkPermissions(req.user, job.createdBy);\n\n  const updatedJob = await Job.findOneAndUpdate({ _id: jobId }, req.body, {\n    new: true,\n    runValidators: true,\n  });\n\n  res.status(StatusCodes.OK).json({ updatedJob });\n};\n```\n\n- utils folder\n- checkPermissions.js\n- import in jobsController.js\n\n```js\ncheckPermissions.js;\n\nimport { UnAuthenticatedError } from '../errors/index.js';\n\nconst checkPermissions = (requestUser, resourceUserId) =\u003e {\n  // if (requestUser.role === 'admin') return\n  if (requestUser.userId === resourceUserId.toString()) return;\n  throw new UnauthorizedError('Not authorized to access this route');\n};\n\nexport default checkPermissions;\n```\n\n#### Remove/Delete Job\n\n```js\njobsController.js;\n\nconst deleteJob = async (req, res) =\u003e {\n  const { id: jobId } = req.params;\n\n  const job = await Job.findOne({ _id: jobId });\n\n  if (!job) {\n    throw new NotFoundError(`No job with id : ${jobId}`);\n  }\n\n  checkPermissions(req.user, job.createdBy);\n\n  await job.remove();\n  res.status(StatusCodes.OK).json({ msg: 'Success! Job removed' });\n};\n```\n\n#### Delete Job - Front-End\n\n```js\nactions.js;\n\nexport const DELETE_JOB_BEGIN = 'DELETE_JOB_BEGIN';\n```\n\n```js\nappContext.js;\n\nconst deleteJob = async (jobId) =\u003e {\n  dispatch({ type: DELETE_JOB_BEGIN });\n  try {\n    await authFetch.delete(`/jobs/${jobId}`);\n    getJobs();\n  } catch (error) {\n    logoutUser();\n  }\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === DELETE_JOB_BEGIN) {\n  return { ...state, isLoading: true };\n}\n```\n\n#### Edit Job - Front-End\n\n```js\nactions.js;\nexport const EDIT_JOB_BEGIN = 'EDIT_JOB_BEGIN';\nexport const EDIT_JOB_SUCCESS = 'EDIT_JOB_SUCCESS';\nexport const EDIT_JOB_ERROR = 'EDIT_JOB_ERROR';\n```\n\n```js\nappContext.js;\nconst editJob = async () =\u003e {\n  dispatch({ type: EDIT_JOB_BEGIN });\n  try {\n    const { position, company, jobLocation, jobType, status } = state;\n\n    await authFetch.patch(`/jobs/${state.editJobId}`, {\n      company,\n      position,\n      jobLocation,\n      jobType,\n      status,\n    });\n    dispatch({\n      type: EDIT_JOB_SUCCESS,\n    });\n    dispatch({ type: CLEAR_VALUES });\n  } catch (error) {\n    if (error.response.status === 401) return;\n    dispatch({\n      type: EDIT_JOB_ERROR,\n      payload: { msg: error.response.data.msg },\n    });\n  }\n  clearAlert();\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === EDIT_JOB_BEGIN) {\n  return { ...state, isLoading: true };\n}\nif (action.type === EDIT_JOB_SUCCESS) {\n  return {\n    ...state,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'success',\n    alertText: 'Job Updated!',\n  };\n}\nif (action.type === EDIT_JOB_ERROR) {\n  return {\n    ...state,\n    isLoading: false,\n    showAlert: true,\n    alertType: 'danger',\n    alertText: action.payload.msg,\n  };\n}\n```\n\n#### Create More Jobs\n\n- [Mockaroo](https://www.mockaroo.com/)\n- setup mock-data.json in the root\n\n#### Populate Database\n\n- create populate.js in the root\n\n```js\npopulate.js;\n\nimport { readFile } from 'fs/promises';\n\nimport dotenv from 'dotenv';\ndotenv.config();\n\nimport connectDB from './db/connect.js';\nimport Job from './models/Job.js';\n\nconst start = async () =\u003e {\n  try {\n    await connectDB(process.env.MONGO_URL);\n    await Job.deleteMany();\n\n    const jsonProducts = JSON.parse(\n      await readFile(new URL('./mock-data.json', import.meta.url))\n    );\n    await Job.create(jsonProducts);\n    console.log('Success!!!!');\n    process.exit(0);\n  } catch (error) {\n    console.log(error);\n    process.exit(1);\n  }\n};\n\nstart();\n```\n\n#### Show Stats - Structure\n\n- aggregation pipeline\n- step by step\n- [Aggregation Pipeline](https://docs.mongodb.com/manual/core/aggregation-pipeline/)\n\n```js\njobsController.js;\n\nimport mongoose from 'mongoose';\n\nconst showStats = async (req, res) =\u003e {\n  let stats = await Job.aggregate([\n    { $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n    { $group: { _id: '$status', count: { $sum: 1 } } },\n  ]);\n\n  res.status(StatusCodes.OK).json({ stats });\n};\n```\n\n#### Show Stats - Object Setup\n\n- [Reduce Basics](https://youtu.be/3WkW9nrS2mw)\n- [Reduce Object Example ](https://youtu.be/5BFkp8JjLEY)\n\n```js\njobsController.js;\n\nconst showStats = async (req, res) =\u003e {\n  let stats = await Job.aggregate([\n    { $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n    { $group: { _id: '$status', count: { $sum: 1 } } },\n  ]);\n\n  stats = stats.reduce((acc, curr) =\u003e {\n    const { _id: title, count } = curr;\n    acc[title] = count;\n    return acc;\n  }, {});\n\n  res.status(StatusCodes.OK).json({ stats });\n};\n```\n\n#### Show Stats - Default Stats\n\n```js\njobsController.js;\n\nconst showStats = async (req, res) =\u003e {\n  let stats = await Job.aggregate([\n    { $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n    { $group: { _id: '$status', count: { $sum: 1 } } },\n  ]);\n  stats = stats.reduce((acc, curr) =\u003e {\n    const { _id: title, count } = curr;\n    acc[title] = count;\n    return acc;\n  }, {});\n\n  const defaultStats = {\n    pending: stats.pending || 0,\n    interview: stats.interview || 0,\n    declined: stats.declined || 0,\n  };\n  let monthlyApplications = [];\n  res.status(StatusCodes.OK).json({ defaultStats, monthlyApplications });\n};\n```\n\n#### Show Stats - Function Setup\n\n```js\nactions.js;\n\nexport const SHOW_STATS_BEGIN = 'SHOW_STATS_BEGIN';\nexport const SHOW_STATS_SUCCESS = 'SHOW_STATS_SUCCESS';\n```\n\n```js\nappContext.js\n\nconst initialState = {\n  stats: {},\n  monthlyApplications: []\n\n}\n\nconst showStats = async () =\u003e {\n    dispatch({ type: SHOW_STATS_BEGIN })\n    try {\n      const { data } = await authFetch('/jobs/stats')\n      dispatch({\n        type: SHOW_STATS_SUCCESS,\n        payload: {\n          stats: data.defaultStats,\n          monthlyApplications: data.monthlyApplications,\n        },\n      })\n    } catch (error) {\nconsole.log(error.response)\n      // logoutUser()\n    }\n\nclearAlert()\n  }\n  value={{showStats}}\n```\n\n```js\nreducers.js;\nif (action.type === SHOW_STATS_BEGIN) {\n  return { ...state, isLoading: true, showAlert: false };\n}\nif (action.type === SHOW_STATS_SUCCESS) {\n  return {\n    ...state,\n    isLoading: false,\n    stats: action.payload.stats,\n    monthlyApplications: action.payload.monthlyApplications,\n  };\n}\n```\n\n#### Stats Page - Structure\n\n- components\n- StatsContainer.js\n- ChartsContainer.js\n- StatsItem.js\n- simple return\n- import/export index.js\n\n```js\nStats.js;\n\nimport { useEffect } from 'react';\nimport { useAppContext } from '../../context/appContext';\nimport { StatsContainer, Loading, ChartsContainer } from '../../components';\n\nconst Stats = () =\u003e {\n  const { showStats, isLoading, monthlyApplications } = useAppContext();\n  useEffect(() =\u003e {\n    showStats();\n  }, []);\n\n  if (isLoading) {\n    return \u003cLoading center /\u003e;\n  }\n\n  return (\n    \u003c\u003e\n      \u003cStatsContainer /\u003e\n      {monthlyApplications.length \u003e 0 \u0026\u0026 \u003cChartsContainer /\u003e}\n    \u003c/\u003e\n  );\n};\n\nexport default Stats;\n```\n\n#### StatsContainer\n\n```js\nStatsContainer.js;\n\nimport { useAppContext } from '../context/appContext';\nimport StatItem from './StatItem';\nimport { FaSuitcaseRolling, FaCalendarCheck, FaBug } from 'react-icons/fa';\nimport Wrapper from '../assets/wrappers/StatsContainer';\nconst StatsContainer = () =\u003e {\n  const { stats } = useAppContext();\n  const defaultStats = [\n    {\n      title: 'pending applications',\n      count: stats.pending || 0,\n      icon: \u003cFaSuitcaseRolling /\u003e,\n      color: '#e9b949',\n      bcg: '#fcefc7',\n    },\n    {\n      title: 'interviews scheduled',\n      count: stats.interview || 0,\n      icon: \u003cFaCalendarCheck /\u003e,\n      color: '#647acb',\n      bcg: '#e0e8f9',\n    },\n    {\n      title: 'jobs declined',\n      count: stats.declined || 0,\n      icon: \u003cFaBug /\u003e,\n      color: '#d66a6a',\n      bcg: '#ffeeee',\n    },\n  ];\n\n  return (\n    \u003cWrapper\u003e\n      {defaultStats.map((item, index) =\u003e {\n        return \u003cStatItem key={index} {...item} /\u003e;\n      })}\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default StatsContainer;\n```\n\n#### StatItem\n\n```js\nStatItem.js;\n\nimport Wrapper from '../assets/wrappers/StatItem';\n\nfunction StatItem({ count, title, icon, color, bcg }) {\n  return (\n    \u003cWrapper color={color} bcg={bcg}\u003e\n      \u003cheader\u003e\n        \u003cspan className='count'\u003e{count}\u003c/span\u003e\n        \u003cdiv className='icon'\u003e{icon}\u003c/div\u003e\n      \u003c/header\u003e\n      \u003ch5 className='title'\u003e{title}\u003c/h5\u003e\n    \u003c/Wrapper\u003e\n  );\n}\n\nexport default StatItem;\n```\n\n#### Aggregate Jobs Based on Year and Month\n\n```js\njobsController.js;\n\nlet monthlyApplications = await Job.aggregate([\n  { $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n  {\n    $group: {\n      _id: {\n        year: {\n          $year: '$createdAt',\n        },\n        month: {\n          $month: '$createdAt',\n        },\n      },\n      count: { $sum: 1 },\n    },\n  },\n  { $sort: { '_id.year': -1, '_id.month': -1 } },\n  { $limit: 6 },\n]);\n```\n\n#### Refactor Data\n\n- install moment.js on the SERVER\n\n```sh\nnpm install moment\n\n```\n\n```js\njobsController.js;\n\nimport moment from 'moment';\n\nmonthlyApplications = monthlyApplications\n  .map((item) =\u003e {\n    const {\n      _id: { year, month },\n      count,\n    } = item;\n    // accepts 0-11\n    const date = moment()\n      .month(month - 1)\n      .year(year)\n      .format('MMM Y');\n    return { date, count };\n  })\n  .reverse();\n```\n\n#### Charts Container\n\n- BarChart.js\n- AreaChart.js\n\n```js\nChartsContainer.js;\nimport React, { useState } from 'react';\n\nimport BarChart from './BarChart';\nimport AreaChart from './AreaChart';\nimport { useAppContext } from '../context/appContext';\nimport Wrapper from '../assets/wrappers/ChartsContainer';\n\nexport default function ChartsContainer() {\n  const [barChart, setBarChart] = useState(true);\n  const { monthlyApplications: data } = useAppContext();\n\n  return (\n    \u003cWrapper\u003e\n      \u003ch4\u003eMonthly Applications\u003c/h4\u003e\n\n      \u003cbutton type='button' onClick={() =\u003e setBarChart(!barChart)}\u003e\n        {barChart ? 'AreaChart' : 'BarChart'}\n      \u003c/button\u003e\n      {barChart ? \u003cBarChart data={data} /\u003e : \u003cAreaChart data={data} /\u003e}\n    \u003c/Wrapper\u003e\n  );\n}\n```\n\n#### Recharts Library\n\n- install in the Client!!!\n\n[Recharts](https://recharts.org)\n\n```sh\nnpm install recharts\n```\n\n#### Bar Chart\n\n```js\nBarChart.js;\n\nimport {\n  BarChart,\n  Bar,\n  XAxis,\n  YAxis,\n  CartesianGrid,\n  Tooltip,\n  ResponsiveContainer,\n} from 'recharts';\n\nconst BarChartComponent = ({ data }) =\u003e {\n  return (\n    \u003cResponsiveContainer width='100%' height={300}\u003e\n      \u003cBarChart\n        data={data}\n        margin={{\n          top: 50,\n        }}\n      \u003e\n        \u003cCartesianGrid strokeDasharray='3 3' /\u003e\n        \u003cXAxis dataKey='date' /\u003e\n        \u003cYAxis allowDecimals={false} /\u003e\n        \u003cTooltip /\u003e\n        \u003cBar dataKey='count' fill='#2cb1bc' barSize={75} /\u003e\n      \u003c/BarChart\u003e\n    \u003c/ResponsiveContainer\u003e\n  );\n};\n```\n\n#### Area Chart\n\n```js\nimport {\n  ResponsiveContainer,\n  AreaChart,\n  Area,\n  XAxis,\n  YAxis,\n  CartesianGrid,\n  Tooltip,\n} from 'recharts';\n\nconst AreaChartComponent = ({ data }) =\u003e {\n  return (\n    \u003cResponsiveContainer width='100%' height={300}\u003e\n      \u003cAreaChart\n        data={data}\n        margin={{\n          top: 50,\n        }}\n      \u003e\n        \u003cCartesianGrid strokeDasharray='3 3' /\u003e\n        \u003cXAxis dataKey='date' /\u003e\n        \u003cYAxis allowDecimals={false} /\u003e\n        \u003cTooltip /\u003e\n        \u003cArea type='monotone' dataKey='count' stroke='#2cb1bc' fill='#bef8fd' /\u003e\n      \u003c/AreaChart\u003e\n    \u003c/ResponsiveContainer\u003e\n  );\n};\n```\n\n#### Filter\n\n#### Get All Jobs - Initial Setup\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n  const { search, status, jobType, sort } = req.query;\n\n  const queryObject = {\n    createdBy: req.user.userId,\n  };\n\n  // NO AWAIT\n  let result = Job.find(queryObject);\n\n  // chain sort conditions\n\n  const jobs = await result;\n\n  res\n    .status(StatusCodes.OK)\n    .json({ jobs, totalJobs: jobs.length, numOfPages: 1 });\n};\n```\n\n#### Status\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n  const { search, status, jobType, sort } = req.query;\n\n  const queryObject = {\n    createdBy: req.user.userId,\n  };\n\n  if (status !== 'all') {\n    queryObject.status = status;\n  }\n\n  // NO AWAIT\n  let result = Job.find(queryObject);\n\n  // chain sort conditions\n\n  const jobs = await result;\n\n  res\n    .status(StatusCodes.OK)\n    .json({ jobs, totalJobs: jobs.length, numOfPages: 1 });\n};\n```\n\n#### JobType\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n  const { search, status, jobType, sort } = req.query;\n\n  const queryObject = {\n    createdBy: req.user.userId,\n  };\n\n  if (status !== 'all') {\n    queryObject.status = status;\n  }\n  if (jobType !== 'all') {\n    queryObject.jobType = jobType;\n  }\n  // NO AWAIT\n  let result = Job.find(queryObject);\n\n  // chain sort conditions\n\n  const jobs = await result;\n\n  res\n    .status(StatusCodes.OK)\n    .json({ jobs, totalJobs: jobs.length, numOfPages: 1 });\n};\n```\n\n#### Search\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n  const { search, status, jobType, sort } = req.query;\n\n  const queryObject = {\n    createdBy: req.user.userId,\n  };\n\n  if (status !== 'all') {\n    queryObject.status = status;\n  }\n  if (jobType !== 'all') {\n    queryObject.jobType = jobType;\n  }\n  if (search) {\n    queryObject.position = { $regex: search, $options: 'i' };\n  }\n  // NO AWAIT\n  let result = Job.find(queryObject);\n\n  // chain sort conditions\n  if (sort === 'latest') {\n    result = result.sort('-createdAt');\n  }\n  if (sort === 'oldest') {\n    result = result.sort('createdAt');\n  }\n  if (sort === 'a-z') {\n    result = result.sort('position');\n  }\n  if (sort === 'z-a') {\n    result = result.sort('-position');\n  }\n  const jobs = await result;\n\n  res\n    .status(StatusCodes.OK)\n    .json({ jobs, totalJobs: jobs.length, numOfPages: 1 });\n};\n```\n\n#### Search Context Setup\n\n```js\nappContext.js\n\nconst initialState = {\n  jobType: 'full-time',\n  jobTypeOptions: ['full-time', 'part-time', 'remote', 'internship'],\n  status: 'pending',\n  statusOptions: ['pending', 'interview', 'declined']\n  //\n  //\n  //\n  search: '',\n  searchStatus: 'all',\n  searchType: 'all',\n  sort: 'latest',\n  sortOptions: ['latest', 'oldest', 'a-z', 'z-a'],\n}\n\nconst clearFilters = () =\u003e{\nconsole.log('clear filters')\n}\n\nvalue={{clearFilters}}\n\n// remember this function :)\nconst handleChange = ({ name, value }) =\u003e {\n    dispatch({\n      type: HANDLE_CHANGE,\n      payload: { name, value },\n    })\n  }\n\n```\n\n#### Search Container - Setup\n\n```js\nSearchContainer.js;\n\nimport { FormRow, FormRowSelect } from '.';\nimport { useAppContext } from '../context/appContext';\nimport Wrapper from '../assets/wrappers/SearchContainer';\nconst SearchContainer = () =\u003e {\n  const {\n    isLoading,\n    search,\n    searchStatus,\n    searchType,\n    sort,\n    sortOptions,\n    statusOptions,\n    jobTypeOptions,\n    handleChange,\n    clearFilters,\n  } = useAppContext();\n\n  const handleSearch = (e) =\u003e {\n    if (isLoading) return;\n    handleChange({ name: e.target.name, value: e.target.value });\n  };\n\n  return (\n    \u003cWrapper\u003e\n      \u003cform className='form'\u003e\n        \u003ch4\u003esearch form\u003c/h4\u003e\n        {/* search position */}\n        \u003cdiv className='form-center'\u003e\n          \u003cFormRow\n            type='text'\n            name='search'\n            value={search}\n            handleChange={handleSearch}\n          \u003e\u003c/FormRow\u003e\n          {/* rest of the inputs */}\n        \u003c/div\u003e\n      \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default SearchContainer;\n```\n\n#### Search Container - Complete\n\n```js\nSearchContainer.js;\n\nimport { FormRow, FormRowSelect } from '.';\nimport { useAppContext } from '../context/appContext';\nimport Wrapper from '../assets/wrappers/SearchContainer';\n\nconst SearchContainer = () =\u003e {\n  const {\n    isLoading,\n    search,\n    handleChange,\n    searchStatus,\n    statusOptions,\n    jobTypeOptions,\n    searchType,\n    clearFilters,\n    sort,\n    sortOptions,\n  } = useAppContext();\n\n  const handleSearch = (e) =\u003e {\n    if (isLoading) return;\n    handleChange({ name: e.target.name, value: e.target.value });\n  };\n  const handleSubmit = (e) =\u003e {\n    e.preventDefault();\n    clearFilters();\n  };\n  return (\n    \u003cWrapper\u003e\n      \u003cform className='form'\u003e\n        \u003ch4\u003esearch form\u003c/h4\u003e\n        {/* search position */}\n        \u003cdiv className='form-center'\u003e\n          \u003cFormRow\n            type='text'\n            name='search'\n            value={search}\n            handleChange={handleSearch}\n          \u003e\u003c/FormRow\u003e\n          {/* search by status */}\n          \u003cFormRowSelect\n            labelText='job status'\n            name='searchStatus'\n            value={searchStatus}\n            handleChange={handleSearch}\n            list={['all', ...statusOptions]}\n          \u003e\u003c/FormRowSelect\u003e\n          {/* search by type */}\n\n          \u003cFormRowSelect\n            labelText='job type'\n            name='searchType'\n            value={searchType}\n            handleChange={handleSearch}\n            list={['all', ...jobTypeOptions]}\n          \u003e\u003c/FormRowSelect\u003e\n          {/* sort */}\n\n          \u003cFormRowSelect\n            name='sort'\n            value={sort}\n            handleChange={handleSearch}\n            list={sortOptions}\n          \u003e\u003c/FormRowSelect\u003e\n          \u003cbutton\n            className='btn btn-block btn-danger'\n            disabled={isLoading}\n            onClick={handleSubmit}\n          \u003e\n            clear filters\n          \u003c/button\u003e\n        \u003c/div\u003e\n      \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default SearchContainer;\n```\n\n#### Clear Filters\n\n```js\nactions.js;\n\nexport const CLEAR_FILTERS = 'CLEAR_FILTERS';\n```\n\n```js\nappContext.js;\n\nconst clearFilters = () =\u003e {\n  dispatch({ type: CLEAR_FILTERS });\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === CLEAR_FILTERS) {\n  return {\n    ...state,\n    search: '',\n    searchStatus: 'all',\n    searchType: 'all',\n    sort: 'latest',\n  };\n}\n```\n\n#### Refactor Get All Jobs\n\n```js\nconst getJobs = async () =\u003e {\n  // will add page later\n  const { search, searchStatus, searchType, sort } = state;\n  let url = `/jobs?status=${searchStatus}\u0026jobType=${searchType}\u0026sort=${sort}`;\n  if (search) {\n    url = url + `\u0026search=${search}`;\n  }\n  dispatch({ type: GET_JOBS_BEGIN });\n  try {\n    const { data } = await authFetch(url);\n    const { jobs, totalJobs, numOfPages } = data;\n    dispatch({\n      type: GET_JOBS_SUCCESS,\n      payload: {\n        jobs,\n        totalJobs,\n        numOfPages,\n      },\n    });\n  } catch (error) {\n    // logoutUser()\n  }\n  clearAlert();\n};\n```\n\n```js\nJobsContainer.js\n\nconst JobsContainer = () =\u003e {\n  const {\n    getJobs,\n    jobs,\n    isLoading,\n    page,\n    totalJobs,\n    search,\n    searchStatus,\n    searchType,\n    sort,\n\n  } = useAppContext()\n  useEffect(() =\u003e {\n    getJobs()\n  }, [ search, searchStatus, searchType, sort])\n\n```\n\n#### Limit and Skip\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n  const { search, status, jobType, sort } = req.query;\n  const queryObject = {\n    createdBy: req.user.userId,\n  };\n  if (search) {\n    queryObject.position = { $regex: search, $options: 'i' };\n  }\n  if (status !== 'all') {\n    queryObject.status = status;\n  }\n  if (jobType !== 'all') {\n    queryObject.jobType = jobType;\n  }\n  let result = Job.find(queryObject);\n\n  if (sort === 'latest') {\n    result = result.sort('-createdAt');\n  }\n  if (sort === 'oldest') {\n    result = result.sort('createdAt');\n  }\n  if (sort === 'a-z') {\n    result = result.sort('position');\n  }\n  if (sort === 'z-a') {\n    result = result.sort('-position');\n  }\n\n  const totalJobs = await result;\n\n  // setup pagination\n  const limit = 10;\n  const skip = 1;\n\n  result = result.skip(skip).limit(limit);\n  // 23\n  // 4 7 7 7 2\n  const jobs = await result;\n  res\n    .status(StatusCodes.OK)\n    .json({ jobs, totalJobs: jobs.length, numOfPages: 1 });\n};\n```\n\n#### Page and Limit\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n  const { search, status, jobType, sort } = req.query;\n  const queryObject = {\n    createdBy: req.user.userId,\n  };\n  if (search) {\n    queryObject.position = { $regex: search, $options: 'i' };\n  }\n  if (status !== 'all') {\n    queryObject.status = status;\n  }\n  if (jobType !== 'all') {\n    queryObject.jobType = jobType;\n  }\n  let result = Job.find(queryObject);\n\n  if (sort === 'latest') {\n    result = result.sort('-createdAt');\n  }\n  if (sort === 'oldest') {\n    result = result.sort('createdAt');\n  }\n  if (sort === 'a-z') {\n    result = result.sort('position');\n  }\n  if (sort === 'z-a') {\n    result = result.sort('-position');\n  }\n\n  // setup pagination\n  const page = Number(req.query.page) || 1;\n  const limit = Number(req.query.limit) || 10;\n  const skip = (page - 1) * limit; //10\n  result = result.skip(skip).limit(limit);\n  // 75\n  // 10 10 10 10 10 10 10 5\n  const jobs = await result;\n  res\n    .status(StatusCodes.OK)\n    .json({ jobs, totalJobs: jobs.length, numOfPages: 1 });\n};\n```\n\n#### Total Jobs and Number Of Pages\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n  const { search, status, jobType, sort } = req.query;\n  const queryObject = {\n    createdBy: req.user.userId,\n  };\n  if (search) {\n    queryObject.position = { $regex: search, $options: 'i' };\n  }\n  if (status !== 'all') {\n    queryObject.status = status;\n  }\n  if (jobType !== 'all') {\n    queryObject.jobType = jobType;\n  }\n  let result = Job.find(queryObject);\n\n  if (sort === 'latest') {\n    result = result.sort('-createdAt');\n  }\n  if (sort === 'oldest') {\n    result = result.sort('createdAt');\n  }\n  if (sort === 'a-z') {\n    result = result.sort('position');\n  }\n  if (sort === 'z-a') {\n    result = result.sort('-position');\n  }\n\n  // setup pagination\n  const page = Number(req.query.page) || 1;\n  const limit = Number(req.query.limit) || 10;\n  const skip = (page - 1) * limit;\n\n  result = result.skip(skip).limit(limit);\n\n  const jobs = await result;\n\n  const totalJobs = await Job.countDocuments(queryObject);\n  const numOfPages = Math.ceil(totalJobs / limit);\n\n  res.status(StatusCodes.OK).json({ jobs, totalJobs, numOfPages });\n};\n```\n\n#### PageBtnContainer Setup\n\n- PageBtnContainer.js\n\n```js\nJobsContainer.js;\n\nimport PageBtnContainer from './PageBtnContainer';\n\nconst { numOfPages } = useAppContext();\n\nreturn (\n  \u003cWrapper\u003e\n    \u003ch5\u003e\n      {totalJobs} job{jobs.length \u003e 1 \u0026\u0026 's'} found\n    \u003c/h5\u003e\n    \u003cdiv className='jobs'\u003e\n      {jobs.map((job) =\u003e {\n        return \u003cJob key={job._id} {...job} /\u003e;\n      })}\n    \u003c/div\u003e\n    {numOfPages \u003e 1 \u0026\u0026 \u003cPageBtnContainer /\u003e}\n  \u003c/Wrapper\u003e\n);\n```\n\n#### PageBtnContainer - Structure\n\n```js\nPageBtnContainer.js;\n\nimport { useAppContext } from '../context/appContext';\nimport { HiChevronDoubleLeft, HiChevronDoubleRight } from 'react-icons/hi';\nimport Wrapper from '../assets/wrappers/PageBtnContainer';\n\nconst PageButtonContainer = () =\u003e {\n  const { numOfPages, page } = useAppContext();\n\n  const prevPage = () =\u003e {\n    console.log('prev page');\n  };\n  const nextPage = () =\u003e {\n    console.log('next page');\n  };\n\n  return (\n    \u003cWrapper\u003e\n      \u003cbutton className='prev-btn' onClick={prevPage}\u003e\n        \u003cHiChevronDoubleLeft /\u003e\n        prev\n      \u003c/button\u003e\n\n      \u003cdiv className='btn-container'\u003ebuttons\u003c/div\u003e\n\n      \u003cbutton className='next-btn' onClick={nextPage}\u003e\n        next\n        \u003cHiChevronDoubleRight /\u003e\n      \u003c/button\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default PageButtonContainer;\n```\n\n#### Button Container\n\n- [Array.from] (https://youtu.be/zg1Bv4xubwo)\n\n```js\nPageBtnContainer.js;\n\nconst pages = Array.from({ length: numOfPages }, (_, index) =\u003e {\n  return index + 1;\n});\n\nreturn (\n  \u003cdiv className='btn-container'\u003e\n    {pages.map((pageNumber) =\u003e {\n      return (\n        \u003cbutton\n          type='button'\n          className={pageNumber === page ? 'pageBtn active' : 'pageBtn'}\n          key={pageNumber}\n          onClick={() =\u003e console.log(page)}\n        \u003e\n          {pageNumber}\n        \u003c/button\u003e\n      );\n    })}\n  \u003c/div\u003e\n);\n```\n\n#### Change Page\n\n```js\nactions.js;\nexport const CHANGE_PAGE = 'CHANGE_PAGE';\n```\n\n```js\nappContext.js\nconst changePage = (page) =\u003e {\n  dispatch({ type: CHANGE_PAGE, payload: { page } })\n}\nvalue={{changePage}}\n```\n\n```js\nreducer.js;\n\nif (action.type === CHANGE_PAGE) {\n  return { ...state, page: action.payload.page };\n}\n```\n\n```js\nPageBtnContainer.js;\n\nconst { changePage } = useAppContext();\nreturn (\n  \u003cbutton\n    type='button'\n    className={pageNumber === page ? 'pageBtn active' : 'pageBtn'}\n    key={pageNumber}\n    onClick={() =\u003e changePage(pageNumber)}\n  \u003e\n    {pageNumber}\n  \u003c/button\u003e\n);\n```\n\n#### Prev and Next Buttons\n\n```js\nPageBtnContainer.js;\nconst prevPage = () =\u003e {\n  let newPage = page - 1;\n  if (newPage \u003c 1) {\n    // newPage = 1\n    // alternative\n    newPage = numOfPages;\n  }\n  changePage(newPage);\n};\nconst nextPage = () =\u003e {\n  let newPage = page + 1;\n  if (newPage \u003e numOfPages) {\n    // newPage = numOfPages\n    // alternative\n    newPage = 1;\n  }\n  changePage(newPage);\n};\n```\n\n#### Trigger New Page\n\n```js\nappContext.js;\n\nconst getJobs = async () =\u003e {\n  const { page, search, searchStatus, searchType, sort } = state;\n\n  let url = `/jobs?page=${page}\u0026status=${searchStatus}\u0026jobType=${searchType}\u0026sort=${sort}`;\n  // rest of the code\n};\n```\n\n```js\nJobsContainer.js;\n\nconst { page } = useAppContext();\nuseEffect(() =\u003e {\n  getJobs();\n}, [page, search, searchStatus, searchType, sort]);\n```\n\n```js\nreducer.js;\n\nif (action.type === HANDLE_CHANGE) {\n  // set back to first page\n\n  return { ...state, page: 1, [action.payload.name]: action.payload.value };\n}\n```\n\n#### Production Setup - Fix Warnings and logoutUser\n\n- getJobs,deleteJob,showStats - invoke logoutUser()\n- fix warnings\n\n```sh\n// eslint-disable-next-line\n```\n\n#### Production Setup - Build Front-End Application\n\n- create front-end production application\n\n```js\npackage.json\n\"scripts\": {\n    \"build-client\": \"cd client \u0026\u0026 npm run build\",\n    \"server\": \"nodemon server.js --ignore client\",\n    \"client\": \"cd client \u0026\u0026 npm run start\",\n    \"start\": \"concurrently --kill-others-on-fail \\\"npm run server\\\" \\\"npm run client\\\"\"\n\n  },\n\n```\n\n```js\nserver.js;\n\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport path from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// only when ready to deploy\napp.use(express.static(path.resolve(__dirname, './client/build')));\n\n// routes\napp.use('/api/v1/auth', authRouter);\napp.use('/api/v1/jobs', authenticateUser, jobsRouter);\n\n// only when ready to deploy\napp.get('*', function (request, response) {\n  response.sendFile(path.resolve(__dirname, './client/build', 'index.html'));\n});\n```\n\n#### Security Packages\n\n- remove log in the error-handler\n- [helmet](https://www.npmjs.com/package/helmet)\n  Helmet helps you secure your Express apps by setting various HTTP headers.\n- [xss-clean](https://www.npmjs.com/package/xss-clean)\n  Node.js Connect middleware to sanitize user input coming from POST body, GET queries, and url params.\n- [express-mongo-sanitize](https://www.npmjs.com/package/express-mongo-sanitize)\n  Sanitizes user-supplied data to prevent MongoDB Operator Injection.\n- [express-rate-limit](https://www.npmjs.com/package/express-rate-limit)\n  Basic rate-limiting middleware for Express.\n\n```sh\nnpm install helmet xss-clean express-mongo-sanitize express-rate-limit\n```\n\n```js\nserver.js;\n\nimport helmet from 'helmet';\nimport xss from 'xss-clean';\nimport mongoSanitize from 'express-mongo-sanitize';\n\napp.use(express.json());\napp.use(helmet());\napp.use(xss());\napp.use(mongoSanitize());\n```\n\n#### Limit Requests\n\n```js\nauthRoutes.js;\n\nimport rateLimiter from 'express-rate-limit';\n\nconst apiLimiter = rateLimiter({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 10,\n  message: 'Too many requests from this IP, please try again after 15 minutes',\n});\n\nrouter.route('/register').post(apiLimiter, register);\nrouter.route('/login').post(apiLimiter, login);\n```\n\n#### Alternative Search with Debounce\n\nclient/components/SearchContainer.js\n\n```js\n\nimport { useState, useMemo } from 'react';\nconst SearchContainer = () =\u003e {\n  const [localSearch, setLocalSearch] = useState('');\n  const {\n    ....\n  } = useAppContext();\n  const handleSearch = (e) =\u003e {\n    handleChange({ name: e.target.name, value: e.target.value });\n  };\n  const handleSubmit = (e) =\u003e {\n    e.preventDefault();\n    clearFilters();\n  };\n  const debounce = () =\u003e {\n    let timeoutID;\n    return (e) =\u003e {\n      setLocalSearch(e.target.value);\n      clearTimeout(timeoutID);\n      timeoutID = setTimeout(() =\u003e {\n        handleChange({ name: e.target.name, value: e.target.value });\n      }, 1000);\n    };\n  };\n\n  const handleSubmit = (e) =\u003e {\n    e.preventDefault();\n    setLocalSearch('');\n    clearFilters();\n  };\n\n  const optimizedDebounce = useMemo(() =\u003e debounce(), []);\n  return (\n    \u003cWrapper\u003e\n      \u003cform className='form'\u003e\n        \u003ch4\u003esearch form\u003c/h4\u003e\n        \u003cdiv className='form-center'\u003e\n          {/* search position */}\n\n          \u003cFormRow\n            type='text'\n            name='search'\n            value={localSearch}\n            handleChange={optimizedDebounce}\n          /\u003e\n         ........\n        \u003c/div\u003e\n      \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default SearchContainer;\n```\n\n#### Test User - Initial Setup\n\n- create new user (test user)\n- populate DB with jobs\n- create a login button\n\nclient/pages/Register.js\n\n```js\nconst Register = () =\u003e {\n  return (\n    \u003cWrapper className='full-page'\u003e\n      \u003cform className='form' onSubmit={onSubmit}\u003e\n        \u003cbutton type='submit' className='btn btn-block' disabled={isLoading}\u003e\n          submit\n        \u003c/button\u003e\n        \u003cbutton\n          type='button'\n          className='btn btn-block btn-hipster'\n          disabled={isLoading}\n          onClick={() =\u003e {\n            setupUser({\n              currentUser: { email: 'testUser@test.com', password: 'secret' },\n              endPoint: 'login',\n              alertText: 'Login Successful! Redirecting...',\n            });\n          }}\n        \u003e\n          {isLoading ? 'loading...' : 'demo app'}\n        \u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n};\nexport default Register;\n```\n\n#### Test User - Restrict Access (server)\n\n- check for test user in auth middleware\n- create new property on user object (true/false)\n- create new middleware (testUser)\n- check for test user, if true send back BadRequest Error\n- add testUser middleware in front of routes you want to restrict access to\n\nmiddleware/auth.js\n\n```js\nimport jwt from 'jsonwebtoken';\nimport { UnAuthenticatedError } from '../errors/index.js';\n\nUnAuthenticatedError;\nconst auth = async (req, res, next) =\u003e {\n  const authHeader = req.headers.authorization;\n  if (!authHeader || !authHeader.startsWith('Bearer')) {\n    throw new UnAuthenticatedError('Authentication Invalid');\n  }\n  const token = authHeader.split(' ')[1];\n  try {\n    const payload = jwt.verify(token, process.env.JWT_SECRET);\n    // TEST USER\n    const testUser = payload.userId === 'testUserID';\n    req.user = { userId: payload.userId, testUser };\n    // TEST USER\n    next();\n  } catch (error) {\n    throw new UnAuthenticatedError('Authentication Invalid');\n  }\n};\n\nexport default auth;\n```\n\nmiddleware/testUser\n\n```js\nimport { BadRequestError } from '../errors/index.js';\n\nconst testUser = (req, res, next) =\u003e {\n  if (req.user.testUser) {\n    throw new BadRequestError('Test User. Read Only!');\n  }\n  next();\n};\n\nexport default testUser;\n```\n\nroutes/jobsRoutes\n\n```js\nimport express from 'express';\nconst router = express.Router();\n\nimport {\n  createJob,\n  deleteJob,\n  getAllJobs,\n  updateJob,\n  showStats,\n} from '../controllers/jobsController.js';\n\nimport testUser from '../middleware/testUser.js';\n\nrouter.route('/').post(testUser, createJob).get(getAllJobs);\n// remember about :id\nrouter.route('/stats').get(showStats);\nrouter.route('/:id').delete(testUser, deleteJob).patch(testUser, updateJob);\n\nexport default router;\n```\n\nroutes/authRoutes\n\n```js\nimport express from 'express';\nconst router = express.Router();\n\nimport rateLimiter from 'express-rate-limit';\nconst apiLimiter = rateLimiter({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 10,\n  message: 'Too many requests from this IP, please try again after 15 minutes',\n});\n\nimport { register, login, updateUser } from '../controllers/authController.js';\nimport authenticateUser from '../middleware/auth.js';\nimport testUser from '../middleware/testUser.js';\nrouter.route('/register').post(apiLimiter, register);\nrouter.route('/login').post(apiLimiter, login);\nrouter.route('/updateUser').patch(authenticateUser, testUser, updateUser);\n\nexport default router;\n```\n\n#### Store JWT in Cookie\n\n- BE PREPARED TO REFACTOR CODE !!!\n- PLEASE DON'T RUSH THROUGH THESES VIDEOS\n- CHECK FEW TIMES BEFORE REMOVING/ADDING CODE\n\n#### Attach Cookies to Login response\n\ncontrollers/authController.js\n\n```js\n// login controller\n\nconst token = user.createJWT();\n\nconst oneDay = 1000 * 60 * 60 * 24;\n\nres.cookie('token', token, {\n  httpOnly: true,\n  expires: new Date(Date.now() + oneDay),\n  secure: process.env.NODE_ENV === 'production',\n});\n```\n\n#### Setup Function in Utils\n\n- create attachCookies.js\n\n```js\nconst attachCookie = ({ res, token }) =\u003e {\n  const oneDay = 1000 * 60 * 60 * 24;\n\n  res.cookie('token', token, {\n    httpOnly: true,\n    expires: new Date(Date.now() + oneDay),\n    secure: process.env.NODE_ENV === 'production',\n  });\n};\n\nexport default attachCookie;\n```\n\n- import in authController.js\n- invoke in register/login/updateUser\n\n```js\nimport attachCookie from '../utils/attachCookie.js';\n\nattachCookie({ res, token });\n```\n\n#### Parse Cookie Coming Back from the Front-End\n\n- install cookie-parser (server)\n\n```sh\nnpm install cookie-parser\n```\n\nserver.js\n\n```js\nimport cookieParser from 'cookie-parser';\n\napp.use(express.json());\napp.use(cookieParser());\n```\n\nmiddleware/auth.js\n\n```js\nconst auth = async (req, res, next) =\u003e {\n  console.log(req.cookies)\n  ....\n}\n```\n\n#### Refactor Auth Middleware\n\nmiddleware/auth.js\n\n```js\nconst auth = async (req, res, next) =\u003e {\n  const token = req.cookies.token;\n  if (!token) {\n    throw new UnAuthenticatedError('Authentication Invalid');\n  }\n  // rest of the code\n};\n```\n\n#### SERVER - Remove Token from JSON Response\n\ncontrollers/authController\n\nregister/login/updateUser\n\n```js\nres.status(StatusCodes.OK).json({ user, location: user.location });\n```\n\n- test the APP\n\n#### FRONT-END Remove Token from CONTEXT\n\n- PLEASE BE CAREFUL WHEN MAKING THESE UPDATES\n  client/context/appContext\n\n- remove\n\n```js\nconst token = localStorage.getItem('token');\nconst user = localStorage.getItem('user');\nconst userLocation = localStorage.getItem('location');\n```\n\n- fix initial state\n\n```js\nconst initialState = {\n  // remove token all together\n  user: null,\n  userLocation: '',\n  jobLocation: '',\n};\n```\n\n- remove request interceptor\n\n```js\nauthFetch.interceptors.request.use(\n  (config) =\u003e {\n    config.headers.common['Authorization'] = `Bearer ${state.token}`;\n    return config;\n  },\n  (error) =\u003e {\n    return Promise.reject(error);\n  }\n);\n```\n\n- remove both addToLocalStorage and removeFromLocalStorage functions\n- remove from setupUser and updateUser (token and local storage functions)\n- remove from the reducer token (COMMAND + F)\n\n```js\nconst logoutUser = async () =\u003e {\n  dispatch({ type: LOGOUT_USER });\n  // remove local storage code\n};\n```\n\n#### Test Expiration\n\n```js\nexpires: new Date(Date.now() + 5000),\n```\n\n#### GET Current User Route\n\ncontrollers/authController.js\n\n```js\nconst getCurrentUser = async (req, res) =\u003e {\n  const user = await User.findOne({ _id: req.user.userId });\n  res.status(StatusCodes.OK).json({ user, location: user.location });\n};\n\nexport { register, login, updateUser, getCurrentUser };\n```\n\nroutes/authRoutes.js\n\n```js\nimport {\n  register,\n  login,\n  updateUser,\n  getCurrentUser,\n} from '../controllers/authController.js';\n\nrouter.route('/register').post(apiLimiter, register);\nrouter.route('/login').post(apiLimiter, login);\nrouter.route('/updateUser').patch(authenticateUser, testUser, updateUser);\nrouter.route('/getCurrentUser').get(authenticateUser, getCurrentUser);\n```\n\n#### GET Current User - Front-End\n\nactions.js\n\n```js\nexport const GET_CURRENT_USER_BEGIN = 'GET_CURRENT_USER_BEGIN';\nexport const GET_CURRENT_USER_SUCCESS = 'GET_CURRENT_USER_SUCCESS';\n```\n\n- setup imports (appContext and reducer)\n\n#### GET Current User Request\n\n- first set the state value (default TRUE !!!)\n  appContext.js\n\n```js\nconst initialState = {\n  userLoading: true,\n};\n\nconst getCurrentUser = async () =\u003e {\n  dispatch({ type: GET_CURRENT_USER_BEGIN });\n  try {\n    const { data } = await authFetch('/auth/getCurrentUser');\n    const { user, location } = data;\n\n    dispatch({\n      type: GET_CURRENT_USER_SUCCESS,\n      payload: { user, location },\n    });\n  } catch (error) {\n    if (error.response.status === 401) return;\n    logoutUser();\n  }\n};\nuseEffect(() =\u003e {\n  getCurrentUser();\n}, []);\n```\n\nreducer.js\n\n```js\nif (action.type === GET_CURRENT_USER_BEGIN) {\n  return { ...state, userLoading: true, showAlert: false };\n}\nif (action.type === GET_CURRENT_USER_SUCCESS) {\n  return {\n    ...state,\n    userLoading: false,\n    user: action.payload.user,\n    userLocation: action.payload.location,\n    jobLocation: action.payload.location,\n  };\n}\n```\n\n```js\nif (action.type === LOGOUT_USER) {\n  return {\n    ...initialState,\n    userLoading: false,\n  };\n}\n```\n\n#### Protected Route FIX\n\n```js\nimport Loading from '../components/Loading';\n\nconst ProtectedRoute = ({ children }) =\u003e {\n  const { user, userLoading } = useAppContext();\n\n  if (userLoading) return \u003cLoading /\u003e;\n\n  if (!user) {\n    return \u003cNavigate to='/landing' /\u003e;\n  }\n  return children;\n};\n\nexport default ProtectedRoute;\n```\n\n#### Landing Page\n\n```js\nimport { Navigate } from 'react-router-dom';\nimport { useAppContext } from '../context/appContext';\n\nconst Landing = () =\u003e {\n  const { user } = useAppContext();\n  return (\n    \u003cReact.Fragment\u003e\n      {user \u0026\u0026 \u003cNavigate to='/' /\u003e}\n      \u003cWrapper\u003e// rest of the code..........\u003c/Wrapper\u003e\n    \u003c/React.Fragment\u003e\n  );\n};\n\nexport default Landing;\n```\n\n#### Logout Route\n\ncontrollers/authController\n\n```js\nconst logout = async (req, res) =\u003e {\n  res.cookie('token', 'logout', {\n    httpOnly: true,\n    expires: new Date(Date.now() + 1000),\n  });\n  res.status(StatusCodes.OK).json({ msg: 'user logged","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjannickpepe%2Fmern-waves","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjannickpepe%2Fmern-waves","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjannickpepe%2Fmern-waves/lists"}