Skip to content

Commit

Permalink
Merge 7c64edc into f92af68
Browse files Browse the repository at this point in the history
  • Loading branch information
alanmoo committed Oct 18, 2018
2 parents f92af68 + 7c64edc commit 0750cd6
Show file tree
Hide file tree
Showing 12 changed files with 512 additions and 27 deletions.
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
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.
18 changes: 17 additions & 1 deletion source/js/buyers-guide/bg-main.js
Expand Up @@ -80,7 +80,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
181 changes: 159 additions & 22 deletions source/js/buyers-guide/components/creep-vote/creep-vote.jsx
@@ -1,42 +1,179 @@
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 = conf[0] + conf[1];

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>
</div>
<Creepometer initialValue={50}></Creepometer>
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(response => {
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" className="creep-vote" 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>
<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 className="text-center">
<button>Likely</button>
<button>Not likely</button>
<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={evt => 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={evt => this.setConfidence(false)}><img alt="thumb down" src="/_images/buyers-guide/icon-thumb-down-black.svg" /> Not likely</span>
</label>
</div>
</div>
</div>
<div className="row">
</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">
<button className="btn btn-ghost mb-2">Vote & See Results</button>
<p>367 votes</p>
<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="col likelyhood-chart p-5">
<LikelyhoodChart values={this.props.votes.confidence} />
</div>
</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 className="text-center">
<div>View comments or share your results</div>
{/* TODO: Make these share links work */}
<div className="share-links">fb, tw, email</div>
</div>
</div>
);
}
}
55 changes: 54 additions & 1 deletion source/js/buyers-guide/components/creep-vote/creep-vote.scss
@@ -1,4 +1,57 @@
$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: #bbbbbb;

&.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;
}
}
@@ -0,0 +1,57 @@
import React from 'react';

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

getInitialState() {
let values = this.props.values;
let data = [
{c: `no-creep`, label: `Not creepy`, value: values[0], offset: 0},
{c: `little-creep`, label: `A little creepy`, value: values[1], offset: 225},
{c: `somewhat-creep`, label: `Somewhat creepy`, value: values[2], offset: 475},
{c: `very-creep`, label: `Very creepy`, value: values[3], offset: 725},
{c: `super-creep`, label: `Super creepy`, value: values[4], offset: 975}
];
let sum = data.reduce((tally, v) => tally + v.value, 0);

return {
totalCreepiness: sum,
creepinessData: data
};
}

render() {
return (
<div>
<table id="creepiness-score">
<tbody>
{
this.state.creepinessData.map((data,index) => {
let percent = Math.round(100 * data.value / this.state.totalCreepiness);
let voteColumn = this.props.userVoteGroup === index ? `your-vote` : ``;

return (
<tr key={data.c} className={`${voteColumn} ${data.c}`}>
<th>
<div className="bar" style={{height: `${percent}px`,}}></div>
<span className="creep-label">{data.label}</span>
<span className="creep-face"></span>
</th>
<td className="creepiness">{percent}%</td>
</tr>
);
})
}
</tbody>
</table>
<div className="row">
<div className="col text-left text-muted">Not creepy</div>
<div className="col text-right text-muted">Super creepy</div>
</div>
</div>
);
}
}

0 comments on commit 0750cd6

Please sign in to comment.