PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / trunk
GiveWP – Donation Plugin and Fundraising Platform vtrunk
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 / Campaigns / resources / admin / components / CampaignFormModal / index.tsx
give / src / Campaigns / resources / admin / components / CampaignFormModal Last commit date
CampaignFormModal.module.scss 1 year ago GoalTypeIcons.tsx 1 year ago index.tsx 8 months ago types.ts 8 months ago
index.tsx
351 lines
1 import {FormProvider, SubmitHandler, useForm} from 'react-hook-form';
2 import {__, sprintf} from '@wordpress/i18n';
3 import styles from './CampaignFormModal.module.scss';
4 import FormModal from '../FormModal';
5 import CampaignsApi from '../api';
6 import {CampaignFormInputs, CampaignModalProps, GoalTypeOption as GoalTypeOptionType} from './types';
7 import {useRef, useState} from 'react';
8 import {Currency, Upload} from '../Inputs';
9 import {
10 AmountFromSubscriptionsIcon,
11 AmountIcon,
12 DonationsIcon,
13 DonorsFromSubscriptionsIcon,
14 DonorsIcon,
15 SubscriptionsIcon,
16 } from './GoalTypeIcons';
17 import {getGiveCampaignsListTableWindowData} from '../CampaignsListTable';
18 import {amountFormatter} from '@givewp/campaigns/utils';
19 import TextareaControl from '../CampaignDetailsPage/Components/TextareaControl';
20 import {CampaignGoalInputAttributes, isValidGoalType} from '../../constants/goalInputAttributes';
21
22 const {currency, isRecurringEnabled} = getGiveCampaignsListTableWindowData();
23 const currencyFormatter = amountFormatter(currency);
24
25 /**
26 * @since 4.0.0
27 */
28 const getGoalTypeIcon = (type: string) => {
29 switch (type) {
30 case 'amount':
31 return <AmountIcon />;
32 case 'donations':
33 return <DonationsIcon />;
34 case 'donors':
35 return <DonorsIcon />;
36 case 'amountFromSubscriptions':
37 return <AmountFromSubscriptionsIcon />;
38 case 'subscriptions':
39 return <SubscriptionsIcon />;
40 case 'donorsFromSubscriptions':
41 return <DonorsFromSubscriptionsIcon />;
42 }
43 };
44
45 /**
46 * Goal Type Option component
47 *
48 * @since 4.0.0
49 */
50 const GoalTypeOption = ({type, label, description, selected, register}: GoalTypeOptionType) => {
51 const divRef = useRef(null);
52 const labelRef = useRef(null);
53
54 const handleDivClick = () => {
55 labelRef.current.click();
56 };
57
58 return (
59 <div
60 className={`${styles.goalTypeOption} ${selected ? styles.goalTypeOptionSelected : ''}`}
61 ref={divRef}
62 onClick={handleDivClick}
63 >
64 <div className={styles.goalTypeOptionIcon}>{getGoalTypeIcon(type)}</div>
65 <div className={styles.goalTypeOptionText}>
66 <label ref={labelRef}>
67 <input type="radio" value={type} {...register('goalType')} />
68 {label}
69 </label>
70 <span>{description}</span>
71 </div>
72 </div>
73 );
74 };
75
76 /**
77 * Campaign Form Modal component
78 *
79 * @since 4.13.0 remove unused date inputs
80 * @since 4.0.0
81 */
82 export default function CampaignFormModal({isOpen, handleClose, apiSettings}: CampaignModalProps) {
83 const API = new CampaignsApi(apiSettings);
84 const [step, setStep] = useState<number>(1);
85
86 const methods = useForm<CampaignFormInputs>({
87 defaultValues: {
88 title: '',
89 shortDescription: '',
90 image: '',
91 goalType: 'amount',
92 goal: null,
93 },
94 });
95
96 const {
97 register,
98 handleSubmit,
99 formState: {errors, isDirty, isSubmitting},
100 setValue,
101 watch,
102 trigger,
103 } = methods;
104
105 const image = watch('image');
106 const selectedGoalType = watch('goalType');
107 const goal = watch('goal');
108
109 const getFormModalTitle = () => {
110 switch (step) {
111 case 1:
112 return __('Tell us about your fundraising cause', 'give');
113 case 2:
114 return __('Set up your campaign goal', 'give');
115 }
116
117 return null;
118 };
119
120 const goalInputAttribute =
121 selectedGoalType && isValidGoalType(selectedGoalType)
122 ? new CampaignGoalInputAttributes(selectedGoalType, currency)
123 : new CampaignGoalInputAttributes('amount', currency);
124
125 const requiredAsterisk = <span className={`givewp-field-required ${styles.fieldRequired}`}>*</span>;
126
127 const validateTitle = async () => {
128 return await trigger('title');
129 };
130
131 const onSubmit: SubmitHandler<CampaignFormInputs> = async (inputs, event) => {
132 event.preventDefault();
133
134 if (step !== 2) {
135 return;
136 }
137
138 try {
139 const response = await API.fetchWithArgs('', inputs, 'POST');
140
141 handleClose(response);
142 } catch (error) {
143 console.error('Error submitting campaign campaign', error);
144 }
145 };
146
147 return (
148 <FormProvider {...methods}>
149 <FormModal
150 isOpen={isOpen}
151 handleClose={handleClose}
152 title={getFormModalTitle()}
153 handleSubmit={handleSubmit(onSubmit)}
154 errors={[]}
155 className={styles.campaignForm}
156 >
157 {step === 1 && (
158 <>
159 <div className="givewp-campaigns__form-row">
160 <label htmlFor="title">
161 {__("What's the title of your campaign?", 'give')} {requiredAsterisk}
162 </label>
163 <span>{__("Give your campaign a title that tells donors what it's about.", 'give')}</span>
164 <input
165 type="text"
166 {...register('title', {required: __('The campaign title is required.', 'give')})}
167 aria-invalid={errors.title ? 'true' : 'false'}
168 placeholder={__('Eg. Holiday Food Drive', 'give')}
169 onBlur={validateTitle}
170 />
171 {errors.title && (
172 <div className={'givewp-campaigns__form-errors'}>
173 <p>{errors.title.message}</p>
174 </div>
175 )}
176 </div>
177 <div className="givewp-campaigns__form-row">
178 <label htmlFor="shortDescription">{__("What's your campaign about?", 'give')}</label>
179 <span>{__('Let your donors know the story behind your campaign.', 'give')}</span>
180 <TextareaControl
181 name="shortDescription"
182 rows={4}
183 maxLength={120}
184 placeholder={__('Brief description for your campaign.', 'give')}
185 />
186 </div>
187 <div className="givewp-campaigns__form-row">
188 <label htmlFor="image">{__('Add a cover image for your campaign.', 'give')}</label>
189 <span>{__('Upload an image to represent and inspire your campaign.', 'give')}</span>
190 <Upload
191 id="givewp-campaigns-upload-cover-image"
192 label={__('Cover', 'give')}
193 actionLabel={__('Select to upload', 'give')}
194 value={image}
195 onChange={(coverImageUrl, coverImageAlt) => {
196 setValue('image', coverImageUrl);
197 }}
198 reset={() => setValue('image', '')}
199 />
200 </div>
201 <button
202 type="submit"
203 onClick={async () => (await validateTitle()) && setStep(2)}
204 className={`button button-primary ${!isDirty ? 'disabled' : ''}`}
205 aria-disabled={!isDirty}
206 disabled={!isDirty}
207 >
208 {__('Continue', 'give')}
209 </button>
210 </>
211 )}
212 {step === 2 && (
213 <>
214 <div className="givewp-campaigns__form-row">
215 <label htmlFor="goalType">
216 {__('How would you like to set your goal?', 'give')} {requiredAsterisk}
217 </label>
218 <span>{__('Set the goal your fundraising efforts will work toward.', 'give')}</span>
219 <div className={styles.goalType}>
220 <GoalTypeOption
221 type={'amount'}
222 label={__('Amount raised', 'give')}
223 description={sprintf(
224 __(
225 'Your goal progress is measured by the total amount of funds raised eg. %s of %s raised.',
226 'give'
227 ),
228 currencyFormatter.format(500),
229 currencyFormatter.format(1000)
230 )}
231 selected={selectedGoalType === 'amount'}
232 register={register}
233 />
234 <GoalTypeOption
235 type={'donations'}
236 label={__('Number of donations', 'give')}
237 description={__(
238 'Your goal progress is measured by the number of donations. eg. 1 of 5 donations.',
239 'give'
240 )}
241 selected={selectedGoalType === 'donations'}
242 register={register}
243 />
244 <GoalTypeOption
245 type={'donors'}
246 label={__('Number of donors', 'give')}
247 description={__(
248 'Your goal progress is measured by the number of donors. eg. 10 of 50 donors have given.',
249 'give'
250 )}
251 selected={selectedGoalType === 'donors'}
252 register={register}
253 />
254 {isRecurringEnabled && (
255 <>
256 <GoalTypeOption
257 type={'amountFromSubscriptions'}
258 label={__('Recurring amount raised', 'give')}
259 description={__(
260 'Only the first donation amount of a recurring donation is counted toward the goal.',
261 'give'
262 )}
263 selected={selectedGoalType === 'amountFromSubscriptions'}
264 register={register}
265 />
266 <GoalTypeOption
267 type={'subscriptions'}
268 label={__('Number of recurring donations', 'give')}
269 description={__(
270 'Only the first donation of a recurring donation is counted toward the goal.',
271 'give'
272 )}
273 selected={selectedGoalType === 'subscriptions'}
274 register={register}
275 />
276 <GoalTypeOption
277 type={'donorsFromSubscriptions'}
278 label={__('Number of recurring donors', 'give')}
279 description={__(
280 'Only the donors that subscribed to a recurring donation are counted toward the goal.',
281 'give'
282 )}
283 selected={selectedGoalType === 'donorsFromSubscriptions'}
284 register={register}
285 />
286 </>
287 )}
288 </div>
289 {errors.goalType && (
290 <div className={'givewp-campaigns__form-errors'}>
291 <p>{errors.goalType.message}</p>
292 </div>
293 )}
294 </div>
295 {selectedGoalType && (
296 <div className="givewp-campaigns__form-row">
297 <label htmlFor="title">
298 {goalInputAttribute.getLabel()} {requiredAsterisk}
299 </label>
300 <span>{goalInputAttribute.getDescription()}</span>
301 {selectedGoalType === 'amount' || selectedGoalType === 'amountFromSubscriptions' ? (
302 <Currency
303 name="goal"
304 currency={currency}
305 placeholder={goalInputAttribute.getPlaceholder()}
306 />
307 ) : (
308 <input
309 type="number"
310 {...register('goal', {valueAsNumber: true})}
311 aria-invalid={errors.goal ? 'true' : 'false'}
312 placeholder={goalInputAttribute.getPlaceholder()}
313 />
314 )}
315 {errors.goal && (
316 <div className={'givewp-campaigns__form-errors'}>
317 <p>{errors.goal.message}</p>
318 </div>
319 )}
320 </div>
321 )}
322 <div
323 className="givewp-campaigns__form-row givewp-campaigns__form-row--half"
324 style={{marginBottom: 0}}
325 >
326 <button
327 type="button"
328 onClick={() => setStep(1)}
329 className={`button button-secondary ${styles.button} ${styles.previousButton}`}
330 >
331 {__('Previous', 'give')}
332 </button>
333
334 <button
335 type="submit"
336 className={`button button-primary ${styles.button} ${
337 !goal || isSubmitting ? 'disabled' : ''
338 }`}
339 aria-disabled={!goal || isSubmitting}
340 disabled={!goal || isSubmitting}
341 >
342 {__('Continue', 'give')}
343 </button>
344 </div>
345 </>
346 )}
347 </FormModal>
348 </FormProvider>
349 );
350 }
351