Skip to content

AlexBetita/product-hunt-clone

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Welcome to ProductHuntClone

Live link: ProductHuntClone

Producthuntclone is my best attempt at cloning producthunt's overall design and functionalites, it's a web application that allow uers to post products that they wanna pitch, made or hunt for. As an annonymous user you can only view all products posted, disucssions and comments. As a registered user you can posts new products, create discussions, create comments, upvote products or discussions, follow users, and search for users, products and discussions.

Table of Contents
  1. Getting Started
  2. Technologies Used
  3. Key Featues
  4. Code Snippets
  5. Wiki
  1. Clone this repository
  2. Install dependencies npm install
  3. Create a .env file based on the .env.example
  4. Set up your PostgreSQL user and password.
  5. Make sure to create the db npx dotenv sequelize-cli db:create
  6. Migrate the models npx dotenv sequelize-cli db:migrate
  7. Populate the data with seeders found in "backend/db/seeders" npx dotenv sequelize-cli db:seed:all
  8. Now run the application npm start
javaScript html css express git react redux redux
  • Non users can view profiles, products, discussions.
  • Users can view, edit, create, delete products.
  • Users can edit, view profile and other users profile.
  • Users can post and view discussions.
  • Users can upvote products.

--not yet implemented--

  • comments,
  • searchbar(current implementation is anyone can look up a user and visit their profile through url https://producthuntclone.herokuapp.com/@[USER_NAME]) or click a users icon
  • edit and delete discussions

Implementation of infinite scroll for products with the use of an external library lodash.

Why lodash? It provides a fully implemented throttler function, what is a throttler function? Basically it throttles multiple function calls and limits it to the most latest call, reason for it being needed in infinite scroll is when you scroll an event fires and for this particular use case as soon as you reach bottom of the page without a throttler multiple scroll events fire even when you have logic that supports current scroll coordinate == bottom document height.

  const products = useSelector((state)=>{
    return state.products.list.map((productId) => state.products[productId])
  })

  let pageCounter = 1
  const throttler = _.throttle(scroll, 500)

  let sortedProducts = {}
  
  Object.keys(products).map((key) =>{
    let str = products[key].createdAt
    str = str.substring(0, str.length - 4);
    if(!sortedProducts[str]){
      sortedProducts[str] = []
      return sortedProducts[str].push(products[key])
    } else {
      return sortedProducts[str].push(products[key])
    }
  })

  useEffect(()=>{
    if(window.addEventListener){
      window.addEventListener('scroll', throttler, true);
      window.addEventListener('scroll', scrollToTopChecker);
    }
    return function cleanup(){
      window.removeEventListener('scroll', throttler, true);
      window.removeEventListener('scroll', scrollToTopChecker);
    }

  }, [throttler])

  const setNextPage = async () => {
    await dispatch(getProducts(pageCounter + 1))
    pageCounter += 1
  }

  function scroll(){
    const pixelFromTopToBottom = Math.max(document.documentElement.scrollTop,document.body.scrollTop);
    if((pixelFromTopToBottom+document.documentElement.clientHeight) >= document.documentElement.scrollHeight ){
      setNextPage()
    }
  }

Implementation of the upvotes feature for products

This block of code checks the current users upvotes and applies a class voted__true to the corresponding element. If there is no current user logged on then no change happens on the element and the first line of code makes it so that non logged in users, can't upvote

const [disableVote, setDisableVote] = useState(false)
let upvoted = useSelector((state)=>{
    if(state.session.user){
      user = state.session.user
      for (const [key, value] of Object.entries(state.session.upvotes)){
        if(key){
          if(products.id === value.id){
            return true
          }
        }
      }
    }
  })

  useEffect(() => {
    if(upvoted){
      upvoteElementRef.current.classList.add('voted__true')
    }
  },[user, upvoted])

This block of code makes it so users can't spam the upvote button in quick succession, only allowing them to click the button after the dispatch finishes, it also makes an animation when an upvote occurs through the use of that setTimeout.

const vote = async () =>{
    if(user){
      if (upvoteElementRef.current.classList.contains('voted__true')){

        setDisableVote(true)
        await upvoteElementRef.current.classList.remove('voted__true')
        await dispatch(voteProduct(products.id))
        setUpvotes(getUpvotes - 1)
        setDisableVote(false)
      } else {
        setDisableVote(true)
        await triangleRef.current.classList.add('hidden')

        await circleRef.current.classList.remove('hidden')
        await circleRef.current.classList.add('scale')

        setTimeout(()=>{
          triangleRef.current.classList.remove('hidden')
          circleRef.current.classList.remove('scale')
          circleRef.current.classList.add('hidden')
        }, 200)
        await upvoteElementRef.current.classList.add('voted__true')

        await dispatch(voteProduct(products.id))
        setUpvotes(getUpvotes + 1)
        setDisableVote(false)
      }
    }
  }

Database Schema

image

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages