Skip to content

Commit

Permalink
add more receiver information fields to Event (#788)
Browse files Browse the repository at this point in the history
- also clean up the React warnings on the donation form

[#188816354]
  • Loading branch information
uraniumanchor authored Feb 7, 2025
1 parent 70a2ec3 commit 96f9ff5
Show file tree
Hide file tree
Showing 17 changed files with 179 additions and 56 deletions.
3 changes: 3 additions & 0 deletions bundles/public/apiv2/Models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export interface Event extends ModelBase {
timezone: string;
receivername: string;
receiver_short: string;
receiver_solicitation_text: string;
receiver_logo: string;
receiver_privacy_policy: string;
paypalcurrency: string;
use_one_step_screening: boolean;
allow_donations: boolean;
Expand Down
4 changes: 0 additions & 4 deletions bundles/tracker/donation/DonationConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ export const EMAIL_OPTIONS = [
name: 'No',
value: 'OPTOUT',
},
{
name: 'Use Existing Preference (No if not set)',
value: 'CURR',
},
];

export const AMOUNT_PRESETS = [25, 50, 75, 100, 250, 500];
Expand Down
3 changes: 3 additions & 0 deletions bundles/tracker/donation/__tests__/validateDonation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const eventDetails = {
csrfToken: 'testing',
currency: 'USD',
receiverName: 'a beneficiary',
receiverPrivacyPolicy: '',
receiverLogo: '',
receiverSolicitationText: '',
prizesUrl: 'https://example.com/prizes',
donateUrl: 'https://example.com/donate',
minimumDonation: 2.0,
Expand Down
57 changes: 39 additions & 18 deletions bundles/tracker/donation/components/Donate.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { shallowEqual, useSelector } from 'react-redux';
import { useLocation } from 'react-router';

import { useConstants } from '@common/Constants';
import { useCachedCallback } from '@public/hooks/useCachedCallback';
import * as CurrencyUtils from '@public/util/currency';
import Anchor from '@uikit/Anchor';
import Button from '@uikit/Button';
import Checkbox from '@uikit/Checkbox';
import Container from '@uikit/Container';
import CurrencyInput from '@uikit/CurrencyInput';
import ErrorAlert from '@uikit/ErrorAlert';
import Header from '@uikit/Header';
import RadioGroup from '@uikit/RadioGroup';
import Text from '@uikit/Text';
import TextInput from '@uikit/TextInput';

import { Donation } from '@tracker/donation/DonationTypes';
import * as EventDetailsStore from '@tracker/event_details/EventDetailsStore';
import useDispatch from '@tracker/hooks/useDispatch';
import { StoreState } from '@tracker/Store';

import { AnalyticsEvent, track } from '../../analytics/Analytics';
import * as DonationActions from '../DonationActions';
import { AMOUNT_PRESETS, EMAIL_OPTIONS } from '../DonationConstants';
import { AMOUNT_PRESETS } from '../DonationConstants';
import * as DonationStore from '../DonationStore';
import DonationIncentives from './DonationIncentives';
import DonationPrizes from './DonationPrizes';
Expand Down Expand Up @@ -54,6 +55,7 @@ const Donate = (props: DonateProps) => {
commentErrors: DonationStore.getCommentFormErrors(state),
donationValidity: DonationStore.validateDonation(state),
}),
shallowEqual,
);

React.useEffect(() => {
Expand All @@ -66,11 +68,21 @@ const Donate = (props: DonateProps) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [eventId]);

const { currency, receiverName, donateUrl, minimumDonation, maximumDonation, step } = eventDetails;
const {
currency,
receiverSolicitationText,
receiverLogo,
receiverPrivacyPolicy,
receiverName,
donateUrl,
minimumDonation,
maximumDonation,
step,
} = eventDetails;
const { name, email, wantsEmails, amount, comment } = donation;

const updateDonation = React.useCallback(
(fields = {}) => {
(fields: Partial<Donation> = {}) => {
dispatch(DonationActions.updateDonation(fields));
},
[dispatch],
Expand All @@ -84,9 +96,9 @@ const Donate = (props: DonateProps) => {

const updateName = React.useCallback((name: string) => updateDonation({ name }), [updateDonation]);
const updateEmail = React.useCallback((email: string) => updateDonation({ email }), [updateDonation]);
const updateWantsEmails = React.useCallback(
(value: 'CURR' | 'OPTIN' | 'OPTOUT') => updateDonation({ wantsEmails: value }),
[updateDonation],
const toggleWantsEmails = React.useCallback(
() => updateDonation({ wantsEmails: donation.wantsEmails === 'OPTIN' ? 'OPTOUT' : 'OPTIN' }),
[donation.wantsEmails, updateDonation],
);
const updateAmount = React.useCallback((amount: number) => updateDonation({ amount }), [updateDonation]);
const updateAmountPreset = useCachedCallback(
Expand Down Expand Up @@ -135,16 +147,20 @@ const Donate = (props: DonateProps) => {

<ErrorAlert errors={commentErrors.requestedsolicitemail} />

<Text size={Text.Sizes.SIZE_16} marginless>
Do you want to receive emails from {receiverName}?
</Text>

<RadioGroup
className={styles.emailOptin}
options={EMAIL_OPTIONS}
value={wantsEmails}
onChange={updateWantsEmails}
/>
<Checkbox
checked={wantsEmails === 'OPTIN'}
onChange={toggleWantsEmails}
label={
<Text size={Text.Sizes.SIZE_14}>
{receiverSolicitationText || `Check here to receive emails from ${receiverName}`}
</Text>
}>
{receiverPrivacyPolicy && (
<Text size={Text.Sizes.SIZE_12}>
Click <Anchor href={receiverPrivacyPolicy}>here</Anchor> for the privacy policy for {receiverName}
</Text>
)}
</Checkbox>

<ErrorAlert errors={commentErrors.amount} />

Expand Down Expand Up @@ -218,6 +234,11 @@ const Donate = (props: DonateProps) => {
Donate {amount != null ? CurrencyUtils.asCurrency(amount, { currency }) : null}
</Button>
</section>
{receiverLogo && (
<section className={styles.section}>
<img style={{ width: '100%' }} alt={receiverName} src={receiverLogo} />
</section>
)}
</Container>
);
};
Expand Down
6 changes: 6 additions & 0 deletions bundles/tracker/donation/components/DonateInitializer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ type DonateInitializerProps = {
event: {
paypalcurrency: string;
receivername: string;
receiver_solicitation_text: string;
receiver_logo: string;
receiver_privacy_policy: string;
};
step: number;
minimumDonation: number;
Expand Down Expand Up @@ -122,6 +125,9 @@ const DonateInitializer = (props: DonateInitializerProps) => {
csrfToken,
currency: event.paypalcurrency,
receiverName: event.receivername,
receiverLogo: event.receiver_logo,
receiverPrivacyPolicy: event.receiver_privacy_policy,
receiverSolicitationText: event.receiver_solicitation_text,
prizesUrl,
donateUrl,
minimumDonation,
Expand Down
21 changes: 12 additions & 9 deletions bundles/tracker/donation/components/DonationBidForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { shallowEqual, useSelector } from 'react-redux';

import { useCachedCallback } from '@public/hooks/useCachedCallback';
import * as CurrencyUtils from '@public/util/currency';
Expand Down Expand Up @@ -32,14 +32,17 @@ type DonationBidFormProps = {
const DonationBidForm = (props: DonationBidFormProps) => {
const { incentiveId, step, total: donationTotal, className, onSubmit } = props;

const { currency, incentive, bidChoices, donation, bids, allocatedTotal } = useSelector((state: StoreState) => ({
currency: EventDetailsStore.getEventCurrency(state),
incentive: EventDetailsStore.getIncentive(state, incentiveId),
bidChoices: EventDetailsStore.getChildIncentives(state, incentiveId),
donation: DonationStore.getDonation(state),
bids: DonationStore.getBids(state),
allocatedTotal: DonationStore.getAllocatedBidTotal(state),
}));
const { currency, incentive, bidChoices, donation, bids, allocatedTotal } = useSelector(
(state: StoreState) => ({
currency: EventDetailsStore.getEventCurrency(state),
incentive: EventDetailsStore.getIncentive(state, incentiveId),
bidChoices: EventDetailsStore.getChildIncentives(state, incentiveId),
donation: DonationStore.getDonation(state),
bids: DonationStore.getBids(state),
allocatedTotal: DonationStore.getAllocatedBidTotal(state),
}),
shallowEqual,
);

const remainingDonationTotal = donationTotal - allocatedTotal;
const remainingDonationTotalString = CurrencyUtils.asCurrency(remainingDonationTotal, { currency });
Expand Down
15 changes: 9 additions & 6 deletions bundles/tracker/donation/components/DonationBids.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { shallowEqual, useSelector } from 'react-redux';

import { useCachedCallback } from '@public/hooks/useCachedCallback';
import * as CurrencyUtils from '@public/util/currency';
Expand Down Expand Up @@ -72,11 +72,14 @@ const DonationBids = (props: DonationBidsProps) => {
const { className } = props;

const dispatch = useDispatch();
const { bids, incentives, bidErrors } = useSelector((state: StoreState) => ({
bids: DonationStore.getBids(state),
incentives: EventDetailsStore.getIncentivesById(state),
bidErrors: DonationStore.getBidsFormErrors(state),
}));
const { bids, incentives, bidErrors } = useSelector(
(state: StoreState) => ({
bids: DonationStore.getBids(state),
incentives: EventDetailsStore.getIncentivesById(state),
bidErrors: DonationStore.getBidsFormErrors(state),
}),
shallowEqual,
);

const handleDeleteBid = useCachedCallback(
incentiveId => {
Expand Down
15 changes: 9 additions & 6 deletions bundles/tracker/donation/components/DonationIncentives.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { shallowEqual, useSelector } from 'react-redux';

import { useCachedCallback } from '@public/hooks/useCachedCallback';
import Button from '@uikit/Button';
Expand Down Expand Up @@ -37,11 +37,14 @@ const DonationIncentives = (props: DonationIncentivesProps) => {
const [selectedIncentiveId, setSelectedIncentiveId] = React.useState<number | undefined>(undefined);
const [showForm, setShowForm] = React.useState(false);
const setShowFormTrue = React.useCallback(() => setShowForm(true), []);
const { bids, allocatedBidTotal, incentives } = useSelector((state: StoreState) => ({
bids: DonationStore.getBids(state),
allocatedBidTotal: DonationStore.getAllocatedBidTotal(state),
incentives: EventDetailsStore.getTopLevelIncentives(state),
}));
const { bids, allocatedBidTotal, incentives } = useSelector(
(state: StoreState) => ({
bids: DonationStore.getBids(state),
allocatedBidTotal: DonationStore.getAllocatedBidTotal(state),
incentives: EventDetailsStore.getTopLevelIncentives(state),
}),
shallowEqual,
);
const searchResults = searchIncentives(incentives, search);
const canAddBid = total - allocatedBidTotal > 0;

Expand Down
3 changes: 3 additions & 0 deletions bundles/tracker/event_details/EventDetailsReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const initialState: EventDetailsState = {
csrfToken: '',
currency: 'USD', // Default to USD to make tests happy
receiverName: '',
receiverSolicitationText: '',
receiverLogo: '',
receiverPrivacyPolicy: '',
prizesUrl: '',
donateUrl: '',
minimumDonation: 1,
Expand Down
3 changes: 3 additions & 0 deletions bundles/tracker/event_details/EventDetailsTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export type EventDetails = {
csrfToken: string;
currency: string;
receiverName: string;
receiverSolicitationText: string;
receiverLogo: string;
receiverPrivacyPolicy: string;
prizesUrl: string;
donateUrl: string;
minimumDonation: number;
Expand Down
4 changes: 2 additions & 2 deletions bundles/tracker/prizes/components/Prize.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { shallowEqual, useSelector } from 'react-redux';
import { useNavigate } from 'react-router';

import { useConstants } from '@common/Constants';
Expand Down Expand Up @@ -102,7 +102,7 @@ const Prize = (props: PrizeProps) => {
eventId: prize != null ? prize.eventId : undefined,
prize,
};
});
}, shallowEqual);

useEffect(() => {
dispatch(PrizeActions.fetchPrizes({ id: prizeId }));
Expand Down
20 changes: 20 additions & 0 deletions spec/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,23 @@ import './matchers';

const modules = require.context('../bundles', true, /(Spec|\.spec)\.[jt]sx?$/);
modules.keys().forEach(modules);

let consoleLogs = [];

function failTest(...args) {
if (args[1] !== 'ReactNumeric') {
consoleLogs.push([...args]);
}
// eslint-disable-next-line no-console
console.log(...args);
}

beforeEach(() => {
consoleLogs = [];
spyOn(console, 'warn').and.callFake(failTest);
spyOn(console, 'error').and.callFake(failTest);
});

afterEach(() => {
expect(consoleLogs).toEqual([]);
});
32 changes: 21 additions & 11 deletions tests/apiv2/test_events.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
from django.urls import reverse
from rest_framework.test import APIClient

from tracker import settings
from tracker import models, settings
from tracker.api.serializers import EventSerializer

from ..util import APITestCase


class TestEvents(APITestCase):
model_name = 'event'
serializer_class = EventSerializer

def setUp(self):
super().setUp()
self.client = APIClient()
self.client.force_authenticate(user=self.super_user)

def test_event_list(self):
events = [self.blank_event, self.event, self.locked_event]
serialized = self.get_paginated_response(
events, EventSerializer(events, many=True).data
)
response = self.client.get(reverse('tracker:api_v2:event-list'))
self.assertEqual(response.data['results'], serialized.data['results'])
events = models.Event.objects.with_annotations()

with self.saveSnapshot():
data = self.get_list()
self.assertExactV2Models(events, data)

data = self.get_list(data={'totals': ''})
self.assertExactV2Models(
events, data, serializer_kwargs={'with_totals': True}
)

def test_event_detail(self):
response = self.client.get(
reverse('tracker:api_v2:event-detail', args=(self.event.pk,))
)
self.assertEqual(response.data, EventSerializer(self.event).data)
event = models.Event.objects.with_annotations().get(id=self.event.id)
with self.saveSnapshot():
data = self.get_detail(event)
self.assertV2ModelPresent(event, data)

data = self.get_detail(event, data={'totals': ''})
self.assertV2ModelPresent(
event, data, serializer_kwargs={'with_totals': True}
)

def test_nonsense_params(self):
# not specific to events, but good enough
Expand Down
3 changes: 3 additions & 0 deletions tracker/admin/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ class EventAdmin(RelatedUserMixin, CustomModelAdmin):
'hashtag',
'receivername',
'receiver_short',
'receiver_solicitation_text',
'receiver_logo',
'receiver_privacy_policy',
'use_one_step_screening',
'minimumdonation',
'auto_approve_threshold',
Expand Down
3 changes: 3 additions & 0 deletions tracker/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,9 @@ class Meta:
'timezone',
'receivername',
'receiver_short',
'receiver_solicitation_text',
'receiver_logo',
'receiver_privacy_policy',
'use_one_step_screening',
'locked',
'allow_donations',
Expand Down
Loading

0 comments on commit 96f9ff5

Please sign in to comment.