Skip to content
Snippets Groups Projects
Commit 0b1d6844 authored by Khalid, Rizwan (UG - Computer Science)'s avatar Khalid, Rizwan (UG - Computer Science)
Browse files

Added ViewPost and AddPost page and started working on the signup/signin

parent 1d41a97c
No related branches found
No related tags found
2 merge requests!8CI/CD,!1Frontend
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { ApolloClient, ApolloProvider, InMemoryCache, createHttpLink, ApolloLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { darkTheme } from './styles.js'
import Home from './components/Pages/Home/Home.js';
import Header from './components/Header/Header.js';
import { Container, CssBaseline, ThemeProvider } from '@material-ui/core';
import ViewPost from './components/Pages/ViewPost/ViewPost.js';
import AddPost from './components/Pages/AddPost/AddPost.js';
import Auth from './components/Auth/Auth.js';
import { AuthProvider } from './context/auth.js';
import AuthRoute from './util/AuthRoute.js';
const httpLink = createHttpLink({
uri: 'http://localhost:5000/graphql'
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('profile');
return {
headers: {
...headers,
Authorization: token ? `Bearer ${token}` : "",
}
}
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
const App = () => {
return (
<div>
Hi
</div>
<BrowserRouter>
<ApolloProvider client={client}>
<AuthProvider>
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<Container maxWidth="lg">
<Header />
<Switch>
<Route path="/" exact component={Home} />
<AuthRoute path="/auth" component={Auth} />
<Route path="/post/:id" component={ViewPost} />
<Route path="/addPost" component={AddPost} />
</Switch>
</Container>
</ThemeProvider>
</AuthProvider>
</ApolloProvider>
</BrowserRouter>
)
}
export default App;
\ No newline at end of file
export default App
import React from 'react'
const Auth = () => {
return (
<div>
TODO
</div>
)
}
export default Auth
......@@ -15,7 +15,7 @@ const FeaturedPost = ({ post }) => {
const history = useHistory();
const viewPost = (postId) => {
history.push(`/post/${postId}`); //TODO implement route
history.push(`/post/${postId}`);
}
return (
......
import React, { useState, useContext } from 'react';
import { useHistory } from 'react-router-dom';
import { useMutation } from '@apollo/client';
import { CREATE_POST, GET_POSTS } from '../../../queries/queries.js';
import { AuthContext } from '../../../context/auth.js'
import { Avatar, Button, Container, Paper, Grid, Typography, TextField } from '@material-ui/core';
import useStyles from './styles';
import PostAddIcon from '@material-ui/icons/PostAdd';
import { DropzoneArea } from 'material-ui-dropzone';
const AddPost = () => {
const classes = useStyles();
const history = useHistory();
const { user } = useContext(AuthContext);
const initialState = {
title: "",
description: "",
image: "",
};
const [formData, setFormData] = useState(initialState);
const validationState = {
title: { error: false, errorMessage: ""},
description: { error: false, errorMessage: ""},
};
const [errorData, setErrorData] = useState(validationState);
const [createNewPost, { error }] = useMutation(CREATE_POST, {
update(proxy, result) {
const data = proxy.readQuery({
query: GET_POSTS
});
if (data) {
proxy.writeQuery({ query: GET_POSTS, data: { getPosts: [result.data.createPost, ...data.getPosts] }});
}
setFormData(initialState);
history.push("/");
},
refetchQueries: [{ query: GET_POSTS }]
});
const validate = () => {
let isValid = true;
let startingState = {
title: { error: false, errorMessage: ""},
description: { error: false, errorMessage: ""},
};
if (formData.title.trim().length === 0) {
startingState = { ...startingState, title: { error: true, errorMessage: "Required" } };
isValid = false;
}
if (formData.description.trim().length === 0) {
startingState = { ...startingState, description: { error: true, errorMessage: "Required" } };
isValid = false;
}
setErrorData(startingState);
return isValid;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
createNewPost({
variables: {
title: formData.title,
description: formData.description,
image: formData.image
}
})
}
};
const base64conv = (files, cb) => {
for (let i = 0; i < files.length; i++) {
let file = files[i];
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
let fileInfo = {
name: file.name,
type: file.type,
size: Math.round(file.size / 1000) + ' kB',
base64: reader.result,
file: file,
};
cb(fileInfo);
}
}
};
const handleFileUpload = async (images) => {
if (images === undefined) return;
base64conv(images, (fileInfo) => {
setFormData({ ...formData, image: fileInfo.base64 })
});
};
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value })
}
return (
<Container component="main" maxWidth="xs" style={{ marginBottom: "32px" }}>
<Paper className={classes.paper} elevation={3}>
{
user ?
(
<>
<Avatar className={classes.avatar}>
<PostAddIcon />
</Avatar>
<Typography variant="h5">Add Post</Typography>
<form className={classes.form} onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={12} sm={12}>
<TextField
name="title"
onChange={handleChange}
variant="outlined"
required
fullWidth
error={errorData.title.error}
helperText={errorData.title.errorMessage}
label="Title"
autoFocus={true}
type="text"
/>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
name="description"
onChange={handleChange}
variant="outlined"
required
fullWidth
error={errorData.description.error}
helperText={errorData.description.errorMessage}
label="Description"
multiline
rowsMax={4}
/>
</Grid>
<DropzoneArea
acceptedFiles={['image/*']}
filesLimit={1}
dropzoneText={"Drag and drop an image here or click here"}
onChange={(files) => handleFileUpload(files)}
/>
</Grid>
<Button type="submit" fullWidth variant="contained" color="primary" className={classes.submit} onClick={validate}>
Submit Post
</Button>
</form>
</>
)
: <Typography variant="h5">Please Sign In</Typography>
}
</Paper>
</Container>
)
}
export default AddPost;
import { makeStyles } from '@material-ui/core/styles';
export default makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: theme.spacing(2),
},
root: {
'& .MuiTextField-root': {
margin: theme.spacing(1),
},
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.primary.main,
},
form: {
width: '100%',
marginTop: theme.spacing(3),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
googleButton: {
marginBottom: theme.spacing(2),
},
}));
\ No newline at end of file
import React, { useState, useContext } from 'react';
import { useQuery, useMutation } from '@apollo/client';
import { GET_POST_BY_ID, DELETE_POST, GET_POSTS, LIKE_POST, ADD_COMMENT } from '../../../queries/queries.js';
import { AuthContext } from '../../../context/auth.js'
import { Button, Card, CardContent, CardMedia, Divider, Grid, TextField, Typography } from '@material-ui/core';
import ThumbUpOutlinedIcon from '@material-ui/icons/ThumbUpOutlined';
import ThumbUpIcon from '@material-ui/icons/ThumbUp';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import useStyles from './styles.js';
import Comment from '../../Comment/Comment.js';
import { useParams, useHistory } from 'react-router';
const ViewPost = () => {
const classes = useStyles();
const history = useHistory();
const { id } = useParams();
const { data } = useQuery(GET_POST_BY_ID, { variables: { postId: id }}, { errorPolicy: "all" });
const post = data?.getPost;
const { user } = useContext(AuthContext);
const[likePost] = useMutation(LIKE_POST, {
errorPolicy: "all"
});
const [newComment, setNewComment] = useState("");
const [commentErrors, setCommentErrors] = useState({ error: false, errorMessage: "" });
const [open, setOpen] = useState(false);
const [addComment] = useMutation(ADD_COMMENT, {
errorPolicy: "all",
update() {
setNewComment("");
}
});
const [deletePostAndComments] = useMutation(DELETE_POST, {
errorPolicy: "all",
update(proxy) {
setOpen(false);
const data = proxy.readQuery({ query: GET_POSTS });
proxy.writeQuery({ query: GET_POSTS, data: { getPosts: data.getPosts.filter(p => p._id !== post._id) }})
history.push("/");
}
});
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleDelete = () => {
deletePost();
}
const deletePost = () => {
deletePostAndComments({
variables: { postId: post._id },
refetchQueries: [{ query: GET_POSTS }],
});
}
const validateComment = () => {
let isValid = true;
let validationState = { error: false, errorMessage: "" };
if (newComment.trim().length === 0) {
isValid = false;
validationState = { error: true, errorMessage: "Comment must not be empty" };
}
setCommentErrors(validationState);
return isValid;
}
const handleCommentSubmit = (e) => {
e.preventDefault();
if (validateComment()) {
addComment({
variables: {
body: newComment,
postId: post._id
},
refetchQueries: [{ query: GET_POST_BY_ID, variables: { postId: id } }]
});
}
}
const handleLikePost = () => {
likePost({
variables: { postId: post._id },
refetchQueries: [{ query: GET_POST_BY_ID, variables: { postId: id } }]
});
}
if (post === undefined) return "Loading Post...";
return (
<Card className={classes.postCard}>
<CardContent>
<Grid container>
<Grid item xs={11}>
<Typography variant="h4">{post.title}</Typography>
</Grid>
<Grid>
{ user && user._id === post.creator._id && <Button variant="outlined" onClick={() => handleClickOpen()}>Delete</Button>}
</Grid>
</Grid>
<Divider />
<Typography variant="body1"><i>Posted by {post.creator.name} on {new Date(post.createdAt).toDateString()}</i></Typography>
<CardMedia className={classes.postImage} image={post.image}/>
<Typography variant="h6" className={classes.postDescription}>{post.description}</Typography>
<Divider />
<Grid container className={classes.addMarginTop}>
<Grid item xs={10}>
<Typography variant="h5">Comments ({post.comments.length})</Typography>
</Grid>
<Grid item align="center">
<Typography variant="h5" >
{post.likes.length} Like{post.likes.length > 1 && "s"}
</Typography>
<Button onClick={() => handleLikePost()} disabled={!user}>
{ user && post.likes.includes(user._id)
? <ThumbUpIcon color="primary" />
: <ThumbUpOutlinedIcon />
}
</Button>
</Grid>
</Grid>
{ post.comments.map(comment => <Comment key={comment._id} comment={comment}/>) }
{ post.comments.length === 0 && <Divider /> }
{ user &&
<Grid style={{ marginTop: "32px" }} container alignItems="center">
<Grid item xs={10}>
<TextField style={{ width: "100%"}} name="comment" label="Comment" variant="outlined" error={commentErrors.error} helperText={commentErrors.errorMessage} onChange={(e) => setNewComment(e.target.value)}/>
</Grid>
<Grid>
<Button style={{ marginLeft: "16px" }} size="large" onClick={(e) => handleCommentSubmit(e)}>Add Comment</Button>
</Grid>
</Grid>
}
</CardContent>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Are you sure you want to delete this post?</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
This post and all its comments and likes will be removed forever
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleDelete} color="primary" autoFocus>
Delete
</Button>
</DialogActions>
</Dialog>
</Card>
)
}
export default ViewPost;
import { makeStyles } from '@material-ui/core/styles';
export default makeStyles((theme) => ({
postCard: {
padding: theme.spacing(3),
display: 'flex',
flexDirection: 'column',
marginBottom: theme.spacing(4)
},
postImage: {
paddingTop: '56.25%',
marginTop: theme.spacing(4)
},
postDescription: {
marginTop: theme.spacing(4),
marginBottom: theme.spacing(4)
},
addMarginTop: {
marginTop: theme.spacing(4),
}
}));
\ No newline at end of file
......@@ -13,7 +13,7 @@ const Post = ({ post }) => {
const history = useHistory();
const viewPost = (postId) => {
history.push(`/post/${postId}`); //TODO implement route
history.push(`/post/${postId}`);
}
return (
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment