Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Post vote graphs #1957

Merged
merged 11 commits into from Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions network-api/networkapi/buyersguide/models.py
Expand Up @@ -313,6 +313,7 @@ def votes(self):
# Build + return the votes dict
votes['creepiness'] = creepiness
votes['confidence'] = confidence_vote_breakdown
votes['total'] = BooleanVote.objects.filter(product=self).count()
return votes

except ObjectDoesNotExist:
Expand Down
Expand Up @@ -23,7 +23,11 @@ <h2 class="h1-heading">{{product.name}}</h2>
</div>
<div class="body-large mb-5">{{product.blurb}}</div>

<div class="creep-vote-target my-5"></div>
<div class="creep-vote-target my-5">
{% csrf_token %}
<input type="hidden" name="productID" value="{{ product.id }}">
<input type="hidden" name="votes" value="{{ product.votes | safe }}">
</div>

<h3 class="h3-heading h3-heading-small">Can it spy on me?</h3>

Expand Down
7 changes: 5 additions & 2 deletions network-api/networkapi/buyersguide/tests.py
Expand Up @@ -44,7 +44,8 @@ def test_aggregate_product_votes_default(self):
'confidence': {
'0': 0,
'1': 0
}
},
'total': 0
})

def test_aggregate_product_votes(self):
Expand Down Expand Up @@ -73,6 +74,7 @@ def test_aggregate_product_votes(self):
self.assertEqual(response.status_code, 201)

call_command('aggregate_product_votes')

self.assertDictEqual(product.votes, {
'creepiness': {
'average': 45,
Expand All @@ -87,7 +89,8 @@ def test_aggregate_product_votes(self):
'confidence': {
'0': 5,
'1': 5
}
},
'total': 10
})


Expand Down
1 change: 1 addition & 0 deletions source/images/buyers-guide/icon-thumb-down-black.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions source/images/buyers-guide/icon-thumb-up-black.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 20 additions & 2 deletions source/js/buyers-guide/bg-main.js
Expand Up @@ -13,7 +13,9 @@ let main = {
this.enableCopyLinks();
this.injectReactComponents();
primaryNav.init();
HomepageSlider.init();
if (document.getElementById(`pni-home`)) {
HomepageSlider.init();
}
},

enableCopyLinks() {
Expand Down Expand Up @@ -80,7 +82,23 @@ let main = {
injectReactComponents() {
if (document.querySelectorAll(`.creep-vote-target`)) {
Array.from(document.querySelectorAll(`.creep-vote-target`)).forEach(element => {
ReactDOM.render(<CreepVote />, element);
let csrf = element.querySelector(`input[name=csrfmiddlewaretoken]`);
let productID = element.querySelector(`input[name=productID]`).value;
let votes = element.querySelector(`input[name=votes]`).value;

try {
votes = JSON.parse(votes.replace(/'/g,`"`));
} catch (e) {
votes = {
creepiness: {
average: 50,
'vote_breakdown': {'0': 0, '1': 0, '2': 0, '3': 0, '4': 0}
},
confidence: {'0': 0, '1': 0}
};
}

ReactDOM.render(<CreepVote csrf={csrf.value} productID={parseInt(productID,10)} votes={votes}/>, element);
});
}

Expand Down
183 changes: 162 additions & 21 deletions source/js/buyers-guide/components/creep-vote/creep-vote.jsx
@@ -1,42 +1,183 @@
import React from 'react';
import Creepometer from '../creepometer/creepometer.jsx';
import CreepChart from '../creepiness-chart/creepiness-chart.jsx';
import LikelyhoodChart from '../likelyhood-chart/likelyhood-chart.jsx';

export default class CreepVote extends React.Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
}

getInitialState() {
// let conf = this.props.votes.confidence;
let totalVotes = this.props.votes.total;

this.state = {};
return {
totalVotes,
creepiness: 50,
confidence: undefined,
didVote: false
};
}

render() {
return (
<div className="creep-vote py-5">
<div className="row mb-5">
<div className="col-12 col-md-6">
<div className="mb-4 text-center">
<h3 className="h5-heading mb-2">How creepy is this product?</h3>
<p>Majority of voters think it is super creepy</p>
showVoteResult() {
if (this.state.creepinessSubmitted && this.state.confidenceSubmitted) {
this.setState({ didVote: true });
}
}

sendVoteFor(payload) {
let attribute = payload.attribute;
let url = `/privacynotincluded/vote`;
let method = `POST`;
let credentials = `same-origin`;
let headers = {
"X-CSRFToken": this.props.csrf,
"Content-Type": `application/json`
};

fetch(url, {
method,
credentials,
headers,
body: JSON.stringify(payload)
})
.then(() => {
let update = {};

update[`${attribute}Submitted`] = true;
this.setState(update, () => {
this.showVoteResult();
});
})
.catch(e => {
console.warn(e);
this.setState({ disableVoteButton: false });
});
}

submitVote(evt) {
evt.preventDefault();

let confidence = this.state.confidence;

if (confidence === undefined) {
return;
}

this.setState({ disableVoteButton: true });

let productID = this.props.productID;

this.sendVoteFor({
attribute: `confidence`,
productID,
value: confidence,
});

this.sendVoteFor({
attribute: `creepiness`,
productID,
value: this.state.creepiness
});
}

setCreepiness(creepiness) {
this.setState({ creepiness });
}

setConfidence(confidence) {
this.setState({ confidence });
}

/**
* @returns {jsx} What users see when they haven't voted on this product yet.
*/
renderVoteAsk() {
return (<form method="post" id="creep-vote" onSubmit={evt => this.submitVote(evt)}>
<div className="row mb-5">
<div className="col-12 col-md-6">
<div className="mb-4 text-center">
<h3 className="h5-heading mb-2">How creepy is this product?</h3>
<p className="help-text">Majority of voters think it is super creepy</p>
</div>
<Creepometer initialValue={this.state.creepiness} onChange={value => this.setCreepiness(value)}></Creepometer>
</div>
<div className="col-12 col-md-6">
<div className="mb-4 text-center">
<h3 className="h5-heading mb-2">How likely are you to buy it?</h3>
<p className="help-text">Majority of voters are not likely to buy it</p>
</div>
<div className="text-center">
<div class="btn-group btn-group-toggle mt-5" data-toggle="buttons">
<label for="likely">
<input type="radio" name="wouldbuy" id="likely" autocomplete="off" required/>
<span class="likely btn" onClick={() => this.setConfidence(true)}><img alt="thumb up" src="/_images/buyers-guide/icon-thumb-up-black.svg" /> Likely</span>
</label>
<label for="unlikely">
<input type="radio" name="wouldbuy" id="unlikely" autocomplete="off" required/>
<span class="unlikely btn" onClick={() => this.setConfidence(false)}><img alt="thumb down" src="/_images/buyers-guide/icon-thumb-down-black.svg" /> Not likely</span>
</label>
</div>
<Creepometer initialValue={50}></Creepometer>
</div>
<div className="col-12 col-md-6">
<div className="mb-4 text-center">
<h3 className="h5-heading mb-2">How likely are you to buy it?</h3>
<p>Majority of voters are not likely to buy it</p>
</div>
</div>
<div className="row">
<div className="col-12 text-center">
<button type="submit" className="btn btn-ghost mb-2" disabled={this.state.confidence===undefined}>Vote & See Results</button>
<p>{this.state.totalVotes} votes</p>
</div>
</div>
</form>);
}

/**
* @returns {jsx} What users see when they have voted on this product.
*/
renderDidVote(){
return(
<div>
<div className="mb-5">
<div className="col-12 text-center">
<h3 className="h5-heading mb-1">Thanks for voting! Here are the results so far:</h3>
<div className="text-muted">{this.state.totalVotes + 1} Votes</div>
</div>
<div className="row mt-3">
<div className="col">
<CreepChart userVoteGroup={Math.floor(this.state.creepiness/20)} values={this.props.votes.creepiness.vote_breakdown} />
</div>
<div className="text-center">
<button>Likely</button>
<button>Not likely</button>
<div className="col likelyhood-chart p-5">
<LikelyhoodChart values={this.props.votes.confidence} />
</div>
</div>
</div>
<div className="row">
<div className="col-12 text-center">
<button className="btn btn-ghost mb-2">Vote & See Results</button>
<p>367 votes</p>
<div className="text-center">
<div><a className="share-results" href="#coral_talk_stream">View comments</a> or share your results</div>
{/* TODO: Make these share links work */}
<div class="social d-flex justify-content-center mt-3">
<a class="social-button social-button-fb" href=""><span class="sr-only">Facebook</span></a>
<a class="social-button social-button-twitter" href=""><span class="sr-only">Twitter</span></a>
<a class="social-button social-button-email" href=""><span class="sr-only">Email</span></a>
</div>
</div>
</div>
);
}

render() {
let voteContent;

if(this.state.didVote){
voteContent = this.renderDidVote();
} else {
voteContent = this.renderVoteAsk();
}

return (
<div className="creep-vote py-5">
{ voteContent }
</div>
);
}
}
65 changes: 64 additions & 1 deletion source/js/buyers-guide/components/creep-vote/creep-vote.scss
@@ -1,4 +1,67 @@
$radio-button-radius: 50px;
$btn-shadow-width: 3px;

.creep-vote {
border-top: 1px solid #c8c8ca;
border-bottom: 1px solid #c8c8ca;
border-top: 1px solid #c8c8ca;

button[disabled] {
color: #bbb;

&.btn-ghost:hover {
cursor: auto;
color: inherit;
background: inherit;
}
}

.btn-group {
display: flex;
justify-content: center;

.btn {
border: 1px solid black;
border-bottom-width: $btn-shadow-width;
color: black;
padding: 0.5em 1.5em;
text-transform: initial;
transition: all 0.1s linear;
}

input { display: none; }

input:checked + span {
background-color: #5cccff;
}

input:-moz-ui-invalid + span {
border-color: red;
}

label:first-of-type span {
border-radius: $radio-button-radius 0 0 $radio-button-radius;
}

label:last-of-type span {
border-radius: 0 $radio-button-radius $radio-button-radius 0;
}

img {
height: 1em;
}
}

.help-text {
font-style: italic;
}

a.share-results {
text-decoration: underline;
color: black;

&:hover {
color: #4383bf;
font-weight: bold;
}
}
}