@churchapps/apphelper 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +3 -3
- package/.eslintrc.json +22 -22
- package/LICENSE +21 -21
- package/README.md +24 -24
- package/dist/components/markdownEditor/editor.css +787 -787
- package/dist/components/markdownEditor/images/icons/arrow-clockwise.svg +3 -3
- package/dist/components/markdownEditor/images/icons/arrow-counterclockwise.svg +3 -3
- package/dist/components/markdownEditor/images/icons/chat-square-quote.svg +3 -3
- package/dist/components/markdownEditor/images/icons/chevron-down.svg +2 -2
- package/dist/components/markdownEditor/images/icons/code.svg +2 -2
- package/dist/components/markdownEditor/images/icons/journal-code.svg +4 -4
- package/dist/components/markdownEditor/images/icons/journal-text.svg +4 -4
- package/dist/components/markdownEditor/images/icons/justify.svg +2 -2
- package/dist/components/markdownEditor/images/icons/link.svg +3 -3
- package/dist/components/markdownEditor/images/icons/list-ol.svg +3 -3
- package/dist/components/markdownEditor/images/icons/list-ul.svg +2 -2
- package/dist/components/markdownEditor/images/icons/pencil-fill.svg +2 -2
- package/dist/components/markdownEditor/images/icons/text-center.svg +2 -2
- package/dist/components/markdownEditor/images/icons/text-left.svg +2 -2
- package/dist/components/markdownEditor/images/icons/text-paragraph.svg +2 -2
- package/dist/components/markdownEditor/images/icons/text-right.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-bold.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-h1.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-h2.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-h3.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-h4.svg +12 -12
- package/dist/components/markdownEditor/images/icons/type-italic.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-strikethrough.svg +2 -2
- package/dist/components/markdownEditor/images/icons/type-underline.svg +2 -2
- package/dist/components/markdownEditor/plugins/customLink/FloatingLinkEditor.d.ts.map +1 -1
- package/dist/components/markdownEditor/plugins/customLink/FloatingLinkEditor.js +10 -3
- package/dist/components/markdownEditor/plugins/customLink/FloatingLinkEditor.js.map +1 -1
- package/package.json +88 -88
- package/src/components/CreatePerson.tsx +80 -80
- package/src/components/DisplayBox.tsx +68 -68
- package/src/components/ErrorMessages.tsx +26 -26
- package/src/components/ExportLink.tsx +67 -67
- package/src/components/FloatingSupport.tsx +16 -16
- package/src/components/FormSubmissionEdit.tsx +120 -120
- package/src/components/HelpIcon.tsx +10 -10
- package/src/components/ImageEditor.tsx +126 -126
- package/src/components/InputBox.tsx +73 -73
- package/src/components/Loading.tsx +29 -29
- package/src/components/PersonAdd.tsx +75 -75
- package/src/components/QuestionEdit.tsx +63 -63
- package/src/components/SmallButton.tsx +39 -39
- package/src/components/SupportModal.tsx +26 -26
- package/src/components/TabPanel.tsx +34 -34
- package/src/components/gallery/GalleryModal.tsx +119 -119
- package/src/components/gallery/StockPhotos.tsx +74 -74
- package/src/components/gallery/index.ts +1 -1
- package/src/components/iconPicker/IconNamesList.ts +2240 -2240
- package/src/components/iconPicker/IconPicker.tsx +153 -153
- package/src/components/index.tsx +24 -24
- package/src/components/markdownEditor/Editor.tsx +132 -132
- package/src/components/markdownEditor/MarkdownEditor.tsx +16 -16
- package/src/components/markdownEditor/MarkdownModal.tsx +46 -46
- package/src/components/markdownEditor/MarkdownPreview.tsx +14 -14
- package/src/components/markdownEditor/editor.css +787 -787
- package/src/components/markdownEditor/images/icons/arrow-clockwise.svg +3 -3
- package/src/components/markdownEditor/images/icons/arrow-counterclockwise.svg +3 -3
- package/src/components/markdownEditor/images/icons/chat-square-quote.svg +3 -3
- package/src/components/markdownEditor/images/icons/chevron-down.svg +2 -2
- package/src/components/markdownEditor/images/icons/code.svg +2 -2
- package/src/components/markdownEditor/images/icons/journal-code.svg +4 -4
- package/src/components/markdownEditor/images/icons/journal-text.svg +4 -4
- package/src/components/markdownEditor/images/icons/justify.svg +2 -2
- package/src/components/markdownEditor/images/icons/link.svg +3 -3
- package/src/components/markdownEditor/images/icons/list-ol.svg +3 -3
- package/src/components/markdownEditor/images/icons/list-ul.svg +2 -2
- package/src/components/markdownEditor/images/icons/pencil-fill.svg +2 -2
- package/src/components/markdownEditor/images/icons/text-center.svg +2 -2
- package/src/components/markdownEditor/images/icons/text-left.svg +2 -2
- package/src/components/markdownEditor/images/icons/text-paragraph.svg +2 -2
- package/src/components/markdownEditor/images/icons/text-right.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-bold.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-h1.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-h2.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-h3.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-h4.svg +12 -12
- package/src/components/markdownEditor/images/icons/type-italic.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-strikethrough.svg +2 -2
- package/src/components/markdownEditor/images/icons/type-underline.svg +2 -2
- package/src/components/markdownEditor/index.ts +2 -2
- package/src/components/markdownEditor/plugins/AutoLinkPlugin.tsx +35 -35
- package/src/components/markdownEditor/plugins/ControlledEditorPlugin.tsx +24 -24
- package/src/components/markdownEditor/plugins/ListMaxIndentLevelPlugin.tsx +68 -68
- package/src/components/markdownEditor/plugins/MarkdownTransformers.ts +106 -106
- package/src/components/markdownEditor/plugins/ReadOnlyPlugin.tsx +15 -15
- package/src/components/markdownEditor/plugins/ToolbarPlugin.tsx +401 -401
- package/src/components/markdownEditor/plugins/customLink/CustomLinkNode.tsx +224 -224
- package/src/components/markdownEditor/plugins/customLink/CustomLinkNodePlugin.tsx +32 -32
- package/src/components/markdownEditor/plugins/customLink/CustomLinkNodeTransformer.tsx +102 -102
- package/src/components/markdownEditor/plugins/customLink/FloatingLinkEditor.tsx +252 -243
- package/src/components/markdownEditor/plugins/customLink/FloatingLinkEditor.types.ts +11 -11
- package/src/components/markdownEditor/plugins/emoji/EmojiNode.tsx +95 -95
- package/src/components/markdownEditor/plugins/emoji/EmojiNodeTransform.ts +41 -41
- package/src/components/markdownEditor/plugins/emoji/EmojiPickerPlugin.tsx +152 -152
- package/src/components/markdownEditor/plugins/emoji/EmojisPlugin.tsx +65 -65
- package/src/components/markdownEditor/plugins/index.ts +6 -6
- package/src/components/markdownEditor/theme.ts +65 -65
- package/src/components/notes/AddNote.tsx +90 -90
- package/src/components/notes/Conversation.tsx +82 -82
- package/src/components/notes/Conversations.tsx +58 -58
- package/src/components/notes/NewConversation.tsx +78 -78
- package/src/components/notes/Note.tsx +44 -44
- package/src/components/notes/Notes.tsx +69 -69
- package/src/components/notes/index.ts +5 -5
- package/src/components/reporting/ChartReport.tsx +98 -98
- package/src/components/reporting/ReportFilter.tsx +54 -54
- package/src/components/reporting/ReportFilterField.tsx +160 -160
- package/src/components/reporting/ReportOutput.tsx +79 -79
- package/src/components/reporting/ReportWithFilter.tsx +70 -70
- package/src/components/reporting/TableReport.tsx +57 -57
- package/src/components/reporting/TreeReport.tsx +111 -111
- package/src/components/reporting/index.ts +4 -4
- package/src/components/wrapper/AppList.tsx +20 -20
- package/src/components/wrapper/ChurchList.tsx +22 -22
- package/src/components/wrapper/Drawers.tsx +60 -60
- package/src/components/wrapper/NavItem.tsx +41 -41
- package/src/components/wrapper/NewPrivateMessage.tsx +103 -103
- package/src/components/wrapper/NotificationMenu.tsx +96 -96
- package/src/components/wrapper/Notifications.tsx +53 -53
- package/src/components/wrapper/PrivateMessageDetails.tsx +24 -24
- package/src/components/wrapper/PrivateMessages.tsx +92 -92
- package/src/components/wrapper/SiteWrapper.tsx +99 -99
- package/src/components/wrapper/TabPanel.tsx +30 -30
- package/src/components/wrapper/UserMenu.tsx +106 -106
- package/src/components/wrapper/index.tsx +5 -5
- package/src/donationComponents/DonationPage.tsx +136 -136
- package/src/donationComponents/components/BankForm.tsx +159 -159
- package/src/donationComponents/components/CardForm.tsx +104 -104
- package/src/donationComponents/components/DonationForm.tsx +235 -235
- package/src/donationComponents/components/FundDonation.tsx +49 -49
- package/src/donationComponents/components/FundDonations.tsx +39 -39
- package/src/donationComponents/components/NonAuthDonation.tsx +31 -31
- package/src/donationComponents/components/NonAuthDonationInner.tsx +259 -259
- package/src/donationComponents/components/PaymentMethods.tsx +135 -135
- package/src/donationComponents/components/RecurringDonations.tsx +121 -121
- package/src/donationComponents/components/RecurringDonationsEdit.tsx +93 -93
- package/src/donationComponents/components/index.tsx +9 -9
- package/src/donationComponents/index.ts +3 -3
- package/src/donationComponents/modals/DonationPreviewModal.tsx +66 -66
- package/src/helpers/AnalyticsHelper.ts +33 -33
- package/src/helpers/ApiHelper.ts +125 -125
- package/src/helpers/AppearanceHelper.ts +69 -69
- package/src/helpers/ArrayHelper.ts +81 -81
- package/src/helpers/CommonEnvironmentHelper.ts +80 -80
- package/src/helpers/CurrencyHelper.ts +10 -10
- package/src/helpers/DateHelper.ts +109 -109
- package/src/helpers/DonationHelper.ts +26 -26
- package/src/helpers/ErrorHelper.ts +36 -36
- package/src/helpers/EventHelper.ts +52 -52
- package/src/helpers/FileHelper.ts +31 -31
- package/src/helpers/PersonHelper.ts +60 -60
- package/src/helpers/SlugHelper.ts +37 -37
- package/src/helpers/SocketHelper.ts +78 -78
- package/src/helpers/Themes.ts +14 -14
- package/src/helpers/UniqueIdHelper.ts +36 -36
- package/src/helpers/UserHelper.ts +59 -59
- package/src/helpers/createEmotionCache.ts +17 -17
- package/src/helpers/index.ts +19 -19
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useMountedState.ts +16 -16
- package/src/index.ts +6 -6
- package/src/interfaces/Access.ts +24 -24
- package/src/interfaces/Attendance.ts +8 -8
- package/src/interfaces/Content.ts +10 -10
- package/src/interfaces/Doing.ts +24 -24
- package/src/interfaces/Donation.ts +45 -45
- package/src/interfaces/Error.ts +17 -17
- package/src/interfaces/Membership.ts +51 -51
- package/src/interfaces/Messaging.ts +11 -11
- package/src/interfaces/Permissions.ts +68 -68
- package/src/interfaces/Reporting.ts +7 -7
- package/src/interfaces/UserContextInterface.ts +13 -13
- package/src/interfaces/index.ts +13 -13
- package/src/pageComponents/LoginPage.tsx +244 -244
- package/src/pageComponents/LogoutPage.tsx +28 -28
- package/src/pageComponents/components/Forgot.tsx +79 -79
- package/src/pageComponents/components/Login.tsx +54 -54
- package/src/pageComponents/components/LoginSetPassword.tsx +63 -63
- package/src/pageComponents/components/Register.tsx +107 -107
- package/src/pageComponents/components/SelectChurchModal.tsx +41 -41
- package/src/pageComponents/components/SelectChurchRegister.tsx +88 -88
- package/src/pageComponents/components/SelectChurchSearch.tsx +69 -69
- package/src/pageComponents/components/SelectableChurch.tsx +38 -38
- package/src/pageComponents/index.ts +3 -3
- package/tsconfig.json +34 -34
- package/tslint.json +14 -14
|
@@ -1,259 +1,259 @@
|
|
|
1
|
-
import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
2
|
-
import React, { useState, useRef } from "react";
|
|
3
|
-
import ReCAPTCHA from "react-google-recaptcha";
|
|
4
|
-
import { ErrorMessages, InputBox } from "../../components";
|
|
5
|
-
import { ApiHelper, DateHelper, CurrencyHelper } from "../../helpers";
|
|
6
|
-
import { FundDonationInterface, FundInterface, PersonInterface, StripeDonationInterface, StripePaymentMethod, UserInterface, ChurchInterface } from "../../interfaces";
|
|
7
|
-
import { FundDonations } from "./FundDonations";
|
|
8
|
-
import { Grid, Alert, TextField, Button, FormControl, InputLabel, Select, MenuItem, PaperProps, FormGroup, FormControlLabel, Checkbox, Typography } from "@mui/material"
|
|
9
|
-
import { DonationHelper } from "../../helpers/DonationHelper";
|
|
10
|
-
|
|
11
|
-
interface Props { churchId: string, mainContainerCssProps?: PaperProps, showHeader?: boolean, recaptchaSiteKey: string, churchLogo?: string }
|
|
12
|
-
|
|
13
|
-
export const NonAuthDonationInner: React.FC<Props> = ({ mainContainerCssProps, showHeader = true, ...props }) => {
|
|
14
|
-
const stripe = useStripe();
|
|
15
|
-
const elements = useElements();
|
|
16
|
-
const formStyling = { style: { base: { fontSize: "18px" } } };
|
|
17
|
-
const [firstName, setFirstName] = React.useState("");
|
|
18
|
-
const [lastName, setLastName] = React.useState("");
|
|
19
|
-
const [email, setEmail] = React.useState("");
|
|
20
|
-
const [fundsTotal, setFundsTotal] = React.useState<number>(0);
|
|
21
|
-
const [transactionFee, setTransactionFee] = React.useState<number>(0);
|
|
22
|
-
const [total, setTotal] = React.useState<number>(0);
|
|
23
|
-
const [errors, setErrors] = React.useState([]);
|
|
24
|
-
const [fundDonations, setFundDonations] = React.useState<FundDonationInterface[]>([]);
|
|
25
|
-
const [funds, setFunds] = React.useState<FundInterface[]>([]);
|
|
26
|
-
const [donationComplete, setDonationComplete] = React.useState(false);
|
|
27
|
-
const [processing, setProcessing] = React.useState(false);
|
|
28
|
-
const [donationType, setDonationType] = useState<"once" | "recurring">("once");
|
|
29
|
-
const [interval, setInterval] = useState("one_month");
|
|
30
|
-
const [startDate, setStartDate] = useState(new Date().toDateString());
|
|
31
|
-
const [captchaResponse, setCaptchaResponse] = useState("");
|
|
32
|
-
const [church, setChurch] = useState<ChurchInterface>();
|
|
33
|
-
const [gateway, setGateway] = React.useState(null);
|
|
34
|
-
const captchaRef = useRef(null);
|
|
35
|
-
|
|
36
|
-
const init = () => {
|
|
37
|
-
ApiHelper.get("/funds/churchId/" + props.churchId, "GivingApi").then(data => {
|
|
38
|
-
setFunds(data);
|
|
39
|
-
if (data.length) setFundDonations([{ fundId: data[0].id }]);
|
|
40
|
-
});
|
|
41
|
-
ApiHelper.get("/churches/" + props.churchId, "MembershipApi").then(data => {
|
|
42
|
-
setChurch(data);
|
|
43
|
-
});
|
|
44
|
-
ApiHelper.get("/gateways/churchId/" + props.churchId, "GivingApi").then(data => {
|
|
45
|
-
if (data.length !== 0) setGateway(data[0]);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const handleCaptchaChange = (value: string) => {
|
|
50
|
-
const captchaToken = captchaRef.current.getValue();
|
|
51
|
-
ApiHelper.postAnonymous("/donate/captcha-verify", { token: captchaToken }, "GivingApi").then((data) => { setCaptchaResponse(data.response); })
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const handleCheckChange = (e: React.SyntheticEvent<Element, Event>, checked: boolean) => {
|
|
55
|
-
let totalPayAmount = checked ? fundsTotal + transactionFee : fundsTotal;
|
|
56
|
-
setTotal(totalPayAmount);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const handleAutoPayFee = () => {
|
|
60
|
-
let totalPayAmount = fundsTotal + transactionFee;
|
|
61
|
-
setTotal(totalPayAmount);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const handleSave = async () => {
|
|
65
|
-
if (validate()) {
|
|
66
|
-
setProcessing(true);
|
|
67
|
-
ApiHelper.post("/users/loadOrCreate", { userEmail: email, firstName, lastName }, "MembershipApi")
|
|
68
|
-
.catch(ex => { setErrors([ex.toString()]); setProcessing(false); })
|
|
69
|
-
.then(async userData => {
|
|
70
|
-
const personData = { churchId: props.churchId, firstName, lastName, email };
|
|
71
|
-
const person = await ApiHelper.post("/people/loadOrCreate", personData, "MembershipApi")
|
|
72
|
-
saveCard(userData, person)
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const saveCard = async (user: UserInterface, person: PersonInterface) => {
|
|
78
|
-
const cardData = elements.getElement(CardElement);
|
|
79
|
-
const stripePM = await stripe.createPaymentMethod({ type: "card", card: cardData });
|
|
80
|
-
if (stripePM.error) { setErrors([stripePM.error.message]); setProcessing(false); }
|
|
81
|
-
else {
|
|
82
|
-
const pm = { id: stripePM.paymentMethod.id, personId: person.id, email: email, name: person.name.display, churchId: props.churchId }
|
|
83
|
-
await ApiHelper.post("/paymentmethods/addcard", pm, "GivingApi").then(result => {
|
|
84
|
-
if (result?.raw?.message) {
|
|
85
|
-
setErrors([result.raw.message]);
|
|
86
|
-
setProcessing(false);
|
|
87
|
-
} else {
|
|
88
|
-
const d: { paymentMethod: StripePaymentMethod, customerId: string } = result;
|
|
89
|
-
saveDonation(d.paymentMethod, d.customerId, person);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const saveDonation = async (paymentMethod: StripePaymentMethod, customerId: string, person?: PersonInterface) => {
|
|
96
|
-
let donation: StripeDonationInterface = {
|
|
97
|
-
amount: total,
|
|
98
|
-
id: paymentMethod.id,
|
|
99
|
-
customerId: customerId,
|
|
100
|
-
type: paymentMethod.type,
|
|
101
|
-
churchId: props.churchId,
|
|
102
|
-
funds: [],
|
|
103
|
-
person: {
|
|
104
|
-
id: person?.id,
|
|
105
|
-
email: person?.contactInfo?.email,
|
|
106
|
-
name: person?.name?.display
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (donationType === "recurring") {
|
|
111
|
-
donation.billing_cycle_anchor = + new Date(startDate);
|
|
112
|
-
donation.interval = DonationHelper.getInterval(interval);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
for (const fundDonation of fundDonations) {
|
|
116
|
-
let fund = funds.find((fund: FundInterface) => fund.id === fundDonation.fundId);
|
|
117
|
-
donation.funds.push({ id: fundDonation.fundId, amount: fundDonation.amount || 0, name: fund.name });
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const churchObj = {
|
|
121
|
-
name: church.name,
|
|
122
|
-
subDomain: church.subDomain,
|
|
123
|
-
churchURL: typeof window !== "undefined" && window.location.origin,
|
|
124
|
-
logo: props?.churchLogo
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
let results;
|
|
128
|
-
if (donationType === "once") results = await ApiHelper.post("/donate/charge/", { ...donation, church: churchObj }, "GivingApi");
|
|
129
|
-
if (donationType === "recurring") results = await ApiHelper.post("/donate/subscribe/", { ...donation, church: churchObj }, "GivingApi");
|
|
130
|
-
|
|
131
|
-
if (results?.status === "succeeded" || results?.status === "pending" || results?.status === "active") {
|
|
132
|
-
setDonationComplete(true)
|
|
133
|
-
}
|
|
134
|
-
if (results?.raw?.message) {
|
|
135
|
-
setErrors([results?.raw?.message]);
|
|
136
|
-
setProcessing(false);
|
|
137
|
-
}
|
|
138
|
-
setProcessing(false);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const validate = () => {
|
|
142
|
-
const result = [];
|
|
143
|
-
if (!firstName) result.push("Please enter your first name.");
|
|
144
|
-
if (!lastName) result.push("Please enter your last name.");
|
|
145
|
-
if (!email) result.push("Please enter your email address.");
|
|
146
|
-
if (fundsTotal === 0) result.push("Amount cannot be $0");
|
|
147
|
-
if (result.length === 0) {
|
|
148
|
-
if (!email.match(/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/)) result.push("Please enter a valid email address"); //eslint-disable-line
|
|
149
|
-
}
|
|
150
|
-
//Todo - make sure the account doesn't exist. (loadOrCreate?)
|
|
151
|
-
setErrors(result);
|
|
152
|
-
return result.length === 0;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
|
156
|
-
const val = e.currentTarget.value;
|
|
157
|
-
switch (e.currentTarget.name) {
|
|
158
|
-
case "firstName": setFirstName(val); break;
|
|
159
|
-
case "lastName": setLastName(val); break;
|
|
160
|
-
case "email": setEmail(val); break;
|
|
161
|
-
case "startDate": setStartDate(val); break;
|
|
162
|
-
case "interval": setInterval(val); break;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const handleFundDonationsChange = (fd: FundDonationInterface[]) => {
|
|
167
|
-
setFundDonations(fd);
|
|
168
|
-
let totalAmount = 0;
|
|
169
|
-
let selectedFunds: any = [];
|
|
170
|
-
for (const fundDonation of fd) {
|
|
171
|
-
totalAmount += fundDonation.amount || 0;
|
|
172
|
-
let fund = funds.find((fund: FundInterface) => fund.id === fundDonation.fundId);
|
|
173
|
-
selectedFunds.push({ id: fundDonation.fundId, amount: fundDonation.amount || 0, name: fund.name });
|
|
174
|
-
}
|
|
175
|
-
setFundsTotal(totalAmount);
|
|
176
|
-
setTotal(totalAmount);
|
|
177
|
-
setTransactionFee(getTransactionFee(totalAmount));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const getTransactionFee = (amount: number) => {
|
|
181
|
-
const fixedFee = 0.30;
|
|
182
|
-
const fixedPercent = 0.029;
|
|
183
|
-
return Math.round(((amount + fixedFee) / (1 - fixedPercent) - amount) * 100) / 100;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const getFundList = () => {
|
|
187
|
-
if (funds) return (<>
|
|
188
|
-
<hr />
|
|
189
|
-
<h4>Funds</h4>
|
|
190
|
-
<FundDonations fundDonations={fundDonations} funds={funds} updatedFunction={handleFundDonationsChange} />
|
|
191
|
-
</>);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
React.useEffect(init, []); //eslint-disable-line
|
|
195
|
-
|
|
196
|
-
React.useEffect(() => { gateway && gateway.payFees === true && handleAutoPayFee() }, [fundDonations]);
|
|
197
|
-
|
|
198
|
-
if (donationComplete) return <Alert severity="success">Thank you for your donation.</Alert>
|
|
199
|
-
else return (
|
|
200
|
-
<InputBox headerIcon={showHeader ? "volunteer_activism" : ""} headerText={showHeader ? "Donate" : ""} saveFunction={handleSave} saveText="Donate" isSubmitting={processing || !captchaResponse || captchaResponse === "robot"} mainContainerCssProps={mainContainerCssProps}>
|
|
201
|
-
<ErrorMessages errors={errors} />
|
|
202
|
-
<Grid container spacing={3}>
|
|
203
|
-
<Grid item md={6} xs={12}>
|
|
204
|
-
<Button aria-label="single-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "once" ? "contained" : "outlined"} onClick={() => setDonationType("once")}>Make a Donation</Button>
|
|
205
|
-
</Grid>
|
|
206
|
-
<Grid item md={6} xs={12}>
|
|
207
|
-
<Button aria-label="recurring-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "recurring" ? "contained" : "outlined"} onClick={() => setDonationType("recurring")}>Make a Recurring Donation</Button>
|
|
208
|
-
</Grid>
|
|
209
|
-
<Grid item md={6} xs={12}>
|
|
210
|
-
<TextField fullWidth label="First Name" name="firstName" value={firstName} onChange={handleChange} />
|
|
211
|
-
</Grid>
|
|
212
|
-
<Grid item md={6} xs={12}>
|
|
213
|
-
<TextField fullWidth label="Last Name" name="lastName" value={lastName} onChange={handleChange} />
|
|
214
|
-
</Grid>
|
|
215
|
-
<Grid item md={6} xs={12}>
|
|
216
|
-
<TextField fullWidth label="Email" name="email" value={email} onChange={handleChange} />
|
|
217
|
-
</Grid>
|
|
218
|
-
<Grid item md={6} xs={12}>
|
|
219
|
-
<ReCAPTCHA sitekey={props.recaptchaSiteKey} ref={captchaRef} onChange={handleCaptchaChange} />
|
|
220
|
-
</Grid>
|
|
221
|
-
</Grid>
|
|
222
|
-
<div style={{ padding: 10, border: "1px solid #CCC", borderRadius: 5, marginTop: 10 }}>
|
|
223
|
-
<CardElement options={formStyling} />
|
|
224
|
-
</div>
|
|
225
|
-
{donationType === "recurring"
|
|
226
|
-
&& <Grid container spacing={3} style={{marginTop:0}}>
|
|
227
|
-
<Grid item md={6} xs={12}>
|
|
228
|
-
<FormControl fullWidth>
|
|
229
|
-
<InputLabel>Frequency</InputLabel>
|
|
230
|
-
<Select label="Frequency" name="interval" aria-label="interval" value={interval} onChange={(e) => {setInterval(e.target.value) }}>
|
|
231
|
-
<MenuItem value="one_week">Weekly</MenuItem>
|
|
232
|
-
<MenuItem value="two_week">Bi-Weekly</MenuItem>
|
|
233
|
-
<MenuItem value="one_month">Monthly</MenuItem>
|
|
234
|
-
<MenuItem value="three_month">Quarterly</MenuItem>
|
|
235
|
-
<MenuItem value="one_year">Annually</MenuItem>
|
|
236
|
-
</Select>
|
|
237
|
-
</FormControl>
|
|
238
|
-
</Grid>
|
|
239
|
-
<Grid item md={6} xs={12}>
|
|
240
|
-
<TextField fullWidth name="startDate" type="date" aria-label="startDate" label="Start Date" value={DateHelper.formatHtml5Date(new Date(startDate))} onChange={handleChange} />
|
|
241
|
-
</Grid>
|
|
242
|
-
</Grid>
|
|
243
|
-
}
|
|
244
|
-
{getFundList()}
|
|
245
|
-
<div>
|
|
246
|
-
{fundsTotal > 0 &&
|
|
247
|
-
<>
|
|
248
|
-
{(gateway && gateway.payFees === true) ? <Typography fontSize={14} fontStyle="italic">*Transaction fees of {CurrencyHelper.formatCurrency(transactionFee)} are applied.</Typography> : (
|
|
249
|
-
<FormGroup>
|
|
250
|
-
<FormControlLabel control={<Checkbox />} name="transaction-fee" label={`I'll generously add ${CurrencyHelper.formatCurrency(transactionFee)} to cover the transaction fees so you can keep 100% of my donation.`} onChange={handleCheckChange} />
|
|
251
|
-
</FormGroup>
|
|
252
|
-
)}
|
|
253
|
-
<p>Total Donation Amount: ${total}</p>
|
|
254
|
-
</>
|
|
255
|
-
}
|
|
256
|
-
</div>
|
|
257
|
-
</InputBox>
|
|
258
|
-
);
|
|
259
|
-
}
|
|
1
|
+
import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
2
|
+
import React, { useState, useRef } from "react";
|
|
3
|
+
import ReCAPTCHA from "react-google-recaptcha";
|
|
4
|
+
import { ErrorMessages, InputBox } from "../../components";
|
|
5
|
+
import { ApiHelper, DateHelper, CurrencyHelper } from "../../helpers";
|
|
6
|
+
import { FundDonationInterface, FundInterface, PersonInterface, StripeDonationInterface, StripePaymentMethod, UserInterface, ChurchInterface } from "../../interfaces";
|
|
7
|
+
import { FundDonations } from "./FundDonations";
|
|
8
|
+
import { Grid, Alert, TextField, Button, FormControl, InputLabel, Select, MenuItem, PaperProps, FormGroup, FormControlLabel, Checkbox, Typography } from "@mui/material"
|
|
9
|
+
import { DonationHelper } from "../../helpers/DonationHelper";
|
|
10
|
+
|
|
11
|
+
interface Props { churchId: string, mainContainerCssProps?: PaperProps, showHeader?: boolean, recaptchaSiteKey: string, churchLogo?: string }
|
|
12
|
+
|
|
13
|
+
export const NonAuthDonationInner: React.FC<Props> = ({ mainContainerCssProps, showHeader = true, ...props }) => {
|
|
14
|
+
const stripe = useStripe();
|
|
15
|
+
const elements = useElements();
|
|
16
|
+
const formStyling = { style: { base: { fontSize: "18px" } } };
|
|
17
|
+
const [firstName, setFirstName] = React.useState("");
|
|
18
|
+
const [lastName, setLastName] = React.useState("");
|
|
19
|
+
const [email, setEmail] = React.useState("");
|
|
20
|
+
const [fundsTotal, setFundsTotal] = React.useState<number>(0);
|
|
21
|
+
const [transactionFee, setTransactionFee] = React.useState<number>(0);
|
|
22
|
+
const [total, setTotal] = React.useState<number>(0);
|
|
23
|
+
const [errors, setErrors] = React.useState([]);
|
|
24
|
+
const [fundDonations, setFundDonations] = React.useState<FundDonationInterface[]>([]);
|
|
25
|
+
const [funds, setFunds] = React.useState<FundInterface[]>([]);
|
|
26
|
+
const [donationComplete, setDonationComplete] = React.useState(false);
|
|
27
|
+
const [processing, setProcessing] = React.useState(false);
|
|
28
|
+
const [donationType, setDonationType] = useState<"once" | "recurring">("once");
|
|
29
|
+
const [interval, setInterval] = useState("one_month");
|
|
30
|
+
const [startDate, setStartDate] = useState(new Date().toDateString());
|
|
31
|
+
const [captchaResponse, setCaptchaResponse] = useState("");
|
|
32
|
+
const [church, setChurch] = useState<ChurchInterface>();
|
|
33
|
+
const [gateway, setGateway] = React.useState(null);
|
|
34
|
+
const captchaRef = useRef(null);
|
|
35
|
+
|
|
36
|
+
const init = () => {
|
|
37
|
+
ApiHelper.get("/funds/churchId/" + props.churchId, "GivingApi").then(data => {
|
|
38
|
+
setFunds(data);
|
|
39
|
+
if (data.length) setFundDonations([{ fundId: data[0].id }]);
|
|
40
|
+
});
|
|
41
|
+
ApiHelper.get("/churches/" + props.churchId, "MembershipApi").then(data => {
|
|
42
|
+
setChurch(data);
|
|
43
|
+
});
|
|
44
|
+
ApiHelper.get("/gateways/churchId/" + props.churchId, "GivingApi").then(data => {
|
|
45
|
+
if (data.length !== 0) setGateway(data[0]);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleCaptchaChange = (value: string) => {
|
|
50
|
+
const captchaToken = captchaRef.current.getValue();
|
|
51
|
+
ApiHelper.postAnonymous("/donate/captcha-verify", { token: captchaToken }, "GivingApi").then((data) => { setCaptchaResponse(data.response); })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const handleCheckChange = (e: React.SyntheticEvent<Element, Event>, checked: boolean) => {
|
|
55
|
+
let totalPayAmount = checked ? fundsTotal + transactionFee : fundsTotal;
|
|
56
|
+
setTotal(totalPayAmount);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const handleAutoPayFee = () => {
|
|
60
|
+
let totalPayAmount = fundsTotal + transactionFee;
|
|
61
|
+
setTotal(totalPayAmount);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handleSave = async () => {
|
|
65
|
+
if (validate()) {
|
|
66
|
+
setProcessing(true);
|
|
67
|
+
ApiHelper.post("/users/loadOrCreate", { userEmail: email, firstName, lastName }, "MembershipApi")
|
|
68
|
+
.catch(ex => { setErrors([ex.toString()]); setProcessing(false); })
|
|
69
|
+
.then(async userData => {
|
|
70
|
+
const personData = { churchId: props.churchId, firstName, lastName, email };
|
|
71
|
+
const person = await ApiHelper.post("/people/loadOrCreate", personData, "MembershipApi")
|
|
72
|
+
saveCard(userData, person)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const saveCard = async (user: UserInterface, person: PersonInterface) => {
|
|
78
|
+
const cardData = elements.getElement(CardElement);
|
|
79
|
+
const stripePM = await stripe.createPaymentMethod({ type: "card", card: cardData });
|
|
80
|
+
if (stripePM.error) { setErrors([stripePM.error.message]); setProcessing(false); }
|
|
81
|
+
else {
|
|
82
|
+
const pm = { id: stripePM.paymentMethod.id, personId: person.id, email: email, name: person.name.display, churchId: props.churchId }
|
|
83
|
+
await ApiHelper.post("/paymentmethods/addcard", pm, "GivingApi").then(result => {
|
|
84
|
+
if (result?.raw?.message) {
|
|
85
|
+
setErrors([result.raw.message]);
|
|
86
|
+
setProcessing(false);
|
|
87
|
+
} else {
|
|
88
|
+
const d: { paymentMethod: StripePaymentMethod, customerId: string } = result;
|
|
89
|
+
saveDonation(d.paymentMethod, d.customerId, person);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const saveDonation = async (paymentMethod: StripePaymentMethod, customerId: string, person?: PersonInterface) => {
|
|
96
|
+
let donation: StripeDonationInterface = {
|
|
97
|
+
amount: total,
|
|
98
|
+
id: paymentMethod.id,
|
|
99
|
+
customerId: customerId,
|
|
100
|
+
type: paymentMethod.type,
|
|
101
|
+
churchId: props.churchId,
|
|
102
|
+
funds: [],
|
|
103
|
+
person: {
|
|
104
|
+
id: person?.id,
|
|
105
|
+
email: person?.contactInfo?.email,
|
|
106
|
+
name: person?.name?.display
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (donationType === "recurring") {
|
|
111
|
+
donation.billing_cycle_anchor = + new Date(startDate);
|
|
112
|
+
donation.interval = DonationHelper.getInterval(interval);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const fundDonation of fundDonations) {
|
|
116
|
+
let fund = funds.find((fund: FundInterface) => fund.id === fundDonation.fundId);
|
|
117
|
+
donation.funds.push({ id: fundDonation.fundId, amount: fundDonation.amount || 0, name: fund.name });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const churchObj = {
|
|
121
|
+
name: church.name,
|
|
122
|
+
subDomain: church.subDomain,
|
|
123
|
+
churchURL: typeof window !== "undefined" && window.location.origin,
|
|
124
|
+
logo: props?.churchLogo
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let results;
|
|
128
|
+
if (donationType === "once") results = await ApiHelper.post("/donate/charge/", { ...donation, church: churchObj }, "GivingApi");
|
|
129
|
+
if (donationType === "recurring") results = await ApiHelper.post("/donate/subscribe/", { ...donation, church: churchObj }, "GivingApi");
|
|
130
|
+
|
|
131
|
+
if (results?.status === "succeeded" || results?.status === "pending" || results?.status === "active") {
|
|
132
|
+
setDonationComplete(true)
|
|
133
|
+
}
|
|
134
|
+
if (results?.raw?.message) {
|
|
135
|
+
setErrors([results?.raw?.message]);
|
|
136
|
+
setProcessing(false);
|
|
137
|
+
}
|
|
138
|
+
setProcessing(false);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const validate = () => {
|
|
142
|
+
const result = [];
|
|
143
|
+
if (!firstName) result.push("Please enter your first name.");
|
|
144
|
+
if (!lastName) result.push("Please enter your last name.");
|
|
145
|
+
if (!email) result.push("Please enter your email address.");
|
|
146
|
+
if (fundsTotal === 0) result.push("Amount cannot be $0");
|
|
147
|
+
if (result.length === 0) {
|
|
148
|
+
if (!email.match(/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/)) result.push("Please enter a valid email address"); //eslint-disable-line
|
|
149
|
+
}
|
|
150
|
+
//Todo - make sure the account doesn't exist. (loadOrCreate?)
|
|
151
|
+
setErrors(result);
|
|
152
|
+
return result.length === 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
|
156
|
+
const val = e.currentTarget.value;
|
|
157
|
+
switch (e.currentTarget.name) {
|
|
158
|
+
case "firstName": setFirstName(val); break;
|
|
159
|
+
case "lastName": setLastName(val); break;
|
|
160
|
+
case "email": setEmail(val); break;
|
|
161
|
+
case "startDate": setStartDate(val); break;
|
|
162
|
+
case "interval": setInterval(val); break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const handleFundDonationsChange = (fd: FundDonationInterface[]) => {
|
|
167
|
+
setFundDonations(fd);
|
|
168
|
+
let totalAmount = 0;
|
|
169
|
+
let selectedFunds: any = [];
|
|
170
|
+
for (const fundDonation of fd) {
|
|
171
|
+
totalAmount += fundDonation.amount || 0;
|
|
172
|
+
let fund = funds.find((fund: FundInterface) => fund.id === fundDonation.fundId);
|
|
173
|
+
selectedFunds.push({ id: fundDonation.fundId, amount: fundDonation.amount || 0, name: fund.name });
|
|
174
|
+
}
|
|
175
|
+
setFundsTotal(totalAmount);
|
|
176
|
+
setTotal(totalAmount);
|
|
177
|
+
setTransactionFee(getTransactionFee(totalAmount));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const getTransactionFee = (amount: number) => {
|
|
181
|
+
const fixedFee = 0.30;
|
|
182
|
+
const fixedPercent = 0.029;
|
|
183
|
+
return Math.round(((amount + fixedFee) / (1 - fixedPercent) - amount) * 100) / 100;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const getFundList = () => {
|
|
187
|
+
if (funds) return (<>
|
|
188
|
+
<hr />
|
|
189
|
+
<h4>Funds</h4>
|
|
190
|
+
<FundDonations fundDonations={fundDonations} funds={funds} updatedFunction={handleFundDonationsChange} />
|
|
191
|
+
</>);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
React.useEffect(init, []); //eslint-disable-line
|
|
195
|
+
|
|
196
|
+
React.useEffect(() => { gateway && gateway.payFees === true && handleAutoPayFee() }, [fundDonations]);
|
|
197
|
+
|
|
198
|
+
if (donationComplete) return <Alert severity="success">Thank you for your donation.</Alert>
|
|
199
|
+
else return (
|
|
200
|
+
<InputBox headerIcon={showHeader ? "volunteer_activism" : ""} headerText={showHeader ? "Donate" : ""} saveFunction={handleSave} saveText="Donate" isSubmitting={processing || !captchaResponse || captchaResponse === "robot"} mainContainerCssProps={mainContainerCssProps}>
|
|
201
|
+
<ErrorMessages errors={errors} />
|
|
202
|
+
<Grid container spacing={3}>
|
|
203
|
+
<Grid item md={6} xs={12}>
|
|
204
|
+
<Button aria-label="single-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "once" ? "contained" : "outlined"} onClick={() => setDonationType("once")}>Make a Donation</Button>
|
|
205
|
+
</Grid>
|
|
206
|
+
<Grid item md={6} xs={12}>
|
|
207
|
+
<Button aria-label="recurring-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "recurring" ? "contained" : "outlined"} onClick={() => setDonationType("recurring")}>Make a Recurring Donation</Button>
|
|
208
|
+
</Grid>
|
|
209
|
+
<Grid item md={6} xs={12}>
|
|
210
|
+
<TextField fullWidth label="First Name" name="firstName" value={firstName} onChange={handleChange} />
|
|
211
|
+
</Grid>
|
|
212
|
+
<Grid item md={6} xs={12}>
|
|
213
|
+
<TextField fullWidth label="Last Name" name="lastName" value={lastName} onChange={handleChange} />
|
|
214
|
+
</Grid>
|
|
215
|
+
<Grid item md={6} xs={12}>
|
|
216
|
+
<TextField fullWidth label="Email" name="email" value={email} onChange={handleChange} />
|
|
217
|
+
</Grid>
|
|
218
|
+
<Grid item md={6} xs={12}>
|
|
219
|
+
<ReCAPTCHA sitekey={props.recaptchaSiteKey} ref={captchaRef} onChange={handleCaptchaChange} />
|
|
220
|
+
</Grid>
|
|
221
|
+
</Grid>
|
|
222
|
+
<div style={{ padding: 10, border: "1px solid #CCC", borderRadius: 5, marginTop: 10 }}>
|
|
223
|
+
<CardElement options={formStyling} />
|
|
224
|
+
</div>
|
|
225
|
+
{donationType === "recurring"
|
|
226
|
+
&& <Grid container spacing={3} style={{marginTop:0}}>
|
|
227
|
+
<Grid item md={6} xs={12}>
|
|
228
|
+
<FormControl fullWidth>
|
|
229
|
+
<InputLabel>Frequency</InputLabel>
|
|
230
|
+
<Select label="Frequency" name="interval" aria-label="interval" value={interval} onChange={(e) => {setInterval(e.target.value) }}>
|
|
231
|
+
<MenuItem value="one_week">Weekly</MenuItem>
|
|
232
|
+
<MenuItem value="two_week">Bi-Weekly</MenuItem>
|
|
233
|
+
<MenuItem value="one_month">Monthly</MenuItem>
|
|
234
|
+
<MenuItem value="three_month">Quarterly</MenuItem>
|
|
235
|
+
<MenuItem value="one_year">Annually</MenuItem>
|
|
236
|
+
</Select>
|
|
237
|
+
</FormControl>
|
|
238
|
+
</Grid>
|
|
239
|
+
<Grid item md={6} xs={12}>
|
|
240
|
+
<TextField fullWidth name="startDate" type="date" aria-label="startDate" label="Start Date" value={DateHelper.formatHtml5Date(new Date(startDate))} onChange={handleChange} />
|
|
241
|
+
</Grid>
|
|
242
|
+
</Grid>
|
|
243
|
+
}
|
|
244
|
+
{getFundList()}
|
|
245
|
+
<div>
|
|
246
|
+
{fundsTotal > 0 &&
|
|
247
|
+
<>
|
|
248
|
+
{(gateway && gateway.payFees === true) ? <Typography fontSize={14} fontStyle="italic">*Transaction fees of {CurrencyHelper.formatCurrency(transactionFee)} are applied.</Typography> : (
|
|
249
|
+
<FormGroup>
|
|
250
|
+
<FormControlLabel control={<Checkbox />} name="transaction-fee" label={`I'll generously add ${CurrencyHelper.formatCurrency(transactionFee)} to cover the transaction fees so you can keep 100% of my donation.`} onChange={handleCheckChange} />
|
|
251
|
+
</FormGroup>
|
|
252
|
+
)}
|
|
253
|
+
<p>Total Donation Amount: ${total}</p>
|
|
254
|
+
</>
|
|
255
|
+
}
|
|
256
|
+
</div>
|
|
257
|
+
</InputBox>
|
|
258
|
+
);
|
|
259
|
+
}
|