PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 3.11.0
GiveWP – Donation Plugin and Fundraising Platform v3.11.0
4.16.7.2 4.16.7.1 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 / DonationForms / resources / registrars / templates / groups / BillingAddress.tsx
give / src / DonationForms / resources / registrars / templates / groups Last commit date
DonationAmount 2 years ago Authentication.tsx 2 years ago BillingAddress.tsx 2 years ago Name.tsx 2 years ago
BillingAddress.tsx
242 lines
1 import type { BillingAddressProps } from "@givewp/forms/propTypes";
2 import { FC, useEffect, useState } from "react";
3 import { __ } from "@wordpress/i18n";
4 import { ErrorMessage } from "@hookform/error-message";
5 import { useCallback } from "@wordpress/element";
6
7 /**
8 * @since 3.0.0
9 */
10 type StatesJsonResponse = {
11 state_label: string;
12 show_field: boolean;
13 states_require: boolean;
14 city_require: boolean;
15 zip_require: boolean;
16 states_found: boolean;
17 states: {[key: string]: string};
18 };
19
20 /**
21 * @since 3.0.0
22 */
23 type State = {
24 label: string;
25 value: string;
26 };
27
28 /**
29 * Get states from the server based on the country value
30 *
31 * @since 3.0.0
32 */
33 async function getStates(url, country) {
34 return await fetch(url, {
35 method: 'POST',
36 headers: {
37 'Content-type': 'application/x-www-form-urlencoded',
38 },
39 body: 'country=' + country,
40 });
41 }
42
43 /**
44 * This component is used to dynamically update the state field based on the country value
45 *
46 * @since 3.4.0 Set current state value to the state input field
47 * @since 3.0.0
48 */
49 function StateFieldContainer({
50 apiUrl,
51 state: HiddenStateField,
52 setCityRequired,
53 setZipRequired,
54 nodeName,
55 }: {
56 apiUrl: string;
57 state: FC;
58 setCityRequired: Function;
59 setZipRequired: Function;
60 nodeName: string;
61 }) {
62 const Label = window.givewp.form.templates.layouts.fieldLabel;
63 const FieldError = window.givewp.form.templates.layouts.fieldError;
64 const NodeWrapper = window.givewp.form.templates.layouts.wrapper;
65 const {useWatch, useFormContext, useFormState} = window.givewp.form.hooks;
66 const {errors} = useFormState();
67 const {setValue, clearErrors} = useFormContext();
68 const country = useWatch({name: 'country'});
69 const [states, setStates] = useState<State[]>([]);
70 const [statesLoading, setStatesLoading] = useState<boolean>(false);
71 const [stateLabel, setStateLabel] = useState<string>('State');
72 const [showField, setShowField] = useState<boolean>(true);
73 const [stateRequired, setStateRequired] = useState<boolean>(false);
74
75 const updateStateValue = useCallback(
76 (event) => {
77 clearErrors('state');
78 setValue('state', event.target.value);
79 },
80 [setValue, clearErrors]
81 );
82
83 const fieldError = errors?.state;
84
85 useEffect(() => {
86 if (!country) {
87 setStates([]);
88 return;
89 }
90
91 setStatesLoading(true);
92 const response = getStates(apiUrl, country);
93 response
94 .then((data) => {
95 if (data.ok) {
96 setStatesLoading(false);
97 setValue('state', '');
98
99 return data.json();
100 }
101 throw new Error('Fail to load states from ' + country);
102 })
103 .then((responseJson: StatesJsonResponse) => {
104 setStateLabel(responseJson.state_label);
105 setShowField(responseJson.show_field);
106 setStateRequired(responseJson.states_require);
107 setCityRequired(responseJson.city_require);
108 setZipRequired(responseJson.zip_require);
109
110 if (responseJson.states_found) {
111 const stateResponse: State[] = [];
112 Object.entries(responseJson.states).forEach(([key, value]) => {
113 if (key) {
114 stateResponse.push({value: key, label: value});
115 }
116 });
117
118 setStates(stateResponse);
119 } else {
120 setStates([]);
121 }
122 })
123 .catch((error) => {
124 console.log(error);
125 });
126 }, [country]);
127
128 if (!showField) {
129 return <HiddenStateField />;
130 }
131
132 if (states.length > 0) {
133 return (
134 /**
135 * TODO: replace with template api component
136 */
137 <NodeWrapper nodeType="fields" type="select" htmlTag="div" name="state">
138 <label>
139 <Label label={stateLabel} required={stateRequired} />
140
141 <select
142 onChange={updateStateValue}
143 disabled={statesLoading}
144 aria-invalid={fieldError ? 'true' : 'false'}
145 >
146 {statesLoading ? (
147 <>
148 <option hidden>{__('Loading...', 'give')}</option>
149 <option disabled>{__('Loading...', 'give')}</option>
150 </>
151 ) : (
152 <>
153 <option hidden>{__(`Select ${stateLabel}`, 'give')}</option>
154 <option disabled>{__(`Select ${stateLabel}`, 'give')}</option>
155 </>
156 )}
157 {states.map(({label, value}) => (
158 <option key={value} value={value}>
159 {label ?? value}
160 </option>
161 ))}
162 </select>
163
164 <HiddenStateField />
165
166 <ErrorMessage
167 errors={errors}
168 name={'state'}
169 render={({message}) => <FieldError error={message} name={nodeName} />}
170 />
171 </label>
172 </NodeWrapper>
173 );
174 }
175
176 return (
177 /**
178 * TODO: replace with template api component
179 */
180 <NodeWrapper nodeType="fields" type="text" htmlTag="div" name="state">
181 <label>
182 <Label label={stateLabel ?? __('State', 'give')} required={stateRequired} />
183
184 <input
185 type="text"
186 onChange={updateStateValue}
187 aria-invalid={fieldError ? 'true' : 'false'}
188 placeholder={statesLoading ? __('Loading...', 'give') : ''}
189 disabled={statesLoading}
190 />
191
192 <HiddenStateField />
193
194 <ErrorMessage
195 errors={errors}
196 name="state"
197 render={({message}) => <FieldError error={message} name={nodeName} />}
198 />
199 </label>
200 </NodeWrapper>
201 );
202 }
203
204 /**
205 * @since 3.4.0 Update city and zip components before rendering to display required asterisk
206 * @since 3.0.0
207 */
208 export default function BillingAddress({
209 groupLabel,
210 nodeComponents: {country: Country, address1: Address1, address2: Address2, city: City, state, zip: Zip},
211 apiUrl,
212 name,
213 }: BillingAddressProps) {
214 // these are necessary to set the required indicator on the city and zip field labels
215 // the actual validation will come from the server as we don't yet have the ability to update the actual client validation rules here
216 const [cityRequired, setCityRequired] = useState(false);
217 const [zipRequired, setZipRequired] = useState(false);
218
219 const CityWithRequired = () => <City validationRules={{required: cityRequired}} />
220 const ZipWithRequired = () => <Zip validationRules={{required: zipRequired}} />
221
222 return (
223 <>
224 <fieldset>
225 {groupLabel && <legend>{groupLabel}</legend>}
226 <Country />
227 <Address1 />
228 <Address2 />
229 <CityWithRequired />
230 <StateFieldContainer
231 apiUrl={apiUrl}
232 state={state}
233 setCityRequired={setCityRequired}
234 setZipRequired={setZipRequired}
235 nodeName={name}
236 />
237 <ZipWithRequired />
238 </fieldset>
239 </>
240 );
241 }
242