├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt ├── src ├── App.js ├── components │ ├── AddCustomer.js │ ├── AddEmployee.js │ ├── DefinitionSearch.js │ ├── EditEmployee.js │ ├── Employee.js │ ├── Header.js │ └── NotFound.js ├── hooks │ └── UseFetch.js ├── index.css ├── index.js ├── pages │ ├── Customer.js │ ├── Customers.js │ ├── Definition.js │ ├── Dictionary.js │ ├── Employees.js │ ├── Login.js │ └── Register.js └── shared.js └── tailwind.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@headlessui/react": "^1.6.6", 7 | "@heroicons/react": "^1.0.6", 8 | "@testing-library/jest-dom": "^5.16.5", 9 | "@testing-library/react": "^13.3.0", 10 | "@testing-library/user-event": "^13.5.0", 11 | "bootstrap": "^5.2.0", 12 | "react": "^18.2.0", 13 | "react-bootstrap": "^2.5.0", 14 | "react-dom": "^18.2.0", 15 | "react-router-dom": "^6.3.0", 16 | "react-scripts": "5.0.1", 17 | "uuid": "^8.3.2", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "devDependencies": { 45 | "autoprefixer": "^10.4.8", 46 | "postcss": "^8.4.14", 47 | "tailwindcss": "^3.1.8" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CalebCurry/react/4c5a435acdd1f8e04bd7e178bf79f990945b78ef/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | import { createContext, useState, useEffect } from 'react'; 3 | import Header from './components/Header'; 4 | import Employees from './pages/Employees'; 5 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 6 | import Customers from './pages/Customers'; 7 | import Dictionary from './pages/Dictionary'; 8 | import Definition from './pages/Definition'; 9 | import NotFound from './components/NotFound'; 10 | import Customer from './pages/Customer'; 11 | import Login from './pages/Login'; 12 | import Register from './pages/Register'; 13 | import { baseUrl } from './shared'; 14 | 15 | export const LoginContext = createContext(); 16 | 17 | function App() { 18 | useEffect(() => { 19 | function refreshTokens() { 20 | if (localStorage.refresh) { 21 | const url = baseUrl + 'api/token/refresh/'; 22 | fetch(url, { 23 | method: 'POST', 24 | headers: { 25 | 'Content-Type': 'application/json', 26 | }, 27 | body: JSON.stringify({ 28 | refresh: localStorage.refresh, 29 | }), 30 | }) 31 | .then((response) => { 32 | return response.json(); 33 | }) 34 | .then((data) => { 35 | localStorage.access = data.access; 36 | localStorage.refresh = data.refresh; 37 | setLoggedIn(true); 38 | }); 39 | } 40 | } 41 | 42 | const minute = 1000 * 60; 43 | refreshTokens(); 44 | setInterval(refreshTokens, minute * 3); 45 | }, []); 46 | 47 | const [loggedIn, setLoggedIn] = useState( 48 | localStorage.access ? true : false 49 | ); 50 | 51 | function changeLoggedIn(value) { 52 | setLoggedIn(value); 53 | if (value === false) { 54 | localStorage.clear(); 55 | } 56 | } 57 | 58 | return ( 59 | 60 | 61 |
62 | 63 | } /> 64 | } /> 65 | } 68 | /> 69 | } /> 70 | } /> 71 | } /> 72 | } /> 73 | } /> 74 | } /> 75 | 76 |
77 |
78 |
79 | ); 80 | } 81 | 82 | export default App; 83 | -------------------------------------------------------------------------------- /src/components/AddCustomer.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Button from 'react-bootstrap/Button'; 3 | import Modal from 'react-bootstrap/Modal'; 4 | 5 | export default function AddCustomer(props) { 6 | const [name, setName] = useState(''); 7 | const [industry, setIndustry] = useState(''); 8 | const [show, setShow] = useState(props.show); 9 | 10 | const handleClose = () => setShow(false); 11 | const handleShow = () => setShow(true); 12 | 13 | return ( 14 | <> 15 | 21 | 22 | 28 | 29 | Add Customer 30 | 31 | 32 |
{ 34 | e.preventDefault(); 35 | setName(''); 36 | setIndustry(''); 37 | props.newCustomer(name, industry); 38 | }} 39 | id="editmodal" 40 | className="w-full max-w-sm" 41 | > 42 |
43 |
44 | 50 |
51 |
52 | { 59 | setName(e.target.value); 60 | }} 61 | /> 62 |
63 |
64 |
65 |
66 | 72 |
73 |
74 | { 81 | setIndustry(e.target.value); 82 | }} 83 | /> 84 |
85 |
86 |
87 |
88 | 89 | 95 | 101 | 102 |
103 | 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /src/components/AddEmployee.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Button from 'react-bootstrap/Button'; 3 | import Modal from 'react-bootstrap/Modal'; 4 | 5 | function AddEmployee(props) { 6 | const [name, setName] = useState(''); 7 | const [role, setRole] = useState(''); 8 | const [img, setImg] = useState(''); 9 | const [show, setShow] = useState(false); 10 | 11 | const handleClose = () => setShow(false); 12 | const handleShow = () => setShow(true); 13 | 14 | return ( 15 | <> 16 | 22 | 23 | 29 | 30 | Add Employee 31 | 32 | 33 |
{ 35 | e.preventDefault(); 36 | setName(''); 37 | setRole(''); 38 | setImg(''); 39 | props.newEmployee(name, role, img); 40 | }} 41 | id="editmodal" 42 | className="w-full max-w-sm" 43 | > 44 |
45 |
46 | 52 |
53 |
54 | { 61 | setName(e.target.value); 62 | }} 63 | /> 64 |
65 |
66 |
67 |
68 | 74 |
75 |
76 | { 83 | setRole(e.target.value); 84 | }} 85 | /> 86 |
87 |
88 |
89 |
90 | 96 |
97 |
98 | { 105 | setImg(e.target.value); 106 | }} 107 | /> 108 |
109 |
110 |
111 |
112 | 113 | 119 | 126 | 127 |
128 | 129 | ); 130 | } 131 | 132 | export default AddEmployee; 133 | -------------------------------------------------------------------------------- /src/components/DefinitionSearch.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | 4 | export default function DefinitionSearch() { 5 | const [word, setWord] = useState(''); 6 | const navigate = useNavigate(); 7 | 8 | return ( 9 |
{ 12 | e.preventDefault(); 13 | navigate('/dictionary/' + word); 14 | }} 15 | > 16 | { 21 | setWord(e.target.value); 22 | }} 23 | /> 24 | 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/EditEmployee.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Button from 'react-bootstrap/Button'; 3 | import Modal from 'react-bootstrap/Modal'; 4 | 5 | function EditEmployee(props) { 6 | const [name, setName] = useState(props.name); 7 | const [role, setRole] = useState(props.role); 8 | 9 | const [show, setShow] = useState(false); 10 | 11 | const handleClose = () => setShow(false); 12 | const handleShow = () => setShow(true); 13 | 14 | return ( 15 | <> 16 | 22 | 23 | 29 | 30 | Update Employee 31 | 32 | 33 |
{ 35 | handleClose(); 36 | e.preventDefault(); 37 | props.updateEmployee(props.id, name, role); 38 | }} 39 | id="editmodal" 40 | className="w-full max-w-sm" 41 | > 42 |
43 |
44 | 50 |
51 |
52 | { 58 | setName(e.target.value); 59 | }} 60 | /> 61 |
62 |
63 |
64 |
65 | 71 |
72 |
73 | { 79 | setRole(e.target.value); 80 | }} 81 | /> 82 |
83 |
84 |
85 |
86 | 87 | 93 | 99 | 100 |
101 | 102 | ); 103 | } 104 | 105 | export default EditEmployee; 106 | -------------------------------------------------------------------------------- /src/components/Employee.js: -------------------------------------------------------------------------------- 1 | import EditEmployee from './EditEmployee'; 2 | 3 | function Employee(props) { 4 | return ( 5 |
6 | 10 |
11 |
12 |

13 | {props.name} 14 |

15 |

{props.role}

16 |
17 | 18 | {props.editEmployee} 19 |
20 |
21 | ); 22 | } 23 | 24 | export default Employee; 25 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | /* This example requires Tailwind CSS v2.0+ */ 2 | import { useContext, useEffect } from 'react'; 3 | import { Disclosure, Menu, Transition } from '@headlessui/react'; 4 | import { BellIcon, MenuIcon, XIcon } from '@heroicons/react/outline'; 5 | import { NavLink } from 'react-router-dom'; 6 | import { LoginContext } from '../App'; 7 | 8 | const navigation = [ 9 | { name: 'Employees', href: '/Employees' }, 10 | { name: 'Customers', href: '/Customers' }, 11 | { name: 'Dictionary', href: '/dictionary' }, 12 | ]; 13 | 14 | function classNames(...classes) { 15 | return classes.filter(Boolean).join(' '); 16 | } 17 | 18 | export default function Header(props) { 19 | const [loggedIn, setLoggedIn] = useContext(LoginContext); 20 | 21 | return ( 22 | <> 23 | 24 | {({ open }) => ( 25 | <> 26 |
27 |
28 |
29 | {/* Mobile menu button*/} 30 | 31 | 32 | Open main menu 33 | 34 | {open ? ( 35 | 46 |
47 |
48 |
49 |
50 | {/*className={classNames( 51 | item.current 52 | ? 'no-underline ' 53 | : 'no-underline', 54 | '' 55 | )}*/} 56 | {navigation.map((item) => ( 57 | { 63 | return ( 64 | 'px-3 py-2 rounded-md text-sm font-medium no-underline ' + 65 | (!isActive 66 | ? ' text-gray-300 hover:bg-gray-700 hover:text-white' 67 | : 'bg-gray-900 text-white') 68 | ); 69 | }} 70 | > 71 | {item.name} 72 | 73 | ))} 74 | {loggedIn ? ( 75 | { 78 | setLoggedIn(false); 79 | localStorage.clear(); 80 | }} 81 | className="px-3 py-2 rounded-md text-sm font-medium no-underline text-gray-300 hover:bg-gray-700 hover:text-white" 82 | > 83 | Logout 84 | 85 | ) : ( 86 | 90 | Login 91 | 92 | )} 93 |
94 |
95 |
96 |
97 | 109 |
110 |
111 |
112 | 113 | 114 |
115 | {navigation.map((item) => ( 116 | { 120 | return ( 121 | 'block px-3 py-2 rounded-md text-base font-medium no-underline ' + 122 | (!isActive 123 | ? ' text-gray-300 hover:bg-gray-700 hover:text-white' 124 | : 'bg-gray-900 text-white') 125 | ); 126 | }} 127 | > 128 | {item.name} 129 | 130 | ))} 131 | {loggedIn ? ( 132 | { 135 | setLoggedIn(false); 136 | localStorage.clear(); 137 | }} 138 | className="block px-3 py-2 rounded-md text-base font-medium no-underline text-gray-300 hover:bg-gray-700 hover:text-white" 139 | > 140 | Logout 141 | 142 | ) : ( 143 | 147 | Login 148 | 149 | )} 150 |
151 |
152 | 153 | )} 154 |
155 |
156 |
157 | {props.children} 158 |
159 |
160 | 161 | ); 162 | } 163 | -------------------------------------------------------------------------------- /src/components/NotFound.js: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

The page you are looking for was not found

; 3 | } 4 | -------------------------------------------------------------------------------- /src/hooks/UseFetch.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useNavigate, useLocation } from 'react-router-dom'; 3 | 4 | export default function useFetch(url, { method, headers, body } = {}) { 5 | const [data, setData] = useState(); 6 | const [errorStatus, setErrorStatus] = useState(); 7 | 8 | const navigate = useNavigate(); 9 | const location = useLocation(); 10 | function request() { 11 | fetch(url, { 12 | method: method, 13 | headers: headers, 14 | body: body, 15 | }) 16 | .then((response) => { 17 | if (response.status === 401) { 18 | navigate('/login', { 19 | state: { 20 | previousUrl: location.pathname, 21 | }, 22 | }); 23 | } 24 | if (!response.ok) { 25 | throw response.status; 26 | } 27 | return response.json(); 28 | }) 29 | .then((data) => { 30 | setData(data); 31 | }) 32 | .catch((e) => { 33 | setErrorStatus(e); 34 | }); 35 | } 36 | 37 | function appendData(newData) { 38 | fetch(url, { 39 | method: 'POST', 40 | headers: headers, 41 | body: JSON.stringify(newData), 42 | }) 43 | .then((response) => { 44 | if (response.status === 401) { 45 | navigate('/login', { 46 | state: { 47 | previousUrl: location.pathname, 48 | }, 49 | }); 50 | } 51 | 52 | if (!response.ok) { 53 | throw response.status; 54 | } 55 | 56 | return response.json(); 57 | }) 58 | .then((d) => { 59 | const submitted = Object.values(d)[0]; 60 | 61 | const newState = { ...data }; 62 | Object.values(newState)[0].push(submitted); 63 | 64 | setData(newState); //new object, it's seen as a state change 65 | }) 66 | .catch((e) => { 67 | console.log(e); 68 | setErrorStatus(e); 69 | }); 70 | } 71 | 72 | return { request, appendData, data, errorStatus }; 73 | } 74 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import 'bootstrap/dist/css/bootstrap.min.css'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /src/pages/Customer.js: -------------------------------------------------------------------------------- 1 | import { useParams, Link, useNavigate, useLocation } from 'react-router-dom'; 2 | import { useEffect, useState, useContext } from 'react'; 3 | import NotFound from '../components/NotFound'; 4 | import { baseUrl } from '../shared'; 5 | import { LoginContext } from '../App'; 6 | 7 | export default function Customer() { 8 | const [loggedIn, setLoggedIn] = useContext(LoginContext); 9 | const { id } = useParams(); 10 | const navigate = useNavigate(); 11 | const [customer, setCustomer] = useState(); 12 | const [tempCustomer, setTempCustomer] = useState(); 13 | const [notFound, setNotFound] = useState(); 14 | const [changed, setChanged] = useState(false); 15 | const [error, setError] = useState(); 16 | 17 | const location = useLocation(); 18 | 19 | useEffect(() => { 20 | if (!customer) return; 21 | if (!customer) return; 22 | 23 | let equal = true; 24 | if (customer.name !== tempCustomer.name) equal = false; 25 | if (customer.industry !== tempCustomer.industry) equal = false; 26 | 27 | if (equal) setChanged(false); 28 | }); 29 | 30 | useEffect(() => { 31 | const url = baseUrl + 'api/customers/' + id; 32 | fetch(url, { 33 | headers: { 34 | 'Content-Type': 'application/json', 35 | Authorization: 'Bearer ' + localStorage.getItem('access'), 36 | }, 37 | }) 38 | .then((response) => { 39 | if (response.status === 404) { 40 | //render a 404 component in this page 41 | setNotFound(true); 42 | } else if (response.status === 401) { 43 | setLoggedIn(false); 44 | navigate('/login', { 45 | state: { 46 | previousUrl: location.pathname, 47 | }, 48 | }); 49 | } 50 | 51 | if (!response.ok) { 52 | throw new Error('Something went wrong, try again later'); 53 | } 54 | 55 | return response.json(); 56 | }) 57 | .then((data) => { 58 | setCustomer(data.customer); 59 | setTempCustomer(data.customer); 60 | setError(undefined); 61 | }) 62 | .catch((e) => { 63 | setError(e.message); 64 | }); 65 | }, []); 66 | 67 | function updateCustomer(e) { 68 | e.preventDefault(); 69 | const url = baseUrl + 'api/customers/' + id; 70 | fetch(url, { 71 | method: 'POST', 72 | headers: { 73 | 'Content-Type': 'application/json', 74 | Authorization: 'Bearer ' + localStorage.getItem('access'), 75 | }, 76 | body: JSON.stringify(tempCustomer), 77 | }) 78 | .then((response) => { 79 | if (response.status === 401) { 80 | setLoggedIn(false); 81 | navigate('/login', { 82 | state: { 83 | previousUrl: location.pathname, 84 | }, 85 | }); 86 | } 87 | if (!response.ok) throw new Error('something went wrong'); 88 | return response.json(); 89 | }) 90 | .then((data) => { 91 | setCustomer(data.customer); 92 | setChanged(false); 93 | setError(undefined); 94 | }) 95 | .catch((e) => { 96 | setError(e.message); 97 | }); 98 | } 99 | 100 | return ( 101 |
102 | {notFound ?

The customer with id {id} was not found

: null} 103 | 104 | {customer ? ( 105 |
106 |
111 |
112 |
113 | 114 |
115 | 116 |
117 | { 123 | setChanged(true); 124 | setTempCustomer({ 125 | ...tempCustomer, 126 | name: e.target.value, 127 | }); 128 | }} 129 | /> 130 |
131 |
132 | 133 |
134 |
135 | 136 |
137 | 138 |
139 | { 145 | setChanged(true); 146 | setTempCustomer({ 147 | ...tempCustomer, 148 | industry: e.target.value, 149 | }); 150 | }} 151 | /> 152 |
153 |
154 |
155 | {changed ? ( 156 |
157 | 166 | 172 |
173 | ) : null} 174 | 175 |
176 | 213 |
214 |
215 | ) : null} 216 | 217 | {error ?

{error}

: null} 218 |
219 | 220 | 223 | 224 |
225 | ); 226 | } 227 | -------------------------------------------------------------------------------- /src/pages/Customers.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useContext } from 'react'; 2 | import { Link, useNavigate, useLocation } from 'react-router-dom'; 3 | import AddCustomer from '../components/AddCustomer'; 4 | import { baseUrl } from '../shared'; 5 | import { LoginContext } from '../App'; 6 | import useFetch from '../hooks/UseFetch'; 7 | 8 | export default function Customers() { 9 | const [loggedIn, setLoggedIn] = useContext(LoginContext); 10 | //const [customers, setCustomers] = useState(); 11 | const [show, setShow] = useState(false); 12 | 13 | function toggleShow() { 14 | setShow(!show); 15 | } 16 | 17 | const location = useLocation(); 18 | const navigate = useNavigate(); 19 | 20 | const url = baseUrl + 'api/customers/'; 21 | const { 22 | request, 23 | appendData, 24 | data: { customers } = {}, 25 | errorStatus, 26 | } = useFetch(url, { 27 | method: 'GET', 28 | headers: { 29 | 'Content-Type': 'application/json', 30 | Authorization: 'Bearer ' + localStorage.getItem('access'), 31 | }, 32 | }); 33 | 34 | useEffect(() => { 35 | request(); 36 | }, []); 37 | 38 | //useEffect(() => { 39 | // console.log(request, appendData, customers, errorStatus); 40 | //}); 41 | 42 | function newCustomer(name, industry) { 43 | appendData({ name: name, industry: industry }); 44 | 45 | if (!errorStatus) { 46 | toggleShow(); 47 | } 48 | } 49 | 50 | return ( 51 | <> 52 |

Here are our customers:

53 | {customers 54 | ? customers.map((customer) => { 55 | return ( 56 |
57 | 58 | 61 | 62 |
63 | ); 64 | }) 65 | : null} 66 | 67 | 72 | 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /src/pages/Definition.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { useParams, useNavigate, Link, useLocation } from 'react-router-dom'; 3 | import { v4 as uuidv4 } from 'uuid'; 4 | import DefinitionSearch from '../components/DefinitionSearch'; 5 | import NotFound from '../components/NotFound'; 6 | import useFetch from '../hooks/UseFetch'; 7 | 8 | export default function Definition() { 9 | //const [word, setWord] = useState(); 10 | //const [notFound, setNotFound] = useState(false); 11 | //const [error, setError] = useState(false); 12 | let { search } = useParams(); 13 | 14 | const location = useLocation(); 15 | const navigate = useNavigate(); 16 | const { 17 | request, 18 | data: [{ meanings: word }] = [{}], 19 | errorStatus, 20 | } = useFetch('https://api.dictionaryapi.dev/api/v2/entries/en/' + search); 21 | 22 | useEffect(() => { 23 | request(); 24 | }, [search]); 25 | 26 | if (errorStatus === 404) { 27 | return ( 28 | <> 29 | 30 | Search another 31 | 32 | ); 33 | } 34 | 35 | if (errorStatus) { 36 | return ( 37 | <> 38 |

There was a problem with the server, try again later.

39 | Search another 40 | 41 | ); 42 | } 43 | 44 | return ( 45 | <> 46 | {word ? ( 47 | <> 48 |

Here is a definition:

49 | {word.map((meaning) => { 50 | return ( 51 |

52 | {meaning.partOfSpeech + ': '} 53 | {meaning.definitions[0].definition} 54 |

55 | ); 56 | })} 57 |

Search again:

58 | 59 | 60 | ) : null} 61 | 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/pages/Dictionary.js: -------------------------------------------------------------------------------- 1 | import DefinitionSearch from '../components/DefinitionSearch'; 2 | 3 | export default function Dictionary() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/Employees.js: -------------------------------------------------------------------------------- 1 | import '../index.css'; 2 | import Employee from '../components/Employee'; 3 | import { useState } from 'react'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | import AddEmployee from '../components/AddEmployee'; 6 | import EditEmployee from '../components/EditEmployee'; 7 | import Header from '../components/Header'; 8 | 9 | function Employees() { 10 | const [employees, setEmployees] = useState([ 11 | { 12 | id: 1, 13 | name: 'Caleb', 14 | role: 'YouTube Sensation', 15 | img: 'https://images.pexels.com/photos/3831645/pexels-photo-3831645.jpeg', 16 | }, 17 | { 18 | id: 2, 19 | name: 'Sal', 20 | role: 'Manager', 21 | img: 'https://images.pexels.com/photos/3586798/pexels-photo-3586798.jpeg', 22 | }, 23 | { 24 | id: 3, 25 | name: 'John', 26 | role: 'Director of Eng.', 27 | img: 'https://images.pexels.com/photos/2095582/pexels-photo-2095582.jpeg', 28 | }, 29 | { 30 | id: 4, 31 | name: 'Melanie', 32 | role: 'Software Engineer', 33 | img: 'https://images.pexels.com/photos/3760583/pexels-photo-3760583.jpeg', 34 | }, 35 | { 36 | id: 5, 37 | name: 'Corey', 38 | role: 'The Devops Guy', 39 | img: 'https://images.pexels.com/photos/2379005/pexels-photo-2379005.jpeg', 40 | }, 41 | { 42 | id: 6, 43 | name: 'Jake', 44 | role: 'Senior', 45 | img: 'https://images.pexels.com/photos/2225298/pexels-photo-2225298.jpeg', 46 | }, 47 | ]); 48 | 49 | function updateEmployee(id, newName, newRole) { 50 | const updatedEmployees = employees.map((employee) => { 51 | if (id == employee.id) { 52 | return { ...employee, name: newName, role: newRole }; 53 | } 54 | 55 | return employee; 56 | }); 57 | setEmployees(updatedEmployees); 58 | } 59 | 60 | function newEmployee(name, role, img) { 61 | const newEmployee = { 62 | id: uuidv4(), 63 | name: name, 64 | role: role, 65 | img: img, 66 | }; 67 | setEmployees([...employees, newEmployee]); 68 | } 69 | 70 | const showEmployees = true; 71 | return ( 72 |
73 | {showEmployees ? ( 74 | <> 75 |
76 | {employees.map((employee) => { 77 | const editEmployee = ( 78 | 84 | ); 85 | return ( 86 | 94 | ); 95 | })} 96 |
97 | 98 | 99 | ) : ( 100 |

You cannot see the employees

101 | )} 102 |
103 | ); 104 | } 105 | 106 | export default Employees; 107 | -------------------------------------------------------------------------------- /src/pages/Login.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from 'react'; 2 | import { baseUrl } from '../shared'; 3 | import { useLocation, useNavigate } from 'react-router-dom'; 4 | import { LoginContext } from '../App'; 5 | 6 | export default function Login() { 7 | const [loggedIn, setLoggedIn] = useContext(LoginContext); 8 | const [username, setUsername] = useState(); 9 | const [password, setPassword] = useState(); 10 | 11 | const location = useLocation(); 12 | const navigate = useNavigate(); 13 | 14 | function login(e) { 15 | e.preventDefault(); 16 | const url = baseUrl + 'api/token/'; 17 | fetch(url, { 18 | method: 'POST', 19 | headers: { 20 | 'Content-Type': 'application/json', 21 | }, 22 | body: JSON.stringify({ 23 | username: username, 24 | password: password, 25 | }), 26 | }) 27 | .then((response) => { 28 | return response.json(); 29 | }) 30 | .then((data) => { 31 | localStorage.setItem('access', data.access); 32 | localStorage.setItem('refresh', data.refresh); 33 | setLoggedIn(true); 34 | navigate( 35 | location?.state?.previousUrl 36 | ? location.state.previousUrl 37 | : '/customers' 38 | ); 39 | }); 40 | } 41 | 42 | return ( 43 |
44 |
45 |
46 | 47 |
48 | 49 |
50 | { 56 | setUsername(e.target.value); 57 | }} 58 | /> 59 |
60 |
61 | 62 |
63 |
64 | 65 |
66 | 67 |
68 | { 74 | setPassword(e.target.value); 75 | }} 76 | /> 77 |
78 |
79 | 82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/pages/Register.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from 'react'; 2 | import { baseUrl } from '../shared'; 3 | import { useLocation, useNavigate } from 'react-router-dom'; 4 | import { LoginContext } from '../App'; 5 | 6 | export default function Register() { 7 | const [loggedIn, setLoggedIn] = useContext(LoginContext); 8 | const [username, setUsername] = useState(); 9 | const [password, setPassword] = useState(); 10 | const [email, setEmail] = useState(); 11 | 12 | const location = useLocation(); 13 | const navigate = useNavigate(); 14 | 15 | useEffect(() => { 16 | localStorage.clear(); 17 | setLoggedIn(false); 18 | }, []); 19 | 20 | function login(e) { 21 | e.preventDefault(); 22 | const url = baseUrl + 'api/register/'; 23 | fetch(url, { 24 | method: 'POST', 25 | headers: { 26 | 'Content-Type': 'application/json', 27 | }, 28 | body: JSON.stringify({ 29 | email: email, 30 | username: username, 31 | password: password, 32 | }), 33 | }) 34 | .then((response) => { 35 | return response.json(); 36 | }) 37 | .then((data) => { 38 | localStorage.setItem('access', data.access); 39 | localStorage.setItem('refresh', data.refresh); 40 | setLoggedIn(true); 41 | navigate( 42 | location?.state?.previousUrl 43 | ? location.state.previousUrl 44 | : '/customers' 45 | ); 46 | }); 47 | } 48 | 49 | return ( 50 |
51 |
52 |
53 | 54 |
55 | 56 |
57 | { 63 | setEmail(e.target.value); 64 | }} 65 | /> 66 |
67 |
68 |
69 |
70 | 71 |
72 | 73 |
74 | { 80 | setUsername(e.target.value); 81 | }} 82 | /> 83 |
84 |
85 | 86 |
87 |
88 | 89 |
90 | 91 |
92 | { 98 | setPassword(e.target.value); 99 | }} 100 | /> 101 |
102 |
103 | 106 |
107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /src/shared.js: -------------------------------------------------------------------------------- 1 | export const baseUrl = 'http://localhost:8000/'; 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{js,jsx,ts,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | --------------------------------------------------------------------------------