PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 3.3.1
GiveWP – Donation Plugin and Fundraising Platform v3.3.1
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
240 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.0.0
47 */
48 function StateFieldContainer({
49 apiUrl,
50 state: HiddenStateField,
51 setCityRequired,
52 setZipRequired,
53 nodeName,
54 }: {
55 apiUrl: string;
56 state: FC;
57 setCityRequired: Function;
58 setZipRequired: Function;
59 nodeName: string;
60 }) {
61 const Label = window.givewp.form.templates.layouts.fieldLabel;
62 const FieldError = window.givewp.form.templates.layouts.fieldError;
63 const NodeWrapper = window.givewp.form.templates.layouts.wrapper;
64 const {useWatch, useFormContext, useFormState} = window.givewp.form.hooks;
65 const {errors} = useFormState();
66 const {setValue, clearErrors} = useFormContext();
67 const country = useWatch({name: 'country'});
68 const [states, setStates] = useState<State[]>([]);
69 const [statesLoading, setStatesLoading] = useState<boolean>(false);
70 const [stateLabel, setStateLabel] = useState<string>('State');
71 const [showField, setShowField] = useState<boolean>(true);
72 const [stateRequired, setStateRequired] = useState<boolean>(false);
73
74 const updateStateValue = useCallback(
75 (event) => {
76 clearErrors('state');
77 setValue('state', event.target.value);
78 },
79 [setValue, clearErrors]
80 );
81
82 const fieldError = errors?.state;
83
84 useEffect(() => {
85 if (!country) {
86 setStates([]);
87 return;
88 }
89
90 setStatesLoading(true);
91 const response = getStates(apiUrl, country);
92 response
93 .then((data) => {
94 if (data.ok) {
95 setStatesLoading(false);
96 setValue('state', null);
97
98 return data.json();
99 }
100 throw new Error('Fail to load states from ' + country);
101 })
102 .then((responseJson: StatesJsonResponse) => {
103 setStateLabel(responseJson.state_label);
104 setShowField(responseJson.show_field);
105 setStateRequired(responseJson.states_require);
106 setCityRequired(responseJson.city_require);
107 setZipRequired(responseJson.zip_require);
108
109 if (responseJson.states_found) {
110 const stateResponse: State[] = [];
111 Object.entries(responseJson.states).forEach(([key, value]) => {
112 if (key) {
113 stateResponse.push({value: key, label: value});
114 }
115 });
116
117 setStates(stateResponse);
118 } else {
119 setStates([]);
120 }
121 })
122 .catch((error) => {
123 console.log(error);
124 });
125 }, [country]);
126
127 if (!showField) {
128 return <HiddenStateField />;
129 }
130
131 if (states.length > 0) {
132 return (
133 /**
134 * TODO: replace with template api component
135 */
136 <NodeWrapper nodeType="fields" type="select" htmlTag="div" name="state">
137 <label>
138 <Label label={stateLabel} required={stateRequired} />
139
140 <select
141 onChange={updateStateValue}
142 disabled={statesLoading}
143 aria-invalid={fieldError ? 'true' : 'false'}
144 >
145 {statesLoading ? (
146 <>
147 <option hidden>{__('Loading...', 'give')}</option>
148 <option disabled>{__('Loading...', 'give')}</option>
149 </>
150 ) : (
151 <>
152 <option hidden>{__(`Select ${stateLabel}`, 'give')}</option>
153 <option disabled>{__(`Select ${stateLabel}`, 'give')}</option>
154 </>
155 )}
156 {states.map(({label, value}) => (
157 <option key={value} value={value}>
158 {label ?? value}
159 </option>
160 ))}
161 </select>
162
163 <HiddenStateField />
164
165 <ErrorMessage
166 errors={errors}
167 name={'state'}
168 render={({message}) => <FieldError error={message} name={nodeName} />}
169 />
170 </label>
171 </NodeWrapper>
172 );
173 }
174
175 return (
176 /**
177 * TODO: replace with template api component
178 */
179 <NodeWrapper nodeType="fields" type="text" htmlTag="div" name="state">
180 <label>
181 <Label label={stateLabel ?? __('State', 'give')} required={stateRequired} />
182
183 {statesLoading ? (
184 <input type="text" disabled={true} value={__('Loading...', 'give')} />
185 ) : (
186 <input
187 type="text"
188 value={''}
189 onChange={updateStateValue}
190 aria-invalid={fieldError ? 'true' : 'false'}
191 />
192 )}
193
194 <HiddenStateField />
195
196 <ErrorMessage
197 errors={errors}
198 name="state"
199 render={({message}) => <FieldError error={message} name={nodeName} />}
200 />
201 </label>
202 </NodeWrapper>
203 );
204 }
205
206 /**
207 * @since 3.0.0
208 */
209 export default function BillingAddress({
210 groupLabel,
211 nodeComponents: {country: Country, address1: Address1, address2: Address2, city: City, state, zip: Zip},
212 apiUrl,
213 name,
214 }: BillingAddressProps) {
215 // these are necessary to set the required indicator on the city and zip field labels
216 // the actual validation will come from the server as we don't yet have the ability to update the actual client validation rules here
217 const [cityRequired, setCityRequired] = useState(false);
218 const [zipRequired, setZipRequired] = useState(false);
219
220 return (
221 <>
222 <fieldset>
223 {groupLabel && <legend>{groupLabel}</legend>}
224 <Country />
225 <Address1 />
226 <Address2 />
227 <City validationRules={{required: cityRequired}} />
228 <StateFieldContainer
229 apiUrl={apiUrl}
230 state={state}
231 setCityRequired={setCityRequired}
232 setZipRequired={setZipRequired}
233 nodeName={name}
234 />
235 <Zip validationRules={{required: zipRequired}} />
236 </fieldset>
237 </>
238 );
239 }
240