Skip to content

Commit

Permalink
WIP: Post vote graphs (#1957)
Browse files Browse the repository at this point in the history
* Creepiness-chart start

* Creepiness chart

* Basic likelyhood graph

* updates to get us most of the way to voting

* Clean up creepiness

* creep vote cleanups

* Don’t repeat thumbs

* slider rewrite (#1964)

slider refinement.

* fix aggregate test
  • Loading branch information
alanmoo committed Oct 18, 2018
1 parent 45c684c commit 5f8bd3c
Show file tree
Hide file tree
Showing 15 changed files with 658 additions and 92 deletions.
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;
}
}
}

0 comments on commit 5f8bd3c

Please sign in to comment.