Project Hoojah
Responsibilities Research, design and build
Technology Stack HTML, SCSS, Adobe XD, Ruby on Rails, ReactJS, Discourse, Docker
Year 2013-2019
Github Repo View in Github
YouTube Videos Pitch: Watch video
Tutorial (EN): Watch video
Tutorial (BM): Watch video
Behance View in Behance

The Hoojah project has… wordpress, html css

The final iteration of Hoojah uses ReactJS on the frontend and Ruby on Rails on the backend.

ReactJS because…

Here I will explain my solution for building a Hujah card component.

Hujah Card

``` javascript // card.js

import React from ‘react’ import { Link } from ‘react-router-dom’ import Linkify from ‘react-linkify’ import AgreeIcon from ‘../Icons/agree’ import NeutralIcon from ‘../Icons/neutral’ import DisagreeIcon from ‘../Icons/disagree’ import ViewsIcon from ‘../Icons/views’ import VotesIcon from ‘../Icons/votes’ import HujahIcon from ‘../Icons/hujah’ import HujahCardHeader from ‘./card_header’ import ButtonAddHujah from ‘../Layouts/button_add_hujah’

class HujahCard extends React.Component { constructor(props) { super(props)

const { hujah } = this.props
const { agree_count, neutral_count, disagree_count } = hujah.attributes

this.state = {
  hujah: hujah,
  hujahParentAvailable: hujah.attributes.hasOwnProperty("parent"),
  showAddHujahButton: hujah.attributes.current_user_vote !== null,
  totalVoteCount: agree_count + neutral_count + disagree_count
}   }

updateVote(vote) { const url = “/api/v1/votes/create”

const body = {
  vote: vote,
  hujah_id: this.state.hujah.id,
  user_id: this.props.currentUser.id
}

const token = document.querySelector('meta[name="csrf-token"]').content

fetch(url, {
  method: "POST",
  headers: {
    "X-CSRF-Token": token,
    "Content-Type": "application/json"
  },
  body: JSON.stringify(body)
})
  .then(response => {
    if (response.ok) {
      return response.json()
    }
    throw new Error("Network response was not ok.")
  })
  .catch(error => console.log(error.message))   }

calculatePercentage(voteCount) { const percentage = voteCount * 100 / this.state.totalVoteCount return ${percentage}% }

handleVoteAgree() { if(this.userNotLoggedIn()) { return this.redirectToLogin() }

const newHujahState = Object.assign({}, this.state.hujah)

var addToTotalVoteCount = 0

if(newHujahState.attributes.current_user_vote == "agree") {
  return
} else {
  newHujahState.attributes.agree_count = newHujahState.attributes.agree_count + 1
  if(newHujahState.attributes.current_user_vote == "neutral") {
    newHujahState.attributes.neutral_count = newHujahState.attributes.neutral_count - 1
  } else if(newHujahState.attributes.current_user_vote == "disagree") {
    newHujahState.attributes.disagree_count = newHujahState.attributes.disagree_count - 1
  }
  if(newHujahState.attributes.current_user_vote == null) {
    addToTotalVoteCount = 1
  }
  newHujahState.attributes.current_user_vote = "agree"
}

this.setState((prevState, props) => {
  return {
    hujah: newHujahState,
    totalVoteCount: prevState.totalVoteCount + addToTotalVoteCount,
    showAddHujahButton: true
  }
})

this.updateVote(1)   }

handleVoteNeutral() { if(this.userNotLoggedIn()) { return this.redirectToLogin() }

const newHujahState = Object.assign({}, this.state.hujah)

var addToTotalVoteCount = 0

if(newHujahState.attributes.current_user_vote == "neutral") {
  return
} else {
  newHujahState.attributes.neutral_count = newHujahState.attributes.neutral_count + 1
  if(newHujahState.attributes.current_user_vote == "agree") {
    newHujahState.attributes.agree_count = newHujahState.attributes.agree_count - 1
  } else if(newHujahState.attributes.current_user_vote == "disagree") {
    newHujahState.attributes.disagree_count = newHujahState.attributes.disagree_count - 1
  }
  if(newHujahState.attributes.current_user_vote == null) {
    addToTotalVoteCount = 1
  }
  newHujahState.attributes.current_user_vote = "neutral"
}

this.setState((prevState, props) => {
  return {
    hujah: newHujahState,
    totalVoteCount: prevState.totalVoteCount + addToTotalVoteCount,
    showAddHujahButton: true
  }
})

this.updateVote(2)   }

handleVoteDisagree() { if(this.userNotLoggedIn()) { return this.redirectToLogin() }

const newHujahState = Object.assign({}, this.state.hujah)

var addToTotalVoteCount = 0

if(newHujahState.attributes.current_user_vote == "disagree") {
  return
} else {
  newHujahState.attributes.disagree_count = newHujahState.attributes.disagree_count + 1
  if(newHujahState.attributes.current_user_vote == "agree") {
    newHujahState.attributes.agree_count = newHujahState.attributes.agree_count - 1
  } else if(newHujahState.attributes.current_user_vote == "neutral") {
    newHujahState.attributes.neutral_count = newHujahState.attributes.neutral_count - 1
  }
  if(newHujahState.attributes.current_user_vote == null) {
    addToTotalVoteCount = 1
  }
  newHujahState.attributes.current_user_vote = "disagree"
}

this.setState((prevState, props) => {
  return {
    hujah: newHujahState,
    totalVoteCount: prevState.totalVoteCount + addToTotalVoteCount,
    showAddHujahButton: true
  }
})

this.updateVote(3)   }

userNotLoggedIn() { return !this.props.loggedInStatus }

redirectToLogin() { this.props.history.push(‘/start/login’) }

render() { // || this.state.hujah.attributes.children_count == 1 if(this.state.hujahParentAvailable) { return null }

const { hujah, hujahParentAvailable, showAddHujahButton, totalVoteCount } = this.state
const { agree_count, neutral_count, disagree_count, body, current_user_vote, children_count, user, slug } = hujah.attributes

const showVoteBar = (
  <div className="card-body p-0">
    <div className="d-flex justify-content-around vote-bar">
      <div className="vote bg-agree" style={{ width: this.calculatePercentage(agree_count) }}></div>
      <div className="vote bg-neutral" style={{ width: this.calculatePercentage(neutral_count) }}></div>
      <div className="vote bg-disagree" style={{ width: this.calculatePercentage(disagree_count) }}></div>
    </div>
  </div>
)

return(
    <div className="shadow card border-0 rounded-0 mb-2">
      <HujahCardHeader hujah={hujah} hujahParentAvailable={hujahParentAvailable} />
      <div className="card-body pb-0">
        <Link to={`/hoojah/${slug}`}>
          <h5 className="card-title text-black text-regular" dangerouslySetInnerHTML={{ __html: body }}></h5>
        </Link>
      </div>
      <div className={`card-body pt-0 ${showAddHujahButton ? "d-flex justify-content-between" : null}`}>
        <div className={`d-flex justify-content-${showAddHujahButton ? "between" : "around"}`}>
          <button className={`shadow btn btn-outline-agree btn-lg btn-circle btn-icon-16 fill-agree ${current_user_vote == "agree" ? "voted" : null} ${showAddHujahButton ? "mr-2" : null}`} onClick={() => this.handleVoteAgree()}><AgreeIcon /></button>
          <button className={`shadow btn btn-outline-neutral btn-lg btn-circle btn-icon-16 fill-neutral neutral ${current_user_vote == "neutral" ? "voted" : null} ${showAddHujahButton ? "mr-2" : null}`} onClick={() => this.handleVoteNeutral()}><NeutralIcon /></button>
          <button className={`shadow btn btn-outline-disagree btn-lg btn-circle btn-icon-16 fill-disagree ${current_user_vote == "disagree" ? "voted" : null}`} onClick={() => this.handleVoteDisagree()}><DisagreeIcon /></button>
        </div>
        {showAddHujahButton ? <ButtonAddHujah hujahParent={hujah} user={user} vote={current_user_vote} /> : null}
      </div>
      {totalVoteCount > 0 ? showVoteBar : null}
      <div className="card-footer d-flex justify-content-between text-grey">
        <div className="d-flex align-items-center">
          <Link to={`/hoojah/${hujah.slug}`} className="text-14 btn-icon-14 fill-grey no-underscore text-grey">
            <VotesIcon />
            <span className="ml-1">{totalVoteCount}</span>
            <span className="mx-2">·</span>
            <HujahIcon />
            <span className="ml-1">{children_count}</span>
          </Link>
        </div>
      </div>
    </div>
)   } }

export default HujahCard ```