Last active
April 5, 2024 13:10
-
-
Save rudzainy/debc9e64270b56150985ba4678b13bd1 to your computer and use it in GitHub Desktop.
Hujah Card
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Fragment } from 'react' | |
import { Link } from 'react-router-dom' | |
import ShareIcon from '../Icons/share' | |
import HujahCardParent from './card_parent' | |
import { | |
EmailShareButton, | |
EmailIcon, | |
FacebookShareButton, | |
FacebookIcon, | |
RedditShareButton, | |
RedditIcon, | |
TelegramShareButton, | |
TelegramIcon, | |
TwitterShareButton, | |
TwitterIcon, | |
WhatsappShareButton, | |
WhatsappIcon | |
} from "react-share" | |
class HujahCardHeader extends React.Component { | |
render() { | |
const { hujah, hujahParentAvailable } = this.props | |
const { parent, user, body } = hujah.attributes | |
const { full_name, username, photo } = user.attributes | |
const messageForSocialMedia = `"${body} (by @${username})" Do you AGREE? NEUTRAL? DISAGREE?` | |
const socialMediaButtonUrl = `${window.location.origin}/hoojah/${hujah.attributes.slug}` | |
const socialMediaButtonClass = "px-3 py-1" | |
return( | |
<Fragment> | |
{ hujahParentAvailable ? <HujahCardParent hujah={parent} /> : null } | |
<div className="card-header border-bottom-0 pb-0 d-flex justify-content-between align-items-center"> | |
<div className="media"> | |
<Link to={`/${username}`}><img src={photo} className="rounded-circle mr-3 avatar" /></Link> | |
<div className="media-body"> | |
<div className="d-flex flex-column"> | |
<Link to={`/${username}`} className="mt-0 mb-0 text-primary">{full_name}</Link> | |
<a className="no-underscore handle"> | |
<small className="text-muted">{`@${username}`}</small> | |
</a> | |
</div> | |
</div> | |
</div> | |
<div className="dropdown"> | |
<button className="btn btn-icon-16 fill-light-grey p-0" type="button" id="moreAction" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | |
<ShareIcon /> | |
</button> | |
<div className="dropdown-menu dropdown-menu-right" aria-labelledby="moreAction"> | |
<h6 className="dropdown-header">Share this hoojah via</h6> | |
<FacebookShareButton | |
url={socialMediaButtonUrl} | |
quote={messageForSocialMedia} | |
hashtag="#hoojah" | |
className={socialMediaButtonClass}> | |
<FacebookIcon size={24} borderRadius={48} className="mr-1" /> Facebook | |
</FacebookShareButton> | |
<TwitterShareButton | |
url={socialMediaButtonUrl} | |
title={messageForSocialMedia} | |
via="hoojah_my" | |
hashtags={["hoojah", "discussions", "malaysia"]} | |
className={socialMediaButtonClass}> | |
<TwitterIcon size={24} borderRadius={48} className="mr-1" /> Twitter | |
</TwitterShareButton> | |
<WhatsappShareButton | |
url={socialMediaButtonUrl} | |
title={messageForSocialMedia} | |
className={socialMediaButtonClass}> | |
<WhatsappIcon size={24} borderRadius={48} className="mr-1" /> WhatsApp | |
</WhatsappShareButton> | |
<TelegramShareButton | |
url={socialMediaButtonUrl} | |
title={messageForSocialMedia} | |
className={socialMediaButtonClass}> | |
<TelegramIcon size={24} borderRadius={48} className="mr-1" /> Telegram | |
</TelegramShareButton> | |
<RedditShareButton | |
url={socialMediaButtonUrl} | |
title={messageForSocialMedia} | |
className={socialMediaButtonClass}> | |
<RedditIcon size={24} borderRadius={48} className="mr-1" /> Reddit | |
</RedditShareButton> | |
<EmailShareButton | |
url={socialMediaButtonUrl} | |
subject={messageForSocialMedia} | |
body="[email protected]" | |
className={socialMediaButtonClass}> | |
<EmailIcon size={24} borderRadius={48} className="mr-1" /> Email | |
</EmailShareButton> | |
</div> | |
</div> | |
</div> | |
</Fragment> | |
) | |
} | |
} | |
export default HujahCardHeader |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { Link } from 'react-router-dom' | |
class HujahParentCard extends React.Component { | |
render() { | |
const hujah = this.props.hujah | |
const { user, body, slug } = hujah.attributes | |
const { full_name, photo } = user.attributes | |
return( | |
<Link to={`/hoojah/${slug}`} className="no-underscore"> | |
<div id="parent-card-container" className="shadow card-body border-bottom border-light-grey px-3 py-1 d-flex align-items-center mb-0"> | |
<div className="media pt-1"> | |
<img src={photo} className="rounded-circle mr-2 avatar-small" /> | |
<div className="media-body"> | |
<div className="d-flex flex-column"> | |
<small className="text-grey"> | |
Response to <span className="text-primary">{full_name}</span>'s hoojah | |
</small> | |
<div className="mb-0 text-grey text-14 text-truncate d-block d-sm-none" style={{ maxWidth: '270px' }} dangerouslySetInnerHTML={{ __html: body }}></div> | |
<div className="mb-0 text-grey text-14 text-truncate d-none d-sm-block d-md-none" style={{ maxWidth: '310px' }} dangerouslySetInnerHTML={{ __html: body }}></div> | |
<div className="mb-0 text-grey text-14 text-truncate d-none d-md-block" style={{ maxWidth: '350px' }} dangerouslySetInnerHTML={{ __html: body }}></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</Link> | |
) | |
} | |
} | |
export default HujahParentCard |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react" | |
import { Link } from 'react-router-dom' | |
import AgreeIcon from '../Icons/agree' | |
import NeutralIcon from '../Icons/neutral' | |
import DisagreeIcon from '../Icons/disagree' | |
class HujahCardSmall extends React.Component { | |
parseVote(vote) { | |
if(vote == 1) { | |
return "agree" | |
} else if(vote == 2) { | |
return "neutral" | |
} else if(vote == 3) { | |
return "disagree" | |
} else { | |
return "primary" | |
} | |
} | |
render() { | |
const hujah = this.props.hujah | |
const { body, vote, agree_count, neutral_count, disagree_count, user, slug } = hujah.attributes | |
const { full_name, username, photo } = user.attributes | |
return( | |
<Link to={`/hoojah/${slug}`} className="no-underscore"> | |
<div className={`shadow card-body border-left-8 border-${this.parseVote(vote)} border-bottom-0 py-0 pl-0 pr-2 d-flex align-items-center mb-1`}> | |
<div className="media py-1"> | |
<img src={photo} className="rounded-circle mx-2 avatar-small" /> | |
<div className="media-body"> | |
<div className="d-flex flex-column"> | |
<div> | |
<span className="my-0 mr-1 text-primary">{full_name}</span> | |
<span className="handle"> | |
<small className="text-muted">{`@${username}`}</small> | |
</span> | |
</div> | |
<p className="mb-0 text-black" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: body }}></p> | |
<div className="d-flex align-items-center text-14 card-body btn-icon-14 text-light-grey fill-light-grey p-0"> | |
<AgreeIcon /> | |
<span className="ml-1">{agree_count}</span> | |
<span className="mx-2">·</span> | |
<NeutralIcon /> | |
<span className="ml-1">{neutral_count}</span> | |
<span className="mx-2">·</span> | |
<DisagreeIcon /> | |
<span className="ml-1">{disagree_count}</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</Link> | |
) | |
} | |
} | |
export default HujahCardSmall |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Hujah < ApplicationRecord | |
belongs_to :user | |
has_many :votes, dependent: :destroy | |
has_many :flags, dependent: :destroy | |
has_many :children, class_name: "Hujah", foreign_key: "parent_id", dependent: :destroy | |
belongs_to :parent, class_name: "Hujah", optional: true | |
validates :body, presence: true | |
slug :set_slug | |
def is_parent? | |
self.parent == nil | |
end | |
def has_parent? | |
self.parent != nil | |
end | |
def has_children? | |
self.children != 0 | |
end | |
def set_slug | |
re = /<("[^"]*"|'[^']*'|[^'">])*>/ | |
self.slug = self.body.gsub(re, '').parameterize | |
end | |
def current_user_vote(logged_in: nil, current_user_id: nil) | |
if logged_in | |
if votes.joins(:user).find_by(user_id: current_user_id).nil? | |
nil | |
else | |
vote = votes.joins(:user).find_by(user_id: current_user_id).vote.last | |
if vote == 1 | |
"agree" | |
elsif vote == 2 | |
"neutral" | |
elsif vote == 3 | |
"disagree" | |
end | |
end | |
else | |
nil | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Api::V1::HujahsController < ApplicationController | |
def index | |
hujahs = Hujah.all.order(updated_at: :desc) | |
serialized_hujahs = HujahSerializer.new(hujahs, params: {logged_in: logged_in?, current_user_id: current_user&.id }).serializable_hash | |
render json: serialized_hujahs | |
end | |
def create | |
hujah = current_user.hujahs.create(hujah_params) | |
if hujah | |
if hujah.has_parent? | |
Notification.create!(user_id: hujah.parent.user.id, category: 3, hujah_id: hujah.parent.id, subject_user_id: current_user.id) | |
end | |
render json: hujah | |
else | |
render json: hujah.errors | |
end | |
end | |
def show | |
if hujah | |
serialized_hujah = HujahSerializer.new(hujah, params: {logged_in: logged_in?, current_user_id: current_user&.id }).serializable_hash | |
render json: serialized_hujah | |
end | |
end | |
def destroy | |
hujah&.destroy | |
render json: { message: 'Hoojah deleted!' } | |
end | |
def new | |
end | |
private | |
def hujah_params | |
params.permit(:body, :parent_id, :vote) | |
end | |
def hujah | |
@hujah ||= Hujah.find_by_slug(params[:slug]) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment