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