Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

WIP Funding thermometer #1292

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ Style/EachWithObject:
Style/FormatString:
Exclude:
- 'app/models/plugins/thermometer.rb'
- 'app/models/plugins/actions_thermometer.rb'
- 'app/models/plugins/donations_thermometer.rb'

# Offense count: 5
# Configuration parameters: MinBodyLength.
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def total_donations
amount = @page.total_donations
goal = @page.fundraising_goal
else
amount = @page.campaign.donations_count
amount = @page.campaign.total_donations
goal = @page.campaign.fundraising_goal
end

Expand Down
19 changes: 19 additions & 0 deletions app/controllers/plugins/actions_thermometers_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

class Plugins::ActionsThermometersController < Plugins::BaseController
private

def permitted_params
params
.require(:plugins_actions_thermometer)
.permit(:title, :offset, :goal, :active, :type)
end

def plugin_class
Plugins::ActionsThermometer
end

def plugin_symbol
:plugins_thermometer
end
end
2 changes: 1 addition & 1 deletion app/controllers/plugins/base_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# frozen_string_literal: true
# # frozen_string_literal: true
class Plugins::BaseController < ApplicationController
before_action :authenticate_user!

Expand Down
29 changes: 21 additions & 8 deletions app/controllers/plugins/thermometers_controller.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
# frozen_string_literal: true

class Plugins::ThermometersController < Plugins::BaseController
private
def update
@plugin = Plugins::Thermometer.find(params[:id])

def permitted_params
params
.require(:plugins_thermometer)
.permit(:title, :offset, :goal, :active)
respond_to do |format|
if @plugin.update(permitted_params)
format.js { render json: {} }
else
format.js { render json: { errors: @plugin.errors, name: plugin_name.to_sym }, status: :unprocessable_entity }
end
end
end

private

def plugin_class
Plugins::Thermometer
params[:plugins_actions_thermometer].blank? ? Plugins::DonationsThermometer : Plugins::ActionsThermometer
end

def permitted_params
params
.require(plugin_name)
.permit(:title, :offset, :goal, :active, :type)
end

def plugin_symbol
:plugins_thermometer
def plugin_name
plugin_class.name.underscore.tr('/', '_')
end
end
1 change: 1 addition & 0 deletions app/controllers/plugins_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

class PluginsController < ApplicationController
before_action :find_page

Expand Down
2 changes: 2 additions & 0 deletions app/javascript/components/AmountSelection/AmountSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
import DonationBands from '../DonationBands/DonationBands';
import Button from '../Button/Button';
import ee from '../../shared/pub_sub';
import Thermometer from '../Thermometer';

import CurrencyAmount from '../../components/CurrencyAmount';

Expand Down Expand Up @@ -74,6 +75,7 @@ export default class AmountSelection extends React.Component<Props, State> {
render() {
return (
<div className="AmountSelection-container section">
<Thermometer />
<DonationBands
ref="donationBands"
amounts={this.props.donationBands[this.props.currency]}
Expand Down
20 changes: 17 additions & 3 deletions app/javascript/components/AmountSelection/AmountSelection.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// @flow
import React from 'react';
import { Provider } from 'react-redux';
import {
shallowWithIntl,
mountWithIntl,
} from '../../../../spec/jest/intl-enzyme-test-helpers';
import { store } from '../../../../spec/jest/mockReduxStore';
import AmountSelection from './AmountSelection';
import type { Props } from './AmountSelection';

Expand All @@ -20,12 +22,20 @@ const defaultProps: Props = {
};

it('renders', () => {
const component = mountWithIntl(<AmountSelection {...defaultProps} />);
const component = mountWithIntl(
<Provider store={store}>
<AmountSelection {...defaultProps} />
</Provider>
);
expect(component.html()).toBeTruthy();
});

describe('Donation bands', () => {
const component = mountWithIntl(<AmountSelection {...defaultProps} />);
const component = mountWithIntl(
<Provider store={store}>
<AmountSelection {...defaultProps} />
</Provider>
);

it('shows the donation band passed as an argument', () => {
const firstButton = component
Expand Down Expand Up @@ -60,7 +70,11 @@ describe('Donation bands', () => {
});

describe('Changing currency', () => {
const component = mountWithIntl(<AmountSelection {...defaultProps} />);
const component = mountWithIntl(
<Provider store={store}>
<AmountSelection {...defaultProps} />
</Provider>
);

it('does not show the currency menu by default', () => {
expect(component.find('.AmountSelection__currency-selector').length).toBe(
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/components/Payment/Payment.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,8 @@ const mapStateToProps = (state: AppState) => ({
? 'gocardless'
: state.fundraiser.currentPaymentType,
fundraiser: state.fundraiser,
page: state.page,
paymentMethods: state.paymentMethods,
paymentMethodss: state.donationsThermometer,
member: state.member,
hideRecurring: state.fundraiser.recurringDefault === 'only_recurring',
formData: {
Expand Down
82 changes: 82 additions & 0 deletions app/javascript/components/Thermometer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { isEmpty, min } from 'lodash';
import CurrencyAmount from './CurrencyAmount';
import type { AppState } from '../state/reducers';
import './Thermometer.scss';

type Props =
| {}
| {
active: boolean,
currency: string,
donations: number,
goal: number,
offset: number,
remaining: number,
title: ?string,
};

export function Thermometer(props: Props) {
// Only render if active
if (isEmpty(props) || !props.active) return null;

// Prevent overflow when donations > goal.
const donations = min([props.donations, props.goal]);
const remaining = props.goal - donations;

const $remaining = (
<CurrencyAmount amount={remaining} currency={props.currency} />
);
const $goal = (
<CurrencyAmount amount={props.goal} currency={props.currency} />
);

return (
<div className="Thermometer">
<p className="Thermometer-title">{props.title}</p>
<div className="Thermometer-stats">
<div className="Thermometer-temperature">
<CurrencyAmount amount={donations} currency={props.currency} />
</div>
<div className="Thermometer-goal">
{remaining > 0 ? (
<FormattedMessage
id="fundraiser.thermometer.remaining"
defaultMessage="{remaining} until {goal}"
values={{ remaining: $remaining, goal: $goal }}
/>
) : (
$goal
)}
</div>
</div>
<div className="Thermometer-bg">
<div
className="Thermometer-mercury"
style={{
width: `${Math.round((donations / props.goal) * 100)}%`,
}}
/>
</div>
</div>
);
}

const mapStateToProps = (state: AppState): Props => {
const data = state.donationsThermometer;
const currency = state.fundraiser.currency;
if (isEmpty(data)) return {};
return {
active: data.active,
currency,
donations: data.totalDonations[currency],
goal: data.goals[currency],
offset: data.offset,
title: data.title,
};
};

export default connect(mapStateToProps)(Thermometer);
55 changes: 55 additions & 0 deletions app/javascript/components/Thermometer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

.Thermometer {

}

.Thermometer-title {
font-weight: bold;
margin-bottom: 1em;
}

.Thermometer-stats {
width: 100%;
line-height: 14px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.Thermometer-temperature {
width: auto;
float: left;
text-align: left;
}
.Thermometer-goal {
width: auto;
text-align: right;
color: rgba(48, 57, 79, 0.5);
}

.Thermometer-bg {
background-color: white;
border-color: #aaa;
border-radius: 4px;
clear: both;
height: 16px;
margin: 5px 0 20px;
padding: 0;
position: relative;
width: 100%;
}

.Thermometer-mercury {
border-radius: 4px;
background-color: #00c0cf;
margin: 0;
padding: 0;
height: 100%;
min-width: 8px;
}

/* Free standing version */
.fundraiser-bar--freestanding {
.Thermometer-bg {
background-color: #f5f5f5;
}
}
19 changes: 19 additions & 0 deletions app/javascript/components/Thermometer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { IntlProvider } from 'react-intl';
import { Thermometer } from './Thermometer';

test('Renders as expected', () => {
const component = renderer.create(
<IntlProvider locale="en-GB" messages={{}}>
<Thermometer
title="Title"
donations={1000}
goal={600000}
currency={'GBP'}
/>
</IntlProvider>
);

expect(component.toJSON()).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Renders as expected 1`] = `null`;
2 changes: 2 additions & 0 deletions app/javascript/fundraiser/FundraiserView.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export class FundraiserView extends Component<Props> {

<StepContent title={<FormattedMessage id="fundraiser.payment" />}>
<Payment
page={this.props.page}
disableFormReveal={this.showStepTwo()}
setSubmitting={s => this.props.setSubmitting(s)}
/>
Expand All @@ -145,6 +146,7 @@ export class FundraiserView extends Component<Props> {
}

export const mapStateToProps = (state: AppState) => ({
features: state.features,
fundraiser: state.fundraiser,
member: state.member,
page: state.page,
Expand Down
39 changes: 39 additions & 0 deletions app/javascript/fundraiser/FundraiserView.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,45 @@ const fetchInitialState = vals => {
...defaults.fundraiser,
...vals,
},
donationsThermometer: {
id: 10,
title: null,
offset: 0,
page_id: 2,
active: true,
created_at: '2018-11-22T17:58:09.748Z',
updated_at: '2018-11-22T17:58:32.582Z',
ref: '',
type: 'DonationsThermometer',
percentage: 0,
remaining_amounts: {
USD: -90,
GBP: -90,
EUR: -90,
CHF: -90,
AUD: -90,
NZD: -90,
CAD: -90,
},
total_donations: {
USD: 10,
GBP: 10,
EUR: 10,
CHF: 10,
AUD: 10,
NZD: 10,
CAD: 10,
},
goals: {
USD: 100,
GBP: 100,
EUR: 100,
CHF: 100,
AUD: 100,
NZD: 100,
CAD: 100,
},
},
},
};
};
Expand Down
Loading