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

Commit

Permalink
Merge pull request #1475 from SumOfUs/development
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
eyko committed Aug 7, 2019
2 parents 9fcad71 + 75bdd97 commit 67f0611
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 92 deletions.
100 changes: 67 additions & 33 deletions app/javascript/components/Braintree/BraintreeCardFields.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,78 @@ import { injectIntl, FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import hostedFields from 'braintree-web/hosted-fields';
import './Braintree.scss';
import ee from '../../shared/pub_sub';

class BraintreeCardFields extends Component {
constructor(props) {
super(props);

this.state = {
hostedFields: null,
errors: {
number: false,
expirationDate: false,
cvv: false,
},
styles: {
input: {
color: '#333',
'font-size': '16px',
},
':focus': { color: '#333' },
'.valid': { color: '#333' },
},
fields: {
number: Object.freeze({
selector: '#braintree-card-number',
placeholder: this.props.intl.formatMessage({
id: 'fundraiser.fields.number',
defaultMessage: 'Card number',
}),
}),
cvv: Object.freeze({
selector: '#braintree-cvv',
placeholder: this.props.intl.formatMessage({
id: 'fundraiser.fields.cvv',
defaultMessage: 'CVV',
}),
}),
expirationDate: Object.freeze({
selector: '#braintree-expiration-date',
placeholder: this.props.intl.formatMessage({
id: 'fundraiser.fields.expiration_format',
defaultMessage: 'MM/YY',
}),
}),
},
};
}

bindGlobalEvents() {
ee.on('fundraiser:configure:hosted_fields', this.configureHostedFields);
}

configureHostedFields = (config = {}) => {
this.setState(
{
styles: config.styles || this.state.styles,
fields: config.fields || this.state.fields,
},
() => {
console.log(this.state.fields);
if (this.state.hostedFields) {
this.teardown();
} else {
this.createHostedFields(this.props.client);
}
}
);
};

componentDidMount() {
if (this.props.client) {
this.createHostedFields(this.props.client);
}
this.bindGlobalEvents();
}

componentWillReceiveProps(newProps) {
Expand All @@ -41,40 +94,21 @@ class BraintreeCardFields extends Component {
hostedFields.create(
{
client,
styles: {
input: {
color: '#333',
'font-size': '16px',
},
':focus': { color: '#333' },
'.valid': { color: '#333' },
},
fields: {
number: Object.freeze({
selector: '#braintree-card-number',
placeholder: formatMessage({
id: 'fundraiser.fields.number',
defaultMessage: 'Card number',
}),
}),
cvv: Object.freeze({
selector: '#braintree-cvv',
placeholder: formatMessage({
id: 'fundraiser.fields.cvv',
defaultMessage: 'CVV',
}),
}),
expirationDate: Object.freeze({
selector: '#braintree-expiration-date',
placeholder: formatMessage({
id: 'fundraiser.fields.expiration_format',
defaultMessage: 'MM/YY',
}),
}),
},
styles: this.state.styles,
fields: this.state.fields,
},
(err, hostedFieldsInstance) => {
if (err && window.Raven) return window.Raven.captureException(err);
if (err) {
if (window.Sentry) {
window.Sentry.captureException(err);
}
ee.emit('fundraiser:configure:hosted_fields:error', err);
return;
}
ee.emit(
'fundraiser:configure:hosted_fields:success',
hostedFieldsInstance
);

this.setState({ hostedFields: hostedFieldsInstance }, () => {
if (this.props.onInit) {
Expand Down
9 changes: 5 additions & 4 deletions app/javascript/legacy/member-facing/backbone/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ const Sidebar = Backbone.View.extend({
maxHeight -= $title.outerHeight();
}
const main = this.$(`.${this.baseClass}__main`);
const overflow = main[0].scrollHeight > maxHeight ? 'scroll' : 'visible';

main.css('overflow', overflow);
main.css('max-height', `${maxHeight}px`);
if (main && main[0]) {
const overflow = main[0].scrollHeight > maxHeight ? 'scroll' : 'visible';
main.css('overflow', overflow);
main.css('max-height', `${maxHeight}px`);
}
},

positionBarTop() {
Expand Down
25 changes: 16 additions & 9 deletions app/javascript/plugins/email_tool/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { camelizeKeys } from '../../util/util';
import Plugin from '../plugin';
import EmailToolView from './EmailToolView';

interface IEmailPensionOptions {
interface IEmailToolOptions {
el: HTMLElement;
namespace: string;
config: any; // todo
Expand All @@ -20,8 +20,14 @@ export const init = options => {
options.el = document.getElementById('email-tool-component');
}
const { el, store } = options;
const member = window.champaign.personalization.member;
const memberData = pick(member, 'name', 'email', 'country', 'postal');
const personalization = window.champaign.personalization;
const memberData = pick(
personalization.member,
'name',
'email',
'country',
'postal'
);
const trackingParams = pick(
window.champaign.personalization.urlParams,
'source',
Expand All @@ -34,8 +40,9 @@ export const init = options => {
...options.config,
...memberData,
...trackingParams,
country: memberData.country || personalization.location.country,
onSuccess(target) {
window.location.href = URI('{{ follow_up_url }}')
window.location.href = URI(`${window.location.pathname}/follow-up`)
.addSearch({
'target[name]': target.name,
'target[title]': target.title,
Expand All @@ -44,20 +51,20 @@ export const init = options => {
},
};

return new EmailPension({
return new EmailTool({
config,
el,
namespace: 'emailpension',
namespace: 'emailtool',
store,
});
};

class EmailPension extends Plugin<any> {
class EmailTool extends Plugin<any> {
public store: Store<IAppState>;
public customRenderer: (instance: EmailPension) => any | undefined;
public customRenderer: (instance: EmailTool) => any | undefined;
public wrappedReactComponent?: React.Component;

constructor(options: IEmailPensionOptions) {
constructor(options: IEmailToolOptions) {
super(options);
this.render();
}
Expand Down
32 changes: 17 additions & 15 deletions app/javascript/plugins/fundraiser/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,10 @@ import {
} from '../../state/fundraiser/actions';
import { IAppState } from '../../types';
import { IFundraiserPluginConfig } from '../../window';
import Plugin from '../plugin';
import Plugin, { IPluginOptions } from '../plugin';
import FundraiserView from './FundraiserView';
import { configureStore, fundraiserData } from './utils';

interface IFundraiserOptions {
el: HTMLElement;
namespace: string;
config: IFundraiserPluginConfig;
store: Store<IAppState>;
}

export function init(options: any) {
if (!options.el) {
options.el = document.getElementById('fundraiser-component');
Expand All @@ -32,18 +25,16 @@ export function init(options: any) {
configureStore(fundraiserData(options.config), options.store.dispatch);

return new Fundraiser({
el: options.el,
...options,
namespace: 'fundraiser',
config: options.config,
store: options.store,
});
}

export class Fundraiser extends Plugin<IFundraiserPluginConfig> {
public store: Store<IAppState>;
public customRenderer: (instance: Fundraiser) => any | undefined;

constructor(options: IFundraiserOptions) {
constructor(options: IPluginOptions<IFundraiserPluginConfig>) {
super(options);
this.render();
}
Expand Down Expand Up @@ -109,9 +100,20 @@ export class Fundraiser extends Plugin<IFundraiserPluginConfig> {
// or perhaps in a braintree service, but not in the react component.
// The <Payment/> react component has become quite complex and practically
// unmaintainable
public makePayment() {
this.events.emit('fundraiser:actions:make_payment');
return this;
public makePayment(callback: (data: any, formData: any) => void) {
return new Promise((resolve, reject) => {
this.events.once('fundraiser:transaction_success', resolve);
this.events.once('fundraiser:transaction_error', reject);
this.events.emit('fundraiser:actions:make_payment');
});
}

public configureHostedFields(config: any) {
return new Promise((resolve, reject) => {
this.events.once('fundraiser:configure:hosted_fields:success', resolve);
this.events.once('fundraiser:configure:hosted_fields:error', reject);
this.events.emit('fundraiser:configure:hosted_fields', config);
});
}

get recurring() {
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/member_facing.slim
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ html lang="#{@page.language.try(:code).try(:downcase)}"
= render partial: "shared/optimizely_snippet" unless @page.optimizely_disabled?

= stylesheet_link_tag "member-facing"
= stylesheet_packs_with_chunks_tag "globals", "plugins"
= stylesheet_packs_with_chunks_tag "globals", "member_facing", "plugins"
body
= render partial: "layouts/double_opt_in_notification"
= render partial: "layouts/notification"
Expand Down
29 changes: 0 additions & 29 deletions app/views/plugins/email_tools/_email_tool.liquid
Original file line number Diff line number Diff line change
@@ -1,34 +1,5 @@
{% if plugins.email_tool[ref].active %}
<div id="email-tool-component"></div>

<script>
$(document).ready(function() {
var personalization = window.champaign.personalization;
// FIXME: don't copy all params, pick only the ones we need.
var data = _.clone(personalization.emailTool['{{ref}}']);
var trackingParams = _.pick(window.champaign.personalization.urlParams,
'source', 'akid', 'referring_akid',
'referrer_id', 'rid');
_.merge(data, {
name: personalization.member.full_name,
email: personalization.member.email,
postal: personalization.member.postal,
country: personalization.member.country || personalization.location.country,
trackingParams: trackingParams,
onSuccess: function (target) {
window.location.href = window.URI("{{ follow_up_url }}")
.addSearch({
'target[name]': target.name,
'target[title]': target.title
})
.toString();
}
});
mountEmailTool('email-tool-component', data);
});
</script>

<div class="body-text text-bottom">
{{ plugins.email_tool[ref].body }}
</div>
Expand Down
68 changes: 67 additions & 1 deletion docs/plugins/Fundraiser.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,77 @@ In addition to the [Plugin](./Plugin.md) properties and methods, `Fundraiser` ha
* `addPaymentMethod(paymentMethodData)`: Allows us to create a new payment method. The `paymentMethodData` object will need to contain a `label`, a `setup` function (optional), and the `onSubmit`, `onSuccess`, and `onFailure` callbacks as properties. **Not implemented**.
* `setAmount(amount: number)`: Sets the selected amount.
* `setCurrency(currencyCode: string)`: Sets the selected currency. Sets the default currency if an unsupported currency is given.

* `configureHostedFields(config: HostedFieldsConfiguration): Promise`

Configures the hosted fields. The `config` parameter is an object with two optional properties: [`styles`](https://braintree.github.io/braintree-web/3.50.0/module-braintree-web_hosted-fields.html#~styleOptions), and [`fields`](https://braintree.github.io/braintree-web/3.50.0/module-braintree-web_hosted-fields.html#~fieldOptions). It returns a Promise that resolves with a hosted fields instance object ([documentation](https://braintree.github.io/braintree-web/3.50.0/HostedFields.html)), or rejects with a braintree error object ([documentation](https://braintree.github.io/braintree-web/3.50.0/BraintreeError.html)).

**Example**:
You need to create DOM elements for each of the fields that you pass in the `config` object, and give them a unique ID. For instance, here I'm creating a hosted fields instance with the Card Number, CVV, and Expiry Date fields. For that, I'll need the markup:

```html
<div class="hosted-fields-container">
<div id="card-number"></div>
<div id="cvv-number"></div>
<div id="expiration-date"></div>
</div>
```
And now I can pass those IDs in my configuration object, along with some basic styles:

```js
fundraiser.configureHostedFields({
fields: {
number: {
selector: '#card-number',
placeholder: 'Card number',
},
cvv: {
selector: '#cvv-number',
placeholder: 'CVV',
},
expirationDate: {
selector: '#expiration-date',
placeholder: 'MM / YY',
},
},
styles: {
input: {
color: '#333',
'font-size': '16px',
},
':focus': { color: '#333' },
'.valid': { color: '#333' },
},
}).then(
hostedFieldsInstance => {
// On success, you'll receive the hosted fields instance
// You can interact with it and listen to events on
// it (e.g.credit card type detected, etc.)
// Documentation: https://braintree.github.io/braintree-web/3.50.0/HostedFields.html
},
error => {
// On error, you'll receive a BraintreeError object
// with the error message and information.
}
);
```

* `setPaymentType(paymentType: string)`: Sets the payment type (gocardless, paypal, card, etc). If the given payment type is not supported, it will be set to the default payment type.
* `setRecurring(value: boolean)`: Updates the recurring value.
* `setStoreInVault(value: boolean)`: Updates `storeInVault` value, which indicates whether we want to save the payment token/grant. This only affects braintree transactions.
* `form` (Getter): Returns a reference to the form's DOM element.
* `makePayment()`: Tries to make a donation.
* `makePayment(): Promise`: Tries to make a donation. This function returns a `Promise` that resolves on success, and rejects on error.
```js
const fundraiser = window.champaign.plugins.fundraiser.default.instance;
fundraiser
.setAmount(1)
.setPaymentType('paypal')
.makePayment()
.then(
(response) => console.log('Success', response),
(error) => console.log('Failure', error)
);
```
* `onComplete()`: Triggers the `onComplete` behaviour (scroll transition, `fundraiser:complete:before` events, etc).
* `render()`: Renders the component. If a custom renderer is specified, it will call the custom renderer and pass itself as the first argument. The custom renderer can then use any of the public methods and properties.
* `resetMember()`: Resets the member (and the form).
Expand Down

0 comments on commit 67f0611

Please sign in to comment.