React Django & Mysql to build a full stack job application website
In the earlier tutorials, you created APIs to perform CRUD operations on products table of MYSQL database, and to allow a visitor to register a user account and login to obtain json web token to access protected views. The backend APIs are ready to be accessed by a client app.
Now, we create a client app using React to use the APIs. After finishing the client app, we have a full stack job application website using React Django and MYSQL. The job application website allows a visitor to create an account and login to post new job application form or update an existing one.
To get started with React, you have to install NPM (Node Package Manager) and a code editor. In my machine, i have Node 16.10.0 and NPM 7.24.0 and VS Code.
To create React jobapp app, change to a working directory and execute the following command:
D:/> npm init react-app jobapp
After the initialization complete, jobapp folder of the React app is created in the drive D:.
To open the jobapp app in VS Code, launch the VS Code -> Open Folder... choose jobapp.
On the top menu, select New Terminal. In the Terminal window, run the following command to run the jobapp.
jobapp>npm run start
We are going to style the React app using bootstrap and use reactstrap components in our forms. Thus, execute the following command to install the dependencies:components/register.js
import React, { useState } from "react" import axios from "axios"; import { useNavigate } from "react-router-dom"; import { Button, FormGroup,Card, CardHeader,CardBody,CardFooter, Input, Form, } from "reactstrap"; const Register = (props) => { const [errors,setErrors] = useState({}); const [inputs, setInputs] = useState({}); const history=useNavigate(); function handleValidation() { let formIsValid = true; let es = {}; //Name if (!inputs.username) { formIsValid = false; es['username']="can not empty!"; } if (typeof inputs.username !== "undefined") { if (!inputs.username.match(/^[a-zA-Z0-9]+$/)) { formIsValid = false; es['username']="only letters and numbers allowed!"; } } // password if (!inputs.password) { formIsValid = false; es['password']="can not empty!"; } if (typeof inputs.password !== "undefined") { if (inputs.password.length<8) { formIsValid = false; es['password']="week password!"; } } //Email if (!inputs.password2) { formIsValid = false; es['password2']="can not empty!"; } if (inputs.password!==inputs.password2) { formIsValid = false; es['password2']="Passwords not match!"; } setErrors(es); return formIsValid; } function handleSubmit(e){ e.preventDefault(); console.log(inputs.username,inputs.password,inputs.password2); if(handleValidation()){ axios({ method: 'post', url: "register/", data: {username: inputs.username,password:inputs.password,password2:inputs.password2}, headers: { "Content-type":"application/json",} }).then(response=>{ try { let dt=JSON.parse(JSON.stringify(response)); if(dt.status===201){ history("/login"); } else{ alert('Failed to create user!'); } } catch (e) { alert('Failed to create user!'); } }); } } const handleChange = (event) =>{ const name = event.target.name; const value = event.target.value; setInputs(values => ({...values, [name]: value})) } return ( <div className="container" style={{width: '18rem'}}> <Card className="my-2" style={{width: '18rem'}} > <CardHeader>Register</CardHeader> <Form className="form"> <CardBody> <FormGroup> <Input type="text" name="username" placeholder="Enter username" value={inputs.username || ""} onChange={handleChange} /> <span style={{color: '#ff2222'}}>{errors.username}</span> </FormGroup> <FormGroup> <Input type="password" name="password" placeholder="Enter password" value={inputs.password || ""} onChange={handleChange} /> <span style={{color: '#ff2222'}}>{errors.password}</span> </FormGroup> <FormGroup> <Input type="password" name="password2" placeholder="Enter confirm password" value={inputs.password2 || ""} onChange={handleChange} /> <span style={{color: '#ff2222'}}>{errors.password2}</span> </FormGroup> </CardBody> <CardFooter> <FormGroup> <Button color="primary" onClick={handleSubmit}>Register</Button> </FormGroup> </CardFooter> </Form> </Card> </div> ) } export default Register
components/login.js
import React, { useState } from "react" import axios from "axios"; import { useNavigate } from "react-router-dom"; import { Link } from "react-router-dom" import { Button, FormGroup,Card, CardHeader,CardBody,CardFooter, Input, Form, Label, } from "reactstrap"; const Login = (props) => { const [username, setUsername]=useState(''); const [password,setPassword]=useState(''); const [logerr,setLogError] = useState(''); const {tokenchange} = props; const history=useNavigate(); function handleTokenChange(tk){ tokenchange(tk); // send token to parent component } function handleSubmit(e){ e.preventDefault(); // Make the POST call by passing a config object to the instance axios({ method: 'post', url: "login/", data: {username: username,password:password}, headers: { "Content-type":"application/json"} }).then(response=>{ try { let dt=JSON.parse(JSON.stringify(response)); if(dt.status===200){ localStorage.setItem("token", dt.data.access); handleTokenChange(dt.data.access); history("/"); } else{ setLogError("Error in login"); } } catch (e) { setLogError('Invalid user!'); } }) .catch(error => { setLogError('Invalid user!'); }); } const handleUsernameInputChange = (event) => { setUsername(event.target.value); } const handlePasswordInputChange = (event) => { setPassword(event.target.value); } return ( <div className="container" style={{width: '25rem'}}> <Card className="my-2" style={{width: '25rem'}} > <CardHeader>Login</CardHeader> <Form className="form"> <CardBody> <FormGroup> <Input type="text" name="username" placeholder="Enter username" value={username} onChange={(e) =>handleUsernameInputChange(e)} required /> </FormGroup> <FormGroup> <Input type="password" name="password" placeholder="Enter password" value={password} onChange={(e) =>handlePasswordInputChange(e)} required /> </FormGroup> </CardBody> <CardFooter> <FormGroup> <Button color="primary" onClick={handleSubmit}>Login</Button> <Label style={{marginLeft: '5px'}}>Don't have an account?</Label> <Link to="/register">create user</Link> <Label className="text-danger">{logerr}</Label> </FormGroup> </CardFooter> </Form> </Card> </div> ) } export default Login
components/logout.js
import React,{useEffect} from "react" import { useNavigate } from "react-router-dom"; const Logout = (props) => { const {tokenchange} = props; const history=useNavigate(); function handleTokenChange (tk) { tokenchange(tk); // send token updated to parent component } useEffect(() => { localStorage.removeItem('token'); handleTokenChange(null); history("/"); }, []); return ( <><div>Logout</div></>
) } export default Logout
import React, { useState, useEffect } from "react" import axios from "axios"; import { getUserId } from "../utils/AuthUser"; import DatePicker from "react-datepicker"; import Moment from 'moment'; import { useNavigate} from "react-router-dom"; import "react-datepicker/dist/react-datepicker.css"; import { Button, FormGroup,Col, Card, CardHeader,CardBody, Input, Form, Label, } from "reactstrap"; const MainComponent = (props) => { const history=useNavigate(); const token= localStorage.getItem("token"); const user=getUserId(token); const [inputs, setInputs] = useState( { 'first_name':'', 'last_name':'', 'email':'', 'sex':'F', 'education':1, 'position': 1, 'employment': 1, 'id':0, } ); const [fileatt, setDocument] = useState(null); const [dob, setDob] = useState(new Date()); const [isEditMode, setIsEditmode]= useState(false); const [message,setMessage]= useState(''); useEffect(() => { if(user) // if login axios.get(`/api/jobbyuser/${user.user_id}/`,{ headers:{ "Content-type":"application/json", }, }) .then((res) => { const jform=JSON.parse(JSON.stringify(res)); if(jform.status===200){ setIsEditmode(true); setInputs(values => ({...values, ['id']: res.data.id})); setInputs(values => ({...values, ['email']: res.data.email})); setInputs(values => ({...values, ['first_name']: res.data.first_name})) ; setInputs(values => ({...values, ['last_name']: res.data.last_name})) ; setInputs(values => ({...values, ['sex']: res.data.sex})) ; const formatDate = Moment(res.data.dob).format('YYYY-MM-DD'); setDob(new Date(formatDate)); setInputs(values => ({...values, ['education']: res.data.education})) ; setInputs(values => ({...values, ['position']: res.data.position})) ; setInputs(values => ({...values, ['employment']: res.data.employment})) ; } }) .catch((err) => console.log(err)); else{ // if not login, redirect to login page history('/login'); } }, []); const handleChange = (event) =>{ const name = event.target.name; const value = event.target.value; setInputs(values => ({...values, [name]: value})) } const handleFileChange = (e) => { setDocument(e.target.files[0]); }; const HIGHSCHOOL = 'High School'; const ASSOCIATE = 'Associate'; const BACHELOR = 'Bachelor'; const EDUCATION_CHOICES=[ {"id":1,"text":HIGHSCHOOL}, {"id":2,"text":ASSOCIATE}, {"id":3,"text":BACHELOR}, ]; const MANAGER = 'manager'; const SALES ='sales'; const CASHIER = 'cashier'; const OTHER ='other'; const POSITION_CHOICES = [ {"id":1,"text":MANAGER}, {"id":2,"text":SALES}, {"id":3,"text":CASHIER}, {"id":4,"text":OTHER}, ]; const EMPLOYED = 'employed'; const SELFEMPLOYED = 'self-employed'; const UNEMPLOYED = 'unemployed'; const STUDENT = 'student'; const EMPLOYMENT_CHOICES = [ {"id":1,"text":EMPLOYED}, {"id":2,"text":SELFEMPLOYED}, {"id":3,"text":UNEMPLOYED}, {"id":4,"text":STUDENT}, ]; const handleSubmit = (e) => {
e.preventDefault(); let form_data = new FormData(); form_data.append('id', inputs.id); form_data.append('first_name', inputs.first_name); form_data.append('last_name', inputs.last_name); form_data.append('sex', inputs.sex); form_data.append('dob',Moment(dob).format('YYYY-MM-DD')); form_data.append('email',inputs.email); form_data.append('position',inputs.position); form_data.append('education',inputs.education); form_data.append('employment',inputs.employment); form_data.append('user_id',user.user_id); if(fileatt!=null) { form_data.append('fileatt', fileatt, fileatt.name); } axios({ method: isEditMode?'put':'post', url: "/api/jforms/"+(isEditMode?inputs.id+"/":""), data: form_data, headers: { "authorization": `Bearer ${token}` //send token to get permission to access protected apis } }).then(res=>{ setIsEditmode(!isEditMode); console.log(res); const resObj=JSON.parse(JSON.stringify(res)); if(resObj.status===201 || resObj.status===200){ setMessage(" saved successfully!"); } else{ setMessage(" Failed to save!"); } }); }; return ( <div className="container" style={{width: '25rem',marginTop:"10px"}}> <Card className="my-2" style={{width: '25rem'}} > <CardHeader>Job Application Form</CardHeader> <Form> <CardBody> <FormGroup row> <Label for="first_name" sm={4} size="lg">First Name</Label> <Col sm={8}> <Input type="text" value={inputs.first_name} name="first_name" id="first_name" placeholder="First Name" bsSize="lg" onChange={(e) =>handleChange(e)} required /> </Col> </FormGroup> <FormGroup row> <Label for="last_name" sm={4}>Last Name</Label> <Col sm={8}> <Input type="text" value={inputs.last_name} name="last_name" id="last_name" placeholder="Last Name" bsSize="lg" onChange={(e) =>handleChange(e)} required /> </Col> </FormGroup> <FormGroup row> <Label for="email" sm={2}>Email</Label> <Col sm={10}> <Input type="text" value={inputs.email} name="email" id="email" placeholder="Email" bsSize="lg" onChange={(e) =>handleChange(e)} required /> </Col> </FormGroup> <FormGroup row> <Label for="sex" sm={2}>Sex</Label> <Col sm={10}> <Input onChange={(e) =>handleChange(e)} type="select" name="sex" id="sex" value={inputs.sex}> <option value="F">Female</option> <option value="M">Male</option> </Input> </Col> </FormGroup> <FormGroup row> <Label for="dob" sm={4}>Date Of Birth</Label> <Col sm={10}> <DatePicker id="dob" name="dob" selected={dob} onChange={(date) => setDob(date)} dateFormat="yyyy-MM-dd" /> </Col> </FormGroup> <FormGroup row> <Label for="edu" sm={4}>Education</Label> <Col sm={10}> <Input onChange={(e) =>handleChange(e)} type="select" name="education" id="education" value={inputs.education}> { EDUCATION_CHOICES.map((edu)=>{ return ( <option key={edu.id} value={edu.id}>{edu.text}</option> ); }) } </Input> </Col> </FormGroup> <FormGroup row> <Label for="pos" sm={10}>What Postion are you applying for?</Label> <Col sm={10}> <Input onChange={(e) =>handleChange(e)} type="select" name="position" id="position" value={inputs.position}> { POSITION_CHOICES.map((pos,index)=>{ return ( <option key={pos.id} value={pos.id}>{pos.text}</option> ); }) } </Input> </Col> </FormGroup> <FormGroup row> <Label for="emp_status" sm={10}>What is your current employment?</Label> <Col sm={10}> <Input onChange={(e) =>handleChange(e)} type="select" name="employment" id="employment" value={inputs.employment}> { EMPLOYMENT_CHOICES.map((emp)=>{ return ( <option key={emp.id} value={emp.id}>{emp.text}</option> ); }) } </Input> </Col> </FormGroup> <FormGroup> <Label for="file" sm={10}>File attachment</Label> <Col sm={10}> <Input type="file" id="fileatt" name="fileatt" accept="application/pdf" onChange={handleFileChange} /> </Col> </FormGroup> <FormGroup> <Button color="primary" onClick={handleSubmit}>Submit</Button> <span>{message}</span> </FormGroup> </CardBody> </Form> </Card> </div> ) } export default MainComponent
import React, { useState } from "react" import "bootstrap/dist/css/bootstrap.min.css"; import NavBar from './components/navbar'; import MainComponent from './components/home'; import { BrowserRouter as Router, Routes,Route } from "react-router-dom"; import Register from "./components/register"; import Login from "./components/login"; import Logout from "./components/logout"; function App() { const [token,setToken] = useState(localStorage.getItem("token")); const handleTokenChange = (tk) =>{ setToken(tk); } return ( <div className="App"> <Router> <NavBar token={token}/> <Routes> <Route path="/" element ={<MainComponent />}/> <Route path="/login" element ={<Login tokenchange={handleTokenChange} />}/> <Route path="/logout" element ={<Logout tokenchange={handleTokenChange} />}/> <Route path="/register" element ={<Register/>}/> </Routes> </Router> </div> ); }
Open package.js file to add proxy to the APIs at localhost:5000. All APIs accesses will forward to http://localhost:5000/.
Save the project. While MYSQL is running. access http://localhost:3000. You will be redirected to the login page.
Video Demo
Comments
Post a Comment