Skip to content

Commit

Permalink
Merge pull request #6 from polito-WA1-AW1-2021/with_auth
Browse files Browse the repository at this point in the history
now with authentication
  • Loading branch information
luigidr authored May 27, 2021
2 parents 35bb360 + fc65c33 commit 6915b79
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ It will be developed in phases, in several weeks, to illustrate the different fe
* _Phase 2_: add a form and its management.
* _Phase 3_: edit form and client-side routing. The application now has 3 different pages.
* _Phase 4_: add interaction with the server for all the operations. Basic error handling.
* _Phase 5_: add login and logout. Sample credentials: [email protected] (psw: student) and [email protected] (psw: student).
39 changes: 38 additions & 1 deletion src/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,42 @@ function updateExam(exam) {
});
}

const API = {getAllCourses, getAllExams, deleteExam, addExam, updateExam};
async function logIn(credentials) {
let response = await fetch('/api/sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
if(response.ok) {
const user = await response.json();
return user.name;
}
else {
try {
const errDetail = await response.json();
throw errDetail.message;
}
catch(err) {
throw err;
}
}
}

async function logOut() {
await fetch('/api/sessions/current', { method: 'DELETE' });
}

async function getUserInfo() {
const response = await fetch(BASEURL + '/sessions/current');
const userInfo = await response.json();
if (response.ok) {
return userInfo;
} else {
throw userInfo; // an object with the error coming from the server
}
}

const API = {getAllCourses, getAllExams, deleteExam, addExam, updateExam, logIn, logOut, getUserInfo};
export default API;
84 changes: 64 additions & 20 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,46 @@ import { Container, Row, Alert } from 'react-bootstrap';
import { ExamScores, ExamForm } from './ExamComponents.js';
import AppTitle from './AppTitle.js';
import { useEffect, useState } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom';
import API from './API';
import { LoginForm, LogoutButton } from './LoginComponents';

function App() {
const [exams, setExams] = useState([]);
const [courses, setCourses] = useState([]);
const [loading, setLoading] = useState(true);
const [dirty, setDirty] = useState(true);
const [errorMsg, setErrorMsg] = useState('');
const [message, setMessage] = useState('');
const [loggedIn, setLoggedIn] = useState(false); // at the beginning, no user is logged in

useEffect(()=> {
const checkAuth = async() => {
try {
// here you have the user info, if already logged in
// TODO: store them somewhere and use them, if needed
await API.getUserInfo();
setLoggedIn(true);
} catch(err) {
console.error(err.error);
}
};
checkAuth();
}, []);

useEffect(()=> {
const getCourses = async () => {
const courses = await API.getAllCourses();
setCourses(courses);
if(loggedIn) {
const courses = await API.getAllCourses();
setCourses(courses);
setDirty(true);
}
};
getCourses()
.catch(err => {
setErrorMsg("Impossible to load your exams! Please, try again later...");
setMessage({msg: "Impossible to load your exams! Please, try again later...", type: 'danger'});
console.error(err);
});;
}, []);
});
}, [loggedIn]);

useEffect(()=> {
const getExams = async () => {
Expand All @@ -36,13 +55,13 @@ function App() {
setLoading(false);
setDirty(false);
}).catch(err => {
setErrorMsg("Impossible to load your exams! Please, try again later...");
setMessage({msg: 'Impossible to load your exams! Please, try again later...', type: 'danger'});
console.error(err);
});;
});
}
}, [courses.length, dirty]);

/* With requests in parallel...
/* TO UPDATE: With requests in parallel...
useEffect(() => {
const promises = [API.getAllCourses(), API.getAllExams()];
Promise.all(promises).then(
Expand Down Expand Up @@ -74,9 +93,9 @@ function App() {

const handleErrors = (err) => {
if(err.errors)
setErrorMsg(err.errors[0].msg + ': ' + err.errors[0].param);
setMessage({msg: err.errors[0].msg + ': ' + err.errors[0].param, type: 'danger'});
else
setErrorMsg(err.error);
setMessage({msg: err.error, type: 'danger'});

setDirty(true);
}
Expand Down Expand Up @@ -124,15 +143,41 @@ function App() {
}).catch(err => handleErrors(err) );;
}

const doLogIn = async (credentials) => {
try {
const user = await API.logIn(credentials);
setLoggedIn(true);
setMessage({msg: `Welcome, ${user}!`, type: 'success'});
} catch(err) {
setMessage({msg: err, type: 'danger'});
}
}

const doLogOut = async () => {
await API.logOut();
setLoggedIn(false);
// clean up everything
setCourses([]);
setExams([]);
}

const examCodes = exams.map(exam => exam.coursecode);

return (<Router>
<Container className="App">
<Row>
<AppTitle />
<AppTitle/>
{loggedIn ? <LogoutButton logout={doLogOut} /> : <Redirect to="/login" />}
</Row>
{message && <Row>
<Alert variant={message.type} onClose={() => setMessage('')} dismissible>{message.msg}</Alert>
</Row> }

<Switch>
<Route path="/login" render={() =>
<>{loggedIn ? <Redirect to="/" /> : <LoginForm login={doLogIn} />}</>
}/>

<Route path="/add" render={() =>
<ExamForm courses={courses.filter(course => !examCodes.includes(course.coursecode))} addOrUpdateExam={addExam}></ExamForm>
}/>
Expand All @@ -149,13 +194,12 @@ function App() {

<Route path="/" render={() =>
<>
<Row>
{errorMsg && <Alert variant='danger' onClose={() => setErrorMsg('')} dismissible>{errorMsg}</Alert>}
</Row>
<Row>
{loading ? <span>🕗 Please wait, loading your exams... 🕗</span> :
<ExamScores exams={exams} courses={courses} deleteExam={deleteExam}/> }
</Row>
{loggedIn ?
<Row>
{loading ? <span>🕗 Please wait, loading your exams... 🕗</span> :
<ExamScores exams={exams} courses={courses} deleteExam={deleteExam}/> }
</Row>
: <Redirect to="/login" /> }
</>
} />

Expand Down
6 changes: 3 additions & 3 deletions src/AppTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Col } from 'react-bootstrap';

function AppTitle() {
return (
<Col>
<h1>Your Exams</h1>
</Col>
<Col>
<h1>Your Exams</h1>
</Col>
);
}

Expand Down
53 changes: 53 additions & 0 deletions src/LoginComponents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Form, Button, Alert, Col } from 'react-bootstrap';
import { useState } from 'react';
//import { Redirect } from 'react-router';

function LoginForm(props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [errorMessage, setErrorMessage] = useState('') ;

const handleSubmit = (event) => {
event.preventDefault();
setErrorMessage('');
const credentials = { username, password };

// SOME VALIDATION, ADD MORE!!!
let valid = true;
if(username === '' || password === '' || password.length < 6)
valid = false;

if(valid)
{
props.login(credentials);
}
else {
// show a better error message...
setErrorMessage('Error(s) in the form, please fix it.')
}
};

return (
<Form>
{errorMessage ? <Alert variant='danger'>{errorMessage}</Alert> : ''}
<Form.Group controlId='username'>
<Form.Label>email</Form.Label>
<Form.Control type='email' value={username} onChange={ev => setUsername(ev.target.value)} />
</Form.Group>
<Form.Group controlId='password'>
<Form.Label>Password</Form.Label>
<Form.Control type='password' value={password} onChange={ev => setPassword(ev.target.value)} />
</Form.Group>
<Button onClick={handleSubmit}>Login</Button>
</Form>)
}

function LogoutButton(props) {
return(
<Col>
<Button variant="outline-primary" onClick={props.logout}>Logout</Button>
</Col>
)
}

export { LoginForm, LogoutButton };

0 comments on commit 6915b79

Please sign in to comment.