<repo_name>Danish903/post_client<file_sep>/src/components/AddPhoto.js
import React from "react";
import { Mutation } from "react-apollo";
import { gql } from "apollo-boost";
import { GET_EVENTS_QUERY } from "./EventDashboard";
import {
   Button,
   Form,
   Message,
   Container,
   TextArea,
   Checkbox
} from "semantic-ui-react";
import { toast } from "react-semantic-toasts/build/toast";
import { USER_POST_QUERY } from "./UserProfile";
import Dropzone from "./Dropzone";

const styleLoadingImage = { opacity: "0.3", filter: "grayscale(100)" };
const upload = gql`
   mutation(
      $file: Upload!
      $title: String!
      $imageURL: String!
      $description: String!
      $published: Boolean!
      $disableComment: Boolean!
      $imageURL_ID: String!
   ) {
      singleUpload(
         data: {
            title: $title
            imageURL: $imageURL
            description: $description
            published: $published
            disableComment: $disableComment
            imageURL_ID: $imageURL_ID
            file: $file
         }
      ) {
         success
      }
   }
`;

const initState = {
   title: "",
   imageURL: "",
   imageURL_ID: "",
   description: "",
   isPublished: false,
   published: true,
   disableComment: false,
   file: null,
   imagePreview: "",
   error: ""
};
class AddPhoto extends React.Component {
   state = {
      title: "",
      imageURL: "",
      imageURL_ID: "",
      description: "",
      isPublished: false,
      published: true,
      disableComment: false,
      image: "",
      loading: false,
      imageUploaded: false,
      file: null,
      imagePreview: null,
      error: ""
   };

   _onChange = (e, data) => {
      const { name, value, checked } = data;

      if (name === "published") {
         this.setState({
            published: !checked,
            isPublished: checked
         });
      } else if (!value) {
         this.setState({
            [name]: value ? value : checked
         });
      } else {
         this.setState({
            [name]: value ? value : checked
         });
      }
   };

   onDrop = ([file]) => {
      const preview = URL.createObjectURL(file);
      this.setState({
         file,
         image: file,
         imagePreview: preview,
         imageURL: preview,
         imageURL_ID: preview
      });
   };

   imageUploading = () => {
      toast({
         description: `Image uploading...`,
         icon: "warning sign",
         type: "success"
      });
   };
   imageUploadedSuccessfully = () => {
      toast({
         description: `Image uploaded Successfully!`,
         icon: "warning sign",
         type: "success"
      });
   };

   _onSubmit = async upload => {
      if (!this.state.file || !this.state.title || !this.state.description) {
         this.setState({ error: "You need to upload an image" });
         return;
      }
      this.setState({ error: "" });
      const variables = {
         title: this.state.title,
         imageURL: this.state.imageURL,
         description: this.state.description,
         published: this.state.published,
         disableComment: this.state.disableComment,
         imageURL_ID: this.state.imageURL_ID,

         file: this.state.file
      };
      this.setState({ loading: true });
      try {
         await upload({ variables });

         this.setState({ loading: false, ...initState });
         this.imageUploadedSuccessfully();
         this.props.history.push("/");
         return;
      } catch (error) {
         console.log(error);
      }
      this.setState({ loading: false, ...initState });
   };

   render() {
      return (
         <Mutation
            mutation={upload}
            refetchQueries={[
               { query: GET_EVENTS_QUERY },
               {
                  query: USER_POST_QUERY
               }
            ]}
         >
            {(upload, { data, loading, error }) => {
               if (error) return <p>{error.message}</p>;
               return (
                  <Container>
                     <Message
                        attached
                        header="Upload your beautiful pciture !"
                        content="Fill out the form below to share your photo"
                     />

                     {this.state.imagePreview && (
                        <img
                           width="200px"
                           src={this.state.imagePreview}
                           alt="imagdde"
                           style={loading ? styleLoadingImage : null}
                        />
                     )}
                     <Form
                        className="attached fluid segment"
                        onSubmit={() => this._onSubmit(upload)}
                     >
                        {!!this.state.error && !this.state.imagePreview && (
                           <Message negative>
                              <p>{this.state.error}</p>
                           </Message>
                        )}
                        {!this.state.imagePreview && !loading && (
                           <Dropzone onDrop={this.onDrop} />
                        )}
                        <Form.Field>
                           <Form.Input
                              fluid
                              label="Title"
                              placeholder="title"
                              type="text"
                              name="title"
                              value={this.state.title}
                              onChange={this._onChange}
                              disabled={this.state.loading}
                              required
                           />
                        </Form.Field>

                        <Form.Field>
                           <label
                              style={
                                 this.state.loading
                                    ? { color: "rgba(0,0,0,.25)" }
                                    : null
                              }
                           >
                              Description
                           </label>
                           <TextArea
                              placeholder="Tell us more"
                              autoHeight
                              name="description"
                              onChange={this._onChange}
                              required
                              value={this.state.description}
                              disabled={this.state.loading}
                           />
                        </Form.Field>
                        <Form.Field>
                           <Checkbox
                              type="checkbox"
                              label="Disable comments for your post"
                              toggle
                              name="disableComment"
                              checked={this.state.disableComment}
                              onChange={this._onChange}
                              disabled={this.state.loading}
                           />
                        </Form.Field>
                        <Form.Field>
                           <Checkbox
                              type="checkbox"
                              label="Make your post private"
                              toggle
                              name="published"
                              checked={this.state.isPublished}
                              onChange={this._onChange}
                              disabled={this.state.loading}
                           />
                        </Form.Field>

                        <Button
                           basic
                           color="black"
                           type="submit"
                           content="Upload your photo"
                           disabled={loading}
                           loading={loading}
                        />
                     </Form>
                  </Container>
               );
            }}
         </Mutation>
      );
   }
}

export default AddPhoto;
<file_sep>/README.md
<div align="center"><strong>N</strong>ode <strong>A</strong>pollo <strong>P</strong>risma <strong>E</strong>xpress <strong>R</strong>eact <strong>G</strong>raphQL <strong>P</strong>ostgresql
</div>
  
<h1 align="center"><strong>Fullstack GraphQL App with React & Prisma</strong></h1>
<h3 align="center">Authentication with roles & permissions. Backend & Frontend</h3>
<h3 align="center">Upload image with expressJs</h3>
<h3 align="center">Messages with GraphQL Subscriptions</h3>
<br />

![image](https://user-images.githubusercontent.com/15246526/38530809-7a9cc69e-3c21-11e8-8eb9-6f143eb7d64d.png)

<div align="center"><strong>🚀 fullstack GraphQL app</strong></div>

### [Online Demo](https://photoups.netlify.com/)

As the Demo Server is hosted on a free Heroku account, the servers its hosted on enter `sleep mode` when not in use. If you notice a delay, please allow a few seconds for the servers to wake and try refreshing a browser.

## Installation

1. Clone project

```
git clone git@github.com:Danish903/post_client.git
```

2. cd into post_client

```
cd web
```

3. Download dependencies

```
npm i
```

4. In `web` create a file called `.env` and add the following line inside: `REACT_APP_SERVER_URL=https://insta-app-server.herokuapp.com` and
   `REACT_APP_WS_URL=wss://insta-app-server.herokuapp.com`

5. Start app

```
npm start
```

## Features

-  Web login/Signup
-  User can post their beautiful pictures
-  User can like/comments in real time on their or other user's post
-  User can make their post private and can disable comments for their post
-  Post owner can delete other user's comments
-  Pagination: Infinite scroller is used for paginating pictures and comments

## Made with..

Frontend:

-  User interfaces: React https://reactjs.org/
-  Design: semantic-ui-react https://react.semantic-ui.com/
-  GraphQL tool: Apollo Client https://www.apollographql.com/

Backend:

-  Server GraphQL: GraphQL yoga https://github.com/prismagraphql/graphql-yoga
-  ORM (object-relational mapping): Prisma https://www.prisma.io/
-  Database Postgresql: https://www.postgresql.org/

## App graphql api source

[Server Code](https://github.com/Danish903/post_server)
<file_sep>/src/components/User.js
import React, { Component } from "react";
import { Query } from "react-apollo";
import { gql } from "apollo-boost";

export const ME_QUERY = gql`
   query {
      me {
         id
         username
         favorites {
            id
            event {
               id
            }
         }
      }
   }
`;
export default class User extends Component {
   render() {
      return (
         <Query {...this.props} query={ME_QUERY}>
            {payload => this.props.children(payload)}
         </Query>
      );
   }
}
<file_sep>/src/components/DeleteModal.js
import React, { Component } from "react";
import { Button, Header, Image, Modal } from "semantic-ui-react";
import { Mutation } from "react-apollo";
import { gql } from "apollo-boost";
import { USER_POST_QUERY } from "./UserProfile";
import { toast } from "react-semantic-toasts";
const DELETE_POST_MUTATION = gql`
   mutation($id: ID!, $img_ID: String) {
      deletePost(id: $id, img_ID: $img_ID) {
         id
      }
   }
`;
class ModalExampleDimmer extends Component {
   state = { open: false };

   show = dimmer => () => this.setState({ dimmer, open: true });
   close = () => {
      this.setState({ open: false });
   };

   deletePost = async deletePost => {
      this.setState({ open: false });
      deletePost().then(() => {
         toast({
            description: `Your post is removed.`,
            icon: "warning sign",
            type: "success"
         });
      });
   };
   _update = (cache, { data: { deletePost } }) => {
      const data = cache.readQuery({ query: USER_POST_QUERY });
      data.userPosts = data.userPosts.filter(post => post.id !== deletePost.id);
      cache.writeQuery({ query: USER_POST_QUERY, data });
   };

   render() {
      const { open } = this.state;
      const { event } = this.props;
      return (
         <Mutation
            mutation={DELETE_POST_MUTATION}
            variables={{ id: event.id, img_ID: event.imageURL_ID }}
            update={this._update}
            optimisticResponse={{
               __typename: "Mutation",
               deletePost: {
                  __typename: "Event",
                  id: event.id
               }
            }}
         >
            {deletePost => (
               <>
                  <Button
                     basic
                     color="red"
                     style={{ width: "100%" }}
                     onClick={this.show(true)}
                  >
                     Delete this post
                  </Button>

                  <Modal open={open} onClose={this.close} size="small">
                     <Modal.Header>Delete This Post </Modal.Header>
                     <Modal.Content image>
                        <Image wrapped src={event.imageURL} size="small" />
                        <Modal.Description>
                           <Header>{event.title}</Header>
                           <p>{event.Description}</p>
                           <p>Do you really want to delete this post?</p>
                        </Modal.Description>
                     </Modal.Content>
                     <Modal.Actions>
                        <Button color="black" onClick={this.close}>
                           Nope
                        </Button>
                        <Button
                           icon="checkmark"
                           labelPosition="right"
                           content="Delete"
                           onClick={() => this.deletePost(deletePost)}
                           color="red"
                        />
                     </Modal.Actions>
                  </Modal>
               </>
            )}
         </Mutation>
      );
   }
}

export default ModalExampleDimmer;
<file_sep>/src/components/Login.js
import React from "react";
import { compose, graphql } from "react-apollo";
import { gql } from "apollo-boost";
import { Link } from "react-router-dom";
import {
   Button,
   Container,
   Form,
   Header,
   Message,
   Icon
} from "semantic-ui-react";
import { withFormik, Field } from "formik";
import { validateLoginSchema } from "../utils/formValidationSchema";
import CustomInputComponet from "./CustomInputComponent";
import { ME_QUERY } from "./User";
import Loader from "./Loader";

const LOGIN_MUTATION = gql`
   mutation($email: String!, $password: String!) {
      login(data: { email: $email, password: $password }) {
         token
      }
   }
`;

const _onSubmit = handleSubmit => {
   handleSubmit();
};
const Login = ({
   errors,
   handleSubmit,
   isSubmitting,
   data: { me, loading }
}) => {
   if (loading) return <Loader />;
   return (
      <Container fluid={false} text>
         {errors && errors.message ? (
            <Header as="h4" color="red">
               {errors.message}
            </Header>
         ) : null}

         <Message
            attached
            header="Welcome to our site!"
            content="Fill out the form below to sign-in"
         />
         <Form
            className="attached fluid segment"
            onSubmit={() => _onSubmit(handleSubmit)}
         >
            <Field
               label="Email"
               type="email"
               name="email"
               placeholder="<EMAIL>"
               component={CustomInputComponet}
            />
            <Field
               label="<PASSWORD>"
               type="<PASSWORD>"
               name="password"
               placeholder="***************"
               component={CustomInputComponet}
            />
            <Button type="submit" disabled={isSubmitting}>
               Login
            </Button>
         </Form>
         <Message attached="bottom" warning>
            <Icon name="help" />
            Don't have account?&nbsp;
            <Link to="/signup">Signup Here</Link>
            &nbsp;instead.
         </Message>
      </Container>
   );
};

// export default Login;
export default compose(
   graphql(ME_QUERY),
   graphql(LOGIN_MUTATION, {
      options: ({ values }) => ({
         refetchQueries: [
            {
               query: ME_QUERY
            }
         ],
         ...values
      })
   }),
   withFormik({
      mapPropsToValues: () => ({ email: "", password: "" }),
      validationSchema: validateLoginSchema,
      handleSubmit: async (
         values,
         { props, setErrors, setSubmitting, resetForm }
      ) => {
         try {
            const res = await props.mutate({
               variables: values
            });

            localStorage.setItem("token", res.data.login.token);
            setSubmitting(false);
            await props.data.refetch({
               query: ME_QUERY
            });

            resetForm();
            props.history.push("/");
         } catch (error) {
            setErrors(error);
            setSubmitting(false);
         }
      }
   })
)(Login);
<file_sep>/src/components/LikeButton.js
import React, { Component } from "react";
import { Mutation, Query } from "react-apollo";
import { gql } from "apollo-boost";
import { Button } from "semantic-ui-react";
import { toast } from "react-semantic-toasts";
import debounce from "lodash.debounce";
import { ME_QUERY } from "./User";
import { GET_EVENTS_QUERY } from "./EventDashboard";
import _ from "lodash";
import User from "./User";

const LIKE_SUBSCRIPTION = gql`
   subscription($eventId: ID!) {
      favoriteEvent(eventId: $eventId) {
         mutation
         node {
            id
            event {
               id
               likes {
                  id
               }
            }
         }
         previousValues {
            id
         }
      }
   }
`;

const GET_FAVORITES_QUERY = gql`
   query($eventId: ID!) {
      getFavoriteEvent(eventId: $eventId) {
         id
         event {
            id
            likes {
               id
            }
         }
      }
   }
`;
const LIKE_PHOTO_MUTATION = gql`
   mutation($id: ID!) {
      likePhoto(id: $id) {
         id
         event {
            id
            likes {
               id
            }
         }
      }
   }
`;

const UNLIKE_PHOTO_MUTATION = gql`
   mutation($id: ID!, $favId: ID!) {
      unLikePhoto(id: $id, favId: $favId) {
         id
      }
   }
`;
class LikeCount extends Component {
   componentDidMount() {
      this.props.subscribeToNewLike();
   }

   disLikePhotoUpdate = (cache, { data: { unLikePhoto } }) => {
      const data = cache.readQuery({ query: ME_QUERY });
      data.me.favorites = data.me.favorites.filter(
         fav => fav.event.id !== this.props.id
      );
      cache.writeQuery({ query: ME_QUERY, data });
      const data2 = cache.readQuery({
         query: GET_FAVORITES_QUERY,
         variables: { eventId: this.props.id }
      });
      data2.getFavoriteEvent = data2.getFavoriteEvent.filter(
         fav => fav.id !== unLikePhoto.id
      );
      cache.writeQuery({
         query: GET_FAVORITES_QUERY,
         variables: { eventId: this.props.id },
         data: { ...data2 }
      });
   };

   likePhotoUpdate = (cache, { data: likePhoto }) => {
      const data = cache.readQuery({ query: ME_QUERY });
      data.me.favorites = [...data.me.favorites, likePhoto.likePhoto];
      cache.writeQuery({ query: ME_QUERY, data });

      const data2 = cache.readQuery({
         query: GET_FAVORITES_QUERY,
         variables: { eventId: this.props.id }
      });
      data2.getFavoriteEvent = [...data2.getFavoriteEvent, likePhoto.likePhoto];
      data2.getFavoriteEvent = _.uniqBy(data2.getFavoriteEvent, "id");

      cache.writeQuery({
         query: GET_FAVORITES_QUERY,
         variables: { eventId: this.props.id },
         data: { ...data2 }
      });
   };
   handleLike = debounce(async (likePhoto, unLikePhoto, isLiked) => {
      if (isLiked) {
         try {
            await unLikePhoto();
         } catch (error) {}
      } else {
         try {
            await likePhoto();
         } catch (error) {}
      }
   }, 200);
   render() {
      const { id, data, loading, exist, authenticated, isLiked } = this.props;
      if (loading) return <p>loading</p>;
      return (
         <Mutation
            mutation={LIKE_PHOTO_MUTATION}
            variables={{ id: id }}
            optimisticResponse={{
               __typename: "Mutation",
               likePhoto: {
                  __typename: "FavoriteEvent",
                  id: -123243434,
                  event: {
                     __typename: "Event",
                     id,
                     likes: [
                        {
                           __typename: "FavoriteEvent",
                           id: -123232
                        }
                     ]
                  }
               }
            }}
            // refetchQueries={[{ query: ME_QUERY }]}
            refetchQueries={[{ query: ME_QUERY }, { query: GET_EVENTS_QUERY }]}
            update={this.likePhotoUpdate}
         >
            {likePhoto => {
               return (
                  <Mutation
                     mutation={UNLIKE_PHOTO_MUTATION}
                     variables={{
                        id,
                        favId: !!exist ? exist.id : null
                     }}
                     optimisticResponse={{
                        __typename: "Mutation",
                        unLikePhoto: {
                           __typename: "FavoriteEvent",
                           id: !!exist ? exist.id : -123232
                        }
                     }}
                     refetchQueries={[
                        { query: ME_QUERY },
                        { query: GET_EVENTS_QUERY }
                     ]}
                     update={this.disLikePhotoUpdate}
                  >
                     {(unLikePhoto, { error }) => {
                        let length;
                        if (!data) {
                           length = 0;
                        }
                        return (
                           <Button
                              as="div"
                              labelPosition="right"
                              disabled={!authenticated}
                              size="mini"
                              onClick={() =>
                                 this.handleLike(
                                    likePhoto,
                                    unLikePhoto,
                                    isLiked
                                 )
                              }
                           >
                              <Button
                                 content={isLiked ? "Liked" : "Like"}
                                 size="mini"
                                 color={isLiked ? "red" : null}
                                 icon="heart"
                                 label={{
                                    as: "a",
                                    basic: true,
                                    content:
                                       length === 0
                                          ? 0
                                          : data.getFavoriteEvent.length
                                    // !!data &&
                                    // data.getFavoriteEvent.length === 0
                                    //    ? 0
                                    //    : data.getFavoriteEvent.length
                                 }}
                                 labelPosition="right"
                              />
                           </Button>
                        );
                     }}
                  </Mutation>
               );
            }}
         </Mutation>
      );
   }
}

class LikeButton extends Component {
   state = {
      like: false
   };
   handleLike = debounce(async (likePhoto, unLikePhoto, isLiked) => {
      if (isLiked) {
         try {
            await unLikePhoto();
         } catch (error) {
            toast({
               title: "Error",
               description: "You already unlike this post",
               icon: "warning sign",
               type: "error"
            });
         }
      } else {
         try {
            await likePhoto();
         } catch (error) {
            toast({
               title: "Error",
               description: `${error.message}`,
               icon: "warning sign",
               type: "error"
            });
         }
      }
   }, 200);

   render() {
      const { id } = this.props;

      return (
         <User>
            {({ data, loading }) => {
               const authenticated = !!data && !!data.me;
               let exist = false;
               let isLiked = false;
               if (authenticated) {
                  const { me } = data;
                  exist = me.favorites.find(fav => fav.event.id === id);

                  isLiked = !!exist;
               }

               return (
                  <Query
                     query={GET_FAVORITES_QUERY}
                     variables={{ eventId: id }}
                  >
                     {({ subscribeToMore, ...result }) => {
                        return (
                           <LikeCount
                              id={id}
                              data={data}
                              exist={exist}
                              authenticated={authenticated}
                              isLiked={isLiked}
                              {...result}
                              subscribeToNewLike={() => {
                                 subscribeToMore({
                                    document: LIKE_SUBSCRIPTION,
                                    variables: { eventId: id },
                                    updateQuery: (
                                       prev,
                                       { subscriptionData }
                                    ) => {
                                       if (!subscriptionData.data) return prev;
                                       const newLike =
                                          subscriptionData.data.favoriteEvent;

                                       if (newLike.mutation === "DELETED") {
                                          const id = newLike.previousValues.id;

                                          return {
                                             ...prev,
                                             getFavoriteEvent: prev.getFavoriteEvent.filter(
                                                fav => fav.id !== id
                                             )
                                          };
                                       }
                                       return {
                                          ...prev,
                                          getFavoriteEvent: [
                                             newLike.node,
                                             ...prev.getFavoriteEvent
                                          ]
                                       };
                                    }
                                 });
                              }}
                           />
                        );
                     }}
                  </Query>
               );
            }}
         </User>
      );
   }
}
export default LikeButton;
<file_sep>/src/components/UserPostsList.js
import React, { Component } from "react";
import moment from "moment";
import { Link } from "react-router-dom";
import { Grid, Card, Label, Icon, Form, Checkbox } from "semantic-ui-react";
import DeleteModal from "./DeleteModal";
export default class UserPostsList extends Component {
   state = {
      modalToggle: false
   };
   componentDidMount() {
      this.props.subscribeToUserPosts();
   }

   toggleModal = () => {
      this.setState(prev => ({
         ...prev,
         modalToggle: !prev.modalToggle
      }));
   };
   render() {
      const {
         data: { userPosts },
         loading,
         error
      } = this.props;

      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error</p>;
      if (userPosts.length === 0) return <p>You don't have any posts.</p>;
      return (
         <Grid style={{ paddingLeft: "50px" }}>
            {userPosts.map((event, i) => (
               <Card
                  key={event.id}
                  style={{
                     marginRight: "40px",
                     marginTop: i === 0 ? "60px !important" : 0,
                     position: "relative",
                     marginBottom: userPosts.length - 1 === i ? "14px" : null
                  }}
               >
                  <Link to={`/photoDetails/${event.id}`}>
                     <img
                        src={event.imageURL}
                        alt={event.name}
                        className="userPostsImage"
                     />
                  </Link>

                  {!event.published && (
                     <Label
                        as="a"
                        color="red"
                        ribbon="right"
                        style={{ transform: "translateX(-105%)" }}
                     >
                        This post is private.
                     </Label>
                  )}
                  <Card.Content>
                     <Card.Header>{event.title}</Card.Header>
                     <Card.Meta>
                        {" "}
                        created: {moment(event.createdAt).format("LL")}
                     </Card.Meta>
                     <Card.Meta>
                        {" "}
                        last updated: {moment(event.updatedAt).format("LL")}
                     </Card.Meta>
                     <Card.Description>{event.description}</Card.Description>
                  </Card.Content>
                  <Card.Content extra className="infoContainer">
                     <div style={{ marginRight: "auto" }}>
                        <p>
                           <Icon name="heart" />
                           {event.likes.length}
                        </p>
                        <Form.Field>
                           <Checkbox
                              type="checkbox"
                              checked={!event.published}
                              label={event.published ? "Public" : "Private"}
                              name="published"
                           />
                        </Form.Field>
                     </div>
                     <div>
                        <p>
                           <Icon name="comment outline" />
                           {event.comments.length}
                        </p>
                        <Icon
                           name="close"
                           color="red"
                           onClick={this.toggleModal}
                        />
                     </div>
                  </Card.Content>
                  <DeleteModal
                     modalToggle={this.state.modalToggle}
                     event={event}
                  />
               </Card>
            ))}
         </Grid>
      );
   }
}
<file_sep>/src/components/EditComment.js
import React, { Component } from "react";
import { Mutation } from "react-apollo";
import { gql } from "apollo-boost";
import { Form, Input, Comment } from "semantic-ui-react";
import { toast } from "react-semantic-toasts";
import { COMMENT_QUERY } from "./CommentContainer";

const UPDATE_COMMENT_MUTATION = gql`
   mutation($id: ID!, $text: String) {
      updateComment(id: $id, data: { text: $text }) {
         id
         text
         createdAt
         user {
            id
            username
         }
      }
   }
`;
export default class EditComment extends Component {
   state = {
      text: this.props.comment.text
   };

   _onChange = e => {
      const { name, value } = e.target;
      this.setState(() => ({ [name]: value }));
   };
   handleUpdateComment = async updateComment => {
      if (!!this.state.text) {
         const variables = {
            id: this.props.comment.id,
            text: this.state.text
         };
         this.props.onCancel();
         await updateComment({
            variables
         }).catch(error => {
            toast({
               description: `${error.message}`,
               icon: "warning sign",
               type: "error"
            });
         });
      } else {
         toast({
            description: `Can't update empty message`,
            icon: "info",
            type: "info"
         });
      }
   };
   updateCache = (cache, { data: { updateComment } }) => {
      const { eventId } = this.props;
      const data = cache.readQuery({
         query: COMMENT_QUERY,
         variables: { eventId }
      });
      data.getComment = data.getComment.map(comment => {
         if (comment.id === updateComment.id) {
            return {
               ...comment,
               ...updateComment
            };
         }
         return comment;
      });
      cache.writeQuery({
         query: COMMENT_QUERY,
         variables: { eventId },
         data
      });
   };
   render() {
      const { onCancel } = this.props;

      return (
         <Mutation
            mutation={UPDATE_COMMENT_MUTATION}
            update={this.updateCache}
            optimisticResponse={{
               __typename: "Mutation",
               updateComment: {
                  __typename: "Comment",
                  id: this.props.comment.id,
                  text: this.state.text,
                  createdAt: this.props.comment.createdAt,
                  user: {
                     __typename: "User",
                     id: this.props.me.id,
                     username: this.props.me.username
                  }
               }
            }}
         >
            {updateComment => (
               <Form onSubmit={() => this.handleUpdateComment(updateComment)}>
                  <Input
                     fluid
                     type="text"
                     placeholder="update comment"
                     name="text"
                     value={this.state.text}
                     size="mini"
                     onChange={this._onChange}
                  >
                     <input />
                  </Input>
                  <Comment.Actions>
                     <Comment.Action
                        onClick={() => this.handleUpdateComment(updateComment)}
                     >
                        save
                     </Comment.Action>
                     <Comment.Action onClick={onCancel}>cancel</Comment.Action>
                  </Comment.Actions>
               </Form>
            )}
         </Mutation>
      );
   }
}
<file_sep>/src/components/CommentContainer.js
import React, { Component } from "react";
import { Query } from "react-apollo";
import { gql } from "apollo-boost";
import { Comment, Header } from "semantic-ui-react";
import AddCommentToPhoto from "./AddCommentToPhoto";
import CommentList from "./CommentList";

export const COMMENT_QUERY = gql`
   query($eventId: ID!, $after: String) {
      getComment(
         eventId: $eventId
         orderBy: createdAt_DESC
         first: 18
         after: $after
      ) {
         id
         text
         createdAt
         user {
            id
            username
         }
      }
   }
`;

const COMMENT_SUBSCRIPTION = gql`
   subscription($eventId: ID!) {
      comment(eventId: $eventId) {
         mutation
         node {
            id
            text
            createdAt
            user {
               id
               username
            }
         }
         previousValues {
            id
         }
      }
   }
`;
export default class CommentContainer extends Component {
   render() {
      const { eventId, host } = this.props;
      return (
         <Comment.Group size="mini">
            <Header as="h3" dividing>
               Comments
            </Header>
            <Query query={COMMENT_QUERY} variables={{ eventId }}>
               {({ subscribeToMore, ...result }) => (
                  <CommentList
                     {...result}
                     eventId={eventId}
                     host={host}
                     subscribeToNewComment={() => {
                        subscribeToMore({
                           document: COMMENT_SUBSCRIPTION,
                           variables: { eventId },
                           updateQuery: (prev, { subscriptionData }) => {
                              if (!subscriptionData.data) return prev;
                              const newComment = subscriptionData.data.comment;
                              let idExist = false;
                              if (newComment.mutation === "CREATED") {
                                 idExist =
                                    prev.getComment.filter(
                                       comment =>
                                          comment.id === newComment.node.id
                                    ).length > 0;
                              }

                              if (idExist) return;
                              if (newComment.mutation === "DELETED") {
                                 const id = newComment.previousValues.id;
                                 return {
                                    ...prev,
                                    getComment: prev.getComment.filter(
                                       comment => comment.id !== id
                                    )
                                 };
                              }
                              if (newComment.mutation === "UPDATED") {
                                 return {
                                    ...prev,
                                    getComment: prev.getComment.map(comment => {
                                       if (comment.id === newComment.node.id) {
                                          return {
                                             ...comment,
                                             ...newComment.node
                                          };
                                       }
                                       return comment;
                                    })
                                 };
                              }
                              return {
                                 ...prev,
                                 getComment: [
                                    newComment.node,
                                    ...prev.getComment
                                 ]
                              };
                           }
                        });
                     }}
                  />
               )}
            </Query>

            <AddCommentToPhoto eventId={eventId} />
         </Comment.Group>
      );
   }
}
<file_sep>/src/routes/PrivateRoute.js
import React from "react";
import { Route, Redirect } from "react-router-dom";

import jsonwebtoken from "jsonwebtoken";

export const isAuthenticate = () => {
   const jwt = localStorage.getItem("token");
   if (!jwt) return false;
   const decoded = jsonwebtoken.decode(jwt);
   if (!decoded) return false;
   if (!(Date.now() / 1000 < decoded.exp)) return false;
   return true;
};
const PrivateRoute = ({ component: Component, ...rest }) => {
   return (
      <Route
         {...rest}
         render={props =>
            isAuthenticate() ? (
               <Component {...props} />
            ) : (
               <Redirect to={"/login"} />
            )
         }
      />
   );
};

export default PrivateRoute;
<file_sep>/src/utils/formValidationSchema.js
import * as yup from "yup";

const validationSchema = yup.object().shape({
   username: yup
      .string()
      .min(5, "Username must be at least 5 characters")
      .max(255)
      .required(),
   email: yup
      .string()
      .min(3, "Email must be at least 3 characters")
      .max(255)
      .email("Inavlid email")
      .required(),
   password: yup
      .string()
      .min(5, "Password must be at least 5 characters")
      .max(255)
      .required()
});

export const validateLoginSchema = yup.object().shape({
   email: yup
      .string()
      .min(3, "Email must be at least 3 characters")
      .max(255)
      .email("Inavlid email")
      .required(),
   password: yup
      .string()
      .min(5, "Password must be at least 5 characters")
      .max(255)
      .required()
});
export { validationSchema as default };
<file_sep>/src/components/EventListItem.js
import React, { PureComponent } from "react";
import { Link } from "react-router-dom";
import { Image, Button, Label, Icon } from "semantic-ui-react";

export default class EventListItem extends PureComponent {
   render() {
      const { event } = this.props;

      return (
         <Image.Group>
            <Link className="imageContainer" to={`/photoDetails/${event.id}`}>
               <img
                  src={event.imageURL}
                  style={{
                     width: "400px",
                     height: "400px",
                     objectFit: "cover"
                  }}
                  alt={event.title}
               />
               <div className="imageAction">
                  <Button as="div" labelPosition="right">
                     <Button color="red" icon>
                        <Icon name="heart" />
                     </Button>
                     <Label basic pointing="left">
                        {event.likes.length}
                     </Label>
                  </Button>
                  <Button as="div" labelPosition="right">
                     <Button icon>
                        <Icon name="comment alternate outline" />
                     </Button>
                     <Label basic pointing="left">
                        {event.comments.length}
                     </Label>
                  </Button>
               </div>
            </Link>
         </Image.Group>
      );
   }
}
<file_sep>/src/routes/Routes.js
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import EventDashboard from "../components/EventDashboard";
import Signup from "../components/Signup";
import Login from "../components/Login";
import Nav from "../components/Nav";
import AddPhoto from "../components/AddPhoto";
import PhotoDetails from "../components/PhotoDetails";
import PageNotFound from "../components/PageNotFound";
import { SemanticToastContainer } from "react-semantic-toasts";
import PrivateRoute from "./PrivateRoute";
import PublicRoute from "./PublicRoute";
import ScrollToTop from "../components/ScrollToTop";
import UserProfile from "../components/UserProfile";

const Routes = () => (
   <Router>
      <ScrollToTop>
         <Nav />
         <SemanticToastContainer />
         <Switch>
            <Route exact path="/" component={EventDashboard} />
            <PublicRoute exact path="/signup" component={Signup} />
            <PublicRoute path="/login" component={Login} />
            <PrivateRoute path="/uploadImage" component={AddPhoto} />
            <Route path="/photoDetails/:id" component={PhotoDetails} />
            <PrivateRoute path="/userprofile" component={UserProfile} />
            <Route component={PageNotFound} />
         </Switch>
      </ScrollToTop>
   </Router>
);

export default Routes;
<file_sep>/src/components/PhotoDetails.js
import React, { Component } from "react";
import { Query } from "react-apollo";
import { gql } from "apollo-boost";
import { Container, Card, Image, Grid, Header } from "semantic-ui-react";
import moment from "moment";
import LikeButton from "./LikeButton";
import CommentContainer from "./CommentContainer";
import Loader from "./Loader";
import PageNotFound from "./PageNotFound";
import DisableComment from "./DisableComment";

export const EVENT_QUERY = gql`
   query($id: ID!) {
      event(id: $id) {
         id
         title
         description
         published
         imageURL
         disableComment
         likes {
            id
         }
         createdAt
         host {
            id
            username
         }
      }
   }
`;

export const SINGLE_EVENT_SUBSCRIPTION = gql`
   subscription($id: ID!) {
      singleEvent(id: $id) {
         mutation
         node {
            id
            title
            disableComment
         }
      }
   }
`;

class SubsComponent extends Component {
   render() {
      const { event } = this.props;

      return (
         <>
            {!event.published && (
               <p>This post is private. Only you can see it.</p>
            )}
            <Grid celled="internally" stackable>
               <Grid.Row columns={2}>
                  <Grid.Column
                     style={{
                        display: "flex",
                        alignItems: "center",
                        alignContent: "center",
                        justifyContent: "center"
                     }}
                  >
                     <Image src={event.imageURL} />
                  </Grid.Column>
                  <Grid.Column>
                     <div className="commentContainer">
                        <Card fluid>
                           <Card.Content>
                              <div className="avatar">
                                 <p>
                                    {!!event ? event.host.username[0] : "NA"}
                                 </p>
                              </div>
                              <Card.Header>
                                 {!!event ? event.host.username : "test"}
                              </Card.Header>
                              <Card.Meta>
                                 {!!event
                                    ? moment(event.createdAt).format("LLLL")
                                    : null}
                              </Card.Meta>
                              <Card.Description>
                                 {!!event ? event.title : null}
                              </Card.Description>
                           </Card.Content>
                           <Card.Content extra>
                              <div
                                 style={{
                                    backround: "red",
                                    display: "flex",
                                    justifyContent: "space-between"
                                 }}
                              >
                                 <LikeButton id={event.id} event={event} />
                                 <DisableComment event={event} />
                              </div>
                           </Card.Content>
                        </Card>
                        {event.disableComment ? (
                           <Header as="h4">
                              Comments are disabled for this post
                           </Header>
                        ) : (
                           <CommentContainer
                              eventId={!!event ? event.id : null}
                              host={event.host}
                           />
                        )}
                     </div>
                  </Grid.Column>
               </Grid.Row>
            </Grid>
         </>
      );
   }
}

export default class PhotoDetails extends Component {
   render() {
      const { id } = this.props.match.params;
      return (
         <Container>
            <Query query={EVENT_QUERY} variables={{ id }}>
               {({ data, loading, error }) => {
                  if (loading) return <Loader />;
                  if (error) return <PageNotFound />;

                  return <SubsComponent event={!!data ? data.event : null} />;
               }}
            </Query>
         </Container>
      );
   }
}
<file_sep>/src/components/EventList.js
import React, { Component } from "react";

import { Grid } from "semantic-ui-react";
import InfiniteScroll from "react-infinite-scroller";
import EventListItem from "./EventListItem";
import Loader from "./Loader";
import uuid from "uuid/v4";

export default class EventList extends Component {
   state = {
      hasMoreItems: true
   };
   componentDidMount() {
      this.unsubscribe = this.props.subscribeToNewEvent();
   }
   componentWillUnmount() {
      this.unsubscribe();
   }
   handleScroll = () => {
      if (this.scroller) {
         console.log(this.scroller);
      }
   };

   render() {
      const { data, loading, error, fetchMore } = this.props;
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error</p>;
      if (data.length === 0) return <p>No pictures found currently</p>;
      return (
         <div>
            {!!data && data.events.length > 0 && (
               <InfiniteScroll
                  key={uuid()}
                  pageStart={0}
                  loadMore={() =>
                     fetchMore({
                        variables: {
                           after: data.events[data.events.length - 1].id
                        },
                        updateQuery: (prev, { fetchMoreResult }) => {
                           if (!fetchMoreResult) return prev;

                           if (fetchMoreResult.events.length < 2) {
                              this.setState({ hasMoreItems: false });
                           }
                           return {
                              ...prev,
                              events: [
                                 ...prev.events,
                                 ...fetchMoreResult.events
                              ]
                           };
                        }
                     })
                  }
                  hasMore={!loading && this.state.hasMoreItems}
                  initialLoad={false}
                  loader={
                     loading &&
                     this.state.hasMoreItems && <Loader key={uuid()} />
                  }
               >
                  <Grid>
                     {data.events.map(event => (
                        <EventListItem key={event.id} event={event} />
                     ))}
                  </Grid>
               </InfiniteScroll>
            )}
         </div>
      );
   }
}

// OFF SET BASED PAGINATION

// export default class EventList extends Component {
//    state = {
//       hasMoreItems: true
//    };
//    componentDidMount() {
//       this.querySubscription = this.props.subscribeToNewEvent();
//    }

//    render() {
//       const { data, loading, error, fetchMore } = this.props;
//       if (loading) return <p>Loading...</p>;
//       if (error) return <p>Error</p>;
//       if (data.length === 0) return <p>No pictures found currently</p>;
//       return (
//          <>
//             {this.state.hasMoreItems && (
//                <Button
//                   onClick={() =>
//                      fetchMore({
//                         variables: {
//                            skip: data.events.length
//                         },
//                         updateQuery: (prev, { fetchMoreResult }) => {
//                            if (!fetchMoreResult) return prev;
//                            if (fetchMoreResult.events.length < 3) {
//                               this.setState({ hasMoreItems: false });
//                            }
//                            return {
//                               ...prev,
//                               events: [
//                                  ...fetchMoreResult.events,
//                                  ...prev.events
//                               ]
//                            };
//                         }
//                      })
//                   }
//                >
//                   Load More
//                </Button>
//             )}
//             <Grid>
//                {data.events.map(event => (
//                   <EventListItem key={event.id} event={event} />
//                ))}
//             </Grid>
//          </>
//       );
//    }
// }
<file_sep>/src/components/UserProfile.js
import React, { Component } from "react";
import { Query } from "react-apollo";
import { gql } from "apollo-boost";
import UserPostsList from "./UserPostsList";

export const USER_POST_QUERY = gql`
   query {
      userPosts(orderBy: createdAt_DESC) {
         id
         title
         imageURL
         imageURL_ID
         likes {
            id
         }
         description
         published
         disableComment
         createdAt
         updatedAt
         comments {
            id
         }
      }
   }
`;

export const USER_POSTS_SUBSCRIPTION = gql`
   subscription {
      userPost {
         mutation
         node {
            id
            title
            imageURL
            imageURL_ID
            likes {
               id
            }
            description
            published
            disableComment
            createdAt
            updatedAt
            comments {
               id
            }
         }
         previousValues {
            id
         }
      }
   }
`;

class UserProfile extends Component {
   render() {
      return (
         <Query query={USER_POST_QUERY}>
            {({ subscribeToMore, ...result }) => {
               if (result.error)
                  return <p> {result.error.message}. Try re login again </p>;
               return (
                  <UserPostsList
                     {...result}
                     subscribeToUserPosts={() =>
                        subscribeToMore({
                           document: USER_POSTS_SUBSCRIPTION,
                           updateQuery: (prev, { subscriptionData }) => {
                              if (!subscriptionData.data) return prev;
                              const newUserPost =
                                 subscriptionData.data.userPost;
                              if (newUserPost.mutation === "UPDATED") {
                                 return {
                                    ...prev,
                                    userPosts: prev.userPosts.map(post => {
                                       if (post.id === newUserPost.node.id) {
                                          return {
                                             ...post,
                                             ...newUserPost.node
                                          };
                                       }
                                       return post;
                                    })
                                 };
                              } else if (newUserPost.mutation === "DELETED") {
                                 const id = newUserPost.previousValues.id;
                                 return {
                                    ...prev,
                                    userPosts: prev.userPosts.filter(
                                       post => post.id !== id
                                    )
                                 };
                              }
                              return {
                                 ...prev,
                                 userPosts: [
                                    newUserPost.node,
                                    ...prev.userPosts
                                 ]
                              };
                           }
                        })
                     }
                  />
               );
            }}
         </Query>
      );
   }
}
export default UserProfile;
