diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 21f9b96..00d8f1c 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -50,7 +50,7 @@ exports.signup = (req, res) => { return res.status(400).json(errors); } - let idToken, userId; + let token, userId; db.doc(`/users/${newUser.handle}`) .get() @@ -68,8 +68,8 @@ exports.signup = (req, res) => { userId = data.user.uid; return data.user.getIdToken(); }) - .then((token) => { - idToken = token; + .then((idToken) => { + token = idToken; const userCred = { email: req.body.email, handle: newUser.handle, @@ -79,7 +79,7 @@ exports.signup = (req, res) => { return db.doc(`/users/${newUser.handle}`).set(userCred); }) .then(() => { - return res.status(201).json({ idToken }); + return res.status(201).json({ token }); }) .catch((err) => { console.error(err); diff --git a/twistter-frontend/src/App.css b/twistter-frontend/src/App.css index 6c1497d..940f1fc 100644 --- a/twistter-frontend/src/App.css +++ b/twistter-frontend/src/App.css @@ -26,7 +26,7 @@ body { white-space: nowrap; } -.register { +.signup { background-color: #1da1f2; border: 1px solid #fff; color: #fff; diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index 8e18621..34d4f26 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -1,9 +1,9 @@ /* eslint-disable */ import React, { Component } from "react"; import "./App.css"; +import axios from "axios"; -import { BrowserRouter as Router } from "react-router-dom"; -import Route from "react-router-dom/Route"; +import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Navbar from "./components/layout/NavBar"; import jwtDecode from "jwt-decode"; @@ -13,55 +13,67 @@ import store from "./redux/store"; import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider'; import createMuiTheme from '@material-ui/core/styles/createMuiTheme'; import themeObject from './util/theme'; - -// Pages -import home from './pages/Home'; -import register from './pages/Register'; -import login from './pages/Login'; -import user from './pages/user'; -import writeMicroblog from "./Writing_Microblogs.js"; -import edit from "./pages/edit.js"; -import userLine from "./Userline.js"; +import { SET_AUTHENTICATED } from './redux/types'; +import { logoutUser, getUserData } from './redux/actions/userActions'; // Components import AuthRoute from "./util/AuthRoute"; -let authenticated; +// Pages +import home from './pages/Home'; +import signup from './pages/Signup'; +import login from './pages/Login'; +import user from './pages/user'; +import logout from './pages/Logout'; +import writeMicroblog from './Writing_Microblogs.js'; +import editProfile from './pages/editProfile'; +import userLine from './Userline.js'; + +const theme = createMuiTheme(themeObject); + const token = localStorage.FBIdToken; if (token) { const decodedToken = jwtDecode(token); if (decodedToken.exp * 1000 < Date.now()) { + store.dispatch(logoutUser); window.location.href = "/login"; - authenticated = false; } else { - authenticated = true; + store.dispatch({ type: SET_AUTHENTICATED }); + axios.defaults.headers.common['Authorization'] = token; + store.dispatch(getUserData()); } } -const theme = createMuiTheme(themeObject); class App extends Component { render() { return ( - - -
- -
-
- - - - - - - -
+ + +
+ +
-
+
+ + {/* AuthRoute checks if the user is logged in and if they are it redirects them to /home */} + + + + + + + + + + + +
+ +
+
- ); } } diff --git a/twistter-frontend/src/Logout.js b/twistter-frontend/src/Logout.js deleted file mode 100644 index db0e002..0000000 --- a/twistter-frontend/src/Logout.js +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable */ -import React, { Component} from 'react'; -import './App.css'; -import logo from './images/twistter-logo.png'; -import TextField from '@material-ui/core/TextField'; -import logoutUser from '../redux/actions/userActions.js' -import { Tooltip, IconButton } from '@material-ui/core'; -import writeMicroblog from './Writing_Microblogs.js'; -import PropTypes from "prop-types"; - -class Logout extends Component { - handleLogout = () => { - localStorage.removeItem("FBIdToken"); - alert("Successfully Logged Out"); - this.props.history.push('/'); - }; - render() { - return( -
- logo -

- Logout of your Twistter Account -

-

- -
- ); - }; -} -Logout.protoTypes = { - classes: PropTypes.object.isRequired, - logoutUser: PropTypes.object.isRequired -}; - -const mapActionsToProps = { - logoutUser -} - -export default Logout; diff --git a/twistter-frontend/src/Userline.js b/twistter-frontend/src/Userline.js index aa75716..8c95620 100644 --- a/twistter-frontend/src/Userline.js +++ b/twistter-frontend/src/Userline.js @@ -23,7 +23,7 @@ class Userline extends Component { componentDidMount() { - axios.get('http://localhost:5001/twistter-e4649/us-central1/api/getallPostsforUser') + axios.get('/getallPostsforUser') .then(res => { const post = res.data; this.setState({microBlogs : post}) diff --git a/twistter-frontend/src/Writing_Microblogs.js b/twistter-frontend/src/Writing_Microblogs.js index 3bda6cc..b1570d8 100644 --- a/twistter-frontend/src/Writing_Microblogs.js +++ b/twistter-frontend/src/Writing_Microblogs.js @@ -34,21 +34,27 @@ class Writing_Microblogs extends Component { handleSubmit(event) { // alert('A title for the microblog was inputted: ' + this.state.title + '\nA microblog was posted: ' + this.state.value); + const postData = { + body: this.state.value, + userHandle: "new user", + userImage: "bing-url", + microBlogTitle: this.state.title, + microBlogTopics: this.state.topics.split(', ') + } + const headers = { + headers: { 'Content-Type': 'application/json'} + } - const response = axios.post( - 'http://localhost:5001/twistter-e4649/us-central1/api/putPost', - { body: this.state.value, - userHandle: "new user", - userImage: "bing-url", - microBlogTitle: this.state.title, - microBlogTopics: this.state.topics.split(', ') - - }, - { headers: { 'Content-Type': 'application/json'} } - - ) - console.log(response.data); - alert('Post was shared successfully!'); + axios + .post('/putPost', postData, headers) + .then((res) =>{ + alert('Post was shared successfully!') + console.log(res.data); + }) + .catch((err) => { + alert('An error occured.'); + console.error(err); + }) event.preventDefault(); this.setState({value: '', title: '',characterCount: 250, topics: ''}) } diff --git a/twistter-frontend/src/components/layout/NavBar.js b/twistter-frontend/src/components/layout/NavBar.js index 0b6ba22..92ad833 100644 --- a/twistter-frontend/src/components/layout/NavBar.js +++ b/twistter-frontend/src/components/layout/NavBar.js @@ -1,12 +1,43 @@ /* eslint-disable */ import React, { Component } from 'react'; import { Link } from 'react-router-dom'; +// import PropTypes from 'prop-types'; + +// Material UI stuff import AppBar from '@material-ui/core/AppBar'; import ToolBar from '@material-ui/core/Toolbar'; import Button from '@material-ui/core/Button'; +import withStyles from "@material-ui/core/styles/withStyles"; -export class Navbar extends Component { - render() { +// Redux stuff +// import { logoutUser } from '../../redux/actions/userActions'; +// import { connect } from 'react-redux'; + +// const styles = { +// form: { +// textAlign: "center" +// }, +// textField: { +// marginBottom: 30 +// }, +// pageTitle: { +// marginBottom: 40 +// }, +// button: { +// positon: "relative", +// marginBottom: 30 +// }, +// progress: { +// position: "absolute" +// } +// }; + + + + + + export class Navbar extends Component { + render() { return ( @@ -16,8 +47,8 @@ export class Navbar extends Component { - + +
diff --git a/twistter-frontend/src/pages/Login.js b/twistter-frontend/src/pages/Login.js index 9492372..7e3be76 100644 --- a/twistter-frontend/src/pages/Login.js +++ b/twistter-frontend/src/pages/Login.js @@ -1,7 +1,6 @@ /* eslint-disable */ import React, { Component } from 'react'; -import '../App.css'; -import axios from 'axios'; +// import '../App.css'; import PropTypes from 'prop-types'; import logo from '../images/twistter-logo.png'; @@ -124,7 +123,7 @@ export class Login extends Component { ({ const mapActionsToProps = { loginUser } + Login.propTypes = { classes: PropTypes.object.isRequired }; +// This mapStateToProps is just synchronizing the 'state' to 'this.props' so we can access it +// The state contains info about the current logged in user export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(Login)); diff --git a/twistter-frontend/src/pages/Logout.js b/twistter-frontend/src/pages/Logout.js new file mode 100644 index 0000000..cd31c79 --- /dev/null +++ b/twistter-frontend/src/pages/Logout.js @@ -0,0 +1,56 @@ +/* eslint-disable */ +import React, { Component } from "react"; +import PropTypes from "prop-types"; + +// Material UI stuff +import Button from "@material-ui/core/Button"; +import withStyles from "@material-ui/core/styles/withStyles"; + +// Redux stuff +import { logoutUser } from "../redux/actions/userActions"; +import { connect } from "react-redux"; + +const styles = { + form: { + textAlign: "center" + }, + textField: { + marginBottom: 30 + }, + pageTitle: { + marginBottom: 40 + }, + button: { + positon: "relative", + marginBottom: 30 + }, + progress: { + position: "absolute" + } +}; + +export class Logout extends Component { + + componentDidMount() { + this.props.logoutUser(); + this.props.history.push('/'); + } + + render() { + return null; + } +} + +const mapStateToProps = (state) => ({ + user: state.user +}); + +const mapActionsToProps = { logoutUser }; + +Logout.propTypes = { + logoutUser: PropTypes.func.isRequired, + user: PropTypes.object.isRequired, + classes: PropTypes.object.isRequired +}; + +export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(Logout)); diff --git a/twistter-frontend/src/pages/Register.js b/twistter-frontend/src/pages/Register.js deleted file mode 100644 index d168315..0000000 --- a/twistter-frontend/src/pages/Register.js +++ /dev/null @@ -1,97 +0,0 @@ -import React, { Component } from 'react'; -import '../App.css'; - -import logo from '../images/twistter-logo.png'; -import axios from 'axios'; -import TextField from '@material-ui/core/TextField'; -import PropTypes from 'prop-types'; - - -class Register extends Component { - - constructor() { - super(); - this.state = { - email: '', - handle: '', - password: '', - confirmPassword: '', - errors: {} - }; - - this.handleSubmit = this.handleSubmit.bind(this); - this.handleChange = this.handleChange.bind(this); -}; - - -handleSubmit = (event) => { - const newUserData = { - email: this.state.email, - handle: this.state.handle, - password: this.state.password, - confirmPassword: this.state.confirmPassword - }; - axios.post('http://localhost:5001/twistter-e4649/us-central1/api/signup', newUserData) - .then(res => { - console.log(res.data); - localStorage.setItem('firebaseIdToken', `Bearer ${res.data.token}`); - this.props.history.push('/'); - }) - .catch(err => { - this.setState({ - errors: err.response.data - }); - }); - alert("You successfully registered"); - event.preventDefault(); - this.setState({email: '', handle: '', password: '', confirmPassword: ''}); -}; - - -handleChange = (event) => { - this.setState({ - [event.target.name]: event.target.value - }); -}; - - render() { - const { classes } = this.props; - const { errors } = this.state; - return ( -
- logo -

- Create your account -

- - - -

- -

- -

- -

- { - errors.general && - (
- {errors.general} -
) - } - - -
- ); - } - -} -Register.propTypes = { - classes: PropTypes.object.isRequired -}; - -export default Register; \ No newline at end of file diff --git a/twistter-frontend/src/pages/Signup.js b/twistter-frontend/src/pages/Signup.js new file mode 100644 index 0000000..d4b9c3c --- /dev/null +++ b/twistter-frontend/src/pages/Signup.js @@ -0,0 +1,197 @@ +/* eslint-disable */ +import React, { Component } from 'react'; +// import '../App.css'; +import PropTypes from 'prop-types'; + +import logo from '../images/twistter-logo.png'; + +// Material-UI stuff +import Button from "@material-ui/core/Button"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import Grid from "@material-ui/core/Grid"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import withStyles from "@material-ui/core/styles/withStyles"; + +// Redux stuff +import { connect } from 'react-redux'; +import { signupUser } from '../redux/actions/userActions'; + +const styles = { + form: { + textAlign: "center" + }, + textField: { + marginBottom: 30 + }, + pageTitle: { + marginBottom: 40 + }, + button: { + positon: "relative", + marginBottom: 30 + }, + progress: { + position: "absolute" + } +}; + +export class Signup extends Component { + + // Constructor for the state + constructor() { + super(); + this.state = { + handle: "", + email: "", + password:"", + confirmPassword: "", + errors: {} + }; + } + + componentWillReceiveProps(nextProps) { + if (nextProps.UI.errors) { + this.setState({ errors: nextProps.UI.errors }); + } + } + + // Runs whenever the submit button is clicked. + // Updates the database entry of the signed in user with the + // data stored in the state. + handleSubmit = (event) => { + event.preventDefault(); + const signupData = { + handle: this.state.handle, + email: this.state.email, + password: this.state.password, + confirmPassword: this.state.confirmPassword + }; + console.log(signupData) + this.props.signupUser(signupData, this.props.history); + }; + + // Updates the state whenever one of the textboxes changes. + // The key is the name of the textbox and the value is the + // value in the text box. + handleChange = (event) => { + this.setState({ + [event.target.name]: event.target.value, + errors: { + [event.target.name]: null + } + }); + }; + + render() { + const { classes, UI: { loading } } = this.props; + const { errors } = this.state; + + return ( + + + + logo + + Create a new account + +
+ + + + + + {errors.general && ( + Wrong Email or Password + )} + +
+ + + ); + } +} + +// Proptypes just confirms that all data in it exists and is of the type that it +// is declared to be +Signup.propTypes = { + classes: PropTypes.object.isRequired, + signupUser: PropTypes.func.isRequired, + user: PropTypes.object.isRequired, + UI: PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => ({ + user: state.user, + UI: state.UI, +}); + +const mapActionsToProps = { + signupUser +} + +Signup.propTypes = { + classes: PropTypes.object.isRequired +}; + +// This mapStateToProps is just synchronizing the 'state' to 'this.props' so we can access it +// The state contains info about the current logged in user + +export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(Signup)); \ No newline at end of file diff --git a/twistter-frontend/src/pages/edit.js b/twistter-frontend/src/pages/editProfile.js similarity index 94% rename from twistter-frontend/src/pages/edit.js rename to twistter-frontend/src/pages/editProfile.js index 14d5172..3c3c51b 100644 --- a/twistter-frontend/src/pages/edit.js +++ b/twistter-frontend/src/pages/editProfile.js @@ -50,6 +50,12 @@ export class edit extends Component { }) .catch((err) => { console.error(err); + if (err.response.status === 403) { + alert("You are not logged in"); + // TODO: Redirect them, to the profile they are trying to edit + // If they are on /itsjimmy/edit, they will be redirected to /itsjimmy + this.props.history.push('../'); + } }); } @@ -104,6 +110,7 @@ export class edit extends Component { // Updates the state whenever one of the textboxes changes. // The key is the name of the textbox and the value is the // value in the text box. + // Also sets errors to null of textboxes that have been edited handleChange = (event) => { this.setState({ [event.target.name]: event.target.value, diff --git a/twistter-frontend/src/redux/actions/userActions.js b/twistter-frontend/src/redux/actions/userActions.js index cc6eb56..9b9b9a1 100644 --- a/twistter-frontend/src/redux/actions/userActions.js +++ b/twistter-frontend/src/redux/actions/userActions.js @@ -1,4 +1,4 @@ -import {SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI} from '../types'; +import {SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI, SET_AUTHENTICATED, SET_UNAUTHENTICATED} from '../types'; import axios from 'axios'; @@ -19,9 +19,7 @@ export const loginUser = (loginData, history) => (dispatch) => { .post("/login", loginData) .then((res) => { // Save the login token - const FBIdToken = `Bearer ${res.data.token}`; - localStorage.setItem('FBIdToken', FBIdToken); - axios.defaults.headers.common['Authorization'] = FBIdToken; + setAuthorizationHeader(res.data.token); dispatch(getUserData()); dispatch({ type: CLEAR_ERRORS }) // Redirects to home page @@ -33,4 +31,38 @@ export const loginUser = (loginData, history) => (dispatch) => { payload: err.response.data, }) }); +}; + +export const signupUser = (newUserData, history) => (dispatch) => { + dispatch({ type: LOADING_UI }); + axios + .post("/signup", newUserData) + .then((res) => { + console.log(res); + console.log(res.data); + // Save the signup token + setAuthorizationHeader(res.data.token); + dispatch(getUserData()); + dispatch({ type: CLEAR_ERRORS }) + // Redirects to home page + history.push('/home'); + }) + .catch((err) => { + dispatch ({ + type: SET_ERRORS, + payload: err.response.data, + }) + }); +}; + +export const logoutUser = () => (dispatch) => { + localStorage.removeItem('FBIdToken'); + delete axios.defaults.headers.common['Authorization']; + dispatch({ type: SET_UNAUTHENTICATED }); +} + +const setAuthorizationHeader = (token) => { + const FBIdToken = `Bearer ${token}`; + localStorage.setItem('FBIdToken', FBIdToken); + axios.defaults.headers.common['Authorization'] = FBIdToken; } \ No newline at end of file diff --git a/twistter-frontend/src/redux/store.js b/twistter-frontend/src/redux/store.js index b79ebe6..e84bcbb 100644 --- a/twistter-frontend/src/redux/store.js +++ b/twistter-frontend/src/redux/store.js @@ -15,12 +15,13 @@ const reducers = combineReducers({ UI: uiReducer }); + const store = createStore( reducers, initialState, compose( applyMiddleware(...middleWare), - window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() + window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f // Can be removed after debugging is finished ) ); diff --git a/twistter-frontend/src/util/AuthRoute.js b/twistter-frontend/src/util/AuthRoute.js index 309ccf0..db4eb36 100644 --- a/twistter-frontend/src/util/AuthRoute.js +++ b/twistter-frontend/src/util/AuthRoute.js @@ -1,22 +1,23 @@ -import React from 'react' -import { Route, Redirect} from 'react-router-dom'; +import React from 'react'; +import { Route, Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; const AuthRoute = ({ component: Component, authenticated, ...rest }) => ( - - authenticated === true ? : } - /> + render={(props) => + authenticated === true ? : + } + /> ); const mapStateToProps = (state) => ({ - authenticated: state.user.authenticated + authenticated: state.user.authenticated }); AuthRoute.propTypes = { - user: PropTypes.object -} + user: PropTypes.object +}; -export default connect(mapStateToProps)(AuthRoute); +export default connect(mapStateToProps)(AuthRoute); \ No newline at end of file