{"id":24829546,"url":"https://github.com/mdcodelab/carreerhub-react-redux-frontend-app","last_synced_at":"2026-04-17T11:30:50.405Z","repository":{"id":155456440,"uuid":"628070629","full_name":"mdcodelab/CarreerHub-react-redux-frontend-app","owner":"mdcodelab","description":"CRUD functionality","archived":false,"fork":false,"pushed_at":"2023-05-05T16:08:24.000Z","size":489,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T02:11:17.331Z","etag":null,"topics":["axios","react","redux-toolkit","styled-components","toastify"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mdcodelab.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":"2023-04-14T21:01:13.000Z","updated_at":"2023-05-06T20:01:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"b295b9a9-6991-428a-b7e7-2de4cfb2b9de","html_url":"https://github.com/mdcodelab/CarreerHub-react-redux-frontend-app","commit_stats":null,"previous_names":["mdcodelab/carreerhub-react-redux-frontend-app"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdcodelab%2FCarreerHub-react-redux-frontend-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdcodelab%2FCarreerHub-react-redux-frontend-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdcodelab%2FCarreerHub-react-redux-frontend-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdcodelab%2FCarreerHub-react-redux-frontend-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mdcodelab","download_url":"https://codeload.github.com/mdcodelab/CarreerHub-react-redux-frontend-app/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245573907,"owners_count":20637674,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["axios","react","redux-toolkit","styled-components","toastify"],"created_at":"2025-01-30T23:26:25.952Z","updated_at":"2026-04-17T11:30:50.350Z","avatar_url":"https://github.com/mdcodelab.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# npx create-react-app career-hub\n\n- resource [Generate Favicons](https://favicon.io/)\n\n#### 1) Normalize.css and Global Styles\n\n- CSS in JS (styled-components)\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\n--------------------------------------------------------------------\n#### 2) Landing Page - Setup\n- npm install styled-components\n\n\n--------------------------------------------------------------------\n#### 3) Logo Component\n\n----------------------------------------------------------------------\n\n#### 4) React Router\n\n-------------------------------------------------------------------\n\n#### 5) Error Page\n\n--------------------------------------------------------------------\n\n#### 6) Register Page - Setup\n\n----------------------------------------------------------------\n\n#### 7) FormRow Component\n\n----------------------------------------------------------------\n\n#### 8) Toggle Member (in the Register)\n\n-------------------------------------------------------------\n\n#### 9) Handle Change and Empty Values\n\n----------------------------------------------------------\n\n#### 10) React Toastify\n\n[React Toastify](https://www.npmjs.com/package/react-toastify)\n\n\nnpm install --save react-toastify\n\n\nApp.js:\n\nimport { ToastContainer } from 'react-toastify';\nimport 'react-toastify/dist/ReactToastify.css';\n\nreturn (\n\u003cRouter\u003e\n\u003cRoutes/\u003e\n\u003cToastContainer/\u003e\n\u003cRouter/\u003e\n)\n\nRegister.js:\nimport { toast } from 'react-toastify';\n\nif (!email || !password || (!isMember \u0026\u0026 !name)) {\n  toast.error('Please Fill Out All Fields');\n  return;\n}\n\n- modifications\n\nposition\n\n\u003cToastContainer position='top-center' \u003e\n\n--------------------------------------------------------------------\n\n\n### 11) User Slice - Setup\n\n- features/user/userSlice.js\n\n```js\nimport { createSlice, createAsyncThunk } from '@reduxjs/toolkit';\nimport { toast } from 'react-toastify';\n\nconst initialState = {\n  isLoading: false,\n  user: null,\n};\n\nconst userSlice = createSlice({\n  name: 'user',\n  initialState,\n});\n\nexport default userSlice.reducer;\n```\n\n- create store.js\n\n```js\nimport { configureStore } from '@reduxjs/toolkit';\n\nimport userSlice from './features/user/userSlice';\n\nexport const store = configureStore({\n  reducer: {\n    user: userSlice,\n  },\n});\n```\n\n- index.js\n\n```js\nimport { store } from './store';\nimport { Provider } from 'react-redux';\n\nroot.render(\n  \u003cProvider store={store}\u003e\n    \u003cApp tab='home' /\u003e\n  \u003c/Provider\u003e\n);\n```\n```sh\nnpm install @reduxjs/toolkit react-redux\n```\n\n---------------------------------------------------------------------\n#### 12) RegisterUser, LoginUser - Placeholders\n\n- userSlice.js\n\n```js\nexport const registerUser = createAsyncThunk(\n  'user/registerUser',\n  async (user, thunkAPI) =\u003e {\n    console.log(`Register User : ${user}`)\n);\nexport const loginUser = createAsyncThunk(\n  'user/loginUser',\n  async (user, thunkAPI) =\u003e {\n    console.log(`Login User : ${user}`)\n});\n```\n\n- Register.js\n\n```js\nimport { useSelector, useDispatch } from 'react-redux';\nimport { loginUser, registerUser } from '../features/user/userSlice';\n\n\nconst Register = () =\u003e {\n  const dispatch = useDispatch();\n  const { isLoading, user } = useSelector((store) =\u003e store.user);\n\n\nconst onSubmit = (e) =\u003e {\n    e.preventDefault();\n    const { name, email, password, isMember } = values;\n    if (!email || !password || (!isMember \u0026\u0026 !name)) {\n      toast.error('Please Fill Out All Fields');\n      return;\n    }\n    if (isMember) {\n      dispatch(loginUser({ email: email, password: password }));\n      return;\n    }\n    dispatch(registerUser({ name, email, password }));\n  };\n```\n--------------------------------------------------------------\n\n#### 13) HTTP Methods\n\n- GET - get resources from the server\n- POST - submit resource to the server\n- PUT/PATCH - modify resource on the server\n- DELETE - delete resource form the server\n\n```js\n// GET\naxios.get(url, options);\n// POST\naxios.post(url, resource, options);\n// PATCH\naxios.patch(url, resource, options);\n// DELETE\naxios.delete(url, options);\n```\n\n```sh\nnpm install axios\n```\n\n----------------------------------------------------------------------\n#### 14) API\n\n- Root URL\n- https://jobify-prod.herokuapp.com/api/v1/toolkit\n\nNODE\n###### Register USER\n\n- https://jobify-prod.herokuapp.com/api/v1/toolkit/auth/register\n\n- POST /auth/register\n- {name:'john',email:'john@gmail.com',password:'secret'}\n- sends back the user object with token\n\n###### Register USER - TESTING()\n\n- POST /auth/testingRegister\n- {name:'john',email:'john@gmail.com',password:'secret'}\n- sends back the user object with token\n\n###### Login USER\n\n- POST /auth/login\n- {email:'john@gmail.com',password:'secret'}\n- sends back the user object with token\n\n###### Update USER\n\n- PATCH /auth/updateUser\n- { email:'john@gmail.com', name:'john', lastName:'smith', location:'my location' }\n- sends back the user object with token\n---------------------------------------------------------------\n\n\n  #### 15) Custom Axios Instance\n\n- utils/axios.js\n\n```js\nimport axios from 'axios';\n\nconst customFetch = axios.create({\n  baseURL: 'https://jobify-prod.herokuapp.com/api/v1/toolkit',\n});\n\nexport default customFetch;\n```\n\nuserSlice.js\n``js\nimport customFetch from '../../utils/axios';\n\n//fir testing:\nexport const registerUser = createAsyncThunk(\n  'user/registerUser',\n  async (user, thunkAPI) =\u003e {\n    try {\n      const resp = await customFetch.post('/auth/testingRegister', user);\n      console.log(resp);\n    } catch (error) {\n      console.log(error.response);\n    }\n  }\n);\n```\n\n------------------------------------------------------------\n#### 16) Register User\n\nuserSlice.js\n\n```js\nexport const registerUser = createAsyncThunk(\n  'user/register',\n  async (user, thunkAPI) =\u003e {\n    try {\n      const resp = await customFetch.post('/auth/register', user);\n      return resp.data;\n    } catch (error) {\n      return thunkAPI.rejectWithValue(error.response.data.msg);\n    }\n  }\n)\n   extraReducers: (builder) =\u003e {\n    builder\n    .addCase(registerUser.pending, (state) =\u003e {\n        state.isLoading = true;})\n\n        .addCase(registerUser.fulfilled, (state, { payload }) =\u003e {\n            const { user } = payload;\n            state.isLoading = false;\n            state.user = user;\n            toast.success(`Hello There ${user.name}`);\n          })\n\n          .addCase(registerUser.rejected, (state, { payload }) =\u003e {\n            state.isLoading = false;\n            toast.error(payload);\n          })  \n}\n```\n----------------------------------------------------------\n#### 17) Login User\n\nuserSlice.js\n\n```js\nexport const loginUser = createAsyncThunk(\n  'user/login',\n  async (user, thunkAPI) =\u003e {\n    try {\n      const resp = await customFetch.post('/auth/register', user);\n      return resp.data;\n    } catch (error) {\n      return thunkAPI.rejectWithValue(error.response.data.msg);\n    }\n  }\n)\nextraReducers:\n      .addCase(loginUser.fulfilled, (state, { payload }) =\u003e {\n        const { user } = payload;\n        state.isLoading = false;\n        state.user = user;\n        addUserToLocalStorage(user);\n\n        toast.success(`Welcome Back ${user.name}`);\n      })\n      .addCase(loginUser.rejected, (state, { payload }) =\u003e {\n        state.isLoading = false;\n        toast.error(payload);\n      })\n  }\n```\n\n### 18) Login localStorage\n\n- utils/localStorage.js\n\n```js\nexport const addUserToLocalStorage = (user) =\u003e {\n  localStorage.setItem('user', JSON.stringify(user));\n};\n\nexport const removeUserFromLocalStorage = () =\u003e {\n  localStorage.removeItem('user');\n};\n\nexport const getUserFromLocalStorage = () =\u003e {\n  const result = localStorage.getItem('user');\n  const user = result ? JSON.parse(result) : null;\n  return user;\n};\n```\n\n- invoke getUserFromLocalStorage when app loads (set it equal to user)\n\n```js\nconst initialState = {\n  isLoading: false,\n  user: getUserFromLocalStorage(),\n};\n\n builder\n    //registerUser\n    .addCase(registerUser.pending, (state) =\u003e {\n        state.isLoading = true;})\n\n        .addCase(registerUser.fulfilled, (state, { payload }) =\u003e {\n            const { user } = payload;\n            state.isLoading = false;\n            state.user = user;\n            addUserToLocalStorage(user);/////////////////\n            toast.success(`Hello There ${user.name}!`);\n          })\n\n          .addCase(registerUser.rejected, (state, { payload }) =\u003e {\n            state.isLoading = false;\n            toast.error(payload);\n          })  \n          //loginUser\n          .addCase(loginUser.pending, (state) =\u003e {\n            state.isLoading = true;})\n    \n            .addCase(loginUser.fulfilled, (state, { payload }) =\u003e {\n                const { user } = payload;\n                state.isLoading = false;\n                state.user = user;\n                addUserToLocalStorage(user); ////////////////////////\n                toast.success(`Welcome back ${user.name}!`);\n              })\n    \n            .addCase(loginUser.rejected, (state, { payload }) =\u003e {\n                state.isLoading = false;\n                toast.error(payload);\n              })  \n}\n\n---------------------------------------------------------------------------------\n\n### 19) Programmatically Navigate To Dashboard\nRegister.js:\n\nconst location=useLocation();\n\n    React.useEffect(() =\u003e {\n      if(user) {\n        setTimeout(() =\u003e {\n          window.location.href=\"/\"\n        }, 2000)\n      }\n    }, [user])\n\n--------------------------------------------------------------------------------\n#### 20) Setup Dashboard Pages\n\n- remove Dashboard.js\n- create Dashboard Folder\n- create Stats, Profile, AddJob, AllJobs, SharedLayout\n\nApp.js\n\n```js\nimport {\n  AllJobs,\n  Profile,\n  SharedLayout,\n  Stats,\n  AddJob,\n} from './dashboardPages/...';\n\nfunction App() {\n  return (\n    \u003cBrowserRouter\u003e\n      \u003cRoutes\u003e\n        \u003cRoute path='/' element={\u003cSharedLayout /\u003e}\u003e\n          \u003cRoute index element={\u003cStats /\u003e} /\u003e\n          \u003cRoute path='all-jobs' element={\u003cAllJobs /\u003e} /\u003e\n          \u003cRoute path='add-job' element={\u003cAddJob /\u003e} /\u003e\n          \u003cRoute path='profile' element={\u003cProfile /\u003e} /\u003e\n        \u003c/Route\u003e\n        \u003cRoute path='register' element={\u003cRegister /\u003e} /\u003e\n        \u003cRoute path='landing' element={\u003cLanding /\u003e} /\u003e\n        \u003cRoute path='*' element={\u003cError /\u003e} /\u003e\n      \u003c/Routes\u003e\n      \u003cToastContainer position='top-center' /\u003e\n    \u003c/BrowserRouter\u003e\n  );\n}\n```\n--------------------------------------------------------\n### 21) Navbar, SmallSidebar, BigSidebar\n\n- create Navbar, SmallSidebar, BigSidebar in components\n\n```js\nSharedLayout.js;\n\nimport { Outlet } from 'react-router-dom';\nimport Navbar etc.\n\nconst SharedLayout = () =\u003e {\n  return (\n    \u003c\u003e\n      \u003cWrapper\u003e\n        \u003cmain className='dashboard'\u003e\n          \u003cSmallSidebar /\u003e\n          \u003cBigSidebar /\u003e\n          \u003cdiv\u003e\n            \u003cNavbar /\u003e\n            \u003cdiv className='dashboard-page'\u003e\n              \u003cOutlet /\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/main\u003e\n      \u003c/Wrapper\u003e\n    \u003c/\u003e\n  );\n};\n\n------------------------------------------------------------\n#### 22) Shared Layout CSS\n\n- BigSidebar,SmallSidebar and Navbar\n------------------------------------------------------------\n\n### 23) Navbar setup\n-----------------------------------------------------------\n### 24) Toggle Dropdown\n\n```js\nNavbar.js\nconst [logout, setLogout]=React.useState(false);\n\n  return (\n    \u003cWrapper\u003e\n    \u003cdiv className='nav-center'\u003e\n      \u003cbutton type='button' className='toggle-btn' onClick={()=\u003e console.log(\"toggle\")}\u003e\n        \u003cFaAlignLeft /\u003e\u003c/button\u003e\n      \u003cdiv\u003e\n        \u003cWrapperLogo\u003e \u003cLogo /\u003e\u003c/WrapperLogo\u003e\n        \u003ch3 className='logo-text'\u003edashboard\u003c/h3\u003e\n      \u003c/div\u003e\n      \u003cdiv className='btn-container'\u003e\n        \u003cbutton type='button'className='btn' onClick={() =\u003e setLogout(!logout)}\u003e\n          \u003cFaUserCircle /\u003e\n          {user?.name}\n          \u003cFaCaretDown /\u003e\n        \u003c/button\u003e\n        \u003cdiv className={logout ? \"dropdown show-dropdown\" : \"dropdown\"}\u003e\n          \u003cbutton type='button' className='dropdown-btn' \n          onClick={() =\u003e console.log(\"logout\")}\u003e\n            Logout\n          \u003c/button\u003e\n        \u003c/div\u003e\n        \n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/Wrapper\u003e\n    )\n    --------------------------------------------------------\n\n    ### 25) Toggle Sidebar\n  \nuserSlice.js\n\n```js\nconst initialState = {\n  isLoading: false,\n  isSidebarOpen: false,\n  user: getUserFromLocalStorage(),\n};\n\nreducers: {\n    toggleSidebar: (state) =\u003e {\n      state.isSidebarOpen = !state.isSidebarOpen;\n    },\n  },\n\nexport const { toggleSidebar } = userSlice.actions;\n\n```\n\nNavbar.js\n\n```js\nimport { toggleSidebar } from '../features/user/userSlice';\n\n\n\u003cbutton type='button' className='toggle-btn' onClick={()=\u003e dispatch(toggleSidebar())}\u003e\n  \u003cFaAlignLeft /\u003e\n\u003c/button\u003e;\n---------------------------------------------------\n\n### 26) Logout functionality\nuserSlice.js\n\n```js\nreducers: {\n    logoutUser: (state) =\u003e {\n      state.user = null;\n      state.isSidebarOpen = false;\n      localStorage.removeItem(\"user\")\n    }\n\nexport const { logoutUser, toggleSidebar } = userSlice.actions;\n\n```\n\nNavbar.js\n\n```js\nimport { toggleSidebar, logoutUser } from '../features/user/userSlice';\n\n\u003cdiv className={showLogout ? 'dropdown show-dropdown' : 'dropdown'}\u003e\n  \u003cbutton\n    type='button'\n    className='dropdown-btn'\n    onClick={() =\u003e {\n      dispatch(logoutUser());\n    }}\n  \u003e\n    logout\n  \u003c/button\u003e\n\u003c/div\u003e;\n```\n--------------------------------------------------------------------\n### 27) Restricted route\n pages/ProtectedRoute.js\n\n```js\nimport { Navigate } from 'react-router-dom';\nimport { useSelector } from 'react-redux';\nconst ProtectedRoute = ({ children }) =\u003e {\n  const { user } = useSelector((store) =\u003e store.user);\n  if (!user) {\n    return \u003cNavigate to='/landing' /\u003e;\n  }\n  return children;\n};\n\nexport default ProtectedRoute;\n```\n\nApp.js\n\n```js\n\u003cRoute\n  path='/'\n  element={\n    \u003cProtectedRoute\u003e\n      \u003cSharedLayout /\u003e\n    \u003c/ProtectedRoute\u003e\n  }\n\u003e\n  ...\n\u003c/Route\u003e\n```\n--------------------------------------------------------------\n#### 28) Small Sidebar - Setup\n-------------------------------------------------------------\n### 29) Small Sidebar Toggle\n\nmallSidebar.js;\n\n```js\nimport { toggleSidebar } from '../features/user/userSlice';\n\n\nconst { isSidebarOpen } = useSelector((store) =\u003e store.user);\n  const dispatch = useDispatch();\n\n\nreturn (\n  \u003cdiv className={isSidebarOpen ? 'sidebar-container show-sidebar' : 'sidebar-container'}\u003e\n    \u003cdiv className='content'\u003e\n        \u003cbutton type='button' className='close-btn' onClick={()=\u003e dispatch(toggleSidebar())}\u003e\n          \u003cFaTimes /\u003e\n        \u003c/button\u003e\n\n);\n```\n---------------------------------------------------------------\n#### 30) Setup Links\n--utils -links.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  { id: 1, text: 'stats', path: '/', icon: \u003cIoBarChartSharp /\u003e },\n  { id: 2, text: 'all jobs', path: 'all-jobs', icon: \u003cMdQueryStats /\u003e },\n  { id: 3, text: 'add job', path: 'add-job', icon: \u003cFaWpforms /\u003e },\n  { id: 4, text: 'profile', path: 'profile', icon: \u003cImProfile /\u003e },\n];\n\nexport default links;\n--------------------------------------------------------------------\n#### 31) Small Sidebar - Nav Links\n\n-------------------------------------------------------------------\n\n#### 32) Big Sidebar - setup\n\n------------------------------------------------------------------\n### 33) Big Profile - setup\n\n----------------------------------------------------------------\n\n### 33) Profile Page structure\nProfile.js:\n```js\nimport React from 'react';\nimport FormRow from '../components/FormRow';\nimport { toast } from \"react-toastify\";\nimport styled from 'styled-components';\nimport { useSelector, useDispatch } from 'react-redux';\n\n\n\nfunction Profile() {\n  const {isLoading, user}=useSelector((store) =\u003e store.user);\n  const dispatch=useDispatch();\n\n  const [userData, setUserData]=React.useState({\n    name: user?.name || \"\",\n    email: user?.email || \"\",\n    lastName: user?.lastName || \"\",\n    location: user?.location || \"\"\n  });\n\n  const handleSubmit = (e) =\u003e {\n      e.preventDefault();\n      const {name, email, lastName, location}=userData;\n      if(!name || !email || !lastName || !location) {\n        toast.error(\"Please fill out all fields!\");\n        return;\n      }\n  }\n\n  const handleChange = (e) =\u003e {\n    const name = e.target.name;\n    const value = e.target.value;\n    setUserData({...userData, [name]: value})\n  }\n  \n\n  return (\n    \u003cWrapper\u003e\n        \u003cform className=\"form\" on Submit ={handleSubmit}\u003e\n            \u003ch3\u003eProfile\u003c/h3\u003e\n            \u003cdiv className=\"form-center\"\u003e\n                \u003cFormRow type=\"text\" name=\"name\" value={userData.name} onChange={handleChange}\u003e\u003c/FormRow\u003e\n                \u003cFormRow type=\"text\" labelText=\"last name\" name=\"lastName\" value={userData.lastName} onChange={handleChange}\u003e\u003c/FormRow\u003e\n                \u003cFormRow type=\"email\" name=\"email\" value={userData.email} onChange={handleChange}\u003e\u003c/FormRow\u003e\n                \u003cFormRow type=\"text\" name=\"location\" value={userData.location} onChange={handleChange}\u003e\u003c/FormRow\u003e\n                \u003cbutton type=\"submit\" className=\"btn btn-block\" disabled={isLoading}\u003e\n                  {isLoading ? \"Pleased wait...\" : \"Save changes\"}\n                \u003c/button\u003e\n            \u003c/div\u003e\n\n        \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n}\n```\n------------------------------------------------------------------------\n### 34) Update user\n- PATCH /auth/updateUser\n- { email:'john@gmail.com', name:'john', lastName:'smith', location:'my location' }\n- authorization header : 'Bearer token'\n- sends back the user object with token\n\nuserSlice.js:\n```js\nexport const updateUser = createAsyncThunk('user/updateUser', async (user, thunkAPI) =\u003e {\n      try {\n        const resp = await customFetch.patch('/auth/updateUser', user, {\n          headers: {\n            authorization: `Bearer ${thunkAPI.getState().user.user.token}`,\n          },\n        });\n        return resp.data;\n      } catch (error) {\n        console.log(error.response);\n        return thunkAPI.rejectWithValue(error.response.data.msg);\n      }\n    }\n  );\n  -extraReducer:\n  .addCase(updateUser.pending, (state) =\u003e {\n                state.isLoading = true;})\n        \n                .addCase(updateUser.fulfilled, (state, { payload }) =\u003e {\n                    const { user } = payload;\n                    state.isLoading = false;\n                    state.user = user;\n                    addUserToLocalStorage(user);\n                    toast.success(\"User Updated!\");\n                  })\n        \n                .addCase(updateUser.rejected, (state, { payload }) =\u003e {\n                    state.isLoading = false;\n                    toast.error(payload);\n                  }) \n\n```\nProfile.js:\n```js\nconst handleSubmit = (e) =\u003e {\n    e.preventDefault();\n    const {name, email, lastName, location}=userData;\n    if(!name || !email || !lastName || !location) {\n      toast.error(\"Please fill out all fields!\");\n      return;\n    }\n    dispatch(updateUser(userData));\n}\n```\n------------------------------------------------------------------\n### 35) Unauthorized - Logout User\n\nuserSlice.js\n\n```js\nexport const updateUser = createAsyncThunk(\n  'user/updateUser',\n  async (user, thunkAPI) =\u003e {\n    try {\n      const resp = await customFetch.patch('/auth/updateUser', user, {\n        headers: {\n          // authorization: `Bearer ${thunkAPI.getState().user.user.token}`,\n          authorization: `Bearer `,\n        },\n      });\n\n      return resp.data;\n    } catch (error) {\n      // console.log(error.response);\n      if (error.response.status === 401) {\n        thunkAPI.dispatch(logoutUser());\n        return thunkAPI.rejectWithValue('Unauthorized! Logging Out...');\n      }\n      return thunkAPI.rejectWithValue(error.response.data.msg);\n    }\n  }\n);\n\n// logoutUser\nlogoutUser: (state) =\u003e {\n      state.user = null;\n      state.isSidebarOpen = false;\n      toast.success('Logout Successful!');\n      removeUserFromLocalStorage();\n    },\n```\n---------------------------------------------------------------------\n#### 36) Job Slice\n\n- features/job/jobSlice.js\n\n```js\nimport { createSlice, createAsyncThunk } from '@reduxjs/toolkit';\nimport { toast } from 'react-toastify';\nimport customFetch from '../../utils/axios';\nimport { getUserFromLocalStorage } from '../../utils/localStorage';\n\nconst initialState = {\n  isLoading: false,\n  position: '',\n  company: '',\n  jobLocation: '',\n  jobTypeOptions: ['full-time', 'part-time', 'remote', 'internship'],\n  jobType: 'full-time',\n  statusOptions: ['interview', 'declined', 'pending'],\n  status: 'pending',\n  isEditing: false,\n  editJobId: '',\n};\n\nconst jobSlice = createSlice({\n  name: 'job',\n  initialState,\n});\n\nexport default jobSlice.reducer;\n```\n\nstore.js\n\n```js\nimport jobSlice from './features/job/jobSlice';\n\nexport const store = configureStore({\n  reducer: {\n    user: userSlice,\n    job: jobSlice,\n  },\n});\n```\n------------------------------------------------------------------------\n#### 37) AddJob Structure\n\nAddJobs.js\n````js\nimport React from 'react';\nimport styled from \"styled-components\";\nimport { toast } from \"react-toastify\";\nimport { useSelector, useDispatch } from 'react-redux';\nimport FormRow from \"../components/FormRow\";\n\nfunction AddJob() {\n  const {isLoading, position, company, jobLocation, jobType, jobTypeOptions, status,\n  statusOptions, isEditing, editJobId}=useSelector((state)=\u003e state.job);\n  const dispatch=useDispatch();\n\n  const onChange = (e) =\u003e {\n    const name = e.target.name;\n    const value = e.target.value;\n    console.log(name, value);\n  }\n\n  const onSubmit = (e) =\u003e {\n    e.preventDefault();\n    if(!position || !company || !jobLocation) {\n      toast.warning(\"Please Fill Out All Fields!\");\n      return;\n    }\n  }\n  \n  return (\n    \u003cWrapper\u003e\n          \u003cform className=\"form\"\u003e\n            \u003ch3\u003e{isEditing ? \"Edit Job\" : \"Add Job\"}\u003c/h3\u003e\n            \u003cdiv className=\"form-center\"\u003e\n              {/* position */}\n              \u003cFormRow type=\"text\" name=\"position\" value={position} onChange={onChange}\u003e\u003c/FormRow\u003e\n                {/* company */}\n              \u003cFormRow type=\"text\" name=\"company\" value={company} onChange={onChange}\u003e\u003c/FormRow\u003e\n                {/* jobLocation */}\n              \u003cFormRow type=\"text\" name=\"jobLocation\" labelText=\"Job Location\" \n              value={jobLocation} onChange={onChange}\u003e\u003c/FormRow\u003e\n              \u003cdiv className=\"btn-container\"\u003e\n                \u003cbutton type=\"button\" className=\"btn btn-block clear-btn\" \n                    onClick={()=\u003e console.log(\"clear values\")}\u003eClear\u003c/button\u003e\n                \u003cbutton type=\"submit\" className=\"btn btn-block submit-btn\" disabled={isLoading}\n                    onClick={onSubmit}\u003eSubmit\u003c/button\u003e\n              \u003c/div\u003e\n            \u003c/div\u003e\n          \u003c/form\u003e\n    \u003c/Wrapper\u003e\n  );\n}\n``\n------------------------------------------------------------------------------\n#### 38) FormRowSelect\n\n```js\n// job status\n\nreturn (\n  \u003cdiv className='form-row'\u003e\n    \u003clabel htmlFor='status' className='form-label'\u003e\n      status\n    \u003c/label\u003e\n    \u003cselect\n      name='status'\n      value={status}\n      onChange={handleJobInput}\n      className='form-select'\n    \u003e\n      {statusOptions.map((itemValue, index) =\u003e {\n        return (\n          \u003coption key={index} value={itemValue}\u003e\n            {itemValue}\n          \u003c/option\u003e\n        );\n      })}\n    \u003c/select\u003e\n  \u003c/div\u003e\n);\n```\n\n- FormRowSelect.js\n\n```js\nconst FormRowSelect = ({ labelText, name, value, handleChange, list }) =\u003e {\n  return (\n    \u003cdiv className='form-row'\u003e\n      \u003clabel htmlFor={name} className='form-label'\u003e\n        {labelText || name}\n      \u003c/label\u003e\n      \u003cselect\n        name={name}\n        value={value}\n        id={name}\n        onChange={handleChange}\n        className='form-select'\n      \u003e\n        {list.map((itemValue, index) =\u003e {\n          return (\n            \u003coption key={index} value={itemValue}\u003e\n              {itemValue}\n            \u003c/option\u003e\n          );\n        })}\n      \u003c/select\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\nAddJob.js\n\n```js\n\n  /* job status */\n\u003cFormRowSelect\n  name='status'\n  value={status}\n  handleChange={handleJobInput}\n  list={statusOptions}\n/\u003e\n\n\u003cFormRowSelect\n  name='jobType'\n  labelText='job type'\n  value={jobType}\n  handleChange={handleJobInput}\n  list={jobTypeOptions}\n/\u003e\n```\n------------------------------------------------------------------\n\n### 39) Handle Change reducer\n```js\n    // reducers\n    handleChange: (state, { payload: { name, value } }) =\u003e {\n      state[name] = value;\n    },\n\nexport const { handleChange } = jobSlice.actions;\n```\n\nAddJob.js\n\n```js\nimport { handleChange } from '../../features/job/jobSlice';\n\nconst handleJobInput = (e) =\u003e {\n  const name = e.target.name;\n  const value = e.target.value;\n  dispatch(handleChange({ name, value }));\n};\n```\n------------------------------------------------------------\n### 40) User Slice - handleClear Reducer\n\n```js\n\n    // reducers\n    handleClear: () =\u003e {\n      return {\n        ...initialState\n      };\n      return initialState\n    },\n\nexport const { handleChange, handleClear } = jobSlice.actions;\n\n```\n\n\n#### 41) Add Job\njobSlice.js\n\n```js\nexport const createJob = createAsyncThunk('job/createJob', async (job, thunkAPI) =\u003e {\n      try {\n        const resp = await customFetch.post('/jobs', job, {\n          headers: {\n            authorization: `Bearer ${thunkAPI.getState().user.user.token}`,\n            //authorization: `Bearer`,\n          },\n        });\n        thunkAPI.dispatch(handleClear());\n        return resp.data;\n      } catch (error) {\n        if (error.response.status === 401) {\n            thunkAPI.dispatch(logoutUser());\n            return thunkAPI.rejectWithValue('Unauthorized! Logging Out...');\n          }\n        // basic setup\n        return thunkAPI.rejectWithValue(error.response.data.msg);\n      }\n    }\n  );\n  `````\n  / extra reducers\n\nextraReducers: {\n    builder: (state) =\u003e {\n      state.isLoading = true;\n    },\n    builder: (state, action) =\u003e {\n      state.isLoading = false;\n      toast.success('Job Created');\n    },\n    builder: (state, { payload }) =\u003e {\n      state.isLoading = false;\n      toast.error(payload);\n    },\n}\n```\n\nAddJob.js\n\n```js\nimport {\n  clearValues,\n  handleChange,\n  createJob,\n} from '../../features/job/jobSlice';\n\nconst handleSubmit = (e) =\u003e {\n  e.preventDefault();\n\n  if (!position || !company || !jobLocation) {\n    toast.error('Please Fill Out All Fields');\n    return;\n  }\n\n  dispatch(createJob({ position, company, jobLocation, jobType, status }));\n};\n```\n----------------------------------------------------------------------\n\n#### 42) Use Existing User Location\n\nAddJob.js\n\n```js\nconst { user } = useSelector((store) =\u003e store.user);\n\nuseEffect(() =\u003e {\n  // eventually will check for isEditing\n  if (!isEditing) {\n    dispatch(handleChange({ name: 'jobLocation', value: user.location }));\n  }\n}, []);\n```\n\njobSlice.js\n\n```js\n\n    // reducers\n    clearValues: () =\u003e {\n      return {\n        ...initialState,\n        jobLocation: getUserFromLocalStorage()?.location || '',\n      };\n    },\n```\n------------------------------------------------------------------\n\n#### 43) Logout Message\nuserSlice.js\n\n```js\nlogoutUser: (state,{payload}) =\u003e {\n      state.user = null;\n      state.isSidebarOpen = false;\n      removeUserFromLocalStorage();\n      if(payload){\n        toast.success(payload)\n      }\n    },\n\n```\nNavbar.js\n\n```js\n\u003cbutton\n  type='button'\n  className='dropdown-btn'\n  onClick={() =\u003e dispatch(logoutUser('Logging out...'))}\n\u003e\n  logout\n\u003c/button\u003e\n```\n------------------------------------------------------------\n\n#### 44) AllJobs Slice\n\n- features/allJobs/allJobsSlice.js\n\n```js\nimport { createSlice, createAsyncThunk } from '@reduxjs/toolkit';\nimport { toast } from 'react-toastify';\nimport customFetch from '../../utils/axios';\n\nconst initialFiltersState = {\n  search: '',\n  searchStatus: 'all',\n  searchType: 'all',\n  sort: 'latest',\n  sortOptions: ['latest', 'oldest', 'a-z', 'z-a'],\n};\n\nconst initialState = {\n  isLoading: false,\n  jobs: [],\n  totalJobs: 0,\n  numOfPages: 1,\n  page: 1,\n  stats: {},\n  monthlyApplications: [],\n  ...initialFiltersState,\n};\n\nconst allJobsSlice = createSlice({\n  name: 'allJobs',\n  initialState,\n});\n\nexport default allJobsSlice.reducer;\n```\n\nstore.js\n\n```js\nimport { configureStore } from '@reduxjs/toolkit';\n\nimport userSlice from './features/user/userSlice';\nimport jobSlice from './features/job/jobSlice';\nimport allJobsSlice from './features/allJobs/allJobsSlice';\n\nexport const store = configureStore({\n  reducer: {\n    user: userSlice,\n    job: jobSlice,\n    allJobs: allJobsSlice,\n  },\n});\n```\n-----------------------------------------------------------------\n#### 45) AllJobs Page Structure\n\n- create\n- SearchContainer.js\n- JobsContainer.js\n- Job.js\n- import/export\n\nAllJobs.js\n\n```js\nimport { JobsContainer, SearchContainer } from '../../components';\n\nconst AllJobs = () =\u003e {\n  return (\n    \u003c\u003e\n      \u003cSearchContainer /\u003e\n      \u003cJobsContainer /\u003e\n    \u003c/\u003e\n  );\n};\n```\n-------------------------------------------------------------\n#### 46) JobsContainer.js\n\n`` JobsContainer.js\nimport Job from './Job';\nimport Wrapper from '../assets/wrappers/JobsContainer';\nimport { useSelector, useDispatch } from 'react-redux';\nimport Loader\n\nconst JobsContainer = () =\u003e {\n  const { jobs, isLoading } = useSelector((store) =\u003e store.allJobs);\n  const dispatch = useDispatch();\n\n  if (isLoading) {\n    return (\n      \u003cWrapper\u003e\n        \u003cLoader\u003e\u003c/Loader\u003e\n      \u003c/Wrapper\u003e\n    );\n  }\n\n  if (jobs.length === 0) {\n    return (\n      \u003cWrapper\u003e\n        \u003ch2\u003eNo jobs to display...\u003c/h2\u003e\n      \u003c/Wrapper\u003e\n    );\n  }\n\n  return (\n    \u003cWrapper\u003e\n      \u003ch5\u003ejobs info\u003c/h5\u003e\n      \u003cdiv className='jobs'\u003e\n        {jobs.map((job) =\u003e {\n          return \u003cJob key={job._id} {...job} /\u003e;\n        })}\n      \u003c/div\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\nexport default JobsContainer;\n```\n\nLoader.js\n\n```js\nconst Loader = ({ center }) =\u003e {\n  return \u003cdiv className={center ? 'loading loading-center' : 'loading'}\u003e\u003c/div\u003e;\n};\n\nexport default Loader;\n```\n--------------------------------------------------------------\n#### 47) GetAllJobs Request\n\n- GET /jobs\n- authorization header : 'Bearer token'\n- returns {jobs:[],totalJobs:number, numOfPages:number }\n\nallJobsSlice.js\n\n``` js\nexport const getAllJobs = createAsyncThunk(\n  'allJobs/getJobs',\n  async (_, thunkAPI) =\u003e {\n    let url = `/jobs`;\n\n    try {\n      const resp = await customFetch.get(url, {\n        headers: {\n          authorization: `Bearer ${thunkAPI.getState().user.user.token}`,\n        },\n      });\n\n      return resp.data;\n    } catch (error) {\n      return thunkAPI.rejectWithValue(error.response.data.msg);\n    }\n  }\n);\n\n// extra reducers\n\nextraReducers: {\n    [getAllJobs.pending]: (state) =\u003e {\n      state.isLoading = true;\n    },\n    [getAllJobs.fulfilled]: (state, { payload }) =\u003e {\n      state.isLoading = false;\n      state.jobs = payload.jobs;\n    },\n    [getAllJobs.rejected]: (state, { payload }) =\u003e {\n      state.isLoading = false;\n      toast.error(payload);\n    },\n}\n\n```\n\nJobsContainer.js\n\n```js\nimport { getAllJobs } from '../features/allJobs/allJobsSlice';\n\nuseEffect(() =\u003e {\n  dispatch(getAllJobs());\n}, []);\n```\n------------------------------------------------------------------\n\n#### 48) Single Job\nJob.js\n\n```js\nimport { FaLocationArrow, FaBriefcase, FaCalendarAlt } from 'react-icons/fa';\nimport { Link } from 'react-router-dom';\nimport { useDispatch } from 'react-redux';\n\nconst Job = ({\n  _id,\n  position,\n  company,\n  jobLocation,\n  jobType,\n  createdAt,\n  status,\n}) =\u003e {\n  const dispatch = useDispatch();\n\n  return (\n    \u003cWrapper\u003e\n      \u003cheader\u003e\n        \u003cdiv className='main-icon'\u003e{company.charAt(0)}\u003c/div\u003e\n        \u003cdiv className='info'\u003e\n          \u003ch5\u003e{position}\u003c/h5\u003e\n          \u003cp\u003e{company}\u003c/p\u003e\n        \u003c/div\u003e\n      \u003c/header\u003e\n      \u003cdiv className='content'\u003e\n        \u003cdiv className='content-center'\u003e\n          \u003ch4\u003emore content\u003c/h4\u003e\n          \u003cdiv className={`status ${status}`}\u003e{status}\u003c/div\u003e\n        \u003c/div\u003e\n        \u003cfooter\u003e\n          \u003cdiv className='actions'\u003e\n            \u003cLink\n              to='/add-job'\n              className='btn edit-btn'\n              onClick={() =\u003e {\n                console.log('edit job');\n              }}\n            \u003e\n              Edit\n            \u003c/Link\u003e\n            \u003cbutton\n              type='button'\n              className='btn delete-btn'\n              onClick={() =\u003e {\n                console.log('delete  job');\n              }}\n            \u003e\n              Delete\n            \u003c/button\u003e\n          \u003c/div\u003e\n        \u003c/footer\u003e\n      \u003c/div\u003e\n    \u003c/Wrapper\u003e\n  );\n};\n\n```\n---------------------------------------------------------------\n#### 49) Job info\nJob.js\n```\n\u003cdiv className=\"more-info\"\u003e\n                \u003cspan className=\"icon\"\u003e\u003cFaLocationArrow\u003e\u003c/FaLocationArrow\u003e\u003c/span\u003e\n                \u003cspan className=\"text\"\u003e{jobLocation}\u003c/span\u003e\n            \u003c/div\u003e\n            \u003cdiv className=\"more-info\"\u003e\n                \u003cspan className=\"icon\"\u003e\u003cFaCalendarAlt\u003e\u003c/FaCalendarAlt\u003e\u003c/span\u003e\n                \u003cspan className=\"text\"\u003e{createdAt}\u003c/span\u003e\n            \u003c/div\u003e\n            \u003cdiv className=\"more-info\"\u003e\n                \u003cspan className=\"icon\"\u003e\u003cFaBriefcase\u003e\u003c/FaBriefcase\u003e\u003c/span\u003e\n                \u003cspan className=\"text\"\u003e{jobType}\u003c/span\u003e\n            \u003c/div\u003e\n```\n------------------------------------------------------------------------\n#### 50) Moment.js\n\n[moment.js](https://momentjs.com/)\n\n```sh\nnpm install moment\n```\n\nJob.js\n\n```js\nconst date = moment(createdAt).format('MMM Do, YYYY');\n```\n---------------------------------------------------------------\n#### 51) Delete job\n\nallJobsSlice.js:\n\nexport const deleteJob = createAsyncThunk(\"allJobs/deleteJob\", async(id, thunkAPI) =\u003e {\n    try {\n        const response = await customFetch.delete(`/jobs/${id}`, {\n            headers: {\n                authorization: `Bearer ${thunkAPI.getState().user.user.token}`\n            }\n        })\n        thunkAPI.dispatch(getAllJobs());\n        return response.data;\n    } catch (error) {\n        return thunkAPI.rejectWithValue(\"There was an error deleting the job\");\n    }\n  })\n\n  extraReducers:\n  .addCase(deleteJob.pending, (state) =\u003e {\n            state.isLoading =true;\n        })\n        .addCase(deleteJob.fulfilled, (state, {payload}) =\u003e {\n           state.isLoading=false;\n           state.jobs=state.jobs.filter((job) =\u003e ( job._id != payload._id))\n           toast.success(\"Job Deleted!\");\n        })\n        .addCase(deleteJob.rejected, (state, {payload}) =\u003e {\n            state.isLoading=false;\n            toast.error(payload);\n         })\n  \n  ```\n  Jobs.js\n  import { deleteJob} from '../features/allJobs/allJobsSlice';\n   \u003cbutton type=\"button\" className=\"btn delete-btn\" onClick={()=\u003e dispatch(deleteJob(_id))}\u003eDelete\u003c/button\u003e\n   ------------------------------------------------------------\n\n#### 52) Edit job functionality\n2 steps process:\ni) click \"Edit\" to navigate to AddJob page and set up those estate values (which have been cleaned).\nWe rewrite them.\nii) edit job request\n-------------------\ni) jobSlice.js\nreducers:\n setEditJob: (state, {payload}) =\u003e {\n          return {...state, isEditing: true, ...payload}\n        }\n        export const {handleChange, handleClear, setEditJob}=jobSlice.actions;\n    \n    Job.js\n    import { setEditJob } from '../features/job/jobSlice';\n    onClick={()=\u003e dispatch(setEditJob({editJobId: _id, position, company, jobLocation, status, jobType}))}\n----\nii) jobSlice.js\nexport const editJob = createAsyncThunk(\"allJobs/editJob\", async({jobId, job}, thunkAPI) =\u003e {\n    try {\n        const response = await customFetch.patch(`/jobs/${jobId}`, job, {\n                headers: {\n                    authorization: `Bearer ${thunkAPI.getState().user.user.token}`  \n                }\n        })\n            return response.data;\n    } catch (error) {\n        return thunkAPI.rejectWithValue(\"There was an error editing the job!\");\n    }\n  })\n\nextraReducers:\n//edit job\n          .addCase(editJob.pending, (state, action) =\u003e {\n            state.isLoading = true;\n          })\n          .addCase(editJob.fulfilled, (state, {payload}) =\u003e {\n            state.isLoading = false;\n            toast.success(\"Job Modified!\");\n          })\n          .addCase(editJob.rejected, (state, {payload}) =\u003e {\n            state.isLoading = false;\n            toast.error(payload);\n          })\n  \n  AddJob.js\n  const onSubmit = (e) =\u003e {\n    .....................\n    if (isEditing) {\n      dispatch(editJob({\n        jobId: editJobId,\n        job: { position, company, jobLocation, jobType, status }\n      }));\n    } else {\n      dispatch(createJob({ position, company, jobLocation, jobType, status }));\n    }\n  }\n```````````````\n------------------------------------------------------------------------------\n#### 53) Test user\nRegister.js\n \u003cbutton type=\"button\" className=\"btn btn-block btn-hipster\" disabled={isLoading}\n            onClick={()=\u003e dispatch(loginUser({email: \"testUser@test.com\", password: \"secret\"}))}\u003e\n              {isLoading ? \"Loading..\" : \"Demo App\"}\n            \u003c/button\u003e\n\n-------------------------------------------------------------------------------\n### 54)\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdcodelab%2Fcarreerhub-react-redux-frontend-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmdcodelab%2Fcarreerhub-react-redux-frontend-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdcodelab%2Fcarreerhub-react-redux-frontend-app/lists"}