@churchapps/apphelper 0.4.7 → 0.4.8

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 (101) hide show
  1. package/CHUMS_COMPONENT_EXTRACTION_CANDIDATES.md +210 -0
  2. package/COMPONENT_USAGE_REPORT.md +322 -0
  3. package/dist/components/header/SecondaryMenu.js +3 -3
  4. package/dist/components/header/SecondaryMenu.js.map +1 -1
  5. package/dist/components/index.d.ts +0 -2
  6. package/dist/components/index.d.ts.map +1 -1
  7. package/dist/components/index.js +1 -5
  8. package/dist/components/index.js.map +1 -1
  9. package/dist/helpers/FileHelper.d.ts +1 -1
  10. package/dist/helpers/FileHelper.d.ts.map +1 -1
  11. package/dist/helpers/FileHelper.js +2 -6
  12. package/dist/helpers/FileHelper.js.map +1 -1
  13. package/dist/index.d.ts +0 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +0 -1
  16. package/dist/index.js.map +1 -1
  17. package/package.json +1 -1
  18. package/src/components/header/SecondaryMenu.tsx +3 -3
  19. package/src/components/index.tsx +0 -2
  20. package/src/helpers/FileHelper.ts +3 -6
  21. package/src/index.ts +0 -1
  22. package/dist/components/CreatePerson.d.ts +0 -15
  23. package/dist/components/CreatePerson.d.ts.map +0 -1
  24. package/dist/components/CreatePerson.js +0 -100
  25. package/dist/components/CreatePerson.js.map +0 -1
  26. package/dist/components/PersonAdd.d.ts +0 -16
  27. package/dist/components/PersonAdd.d.ts.map +0 -1
  28. package/dist/components/PersonAdd.js +0 -52
  29. package/dist/components/PersonAdd.js.map +0 -1
  30. package/dist/donationComponents/DonationPage.d.ts +0 -11
  31. package/dist/donationComponents/DonationPage.d.ts.map +0 -1
  32. package/dist/donationComponents/DonationPage.js +0 -150
  33. package/dist/donationComponents/DonationPage.js.map +0 -1
  34. package/dist/donationComponents/components/BankForm.d.ts +0 -14
  35. package/dist/donationComponents/components/BankForm.d.ts.map +0 -1
  36. package/dist/donationComponents/components/BankForm.js +0 -125
  37. package/dist/donationComponents/components/BankForm.js.map +0 -1
  38. package/dist/donationComponents/components/CardForm.d.ts +0 -13
  39. package/dist/donationComponents/components/CardForm.d.ts.map +0 -1
  40. package/dist/donationComponents/components/CardForm.js +0 -122
  41. package/dist/donationComponents/components/CardForm.js.map +0 -1
  42. package/dist/donationComponents/components/DonationForm.d.ts +0 -15
  43. package/dist/donationComponents/components/DonationForm.d.ts.map +0 -1
  44. package/dist/donationComponents/components/DonationForm.js +0 -200
  45. package/dist/donationComponents/components/DonationForm.js.map +0 -1
  46. package/dist/donationComponents/components/FundDonation.d.ts +0 -12
  47. package/dist/donationComponents/components/FundDonation.d.ts.map +0 -1
  48. package/dist/donationComponents/components/FundDonation.js +0 -32
  49. package/dist/donationComponents/components/FundDonation.js.map +0 -1
  50. package/dist/donationComponents/components/FundDonations.d.ts +0 -11
  51. package/dist/donationComponents/components/FundDonations.d.ts.map +0 -1
  52. package/dist/donationComponents/components/FundDonations.js +0 -33
  53. package/dist/donationComponents/components/FundDonations.js.map +0 -1
  54. package/dist/donationComponents/components/NonAuthDonation.d.ts +0 -12
  55. package/dist/donationComponents/components/NonAuthDonation.d.ts.map +0 -1
  56. package/dist/donationComponents/components/NonAuthDonation.js +0 -27
  57. package/dist/donationComponents/components/NonAuthDonation.js.map +0 -1
  58. package/dist/donationComponents/components/NonAuthDonationInner.d.ts +0 -12
  59. package/dist/donationComponents/components/NonAuthDonationInner.d.ts.map +0 -1
  60. package/dist/donationComponents/components/NonAuthDonationInner.js +0 -276
  61. package/dist/donationComponents/components/NonAuthDonationInner.js.map +0 -1
  62. package/dist/donationComponents/components/PaymentMethods.d.ts +0 -14
  63. package/dist/donationComponents/components/PaymentMethods.d.ts.map +0 -1
  64. package/dist/donationComponents/components/PaymentMethods.js +0 -86
  65. package/dist/donationComponents/components/PaymentMethods.js.map +0 -1
  66. package/dist/donationComponents/components/RecurringDonations.d.ts +0 -10
  67. package/dist/donationComponents/components/RecurringDonations.d.ts.map +0 -1
  68. package/dist/donationComponents/components/RecurringDonations.js +0 -93
  69. package/dist/donationComponents/components/RecurringDonations.js.map +0 -1
  70. package/dist/donationComponents/components/RecurringDonationsEdit.d.ts +0 -11
  71. package/dist/donationComponents/components/RecurringDonationsEdit.d.ts.map +0 -1
  72. package/dist/donationComponents/components/RecurringDonationsEdit.js +0 -66
  73. package/dist/donationComponents/components/RecurringDonationsEdit.js.map +0 -1
  74. package/dist/donationComponents/components/index.d.ts +0 -10
  75. package/dist/donationComponents/components/index.d.ts.map +0 -1
  76. package/dist/donationComponents/components/index.js +0 -22
  77. package/dist/donationComponents/components/index.js.map +0 -1
  78. package/dist/donationComponents/index.d.ts +0 -4
  79. package/dist/donationComponents/index.d.ts.map +0 -1
  80. package/dist/donationComponents/index.js +0 -10
  81. package/dist/donationComponents/index.js.map +0 -1
  82. package/dist/donationComponents/modals/DonationPreviewModal.d.ts +0 -15
  83. package/dist/donationComponents/modals/DonationPreviewModal.d.ts.map +0 -1
  84. package/dist/donationComponents/modals/DonationPreviewModal.js +0 -33
  85. package/dist/donationComponents/modals/DonationPreviewModal.js.map +0 -1
  86. package/src/components/CreatePerson.tsx +0 -135
  87. package/src/components/PersonAdd.tsx +0 -81
  88. package/src/donationComponents/DonationPage.tsx +0 -215
  89. package/src/donationComponents/components/BankForm.tsx +0 -161
  90. package/src/donationComponents/components/CardForm.tsx +0 -106
  91. package/src/donationComponents/components/DonationForm.tsx +0 -255
  92. package/src/donationComponents/components/FundDonation.tsx +0 -58
  93. package/src/donationComponents/components/FundDonations.tsx +0 -44
  94. package/src/donationComponents/components/NonAuthDonation.tsx +0 -33
  95. package/src/donationComponents/components/NonAuthDonationInner.tsx +0 -295
  96. package/src/donationComponents/components/PaymentMethods.tsx +0 -137
  97. package/src/donationComponents/components/RecurringDonations.tsx +0 -123
  98. package/src/donationComponents/components/RecurringDonationsEdit.tsx +0 -95
  99. package/src/donationComponents/components/index.tsx +0 -9
  100. package/src/donationComponents/index.ts +0 -3
  101. package/src/donationComponents/modals/DonationPreviewModal.tsx +0 -68
@@ -1,295 +0,0 @@
1
- "use client";
2
-
3
- import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
4
- import React, { useState, useRef } from "react";
5
- import ReCAPTCHA from "react-google-recaptcha";
6
- import { ErrorMessages, InputBox } from "../../components";
7
- import { ApiHelper, DateHelper, CurrencyHelper, Locale } from "../../helpers";
8
- import { FundDonationInterface, FundInterface, PersonInterface, StripeDonationInterface, StripePaymentMethod, UserInterface, ChurchInterface } from "@churchapps/helpers";
9
- import { FundDonations } from "./FundDonations";
10
- import { Grid, Alert, TextField, Button, FormControl, InputLabel, Select, MenuItem, PaperProps, FormGroup, FormControlLabel, Checkbox, Typography } from "@mui/material"
11
- import { DonationHelper } from "../../helpers/DonationHelper";
12
-
13
- interface Props { churchId: string, mainContainerCssProps?: PaperProps, showHeader?: boolean, recaptchaSiteKey: string, churchLogo?: string }
14
-
15
- export const NonAuthDonationInner: React.FC<Props> = ({ mainContainerCssProps, showHeader = true, ...props }) => {
16
- const stripe = useStripe();
17
- const elements = useElements();
18
- const formStyling = { style: { base: { fontSize: "18px" } } };
19
- const [firstName, setFirstName] = React.useState("");
20
- const [lastName, setLastName] = React.useState("");
21
- const [email, setEmail] = React.useState("");
22
- const [fundsTotal, setFundsTotal] = React.useState<number>(0);
23
- const [transactionFee, setTransactionFee] = React.useState<number>(0);
24
- const [total, setTotal] = React.useState<number>(0);
25
- const [errors, setErrors] = React.useState([]);
26
- const [fundDonations, setFundDonations] = React.useState<FundDonationInterface[]>([]);
27
- const [funds, setFunds] = React.useState<FundInterface[]>([]);
28
- const [donationComplete, setDonationComplete] = React.useState(false);
29
- const [processing, setProcessing] = React.useState(false);
30
- const [donationType, setDonationType] = useState<"once" | "recurring">("once");
31
- const [interval, setInterval] = useState("one_month");
32
- const [startDate, setStartDate] = useState(new Date().toDateString());
33
- const [captchaResponse, setCaptchaResponse] = useState("");
34
- const [church, setChurch] = useState<ChurchInterface>();
35
- const [gateway, setGateway] = React.useState(null);
36
- const [searchParams, setSearchParams] = React.useState<any>();
37
- const captchaRef = useRef(null);
38
-
39
- const getUrlParam = (param: string) => {
40
- if (typeof window === "undefined") return null;
41
- const urlParams = new URLSearchParams(window.location.search);
42
- return urlParams.get(param);
43
- }
44
-
45
- const init = () => {
46
- const fundId = getUrlParam("fundId");
47
- const amount = getUrlParam("amount");
48
- setSearchParams({ fundId, amount });
49
-
50
- ApiHelper.get("/funds/churchId/" + props.churchId, "GivingApi").then(data => {
51
- setFunds(data);
52
- if (fundId && fundId !== "") {
53
- const selectedFund = data.find((f: FundInterface) => f.id === fundId);
54
- if (selectedFund) {
55
- setFundDonations([{ fundId: selectedFund.id, amount: (amount && amount !== "") ? parseFloat(amount) : 0}]);
56
- }
57
- } else if (data.length) {
58
- setFundDonations([{ fundId: data[0].id }]);
59
- }
60
- });
61
- ApiHelper.get("/churches/" + props.churchId, "MembershipApi").then(data => {
62
- setChurch(data);
63
- });
64
- ApiHelper.get("/gateways/churchId/" + props.churchId, "GivingApi").then(data => {
65
- if (data.length !== 0) setGateway(data[0]);
66
- });
67
- }
68
-
69
- const handleCaptchaChange = (value: string) => {
70
- const captchaToken = captchaRef.current.getValue();
71
- ApiHelper.postAnonymous("/donate/captcha-verify", { token: captchaToken }, "GivingApi").then((data) => { setCaptchaResponse(data.response); })
72
- }
73
-
74
- const handleCheckChange = (e: React.SyntheticEvent<Element, Event>, checked: boolean) => {
75
- let totalPayAmount = checked ? fundsTotal + transactionFee : fundsTotal;
76
- setTotal(totalPayAmount);
77
- }
78
-
79
- // const handleAutoPayFee = () => {
80
- // let totalPayAmount = fundsTotal + transactionFee;
81
- // setTotal(totalPayAmount);
82
- // }
83
-
84
- const handleSave = async () => {
85
- if (validate()) {
86
- setProcessing(true);
87
- ApiHelper.post("/users/loadOrCreate", { userEmail: email, firstName, lastName }, "MembershipApi")
88
- .catch(ex => { setErrors([ex.toString()]); setProcessing(false); })
89
- .then(async userData => {
90
- const personData = { churchId: props.churchId, firstName, lastName, email };
91
- const person = await ApiHelper.post("/people/loadOrCreate", personData, "MembershipApi")
92
- saveCard(userData, person)
93
- });
94
- }
95
- }
96
-
97
- const saveCard = async (user: UserInterface, person: PersonInterface) => {
98
- const cardData = elements.getElement(CardElement);
99
- const stripePM = await stripe.createPaymentMethod({ type: "card", card: cardData });
100
- if (stripePM.error) { setErrors([stripePM.error.message]); setProcessing(false); }
101
- else {
102
- const pm = { id: stripePM.paymentMethod.id, personId: person.id, email: email, name: person.name.display, churchId: props.churchId }
103
- await ApiHelper.post("/paymentmethods/addcard", pm, "GivingApi").then(result => {
104
- if (result?.raw?.message) {
105
- setErrors([result.raw.message]);
106
- setProcessing(false);
107
- } else {
108
- const d: { paymentMethod: StripePaymentMethod, customerId: string } = result;
109
- saveDonation(d.paymentMethod, d.customerId, person);
110
- }
111
- });
112
- }
113
- }
114
-
115
- const saveDonation = async (paymentMethod: StripePaymentMethod, customerId: string, person?: PersonInterface) => {
116
- let donation: StripeDonationInterface = {
117
- amount: total,
118
- id: paymentMethod.id,
119
- customerId: customerId,
120
- type: paymentMethod.type,
121
- churchId: props.churchId,
122
- funds: [],
123
- person: {
124
- id: person?.id,
125
- email: person?.contactInfo?.email,
126
- name: person?.name?.display
127
- }
128
- }
129
-
130
- if (donationType === "recurring") {
131
- donation.billing_cycle_anchor = + new Date(startDate);
132
- donation.interval = DonationHelper.getInterval(interval);
133
- }
134
-
135
- for (const fundDonation of fundDonations) {
136
- let fund = funds.find((fund: FundInterface) => fund.id === fundDonation.fundId);
137
- donation.funds.push({ id: fundDonation.fundId, amount: fundDonation.amount || 0, name: fund.name });
138
- }
139
-
140
- const churchObj = {
141
- name: church.name,
142
- subDomain: church.subDomain,
143
- churchURL: typeof window !== "undefined" && window.location.origin,
144
- logo: props?.churchLogo
145
- }
146
-
147
- let results;
148
- if (donationType === "once") results = await ApiHelper.post("/donate/charge/", { ...donation, church: churchObj }, "GivingApi");
149
- if (donationType === "recurring") results = await ApiHelper.post("/donate/subscribe/", { ...donation, church: churchObj }, "GivingApi");
150
-
151
- if (results?.status === "succeeded" || results?.status === "pending" || results?.status === "active") {
152
- setDonationComplete(true)
153
- }
154
- if (results?.raw?.message) {
155
- setErrors([results?.raw?.message]);
156
- setProcessing(false);
157
- }
158
- setProcessing(false);
159
- }
160
-
161
- const validate = () => {
162
- const result = [];
163
- if (!firstName) result.push(Locale.label("donation.donationForm.validate.firstName"));
164
- if (!lastName) result.push(Locale.label("donation.donationForm.validate.lastName"));
165
- if (!email) result.push(Locale.label("donation.donationForm.validate.email"));
166
- if (fundsTotal === 0) result.push(Locale.label("donation.donationForm.validate.amount"));
167
- if (result.length === 0) {
168
- if (!email.match(/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w+)+$/)) result.push(Locale.label("donation.donationForm.validate.validEmail")); //eslint-disable-line
169
- }
170
- //Todo - make sure the account doesn't exist. (loadOrCreate?)
171
- setErrors(result);
172
- return result.length === 0;
173
- }
174
-
175
- const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
176
- const val = e.currentTarget.value;
177
- switch (e.currentTarget.name) {
178
- case "firstName": setFirstName(val); break;
179
- case "lastName": setLastName(val); break;
180
- case "email": setEmail(val); break;
181
- case "startDate": setStartDate(val); break;
182
- case "interval": setInterval(val); break;
183
- }
184
- }
185
-
186
- const handleFundDonationsChange = async (fd: FundDonationInterface[]) => {
187
- setFundDonations(fd);
188
- let totalAmount = 0;
189
- let selectedFunds: any = [];
190
- for (const fundDonation of fd) {
191
- totalAmount += fundDonation.amount || 0;
192
- let fund = funds.find((fund: FundInterface) => fund.id === fundDonation.fundId);
193
- selectedFunds.push({ id: fundDonation.fundId, amount: fundDonation.amount || 0, name: fund.name });
194
- }
195
- setFundsTotal(totalAmount);
196
- setTotal(totalAmount);
197
-
198
- const fee = await getTransactionFee(totalAmount);
199
- setTransactionFee(fee);
200
-
201
- if (gateway && gateway.payFees === true) {
202
- setTotal(totalAmount + fee);
203
- }
204
- }
205
-
206
- const getTransactionFee = async (amount: number) => {
207
- if (amount > 0) {
208
- try {
209
- const response = await ApiHelper.post("/donate/fee?churchId=" + props.churchId, { type: "creditCard", amount }, "GivingApi");
210
- return response.calculatedFee;
211
- } catch (error) {
212
- console.log("Error calculating transaction fee: ", error);
213
- return 0;
214
- }
215
- } else {
216
- return 0;
217
- }
218
- }
219
-
220
- const getFundList = () => {
221
- if (funds) return (<>
222
- <hr />
223
- <h4>{Locale.label("donation.donationForm.funds")}</h4>
224
- <FundDonations fundDonations={fundDonations} funds={funds} params={searchParams} updatedFunction={handleFundDonationsChange} />
225
- </>);
226
- }
227
-
228
- React.useEffect(init, []); //eslint-disable-line
229
-
230
- // React.useEffect(() => { gateway && gateway.payFees === true && handleAutoPayFee() }, [fundDonations]);
231
-
232
- if (donationComplete) return <Alert severity="success">{Locale.label("donation.donationForm.thankYou")}</Alert>
233
- else return (
234
- <InputBox headerIcon={showHeader ? "volunteer_activism" : ""} headerText={showHeader ? "Donate" : ""} saveFunction={handleSave} saveText="Donate" isSubmitting={processing || !captchaResponse || captchaResponse === "robot"} mainContainerCssProps={mainContainerCssProps}>
235
- <ErrorMessages errors={errors} />
236
- <Grid container spacing={3}>
237
- <Grid size={{ xs: 12, md: 6 }}>
238
- <Button aria-label="single-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "once" ? "contained" : "outlined"} onClick={() => setDonationType("once")}>{Locale.label("donation.donationForm.make")}</Button>
239
- </Grid>
240
- <Grid size={{ xs: 12, md: 6 }}>
241
- <Button aria-label="recurring-donation" size="small" fullWidth style={{ minHeight: "50px" }} variant={donationType === "recurring" ? "contained" : "outlined"} onClick={() => setDonationType("recurring")}>{Locale.label("donation.donationForm.makeRecurring")}</Button>
242
- </Grid>
243
- <Grid size={{ xs: 12, md: 6 }}>
244
- <TextField fullWidth label={Locale.label("person.firstName")} name="firstName" value={firstName} onChange={handleChange} />
245
- </Grid>
246
- <Grid size={{ xs: 12, md: 6 }}>
247
- <TextField fullWidth label={Locale.label("person.lastName")} name="lastName" value={lastName} onChange={handleChange} />
248
- </Grid>
249
- <Grid size={{ xs: 12, md: 6 }}>
250
- <TextField fullWidth label={Locale.label("person.email")} name="email" value={email} onChange={handleChange} />
251
- </Grid>
252
- <Grid size={{ xs: 12, md: 6 }}>
253
- <ReCAPTCHA sitekey={props.recaptchaSiteKey} ref={captchaRef} onChange={handleCaptchaChange} />
254
- </Grid>
255
- </Grid>
256
- <div style={{ padding: 10, border: "1px solid #CCC", borderRadius: 5, marginTop: 10 }}>
257
- <CardElement options={formStyling} />
258
- </div>
259
- {donationType === "recurring"
260
- && <Grid container spacing={3} style={{ marginTop: 0 }}>
261
- <Grid size={{ xs: 12, md: 6 }}>
262
- <FormControl fullWidth>
263
- <InputLabel>{Locale.label("donation.donationForm.frequency")}</InputLabel>
264
- <Select label="Frequency" name="interval" aria-label="interval" value={interval} onChange={(e) => { setInterval(e.target.value) }}>
265
- <MenuItem value="one_week">{Locale.label("donation.donationForm.weekly")}</MenuItem>
266
- <MenuItem value="two_week">{Locale.label("donation.donationForm.biWeekly")}</MenuItem>
267
- <MenuItem value="one_month">{Locale.label("donation.donationForm.monthly")}</MenuItem>
268
- <MenuItem value="three_month">{Locale.label("donation.donationForm.quarterly")}</MenuItem>
269
- <MenuItem value="one_year">{Locale.label("donation.donationForm.annually")}</MenuItem>
270
- </Select>
271
- </FormControl>
272
- </Grid>
273
- <Grid size={{ xs: 12, md: 6 }}>
274
- <TextField fullWidth name="startDate" type="date" aria-label="startDate" label={Locale.label("donation.donationForm.startDate")} value={DateHelper.formatHtml5Date(new Date(startDate))} onChange={handleChange} />
275
- </Grid>
276
- </Grid>
277
- }
278
- {getFundList()}
279
- <div>
280
- {fundsTotal > 0
281
- && <>
282
- {(gateway && gateway.payFees === true)
283
- ? <Typography fontSize={14} fontStyle="italic">*{Locale.label("donation.donationForm.fees").replace("{}", CurrencyHelper.formatCurrency(transactionFee))}</Typography>
284
- : (
285
- <FormGroup>
286
- <FormControlLabel control={<Checkbox />} name="transaction-fee" label={Locale.label("donation.donationForm.cover").replace("{}", CurrencyHelper.formatCurrency(transactionFee))} onChange={handleCheckChange} />
287
- </FormGroup>
288
- )}
289
- <p>Total Donation Amount: ${total}</p>
290
- </>
291
- }
292
- </div>
293
- </InputBox>
294
- );
295
- }
@@ -1,137 +0,0 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { Stripe } from "@stripe/stripe-js";
5
- import { Elements } from "@stripe/react-stripe-js";
6
- import { CardForm, BankForm } from ".";
7
- import { DisplayBox, Loading } from "../../components";
8
- import { ApiHelper, Locale, UserHelper } from "../../helpers";
9
- import { PersonInterface, StripePaymentMethod, Permissions } from "@churchapps/helpers";
10
- import { Icon, Table, TableBody, TableCell, TableRow, IconButton, Menu, MenuItem } from "@mui/material";
11
-
12
- interface Props { person: PersonInterface, customerId: string, paymentMethods: StripePaymentMethod[], stripePromise: Promise<Stripe>, appName: string, dataUpdate: (message?: string) => void }
13
-
14
- export const PaymentMethods: React.FC<Props> = (props) => {
15
- const [editPaymentMethod, setEditPaymentMethod] = React.useState<StripePaymentMethod>(new StripePaymentMethod());
16
- const [mode, setMode] = React.useState("display");
17
- const [verify, setVerify] = React.useState<boolean>(false);
18
-
19
- const handleEdit = (pm?: StripePaymentMethod, verifyAccount?: boolean) => (e: React.MouseEvent) => {
20
- e.preventDefault();
21
- setEditPaymentMethod(pm);
22
- setVerify(verifyAccount)
23
- setMode("edit");
24
- }
25
-
26
- const handleDelete = async () => {
27
- let confirmed = window.confirm(Locale.label("donation.paymentMethods.confirmDelete"));
28
- if (confirmed) {
29
- ApiHelper.delete("/paymentmethods/" + editPaymentMethod.id + "/" + props.customerId, "GivingApi").then(() => {
30
- setMode("display");
31
- props.dataUpdate(Locale.label("donation.paymentMethods.deleted"));
32
- })
33
- }
34
- }
35
-
36
- const MenuIcon = () => {
37
- const [anchorEl, setAnchorEl] = React.useState(null);
38
- const open = Boolean(anchorEl);
39
- const handleClick = (e: React.MouseEvent) => {
40
- setAnchorEl(e.currentTarget);
41
- };
42
- const handleClose = () => {
43
- setAnchorEl(null);
44
- };
45
- return (
46
- <>
47
- <IconButton
48
- aria-label="add-button"
49
- id="addBtnGroup"
50
- aria-controls={open ? "add-menu" : undefined}
51
- aria-expanded={open ? "true" : undefined}
52
- aria-haspopup="true"
53
- onClick={handleClick}
54
- >
55
- <Icon color="primary">add</Icon>
56
- </IconButton>
57
- <Menu
58
- id="add-menu"
59
- MenuListProps={{
60
- "aria-labelledby": "addBtnGroup"
61
- }}
62
- anchorEl={anchorEl}
63
- open={open}
64
- onClose={handleClose}
65
- >
66
- <MenuItem aria-label="add-card" onClick={handleEdit(new StripePaymentMethod({ type: "card" }))}>
67
- <Icon sx={{mr: "3px"}}>credit_card</Icon> {Locale.label("donation.paymentMethods.addCard")}
68
- </MenuItem>
69
- <MenuItem aria-label="add-bank" onClick={handleEdit(new StripePaymentMethod({ type: "bank" }))}>
70
- <Icon sx={{mr: "3px"}}>account_balance</Icon> {Locale.label("donation.paymentMethods.addBank")}
71
- </MenuItem>
72
- </Menu>
73
- </>
74
- );
75
- }
76
-
77
- const getNewContent = () => {
78
- if (!UserHelper.checkAccess(Permissions.givingApi.settings.edit) && props.appName !== "B1App") return null;
79
- return <MenuIcon />;
80
- }
81
-
82
- const getEditOptions = (pm: StripePaymentMethod) => {
83
- if (!UserHelper.checkAccess(Permissions.givingApi.settings.edit) && props.appName !== "B1App") return null;
84
- return <a aria-label="edit-button" onClick={handleEdit(pm)} href="about:blank"><Icon>edit</Icon></a>;
85
- }
86
-
87
- const getPMIcon = (type: string) => (type === "card" ? <Icon>credit_card</Icon> : <Icon>account_balance</Icon>)
88
-
89
- const getPaymentRows = () => {
90
- let rows: React.ReactElement[] = [];
91
-
92
- props.paymentMethods.forEach((method: StripePaymentMethod) => {
93
- rows.push(
94
- <TableRow key={method.id}>
95
- <TableCell className="capitalize">{getPMIcon(method.type)} {method.name + " ****" + method.last4}</TableCell>
96
- <TableCell>{method?.status === "new" && <a href="about:blank" aria-label="verify-account" onClick={handleEdit(method, true)}>{Locale.label("donation.paymentMethods.verify")}</a>}</TableCell>
97
- <TableCell align="right">{getEditOptions(method)}</TableCell>
98
- </TableRow>
99
- );
100
- });
101
- return rows;
102
- }
103
-
104
- const PaymentMethodsTable = () => {
105
- if (!props.paymentMethods) return <Loading></Loading>
106
- if (props.paymentMethods.length) {
107
- return (
108
- <Table>
109
- <TableBody>
110
- {getPaymentRows()}
111
- </TableBody>
112
- </Table>
113
- );
114
- }
115
- else return <div>{Locale.label("donation.paymentMethods.noMethod")}</div>
116
- }
117
-
118
- const EditForm = () => (
119
- <Elements stripe={props.stripePromise}>
120
- {editPaymentMethod.type === "card" && <CardForm card={editPaymentMethod} customerId={props.customerId} person={props.person} setMode={setMode} deletePayment={handleDelete} updateList={(message) => { props.dataUpdate(message) }} />}
121
- {editPaymentMethod.type === "bank" && <BankForm bank={editPaymentMethod} showVerifyForm={verify} customerId={props.customerId} person={props.person} setMode={setMode} deletePayment={handleDelete} updateList={(message) => { props.dataUpdate(message) }} />}
122
- </Elements>
123
- )
124
-
125
- const PaymentMethods = () => {
126
- if (mode === "display") {
127
- return (
128
- <DisplayBox aria-label="payment-methods-box" headerIcon="credit_card" headerText="Payment Methods" editContent={getNewContent()}>
129
- <PaymentMethodsTable></PaymentMethodsTable>
130
- </DisplayBox>
131
- );
132
- }
133
- else return <EditForm></EditForm>;
134
- }
135
-
136
- return props.stripePromise ? <PaymentMethods></PaymentMethods> : null;
137
- }
@@ -1,123 +0,0 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { DisplayBox } from "../../components";
5
- import { ApiHelper, UserHelper, CurrencyHelper, DateHelper, Locale } from "../../helpers";
6
- import { Permissions, SubscriptionInterface } from "@churchapps/helpers";
7
- import { RecurringDonationsEdit } from ".";
8
- import { Icon, Table, TableBody, TableCell, TableRow, TableHead } from "@mui/material";
9
-
10
- interface Props { customerId: string, paymentMethods: any[], appName: string, dataUpdate: (message?: string) => void, };
11
-
12
- export const RecurringDonations: React.FC<Props> = (props) => {
13
- const [subscriptions, setSubscriptions] = React.useState<SubscriptionInterface[]>([]);
14
- const [mode, setMode] = React.useState("display");
15
- const [editSubscription, setEditSubscription] = React.useState<SubscriptionInterface>();
16
-
17
- const loadData = () => {
18
- if (props.customerId) {
19
- ApiHelper.get("/customers/" + props.customerId + "/subscriptions", "GivingApi").then(subResult => {
20
- const subs: SubscriptionInterface[] = [];
21
- const requests = subResult.data?.map((s: any) => ApiHelper.get("/subscriptionfunds?subscriptionId=" + s.id, "GivingApi").then(subFunds => {
22
- s.funds = subFunds;
23
- subs.push(s);
24
- }));
25
- return requests && Promise.all(requests).then(() => {
26
- setSubscriptions(subs);
27
- });
28
- });
29
- }
30
- }
31
-
32
- const handleUpdate = (message: string) => {
33
- loadData();
34
- setMode("display");
35
- if (message) props.dataUpdate(message);
36
- }
37
-
38
- const handleEdit = (sub: SubscriptionInterface) => (e: React.MouseEvent) => {
39
- e.preventDefault();
40
- setEditSubscription(sub);
41
- setMode("edit");
42
- }
43
-
44
- const getPaymentMethod = (sub: SubscriptionInterface) => {
45
- const pm = props.paymentMethods.find((pm: any) => pm.id === (sub.default_payment_method || sub.default_source));
46
- if (!pm) return <span style={{ color: "red" }}>{Locale.label("donation.recurring.notFound")}</span>;
47
- return `${pm.name} ****${pm.last4}`;
48
- }
49
-
50
- const getInterval = (subscription: SubscriptionInterface) => {
51
- let interval = subscription.plan.interval_count + " " + subscription.plan.interval;
52
- return subscription.plan.interval_count > 1 ? interval + "s" : interval;
53
- }
54
-
55
- const getFunds = (subscription: SubscriptionInterface) => {
56
- let result: React.ReactElement[] = [];
57
- subscription.funds.forEach((fund: any) => {
58
- result.push(
59
- <div key={subscription.id + fund.id}>
60
- {fund.name} <span style={{ float: "right" }}>{CurrencyHelper.formatCurrency(fund.amount)}</span>
61
- </div>
62
- );
63
- });
64
- const total = (subscription.plan.amount / 100);
65
- result.push(
66
- <div key={subscription.id + "-total"} style={{ borderTop: "solid #dee2e6 1px" }}>
67
- Total <span style={{ float: "right" }}>{CurrencyHelper.formatCurrency(total)}</span>
68
- </div>
69
- );
70
- return result;
71
- }
72
-
73
- const getEditOptions = (sub: SubscriptionInterface) => {
74
- if ((!UserHelper.checkAccess(Permissions.givingApi.settings.edit) && props.appName !== "B1App") || props?.paymentMethods?.length === 0) return null;
75
- return <a aria-label="edit-button" onClick={handleEdit(sub)} href="about:blank"><Icon>edit</Icon></a>;
76
- }
77
-
78
- const getTableHeader = () => {
79
- let result: React.ReactElement[] = [];
80
- result.push(<TableRow key="header" sx={{textAlign: "left"}}><TableCell><b>{Locale.label("donation.recurring.startDate")}</b></TableCell><TableCell><b>{Locale.label("donation.recurring.amount")}</b></TableCell><TableCell><b>{Locale.label("donation.recurring.interval")}</b></TableCell><TableCell><b>{Locale.label("donation.recurring.paymentMethod")}</b></TableCell>{props?.paymentMethods?.length > 0 && <TableCell></TableCell>}</TableRow>);
81
- return result;
82
- }
83
-
84
- const getTableRows = () => {
85
- let rows: React.ReactElement[] = [];
86
-
87
- subscriptions.forEach((sub: any) => {
88
- rows.push(
89
- <TableRow key={sub.id}>
90
- <TableCell>{DateHelper.prettyDate(new Date(sub.billing_cycle_anchor * 1000))}</TableCell>
91
- <TableCell>{getFunds(sub)}</TableCell>
92
- <TableCell>{Locale.label("donation.recurring.every")} {getInterval(sub)}</TableCell>
93
- <TableCell className="capitalize">{getPaymentMethod(sub)}</TableCell>
94
- <TableCell align="right">{getEditOptions(sub)}</TableCell>
95
- </TableRow>
96
- );
97
- });
98
- return rows;
99
- }
100
-
101
- const getSubscriptionsTable = () => (
102
- <Table>
103
- <TableHead>{getTableHeader()}</TableHead>
104
- <TableBody>{getTableRows()}</TableBody>
105
- </Table>
106
- )
107
-
108
- React.useEffect(loadData, []); //eslint-disable-line
109
-
110
- if (!subscriptions.length) return null;
111
- if (mode === "display") {
112
- return (
113
- <DisplayBox data-testid="recurring-donations" headerIcon="restart_alt" headerText="Recurring Donations">
114
- {getSubscriptionsTable()}
115
- </DisplayBox>
116
- );
117
- }
118
- if (mode === "edit" && editSubscription) {
119
- return (
120
- <RecurringDonationsEdit customerId={props.customerId} paymentMethods={props.paymentMethods} editSubscription={editSubscription} subscriptionUpdated={handleUpdate} />
121
- );
122
- }
123
- }
@@ -1,95 +0,0 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { ApiHelper, Locale } from "../../helpers";
5
- import { InputBox } from "../../components";
6
- import { StripePaymentMethod, SubscriptionInterface } from "@churchapps/helpers";
7
- import { FormControl, Grid, InputLabel, MenuItem, Select, SelectChangeEvent, TextField } from "@mui/material"
8
- import { DonationHelper } from "../../helpers"
9
-
10
- interface Props { subscriptionUpdated: (message?: string) => void, customerId: string, paymentMethods: StripePaymentMethod[], editSubscription: SubscriptionInterface };
11
-
12
- export const RecurringDonationsEdit: React.FC<Props> = (props) => {
13
- const [editSubscription, setEditSubscription] = React.useState<SubscriptionInterface>(props.editSubscription);
14
- const [interval, setInterval] = React.useState("one_month");
15
-
16
- const handleCancel = () => { props.subscriptionUpdated(); }
17
- const handleSave = () => {
18
- let sub = { ...editSubscription } as SubscriptionInterface;
19
- const pmFound = props.paymentMethods.find((pm: StripePaymentMethod) => pm.id === sub.id);
20
- if (!pmFound) {
21
- let pm = props.paymentMethods[0];
22
- sub.default_payment_method = pm.type === "card" ? pm.id : null;
23
- sub.default_source = pm.type === "bank" ? pm.id : null;
24
- }
25
- ApiHelper.post("/subscriptions", [sub], "GivingApi").then(() => props.subscriptionUpdated(Locale.label("donation.donationForm.recurringUpdated")))
26
- }
27
-
28
- const handleDelete = () => {
29
- const conf = window.confirm(Locale.label("donation.donationForm.confirmDelete"));
30
- if (!conf) return;
31
- let promises = [];
32
- promises.push(ApiHelper.delete("/subscriptions/" + props.editSubscription.id, "GivingApi"));
33
- promises.push(ApiHelper.delete("/subscriptionfunds/subscription/" + props.editSubscription.id, "GivingApi"));
34
- Promise.all(promises).then(() => props.subscriptionUpdated(Locale.label("donation.donationForm.cancelled")));
35
- }
36
-
37
- const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | SelectChangeEvent<string>) => {
38
- let sub = { ...editSubscription } as SubscriptionInterface;
39
- let value = e.target.value;
40
- switch (e.target.name) {
41
- case "method":
42
- let pm = props.paymentMethods.find((pm: StripePaymentMethod) => pm.id === value);
43
- sub.default_payment_method = pm.type === "card" ? value : null;
44
- sub.default_source = pm.type === "bank" ? value : null;
45
- break;
46
- case "interval":
47
- setInterval(value);
48
- const inter = DonationHelper.getInterval(value);
49
- sub.plan.interval_count = inter.interval_count;
50
- sub.plan.interval = inter.interval;
51
- break;
52
- }
53
- setEditSubscription(sub);
54
- }
55
-
56
- const getFields = () => (
57
- <>
58
- <Grid container spacing={3}>
59
- <Grid size={{ xs: 12, md: 6 }}>
60
- <FormControl fullWidth>
61
- <InputLabel>{Locale.label("donation.donationForm.method")}</InputLabel>
62
- <Select label={Locale.label("donation.donationForm.method")} name="method" aria-label="method" value={editSubscription.default_payment_method || editSubscription.default_source} className="capitalize" onChange={handleChange}>
63
- {props.paymentMethods.map((paymentMethod: any, i: number) => <MenuItem key={i} value={paymentMethod.id}>{paymentMethod.name} ****{paymentMethod.last4}</MenuItem>)}
64
- </Select>
65
- </FormControl>
66
- </Grid>
67
- <Grid size={{ xs: 12, md: 6 }}>
68
- <FormControl fullWidth>
69
- <InputLabel>{Locale.label("donation.donationForm.frequency")}</InputLabel>
70
- <Select label={Locale.label("donation.donationForm.frequency")} name="interval" aria-label="interval" value={interval} onChange={handleChange}>
71
- <MenuItem value="one_week">{Locale.label("donation.donationForm.weekly")}</MenuItem>
72
- <MenuItem value="two_week">{Locale.label("donation.donationForm.biWeekly")}</MenuItem>
73
- <MenuItem value="one_month">{Locale.label("donation.donationForm.monthly")}</MenuItem>
74
- <MenuItem value="three_month">{Locale.label("donation.donationForm.quarterly")}</MenuItem>
75
- <MenuItem value="one_year">{Locale.label("donation.donationForm.annually")}</MenuItem>
76
- </Select>
77
- </FormControl>
78
- </Grid>
79
- </Grid>
80
- </>
81
- )
82
-
83
- React.useEffect(() => {
84
- if (props.editSubscription) {
85
- const keyName = DonationHelper.getIntervalKeyName(props.editSubscription.plan.interval_count, props.editSubscription.plan.interval);
86
- setInterval(keyName);
87
- }
88
- }, [props.editSubscription]);
89
-
90
- return (
91
- <InputBox aria-label="person-details-box" headerIcon="person" headerText={Locale.label("donation.donationForm.editRecurring")} ariaLabelSave="save-button" ariaLabelDelete="delete-button" cancelFunction={handleCancel} deleteFunction={handleDelete} saveFunction={handleSave}>
92
- {getFields()}
93
- </InputBox>
94
- );
95
- }
@@ -1,9 +0,0 @@
1
- export { BankForm } from "./BankForm";
2
- export { CardForm } from "./CardForm";
3
- export { DonationForm } from "./DonationForm";
4
- export { NonAuthDonation } from "./NonAuthDonation";
5
- export { PaymentMethods } from "./PaymentMethods";
6
- export { RecurringDonations } from "./RecurringDonations";
7
- export { RecurringDonationsEdit } from "./RecurringDonationsEdit";
8
- export { FundDonations } from "./FundDonations";
9
- export { FundDonation } from "./FundDonation";
@@ -1,3 +0,0 @@
1
- export { FundDonations } from "./components/FundDonations";
2
- export { NonAuthDonation } from "./components/NonAuthDonation";
3
- export { DonationPage } from "./DonationPage";