PluginProbe ʕ •ᴥ•ʔ
Advanced Ads – Ad Manager & AdSense / 2.0.25
Advanced Ads – Ad Manager & AdSense v2.0.25
2.0.25 2.0.24 2.0.23 2.0.22 2.0.21 1.38.0 1.39.0 1.39.1 1.39.2 1.39.3 1.39.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.40.0 1.40.1 1.40.2 1.41.0 1.42.0 1.42.1 1.42.2 1.43.0 1.44.0 1.44.1 1.45.0 1.45.1 1.46.0 1.47.0 1.47.1 1.47.2 1.47.3 1.47.4 1.47.5 1.48.0 1.48.1 1.49.0 1.5.0 1.5.0.1 1.5.1 1.5.2 1.5.2.1 1.5.4 1.5.4.1 1.5.5 1.50.0 1.51.0 1.51.1 1.51.2 1.51.3 1.52.0 1.52.1 1.52.2 1.52.3 1.52.4 1.53.0 1.53.1 1.53.2 1.54.0 1.54.1 1.55.0 1.56.0 1.56.1 1.56.2 1.56.3 1.56.4 1.6 1.6.1 1.6.10 1.6.10.1 1.6.10.2 1.6.11 1.6.11.1 1.6.12 1.6.13 1.6.14 1.6.15 1.6.16 1.6.17 1.6.17.1 1.6.17.2 1.6.2 1.6.2.1 1.6.3 1.6.4 1.6.4.1 1.6.5 1.6.6 1.6.6.1 1.6.7 1.6.7.1 1.6.8 1.6.8.1 1.6.8.2 1.6.8.3 1.6.9 1.6.9.1 1.6.9.2 1.6.9.3 1.6.9.4 1.7 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.1.1 1.7.1.2 1.7.1.3 1.7.1.4 1.7.1.5 1.7.10 trunk 1.7.11 1.0.1 1.7.12 1.0.2 1.7.13 1.0.3 1.7.14 1.1.0 1.7.15 1.1.1 1.7.16 1.1.2 1.7.17 1.1.3 1.7.18 1.10 1.7.19 1.10.1 1.7.2 1.10.10 1.7.2.1 1.10.11 1.7.20 1.10.12 1.7.21 1.10.2 1.7.22 1.10.3 1.7.23 1.10.4 1.7.24 1.10.5 1.7.25 1.10.6 1.7.3 1.10.7 1.7.4 1.10.8 1.7.4.1 1.10.9 1.7.4.2 1.11 1.7.4.3 1.11.1 1.7.4.4 1.11.2 1.7.4.5 1.12 1.7.5 1.13 1.7.5.1 1.13.1 1.7.6 1.13.2 1.7.7 1.13.3 1.7.8 1.13.4 1.7.9 1.13.5 1.7.9.1 1.13.6 1.7.9.2 1.13.7 1.7.9.3 1.13.8 1.8 1.14 1.8.1 1.14.1 1.8.10 1.14.10 1.8.11 1.14.11 1.8.12 1.14.2 1.8.13 1.14.3 1.8.14 1.14.4 1.8.15 1.14.5 1.8.16 1.14.6 1.8.17 1.14.7 1.8.18 1.14.8 1.8.19 1.14.9 1.8.2 1.15 1.8.20 1.16 1.8.21 1.16.1 1.8.22 1.17 1.8.23 1.17.1 1.8.24 1.17.10 1.8.25 1.17.10-rc.1 1.8.26 1.17.11 1.8.27 1.17.12 1.8.28 1.17.12-rc.1 1.8.29 1.17.2 1.8.3 1.17.3 1.8.30 1.17.4 1.8.4 1.17.5 1.8.5 1.17.6 1.8.6 1.17.7 1.8.7 1.17.8 1.8.8 1.17.9 1.8.9 1.17.9-beta.1 1.9 1.18.0 2.0.0 1.19.0 2.0.1 1.19.1 2.0.10 1.2 2.0.11 1.2.1 2.0.12 1.2.2 2.0.13 1.2.3 2.0.14 1.2.4 2.0.15 1.2.5 2.0.16 1.2.6 2.0.17 1.2.7 2.0.18 1.20.0 2.0.19 1.20.0-rc.1 2.0.2 1.20.0-rc.2 2.0.20 1.20.1 2.0.3 1.20.2 2.0.4 1.20.3 2.0.5 1.21.0 2.0.6 1.21.1 2.0.7 1.22.0 2.0.8 1.22.1 2.0.9 1.22.2 1.23.0 1.23.1 1.23.2 1.24.0 1.24.1 1.24.2 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.29.0 1.29.1 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.30.0 1.30.1 1.30.2 1.30.2-rc.1 1.30.3 1.30.4 1.30.4-rc.1 1.30.5 1.31.0 1.31.1 1.32.0 1.32.0-rc.1 1.33.0 1.33.1 1.33.2 1.34.0 1.35.0 1.35.1 1.36.0 1.36.1 1.36.2 1.36.3 1.37.0 1.37.1 1.37.2
advanced-ads / src / admin / screen-support / components / TicketModal.jsx
advanced-ads / src / admin / screen-support / components Last commit date
CategoryCard.jsx 4 days ago FAQs.jsx 4 days ago PaidSupport.jsx 4 days ago SearchBox.jsx 4 days ago SupportForum.jsx 4 days ago TicketModal.jsx 4 days ago Videos.jsx 4 days ago
TicketModal.jsx
396 lines
1 /**
2 * External Dependencies
3 */
4 import { __, sprintf } from '@wordpress/i18n';
5 import { Modal } from '@wordpress/components';
6 import { useRef, useState } from '@wordpress/element';
7
8 /**
9 * Internal Dependencies
10 */
11 import { siteInfo } from '@advancedAds';
12 import { createToast } from '@advancedAds/utils';
13 import { FileUploaderField } from '@admin/components/FileUploaderField';
14
15 import { base64ToBlob, validate } from '../utils';
16
17 const TICKET_API =
18 'https://wpadvancedads.com/wp-json/advanced-ads/v1/create-ticket';
19
20 export function TicketModal( { closeModal } ) {
21 const uploaderRef = useRef( null );
22
23 const [ email, setEmail ] = useState( siteInfo.currentUserEmail ?? '' );
24 const [ subject, setSubject ] = useState( '' );
25 const [ message, setMessage ] = useState( '' );
26 const [ termsAccepted, setTermsAccepted ] = useState( false );
27
28 const [ errors, setErrors ] = useState( {} );
29 const [ isSubmitting, setIsSubmitting ] = useState( false );
30
31 const p2 = sprintf(
32 /* translators: 1: opening a tag, 2: opening a tag, 3: closing a tag */
33 __(
34 'By submitting this form, I accept the %1$sTerms%3$s and %2$sPrivacy Policy%3$s and consent that my personal information in this form will be stored and processed for the purposes of providing support.',
35 'advanced-ads'
36 ),
37 '<a href="https://wpadvancedads.com/terms/" target="_blank">',
38 '<a href="https://wpadvancedads.com/privacy-policy/" target="_blank">',
39 '</a>'
40 );
41
42 async function handleSubmit( e ) {
43 e.preventDefault();
44
45 const { next, trimmedEmail, trimmedSubject, trimmedMessage } = validate(
46 email,
47 subject,
48 message,
49 termsAccepted
50 );
51 setErrors( next );
52
53 if ( Object.keys( next ).length > 0 ) {
54 return;
55 }
56
57 const uploader = uploaderRef.current;
58 const selectedFiles = uploader?.selectedFiles ?? [];
59 const hasFiles = selectedFiles.length > 0;
60
61 const basePayload = {
62 email: trimmedEmail,
63 subject: trimmedSubject,
64 message: trimmedMessage,
65 terms: termsAccepted ? 'on' : '',
66 domain: siteInfo.siteUrl,
67 php_version: siteInfo.phpVersion,
68 wp_version: siteInfo.wpVersion,
69 };
70
71 setIsSubmitting( true );
72
73 let submitBody;
74 /** @type {Record<string, string>} */
75 const headers = {};
76
77 if ( hasFiles ) {
78 const submitFormData = new FormData();
79 for ( const [ key, value ] of Object.entries( basePayload ) ) {
80 submitFormData.append( key, String( value ) );
81 }
82 selectedFiles.forEach( ( fileObj ) => {
83 const blob = base64ToBlob( fileObj.fileimage );
84 submitFormData.append(
85 'attachments[]',
86 blob,
87 fileObj.filename
88 );
89 } );
90 submitBody = submitFormData;
91 } else {
92 submitBody = JSON.stringify( basePayload );
93 headers[ 'Content-Type' ] = 'application/json';
94 }
95
96 try {
97 const response = await fetch( TICKET_API, {
98 method: 'POST',
99 body: submitBody,
100 headers,
101 } );
102
103 if ( response.ok ) {
104 uploader?.reset?.();
105 closeModal();
106 createToast( {
107 type: 'muted',
108 iconType: 'success',
109 title: __( 'Ticket submitted', 'advanced-ads' ),
110 message: __(
111 'Your ticket has been submitted successfully.',
112 'advanced-ads'
113 ),
114 } );
115 return;
116 }
117
118 throw new Error( __( 'Failed to submit form', 'advanced-ads' ) );
119 } catch ( error ) {
120 createToast( {
121 type: 'error',
122 title: __( 'Failed to submit form', 'advanced-ads' ),
123 message:
124 error instanceof Error
125 ? error.message
126 : __( 'Something went wrong.', 'advanced-ads' ),
127 inDialog: true,
128 } );
129 } finally {
130 setIsSubmitting( false );
131 }
132 }
133
134 return (
135 <Modal
136 title={ __( 'Get help from our support team', 'advanced-ads' ) }
137 size="small"
138 onRequestClose={ closeModal }
139 shouldCloseOnEscape={ true }
140 shouldCloseOnClickOutside={ false }
141 className="advads-modal w-lg max-w-lg"
142 overlayClassName="bg-black/50 backdrop-blur-[1px]"
143 >
144 <p className="mt-0">
145 { __(
146 'Describe your issue and our support team will review it as soon as possible. You’ll receive a confirmation email after submitting your request.',
147 'advanced-ads'
148 ) }
149 </p>
150
151 <form onSubmit={ handleSubmit }>
152 <div className="advads-form">
153 <div
154 className={
155 'advads-field' + ( errors.email ? ' invalid' : '' )
156 }
157 >
158 <div className="advads-field-label">
159 <label htmlFor="email">
160 { __( 'Email address', 'advanced-ads' ) } *
161 </label>
162 </div>
163 <div className="advads-field-input">
164 <input
165 type="email"
166 name="email"
167 id="email"
168 className="regular-text"
169 autoComplete="email"
170 placeholder={ __(
171 'Enter your email address',
172 'advanced-ads'
173 ) }
174 value={ email }
175 onChange={ ( ev ) => {
176 setEmail( ev.target.value );
177 if ( errors.email ) {
178 setErrors( ( prev ) => {
179 const next = { ...prev };
180 delete next.email;
181 return next;
182 } );
183 }
184 } }
185 aria-invalid={ errors.email ? 'true' : 'false' }
186 aria-describedby={
187 errors.email ? 'email-error' : undefined
188 }
189 />
190 </div>
191 { errors.email && (
192 <p
193 id="email-error"
194 className="text-sm text-red-600 mt-2"
195 role="alert"
196 >
197 { errors.email }
198 </p>
199 ) }
200 </div>
201
202 <div
203 className={
204 'advads-field' +
205 ( errors.subject ? ' invalid' : '' )
206 }
207 >
208 <div className="advads-field-label">
209 <label htmlFor="subject">
210 { __( 'Subject', 'advanced-ads' ) } *
211 </label>
212 </div>
213 <div className="advads-field-input">
214 <input
215 type="text"
216 name="subject"
217 id="subject"
218 className="regular-text"
219 placeholder={ __(
220 'Enter the subject of your issue',
221 'advanced-ads'
222 ) }
223 value={ subject }
224 onChange={ ( ev ) => {
225 setSubject( ev.target.value );
226 if ( errors.subject ) {
227 setErrors( ( prev ) => {
228 const next = { ...prev };
229 delete next.subject;
230 return next;
231 } );
232 }
233 } }
234 aria-invalid={
235 errors.subject ? 'true' : 'false'
236 }
237 aria-describedby={
238 errors.subject ? 'subject-error' : undefined
239 }
240 />
241 </div>
242 { errors.subject && (
243 <p
244 id="subject-error"
245 className="text-sm text-red-600 mt-2"
246 role="alert"
247 >
248 { errors.subject }
249 </p>
250 ) }
251 </div>
252
253 <div
254 className={
255 'advads-field' +
256 ( errors.message ? ' invalid' : '' )
257 }
258 >
259 <div className="advads-field-label">
260 <label htmlFor="message">
261 { __( 'Your Message', 'advanced-ads' ) } *
262 </label>
263 </div>
264 <div className="advads-field-input">
265 <textarea
266 name="message"
267 id="message"
268 className={
269 'large-text' +
270 ( errors.message ? ' !border-red-300' : '' )
271 }
272 placeholder={ __(
273 'Enter the message of your issue',
274 'advanced-ads'
275 ) }
276 rows="5"
277 value={ message }
278 onChange={ ( ev ) => {
279 setMessage( ev.target.value );
280 if ( errors.message ) {
281 setErrors( ( prev ) => {
282 const next = { ...prev };
283 delete next.message;
284 return next;
285 } );
286 }
287 } }
288 aria-invalid={
289 errors.message ? 'true' : 'false'
290 }
291 aria-describedby={
292 errors.message ? 'message-error' : undefined
293 }
294 />
295 </div>
296 { errors.message && (
297 <p
298 id="message-error"
299 className="text-sm text-red-600 mt-2"
300 role="alert"
301 >
302 { errors.message }
303 </p>
304 ) }
305 </div>
306
307 <div className="advads-field">
308 <FileUploaderField
309 uploaderRef={ uploaderRef }
310 maxFiles={ 5 }
311 maxSizeMB={ 5 }
312 allowedExt={ [
313 'jpg',
314 'jpeg',
315 'png',
316 'gif',
317 'pdf',
318 'txt',
319 'docx',
320 'log',
321 ] }
322 />
323 </div>
324
325 <div
326 className={
327 'advads-field' + ( errors.terms ? ' invalid' : '' )
328 }
329 >
330 <div className="flex items-start gap-2">
331 <input
332 type="checkbox"
333 name="terms"
334 id="terms"
335 className="mt-0.5"
336 checked={ termsAccepted }
337 onChange={ ( ev ) => {
338 setTermsAccepted( ev.target.checked );
339 if ( errors.terms ) {
340 setErrors( ( prev ) => {
341 const next = { ...prev };
342 delete next.terms;
343 return next;
344 } );
345 }
346 } }
347 aria-labelledby="terms-consent-label"
348 aria-invalid={ errors.terms ? 'true' : 'false' }
349 aria-describedby={
350 errors.terms ? 'terms-error' : undefined
351 }
352 />
353 <label
354 htmlFor="terms"
355 id="terms-consent-label"
356 dangerouslySetInnerHTML={ { __html: p2 } }
357 />
358 </div>
359 { errors.terms && (
360 <p
361 id="terms-error"
362 className="text-sm text-red-600 mt-2"
363 role="alert"
364 >
365 { errors.terms }
366 </p>
367 ) }
368 </div>
369 </div>
370
371 <div className="pt-12 flex gap-x-4 justify-end -mb-2">
372 <button
373 type="button"
374 data-dialog-close
375 className="button button-secondary"
376 onClick={ closeModal }
377 disabled={ isSubmitting }
378 >
379 { __( 'Cancel', 'advanced-ads' ) }
380 </button>
381 <button
382 type="submit"
383 className={
384 'button button-primary' +
385 ( isSubmitting ? ' submitting' : '' )
386 }
387 disabled={ isSubmitting }
388 >
389 { __( 'Submit', 'advanced-ads' ) }
390 </button>
391 </div>
392 </form>
393 </Modal>
394 );
395 }
396