Merge pull request #67 from ClaytonWWilson/verify-profile

Users can be verified and have a check mark displayed on their profiles
This commit is contained in:
Clayton Wilson 2019-11-01 16:11:53 -04:00 committed by GitHub
commit e644498108
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 260 additions and 8 deletions

View File

@ -77,7 +77,8 @@ exports.signup = (req, res) => {
createdAt: newUser.createdAt, createdAt: newUser.createdAt,
userId, userId,
followedTopics: [], followedTopics: [],
imageUrl: defaultImageUrl imageUrl: defaultImageUrl,
verified: false
}; };
return db.doc(`/users/${newUser.handle}`).set(userCred); return db.doc(`/users/${newUser.handle}`).set(userCred);
}) })
@ -346,6 +347,59 @@ exports.getAuthenticatedUser = (req, res) => {
}); });
}; };
// Verifies the user sent to the request
// Must be run by the Admin user
exports.verifyUser = (req, res) => {
if (req.userData.handle !== "Admin") {
return res.status(403).json({error: "This must be done as Admin"});
}
db.doc(`/users/${req.body.user}`)
.get()
.then((doc) => {
if (doc.exists) {
let verifiedUser = doc.data();
verifiedUser.verified = true;
return db.doc(`/users/${req.body.user}`).set(verifiedUser, {merge: true});
} else {
return res.status(400).json({error: `User ${req.body.user} was not found`});
}
})
.then(() => {
return res.status(201).json({message: `${req.body.user} is now verified`});
})
.catch((err) => {
console.error(err);
return res.status(500).json({error: err.code});
});
}
// Unverifies the user sent to the request
// Must be run by admin
exports.unverifyUser = (req, res) => {
if (req.userData.handle !== "Admin") {
return res.status(403).json({error: "This must be done as Admin"});
}
db.doc(`/users/${req.body.user}`)
.get()
.then((doc) => {
if (doc.exists) {
let unverifiedUser = doc.data();
unverifiedUser.verified = false;
return db.doc(`/users/${req.body.user}`).set(unverifiedUser, {merge: true});
} else {
return res.status(400).json({error: `User ${req.body.user} was not found`});
}
})
.then(() => {
return res.status(201).json({message: `${req.body.user} is no longer verified`});
})
.catch((err) => {
console.error(err);
return res.status(500).json({error: err.code});
});
}
exports.getUserHandles = (req, res) => { exports.getUserHandles = (req, res) => {
admin admin
.firestore() .firestore()

View File

@ -17,6 +17,8 @@ const {
signup, signup,
deleteUser, deleteUser,
updateProfileInfo, updateProfileInfo,
verifyUser,
unverifyUser,
getUserHandles getUserHandles
} = require("./handlers/users"); } = require("./handlers/users");
@ -42,6 +44,14 @@ app.post("/updateProfileInfo", fbAuth, updateProfileInfo);
app.get("/user", fbAuth, getAuthenticatedUser); app.get("/user", fbAuth, getAuthenticatedUser);
// Verifies the user sent to the request
// Must be run by the Admin user
app.post("/verifyUser", fbAuth, verifyUser);
// Unverifies the user sent to the request
// Must be run by admin
app.post("/unverifyUser", fbAuth, unverifyUser);
// get user handles with search phase // get user handles with search phase
app.get("/getUserHandles", fbAuth, getUserHandles); app.get("/getUserHandles", fbAuth, getUserHandles);

View File

@ -31,6 +31,7 @@ import Delete from "./pages/Delete";
import writeMicroblog from "./Writing_Microblogs.js"; import writeMicroblog from "./Writing_Microblogs.js";
import editProfile from "./pages/editProfile"; import editProfile from "./pages/editProfile";
import userLine from "./Userline.js"; import userLine from "./Userline.js";
import verify from "./pages/verify";
import Search from "./pages/Search.js"; import Search from "./pages/Search.js";
const theme = createMuiTheme(themeObject); const theme = createMuiTheme(themeObject);
@ -76,6 +77,7 @@ class App extends Component {
<Route exact path="/home" component={home} /> <Route exact path="/home" component={home} />
<Route exact path="/user" component={user} /> <Route exact path="/user" component={user} />
<Route exact path="/edit" component={editProfile} /> <Route exact path="/edit" component={editProfile} />
<Route exact path="/verify" component={verify}/>
<Route exact path="/search" component={Search} /> <Route exact path="/search" component={Search} />
<AuthRoute exact path="/" component={home} /> <AuthRoute exact path="/" component={home} />

View File

@ -16,8 +16,9 @@ import Grid from "@material-ui/core/Grid";
import Chip from "@material-ui/core/Chip"; import Chip from "@material-ui/core/Chip";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import AddCircle from "@material-ui/icons/AddCircle"; import AddCircle from '@material-ui/icons/AddCircle';
import TextField from "@material-ui/core/TextField"; import TextField from '@material-ui/core/TextField';
import VerifiedIcon from '@material-ui/icons/CheckSharp';
// component // component
import '../App.css'; import '../App.css';
@ -72,7 +73,8 @@ class user extends Component {
.then(res => { .then(res => {
this.setState({ this.setState({
profile: res.data.credentials.handle, profile: res.data.credentials.handle,
imageUrl: res.data.credentials.imageUrl imageUrl: res.data.credentials.imageUrl,
verified: res.data.credentials.verified ? res.data.credentials.verified : false
}); });
}) })
.catch(err => console.log(err)); .catch(err => console.log(err));
@ -100,10 +102,12 @@ class user extends Component {
render() { render() {
let authenticated = this.props.user.authenticated; let authenticated = this.props.user.authenticated;
let classes = this.props; let classes = this.props;
let profileMarkup = this.state.profile ? ( let profileMarkup = this.state.profile ? (
<p> <div>
<Typography variant='h5'>{this.state.profile}</Typography> <Typography variant='h5'>@{this.state.profile} {this.state.verified ? (<VerifiedIcon style={{fill: "#1397D5"}}/>): (null)}</Typography>
</p>) : (<p>loading username...</p>); </div>) : (<p>loading username...</p>);
let topicsMarkup = this.state.topics ? ( let topicsMarkup = this.state.topics ? (
this.state.topics.map( this.state.topics.map(
topic => ( topic => (
@ -166,7 +170,36 @@ class user extends Component {
onClick={this.handleAddCircle} onClick={this.handleAddCircle}
/> />
<br /> <br />
{authenticated && <Button component={ Link } to='/edit'>Edit Profile Info</Button>} <Grid container direction="column">
<Grid item>
{
authenticated &&
<Button
style={{width:150, marginBottom: 10, marginTop: 5}}
component={ Link }
to='/edit'
variant="outlined"
color="primary"
>
Edit Profile
</Button>}
</Grid>
<Grid item>
{
authenticated &&
this.state.profile === 'Admin' &&
<Button
style={{width:150}}
component={ Link }
variant="outlined"
color="primary"
to='/verify'
>
Verify Users
</Button>}
</Grid>
</Grid>
</Grid> </Grid>
<Grid item sm={4} xs={8}> <Grid item sm={4} xs={8}>
{postMarkup} {postMarkup}

View File

@ -0,0 +1,153 @@
import React, { Component } from "react";
import axios from "axios";
import PropTypes from "prop-types";
// TODO: Add a read-only '@' in the left side of the handle input
// TODO: Add a cancel button, that takes the user back to their profile page
// Material-UI stuff
import Button from "@material-ui/core/Button";
import { Link } from 'react-router-dom';
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";
const styles = {
form: {
textAlign: "center"
},
textField: {
marginBottom: 30
},
pageTitle: {
// marginTop: 20,
marginBottom: 40
},
button: {
positon: "relative",
marginBottom: 10
},
progress: {
position: "absolute"
}
};
export class verify extends Component {
// Constructor for the state
constructor() {
super();
this.state = {
handle: "",
loading: false,
errors: {}
};
}
// // Runs whenever the submit button is clicked.
handleSubmit = (event) => {
event.preventDefault();
this.setState({
loading: true
});
const verifyHandle = {
user: this.state.handle
};
axios
.post("/verifyUser", verifyHandle)
.then((res) => {
console.log(res);
this.setState({
loading: false
});
// this.props.history.push('/');
// TODO: Need to redirect user to their profile page
})
.catch((err) => {
console.log(err);
this.setState({
errors: err.response.data,
loading: false
});
});
};
// 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,
errors: {
[event.target.name]: null
}
});
};
render() {
const { classes } = this.props;
const { errors, loading } = this.state;
return (
<Grid container className={classes.form}>
<Grid item sm />
<Grid item sm>
<Typography variant="h4" className={classes.pageTitle}>
Verify Users
</Typography>
<form noValidate onSubmit={this.handleSubmit}>
<TextField
id="handle"
name="handle"
label="Username"
className={classes.textField}
value={this.state.handle}
// helperText={errors.handle}
// error={errors.handle ? true : false}
variant="outlined"
onChange={this.handleChange}
fullWidth
/>
<Grid container direction="column">
<Grid item>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
disabled={loading}
>
Submit
{loading && (
<CircularProgress size={30} className={classes.progress} />
)}
</Button>
</Grid>
<Grid item>
<Button
variant="oulined"
color="primary"
className={classes.button}
component={ Link }
to='/user'
>
Back to Profile
</Button>
</Grid>
</Grid>
</form>
</Grid>
<Grid item sm />
</Grid>
);
}
}
verify.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(verify);