@churchapps/apphelper 0.0.8 → 0.0.9

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.
Files changed (185) hide show
  1. package/.eslintignore +3 -3
  2. package/.eslintrc.json +22 -22
  3. package/LICENSE +21 -21
  4. package/README.md +16 -16
  5. package/dist/components/ExportLink.d.ts.map +1 -1
  6. package/dist/components/ExportLink.js +9 -7
  7. package/dist/components/ExportLink.js.map +1 -1
  8. package/dist/components/markdownEditor/editor.css +787 -787
  9. package/dist/components/markdownEditor/images/icons/arrow-clockwise.svg +3 -3
  10. package/dist/components/markdownEditor/images/icons/arrow-counterclockwise.svg +3 -3
  11. package/dist/components/markdownEditor/images/icons/chat-square-quote.svg +3 -3
  12. package/dist/components/markdownEditor/images/icons/chevron-down.svg +2 -2
  13. package/dist/components/markdownEditor/images/icons/code.svg +2 -2
  14. package/dist/components/markdownEditor/images/icons/journal-code.svg +4 -4
  15. package/dist/components/markdownEditor/images/icons/journal-text.svg +4 -4
  16. package/dist/components/markdownEditor/images/icons/justify.svg +2 -2
  17. package/dist/components/markdownEditor/images/icons/link.svg +3 -3
  18. package/dist/components/markdownEditor/images/icons/list-ol.svg +3 -3
  19. package/dist/components/markdownEditor/images/icons/list-ul.svg +2 -2
  20. package/dist/components/markdownEditor/images/icons/pencil-fill.svg +2 -2
  21. package/dist/components/markdownEditor/images/icons/text-center.svg +2 -2
  22. package/dist/components/markdownEditor/images/icons/text-left.svg +2 -2
  23. package/dist/components/markdownEditor/images/icons/text-paragraph.svg +2 -2
  24. package/dist/components/markdownEditor/images/icons/text-right.svg +2 -2
  25. package/dist/components/markdownEditor/images/icons/type-bold.svg +2 -2
  26. package/dist/components/markdownEditor/images/icons/type-h1.svg +2 -2
  27. package/dist/components/markdownEditor/images/icons/type-h2.svg +2 -2
  28. package/dist/components/markdownEditor/images/icons/type-h3.svg +2 -2
  29. package/dist/components/markdownEditor/images/icons/type-h4.svg +12 -12
  30. package/dist/components/markdownEditor/images/icons/type-italic.svg +2 -2
  31. package/dist/components/markdownEditor/images/icons/type-strikethrough.svg +2 -2
  32. package/dist/components/markdownEditor/images/icons/type-underline.svg +2 -2
  33. package/package.json +84 -84
  34. package/src/components/CreatePerson.tsx +80 -80
  35. package/src/components/DisplayBox.tsx +68 -68
  36. package/src/components/ErrorMessages.tsx +26 -26
  37. package/src/components/ExportLink.tsx +66 -61
  38. package/src/components/FloatingSupport.tsx +16 -16
  39. package/src/components/FormSubmissionEdit.tsx +120 -120
  40. package/src/components/HelpIcon.tsx +10 -10
  41. package/src/components/ImageEditor.tsx +126 -126
  42. package/src/components/InputBox.tsx +73 -73
  43. package/src/components/Loading.tsx +29 -29
  44. package/src/components/PersonAdd.tsx +75 -75
  45. package/src/components/QuestionEdit.tsx +62 -62
  46. package/src/components/SmallButton.tsx +39 -39
  47. package/src/components/SupportModal.tsx +26 -26
  48. package/src/components/TabPanel.tsx +34 -34
  49. package/src/components/gallery/GalleryModal.tsx +102 -102
  50. package/src/components/gallery/StockPhotos.tsx +74 -74
  51. package/src/components/gallery/index.ts +1 -1
  52. package/src/components/index.tsx +23 -23
  53. package/src/components/markdownEditor/Editor.tsx +132 -132
  54. package/src/components/markdownEditor/MarkdownEditor.tsx +16 -16
  55. package/src/components/markdownEditor/MarkdownModal.tsx +46 -46
  56. package/src/components/markdownEditor/MarkdownPreview.tsx +14 -14
  57. package/src/components/markdownEditor/editor.css +787 -787
  58. package/src/components/markdownEditor/images/icons/arrow-clockwise.svg +3 -3
  59. package/src/components/markdownEditor/images/icons/arrow-counterclockwise.svg +3 -3
  60. package/src/components/markdownEditor/images/icons/chat-square-quote.svg +3 -3
  61. package/src/components/markdownEditor/images/icons/chevron-down.svg +2 -2
  62. package/src/components/markdownEditor/images/icons/code.svg +2 -2
  63. package/src/components/markdownEditor/images/icons/journal-code.svg +4 -4
  64. package/src/components/markdownEditor/images/icons/journal-text.svg +4 -4
  65. package/src/components/markdownEditor/images/icons/justify.svg +2 -2
  66. package/src/components/markdownEditor/images/icons/link.svg +3 -3
  67. package/src/components/markdownEditor/images/icons/list-ol.svg +3 -3
  68. package/src/components/markdownEditor/images/icons/list-ul.svg +2 -2
  69. package/src/components/markdownEditor/images/icons/pencil-fill.svg +2 -2
  70. package/src/components/markdownEditor/images/icons/text-center.svg +2 -2
  71. package/src/components/markdownEditor/images/icons/text-left.svg +2 -2
  72. package/src/components/markdownEditor/images/icons/text-paragraph.svg +2 -2
  73. package/src/components/markdownEditor/images/icons/text-right.svg +2 -2
  74. package/src/components/markdownEditor/images/icons/type-bold.svg +2 -2
  75. package/src/components/markdownEditor/images/icons/type-h1.svg +2 -2
  76. package/src/components/markdownEditor/images/icons/type-h2.svg +2 -2
  77. package/src/components/markdownEditor/images/icons/type-h3.svg +2 -2
  78. package/src/components/markdownEditor/images/icons/type-h4.svg +12 -12
  79. package/src/components/markdownEditor/images/icons/type-italic.svg +2 -2
  80. package/src/components/markdownEditor/images/icons/type-strikethrough.svg +2 -2
  81. package/src/components/markdownEditor/images/icons/type-underline.svg +2 -2
  82. package/src/components/markdownEditor/index.ts +2 -2
  83. package/src/components/markdownEditor/plugins/AutoLinkPlugin.tsx +35 -35
  84. package/src/components/markdownEditor/plugins/ControlledEditorPlugin.tsx +24 -24
  85. package/src/components/markdownEditor/plugins/ListMaxIndentLevelPlugin.tsx +68 -68
  86. package/src/components/markdownEditor/plugins/MarkdownTransformers.ts +106 -106
  87. package/src/components/markdownEditor/plugins/ReadOnlyPlugin.tsx +15 -15
  88. package/src/components/markdownEditor/plugins/ToolbarPlugin.tsx +401 -401
  89. package/src/components/markdownEditor/plugins/customLink/CustomLinkNode.tsx +224 -224
  90. package/src/components/markdownEditor/plugins/customLink/CustomLinkNodePlugin.tsx +32 -32
  91. package/src/components/markdownEditor/plugins/customLink/CustomLinkNodeTransformer.tsx +102 -102
  92. package/src/components/markdownEditor/plugins/customLink/FloatingLinkEditor.tsx +243 -243
  93. package/src/components/markdownEditor/plugins/customLink/FloatingLinkEditor.types.ts +11 -11
  94. package/src/components/markdownEditor/plugins/emoji/EmojiNode.tsx +95 -95
  95. package/src/components/markdownEditor/plugins/emoji/EmojiNodeTransform.ts +41 -41
  96. package/src/components/markdownEditor/plugins/emoji/EmojiPickerPlugin.tsx +152 -152
  97. package/src/components/markdownEditor/plugins/emoji/EmojisPlugin.tsx +65 -65
  98. package/src/components/markdownEditor/plugins/index.ts +6 -6
  99. package/src/components/markdownEditor/theme.ts +65 -65
  100. package/src/components/material/AppList.tsx +20 -20
  101. package/src/components/material/ChurchList.tsx +22 -22
  102. package/src/components/material/NavItem.tsx +41 -41
  103. package/src/components/material/NewPrivateMessage.tsx +103 -103
  104. package/src/components/material/PrivateMessageDetails.tsx +23 -23
  105. package/src/components/material/PrivateMessages.tsx +87 -87
  106. package/src/components/material/SiteWrapper.tsx +140 -140
  107. package/src/components/material/UserMenu.tsx +141 -141
  108. package/src/components/material/iconPicker/IconNamesList.ts +2240 -2240
  109. package/src/components/material/iconPicker/IconPicker.tsx +153 -153
  110. package/src/components/material/index.tsx +6 -6
  111. package/src/components/notes/AddNote.tsx +90 -90
  112. package/src/components/notes/Conversation.tsx +82 -82
  113. package/src/components/notes/Conversations.tsx +58 -58
  114. package/src/components/notes/NewConversation.tsx +78 -78
  115. package/src/components/notes/Note.tsx +44 -44
  116. package/src/components/notes/Notes.tsx +52 -52
  117. package/src/components/notes/index.ts +5 -5
  118. package/src/components/reporting/ChartReport.tsx +98 -98
  119. package/src/components/reporting/ReportFilter.tsx +54 -54
  120. package/src/components/reporting/ReportFilterField.tsx +160 -160
  121. package/src/components/reporting/ReportOutput.tsx +79 -79
  122. package/src/components/reporting/ReportWithFilter.tsx +70 -70
  123. package/src/components/reporting/TableReport.tsx +57 -57
  124. package/src/components/reporting/TreeReport.tsx +111 -111
  125. package/src/components/reporting/index.ts +4 -4
  126. package/src/donationComponents/DonationPage.tsx +136 -136
  127. package/src/donationComponents/components/BankForm.tsx +159 -159
  128. package/src/donationComponents/components/CardForm.tsx +104 -104
  129. package/src/donationComponents/components/DonationForm.tsx +235 -235
  130. package/src/donationComponents/components/FundDonation.tsx +49 -49
  131. package/src/donationComponents/components/FundDonations.tsx +39 -39
  132. package/src/donationComponents/components/NonAuthDonation.tsx +31 -31
  133. package/src/donationComponents/components/NonAuthDonationInner.tsx +259 -259
  134. package/src/donationComponents/components/PaymentMethods.tsx +135 -135
  135. package/src/donationComponents/components/RecurringDonations.tsx +121 -121
  136. package/src/donationComponents/components/RecurringDonationsEdit.tsx +93 -93
  137. package/src/donationComponents/components/index.tsx +9 -9
  138. package/src/donationComponents/index.ts +3 -3
  139. package/src/donationComponents/modals/DonationPreviewModal.tsx +66 -66
  140. package/src/helpers/AnalyticsHelper.ts +33 -33
  141. package/src/helpers/ApiHelper.ts +125 -125
  142. package/src/helpers/AppearanceHelper.ts +69 -69
  143. package/src/helpers/ArrayHelper.ts +81 -81
  144. package/src/helpers/CommonEnvironmentHelper.ts +80 -80
  145. package/src/helpers/CurrencyHelper.ts +10 -10
  146. package/src/helpers/DateHelper.ts +108 -108
  147. package/src/helpers/DonationHelper.ts +26 -26
  148. package/src/helpers/ErrorHelper.ts +36 -36
  149. package/src/helpers/EventHelper.ts +52 -52
  150. package/src/helpers/FileHelper.ts +31 -31
  151. package/src/helpers/PersonHelper.ts +60 -60
  152. package/src/helpers/SocketHelper.ts +76 -76
  153. package/src/helpers/Themes.ts +14 -14
  154. package/src/helpers/UniqueIdHelper.ts +36 -36
  155. package/src/helpers/UserHelper.ts +59 -59
  156. package/src/helpers/createEmotionCache.ts +17 -17
  157. package/src/helpers/index.ts +18 -18
  158. package/src/hooks/index.ts +1 -1
  159. package/src/hooks/useMountedState.ts +16 -16
  160. package/src/index.ts +6 -6
  161. package/src/interfaces/Access.ts +24 -24
  162. package/src/interfaces/Attendance.ts +8 -8
  163. package/src/interfaces/Content.ts +10 -10
  164. package/src/interfaces/Doing.ts +24 -24
  165. package/src/interfaces/Donation.ts +45 -45
  166. package/src/interfaces/Error.ts +17 -17
  167. package/src/interfaces/Membership.ts +51 -51
  168. package/src/interfaces/Messaging.ts +20 -20
  169. package/src/interfaces/Permissions.ts +68 -68
  170. package/src/interfaces/Reporting.ts +7 -7
  171. package/src/interfaces/UserContextInterface.ts +13 -13
  172. package/src/interfaces/index.ts +13 -13
  173. package/src/pageComponents/LoginPage.tsx +244 -244
  174. package/src/pageComponents/LogoutPage.tsx +28 -28
  175. package/src/pageComponents/components/Forgot.tsx +79 -79
  176. package/src/pageComponents/components/Login.tsx +54 -54
  177. package/src/pageComponents/components/LoginSetPassword.tsx +63 -63
  178. package/src/pageComponents/components/Register.tsx +107 -107
  179. package/src/pageComponents/components/SelectChurchModal.tsx +41 -41
  180. package/src/pageComponents/components/SelectChurchRegister.tsx +88 -88
  181. package/src/pageComponents/components/SelectChurchSearch.tsx +69 -69
  182. package/src/pageComponents/components/SelectableChurch.tsx +38 -38
  183. package/src/pageComponents/index.ts +3 -3
  184. package/tsconfig.json +34 -34
  185. package/tslint.json +14 -14
@@ -1,235 +1,235 @@
1
- /* eslint-disable multiline-ternary */
2
- import React from "react";
3
- import { Stripe } from "@stripe/stripe-js";
4
- import { InputBox, ErrorMessages } from "../../components";
5
- import { FundDonations } from ".";
6
- import { DonationPreviewModal } from "../modals/DonationPreviewModal";
7
- import { ApiHelper, CurrencyHelper, DateHelper } from "../../helpers";
8
- import { PersonInterface, StripePaymentMethod, StripeDonationInterface, FundDonationInterface, FundInterface, ChurchInterface } from "../../interfaces";
9
- import { Grid, InputLabel, MenuItem, Select, TextField, FormControl, Button, SelectChangeEvent, FormControlLabel, Checkbox, FormGroup, Typography } from "@mui/material"
10
- import { DonationHelper } from "../../helpers";
11
-
12
- interface Props { person: PersonInterface, customerId: string, paymentMethods: StripePaymentMethod[], stripePromise: Promise<Stripe>, donationSuccess: (message: string) => void, church?: ChurchInterface, churchLogo?: string }
13
-
14
- export const DonationForm: React.FC<Props> = (props) => {
15
- const [errorMessage, setErrorMessage] = React.useState<string>();
16
- const [fundDonations, setFundDonations] = React.useState<FundDonationInterface[]>();
17
- const [funds, setFunds] = React.useState<FundInterface[]>([]);
18
- const [fundsTotal, setFundsTotal] = React.useState<number>(0);
19
- const [transactionFee, setTransactionFee] = React.useState<number>(0);
20
- const [payFee, setPayFee] = React.useState<number>(0);
21
- const [total, setTotal] = React.useState<number>(0);
22
- const [paymentMethodName, setPaymentMethodName] = React.useState<string>(`${props?.paymentMethods[0]?.name} ****${props?.paymentMethods[0]?.last4}`);
23
- const [donationType, setDonationType] = React.useState<string>();
24
- const [showDonationPreviewModal, setShowDonationPreviewModal] = React.useState<boolean>(false);
25
- const [interval, setInterval] = React.useState("one_month");
26
- const [gateway, setGateway] = React.useState(null);
27
- const [donation, setDonation] = React.useState<StripeDonationInterface>({
28
- id: props?.paymentMethods[0]?.id,
29
- type: props?.paymentMethods[0]?.type,
30
- customerId: props.customerId,
31
- person: {
32
- id: props.person?.id,
33
- email: props.person?.contactInfo.email,
34
- name: props.person?.name.display
35
- },
36
- amount: 0,
37
- billing_cycle_anchor: + new Date(),
38
- interval: {
39
- interval_count: 1,
40
- interval: "month"
41
- },
42
- funds: []
43
- });
44
-
45
- const loadData = () => {
46
- ApiHelper.get("/funds", "GivingApi").then(data => {
47
- setFunds(data);
48
- if (data.length) setFundDonations([{ fundId: data[0].id }]);
49
- });
50
- ApiHelper.get("/gateways", "GivingApi").then((data) => {
51
- if (data.length !== 0) setGateway(data[0]);
52
- });
53
- }
54
-
55
- const handleKeyDown = (e: React.KeyboardEvent<any>) => { if (e.key === "Enter") { e.preventDefault(); handleSave(); } }
56
-
57
- const handleCheckChange = (e: React.SyntheticEvent<Element, Event>, checked: boolean) => {
58
- let d = { ...donation } as StripeDonationInterface;
59
- d.amount = checked ? fundsTotal + transactionFee : fundsTotal;
60
- let showFee = checked ? transactionFee : 0;
61
- setTotal(d.amount);
62
- setPayFee(showFee);
63
- setDonation(d);
64
- }
65
-
66
- const handleAutoPayFee = () => {
67
- let d = { ...donation } as StripeDonationInterface;
68
- d.amount = fundsTotal + transactionFee;
69
- let showFee = transactionFee;
70
- setTotal(d.amount);
71
- setPayFee(showFee);
72
- setDonation(d);
73
- }
74
-
75
- const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> | SelectChangeEvent<string>) => {
76
- setErrorMessage(null);
77
- let d = { ...donation } as StripeDonationInterface;
78
- let value = e.target.value;
79
- switch (e.target.name) {
80
- case "method":
81
- d.id = value;
82
- let pm = props.paymentMethods.find(pm => pm.id === value);
83
- d.type = pm.type;
84
- setPaymentMethodName(`${pm.name} ****${pm.last4}`);
85
- break;
86
- case "type": setDonationType(value); break;
87
- case "date": d.billing_cycle_anchor = + new Date(value); break;
88
- case "interval":
89
- setInterval(value);
90
- d.interval = DonationHelper.getInterval(value);
91
- break;
92
- case "notes": d.notes = value; break;
93
- case "transaction-fee":
94
- const element = e.target as HTMLInputElement
95
- d.amount = element.checked ? fundsTotal + transactionFee : fundsTotal;
96
- let showFee = element.checked ? transactionFee : 0;
97
- setTotal(d.amount);
98
- setPayFee(showFee);
99
- }
100
- setDonation(d);
101
- }
102
-
103
- const handleCancel = () => { setDonationType(null); }
104
- const handleSave = () => {
105
- if (donation.amount < .5) setErrorMessage("Donation amount must be greater than $0.50");
106
- else setShowDonationPreviewModal(true);
107
- }
108
- const handleDonationSelect = (type: string) => {
109
- let dt = donationType === type ? null : type;
110
- setDonationType(dt);
111
- }
112
-
113
- const makeDonation = async (message: string) => {
114
- let results;
115
-
116
- const churchObj = {
117
- name: props?.church?.name,
118
- subDomain: props?.church?.subDomain,
119
- churchURL: typeof window !== "undefined" && window.location.origin,
120
- logo: props?.churchLogo
121
- }
122
-
123
- if (donationType === "once") results = await ApiHelper.post("/donate/charge/", {...donation, church: churchObj }, "GivingApi");
124
- if (donationType === "recurring") results = await ApiHelper.post("/donate/subscribe/", {...donation, church: churchObj }, "GivingApi");
125
-
126
- if (results?.status === "succeeded" || results?.status === "pending" || results?.status === "active") {
127
- setShowDonationPreviewModal(false);
128
- setDonationType(null);
129
- props.donationSuccess(message);
130
- }
131
- if (results?.raw?.message) {
132
- setShowDonationPreviewModal(false);
133
- setErrorMessage("Error: " + results?.raw?.message);
134
- }
135
- }
136
-
137
- const handleFundDonationsChange = (fd: FundDonationInterface[]) => {
138
- setErrorMessage(null);
139
- setFundDonations(fd);
140
- let totalAmount = 0;
141
- let selectedFunds: any = [];
142
- for (const fundDonation of fd) {
143
- totalAmount += fundDonation.amount || 0;
144
- let fund = funds.find((fund: FundInterface) => fund.id === fundDonation.fundId);
145
- selectedFunds.push({ id: fundDonation.fundId, amount: fundDonation.amount || 0, name: fund.name });
146
- }
147
- let d = { ...donation };
148
- d.amount = totalAmount;
149
- d.funds = selectedFunds;
150
- setDonation(d);
151
- setFundsTotal(totalAmount);
152
- setTotal(totalAmount);
153
- setTransactionFee(getTransactionFee(totalAmount));
154
- }
155
-
156
- const getTransactionFee = (amount: number) => {
157
- const fixedFee = 0.30;
158
- const fixedPercent = 0.029;
159
- return Math.round(((amount + fixedFee) / (1 - fixedPercent) - amount) * 100) / 100;
160
- }
161
-
162
- React.useEffect(loadData, [props.person?.id]);
163
- // eslint-disable-next-line react-hooks/exhaustive-deps
164
- React.useEffect(() => { gateway && gateway.payFees === true && handleAutoPayFee() }, [fundDonations]);
165
-
166
- if (!funds.length || !props?.paymentMethods[0]?.id) return null;
167
- else return (
168
- <>
169
- <DonationPreviewModal show={showDonationPreviewModal} onHide={() => setShowDonationPreviewModal(false)} handleDonate={makeDonation} donation={donation} donationType={donationType} payFee={payFee} paymentMethodName={paymentMethodName} funds={funds} />
170
- <InputBox id="donationBox" aria-label="donation-box" headerIcon="volunteer_activism" headerText="Donate" ariaLabelSave="save-button" cancelFunction={donationType ? handleCancel : undefined} saveFunction={donationType ? handleSave : undefined} saveText="Preview Donation">
171
- <Grid container spacing={3}>
172
- <Grid item md={6} xs={12}>
173
- <Button aria-label="single-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "once" ? "contained" : "outlined"} onClick={() => handleDonationSelect("once")}>Make a Donation</Button>
174
- </Grid>
175
- <Grid item md={6} xs={12}>
176
- <Button aria-label="recurring-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "recurring" ? "contained" : "outlined"} onClick={() => handleDonationSelect("recurring")}>Make a Recurring Donation</Button>
177
- </Grid>
178
- </Grid>
179
- {donationType
180
- && <div style={{ marginTop: "20px" }}>
181
- <Grid container spacing={3}>
182
- <Grid item md={12} xs={12}>
183
- <FormControl fullWidth>
184
- <InputLabel>Method</InputLabel>
185
- <Select label="Method" name="method" aria-label="method" value={donation.id} className="capitalize" onChange={handleChange}>
186
- {props.paymentMethods.map((paymentMethod: any, i: number) => <MenuItem key={i} value={paymentMethod.id}>{paymentMethod.name} ****{paymentMethod.last4}</MenuItem>)}
187
- </Select>
188
- </FormControl>
189
- </Grid>
190
- </Grid>
191
- {donationType === "recurring"
192
- && <Grid container spacing={3} style={{marginTop:10}}>
193
- <Grid item md={6} xs={12}>
194
- <TextField fullWidth name="date" type="date" aria-label="date" label="Start Date" value={DateHelper.formatHtml5Date(new Date(donation.billing_cycle_anchor))} onChange={handleChange} onKeyDown={handleKeyDown} />
195
- </Grid>
196
- <Grid item md={6} xs={12}>
197
- <FormControl fullWidth>
198
- <InputLabel>Frequency</InputLabel>
199
- <Select label="Frequency" name="interval" aria-label="interval" value={interval} onChange={handleChange}>
200
- <MenuItem value="one_week">Weekly</MenuItem>
201
- <MenuItem value="two_week">Bi-Weekly</MenuItem>
202
- <MenuItem value="one_month">Monthly</MenuItem>
203
- <MenuItem value="three_month">Quarterly</MenuItem>
204
- <MenuItem value="one_year">Annually</MenuItem>
205
- </Select>
206
- </FormControl>
207
- </Grid>
208
- </Grid>
209
- }
210
- <div className="form-group">
211
- {funds && fundDonations
212
- && <>
213
- <h4>Fund</h4>
214
- <FundDonations fundDonations={fundDonations} funds={funds} updatedFunction={handleFundDonationsChange} />
215
- </>
216
- }
217
- {fundsTotal > 0
218
- && <>
219
- {(gateway && gateway.payFees === true) ? <Typography fontSize={14} fontStyle="italic">*Transaction fees of {CurrencyHelper.formatCurrency(transactionFee)} are applied.</Typography> : (
220
- <FormGroup>
221
- <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} />
222
- </FormGroup>
223
- )}
224
- <p>Total Donation Amount: ${total}</p>
225
- </>
226
- }
227
- <TextField fullWidth label="Notes" multiline aria-label="note" name="notes" value={donation.notes || ""} onChange={handleChange} onKeyDown={handleKeyDown} />
228
- </div>
229
- {errorMessage && <ErrorMessages errors={[errorMessage]}></ErrorMessages>}
230
- </div>
231
- }
232
- </InputBox>
233
- </>
234
- );
235
- }
1
+ /* eslint-disable multiline-ternary */
2
+ import React from "react";
3
+ import { Stripe } from "@stripe/stripe-js";
4
+ import { InputBox, ErrorMessages } from "../../components";
5
+ import { FundDonations } from ".";
6
+ import { DonationPreviewModal } from "../modals/DonationPreviewModal";
7
+ import { ApiHelper, CurrencyHelper, DateHelper } from "../../helpers";
8
+ import { PersonInterface, StripePaymentMethod, StripeDonationInterface, FundDonationInterface, FundInterface, ChurchInterface } from "../../interfaces";
9
+ import { Grid, InputLabel, MenuItem, Select, TextField, FormControl, Button, SelectChangeEvent, FormControlLabel, Checkbox, FormGroup, Typography } from "@mui/material"
10
+ import { DonationHelper } from "../../helpers";
11
+
12
+ interface Props { person: PersonInterface, customerId: string, paymentMethods: StripePaymentMethod[], stripePromise: Promise<Stripe>, donationSuccess: (message: string) => void, church?: ChurchInterface, churchLogo?: string }
13
+
14
+ export const DonationForm: React.FC<Props> = (props) => {
15
+ const [errorMessage, setErrorMessage] = React.useState<string>();
16
+ const [fundDonations, setFundDonations] = React.useState<FundDonationInterface[]>();
17
+ const [funds, setFunds] = React.useState<FundInterface[]>([]);
18
+ const [fundsTotal, setFundsTotal] = React.useState<number>(0);
19
+ const [transactionFee, setTransactionFee] = React.useState<number>(0);
20
+ const [payFee, setPayFee] = React.useState<number>(0);
21
+ const [total, setTotal] = React.useState<number>(0);
22
+ const [paymentMethodName, setPaymentMethodName] = React.useState<string>(`${props?.paymentMethods[0]?.name} ****${props?.paymentMethods[0]?.last4}`);
23
+ const [donationType, setDonationType] = React.useState<string>();
24
+ const [showDonationPreviewModal, setShowDonationPreviewModal] = React.useState<boolean>(false);
25
+ const [interval, setInterval] = React.useState("one_month");
26
+ const [gateway, setGateway] = React.useState(null);
27
+ const [donation, setDonation] = React.useState<StripeDonationInterface>({
28
+ id: props?.paymentMethods[0]?.id,
29
+ type: props?.paymentMethods[0]?.type,
30
+ customerId: props.customerId,
31
+ person: {
32
+ id: props.person?.id,
33
+ email: props.person?.contactInfo.email,
34
+ name: props.person?.name.display
35
+ },
36
+ amount: 0,
37
+ billing_cycle_anchor: + new Date(),
38
+ interval: {
39
+ interval_count: 1,
40
+ interval: "month"
41
+ },
42
+ funds: []
43
+ });
44
+
45
+ const loadData = () => {
46
+ ApiHelper.get("/funds", "GivingApi").then(data => {
47
+ setFunds(data);
48
+ if (data.length) setFundDonations([{ fundId: data[0].id }]);
49
+ });
50
+ ApiHelper.get("/gateways", "GivingApi").then((data) => {
51
+ if (data.length !== 0) setGateway(data[0]);
52
+ });
53
+ }
54
+
55
+ const handleKeyDown = (e: React.KeyboardEvent<any>) => { if (e.key === "Enter") { e.preventDefault(); handleSave(); } }
56
+
57
+ const handleCheckChange = (e: React.SyntheticEvent<Element, Event>, checked: boolean) => {
58
+ let d = { ...donation } as StripeDonationInterface;
59
+ d.amount = checked ? fundsTotal + transactionFee : fundsTotal;
60
+ let showFee = checked ? transactionFee : 0;
61
+ setTotal(d.amount);
62
+ setPayFee(showFee);
63
+ setDonation(d);
64
+ }
65
+
66
+ const handleAutoPayFee = () => {
67
+ let d = { ...donation } as StripeDonationInterface;
68
+ d.amount = fundsTotal + transactionFee;
69
+ let showFee = transactionFee;
70
+ setTotal(d.amount);
71
+ setPayFee(showFee);
72
+ setDonation(d);
73
+ }
74
+
75
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> | SelectChangeEvent<string>) => {
76
+ setErrorMessage(null);
77
+ let d = { ...donation } as StripeDonationInterface;
78
+ let value = e.target.value;
79
+ switch (e.target.name) {
80
+ case "method":
81
+ d.id = value;
82
+ let pm = props.paymentMethods.find(pm => pm.id === value);
83
+ d.type = pm.type;
84
+ setPaymentMethodName(`${pm.name} ****${pm.last4}`);
85
+ break;
86
+ case "type": setDonationType(value); break;
87
+ case "date": d.billing_cycle_anchor = + new Date(value); break;
88
+ case "interval":
89
+ setInterval(value);
90
+ d.interval = DonationHelper.getInterval(value);
91
+ break;
92
+ case "notes": d.notes = value; break;
93
+ case "transaction-fee":
94
+ const element = e.target as HTMLInputElement
95
+ d.amount = element.checked ? fundsTotal + transactionFee : fundsTotal;
96
+ let showFee = element.checked ? transactionFee : 0;
97
+ setTotal(d.amount);
98
+ setPayFee(showFee);
99
+ }
100
+ setDonation(d);
101
+ }
102
+
103
+ const handleCancel = () => { setDonationType(null); }
104
+ const handleSave = () => {
105
+ if (donation.amount < .5) setErrorMessage("Donation amount must be greater than $0.50");
106
+ else setShowDonationPreviewModal(true);
107
+ }
108
+ const handleDonationSelect = (type: string) => {
109
+ let dt = donationType === type ? null : type;
110
+ setDonationType(dt);
111
+ }
112
+
113
+ const makeDonation = async (message: string) => {
114
+ let results;
115
+
116
+ const churchObj = {
117
+ name: props?.church?.name,
118
+ subDomain: props?.church?.subDomain,
119
+ churchURL: typeof window !== "undefined" && window.location.origin,
120
+ logo: props?.churchLogo
121
+ }
122
+
123
+ if (donationType === "once") results = await ApiHelper.post("/donate/charge/", {...donation, church: churchObj }, "GivingApi");
124
+ if (donationType === "recurring") results = await ApiHelper.post("/donate/subscribe/", {...donation, church: churchObj }, "GivingApi");
125
+
126
+ if (results?.status === "succeeded" || results?.status === "pending" || results?.status === "active") {
127
+ setShowDonationPreviewModal(false);
128
+ setDonationType(null);
129
+ props.donationSuccess(message);
130
+ }
131
+ if (results?.raw?.message) {
132
+ setShowDonationPreviewModal(false);
133
+ setErrorMessage("Error: " + results?.raw?.message);
134
+ }
135
+ }
136
+
137
+ const handleFundDonationsChange = (fd: FundDonationInterface[]) => {
138
+ setErrorMessage(null);
139
+ setFundDonations(fd);
140
+ let totalAmount = 0;
141
+ let selectedFunds: any = [];
142
+ for (const fundDonation of fd) {
143
+ totalAmount += fundDonation.amount || 0;
144
+ let fund = funds.find((fund: FundInterface) => fund.id === fundDonation.fundId);
145
+ selectedFunds.push({ id: fundDonation.fundId, amount: fundDonation.amount || 0, name: fund.name });
146
+ }
147
+ let d = { ...donation };
148
+ d.amount = totalAmount;
149
+ d.funds = selectedFunds;
150
+ setDonation(d);
151
+ setFundsTotal(totalAmount);
152
+ setTotal(totalAmount);
153
+ setTransactionFee(getTransactionFee(totalAmount));
154
+ }
155
+
156
+ const getTransactionFee = (amount: number) => {
157
+ const fixedFee = 0.30;
158
+ const fixedPercent = 0.029;
159
+ return Math.round(((amount + fixedFee) / (1 - fixedPercent) - amount) * 100) / 100;
160
+ }
161
+
162
+ React.useEffect(loadData, [props.person?.id]);
163
+ // eslint-disable-next-line react-hooks/exhaustive-deps
164
+ React.useEffect(() => { gateway && gateway.payFees === true && handleAutoPayFee() }, [fundDonations]);
165
+
166
+ if (!funds.length || !props?.paymentMethods[0]?.id) return null;
167
+ else return (
168
+ <>
169
+ <DonationPreviewModal show={showDonationPreviewModal} onHide={() => setShowDonationPreviewModal(false)} handleDonate={makeDonation} donation={donation} donationType={donationType} payFee={payFee} paymentMethodName={paymentMethodName} funds={funds} />
170
+ <InputBox id="donationBox" aria-label="donation-box" headerIcon="volunteer_activism" headerText="Donate" ariaLabelSave="save-button" cancelFunction={donationType ? handleCancel : undefined} saveFunction={donationType ? handleSave : undefined} saveText="Preview Donation">
171
+ <Grid container spacing={3}>
172
+ <Grid item md={6} xs={12}>
173
+ <Button aria-label="single-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "once" ? "contained" : "outlined"} onClick={() => handleDonationSelect("once")}>Make a Donation</Button>
174
+ </Grid>
175
+ <Grid item md={6} xs={12}>
176
+ <Button aria-label="recurring-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "recurring" ? "contained" : "outlined"} onClick={() => handleDonationSelect("recurring")}>Make a Recurring Donation</Button>
177
+ </Grid>
178
+ </Grid>
179
+ {donationType
180
+ && <div style={{ marginTop: "20px" }}>
181
+ <Grid container spacing={3}>
182
+ <Grid item md={12} xs={12}>
183
+ <FormControl fullWidth>
184
+ <InputLabel>Method</InputLabel>
185
+ <Select label="Method" name="method" aria-label="method" value={donation.id} className="capitalize" onChange={handleChange}>
186
+ {props.paymentMethods.map((paymentMethod: any, i: number) => <MenuItem key={i} value={paymentMethod.id}>{paymentMethod.name} ****{paymentMethod.last4}</MenuItem>)}
187
+ </Select>
188
+ </FormControl>
189
+ </Grid>
190
+ </Grid>
191
+ {donationType === "recurring"
192
+ && <Grid container spacing={3} style={{marginTop:10}}>
193
+ <Grid item md={6} xs={12}>
194
+ <TextField fullWidth name="date" type="date" aria-label="date" label="Start Date" value={DateHelper.formatHtml5Date(new Date(donation.billing_cycle_anchor))} onChange={handleChange} onKeyDown={handleKeyDown} />
195
+ </Grid>
196
+ <Grid item md={6} xs={12}>
197
+ <FormControl fullWidth>
198
+ <InputLabel>Frequency</InputLabel>
199
+ <Select label="Frequency" name="interval" aria-label="interval" value={interval} onChange={handleChange}>
200
+ <MenuItem value="one_week">Weekly</MenuItem>
201
+ <MenuItem value="two_week">Bi-Weekly</MenuItem>
202
+ <MenuItem value="one_month">Monthly</MenuItem>
203
+ <MenuItem value="three_month">Quarterly</MenuItem>
204
+ <MenuItem value="one_year">Annually</MenuItem>
205
+ </Select>
206
+ </FormControl>
207
+ </Grid>
208
+ </Grid>
209
+ }
210
+ <div className="form-group">
211
+ {funds && fundDonations
212
+ && <>
213
+ <h4>Fund</h4>
214
+ <FundDonations fundDonations={fundDonations} funds={funds} updatedFunction={handleFundDonationsChange} />
215
+ </>
216
+ }
217
+ {fundsTotal > 0
218
+ && <>
219
+ {(gateway && gateway.payFees === true) ? <Typography fontSize={14} fontStyle="italic">*Transaction fees of {CurrencyHelper.formatCurrency(transactionFee)} are applied.</Typography> : (
220
+ <FormGroup>
221
+ <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} />
222
+ </FormGroup>
223
+ )}
224
+ <p>Total Donation Amount: ${total}</p>
225
+ </>
226
+ }
227
+ <TextField fullWidth label="Notes" multiline aria-label="note" name="notes" value={donation.notes || ""} onChange={handleChange} onKeyDown={handleKeyDown} />
228
+ </div>
229
+ {errorMessage && <ErrorMessages errors={[errorMessage]}></ErrorMessages>}
230
+ </div>
231
+ }
232
+ </InputBox>
233
+ </>
234
+ );
235
+ }
@@ -1,49 +1,49 @@
1
- import React from "react";
2
- import { FundDonationInterface, FundInterface } from "../../interfaces";
3
- import { FormControl, Grid, InputLabel, MenuItem, Select, SelectChangeEvent, TextField } from "@mui/material"
4
-
5
- interface Props {
6
- fundDonation: FundDonationInterface,
7
- funds: FundInterface[],
8
- index: number,
9
- updatedFunction: (fundDonation: FundDonationInterface, index: number) => void
10
- }
11
-
12
- export const FundDonation: React.FC<Props> = (props) => {
13
-
14
- const getOptions = () => {
15
- let result = [];
16
- for (let i = 0; i < props.funds.length; i++) result.push(<MenuItem key={i} value={props.funds[i].id}>{props.funds[i].name}</MenuItem>);
17
- return result;
18
- }
19
-
20
- const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> | SelectChangeEvent<string>) => {
21
- let fd = { ...props.fundDonation }
22
- switch (e.target.name) {
23
- case "amount":
24
- fd.amount = parseFloat(e.target.value.replace("$", "").replace(",", ""));
25
- break;
26
- case "fund":
27
- fd.fundId = e.target.value;
28
- break;
29
- }
30
- props.updatedFunction(fd, props.index);
31
- }
32
-
33
- return (
34
- <Grid container spacing={3}>
35
- <Grid item md={6} xs={12}>
36
- <TextField fullWidth name="amount" label="Amount" type="number" aria-label="amount" lang="en-150" value={props.fundDonation.amount || ""} onChange={handleChange} />
37
- </Grid>
38
- <Grid item md={6} xs={12}>
39
- <FormControl fullWidth>
40
- <InputLabel>Fund</InputLabel>
41
- <Select fullWidth label="Fund" name="fund" aria-label="fund" value={props.fundDonation.fundId} onChange={handleChange}>
42
- {getOptions()}
43
- </Select>
44
- </FormControl>
45
- </Grid>
46
- </Grid>
47
- );
48
- }
49
-
1
+ import React from "react";
2
+ import { FundDonationInterface, FundInterface } from "../../interfaces";
3
+ import { FormControl, Grid, InputLabel, MenuItem, Select, SelectChangeEvent, TextField } from "@mui/material"
4
+
5
+ interface Props {
6
+ fundDonation: FundDonationInterface,
7
+ funds: FundInterface[],
8
+ index: number,
9
+ updatedFunction: (fundDonation: FundDonationInterface, index: number) => void
10
+ }
11
+
12
+ export const FundDonation: React.FC<Props> = (props) => {
13
+
14
+ const getOptions = () => {
15
+ let result = [];
16
+ for (let i = 0; i < props.funds.length; i++) result.push(<MenuItem key={i} value={props.funds[i].id}>{props.funds[i].name}</MenuItem>);
17
+ return result;
18
+ }
19
+
20
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> | SelectChangeEvent<string>) => {
21
+ let fd = { ...props.fundDonation }
22
+ switch (e.target.name) {
23
+ case "amount":
24
+ fd.amount = parseFloat(e.target.value.replace("$", "").replace(",", ""));
25
+ break;
26
+ case "fund":
27
+ fd.fundId = e.target.value;
28
+ break;
29
+ }
30
+ props.updatedFunction(fd, props.index);
31
+ }
32
+
33
+ return (
34
+ <Grid container spacing={3}>
35
+ <Grid item md={6} xs={12}>
36
+ <TextField fullWidth name="amount" label="Amount" type="number" aria-label="amount" lang="en-150" value={props.fundDonation.amount || ""} onChange={handleChange} />
37
+ </Grid>
38
+ <Grid item md={6} xs={12}>
39
+ <FormControl fullWidth>
40
+ <InputLabel>Fund</InputLabel>
41
+ <Select fullWidth label="Fund" name="fund" aria-label="fund" value={props.fundDonation.fundId} onChange={handleChange}>
42
+ {getOptions()}
43
+ </Select>
44
+ </FormControl>
45
+ </Grid>
46
+ </Grid>
47
+ );
48
+ }
49
+