├── src ├── index.css ├── assets │ └── images │ │ └── logo.png ├── components │ ├── Card.jsx │ ├── Spinner.jsx │ ├── ViewAllJobs.jsx │ ├── Hero.jsx │ ├── JobListings.jsx │ ├── HomeCards.jsx │ ├── Navbar.jsx │ └── JobListing.jsx ├── pages │ ├── JobsPage.jsx │ ├── HomePage.jsx │ ├── NotFoundPage.jsx │ └── AddJobPage.jsx ├── layouts │ └── MainLayout.jsx ├── main.jsx ├── store │ └── job-slice.js ├── App.jsx └── jobs.json ├── postcss.config.js ├── .gitignore ├── tailwind.config.js ├── index.html ├── README.md ├── vite.config.js ├── package.json ├── eslint.config.js └── public └── vite.svg /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abdul-majid-ashrafi/React-Boilerplate-2024/main/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/components/Card.jsx: -------------------------------------------------------------------------------- 1 | const Card = ({ children, bg = 'bg-gray-100' }) => { 2 | return
{children}
; 3 | }; 4 | export default Card; 5 | -------------------------------------------------------------------------------- /src/pages/JobsPage.jsx: -------------------------------------------------------------------------------- 1 | import JobListings from '../components/JobListings'; 2 | 3 | const JobsPage = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | export default JobsPage; 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: { 6 | fontFamily: { 7 | sans: ['Roboto', 'sans-serif'], 8 | }, 9 | gridTemplateColumns: { 10 | '70/30': '70% 28%', 11 | }, 12 | }, 13 | }, 14 | plugins: [], 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/Spinner.jsx: -------------------------------------------------------------------------------- 1 | import ClipLoader from 'react-spinners/ClipLoader'; 2 | 3 | const override = { 4 | display: 'block', 5 | margin: '100px auto', 6 | }; 7 | 8 | const Spinner = ({ loading }) => { 9 | return ( 10 | 16 | ); 17 | }; 18 | export default Spinner; 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom'; 2 | import { ToastContainer } from 'react-toastify'; 3 | import 'react-toastify/dist/ReactToastify.css'; 4 | import Navbar from '../components/Navbar'; 5 | 6 | const MainLayout = () => { 7 | return ( 8 | <> 9 | 10 | 11 | {/* */} 12 | 13 | ); 14 | }; 15 | export default MainLayout; 16 | -------------------------------------------------------------------------------- /src/components/ViewAllJobs.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | const ViewAllJobs = () => { 4 | return ( 5 |
6 | 10 | View All Jobs 11 | 12 |
13 | ); 14 | }; 15 | export default ViewAllJobs; 16 | -------------------------------------------------------------------------------- /src/pages/HomePage.jsx: -------------------------------------------------------------------------------- 1 | import Hero from '../components/Hero'; 2 | import HomeCards from '../components/HomeCards'; 3 | import JobListings from '../components/JobListings'; 4 | import ViewAllJobs from '../components/ViewAllJobs'; 5 | 6 | const HomePage = () => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | export default HomePage; 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | port: 3000, 9 | // proxy: { 10 | // '/api': { 11 | // target: 'http://localhost:8000', 12 | // changeOrigin: true, 13 | // rewrite: (path) => path.replace(/^\/api/, ''), 14 | // }, 15 | // }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import { configureStore } from '@reduxjs/toolkit'; 4 | import { Provider } from "react-redux"; 5 | import './index.css' 6 | import App from './App.jsx' 7 | import jobReducer from './store/job-slice.js' 8 | 9 | const store = configureStore({ 10 | reducer: { jobs: jobReducer } 11 | }) 12 | 13 | createRoot(document.getElementById('root')).render( 14 | 15 | 16 | 17 | 18 | , 19 | ) 20 | -------------------------------------------------------------------------------- /src/components/Hero.jsx: -------------------------------------------------------------------------------- 1 | const Hero = ({ 2 | title = 'Become a React Dev', 3 | subtitle = 'Find the React job that fits your skill set', 4 | }) => { 5 | return ( 6 |
7 |
8 |
9 |

10 | {title} 11 |

12 |

{subtitle}

13 |
14 |
15 |
16 | ); 17 | }; 18 | export default Hero; 19 | -------------------------------------------------------------------------------- /src/pages/NotFoundPage.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import { FaExclamationTriangle } from 'react-icons/fa'; 3 | 4 | const NotFoundPage = () => { 5 | return ( 6 |
7 | 8 |

404 Not Found

9 |

This page does not exist

10 | 14 | Go Back 15 | 16 |
17 | ); 18 | }; 19 | export default NotFoundPage; 20 | -------------------------------------------------------------------------------- /src/store/job-slice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import localJobs from '../jobs.json'; 3 | 4 | const initialState = { 5 | list: localJobs?.jobs || [] 6 | }; 7 | 8 | const jobSlice = createSlice({ 9 | name: "job", 10 | initialState, 11 | reducers: { 12 | 13 | setJobs: (state, action) => { 14 | // Check if action.payload is an array before using the spread operator 15 | if (Array.isArray(action.payload)) { 16 | state.list = [...state.list, ...action.payload]; 17 | } else { 18 | console.error('Expected payload to be an array:', action.payload); 19 | } 20 | }, 21 | } 22 | }); 23 | 24 | export const { setJobs } = jobSlice.actions; 25 | export default jobSlice.reducer; 26 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Route, 3 | createBrowserRouter, 4 | createRoutesFromElements, 5 | RouterProvider, 6 | } from 'react-router-dom'; 7 | import MainLayout from './layouts/MainLayout'; 8 | import HomePage from './pages/HomePage'; 9 | import JobsPage from './pages/JobsPage'; 10 | import NotFoundPage from './pages/NotFoundPage'; 11 | import AddJobPage from './pages/AddJobPage'; 12 | 13 | const App = () => { 14 | 15 | const router = createBrowserRouter( 16 | createRoutesFromElements( 17 | }> 18 | } /> 19 | } /> 20 | } /> 21 | } /> 22 | 23 | ) 24 | ); 25 | 26 | return ; 27 | }; 28 | export default App; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-biolerplate", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@reduxjs/toolkit": "^2.0.0-rc.3", 14 | "react": "^18.3.1", 15 | "react-dom": "^18.3.1", 16 | "react-icons": "^5.4.0", 17 | "react-redux": "^9.2.0", 18 | "react-router-dom": "^7.1.1", 19 | "react-spinners": "^0.15.0", 20 | "react-toastify": "^11.0.2" 21 | }, 22 | "devDependencies": { 23 | "@eslint/js": "^9.17.0", 24 | "@types/react": "^18.3.18", 25 | "@types/react-dom": "^18.3.5", 26 | "@vitejs/plugin-react": "^4.3.4", 27 | "autoprefixer": "^10.4.20", 28 | "eslint": "^9.17.0", 29 | "eslint-plugin-react": "^7.37.2", 30 | "eslint-plugin-react-hooks": "^5.0.0", 31 | "eslint-plugin-react-refresh": "^0.4.16", 32 | "globals": "^15.14.0", 33 | "postcss": "^8.4.49", 34 | "tailwindcss": "^3.4.17", 35 | "vite": "^6.0.6" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /src/components/JobListings.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import JobListing from './JobListing'; 3 | import Spinner from './Spinner'; 4 | import { useSelector } from 'react-redux'; 5 | 6 | const JobListings = ({ isHome = false }) => { 7 | const [loading, setLoading] = useState(true); 8 | 9 | const jobs = useSelector((state) => state.jobs.list); 10 | 11 | useEffect(() => { 12 | if (jobs.length) { 13 | setLoading(false); 14 | } 15 | }, [jobs]); 16 | 17 | return ( 18 |
19 |
20 |

21 | {isHome ? 'Recent Jobs' : 'Browse Jobs'} 22 |

23 | 24 | {loading ? ( 25 | 26 | ) : ( 27 |
28 | {jobs.map((job, idx) => ( 29 | 30 | ))} 31 |
32 | )} 33 |
34 |
35 | ); 36 | }; 37 | export default JobListings; 38 | -------------------------------------------------------------------------------- /src/components/HomeCards.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import Card from './Card'; 3 | 4 | const HomeCards = () => { 5 | return ( 6 |
7 |
8 |
9 | 10 |

For Developers

11 |

12 | Browse our React jobs and start your career today 13 |

14 | 18 | Browse Jobs 19 | 20 |
21 | 22 |

For Employers

23 |

24 | List your job to find the perfect developer for the role 25 |

26 | 30 | Add Job 31 | 32 |
33 |
34 |
35 |
36 | ); 37 | }; 38 | export default HomeCards; 39 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { NavLink } from 'react-router-dom'; 2 | import logo from '../assets/images/logo.png'; 3 | 4 | const Navbar = () => { 5 | const linkClass = ({ isActive }) => 6 | isActive 7 | ? 'bg-black text-white hover:bg-gray-900 hover:text-white rounded-md px-3 py-2' 8 | : 'text-white hover:bg-gray-900 hover:text-white rounded-md px-3 py-2'; 9 | 10 | return ( 11 | 38 | ); 39 | }; 40 | export default Navbar; 41 | -------------------------------------------------------------------------------- /src/components/JobListing.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { FaMapMarker } from 'react-icons/fa'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const JobListing = ({ job }) => { 6 | const [showFullDescription, setShowFullDescription] = useState(false); 7 | 8 | let description = job.description; 9 | 10 | if (!showFullDescription) { 11 | description = description.substring(0, 90) + '...'; 12 | } 13 | 14 | return ( 15 |
16 |
17 |
18 |
{job.type}
19 |

{job.title}

20 |
21 | 22 |
{description}
23 | 24 | 30 | 31 |

{job.salary} / Year

32 | 33 |
34 | 35 |
36 |
37 | 38 | {job.location} 39 |
40 | 44 | Read More 45 | 46 |
47 |
48 |
49 | ); 50 | }; 51 | export default JobListing; 52 | -------------------------------------------------------------------------------- /src/jobs.json: -------------------------------------------------------------------------------- 1 | { 2 | "jobs": [ 3 | { 4 | "id": "1", 5 | "title": "Senior React Developer", 6 | "type": "Full-Time", 7 | "description": "We are seeking a talented Front-End Developer to join our team in Boston, MA. The ideal candidate will have strong skills in HTML, CSS, and JavaScript, with experience working with modern JavaScript frameworks such as React or Angular.", 8 | "location": "Boston, MA", 9 | "salary": "$70K - $80K", 10 | "company": { 11 | "name": "NewTek Solutions", 12 | "description": "NewTek Solutions is a leading technology company specializing in web development and digital solutions. We pride ourselves on delivering high-quality products and services to our clients while fostering a collaborative and innovative work environment.", 13 | "contactEmail": "contact@teksolutions.com", 14 | "contactPhone": "555-555-5555" 15 | } 16 | }, 17 | { 18 | "id": "2", 19 | "title": "Front-End Engineer (React & Redux)", 20 | "type": "Full-Time", 21 | "location": "Miami, FL", 22 | "description": "Join our team as a Front-End Developer in sunny Miami, FL. We are looking for a motivated individual with a passion for crafting beautiful and responsive web applications. Experience with UI/UX design principles and a strong attention to detail are highly desirable.", 23 | "salary": "$70K - $80K", 24 | "company": { 25 | "name": "Veneer Solutions", 26 | "description": "Veneer Solutions is a creative agency specializing in digital design and development. Our team is dedicated to pushing the boundaries of creativity and innovation to deliver exceptional results for our clients.", 27 | "contactEmail": "contact@loremipsum.com", 28 | "contactPhone": "555-555-5555" 29 | } 30 | }, 31 | { 32 | "id": "3", 33 | "title": "React.js Dev", 34 | "type": "Full-Time", 35 | "location": "Brooklyn, NY", 36 | "description": "Are you passionate about front-end development? Join our team in vibrant Brooklyn, NY, and work on exciting projects that make a difference. We offer competitive compensation and a collaborative work environment where your ideas are valued.", 37 | "salary": "$70K - $80K", 38 | "company": { 39 | "name": "Dolor Cloud", 40 | "description": "Dolor Cloud is a leading technology company specializing in digital solutions for businesses of all sizes. With a focus on innovation and customer satisfaction, we are committed to delivering cutting-edge products and services.", 41 | "contactEmail": "contact@dolorsitamet.com", 42 | "contactPhone": "555-555-5555" 43 | } 44 | }, 45 | { 46 | "id": "4", 47 | "title": "React Front-End Developer", 48 | "type": "Part-Time", 49 | "description": "Join our team as a Part-Time Front-End Developer in beautiful Pheonix, AZ. We are looking for a self-motivated individual with a passion for creating engaging user experiences. This position offers flexible hours and the opportunity to work remotely.", 50 | "location": "Pheonix, AZ", 51 | "salary": "$60K - $70K", 52 | "company": { 53 | "name": "Alpha Elite", 54 | "description": "Alpha Elite is a dynamic startup specializing in digital marketing and web development. We are committed to fostering a diverse and inclusive workplace where creativity and innovation thrive.", 55 | "contactEmail": "contact@adipisicingelit.com", 56 | "contactPhone": "555-555-5555" 57 | } 58 | }, 59 | { 60 | "id": "5", 61 | "title": "Full Stack React Developer", 62 | "type": "Full-Time", 63 | "description": "Exciting opportunity for a Full-Time Front-End Developer in bustling Atlanta, GA. We are seeking a talented individual with a passion for building elegant and scalable web applications. Join our team and make an impact!", 64 | "location": "Atlanta, GA", 65 | "salary": "$90K - $100K", 66 | "company": { 67 | "name": "Browning Technologies", 68 | "description": "Browning Technologies is a rapidly growing technology company specializing in e-commerce solutions. We offer a dynamic and collaborative work environment where employees are encouraged to think creatively and innovate.", 69 | "contactEmail": "contact@consecteturadipisicing.com", 70 | "contactPhone": "555-555-5555" 71 | } 72 | }, 73 | { 74 | "id": "6", 75 | "title": "React Native Developer", 76 | "type": "Full-Time", 77 | "description": "Join our team as a Front-End Developer in beautiful Portland, OR. We are looking for a skilled and enthusiastic individual to help us create innovative web solutions. Competitive salary and great benefits package available.", 78 | "location": "Portland, OR", 79 | "salary": "$100K - $110K", 80 | "company": { 81 | "name": "Port Solutions INC", 82 | "description": "Port Solutions is a leading technology company specializing in software development and digital marketing. We are committed to providing our clients with cutting-edge solutions and our employees with a supportive and rewarding work environment.", 83 | "contactEmail": "contact@ipsumlorem.com", 84 | "contactPhone": "555-555-5555" 85 | } 86 | } 87 | ] 88 | } -------------------------------------------------------------------------------- /src/pages/AddJobPage.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { toast } from 'react-toastify'; 4 | import { useDispatch } from 'react-redux'; 5 | import { setJobs } from "../store/job-slice"; 6 | 7 | 8 | const AddJobPage = () => { 9 | const [title, setTitle] = useState(''); 10 | const [type, setType] = useState('Full-Time'); 11 | const [location, setLocation] = useState(''); 12 | const [description, setDescription] = useState(''); 13 | const [salary, setSalary] = useState('Under $50K'); 14 | const [companyName, setCompanyName] = useState(''); 15 | const [companyDescription, setCompanyDescription] = useState(''); 16 | const [contactEmail, setContactEmail] = useState(''); 17 | const [contactPhone, setContactPhone] = useState(''); 18 | 19 | const navigate = useNavigate(); 20 | const dispatch = useDispatch(); 21 | 22 | const submitForm = (e) => { 23 | e.preventDefault(); 24 | 25 | const newJob = { 26 | title, 27 | type, 28 | location, 29 | description, 30 | salary, 31 | company: { 32 | name: companyName, 33 | description: companyDescription, 34 | contactEmail, 35 | contactPhone, 36 | }, 37 | }; 38 | 39 | dispatch(setJobs([newJob])); 40 | 41 | toast.success('Job Added Successfully'); 42 | 43 | return navigate('/jobs'); 44 | }; 45 | 46 | return ( 47 |
48 |
49 |
50 |
51 |

Add Job

52 | 53 |
54 | 60 | 73 |
74 | 75 |
76 | 79 | setTitle(e.target.value)} 88 | /> 89 |
90 |
91 | 97 | 106 |
107 | 108 |
109 | 115 | 135 |
136 | 137 |
138 | 141 | setLocation(e.target.value)} 150 | /> 151 |
152 | 153 |

Company Info

154 | 155 |
156 | 162 | setCompanyName(e.target.value)} 170 | /> 171 |
172 | 173 |
174 | 180 | 189 |
190 | 191 |
192 | 198 | setContactEmail(e.target.value)} 207 | /> 208 |
209 |
210 | 216 | setContactPhone(e.target.value)} 224 | /> 225 |
226 | 227 |
228 | 234 |
235 |
236 |
237 |
238 |
239 | ); 240 | }; 241 | export default AddJobPage; 242 | --------------------------------------------------------------------------------