PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.16.6
GiveWP – Donation Plugin and Fundraising Platform v4.16.6
4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / Subscriptions / resources / admin / components / SubscriptionDetailsPage / Tabs / Donations / AddRenewalModal / index.tsx
give / src / Subscriptions / resources / admin / components / SubscriptionDetailsPage / Tabs / Donations / AddRenewalModal Last commit date
index.tsx 9 months ago styles.module.scss 11 months ago
index.tsx
273 lines
1 /**
2 * External Dependencies
3 */
4 import { useState } from 'react';
5
6 /**
7 * WordPress Dependencies
8 */
9 import { __ } from '@wordpress/i18n';
10 import { Spinner } from '@wordpress/components';
11
12 /**
13 * Internal Dependencies
14 */
15 import ModalDialog from '@givewp/components/AdminUI/ModalDialog';
16 import styles from './styles.module.scss';
17 import { CurrencyControl } from '@givewp/form-builder-library';
18 import { useFormContext } from 'react-hook-form';
19 import { CurrencyCode } from '@givewp/form-builder-library/build/CurrencyControl/CurrencyCode';
20 import { formatToDateLocalInput } from '@givewp/admin/common';
21 import { InfoIcon } from '@givewp/components/AdminDetailsPage/Icons';
22
23 /**
24 * @since 4.8.0
25 */
26 interface AddRenewalDialogProps {
27 isOpen: boolean;
28 handleClose: () => void;
29 handleConfirm: (data: any) => void;
30 }
31
32 interface RenewalData {
33 amount: number | string;
34 date: string;
35 updateRenewalDate: boolean;
36 transactionId?: string;
37 }
38
39 /**
40 * @since 4.8.0
41 */
42 export default function AddRenewalDialog({
43 isOpen,
44 handleClose,
45 handleConfirm,
46 }: AddRenewalDialogProps) {
47 const { getValues } = useFormContext();
48 const { amount } = getValues();
49
50 const [data, setData] = useState<any>({
51 amount: amount.value,
52 date: formatToDateLocalInput(new Date().toISOString()),
53 updateRenewalDate: false,
54 transactionId: '',
55 });
56 const [errors, setErrors] = useState<Partial<any>>({});
57 const [isLoading, setIsLoading] = useState(false);
58
59
60 const handleFieldChange = (field: keyof RenewalData, value: typeof data[keyof RenewalData]) => {
61 setData(prev => ({
62 ...prev,
63 [field]: value
64 }));
65
66 // Clear error for this field if it exists
67 if (errors[field]) {
68 setErrors(prev => ({
69 ...prev,
70 [field]: ''
71 }));
72 }
73 };
74
75 const validateForm = (): boolean => {
76 const newErrors: Partial<RenewalData> = {};
77
78 if (!data.amount) {
79 newErrors.amount = __('Amount is required', 'give');
80 }
81
82 if (!data.date) {
83 newErrors.date = __('Date is required', 'give');
84 }
85
86 setErrors(newErrors);
87 return Object.keys(newErrors).length === 0;
88 };
89
90 const resetData = () => {
91 setData({
92 amount: amount.value,
93 date: formatToDateLocalInput(new Date().toISOString()),
94 updateRenewalDate: false,
95 transactionId: '',
96 });
97 };
98
99 const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
100 e.stopPropagation();
101 e.preventDefault();
102
103 if (!validateForm() || isLoading) {
104 return;
105 }
106
107 setIsLoading(true);
108
109 try {
110 await handleConfirm({
111 ...data,
112 amount: {
113 value: data.amount,
114 currency: amount.currency,
115 },
116 });
117 resetData();
118 setErrors({});
119 setIsLoading(false);
120 handleClose();
121 } catch (error) {
122 console.error('Error adding renewal:', error);
123 setIsLoading(false);
124 }
125 };
126
127 const handleCancel = () => {
128 resetData();
129 setErrors({});
130 handleClose();
131 };
132
133 const isFormValid = data.amount && data.date;
134 const isSubmitDisabled = !isFormValid || isLoading;
135
136 return (
137 <ModalDialog
138 isOpen={isOpen}
139 showHeader={true}
140 handleClose={handleCancel}
141 title={__('Renewal donation', 'give')}
142 >
143 <div className={styles.addRenewalDialog}>
144 <form onSubmit={handleFormSubmit}>
145 <div className={styles.content}>
146 <div className={styles.formField}>
147 <label htmlFor="amount" className={styles.label}>
148 {__('Amount', 'give')}
149 </label>
150 <CurrencyControl
151 id="amount"
152 name="amount"
153 className={`${styles.input} ${errors.amount ? styles.inputError : ''}`}
154 currency={amount.currency as CurrencyCode}
155 disabled={false}
156 placeholder={__('Enter amount', 'give')}
157 value={data.amount}
158 onValueChange={(value) => {
159 handleFieldChange('amount', Number(value ?? 0));
160 }}
161 />
162 {errors.amount && (
163 <div
164 id="amount-error"
165 className={styles.errorMessage}
166 role="alert"
167 aria-live="polite"
168 >
169 {errors.amount}
170 </div>
171 )}
172 </div>
173
174 <div className={styles.formField}>
175 <label htmlFor="date" className={styles.label}>
176 {__('Date', 'give')}
177 </label>
178 <input
179 type="date"
180 id="date"
181 className={`${styles.input} ${errors.date ? styles.inputError : ''}`}
182 value={data.date}
183 onChange={(e) => {
184 handleFieldChange('date', e.target.value);
185 }}
186 />
187 {errors.date && (
188 <div
189 id="date-error"
190 className={styles.errorMessage}
191 role="alert"
192 aria-live="polite"
193 >
194 {errors.date}
195 </div>
196 )}
197 </div>
198
199 <div className={styles.checkboxField}>
200 <label className={styles.checkboxLabel}>
201 <input
202 type="checkbox"
203 className={styles.checkbox}
204 checked={data.updateRenewalDate}
205 onChange={(e) => handleFieldChange('updateRenewalDate', e.target.checked)}
206 aria-describedby="update-renewal-date-description"
207 />
208 <span id="update-renewal-date-description">
209 {__('Update renewal date', 'give')}
210 </span>
211 </label>
212 </div>
213
214 <div className={styles.formField}>
215 <label htmlFor="transactionId" className={styles.label}>
216 {__('Transaction ID', 'give')} <span className={styles.optional}>({__('optional', 'give')})</span>
217 </label>
218 <input
219 type="text"
220 id="transactionId"
221 className={`${styles.input} ${errors.transactionId ? styles.inputError : ''}`}
222 value={data.transactionId}
223 onChange={(e) => handleFieldChange('transactionId', e.target.value)}
224 />
225 {errors.transactionId && (
226 <div
227 id="transactionId-error"
228 className={styles.errorMessage}
229 role="alert"
230 aria-live="polite"
231 >
232 {errors.transactionId}
233 </div>
234 )}
235 </div>
236 </div>
237
238 <div className={styles.buttons}>
239 <button
240 type="submit"
241 className={`button button-primary ${styles.addButton} ${isLoading ? styles.loading : ''}`}
242 disabled={isSubmitDisabled && !isLoading}
243 aria-describedby={!isFormValid ? 'submit-button-description' : undefined}
244 >
245 {isLoading ? (
246 <>
247 {__('Adding renewal', 'give')}
248 <Spinner />
249 </>
250 ) : __('Add renewal', 'give')}
251 </button>
252
253 {!isFormValid && (
254 <div
255 id="submit-button-description"
256 className="screen-reader-text"
257 aria-live="polite"
258 >
259 {__('Please fill in all required fields to continue', 'give')}
260 </div>
261 )}
262 </div>
263
264 <div className={styles.note}>
265 <InfoIcon />
266 {__('Please note that this will not charge the donor nor create the renewal at the gateway.', 'give')}
267 </div>
268 </form>
269 </div>
270 </ModalDialog>
271 );
272 }
273