Commit 96a1cc90 authored by Stephanie Kirtiadi's avatar Stephanie Kirtiadi
Browse files

Added a boolean field on donate page for donation counter.

Part of the porting of donation site to the main Tor site.
This counter will need to request to the donate server and
if the field is checked, the counter will show up on the donate
page.

Issue #48277
parent 11631615
......@@ -85,3 +85,5 @@ tshirt_perk_monthly_image: /static/images/donate/t-shirt-take-back-internet.png
image: /static/images/donate/donate-header.png
---
success_redirect_url:
---
counter: yes
lego @ 3e7019ee
Subproject commit d2927b0127d207b51e06fff39fe3595ebe0a4a31
Subproject commit 3e7019ee37daa80770cfd986bd4a5bfc01b58103
......@@ -44,6 +44,10 @@ type = string
label = URL to redirect to after donation completed successfully
type = url
[fields.counter]
label = Display Counter
type = boolean
[fields.single_one]
label = Single Price 1 (in US cents)
type = integer
......
import React, {useState} from 'react';
import {Counter} from './counter';
export function CampaignTotals(props) {
const {showCounter, donateProccessorBaseUrl} = props;
const [donation, setDonation] = useState(0);
const [totalDonation, setTotalDonation] = useState(0);
const [totalMatchedDonation, setTotalMatchedDonation] = useState(0);
const [countersState, setCountersState] = useState('flashing');
const [resolvedIndex, setResolvedIndex] = useState(6);
if (showCounter != "True") {
return null;
}
const requestTotal = async () => {
const options = {
headers: {
'Content-Type': 'application/json',
},
method: 'GET',
};
const response = await fetch(`${donateProccessorBaseUrl}/campaign-totals`, options);
const response_data = await response.json();
if ('errors' in response_data) {
if (response_data['errors'].length > 0) {
console.log(`Error fetching campaign totals from ${donateProccessorBaseUrl}/campaign-totals`, response_data);
} else {
handleCampaignTotalsResponseData(response_data['data']);
}
}
};
const convertAmountToArrayOfStringToDisplay = (amount) => {
var amountArray = convertNumberToArrayOfString(amount);
while (amountArray.length < 6) {
amountArray.unshift("0");
}
return amountArray;
}
const convertNumberToArrayOfString = (number) => {
return parseInt(number).toString().split("");
}
const counters = [
{
'type': 'supporters',
'label': 'Number of Donations',
'amountArrayWithoutZeroPrefixes': convertNumberToArrayOfString(donation).length,
'amountArray': convertAmountToArrayOfStringToDisplay(donation)
},
{
'type': 'total-donated',
'label': 'Total Donated',
'amountArrayWithoutZeroPrefixes': convertNumberToArrayOfString(totalDonation).length,
'amountArray': convertAmountToArrayOfStringToDisplay(totalDonation)
},
{
'type': 'total-matched',
'label': "Total Raised with Mozilla's Match",
'amountArrayWithoutZeroPrefixes': convertNumberToArrayOfString(totalMatchedDonation).length,
'amountArray': convertAmountToArrayOfStringToDisplay(totalMatchedDonation)
}
];
const getMaxAmountArray = () => {
const lengthOfArrays = counters.map(counter => counter.amountArray.length);
return Math.max(...lengthOfArrays);
}
const handleCampaignTotalsResponseData = (data) => {
setDonation(data['totalDonations']);
setTotalDonation(data['totalAmount']);
setTotalMatchedDonation(data['amountWithMozilla']);
setResolvedIndex(getMaxAmountArray());
setCountersState('resolving');
}
if (donation == 0 && totalDonation == 0 && totalMatchedDonation == 0) {
requestTotal();
}
if (countersState == 'resolving') {
if (resolvedIndex >= 0) {
setTimeout(() => {
setResolvedIndex(resolvedIndex - 1);
}, 500);
} else {
setCountersState('resolved');
}
}
function renderCounters(item) {
return (
<Counter
key={item.type}
type={item.type}
label={item.label}
amountArray={item.amountArray}
amountArrayWithoutZeroPrefixes={item.amountArrayWithoutZeroPrefixes}
countersState={countersState}
resolvedIndex={resolvedIndex}
maxArrayLength={getMaxAmountArray()}
/>
);
}
return (
<React.Fragment>
{counters.map((item) => renderCounters(item))}
</React.Fragment>
);
}
import React from 'react';
import {CounterCharacter} from './counter_character';
import {FlashingCounterCharacter} from './flashing_counter_character';
export function Counter(props) {
const {type, label, amountArray, countersState, resolvedIndex, maxArrayLength, amountArrayWithoutZeroPrefixes} = props;
const counterResolvedIndex= resolvedIndex-(maxArrayLength-amountArray.length);
function renderLetters(item, index) {
return (
<CounterCharacter
key={index}
amountArrayLength={amountArray.length}
characterIndex={index}
number={item}
amountArrayWithoutZeroPrefixes={amountArrayWithoutZeroPrefixes}
counterResolvedIndex={counterResolvedIndex}
countersState={countersState}
/>
);
}
return (
<div className={type}>
<div className="characters">
{amountArray.map((item, index) => renderLetters(item, index))}
</div>
<div className="label">{label}</div>
</div>
);
}
import React, {useState} from 'react';
export function CounterCharacter(props) {
const {number, characterIndex, amountArrayWithoutZeroPrefixes, amountArrayLength, counterResolvedIndex, countersState} = props;
const [characterFlashingState, setCharacterFlashingState] = useState('covered');
if (countersState == 'flashing' || (countersState == 'resolving' && characterIndex < counterResolvedIndex)) {
// Flashing Characters
setTimeout(() => {
var newState = '';
if (Math.random() <= 0.5) {
newState = 'covered';
}
setCharacterFlashingState(newState);
}, 100);
const characterClasses = ['character', characterFlashingState];
return (
<div className={characterClasses.join(" ")}>
<div className="cover"></div>
0
</div>
);
} else {
// Resolved Characters
var characterState = 'covered';
const lastResolvedIndex = amountArrayLength - amountArrayWithoutZeroPrefixes;
var label = 0;
const isCharacterResolved = () => {
if (characterIndex >= lastResolvedIndex) {
return true;
}
return false;
}
if (isCharacterResolved()) {
characterState = 'resolved';
label = number;
} else {
characterState = '';
}
const characterClasses = ['character', characterState];
const renderCover = () => {
if (isCharacterResolved()) {
return null;
} else {
return (
<div className="covered"></div>
);
}
}
return (
<div className={characterClasses.join(" ")}>
{renderCover()}
{label}
</div>
);
}
}
import React, {useState} from 'react';
export function FlashingCounterCharacter(props) {
const {countersState} = props;
const [characterState, setCharacterState] = useState('covered');
if (countersState == 'resolved') {
return null;
}
setTimeout(() => {
var newState = '';
if (Math.random() <= 0.5) {
newState = 'covered';
}
setCharacterState(newState);
}, 100);
const characterClasses = ['character', characterState];
return (
<div className={characterClasses.join(" ")}>
<div className="cover"></div>
0
</div>
);
}
import 'babel-polyfill';
import {LoadingDialog} from './loading_dialog';
import {GivingForm} from './giving_form';
import {CampaignTotals} from './campaign_totals';
import React from 'react';
import ReactDOM from 'react-dom';
import {AppContext} from './app_context';
......@@ -10,6 +11,7 @@ const reactCallbacks = {};
const availableComponents = {
'LoadingDialog': LoadingDialog,
'GivingForm': GivingForm,
'CampaignTotals': CampaignTotals
};
const appContext = {};
......
<div>
{{ this.intro }}
</div>
<div id="giving-form" class="donate-form">
<div class="donate-form">
<div id="campaign-totals-area"></div>
<div id="giving-form"></div>
<div id="loading-dialog"></div>
</div>
<script type="text/javascript" src="https://js.stripe.com/v3/"></script>
<script type="text/javascript" src="https://www.paypal.com/sdk/js?client-id={{ bag('donate', envvars('ENV'), 'paypalClientId') }}&vault=true"></script>
<script type="text/javascript">
window.reactComponents = [
{
id: 'campaign-totals-area',
name: 'CampaignTotals',
props: {
showCounter: `{{ this.counter }}`,
'donateProccessorBaseUrl': `{{ bag('donate', envvars('ENV'), 'donateProccessorBaseUrl') }}`,
},
},
{
id: 'loading-dialog',
name: 'LoadingDialog',
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment