{"id":22387159,"url":"https://github.com/codelikeagirl29/my-jobspace","last_synced_at":"2026-04-01T18:32:20.426Z","repository":{"id":128288554,"uuid":"534562875","full_name":"CodeLikeAGirl29/my-jobspace","owner":"CodeLikeAGirl29","description":"Full stack Job Search dashboard using React, JWT, and MongoDB. For applicants to keep up with their job search and where they've applied.","archived":false,"fork":false,"pushed_at":"2023-10-10T20:22:47.000Z","size":389,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-03T14:21:44.805Z","etag":null,"topics":["authentication","css","express","heroku-deployment","javascript-web-tokens","jobify","jobspace","jwt","login","mern","mern-stack","mongodb","nodejs","nodemon","react","react-hooks","styled-components"],"latest_commit_sha":null,"homepage":"https://my-jobspace.herokuapp.com/","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/CodeLikeAGirl29.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":"2022-09-09T08:33:02.000Z","updated_at":"2023-10-05T09:57:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"413c6618-0882-4b18-b677-b378836ff5b3","html_url":"https://github.com/CodeLikeAGirl29/my-jobspace","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/CodeLikeAGirl29/my-jobspace","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeLikeAGirl29%2Fmy-jobspace","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeLikeAGirl29%2Fmy-jobspace/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeLikeAGirl29%2Fmy-jobspace/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeLikeAGirl29%2Fmy-jobspace/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CodeLikeAGirl29","download_url":"https://codeload.github.com/CodeLikeAGirl29/my-jobspace/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeLikeAGirl29%2Fmy-jobspace/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31042319,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-27T09:35:52.079Z","status":"ssl_error","status_checked_at":"2026-03-27T09:35:20.916Z","response_time":164,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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":["authentication","css","express","heroku-deployment","javascript-web-tokens","jobify","jobspace","jwt","login","mern","mern-stack","mongodb","nodejs","nodemon","react","react-hooks","styled-components"],"created_at":"2024-12-05T02:07:54.717Z","updated_at":"2026-04-01T18:32:20.409Z","avatar_url":"https://github.com/CodeLikeAGirl29.png","language":"JavaScript","funding_links":["https://www.buymeacoffee.com/johnsmilga"],"categories":[],"sub_categories":[],"readme":"# Jobspace\n\n#### Track Your Job Search\n\nProject in Action - [Jobspace](https://my-jobspace.herokuapp.com/)\n\n![screenshot](https://res.cloudinary.com/codelikeagirl29/image/upload/v1662720108/projects/Jobscape_nrvzkg.png)\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\t// 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\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003ch1\u003e Component\u003c/h1\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\t\u003cLink to='/'\u003eDashboard\u003c/Link\u003e\n\t\u003cLink to='/register'\u003eRegister\u003c/Link\u003e\n\t\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\t\u003cLink to='/register' className='btn btn-hero'\u003e\n\t\tLogin / Register\n\t\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\t\u003cWrapper className='full-page'\u003e\n\t\t\u003cdiv\u003e\n\t\t\t\u003cimg src={img} alt='not found' /\u003e\n\t\t\t\u003ch3\u003etext\u003c/h3\u003e\n\t\t\t\u003cp\u003etext\u003c/p\u003e\n\t\t\t\u003cLink to='/'\u003eback home\u003c/Link\u003e\n\t\t\u003c/div\u003e\n\t\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\tname: \"\",\n\temail: \"\",\n\tpassword: \"\",\n\tisMember: true,\n};\n// if possible prefer local state\n// global state\n\nfunction Register() {\n\tconst [values, setValues] = useState(initialState);\n\n\t// global context and useNavigate later\n\n\tconst handleChange = (e) =\u003e {\n\t\tconsole.log(e.target);\n\t};\n\n\tconst onSubmit = (e) =\u003e {\n\t\te.preventDefault();\n\t\tconsole.log(e.target);\n\t};\n\treturn (\n\t\t\u003cWrapper className='full-page'\u003e\n\t\t\t\u003cform className='form' onSubmit={onSubmit}\u003e\n\t\t\t\t\u003cLogo /\u003e\n\t\t\t\t\u003ch3\u003eLogin\u003c/h3\u003e\n\n\t\t\t\t{/* name field */}\n\t\t\t\t\u003cdiv className='form-row'\u003e\n\t\t\t\t\t\u003clabel htmlFor='name' className='form-label'\u003e\n\t\t\t\t\t\tname\n\t\t\t\t\t\u003c/label\u003e\n\n\t\t\t\t\t\u003cinput\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tvalue={values.name}\n\t\t\t\t\t\tname='name'\n\t\t\t\t\t\tonChange={handleChange}\n\t\t\t\t\t\tclassName='form-input'\n\t\t\t\t\t/\u003e\n\t\t\t\t\u003c/div\u003e\n\n\t\t\t\t\u003cbutton type='submit' className='btn btn-block'\u003e\n\t\t\t\t\tsubmit\n\t\t\t\t\u003c/button\u003e\n\t\t\t\u003c/form\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\treturn (\n\t\t\u003cdiv className='form-row'\u003e\n\t\t\t\u003clabel htmlFor={name} className='form-label'\u003e\n\t\t\t\t{labelText || name}\n\t\t\t\u003c/label\u003e\n\n\t\t\t\u003cinput\n\t\t\t\ttype={type}\n\t\t\t\tvalue={value}\n\t\t\t\tname={name}\n\t\t\t\tonChange={handleChange}\n\t\t\t\tclassName='form-input'\n\t\t\t/\u003e\n\t\t\u003c/div\u003e\n\t);\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\treturn \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\tsetValues({ ...values, isMember: !values.isMember });\n};\n\nreturn (\n\t\u003cWrapper\u003e\n\t\t{/* control h3 */}\n\n\t\t\u003ch3\u003e{values.isMember ? \"Login\" : \"Register\"}\u003c/h3\u003e\n\n\t\t{/* toggle name */}\n\n\t\t{!values.isMember \u0026\u0026 (\n\t\t\t\u003cFormRow\n\t\t\t\ttype='text'\n\t\t\t\tname='name'\n\t\t\t\tvalue={values.name}\n\t\t\t\thandleChange={handleChange}\n\t\t\t/\u003e\n\t\t)}\n\n\t\t{/* right after submit btn */}\n\t\t{/* toggle button */}\n\n\t\t\u003cp\u003e\n\t\t\t{values.isMember ? \"Not a member yet?\" : \"Already a member?\"}\n\n\t\t\t\u003cbutton type='button' onClick={toggleMember} className='member-btn'\u003e\n\t\t\t\t{values.isMember ? \"Register\" : \"Login\"}\n\t\t\t\u003c/button\u003e\n\t\t\u003c/p\u003e\n\t\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\tisLoading: false,\n\tshowAlert: false,\n\talertText: \"\",\n\talertType: \"\",\n};\nconst AppContext = React.createContext();\nconst AppProvider = ({ children }) =\u003e {\n\tconst [state, setState] = useState(initialState);\n\n\treturn (\n\t\t\u003cAppContext.Provider\n\t\t\tvalue={{\n\t\t\t\t...state,\n\t\t\t}}\n\t\t\u003e\n\t\t\t{children}\n\t\t\u003c/AppContext.Provider\u003e\n\t);\n};\n// make sure use\nexport const useAppContext = () =\u003e {\n\treturn useContext(AppContext);\n};\n\nexport { AppProvider };\n```\n\n- index.js\n\n```js\nimport { AppProvider } from \"./context/appContext\";\n\nReactDOM.render(\n\t\u003cReact.StrictMode\u003e\n\t\t\u003cAppProvider\u003e\n\t\t\t\u003cApp /\u003e\n\t\t\u003c/AppProvider\u003e\n\t\u003c/React.StrictMode\u003e,\n\tdocument.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\tthrow 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\treturn {\n\t\t...state,\n\t\tshowAlert: true,\n\t\talertType: \"danger\",\n\t\talertText: \"Please provide all values!\",\n\t};\n}\n```\n\n```js\nAlert.js in Components;\n\nimport { useAppContext } from \"../context/appContext\";\n\nconst Alert = () =\u003e {\n\tconst { alertType, alertText } = useAppContext();\n\treturn \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\tsetValues({ ...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\te.preventDefault();\n\tconst { name, email, password, isMember } = values;\n\tif (!email || !password || (!isMember \u0026\u0026 !name)) {\n\t\tdisplayAlert();\n\t\treturn;\n\t}\n\tconsole.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\treturn {\n\t\t...state,\n\t\tshowAlert: false,\n\t\talertType: \"\",\n\t\talertText: \"\",\n\t};\n}\n```\n\n```js\nappContext.js;\n\nconst displayAlert = () =\u003e {\n\tdispatch({\n\t\ttype: DISPLAY_ALERT,\n\t});\n\tclearAlert();\n};\n\nconst clearAlert = () =\u003e {\n\tsetTimeout(() =\u003e {\n\t\tdispatch({\n\t\t\ttype: CLEAR_ALERT,\n\t\t});\n\t}, 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\tres.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# 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\t// 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\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003ch1\u003e Component\u003c/h1\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\t\u003cLink to='/'\u003eDashboard\u003c/Link\u003e\n\t\u003cLink to='/register'\u003eRegister\u003c/Link\u003e\n\t\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\t\u003cLink to='/register' className='btn btn-hero'\u003e\n\t\tLogin / Register\n\t\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\t\u003cWrapper className='full-page'\u003e\n\t\t\u003cdiv\u003e\n\t\t\t\u003cimg src={img} alt='not found' /\u003e\n\t\t\t\u003ch3\u003etext\u003c/h3\u003e\n\t\t\t\u003cp\u003etext\u003c/p\u003e\n\t\t\t\u003cLink to='/'\u003eback home\u003c/Link\u003e\n\t\t\u003c/div\u003e\n\t\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\tname: \"\",\n\temail: \"\",\n\tpassword: \"\",\n\tisMember: true,\n};\n// if possible prefer local state\n// global state\n\nfunction Register() {\n\tconst [values, setValues] = useState(initialState);\n\n\t// global context and useNavigate later\n\n\tconst handleChange = (e) =\u003e {\n\t\tconsole.log(e.target);\n\t};\n\n\tconst onSubmit = (e) =\u003e {\n\t\te.preventDefault();\n\t\tconsole.log(e.target);\n\t};\n\treturn (\n\t\t\u003cWrapper className='full-page'\u003e\n\t\t\t\u003cform className='form' onSubmit={onSubmit}\u003e\n\t\t\t\t\u003cLogo /\u003e\n\t\t\t\t\u003ch3\u003eLogin\u003c/h3\u003e\n\n\t\t\t\t{/* name field */}\n\t\t\t\t\u003cdiv className='form-row'\u003e\n\t\t\t\t\t\u003clabel htmlFor='name' className='form-label'\u003e\n\t\t\t\t\t\tname\n\t\t\t\t\t\u003c/label\u003e\n\n\t\t\t\t\t\u003cinput\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tvalue={values.name}\n\t\t\t\t\t\tname='name'\n\t\t\t\t\t\tonChange={handleChange}\n\t\t\t\t\t\tclassName='form-input'\n\t\t\t\t\t/\u003e\n\t\t\t\t\u003c/div\u003e\n\n\t\t\t\t\u003cbutton type='submit' className='btn btn-block'\u003e\n\t\t\t\t\tsubmit\n\t\t\t\t\u003c/button\u003e\n\t\t\t\u003c/form\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\treturn (\n\t\t\u003cdiv className='form-row'\u003e\n\t\t\t\u003clabel htmlFor={name} className='form-label'\u003e\n\t\t\t\t{labelText || name}\n\t\t\t\u003c/label\u003e\n\n\t\t\t\u003cinput\n\t\t\t\ttype={type}\n\t\t\t\tvalue={value}\n\t\t\t\tname={name}\n\t\t\t\tonChange={handleChange}\n\t\t\t\tclassName='form-input'\n\t\t\t/\u003e\n\t\t\u003c/div\u003e\n\t);\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\treturn \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\tsetValues({ ...values, isMember: !values.isMember });\n};\n\nreturn (\n\t\u003cWrapper\u003e\n\t\t{/* control h3 */}\n\n\t\t\u003ch3\u003e{values.isMember ? \"Login\" : \"Register\"}\u003c/h3\u003e\n\n\t\t{/* toggle name */}\n\n\t\t{!values.isMember \u0026\u0026 (\n\t\t\t\u003cFormRow\n\t\t\t\ttype='text'\n\t\t\t\tname='name'\n\t\t\t\tvalue={values.name}\n\t\t\t\thandleChange={handleChange}\n\t\t\t/\u003e\n\t\t)}\n\n\t\t{/* right after submit btn */}\n\t\t{/* toggle button */}\n\n\t\t\u003cp\u003e\n\t\t\t{values.isMember ? \"Not a member yet?\" : \"Already a member?\"}\n\n\t\t\t\u003cbutton type='button' onClick={toggleMember} className='member-btn'\u003e\n\t\t\t\t{values.isMember ? \"Register\" : \"Login\"}\n\t\t\t\u003c/button\u003e\n\t\t\u003c/p\u003e\n\t\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\tisLoading: false,\n\tshowAlert: false,\n\talertText: \"\",\n\talertType: \"\",\n};\nconst AppContext = React.createContext();\nconst AppProvider = ({ children }) =\u003e {\n\tconst [state, setState] = useState(initialState);\n\n\treturn (\n\t\t\u003cAppContext.Provider\n\t\t\tvalue={{\n\t\t\t\t...state,\n\t\t\t}}\n\t\t\u003e\n\t\t\t{children}\n\t\t\u003c/AppContext.Provider\u003e\n\t);\n};\n// make sure use\nexport const useAppContext = () =\u003e {\n\treturn useContext(AppContext);\n};\n\nexport { AppProvider };\n```\n\n- index.js\n\n```js\nimport { AppProvider } from \"./context/appContext\";\n\nReactDOM.render(\n\t\u003cReact.StrictMode\u003e\n\t\t\u003cAppProvider\u003e\n\t\t\t\u003cApp /\u003e\n\t\t\u003c/AppProvider\u003e\n\t\u003c/React.StrictMode\u003e,\n\tdocument.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\tthrow 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\treturn {\n\t\t...state,\n\t\tshowAlert: true,\n\t\talertType: \"danger\",\n\t\talertText: \"Please provide all values!\",\n\t};\n}\n```\n\n```js\nAlert.js in Components;\n\nimport { useAppContext } from \"../context/appContext\";\n\nconst Alert = () =\u003e {\n\tconst { alertType, alertText } = useAppContext();\n\treturn \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\tsetValues({ ...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\te.preventDefault();\n\tconst { name, email, password, isMember } = values;\n\tif (!email || !password || (!isMember \u0026\u0026 !name)) {\n\t\tdisplayAlert();\n\t\treturn;\n\t}\n\tconsole.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\treturn {\n\t\t...state,\n\t\tshowAlert: false,\n\t\talertType: \"\",\n\t\talertText: \"\",\n\t};\n}\n```\n\n```js\nappContext.js;\n\nconst displayAlert = () =\u003e {\n\tdispatch({\n\t\ttype: DISPLAY_ALERT,\n\t});\n\tclearAlert();\n};\n\nconst clearAlert = () =\u003e {\n\tsetTimeout(() =\u003e {\n\t\tdispatch({\n\t\t\ttype: CLEAR_ALERT,\n\t\t});\n\t}, 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\tres.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\texpiresIn: 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\tuser: null,\n\ttoken: null,\n\tuserLocation: \"\",\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\tconsole.log(currentUser);\n};\n```\n\n- import in Register.js\n\n```js\nRegister.js;\n\nconst currentUser = { name, email, password };\nif (isMember) {\n\tconsole.log(\"already a member\");\n} else {\n\tregisterUser(currentUser);\n}\n\nreturn (\n\t\u003cbutton type='submit' className='btn btn-block' disabled={isLoading}\u003e\n\t\tsubmit\n\t\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\tdispatch({ type: REGISTER_USER_BEGIN });\n\ttry {\n\t\tconst response = await axios.post(\"/api/v1/auth/register\", currentUser);\n\t\tconsole.log(response);\n\t\tconst { user, token, location } = response.data;\n\t\tdispatch({\n\t\t\ttype: REGISTER_USER_SUCCESS,\n\t\t\tpayload: {\n\t\t\t\tuser,\n\t\t\t\ttoken,\n\t\t\t\tlocation,\n\t\t\t},\n\t\t});\n\n\t\t// will add later\n\t\t// addUserToLocalStorage({\n\t\t//   user,\n\t\t//   token,\n\t\t//   location,\n\t\t// })\n\t} catch (error) {\n\t\tconsole.log(error.response);\n\t\tdispatch({\n\t\t\ttype: REGISTER_USER_ERROR,\n\t\t\tpayload: { msg: error.response.data.msg },\n\t\t});\n\t}\n\tclearAlert();\n};\n```\n\n```js\nreducer.js;\nif (action.type === REGISTER_USER_BEGIN) {\n\treturn { ...state, isLoading: true };\n}\nif (action.type === REGISTER_USER_SUCCESS) {\n\treturn {\n\t\t...state,\n\t\tuser: action.payload.user,\n\t\ttoken: action.payload.token,\n\t\tuserLocation: action.payload.location,\n\t\tjobLocation: action.payload.location,\n\t\tisLoading: false,\n\t\tshowAlert: true,\n\t\talertType: \"success\",\n\t\talertText: \"User Created! Redirecting...\",\n\t};\n}\nif (action.type === REGISTER_USER_ERROR) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tshowAlert: true,\n\t\talertType: \"danger\",\n\t\talertText: action.payload.msg,\n\t};\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\tconst { user } = useAppContext();\n\tconst navigate = useNavigate();\n\n\tuseEffect(() =\u003e {\n\t\tif (user) {\n\t\t\tsetTimeout(() =\u003e {\n\t\t\t\tnavigate(\"/\");\n\t\t\t}, 3000);\n\t\t}\n\t}, [user, navigate]);\n};\n```\n\n#### Local Storage\n\n```js\nappContext.js;\nconst addUserToLocalStorage = ({ user, token, location }) =\u003e {\n\tlocalStorage.setItem(\"user\", JSON.stringify(user));\n\tlocalStorage.setItem(\"token\", token);\n\tlocalStorage.setItem(\"location\", location);\n};\n\nconst removeUserFromLocalStorage = () =\u003e {\n\tlocalStorage.removeItem(\"token\");\n\tlocalStorage.removeItem(\"user\");\n\tlocalStorage.removeItem(\"location\");\n};\n\nconst registerUser = async (currentUser) =\u003e {\n\t// in try block\n\taddUserToLocalStorage({\n\t\tuser,\n\t\ttoken,\n\t\tlocation,\n\t});\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\tuser: user ? JSON.parse(user) : null,\n\ttoken: token,\n\tuserLocation: userLocation || \"\",\n\tjobLocation: 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\tapp.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\tconstructor(message) {\n\t\tsuper(message);\n\t\tthis.statusCode = StatusCodes.UNAUTHORIZED;\n\t}\n}\n```\n\n#### Compare Password\n\n```js\nUser.js in models;\n\nUserSchema.methods.comparePassword = async function (candidatePassword) {\n\tconst isMatch = await bcrypt.compare(candidatePassword, this.password);\n\treturn isMatch;\n};\n```\n\n```js\nauthController.js;\nconst login = async (req, res) =\u003e {\n\tconst { email, password } = req.body;\n\tif (!email || !password) {\n\t\tthrow new BadRequestError(\"Please provide all values\");\n\t}\n\tconst user = await User.findOne({ email }).select(\"+password\");\n\n\tif (!user) {\n\t\tthrow new UnauthenticatedError(\"Invalid Credentials\");\n\t}\n\tconst isPasswordCorrect = await user.comparePassword(password);\n\tif (!isPasswordCorrect) {\n\t\tthrow new UnauthenticatedError(\"Invalid Credentials\");\n\t}\n\tconst token = user.createJWT();\n\tuser.password = undefined;\n\tres.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\tconsole.log(currentUser);\n};\n```\n\n- import in Register.js\n\n```js\nRegister.js;\n\nif (isMember) {\n\tloginUser(currentUser);\n} else {\n\tregisterUser(currentUser);\n}\n```\n\n#### Login User - Complete\n\n```js\nappContext.js;\nconst loginUser = async (currentUser) =\u003e {\n\tdispatch({ type: LOGIN_USER_BEGIN });\n\ttry {\n\t\tconst { data } = await axios.post(\"/api/v1/auth/login\", currentUser);\n\t\tconst { user, token, location } = data;\n\n\t\tdispatch({\n\t\t\ttype: LOGIN_USER_SUCCESS,\n\t\t\tpayload: { user, token, location },\n\t\t});\n\n\t\taddUserToLocalStorage({ user, token, location });\n\t} catch (error) {\n\t\tdispatch({\n\t\t\ttype: LOGIN_USER_ERROR,\n\t\t\tpayload: { msg: error.response.data.msg },\n\t\t});\n\t}\n\tclearAlert();\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === LOGIN_USER_BEGIN) {\n\treturn {\n\t\t...state,\n\t\tisLoading: true,\n\t};\n}\nif (action.type === LOGIN_USER_SUCCESS) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tuser: action.payload.user,\n\t\ttoken: action.payload.token,\n\t\tuserLocation: action.payload.location,\n\t\tjobLocation: action.payload.location,\n\t\tshowAlert: true,\n\t\talertType: \"success\",\n\t\talertText: \"Login Successful! Redirecting...\",\n\t};\n}\nif (action.type === LOGIN_USER_ERROR) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tshowAlert: true,\n\t\talertType: \"danger\",\n\t\talertText: action.payload.msg,\n\t};\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\tdispatch({ type: SETUP_USER_BEGIN });\n\ttry {\n\t\tconst { data } = await axios.post(`/api/v1/auth/${endPoint}`, currentUser);\n\n\t\tconst { user, token, location } = data;\n\t\tdispatch({\n\t\t\ttype: SETUP_USER_SUCCESS,\n\t\t\tpayload: { user, token, location, alertText },\n\t\t});\n\t\taddUserToLocalStorage({ user, token, location });\n\t} catch (error) {\n\t\tdispatch({\n\t\t\ttype: SETUP_USER_ERROR,\n\t\t\tpayload: { msg: error.response.data.msg },\n\t\t});\n\t}\n\tclearAlert();\n};\n```\n\n```js\nreducer.js;\nif (action.type === SETUP_USER_BEGIN) {\n\treturn { ...state, isLoading: true };\n}\nif (action.type === SETUP_USER_SUCCESS) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\ttoken: action.payload.token,\n\t\tuser: action.payload.user,\n\t\tuserLocation: action.payload.location,\n\t\tjobLocation: action.payload.location,\n\t\tshowAlert: true,\n\t\talertType: \"success\",\n\t\talertText: action.payload.alertText,\n\t};\n}\nif (action.type === SETUP_USER_ERROR) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tshowAlert: true,\n\t\talertType: \"danger\",\n\t\talertText: action.payload.msg,\n\t};\n}\n```\n\n- import/export\n\n```js\nRegister.js;\n\nconst onSubmit = (e) =\u003e {\n\te.preventDefault();\n\tconst { name, email, password, isMember } = values;\n\tif (!email || !password || (!isMember \u0026\u0026 !name)) {\n\t\tdisplayAlert();\n\t\treturn;\n\t}\n\tconst currentUser = { name, email, password };\n\tif (isMember) {\n\t\tsetupUser({\n\t\t\tcurrentUser,\n\t\t\tendPoint: \"login\",\n\t\t\talertText: \"Login Successful! Redirecting...\",\n\t\t});\n\t} else {\n\t\tsetupUser({\n\t\t\tcurrentUser,\n\t\t\tendPoint: \"register\",\n\t\t\talertText: \"User Created! Redirecting...\",\n\t\t});\n\t}\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\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cnav\u003e\n\t\t\t\t\u003cLink to='all-jobs'\u003eall jobs\u003c/Link\u003e\n\t\t\t\t\u003cLink to='add-job'\u003eall jobs\u003c/Link\u003e\n\t\t\t\u003c/nav\u003e\n\t\t\t\u003cOutlet /\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\tpath='/'\n\telement={\n\t\t\u003cProtectedRoute\u003e\n\t\t\t\u003cSharedLayout /\u003e\n\t\t\u003c/ProtectedRoute\u003e\n\t}\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\tconst { user } = useAppContext();\n\tif (!user) {\n\t\treturn \u003cNavigate to='/landing' /\u003e;\n\t}\n\treturn 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\tconst { user } = useAppContext();\n\treturn (\n\t\t\u003c\u003e\n\t\t\t\u003cWrapper\u003e\n\t\t\t\t\u003cmain className='dashboard'\u003e\n\t\t\t\t\t\u003cSmallSidebar /\u003e\n\t\t\t\t\t\u003cBigSidebar /\u003e\n\t\t\t\t\t\u003cdiv\u003e\n\t\t\t\t\t\t\u003cNavbar /\u003e\n\t\t\t\t\t\t\u003cdiv className='dashboard-page'\u003e\n\t\t\t\t\t\t\t\u003cOutlet /\u003e\n\t\t\t\t\t\t\u003c/div\u003e\n\t\t\t\t\t\u003c/div\u003e\n\t\t\t\t\u003c/main\u003e\n\t\t\t\u003c/Wrapper\u003e\n\t\t\u003c/\u003e\n\t);\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\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cdiv className='nav-center'\u003e\n\t\t\t\t\u003cbutton\n\t\t\t\t\tclassName='toggle-btn'\n\t\t\t\t\tonClick={() =\u003e console.log(\"toggle sidebar\")}\n\t\t\t\t\u003e\n\t\t\t\t\t\u003cFaAlignLeft /\u003e\n\t\t\t\t\u003c/button\u003e\n\n\t\t\t\t\u003cdiv\u003e\n\t\t\t\t\t\u003cLogo /\u003e\n\t\t\t\t\t\u003ch3 className='logo-text'\u003edashboard\u003c/h3\u003e\n\t\t\t\t\u003c/div\u003e\n\n\t\t\t\t\u003cdiv className='btn-container'\u003e\n\t\t\t\t\t\u003cbutton className='btn' onClick={() =\u003e console.log(\"show logout\")}\u003e\n\t\t\t\t\t\t\u003cFaUserCircle /\u003e\n\t\t\t\t\t\tjohn\n\t\t\t\t\t\t\u003cFaCaretDown /\u003e\n\t\t\t\t\t\u003c/button\u003e\n\t\t\t\t\t\u003cdiv className='dropdown show-dropdown'\u003e\n\t\t\t\t\t\t\u003cbutton\n\t\t\t\t\t\t\tonClick={() =\u003e console.log(\"logout user\")}\n\t\t\t\t\t\t\tclassName='dropdown-btn'\n\t\t\t\t\t\t\u003e\n\t\t\t\t\t\t\tlogout\n\t\t\t\t\t\t\u003c/button\u003e\n\t\t\t\t\t\u003c/div\u003e\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/div\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\tshowSidebar: false,\n};\n\nconst toggleSidebar = () =\u003e {\n\tdispatch({ type: TOGGLE_SIDEBAR });\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === TOGGLE_SIDEBAR) {\n\treturn { ...state, showSidebar: !state.showSidebar };\n}\n```\n\n```js\nNavbar.js;\n\nconst { toggleSidebar } = useAppContext();\n\nreturn (\n\t\u003cbutton className='toggle-btn' onClick={toggleSidebar}\u003e\n\t\t\u003cFaAlignLeft /\u003e\n\t\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\treturn {\n\t\t...initialState,\n\t\tuser: null,\n\t\ttoken: null,\n\t\tuserLocation: \"\",\n\t\tjobLocation: \"\",\n\t};\n}\n```\n\n```js\nNavbar.js;\n\nconst { user, logoutUser, toggleSidebar } = useAppContext();\n\nreturn (\n\t\u003cdiv className='btn-container'\u003e\n\t\t\u003cbutton className='btn' onClick={() =\u003e setShowLogout(!showLogout)}\u003e\n\t\t\t\u003cFaUserCircle /\u003e\n\t\t\t{user.name}\n\t\t\t{user \u0026\u0026 user.name}\n\t\t\t{user?.name} // optional chaining\n\t\t\t\u003cFaCaretDown /\u003e\n\t\t\u003c/button\u003e\n\t\t\u003cdiv className={showLogout ? \"dropdown show-dropdown\" : \"dropdown\"}\u003e\n\t\t\t\u003cbutton onClick={logoutUser} className='dropdown-btn'\u003e\n\t\t\t\tlogout\n\t\t\t\u003c/button\u003e\n\t\t\u003c/div\u003e\n\t\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\t{\n\t\tid: 1,\n\t\ttext: \"stats\",\n\t\tpath: \"/\",\n\t\ticon: \u003cIoBarChartSharp /\u003e,\n\t},\n\t{\n\t\tid: 2,\n\t\ttext: \"all jobs\",\n\t\tpath: \"all-jobs\",\n\t\ticon: \u003cMdQueryStats /\u003e,\n\t},\n\t{\n\t\tid: 3,\n\t\ttext: \"add job\",\n\t\tpath: \"add-job\",\n\t\ticon: \u003cFaWpforms /\u003e,\n\t},\n\t{\n\t\tid: 4,\n\t\ttext: \"profile\",\n\t\tpath: \"profile\",\n\t\ticon: \u003cImProfile /\u003e,\n\t},\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\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cdiv className='sidebar-container show-sidebar'\u003e\n\t\t\t\t\u003cdiv className='content'\u003e\n\t\t\t\t\t\u003cbutton className='close-btn' onClick={() =\u003e console.log(\"toggle\")}\u003e\n\t\t\t\t\t\t\u003cFaTimes /\u003e\n\t\t\t\t\t\u003c/button\u003e\n\t\t\t\t\t\u003cheader\u003e\n\t\t\t\t\t\t\u003cLogo /\u003e\n\t\t\t\t\t\u003c/header\u003e\n\t\t\t\t\t\u003cdiv className='nav-links'\u003enav links\u003c/div\u003e\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/div\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\t\u003cdiv\n\t\tclassName={\n\t\t\tshowSidebar ? \"sidebar-container show-sidebar\" : \"sidebar-container\"\n\t\t}\n\t\u003e\u003c/div\u003e\n);\n```\n\n```js\nSmallSidebar.js;\n\nreturn (\n\t\u003cbutton className='close-btn' onClick={toggleSidebar}\u003e\n\t\t\u003cFaTimes /\u003e\n\t\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\t\u003cdiv className='nav-links'\u003e\n\t\t{links.map((link) =\u003e {\n\t\t\tconst { text, path, id, icon } = link;\n\n\t\t\treturn (\n\t\t\t\t\u003cNavLink\n\t\t\t\t\tto={path}\n\t\t\t\t\tclassName={({ isActive }) =\u003e\n\t\t\t\t\t\tisActive ? \"nav-link active\" : \"nav-link\"\n\t\t\t\t\t}\n\t\t\t\t\tkey={id}\n\t\t\t\t\tonClick={toggleSidebar}\n\t\t\t\t\u003e\n\t\t\t\t\t\u003cspan className='icon'\u003e{icon}\u003c/span\u003e\n\t\t\t\t\t{text}\n\t\t\t\t\u003c/NavLink\u003e\n\t\t\t);\n\t\t})}\n\t\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\treturn (\n\t\t\u003cdiv className='nav-links'\u003e\n\t\t\t{links.map((link) =\u003e {\n\t\t\t\tconst { text, path, id, icon } = link;\n\n\t\t\t\treturn (\n\t\t\t\t\t\u003cNavLink\n\t\t\t\t\t\tto={path}\n\t\t\t\t\t\tkey={id}\n\t\t\t\t\t\tonClick={toggleSidebar}\n\t\t\t\t\t\tclassName={({ isActive }) =\u003e\n\t\t\t\t\t\t\tisActive ? \"nav-link active\" : \"nav-link\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\u003e\n\t\t\t\t\t\t\u003cspan className='icon'\u003e{icon}\u003c/span\u003e\n\t\t\t\t\t\t{text}\n\t\t\t\t\t\u003c/NavLink\u003e\n\t\t\t\t);\n\t\t\t})}\n\t\t\u003c/div\u003e\n\t);\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\tconst { showSidebar } = useAppContext();\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cdiv\n\t\t\t\tclassName={\n\t\t\t\t\tshowSidebar ? \"sidebar-container \" : \"sidebar-container show-sidebar\"\n\t\t\t\t}\n\t\t\t\u003e\n\t\t\t\t\u003cdiv className='content'\u003e\n\t\t\t\t\t\u003cheader\u003e\n\t\t\t\t\t\t\u003cLogo /\u003e\n\t\t\t\t\t\u003c/header\u003e\n\t\t\t\t\t\u003cNavLinks /\u003e\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/div\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\n};\n\nexport default BigSidebar;\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\tconsole.log(\"authenticate user\");\n\tnext();\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\tconst headers = req.headers;\n\tconst authHeader = req.headers.authorization;\n\tconsole.log(headers);\n\tconsole.log(authHeader);\n\tnext();\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\t{\n\t\ttoken;\n\t}\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\tconst authHeader = req.headers.authorization;\n\n\tif (!authHeader) {\n\t\t// why, well is it 400 or 404?\n\t\t// actually 401\n\t\tthrow new UnAuthenticatedError(\"Authentication Invalid\");\n\t}\n\n\tnext();\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\t// check header\n\tconst authHeader = req.headers.authorization;\n\tif (!authHeader || !authHeader.startsWith(\"Bearer\")) {\n\t\tthrow new UnauthenticatedError(\"Authentication invalid\");\n\t}\n\tconst token = authHeader.split(\" \")[1];\n\n\ttry {\n\t\tconst payload = jwt.verify(token, process.env.JWT_SECRET);\n\t\t// console.log(payload)\n\t\t// attach the user request object\n\t\t// req.user = payload\n\t\treq.user = { userId: payload.userId };\n\t\tnext();\n\t} catch (error) {\n\t\tthrow new UnauthenticatedError(\"Authentication invalid\");\n\t}\n};\n\nexport default auth;\n```\n\n#### Update User\n\n```js\nconst updateUser = async (req, res) =\u003e {\n\tconst { email, name, lastName, location } = req.body;\n\tif (!email || !name || !lastName || !location) {\n\t\tthrow new BadRequestError(\"Please provide all values\");\n\t}\n\n\tconst user = await User.findOne({ _id: req.user.userId });\n\n\tuser.email = email;\n\tuser.name = name;\n\tuser.lastName = lastName;\n\tuser.location = location;\n\n\tawait user.save();\n\n\t// various setups\n\t// in this case only id\n\t// if other properties included, must re-generate\n\n\tconst token = user.createJWT();\n\tres.status(StatusCodes.OK).json({\n\t\tuser,\n\t\ttoken,\n\t\tlocation: user.location,\n\t});\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\tconsole.log(this.modifiedPaths());\n\tconsole.log(this.isModified(\"name\"));\n\n\t// if (!this.isModified('password')) return\n\t// const salt = await bcrypt.genSalt(10)\n\t// 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\tconst { user, showAlert, displayAlert, updateUser, isLoading } =\n\t\tuseAppContext();\n\tconst [name, setName] = useState(user?.name);\n\tconst [email, setEmail] = useState(user?.email);\n\tconst [lastName, setLastName] = useState(user?.lastName);\n\tconst [location, setLocation] = useState(user?.location);\n\n\tconst handleSubmit = (e) =\u003e {\n\t\te.preventDefault();\n\t\tif (!name || !email || !lastName || !location) {\n\t\t\t// test and remove temporary\n\t\t\tdisplayAlert();\n\t\t\treturn;\n\t\t}\n\n\t\tupdateUser({ name, email, lastName, location });\n\t};\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cform className='form' onSubmit={handleSubmit}\u003e\n\t\t\t\t\u003ch3\u003eprofile \u003c/h3\u003e\n\t\t\t\t{showAlert \u0026\u0026 \u003cAlert /\u003e}\n\n\t\t\t\t{/* name */}\n\t\t\t\t\u003cdiv className='form-center'\u003e\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tname='name'\n\t\t\t\t\t\tvalue={name}\n\t\t\t\t\t\thandleChange={(e) =\u003e setName(e.target.value)}\n\t\t\t\t\t/\u003e\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\tlabelText='last name'\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tname='lastName'\n\t\t\t\t\t\tvalue={lastName}\n\t\t\t\t\t\thandleChange={(e) =\u003e setLastName(e.target.value)}\n\t\t\t\t\t/\u003e\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\ttype='email'\n\t\t\t\t\t\tname='email'\n\t\t\t\t\t\tvalue={email}\n\t\t\t\t\t\thandleChange={(e) =\u003e setEmail(e.target.value)}\n\t\t\t\t\t/\u003e\n\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tname='location'\n\t\t\t\t\t\tvalue={location}\n\t\t\t\t\t\thandleChange={(e) =\u003e setLocation(e.target.value)}\n\t\t\t\t\t/\u003e\n\t\t\t\t\t\u003cbutton className='btn btn-block' type='submit' disabled={isLoading}\u003e\n\t\t\t\t\t\t{isLoading ? \"Please Wait...\" : \"save changes\"}\n\t\t\t\t\t\u003c/button\u003e\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/form\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\n};\n\nexport default Profile;\n```\n\n#### Bearer Token - Manual Approach\n\n```js\nappContext.js;\n\nconst updaterUser = async (currentUser) =\u003e {\n\ttry {\n\t\tconst { data } = await axios.patch(\"/api/v1/auth/updateUser\", currentUser, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${state.token}`,\n\t\t\t},\n\t\t});\n\t\tconsole.log(data);\n\t} catch (error) {\n\t\tconsole.log(error.response);\n\t}\n};\n```\n\n#### Axios - Global Setup\n\n```js\nappContext.js;\n\naxios.defaults.headers.common[\"Authorization\"] = `Bearer ${state.token}`;\n```\n\n#### Axios - Setup Instance\n\n```js\nAppContext.js;\n\nconst authFetch = axios.create({\n\tbaseURL: \"/api/v1\",\n\theaders: {\n\t\tAuthorization: `Bearer ${state.token}`,\n\t},\n});\n\nconst updaterUser = async (currentUser) =\u003e {\n\ttry {\n\t\tconst { data } = await authFetch.patch(\"/auth/updateUser\", currentUser);\n\t} catch (error) {\n\t\tconsole.log(error.response);\n\t}\n};\n```\n\n#### Axios - Interceptors\n\n- will use instance, but can use axios instead\n\n```js\nappContext.js;\n\n// response interceptor\nauthFetch.interceptors.request.use(\n\t(config) =\u003e {\n\t\tconfig.headers.common[\"Authorization\"] = `Bearer ${state.token}`;\n\t\treturn config;\n\t},\n\t(error) =\u003e {\n\t\treturn Promise.reject(error);\n\t}\n);\n// response interceptor\nauthFetch.interceptors.response.use(\n\t(response) =\u003e {\n\t\treturn response;\n\t},\n\t(error) =\u003e {\n\t\tconsole.log(error.response);\n\t\tif (error.response.status === 401) {\n\t\t\tconsole.log(\"AUTH ERROR\");\n\t\t}\n\t\treturn Promise.reject(error);\n\t}\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\tdispatch({ type: UPDATE_USER_BEGIN });\n\ttry {\n\t\tconst { data } = await authFetch.patch(\"/auth/updateUser\", currentUser);\n\n\t\t// no token\n\t\tconst { user, location, token } = data;\n\n\t\tdispatch({\n\t\t\ttype: UPDATE_USER_SUCCESS,\n\t\t\tpayload: { user, location, token },\n\t\t});\n\n\t\taddUserToLocalStorage({ user, location, token });\n\t} catch (error) {\n\t\tdispatch({\n\t\t\ttype: UPDATE_USER_ERROR,\n\t\t\tpayload: { msg: error.response.data.msg },\n\t\t});\n\t}\n\tclearAlert();\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\t(response) =\u003e {\n\t\treturn response;\n\t},\n\t(error) =\u003e {\n\t\tif (error.response.status === 401) {\n\t\t\tlogoutUser();\n\t\t}\n\t\treturn Promise.reject(error);\n\t}\n);\n\nconst updateUser = async (currentUser) =\u003e {\n\tdispatch({ type: UPDATE_USER_BEGIN });\n\ttry {\n\t\tconst { data } = await authFetch.patch(\"/auth/updateUser\", currentUser);\n\n\t\t// no token\n\t\tconst { user, location } = data;\n\n\t\tdispatch({\n\t\t\ttype: UPDATE_USER_SUCCESS,\n\t\t\tpayload: { user, location, token },\n\t\t});\n\n\t\taddUserToLocalStorage({ user, location, token: initialState.token });\n\t} catch (error) {\n\t\tif (error.response.status !== 401) {\n\t\t\tdispatch({\n\t\t\t\ttype: UPDATE_USER_ERROR,\n\t\t\t\tpayload: { msg: error.response.data.msg },\n\t\t\t});\n\t\t}\n\t}\n\tclearAlert();\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\t{\n\t\tcompany: {\n\t\t\ttype: String,\n\t\t\trequired: [true, \"Please provide company name\"],\n\t\t\tmaxlength: 50,\n\t\t},\n\t\tposition: {\n\t\t\ttype: String,\n\t\t\trequired: [true, \"Please provide position\"],\n\t\t\tmaxlength: 100,\n\t\t},\n\t\tstatus: {\n\t\t\ttype: String,\n\t\t\tenum: [\"interview\", \"declined\", \"pending\"],\n\t\t\tdefault: \"pending\",\n\t\t},\n\n\t\tjobType: {\n\t\t\ttype: String,\n\t\t\tenum: [\"full-time\", \"part-time\", \"remote\", \"internship\"],\n\t\t\tdefault: \"full-time\",\n\t\t},\n\t\tjobLocation: {\n\t\t\ttype: String,\n\t\t\tdefault: \"my city\",\n\t\t\trequired: true,\n\t\t},\n\t\tcreatedBy: {\n\t\t\ttype: mongoose.Types.ObjectId,\n\t\t\tref: \"User\",\n\t\t\trequired: [true, \"Please provide user\"],\n\t\t},\n\t},\n\t{ 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\tconst { position, company } = req.body;\n\n\tif (!position || !company) {\n\t\tthrow new BadRequestError(\"Please Provide All Values\");\n\t}\n\n\treq.body.createdBy = req.user.userId;\n\n\tconst job = await Job.create(req.body);\n\tres.status(StatusCodes.CREATED).json({ job });\n};\n```\n\n#### Job State Values\n\n```js\nappContext.js;\nconst initialState = {\n\tisEditing: false,\n\teditJobId: \"\",\n\tposition: \"\",\n\tcompany: \"\",\n\t// jobLocation\n\tjobTypeOptions: [\"full-time\", \"part-time\", \"remote\", \"internship\"],\n\tjobType: \"full-time\",\n\tstatusOptions: [\"pending\", \"interview\", \"declined\"],\n\tstatus: \"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\tconst {\n\t\tisEditing,\n\t\tshowAlert,\n\t\tdisplayAlert,\n\t\tposition,\n\t\tcompany,\n\t\tjobLocation,\n\t\tjobType,\n\t\tjobTypeOptions,\n\t\tstatus,\n\t\tstatusOptions,\n\t} = useAppContext();\n\n\tconst handleSubmit = (e) =\u003e {\n\t\te.preventDefault();\n\n\t\tif (!position || !company || !jobLocation) {\n\t\t\tdisplayAlert();\n\t\t\treturn;\n\t\t}\n\t\tconsole.log(\"create job\");\n\t};\n\n\tconst handleJobInput = (e) =\u003e {\n\t\tconst name = e.target.name;\n\t\tconst value = e.target.value;\n\t\tconsole.log(`${name}:${value}`);\n\t};\n\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cform className='form'\u003e\n\t\t\t\t\u003ch3\u003e{isEditing ? \"edit job\" : \"add job\"} \u003c/h3\u003e\n\t\t\t\t{showAlert \u0026\u0026 \u003cAlert /\u003e}\n\n\t\t\t\t{/* position */}\n\t\t\t\t\u003cdiv className='form-center'\u003e\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tname='position'\n\t\t\t\t\t\tvalue={position}\n\t\t\t\t\t\thandleChange={handleJobInput}\n\t\t\t\t\t/\u003e\n\t\t\t\t\t{/* company */}\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tname='company'\n\t\t\t\t\t\tvalue={company}\n\t\t\t\t\t\thandleChange={handleJobInput}\n\t\t\t\t\t/\u003e\n\t\t\t\t\t{/* location */}\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tlabelText='location'\n\t\t\t\t\t\tname='jobLocation'\n\t\t\t\t\t\tvalue={jobLocation}\n\t\t\t\t\t\thandleChange={handleJobInput}\n\t\t\t\t\t/\u003e\n\t\t\t\t\t{/* job type */}\n\n\t\t\t\t\t{/* job status */}\n\n\t\t\t\t\t\u003cdiv className='btn-container'\u003e\n\t\t\t\t\t\t\u003cbutton\n\t\t\t\t\t\t\tclassName='btn btn-block submit-btn'\n\t\t\t\t\t\t\ttype='submit'\n\t\t\t\t\t\t\tonClick={handleSubmit}\n\t\t\t\t\t\t\u003e\n\t\t\t\t\t\t\tsubmit\n\t\t\t\t\t\t\u003c/button\u003e\n\t\t\t\t\t\u003c/div\u003e\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/form\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\n};\n\nexport default AddJob;\n```\n\n#### Select Input\n\n```js\nreturn (\n\t// job type\n\t\u003cdiv className='form-row'\u003e\n\t\t\u003clabel htmlFor='jobType' className='form-label'\u003e\n\t\t\tjob type\n\t\t\u003c/label\u003e\n\n\t\t\u003cselect\n\t\t\tname='jobType'\n\t\t\tvalue={jobType}\n\t\t\tonChange={handleJobInput}\n\t\t\tclassName='form-select'\n\t\t\u003e\n\t\t\t{jobTypeOptions.map((itemValue, index) =\u003e {\n\t\t\t\treturn (\n\t\t\t\t\t\u003coption key={index} value={itemValue}\u003e\n\t\t\t\t\t\t{itemValue}\n\t\t\t\t\t\u003c/option\u003e\n\t\t\t\t);\n\t\t\t})}\n\t\t\u003c/select\u003e\n\t\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\treturn (\n\t\t\u003cdiv className='form-row'\u003e\n\t\t\t\u003clabel htmlFor={name} className='form-label'\u003e\n\t\t\t\t{labelText || name}\n\t\t\t\u003c/label\u003e\n\n\t\t\t\u003cselect\n\t\t\t\tname={name}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={handleChange}\n\t\t\t\tclassName='form-select'\n\t\t\t\u003e\n\t\t\t\t{list.map((itemValue, index) =\u003e {\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\u003coption key={index} value={itemValue}\u003e\n\t\t\t\t\t\t\t{itemValue}\n\t\t\t\t\t\t\u003c/option\u003e\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t\u003c/select\u003e\n\t\t\u003c/div\u003e\n\t);\n};\n\nexport default FormRowSelect;\n```\n\n```js\nAddJob.js;\n\nreturn (\n\t\u003c\u003e\n\t\t{/* job status */}\n\n\t\t\u003cFormRowSelect\n\t\t\tname='status'\n\t\t\tvalue={status}\n\t\t\thandleChange={handleJobInput}\n\t\t\tlist={statusOptions}\n\t\t/\u003e\n\n\t\t{/* job type */}\n\t\t\u003cFormRowSelect\n\t\t\tlabelText='type'\n\t\t\tname='jobType'\n\t\t\tvalue={jobType}\n\t\t\thandleChange={handleJobInput}\n\t\t\tlist={jobTypeOptions}\n\t\t/\u003e\n\t\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\treturn { ...state, [action.payload.name]: action.payload.value };\n}\n```\n\n```js\nAddJob.js;\n\nconst { handleChange } = useAppContext();\n\nconst handleJobInput = (e) =\u003e {\n\thandleChange({ 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\tconst initialState = {\n\t\tisEditing: false,\n\t\teditJobId: \"\",\n\t\tposition: \"\",\n\t\tcompany: \"\",\n\t\tjobLocation: state.userLocation,\n\t\tjobType: \"full-time\",\n\t\tstatus: \"pending\",\n\t};\n\treturn { ...state, ...initialState };\n}\n```\n\n```js\nAddJob.js;\n\nconst { clearValues } = useAppContext();\n\nreturn (\n\t\u003cdiv className='btn-container'\u003e\n\t\t{/* submit button */}\n\n\t\t\u003cbutton\n\t\t\tclassName='btn btn-block clear-btn'\n\t\t\tonClick={(e) =\u003e {\n\t\t\t\te.preventDefault();\n\t\t\t\tclearValues();\n\t\t\t}}\n\t\t\u003e\n\t\t\tclear\n\t\t\u003c/button\u003e\n\t\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\tdispatch({ type: CREATE_JOB_BEGIN });\n\ttry {\n\t\tconst { position, company, jobLocation, jobType, status } = state;\n\n\t\tawait authFetch.post(\"/jobs\", {\n\t\t\tcompany,\n\t\t\tposition,\n\t\t\tjobLocation,\n\t\t\tjobType,\n\t\t\tstatus,\n\t\t});\n\t\tdispatch({\n\t\t\ttype: CREATE_JOB_SUCCESS,\n\t\t});\n\t\t// call function instead clearValues()\n\t\tdispatch({ type: CLEAR_VALUES });\n\t} catch (error) {\n\t\tif (error.response.status === 401) return;\n\t\tdispatch({\n\t\t\ttype: CREATE_JOB_ERROR,\n\t\t\tpayload: { msg: error.response.data.msg },\n\t\t});\n\t}\n\tclearAlert();\n};\n```\n\n```js\nAddJob.js;\n\nconst { createJob } = useAppContext();\n\nconst handleSubmit = (e) =\u003e {\n\te.preventDefault();\n\t// while testing\n\n\t// if (!position || !company || !jobLocation) {\n\t//   displayAlert()\n\t//   return\n\t// }\n\tif (isEditing) {\n\t\t// eventually editJob()\n\t\treturn;\n\t}\n\tcreateJob();\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === CREATE_JOB_BEGIN) {\n\treturn { ...state, isLoading: true };\n}\nif (action.type === CREATE_JOB_SUCCESS) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tshowAlert: true,\n\t\talertType: \"success\",\n\t\talertText: \"New Job Created!\",\n\t};\n}\nif (action.type === CREATE_JOB_ERROR) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tshowAlert: true,\n\t\talertType: \"danger\",\n\t\talertText: action.payload.msg,\n\t};\n}\n```\n\n#### Get All Jobs\n\n```js\njobsController.js;\n\nconst getAllJobs = async (req, res) =\u003e {\n\tconst jobs = await Job.find({ createdBy: req.user.userId });\n\n\tres\n\t\t.status(StatusCodes.OK)\n\t\t.json({ jobs, totalJobs: jobs.length, numOfPages: 1 });\n};\n```\n\n#### Jobs State Values\n\n```js\nappContext.js;\n\nconst initialState = {\n\tjobs: [],\n\ttotalJobs: 0,\n\tnumOfPages: 1,\n\tpage: 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\treturn { ...state, isLoading: true, showAlert: false };\n}\nif (action.type === GET_JOBS_SUCCESS) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tjobs: action.payload.jobs,\n\t\ttotalJobs: action.payload.totalJobs,\n\t\tnumOfPages: action.payload.numOfPages,\n\t};\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\treturn (\n\t\t\u003c\u003e\n\t\t\t\u003cSearchContainer /\u003e\n\t\t\t\u003cJobsContainer /\u003e\n\t\t\u003c/\u003e\n\t);\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\tconst { getJobs, jobs, isLoading, page, totalJobs } = useAppContext();\n\tuseEffect(() =\u003e {\n\t\tgetJobs();\n\t}, []);\n\n\tif (isLoading) {\n\t\treturn \u003cLoading center /\u003e;\n\t}\n\tif (jobs.length === 0) {\n\t\treturn (\n\t\t\t\u003cWrapper\u003e\n\t\t\t\t\u003ch2\u003eNo jobs to display...\u003c/h2\u003e\n\t\t\t\u003c/Wrapper\u003e\n\t\t);\n\t}\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003ch5\u003e\n\t\t\t\t{totalJobs} job{jobs.length \u003e 1 \u0026\u0026 \"s\"} found\n\t\t\t\u003c/h5\u003e\n\t\t\t\u003cdiv className='jobs'\u003e\n\t\t\t\t{jobs.map((job) =\u003e {\n\t\t\t\t\treturn \u003cJob key={job._id} {...job} /\u003e;\n\t\t\t\t})}\n\t\t\t\u003c/div\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\n};\n\nexport default JobsContainer;\n```\n\n```js\nJob.js;\n\nimport moment from \"moment\";\n\nconst Job = ({ company }) =\u003e {\n\treturn \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\tlet date = moment(createdAt);\n\tdate = date.format(\"MMM Do, YYYY\");\n\treturn (\n\t\t\u003cdiv\u003e\n\t\t\t\u003ch5\u003e{company}\u003c/h5\u003e\n\t\t\t\u003ch5\u003e{date}\u003c/h5\u003e\n\t\t\u003c/div\u003e\n\t);\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\t_id,\n\tposition,\n\tcompany,\n\tjobLocation,\n\tjobType,\n\tcreatedAt,\n\tstatus,\n}) =\u003e {\n\tconst { setEditJob, deleteJob } = useAppContext();\n\n\tlet date = moment(createdAt);\n\tdate = date.format(\"MMM Do, YYYY\");\n\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cheader\u003e\n\t\t\t\t\u003cdiv className='main-icon'\u003e{company.charAt(0)}\u003c/div\u003e\n\t\t\t\t\u003cdiv className='info'\u003e\n\t\t\t\t\t\u003ch5\u003e{position}\u003c/h5\u003e\n\t\t\t\t\t\u003cp\u003e{company}\u003c/p\u003e\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/header\u003e\n\t\t\t\u003cdiv className='content'\u003e\n\t\t\t\t{/* content center later */}\n\t\t\t\t\u003cfooter\u003e\n\t\t\t\t\t\u003cdiv className='actions'\u003e\n\t\t\t\t\t\t\u003cLink\n\t\t\t\t\t\t\tto='/add-job'\n\t\t\t\t\t\t\tonClick={() =\u003e setEditJob(_id)}\n\t\t\t\t\t\t\tclassName='btn edit-btn'\n\t\t\t\t\t\t\u003e\n\t\t\t\t\t\t\tEdit\n\t\t\t\t\t\t\u003c/Link\u003e\n\t\t\t\t\t\t\u003cbutton\n\t\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\t\tclassName='btn delete-btn'\n\t\t\t\t\t\t\tonClick={() =\u003e deleteJob(_id)}\n\t\t\t\t\t\t\u003e\n\t\t\t\t\t\t\tDelete\n\t\t\t\t\t\t\u003c/button\u003e\n\t\t\t\t\t\u003c/div\u003e\n\t\t\t\t\u003c/footer\u003e\n\t\t\t\u003c/div\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cspan className='icon'\u003e{icon}\u003c/span\u003e\n\t\t\t\u003cspan className='text'\u003e{text}\u003c/span\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\n};\n\nexport default JobInfo;\n```\n\n```js\nJob.js;\nreturn (\n\t\u003cdiv className='content'\u003e\n\t\t\u003cdiv className='content-center'\u003e\n\t\t\t\u003cJobInfo icon={\u003cFaLocationArrow /\u003e} text={jobLocation} /\u003e\n\t\t\t\u003cJobInfo icon={\u003cFaCalendarAlt /\u003e} text={date} /\u003e\n\t\t\t\u003cJobInfo icon={\u003cFaBriefcase /\u003e} text={jobType} /\u003e\n\t\t\t\u003cdiv className={`status ${status}`}\u003e{status}\u003c/div\u003e\n\t\t\u003c/div\u003e\n\t\t{/* footer content */}\n\t\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\tconst job = state.jobs.find((job) =\u003e job._id === action.payload.id);\n\tconst { _id, position, company, jobLocation, jobType, status } = job;\n\treturn {\n\t\t...state,\n\t\tisEditing: true,\n\t\teditJobId: _id,\n\t\tposition,\n\t\tcompany,\n\t\tjobLocation,\n\t\tjobType,\n\t\tstatus,\n\t};\n}\n```\n\n```js\nAddJob.js;\nconst { isEditing, editJob } = useAppContext();\nconst handleSubmit = (e) =\u003e {\n\te.preventDefault();\n\n\tif (!position || !company || !jobLocation) {\n\t\tdisplayAlert();\n\t\treturn;\n\t}\n\tif (isEditing) {\n\t\teditJob();\n\t\treturn;\n\t}\n\tcreateJob();\n};\n```\n\n#### Edit Job - Server\n\n```js\njobsController.js;\n\nconst updateJob = async (req, res) =\u003e {\n\tconst { id: jobId } = req.params;\n\n\tconst { company, position } = req.body;\n\n\tif (!company || !position) {\n\t\tthrow new BadRequestError(\"Please Provide All Values\");\n\t}\n\n\tconst job = await Job.findOne({ _id: jobId });\n\n\tif (!job) {\n\t\tthrow new NotFoundError(`No job with id ${jobId}`);\n\t}\n\n\t// check permissions\n\n\tconst updatedJob = await Job.findOneAndUpdate({ _id: jobId }, req.body, {\n\t\tnew: true,\n\t\trunValidators: true,\n\t});\n\n\tres.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\tconst { id: jobId } = req.params;\n\tconst { company, position, jobLocation } = req.body;\n\n\tif (!position || !company) {\n\t\tthrow new BadRequestError(\"Please provide all values\");\n\t}\n\tconst job = await Job.findOne({ _id: jobId });\n\n\tif (!job) {\n\t\tthrow new NotFoundError(`No job with id :${jobId}`);\n\t}\n\n\t// check permissions\n\n\t// alternative approach\n\n\tjob.position = position;\n\tjob.company = company;\n\tjob.jobLocation = jobLocation;\n\n\tawait job.save();\n\tres.status(StatusCodes.OK).json({ job });\n};\n```\n\n#### Check Permissions\n\n```js\njobsController.js;\n\nconst updateJob = async (req, res) =\u003e {\n\tconst { id: jobId } = req.params;\n\tconst { company, position, status } = req.body;\n\n\tif (!position || !company) {\n\t\tthrow new BadRequestError(\"Please provide all values\");\n\t}\n\tconst job = await Job.findOne({ _id: jobId });\n\n\tif (!job) {\n\t\tthrow new NotFoundError(`No job with id :${jobId}`);\n\t}\n\n\t// check permissions\n\t// req.user.userId (string) === job.createdBy(object)\n\t// throw new UnAuthorizedError('Not authorized to access this route')\n\n\t// console.log(typeof req.user.userId)\n\t// console.log(typeof job.createdBy)\n\n\tcheckPermissions(req.user, job.createdBy);\n\n\tconst updatedJob = await Job.findOneAndUpdate({ _id: jobId }, req.body, {\n\t\tnew: true,\n\t\trunValidators: true,\n\t});\n\n\tres.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 { UnAuthorizedError } from \"../errors/index.js\";\n\nconst checkPermissions = (requestUser, resourceUserId) =\u003e {\n\t// if (requestUser.role === 'admin') return\n\tif (requestUser.userId === resourceUserId.toString()) return;\n\tthrow new CustomError.UnauthorizedError(\n\t\t\"Not authorized to access this route\"\n\t);\n};\n\nexport default checkPermissions;\n```\n\n#### Remove/Delete Job\n\n```js\njobsController.js;\n\nconst deleteJob = async (req, res) =\u003e {\n\tconst { id: jobId } = req.params;\n\n\tconst job = await Job.findOne({ _id: jobId });\n\n\tif (!job) {\n\t\tthrow new CustomError.NotFoundError(`No job with id : ${jobId}`);\n\t}\n\n\tcheckPermissions(req.user, job.createdBy);\n\n\tawait job.remove();\n\tres.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\tdispatch({ type: DELETE_JOB_BEGIN });\n\ttry {\n\t\tawait authFetch.delete(`/jobs/${jobId}`);\n\t\tgetJobs();\n\t} catch (error) {\n\t\tlogoutUser();\n\t}\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === DELETE_JOB_BEGIN) {\n\treturn { ...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\tdispatch({ type: EDIT_JOB_BEGIN });\n\ttry {\n\t\tconst { position, company, jobLocation, jobType, status } = state;\n\n\t\tawait authFetch.patch(`/jobs/${state.editJobId}`, {\n\t\t\tcompany,\n\t\t\tposition,\n\t\t\tjobLocation,\n\t\t\tjobType,\n\t\t\tstatus,\n\t\t});\n\t\tdispatch({\n\t\t\ttype: EDIT_JOB_SUCCESS,\n\t\t});\n\t\tdispatch({ type: CLEAR_VALUES });\n\t} catch (error) {\n\t\tif (error.response.status === 401) return;\n\t\tdispatch({\n\t\t\ttype: EDIT_JOB_ERROR,\n\t\t\tpayload: { msg: error.response.data.msg },\n\t\t});\n\t}\n\tclearAlert();\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === EDIT_JOB_BEGIN) {\n\treturn { ...state, isLoading: true };\n}\nif (action.type === EDIT_JOB_SUCCESS) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tshowAlert: true,\n\t\talertType: \"success\",\n\t\talertText: \"Job Updated!\",\n\t};\n}\nif (action.type === EDIT_JOB_ERROR) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tshowAlert: true,\n\t\talertType: \"danger\",\n\t\talertText: action.payload.msg,\n\t};\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\ttry {\n\t\tawait connectDB(process.env.MONGO_URL);\n\t\tawait Job.deleteMany();\n\n\t\tconst jsonProducts = JSON.parse(\n\t\t\tawait readFile(new URL(\"./mock-data.json\", import.meta.url))\n\t\t);\n\t\tawait Job.create(jsonProducts);\n\t\tconsole.log(\"Success!!!!\");\n\t\tprocess.exit(0);\n\t} catch (error) {\n\t\tconsole.log(error);\n\t\tprocess.exit(1);\n\t}\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\tlet stats = await Job.aggregate([\n\t\t{ $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n\t\t{ $group: { _id: \"$status\", count: { $sum: 1 } } },\n\t]);\n\n\tres.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\tlet stats = await Job.aggregate([\n\t\t{ $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n\t\t{ $group: { _id: \"$status\", count: { $sum: 1 } } },\n\t]);\n\n\tstats = stats.reduce((acc, curr) =\u003e {\n\t\tconst { _id: title, count } = curr;\n\t\tacc[title] = count;\n\t\treturn acc;\n\t}, {});\n\n\tres.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\tlet stats = await Job.aggregate([\n\t\t{ $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n\t\t{ $group: { _id: \"$status\", count: { $sum: 1 } } },\n\t]);\n\tstats = stats.reduce((acc, curr) =\u003e {\n\t\tconst { _id: title, count } = curr;\n\t\tacc[title] = count;\n\t\treturn acc;\n\t}, {});\n\n\tconst defaultStats = {\n\t\tpending: stats.pending || 0,\n\t\tinterview: stats.interview || 0,\n\t\tdeclined: stats.declined || 0,\n\t};\n\tlet monthlyApplications = [];\n\tres.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\treturn { ...state, isLoading: true, showAlert: false };\n}\nif (action.type === SHOW_STATS_SUCCESS) {\n\treturn {\n\t\t...state,\n\t\tisLoading: false,\n\t\tstats: action.payload.stats,\n\t\tmonthlyApplications: action.payload.monthlyApplications,\n\t};\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\tconst { showStats, isLoading, monthlyApplications } = useAppContext();\n\tuseEffect(() =\u003e {\n\t\tshowStats();\n\t}, []);\n\n\tif (isLoading) {\n\t\treturn \u003cLoading center /\u003e;\n\t}\n\n\treturn (\n\t\t\u003c\u003e\n\t\t\t\u003cStatsContainer /\u003e\n\t\t\t{monthlyApplications.length \u003e 0 \u0026\u0026 \u003cChartsContainer /\u003e}\n\t\t\u003c/\u003e\n\t);\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\tconst { stats } = useAppContext();\n\tconst defaultStats = [\n\t\t{\n\t\t\ttitle: \"pending applications\",\n\t\t\tcount: stats.pending || 0,\n\t\t\ticon: \u003cFaSuitcaseRolling /\u003e,\n\t\t\tcolor: \"#e9b949\",\n\t\t\tbcg: \"#fcefc7\",\n\t\t},\n\t\t{\n\t\t\ttitle: \"interviews scheduled\",\n\t\t\tcount: stats.interview || 0,\n\t\t\ticon: \u003cFaCalendarCheck /\u003e,\n\t\t\tcolor: \"#647acb\",\n\t\t\tbcg: \"#e0e8f9\",\n\t\t},\n\t\t{\n\t\t\ttitle: \"jobs declined\",\n\t\t\tcount: stats.declined || 0,\n\t\t\ticon: \u003cFaBug /\u003e,\n\t\t\tcolor: \"#d66a6a\",\n\t\t\tbcg: \"#ffeeee\",\n\t\t},\n\t];\n\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t{defaultStats.map((item, index) =\u003e {\n\t\t\t\treturn \u003cStatItem key={index} {...item} /\u003e;\n\t\t\t})}\n\t\t\u003c/Wrapper\u003e\n\t);\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\treturn (\n\t\t\u003cWrapper color={color} bcg={bcg}\u003e\n\t\t\t\u003cheader\u003e\n\t\t\t\t\u003cspan className='count'\u003e{count}\u003c/span\u003e\n\t\t\t\t\u003cdiv className='icon'\u003e{icon}\u003c/div\u003e\n\t\t\t\u003c/header\u003e\n\t\t\t\u003ch5 className='title'\u003e{title}\u003c/h5\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\t{ $match: { createdBy: mongoose.Types.ObjectId(req.user.userId) } },\n\t{\n\t\t$group: {\n\t\t\t_id: {\n\t\t\t\tyear: {\n\t\t\t\t\t$year: \"$createdAt\",\n\t\t\t\t},\n\t\t\t\tmonth: {\n\t\t\t\t\t$month: \"$createdAt\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcount: { $sum: 1 },\n\t\t},\n\t},\n\t{ $sort: { \"_id.year\": -1, \"_id.month\": -1 } },\n\t{ $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\t.map((item) =\u003e {\n\t\tconst {\n\t\t\t_id: { year, month },\n\t\t\tcount,\n\t\t} = item;\n\t\t// accepts 0-11\n\t\tconst date = moment()\n\t\t\t.month(month - 1)\n\t\t\t.year(year)\n\t\t\t.format(\"MMM Y\");\n\t\treturn { date, count };\n\t})\n\t.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\tconst [barChart, setBarChart] = useState(true);\n\tconst { monthlyApplications: data } = useAppContext();\n\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003ch4\u003eMonthly Applications\u003c/h4\u003e\n\n\t\t\t\u003cbutton type='button' onClick={() =\u003e setBarChart(!barChart)}\u003e\n\t\t\t\t{barChart ? \"AreaChart\" : \"BarChart\"}\n\t\t\t\u003c/button\u003e\n\t\t\t{barChart ? \u003cBarChart data={data} /\u003e : \u003cAreaChart data={data} /\u003e}\n\t\t\u003c/Wrapper\u003e\n\t);\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\tBarChart,\n\tBar,\n\tXAxis,\n\tYAxis,\n\tCartesianGrid,\n\tTooltip,\n\tResponsiveContainer,\n} from \"recharts\";\n\nconst BarChartComponent = ({ data }) =\u003e {\n\treturn (\n\t\t\u003cResponsiveContainer width='100%' height={300}\u003e\n\t\t\t\u003cBarChart\n\t\t\t\tdata={data}\n\t\t\t\tmargin={{\n\t\t\t\t\ttop: 50,\n\t\t\t\t}}\n\t\t\t\u003e\n\t\t\t\t\u003cCartesianGrid strokeDasharray='3 3' /\u003e\n\t\t\t\t\u003cXAxis dataKey='date' /\u003e\n\t\t\t\t\u003cYAxis allowDecimals={false} /\u003e\n\t\t\t\t\u003cTooltip /\u003e\n\t\t\t\t\u003cBar dataKey='count' fill='#2cb1bc' barSize={75} /\u003e\n\t\t\t\u003c/BarChart\u003e\n\t\t\u003c/ResponsiveContainer\u003e\n\t);\n};\n```\n\n#### Area Chart\n\n```js\nimport {\n\tResponsiveContainer,\n\tAreaChart,\n\tArea,\n\tXAxis,\n\tYAxis,\n\tCartesianGrid,\n\tTooltip,\n} from \"recharts\";\n\nconst AreaChartComponent = ({ data }) =\u003e {\n\treturn (\n\t\t\u003cResponsiveContainer width='100%' height={300}\u003e\n\t\t\t\u003cAreaChart\n\t\t\t\tdata={data}\n\t\t\t\tmargin={{\n\t\t\t\t\ttop: 50,\n\t\t\t\t}}\n\t\t\t\u003e\n\t\t\t\t\u003cCartesianGrid strokeDasharray='3 3' /\u003e\n\t\t\t\t\u003cXAxis dataKey='date' /\u003e\n\t\t\t\t\u003cYAxis allowDecimals={false} /\u003e\n\t\t\t\t\u003cTooltip /\u003e\n\t\t\t\t\u003cArea type='monotone' dataKey='count' stroke='#2cb1bc' fill='#bef8fd' /\u003e\n\t\t\t\u003c/AreaChart\u003e\n\t\t\u003c/ResponsiveContainer\u003e\n\t);\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\tconst { search, status, jobType, sort } = req.query;\n\n\tconst queryObject = {\n\t\tcreatedBy: req.user.userId,\n\t};\n\n\t// NO AWAIT\n\tlet result = Job.find(queryObject);\n\n\t// chain sort conditions\n\n\tconst jobs = await result;\n\n\tres\n\t\t.status(StatusCodes.OK)\n\t\t.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\tconst { search, status, jobType, sort } = req.query;\n\n\tconst queryObject = {\n\t\tcreatedBy: req.user.userId,\n\t};\n\n\tif (status !== \"all\") {\n\t\tqueryObject.status = status;\n\t}\n\n\t// NO AWAIT\n\tlet result = Job.find(queryObject);\n\n\t// chain sort conditions\n\n\tconst jobs = await result;\n\n\tres\n\t\t.status(StatusCodes.OK)\n\t\t.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\tconst { search, status, jobType, sort } = req.query;\n\n\tconst queryObject = {\n\t\tcreatedBy: req.user.userId,\n\t};\n\n\tif (status !== \"all\") {\n\t\tqueryObject.status = status;\n\t}\n\tif (jobType !== \"all\") {\n\t\tqueryObject.jobType = jobType;\n\t}\n\t// NO AWAIT\n\tlet result = Job.find(queryObject);\n\n\t// chain sort conditions\n\n\tconst jobs = await result;\n\n\tres\n\t\t.status(StatusCodes.OK)\n\t\t.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\tconst { search, status, jobType, sort } = req.query;\n\n\tconst queryObject = {\n\t\tcreatedBy: req.user.userId,\n\t};\n\n\tif (status !== \"all\") {\n\t\tqueryObject.status = status;\n\t}\n\tif (jobType !== \"all\") {\n\t\tqueryObject.jobType = jobType;\n\t}\n\tif (search) {\n\t\tqueryObject.position = { $regex: search, $options: \"i\" };\n\t}\n\t// NO AWAIT\n\tlet result = Job.find(queryObject);\n\n\t// chain sort conditions\n\tif (sort === \"latest\") {\n\t\tresult = result.sort(\"-createdAt\");\n\t}\n\tif (sort === \"oldest\") {\n\t\tresult = result.sort(\"createdAt\");\n\t}\n\tif (sort === \"a-z\") {\n\t\tresult = result.sort(\"position\");\n\t}\n\tif (sort === \"z-a\") {\n\t\tresult = result.sort(\"-position\");\n\t}\n\tconst jobs = await result;\n\n\tres\n\t\t.status(StatusCodes.OK)\n\t\t.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\tconst {\n\t\tisLoading,\n\t\tsearch,\n\t\tsearchStatus,\n\t\tsearchType,\n\t\tsort,\n\t\tsortOptions,\n\t\tstatusOptions,\n\t\tjobTypeOptions,\n\t\thandleChange,\n\t\tclearFilters,\n\t} = useAppContext();\n\n\tconst handleSearch = (e) =\u003e {\n\t\tif (isLoading) return;\n\t\thandleChange({ name: e.target.name, value: e.target.value });\n\t};\n\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cform className='form'\u003e\n\t\t\t\t\u003ch4\u003esearch form\u003c/h4\u003e\n\t\t\t\t{/* search position */}\n\t\t\t\t\u003cdiv className='form-center'\u003e\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tname='search'\n\t\t\t\t\t\tvalue={search}\n\t\t\t\t\t\thandleChange={handleSearch}\n\t\t\t\t\t\u003e\u003c/FormRow\u003e\n\t\t\t\t\t{/* rest of the inputs */}\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/form\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\tconst {\n\t\tisLoading,\n\t\tsearch,\n\t\thandleChange,\n\t\tsearchStatus,\n\t\tstatusOptions,\n\t\tjobTypeOptions,\n\t\tsearchType,\n\t\tclearFilters,\n\t\tsort,\n\t\tsortOptions,\n\t} = useAppContext();\n\n\tconst handleSearch = (e) =\u003e {\n\t\tif (isLoading) return;\n\t\thandleChange({ name: e.target.name, value: e.target.value });\n\t};\n\tconst handleSubmit = (e) =\u003e {\n\t\te.preventDefault();\n\t\tclearFilters();\n\t};\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cform className='form'\u003e\n\t\t\t\t\u003ch4\u003esearch form\u003c/h4\u003e\n\t\t\t\t{/* search position */}\n\t\t\t\t\u003cdiv className='form-center'\u003e\n\t\t\t\t\t\u003cFormRow\n\t\t\t\t\t\ttype='text'\n\t\t\t\t\t\tname='search'\n\t\t\t\t\t\tvalue={search}\n\t\t\t\t\t\thandleChange={handleSearch}\n\t\t\t\t\t\u003e\u003c/FormRow\u003e\n\t\t\t\t\t{/* search by status */}\n\t\t\t\t\t\u003cFormRowSelect\n\t\t\t\t\t\tlabelText='job status'\n\t\t\t\t\t\tname='searchStatus'\n\t\t\t\t\t\tvalue={searchStatus}\n\t\t\t\t\t\thandleChange={handleSearch}\n\t\t\t\t\t\tlist={[\"all\", ...statusOptions]}\n\t\t\t\t\t\u003e\u003c/FormRowSelect\u003e\n\t\t\t\t\t{/* search by type */}\n\n\t\t\t\t\t\u003cFormRowSelect\n\t\t\t\t\t\tlabelText='job type'\n\t\t\t\t\t\tname='searchType'\n\t\t\t\t\t\tvalue={searchType}\n\t\t\t\t\t\thandleChange={handleSearch}\n\t\t\t\t\t\tlist={[\"all\", ...jobTypeOptions]}\n\t\t\t\t\t\u003e\u003c/FormRowSelect\u003e\n\t\t\t\t\t{/* sort */}\n\n\t\t\t\t\t\u003cFormRowSelect\n\t\t\t\t\t\tname='sort'\n\t\t\t\t\t\tvalue={sort}\n\t\t\t\t\t\thandleChange={handleSearch}\n\t\t\t\t\t\tlist={sortOptions}\n\t\t\t\t\t\u003e\u003c/FormRowSelect\u003e\n\t\t\t\t\t\u003cbutton\n\t\t\t\t\t\tclassName='btn btn-block btn-danger'\n\t\t\t\t\t\tdisabled={isLoading}\n\t\t\t\t\t\tonClick={handleSubmit}\n\t\t\t\t\t\u003e\n\t\t\t\t\t\tclear filters\n\t\t\t\t\t\u003c/button\u003e\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/form\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\tdispatch({ type: CLEAR_FILTERS });\n};\n```\n\n```js\nreducer.js;\n\nif (action.type === CLEAR_FILTERS) {\n\treturn {\n\t\t...state,\n\t\tsearch: \"\",\n\t\tsearchStatus: \"all\",\n\t\tsearchType: \"all\",\n\t\tsort: \"latest\",\n\t};\n}\n```\n\n#### Refactor Get All Jobs\n\n```js\nconst getJobs = async () =\u003e {\n\t// will add page later\n\tconst { search, searchStatus, searchType, sort } = state;\n\tlet url = `/jobs?status=${searchStatus}\u0026jobType=${searchType}\u0026sort=${sort}`;\n\tif (search) {\n\t\turl = url + `\u0026search=${search}`;\n\t}\n\tdispatch({ type: GET_JOBS_BEGIN });\n\ttry {\n\t\tconst { data } = await authFetch(url);\n\t\tconst { jobs, totalJobs, numOfPages } = data;\n\t\tdispatch({\n\t\t\ttype: GET_JOBS_SUCCESS,\n\t\t\tpayload: {\n\t\t\t\tjobs,\n\t\t\t\ttotalJobs,\n\t\t\t\tnumOfPages,\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\t// logoutUser()\n\t}\n\tclearAlert();\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\tconst { search, status, jobType, sort } = req.query;\n\tconst queryObject = {\n\t\tcreatedBy: req.user.userId,\n\t};\n\tif (search) {\n\t\tqueryObject.position = { $regex: search, $options: \"i\" };\n\t}\n\tif (status !== \"all\") {\n\t\tqueryObject.status = status;\n\t}\n\tif (jobType !== \"all\") {\n\t\tqueryObject.jobType = jobType;\n\t}\n\tlet result = Job.find(queryObject);\n\n\tif (sort === \"latest\") {\n\t\tresult = result.sort(\"-createdAt\");\n\t}\n\tif (sort === \"oldest\") {\n\t\tresult = result.sort(\"createdAt\");\n\t}\n\tif (sort === \"a-z\") {\n\t\tresult = result.sort(\"position\");\n\t}\n\tif (sort === \"z-a\") {\n\t\tresult = result.sort(\"-position\");\n\t}\n\n\tconst totalJobs = await result;\n\n\t// setup pagination\n\tconst limit = 10;\n\tconst skip = 1;\n\n\tresult = result.skip(skip).limit(limit);\n\t// 23\n\t// 4 7 7 7 2\n\tconst jobs = await result;\n\tres\n\t\t.status(StatusCodes.OK)\n\t\t.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\tconst { search, status, jobType, sort } = req.query;\n\tconst queryObject = {\n\t\tcreatedBy: req.user.userId,\n\t};\n\tif (search) {\n\t\tqueryObject.position = { $regex: search, $options: \"i\" };\n\t}\n\tif (status !== \"all\") {\n\t\tqueryObject.status = status;\n\t}\n\tif (jobType !== \"all\") {\n\t\tqueryObject.jobType = jobType;\n\t}\n\tlet result = Job.find(queryObject);\n\n\tif (sort === \"latest\") {\n\t\tresult = result.sort(\"-createdAt\");\n\t}\n\tif (sort === \"oldest\") {\n\t\tresult = result.sort(\"createdAt\");\n\t}\n\tif (sort === \"a-z\") {\n\t\tresult = result.sort(\"position\");\n\t}\n\tif (sort === \"z-a\") {\n\t\tresult = result.sort(\"-position\");\n\t}\n\n\t// setup pagination\n\tconst page = Number(req.query.page) || 1;\n\tconst limit = Number(req.query.limit) || 10;\n\tconst skip = (page - 1) * limit; //10\n\tresult = result.skip(skip).limit(limit);\n\t// 75\n\t// 10 10 10 10 10 10 10 5\n\tconst jobs = await result;\n\tres\n\t\t.status(StatusCodes.OK)\n\t\t.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\tconst { search, status, jobType, sort } = req.query;\n\tconst queryObject = {\n\t\tcreatedBy: req.user.userId,\n\t};\n\tif (search) {\n\t\tqueryObject.position = { $regex: search, $options: \"i\" };\n\t}\n\tif (status !== \"all\") {\n\t\tqueryObject.status = status;\n\t}\n\tif (jobType !== \"all\") {\n\t\tqueryObject.jobType = jobType;\n\t}\n\tlet result = Job.find(queryObject);\n\n\tif (sort === \"latest\") {\n\t\tresult = result.sort(\"-createdAt\");\n\t}\n\tif (sort === \"oldest\") {\n\t\tresult = result.sort(\"createdAt\");\n\t}\n\tif (sort === \"a-z\") {\n\t\tresult = result.sort(\"position\");\n\t}\n\tif (sort === \"z-a\") {\n\t\tresult = result.sort(\"-position\");\n\t}\n\n\t// setup pagination\n\tconst page = Number(req.query.page) || 1;\n\tconst limit = Number(req.query.limit) || 10;\n\tconst skip = (page - 1) * limit;\n\n\tresult = result.skip(skip).limit(limit);\n\n\tconst jobs = await result;\n\n\tconst totalJobs = await Job.countDocuments(queryObject);\n\tconst numOfPages = Math.ceil(totalJobs / limit);\n\n\tres.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\t\u003cWrapper\u003e\n\t\t\u003ch5\u003e\n\t\t\t{totalJobs} job{jobs.length \u003e 1 \u0026\u0026 \"s\"} found\n\t\t\u003c/h5\u003e\n\t\t\u003cdiv className='jobs'\u003e\n\t\t\t{jobs.map((job) =\u003e {\n\t\t\t\treturn \u003cJob key={job._id} {...job} /\u003e;\n\t\t\t})}\n\t\t\u003c/div\u003e\n\t\t{numOfPages \u003e 1 \u0026\u0026 \u003cPageBtnContainer /\u003e}\n\t\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\tconst { numOfPages, page } = useAppContext();\n\n\tconst prevPage = () =\u003e {\n\t\tconsole.log(\"prev page\");\n\t};\n\tconst nextPage = () =\u003e {\n\t\tconsole.log(\"next page\");\n\t};\n\n\treturn (\n\t\t\u003cWrapper\u003e\n\t\t\t\u003cbutton className='prev-btn' onClick={prevPage}\u003e\n\t\t\t\t\u003cHiChevronDoubleLeft /\u003e\n\t\t\t\tprev\n\t\t\t\u003c/button\u003e\n\n\t\t\t\u003cdiv className='btn-container'\u003ebuttons\u003c/div\u003e\n\n\t\t\t\u003cbutton className='next-btn' onClick={nextPage}\u003e\n\t\t\t\tnext\n\t\t\t\t\u003cHiChevronDoubleRight /\u003e\n\t\t\t\u003c/button\u003e\n\t\t\u003c/Wrapper\u003e\n\t);\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\treturn index + 1;\n});\n\nreturn (\n\t\u003cdiv className='btn-container'\u003e\n\t\t{pages.map((pageNumber) =\u003e {\n\t\t\treturn (\n\t\t\t\t\u003cbutton\n\t\t\t\t\ttype='button'\n\t\t\t\t\tclassName={pageNumber === page ? \"pageBtn active\" : \"pageBtn\"}\n\t\t\t\t\tkey={pageNumber}\n\t\t\t\t\tonClick={() =\u003e console.log(page)}\n\t\t\t\t\u003e\n\t\t\t\t\t{pageNumber}\n\t\t\t\t\u003c/button\u003e\n\t\t\t);\n\t\t})}\n\t\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\treturn { ...state, page: action.payload.page };\n}\n```\n\n```js\nPageBtnContainer.js;\n\nconst { changePage } = useAppContext();\nreturn (\n\t\u003cbutton\n\t\ttype='button'\n\t\tclassName={pageNumber === page ? \"pageBtn active\" : \"pageBtn\"}\n\t\tkey={pageNumber}\n\t\tonClick={() =\u003e changePage(pageNumber)}\n\t\u003e\n\t\t{pageNumber}\n\t\u003c/button\u003e\n);\n```\n\n#### Prev and Next Buttons\n\n```js\nPageBtnContainer.js;\nconst prevPage = () =\u003e {\n\tlet newPage = page - 1;\n\tif (newPage \u003c 1) {\n\t\t// newPage = 1\n\t\t// alternative\n\t\tnewPage = numOfPages;\n\t}\n\tchangePage(newPage);\n};\nconst nextPage = () =\u003e {\n\tlet newPage = page + 1;\n\tif (newPage \u003e numOfPages) {\n\t\t// newPage = numOfPages\n\t\t// alternative\n\t\tnewPage = 1;\n\t}\n\tchangePage(newPage);\n};\n```\n\n#### Trigger New Page\n\n```js\nappContext.js;\n\nconst getJobs = async () =\u003e {\n\tconst { page, search, searchStatus, searchType, sort } = state;\n\n\tlet url = `/jobs?page=${page}\u0026status=${searchStatus}\u0026jobType=${searchType}\u0026sort=${sort}`;\n\t// rest of the code\n};\n```\n\n```js\nJobsContainer.js;\n\nconst { page } = useAppContext();\nuseEffect(() =\u003e {\n\tgetJobs();\n}, [page, search, searchStatus, searchType, sort]);\n```\n\n```js\nreducer.js;\n\nif (action.type === HANDLE_CHANGE) {\n\t// set back to first page\n\n\treturn { ...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\tresponse.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\twindowMs: 15 * 60 * 1000, // 15 minutes\n\tmax: 10,\n\tmessage: \"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#### Deploy To Heroku\n\n- heroku login\n\n```js\npackage.json\n\n\"engines\": {\n    \"node\": \"16.x\"\n  },\n\"scripts\":{\n    \"build-client\": \"cd client \u0026\u0026 npm run build\",\n    \"install-client\": \"cd client \u0026\u0026 npm install\",\n    \"heroku-postbuild\": \"npm run install-client \u0026\u0026 npm run build-client\",\n}\n```\n\n```js\nProcfile\n\nweb: node server.js\n```\n\n- rm -rf .git\n- git init\n- git add .\n- git commit -m \"first commit\"\n- heroku create nameOfTheApp\n- git remote -v\n- add env variables\n- git push heroku main/master\n\n---\n\n#### Support\n\nFind the App Useful? [You can always buy me a coffee](https://www.buymeacoffee.com/johnsmilga)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodelikeagirl29%2Fmy-jobspace","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodelikeagirl29%2Fmy-jobspace","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodelikeagirl29%2Fmy-jobspace/lists"}