/Doodler

ONE WEEK CHALLENGE (TEAM): A social media platform for doodlers. Draw your own doodles share them with others!! (MERN)

Primary LanguageJavaScript

Doodler ReadMe

Team members

Deployed app here Doodler

Create an account and login to see the full features!

Project Overview - 7 days

*Note - no extra work has been done to this apart from deployment. This is to give an accurate representation of what can be accomplished in a set time frame.

Doodler is a social site for users to create and share their doodles.

Screenshot 2021-05-04 at 15 51 55

Initialisation

  • Make sure mongoDB is running mongod --dbpath ~/data/db
  • Server directory cd backend
  • yarn
  • yarn seed
  • yarn serve
  • Front end cd client
  • Install front-end dependencies: yarn
  • Start front-end server: yarn start

Project brief

  • Build a full-stack application by making your own backend and your own front-end
  • Use a MongoDB and Node.js API using express framework to serve your data from a MongoDB database
  • Consume your API with a separate front-end built with React
  • Be a complete product
  • Be deployed online so it’s publicly accessible

Technologies Used

  • MongoDB/Mongoose
  • Express
  • React.js (hooks)
  • Node.js
  • SCSS
  • Bulma
  • Axios
  • Nodemon
  • HTTP-proxy-middleware
  • Bcrypt
  • Body-parser
  • jsonwebtoken
  • Git/GitHub

Approach Taken

While discussing ideas we found a canvas component that allows users to make a doodle. We knew we wanted this as the focus of our app and got to work.

We met and had some wireframes drawn up of how we want the app to look like.

Screenshot 2021-05-04 at 15 02 14

Screenshot 2021-05-04 at 15 02 36

Screenshot 2021-05-04 at 15 02 51

We used Trello to track our progress and raise issues.

Screenshot 2021-05-04 at 15 05 33

Backend development

The doodles are referred as artwork in our schematics. They contain both embedded and reference relationships.

Schemas

const commentSchema = new mongoose.Schema({
  commentText: { type: String, required: false }, //! Sami change to false for star rating 
  rating: { type: Number, required: false, min: 1, max: 5 },
  owner: { type: mongoose.Schema.ObjectId, ref: 'User', required: false },
  username: { type: String , required: false }
},
{ timestamps: true
})

const favouriteSchema = new mongoose.Schema({
  owner: { type: mongoose.Schema.ObjectId, ref: 'User', required: false }
},
{ timestamps: true }
)

const artworkSchema = new mongoose.Schema({
  title: { type: String, required: true, maxlength: 60 },
  description: { type: String, required: false, maxlength: 500 },
  doodleData: { type: String, required: true },
  owner: { type: mongoose.Schema.ObjectId, ref: 'User', required: true  },
  comments: [commentSchema],
  favourites: [ favouriteSchema ]
},
{ timestamps: true }
)

Controllers

Example of controller for liking / adding to favourites.

export const addLike = async (req, res) => {
// const currentUser = req.currentUser._id
  try {
    const { id } = req.params
    const artworkToLike = await Artwork.findById(id)
    if (!artworkToLike) {
      throw new Error('πŸŸ₯ no artwork found to like πŸŸ₯ ')
    }
    const newLike = { owner: req.currentUser }
    const favouritesArray = artworkToLike.favourites 
    const currentUserLoggedInId = JSON.stringify(req.currentUser._id)
    const hasUserLikedBefore = await favouritesArray.find(item => JSON.stringify(item.owner._id) === currentUserLoggedInId) 
    
    console.log('🐝 ~ file: artworkController.js ~ line 107 ~ hasUserLikedBefore', hasUserLikedBefore)
    if (hasUserLikedBefore) {
      const likeToRemove = await artworkToLike.favourites.id(hasUserLikedBefore._id)
      await likeToRemove.remove()
      await	artworkToLike.save()
      res.status(200).json( { message: 'Dis-liked!' })
    } else if (!hasUserLikedBefore) {
      artworkToLike.favourites.push(newLike)
      await	artworkToLike.save()
      res.status(200).json( { message: 'liked!' })
    }
  } catch (err) {
    console.log('🐝 ~ file: artworkController.js ~ line 128 ~ error', err)
    res.status(500).json( { message: err.message })
  }
}

!Screenshot 2021-05-06 at 19 22 59

Like feauture

The like button was something I focused on, front and back-end. This will check if the user has liked previously and dislike if so, or add a like if not.

export const addLike = async (req, res) => {
// const currentUser = req.currentUser._id
  try {
    const { id } = req.params
    const artworkToLike = await Artwork.findById(id)
    if (!artworkToLike) {
      throw new Error('πŸŸ₯ no artwork found to like πŸŸ₯ ')
    }
    const newLike = { owner: req.currentUser }
    const favouritesArray = artworkToLike.favourites 
    const currentUserLoggedInId = JSON.stringify(req.currentUser._id)
    const hasUserLikedBefore = await favouritesArray.find(item => JSON.stringify(item.owner._id) === currentUserLoggedInId) 
    
    console.log('🐝 ~ file: artworkController.js ~ line 107 ~ hasUserLikedBefore', hasUserLikedBefore)
    if (hasUserLikedBefore) {
      const likeToRemove = await artworkToLike.favourites.id(hasUserLikedBefore._id)
      await likeToRemove.remove()
      await	artworkToLike.save()
      res.status(200).json( { message: 'Dis-liked!' })
    } else if (!hasUserLikedBefore) {
      artworkToLike.favourites.push(newLike)
      await	artworkToLike.save()
      res.status(200).json( { message: 'liked!' })
    }
  } catch (err) {
    console.log('🐝 ~ file: artworkController.js ~ line 128 ~ error', err)
    res.status(500).json( { message: err.message })
  }
}

Front end

My focus was on the gallery page, user authentication and the like and comment features.

My Highlights

Comments

The comment feed component uses the doodle ID to get all its comments and formats the timestamp to display a comment feed. Using an interval this refreshes every five seconds.

const CommentFeed = ({ _id }) => {
  const [commentsArray, setCommentsArray] = useState([])
  useEffect(() => {
    getComments()
    const interval = setInterval(getComments, 5000)
    return () => {
      clearInterval(interval)
    }
  }, [])

  const getComments = async () =>{ 
    const response = await axios.get(`/api/artwork/${_id}/getComments`) 
    const newCommentsArray = response.data
    setCommentsArray(newCommentsArray)
  }
  const formattedTimestamp = (timestamp) =>{
    const date = new Date(timestamp)
    const toString = date.toString()
    const dateSlice = toString.slice(4,10)
    const timeSlice = toString.slice(15,21)
    return `${dateSlice} at ${timeSlice}`
  }

To add comments - this will also take a user rating, between 1-5 and add this to the doodle.

const CommentForm = ({ _id }) => {
  const [userComment, setUserComment] = useState({
    commentText: '',
    rating: 1
  })
  const [userRating, setUserRating] = useState(null)
  
  const [isThereComment, setIsThereComment] = useState(null)
  const [isThereRating, setIsThereRating] = useState(null)
  console.log('🐝 ~ file: CommentForm.js ~ line 25 ~ isThereRating', isThereRating)



  const handleCommentChange = (event) => {
    //?get the value of what's being typed in the form and updating state
    const newUserComment = { ...userComment, [event.target.name]: event.target.value }
    // console.log('🐝 ~ file: Login.js ~ line 25 ~ event', event)
    setUserComment(newUserComment)
  }

  const handleRatingChange = (event) => {
    console.log('🐝 ~ file: CommentForm.js ~ line 65 ~ event', event)
    setIsThereRating(true)
    setUserRating(event)
  }


  const handleCommentPost = async(event) => {
    event.preventDefault()
    const isThereComment = !!userComment.commentText
    if (!userIsAuthenticated()) {
      userNeedsToLogin('Please login to review and comment!☺️')
    }
    if (!isThereComment) {
      setIsThereComment(false)
      console.log('NO COMMENT')
      commentPopup(0)
      return 0
    } if (!isThereRating) {
      ratingPopup(false)
      return 0
    }
    try {
      const commentToAdd = { ...userComment, rating: userRating }
      await axios.post(`/api/${_id}/comment`, commentToAdd, { headers: { Authorization: `Bearer ${getTokenFromLocalStorage()}` } } )
      console.log('🐝 ~ file: CommentForm.js ~ line 23 ~ commentToAdd', commentToAdd)
      commentPopup(true)
      setUserComment({ commentText: '' })
    } catch (err) {
      console.log('πŸ”΄ ~ file: CommentForm.js ~ line 24 ~ err', err)
    }
  }

Screenshot 2021-05-24 at 16 18 13

I really Like how the gallery page turned out with the cards and getting the filter feature to work. Screenshot 2021-05-04 at 15 48 33

Wins and Challenges

Challenges 😧

  • Managing and splitting work proved challenging initially!
  • Merging and solving conflicts was something we had to learn on the fly and practice.

Wins πŸ†

  • Working as a team to create a really cool interactive app!
  • happy to get a working like button which updates correctly

Key Learnings

  • Using GitHub with others on a project.
  • Splitting workload with team.
  • Using SVGs.

Final product walkthrough

Screenshot 2021-05-24 at 16 49 13 Screenshot 2021-05-24 at 16 50 28 Screenshot 2021-05-24 at 16 50 51 Screenshot 2021-05-24 at 16 51 15 Screenshot 2021-05-24 at 16 51 45