PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / wizard / assets / src / wizard-steps / GlobalCheckout.js

GlobalCheckout.js in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.3.0, at wizard/assets/src/wizard-steps/GlobalCheckout.js

605 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useState, useEffect, useCallback } from '@wordpress/element';
2 import { __ } from '@wordpress/i18n';
3 import { useHistory } from 'react-router-dom';
4 import apiFetch from '@wordpress/api-fetch';
5 import { useStateValue } from '../utils/StateProvider';
6 import { RadioGroup } from '@headlessui/react';
7 import { ColorPickerField } from '@WizardFields';
8 import { sendPostMessage } from '@Utils/Helpers';
9 import parse from 'html-react-parser';
10 import { ArrowRightIcon, ArrowLeftIcon } from '@heroicons/react/24/outline';
11
12 import GlobalFlowItem from './components/GlobalFlowItem';
13 import UploadSiteLogo from './components/UploadSiteLogo';
14 import NoFlowsFound from './components/NoFlowsFound';
15 import GlobalFlowHeader from './components/GlobalFlowHeader';
16 import GlobalFlowLibrarySkeleton from './skeletons/GlobalFlowLibrarySkeleton';
17 import TemplateLoadingSkeleton from './skeletons/TemplateLoadingSkeleton';
18 import InstantCheckoutImage from '@WizardImages/instant-checkout.png';
19
20 import InstantCheckoutData from './data/InstantCheckoutData.json';
21
22 function GlobalCheckout() {
23 const [ processing, setProcessing ] = useState( true );
24 const [ previewProcessing, setPreviewProcessing ] = useState( true );
25
26 const [ ShowSideBar, setShowSideBar ] = useState( false );
27
28 const [ showFlowNotFound, setShowFlowNotFound ] = useState( false );
29
30 const [ filteredFlows, setFilteredFlows ] = useState( 0 );
31
32 const [ selectedStoreFlow, setSelectedFlow ] = useState();
33 const [ selectedStoreFlowTitle, setSelectedFlowTitle ] = useState();
34
35 const [ { action_button, selected_page_builder, site_logo }, dispatch ] =
36 useStateValue();
37
38 // Removing cartflows_wizard.default_page_builder to ensure the onboarding step displays templates only for the selected builder, not the default or previously chosen one.
39
40 const page_builder = selected_page_builder
41 ? selected_page_builder
42 : 'gutenberg';
43 const [ previewUrl, setPreviewUrl ] = useState();
44
45 const [ importErrors, setImportErrors ] = useState( {
46 hasError: false,
47 errorMessage: '',
48 callToAction: '',
49 } );
50
51 const { hasError, errorMessage, callToAction } = importErrors;
52
53 const history = useHistory();
54
55 const redirectNextStep = function () {
56 history.push( {
57 pathname: 'index.php',
58 search: `?page=cartflow-setup&step=optin`,
59 } );
60 };
61
62 function classNames( ...classes ) {
63 return classes.filter( Boolean ).join( ' ' );
64 }
65
66 const changeButtonText = useCallback( ( data ) => {
67 dispatch( {
68 status: 'SET_NEXT_STEP',
69 action_button: data,
70 } );
71 }, [] );
72
73 const wcfCartflowsTypePro = function () {
74 if (
75 cartflows_wizard.is_pro &&
76 'pro' === cartflows_wizard.cf_pro_type
77 ) {
78 return true;
79 }
80
81 return false;
82 };
83
84 const updateStoreCheckoutFunnelList = (
85 InstantCheckoutJsonData,
86 pageBuilderName,
87 pageBuilderSlug
88 ) => {
89 // Make a deep copy of the flow data to avoid mutation
90 const updatedInstantCheckoutData = JSON.parse(
91 JSON.stringify( InstantCheckoutJsonData )
92 );
93
94 // Update the placeholders with dynamic values
95 updatedInstantCheckoutData.page_builder = pageBuilderSlug;
96 updatedInstantCheckoutData.cartflows_flow_page_builder[ 0 ].name =
97 pageBuilderName;
98 updatedInstantCheckoutData.cartflows_flow_page_builder[ 0 ].slug =
99 pageBuilderSlug;
100 updatedInstantCheckoutData.featured_image_url = InstantCheckoutImage;
101 updatedInstantCheckoutData.thumbnail_image_url = InstantCheckoutImage;
102
103 return updatedInstantCheckoutData;
104 };
105
106 const showHideFooterImportButton = ( showHide ) => {
107 dispatch( {
108 status: 'SET_SHOW_FOOTER_IMPORT_BUTTON',
109 showButton: showHide,
110 } );
111 };
112
113 useEffect( () => {
114 // Set Foooter button text.
115 changeButtonText( {
116 button_text: __( 'Import & Continue', 'cartflows' ),
117 button_class: 'wcf-import-global-flow',
118 } );
119
120 if ( filteredFlows <= 0 ) {
121 const formData = new window.FormData();
122 formData.append( 'action', 'cartflows_get_global_flow_list' );
123 formData.append(
124 'security',
125 cartflows_wizard.get_global_flow_list_nonce
126 );
127
128 formData.append( 'page_builder', page_builder );
129
130 setProcessing( true );
131
132 apiFetch( {
133 url: cartflows_wizard.ajax_url,
134 method: 'POST',
135 body: formData,
136 } ).then( ( response ) => {
137 if ( response?.data?.flows?.length > 0 ) {
138 const all_flows = Object.values( response.data.flows );
139 const parsedFlows = [];
140 all_flows.map(
141 ( flows ) => ( parsedFlows[ flows.ID ] = flows )
142 );
143 setFilteredFlows( parsedFlows );
144
145 setInstantCheckoutFunnel( parsedFlows );
146
147 setProcessing( false );
148 } else {
149 setProcessing( false );
150 setShowFlowNotFound( false );
151
152 // If there are no funnels found then still show the instant checkout funnels.
153 const parsedFlows = setInstantCheckoutFunnel( [], true );
154
155 setFilteredFlows( parsedFlows );
156 }
157 } );
158 }
159
160 const setInstantCheckoutFunnel = (
161 parsedFlows = [],
162 shouldReturn = false
163 ) => {
164 /**
165 * Add Instant Checkout Store Checkout Funnel Option to the existing.
166 * To-Do: Remove this when a ready-made template is been introduced.
167 *
168 * @since 2.1.0
169 */
170 parsedFlows[ 0 ] = updateStoreCheckoutFunnelList(
171 InstantCheckoutData,
172 page_builder.charAt( 0 ).toUpperCase() +
173 page_builder.slice( 1 ),
174 page_builder
175 );
176
177 setSelectedFlow( Object.keys( parsedFlows )[ 0 ] );
178 setSelectedFlowTitle(
179 parsedFlows[ Object.keys( parsedFlows )[ 0 ] ].title
180 );
181
182 // Setting the instant Checkout as default option to be selected so show the import store checkout button on the footer.
183 showHideFooterImportButton( true );
184
185 if ( shouldReturn ) {
186 return parsedFlows;
187 }
188 };
189
190 const importStoreCheckoutSuccessEvent = function () {
191 changeButtonText( {
192 button_text: __( 'Processing..', 'cartflows' ),
193 } );
194
195 dispatch( {
196 status: 'SET_STORE_CHECKOUT_IMPORTED',
197 storeCheckoutImported: true,
198 } );
199 console.log( 'Store Checkout Imported' );
200 // Redirect to next step once the import is success.
201 setTimeout( function () {
202 redirectNextStep();
203 }, 1000 );
204 };
205
206 document.addEventListener(
207 'wcf-store-checkout-import-success',
208 importStoreCheckoutSuccessEvent,
209 false
210 );
211
212 const importStoreCheckoutProcessEvent = function () {
213 changeButtonText( {
214 button_text: __( 'Importing..', 'cartflows' ),
215 } );
216 };
217
218 document.addEventListener(
219 'wcf-store-checkout-import-text-processing',
220 importStoreCheckoutProcessEvent,
221 false
222 );
223
224 const importStoreCheckoutErrorEvent = function ( e ) {
225 setImportErrors( {
226 hasError: e.detail.is_error,
227 errorMessage: e.detail.errorMsg,
228 callToAction: e.detail.callToAction,
229 } );
230
231 changeButtonText( {
232 button_text: __( 'Importing Failed..', 'cartflows' ),
233 } );
234 };
235
236 document.addEventListener(
237 'wcf-store-checkout-import-error',
238 importStoreCheckoutErrorEvent,
239 false
240 );
241
242 return () => {
243 document.removeEventListener(
244 'wcf-store-checkout-import-success',
245 importStoreCheckoutSuccessEvent
246 );
247
248 document.removeEventListener(
249 'wcf-store-checkout-import-text-processing',
250 importStoreCheckoutProcessEvent
251 );
252
253 document.removeEventListener(
254 'wcf-store-checkout-import-error',
255 importStoreCheckoutErrorEvent
256 );
257 };
258 }, [ changeButtonText ] );
259
260 const showOptionsSideBar = function ( e ) {
261 e.preventDefault();
262 let selected_flow_id = '';
263 let wrapper_element = '';
264 if ( ! ShowSideBar ) {
265 wrapper_element = e.target.closest( '.wcf-item' );
266
267 if ( wrapper_element ) {
268 selected_flow_id = wrapper_element.hasAttribute( 'data-key' )
269 ? wrapper_element.getAttribute( 'data-key' )
270 : '';
271
272 setSelectedFlow( selected_flow_id );
273 showHideFooterImportButton( true );
274 }
275 } else {
276 setSelectedFlow( selectedStoreFlow );
277 showHideFooterImportButton( false );
278 }
279 /**
280 * Don't open the preview panel if the selected funnel is Instant Checkout.
281 * To-Do: Remove this compatibility when a ready-made Instant Checkout funnel is introduced.
282 * Zero index/funnel ID is set for the Instant Checkout only.
283 */
284 if ( '0' === selected_flow_id ) {
285 return;
286 }
287
288 /* Set show popup true/false */
289 if ( ShowSideBar ) {
290 setShowSideBar( false );
291 history.push( {
292 pathname: 'index.php',
293 search: `?page=cartflow-setup&step=store-checkout`,
294 } );
295 } else {
296 if ( null === wrapper_element || 'undefined' === wrapper_element ) {
297 return;
298 }
299
300 const flow_title = filteredFlows[ selected_flow_id ].title;
301
302 setPreviewProcessing( true );
303 setShowSideBar( true );
304 setSelectedFlowTitle( flow_title );
305 _get_flow_url( filteredFlows[ selected_flow_id ].steps );
306 history.push( {
307 pathname: 'index.php',
308 search: `?page=cartflow-setup&step=store-checkout&preview=true`,
309 } );
310 }
311 };
312
313 const _get_flow_url = function ( steps ) {
314 if ( steps.length > 0 ) {
315 steps.forEach( ( step ) => {
316 if ( 'checkout' === step.type ) {
317 setPreviewUrl(
318 step.link +
319 '?wcf-remove-cross-origin=true&wcf-load-onboarding-iframe=true'
320 );
321 }
322 } );
323 }
324 };
325
326 /**
327 * Set the customizer logo if set and remove the loading skeleton on iframe load.
328 */
329 const handleIframeOnLoad = function () {
330 setPreviewProcessing( false );
331 setCustomizerLogo( site_logo );
332 };
333
334 /**
335 * Change the pallete color on selection.
336 *
337 * @param {event} event
338 */
339 const onPaletteChange = ( event ) => {
340 const color_val = event.hex;
341
342 sendPostMessage( {
343 action: 'changePrimaryColor',
344 data: {
345 default_builder: page_builder,
346 primary_color: color_val,
347 values_to_change: [
348 {
349 'background-color': color_val,
350 'border-color': color_val,
351 },
352 ],
353 },
354 } );
355 };
356
357 /**
358 * Set the customizer logo when the iframe is loading so as to avoid delay.
359 *
360 * @param {logo_data} logo_data
361 */
362 const setCustomizerLogo = function ( logo_data ) {
363 let preview_action = 'changeHeaderLogo';
364
365 if ( '' === logo_data && '' === cartflows_wizard.site_logo ) {
366 preview_action = 'clearHeaderLogo';
367 }
368
369 sendPostMessage( {
370 action: preview_action,
371 data: {
372 default_builder: page_builder,
373 site_logo: logo_data,
374 },
375 } );
376 };
377
378 return (
379 <div className="wcf-col wcf-flow-list-wrapper">
380 <div className="wcf-container">
381 { /* Main Step Content */ }
382 <div className="wcf-col wcf-col--left">
383 <div className="max-w-full text-center mt-4">
384 <div className="max-w-full">
385 { processing && <GlobalFlowLibrarySkeleton /> }
386
387 { showFlowNotFound && <NoFlowsFound /> }
388
389 { ! processing && filteredFlows.length > 0 && (
390 <>
391 <GlobalFlowHeader />
392
393 <RadioGroup
394 value={ selectedStoreFlow }
395 // onChange={ showOptionsSideBar }
396 onClick={ showOptionsSideBar }
397 className={
398 'wcf-store-flow-importer__list wcf-items-list wcf-flow-row relative flex gap-6 flex-wrap justify-center'
399 }
400 >
401 { filteredFlows.map( ( item ) => (
402 <RadioGroup.Option
403 key={ item.ID }
404 value={ item.ID }
405 data-key={ item.ID }
406 className={ ( {
407 checked,
408 active,
409 } ) =>
410 classNames(
411 `wcf-item hover:translate-y-[-1px] rounded transition-all flex-grow-0 flex-shrink-0 basis-[calc(33.33%-7rem)] relative`,
412 checked
413 ? 'border-0'
414 : 'border-gray-300',
415 active
416 ? 'border-0'
417 : 'border-gray-300'
418 )
419 }
420 >
421 { ( { checked, active } ) => (
422 <>
423 <GlobalFlowItem
424 key={ item.ID }
425 item={ item }
426 isChecked={
427 checked
428 }
429 isActive={ active }
430 />
431 { /* <div
432 className={ classNames(
433 active
434 ? 'border-2'
435 : 'border-2',
436 checked
437 ? 'border-orange-500'
438 : 'border-transparent',
439 'absolute -inset-px rounded pointer-events-none'
440 ) }
441 aria-hidden="true"
442 /> */ }
443 </>
444 ) }
445 </RadioGroup.Option>
446 ) ) }
447 </RadioGroup>
448
449 <span
450 id={
451 'wcf-selected-store-checkout-template'
452 }
453 data-selected-flow={ selectedStoreFlow }
454 data-selected-flow-info={ JSON.stringify(
455 filteredFlows[ selectedStoreFlow ]
456 ) }
457 ></span>
458 </>
459 ) }
460 </div>
461 </div>
462 </div>
463 </div>
464
465 { /* Sidemenu */ }
466 { ShowSideBar && (
467 <div className="wcf-bg--overlay w-full h-full bg-white absolute before:block top-0 right-0 left-0 z-50">
468 <div className="wcf-sidebar bg-[#F7F7F9] shadow w-full">
469 <div className="wcf-sidebar--header">
470 <div className="wcf-template-info flex gap-3 items-center">
471 <button
472 type="button"
473 className="p-3 border border-slate-200 rounded hover:border-slate-500 text-gray-400 hover:text-gray-800 "
474 onClick={ showOptionsSideBar }
475 >
476 <ArrowLeftIcon
477 className="w-4 h-4 stroke-2"
478 aria-hidden="true"
479 />
480 </button>
481
482 <div className="wcf-template-name">
483 <p className="text-[#6B7280]">
484 { __(
485 'Selected Template:',
486 'cartflows'
487 ) }
488 </p>
489 <h3 className="font-semibold text-gray-600 text-base">
490 { selectedStoreFlowTitle }
491 </h3>
492 </div>
493 </div>
494 <div className="wcf-header-action--buttons flex gap-4">
495 <div className="wcf-design--options flex gap-6">
496 <UploadSiteLogo
497 defaultPageBuilder={ page_builder }
498 />
499 <ColorPickerField
500 id={ 'primary_color' }
501 name={ 'primary_color' }
502 label={ __(
503 'Change Primary Color',
504 'cartflows'
505 ) }
506 value={ '' }
507 displayAs={ 'button' }
508 handleOnchange={ onPaletteChange }
509 />
510 </div>
511 </div>
512 </div>
513 </div>
514
515 <div className="wcf-sidebar-template-preview w-full h-screen relative">
516 { previewProcessing ? (
517 <TemplateLoadingSkeleton />
518 ) : null }
519
520 { '' !== previewUrl && (
521 <iframe
522 id="cartflows-templates-preview"
523 title="Website Preview"
524 height="100%"
525 width="100%"
526 src={ previewUrl }
527 onLoad={ handleIframeOnLoad }
528 // eslint-disable-next-line react/no-unknown-property
529 allowpaymentrequest="true"
530 sandbox="allow-scripts allow-same-origin"
531 />
532 ) }
533
534 <div
535 className={ `wcf-sidebar--footer fixed bottom-0 bg-white p-3.5 w-full border-t border-slate-200 ${
536 previewProcessing ? 'wcf-content-blocked' : ''
537 }` }
538 >
539 <div className="wcf-options--row flex justify-between items-center">
540 <div className="wcf-footer--info-wrapper">
541 <span className="text-sm font-medium text-primary-600 text-center block tracking-[.24em] uppercase">
542 { __( 'Step 4 of 6', 'cartflows' ) }
543 </span>
544 </div>
545 <div className="wcf-footer--action-buttons flex items-center gap-5">
546 { hasError && (
547 <p className="wcf-import-error-wrapper flex bg-red-100 p-3 rounded items-center gap-5">
548 <h3 className="wcf-import-error--heading font-normal text-red-700 text-sm">
549 { parse( errorMessage ) }
550 </h3>
551 <span className="wcf-import-error--message text-sm text-slate-800">
552 { parse( callToAction ) }
553 </span>
554 </p>
555 ) }
556
557 { 'pro' ===
558 filteredFlows[ selectedStoreFlow ]
559 .type && ! wcfCartflowsTypePro() ? (
560 <>
561 <div className="font-medium text-sm text-red-700">
562 { __(
563 'Access all of our pro templates when you upgrade your plan to CartFlows Pro today.',
564 'cartflows'
565 ) }
566 </div>
567 <a
568 className={ `wcf-wizard--button px-5 py-2 text-sm hover:text-white` }
569 href="https://cartflows.com/?utm_source=dashboard&utm_medium=free-cartflows&utm_campaign=go-pro"
570 target="_blank"
571 rel="noreferrer"
572 >
573 { __(
574 'Get CartFlows Pro',
575 'cartflows'
576 ) }
577 </a>
578 </>
579 ) : (
580 <button
581 className={ `wcf-wizard--button px-5 py-2 text-sm ${
582 action_button.button_class
583 ? action_button.button_class
584 : ''
585 }` }
586 >
587 { action_button.button_text }
588 <ArrowRightIcon
589 className="w-5 mt-0.5 ml-1.5 stroke-2"
590 aria-hidden="true"
591 />
592 </button>
593 ) }
594 </div>
595 </div>
596 </div>
597 </div>
598 </div>
599 ) }
600 </div>
601 );
602 }
603
604 export default GlobalCheckout;
605