PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.1.2
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.1.2
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.1.2, at wizard/assets/src/wizard-steps/GlobalCheckout.js

599 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useState, useEffect, useCallback } from 'react';
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 = document.addEventListener(
191 'wcf-store-checkout-import-success',
192 function () {
193 changeButtonText( {
194 button_text: __( 'Processing..', 'cartflows' ),
195 } );
196
197 dispatch( {
198 status: 'SET_STORE_CHECKOUT_IMPORTED',
199 storeCheckoutImported: true,
200 } );
201 console.log( 'Store Checkout Imported' );
202 // Redirect to next step once the import is success.
203 setTimeout( function () {
204 redirectNextStep();
205 }, 1000 );
206 },
207 false
208 );
209
210 const importStoreCheckoutProcessEvent = document.addEventListener(
211 'wcf-store-checkout-import-text-processing',
212 function () {
213 changeButtonText( {
214 button_text: __( 'Importing..', 'cartflows' ),
215 } );
216 },
217 false
218 );
219
220 const importStoreCheckoutErrorEvent = document.addEventListener(
221 'wcf-store-checkout-import-error',
222 function ( e ) {
223 setImportErrors( {
224 hasError: e.detail.is_error,
225 errorMessage: e.detail.errorMsg,
226 callToAction: e.detail.callToAction,
227 } );
228
229 changeButtonText( {
230 button_text: __( 'Importing Failed..', 'cartflows' ),
231 } );
232 },
233 false
234 );
235
236 return () => {
237 document.removeEventListener(
238 'wcf-store-checkout-import-success',
239 importStoreCheckoutSuccessEvent
240 );
241
242 document.removeEventListener(
243 'wcf-store-checkout-import-text-processing',
244 importStoreCheckoutProcessEvent
245 );
246
247 document.removeEventListener(
248 'wcf-store-checkout-import-error',
249 importStoreCheckoutErrorEvent
250 );
251 };
252 }, [ changeButtonText ] );
253
254 const showOptionsSideBar = function ( e ) {
255 e.preventDefault();
256 let selected_flow_id = '';
257 let wrapper_element = '';
258 if ( ! ShowSideBar ) {
259 wrapper_element = e.target.closest( '.wcf-item' );
260
261 if ( wrapper_element ) {
262 selected_flow_id = wrapper_element.hasAttribute( 'data-key' )
263 ? wrapper_element.getAttribute( 'data-key' )
264 : '';
265
266 setSelectedFlow( selected_flow_id );
267 showHideFooterImportButton( true );
268 }
269 } else {
270 setSelectedFlow( selectedStoreFlow );
271 showHideFooterImportButton( false );
272 }
273 /**
274 * Don't open the preview panel if the selected funnel is Instant Checkout.
275 * To-Do: Remove this compatibility when a ready-made Instant Checkout funnel is introduced.
276 * Zero index/funnel ID is set for the Instant Checkout only.
277 */
278 if ( '0' === selected_flow_id ) {
279 return;
280 }
281
282 /* Set show popup true/false */
283 if ( ShowSideBar ) {
284 setShowSideBar( false );
285 history.push( {
286 pathname: 'index.php',
287 search: `?page=cartflow-setup&step=store-checkout`,
288 } );
289 } else {
290 if ( null === wrapper_element || 'undefined' === wrapper_element ) {
291 return;
292 }
293
294 const flow_title = filteredFlows[ selected_flow_id ].title;
295
296 setPreviewProcessing( true );
297 setShowSideBar( true );
298 setSelectedFlowTitle( flow_title );
299 _get_flow_url( filteredFlows[ selected_flow_id ].steps );
300 history.push( {
301 pathname: 'index.php',
302 search: `?page=cartflow-setup&step=store-checkout&preview=true`,
303 } );
304 }
305 };
306
307 const _get_flow_url = function ( steps ) {
308 if ( steps.length > 0 ) {
309 steps.forEach( ( step ) => {
310 if ( 'checkout' === step.type ) {
311 setPreviewUrl(
312 step.link +
313 '?wcf-remove-cross-origin=true&wcf-load-onboarding-iframe=true'
314 );
315 }
316 } );
317 }
318 };
319
320 /**
321 * Set the customizer logo if set and remove the loading skeleton on iframe load.
322 */
323 const handleIframeOnLoad = function () {
324 setPreviewProcessing( false );
325 setCustomizerLogo( site_logo );
326 };
327
328 /**
329 * Change the pallete color on selection.
330 *
331 * @param {event} event
332 */
333 const onPaletteChange = ( event ) => {
334 const color_val = event.hex;
335
336 sendPostMessage( {
337 action: 'changePrimaryColor',
338 data: {
339 default_builder: page_builder,
340 primary_color: color_val,
341 values_to_change: [
342 {
343 'background-color': color_val,
344 'border-color': color_val,
345 },
346 ],
347 },
348 } );
349 };
350
351 /**
352 * Set the customizer logo when the iframe is loading so as to avoid delay.
353 *
354 * @param {logo_data} logo_data
355 */
356 const setCustomizerLogo = function ( logo_data ) {
357 let preview_action = 'changeHeaderLogo';
358
359 if ( '' === logo_data && '' === cartflows_wizard.site_logo ) {
360 preview_action = 'clearHeaderLogo';
361 }
362
363 sendPostMessage( {
364 action: preview_action,
365 data: {
366 default_builder: page_builder,
367 site_logo: logo_data,
368 },
369 } );
370 };
371
372 return (
373 <div className="wcf-col wcf-flow-list-wrapper">
374 <div className="wcf-container">
375 { /* Main Step Content */ }
376 <div className="wcf-col wcf-col--left">
377 <div className="max-w-full text-center mt-4">
378 <div className="max-w-full">
379 { processing && <GlobalFlowLibrarySkeleton /> }
380
381 { showFlowNotFound && <NoFlowsFound /> }
382
383 { ! processing && filteredFlows.length > 0 && (
384 <>
385 <GlobalFlowHeader />
386
387 <RadioGroup
388 value={ selectedStoreFlow }
389 // onChange={ showOptionsSideBar }
390 onClick={ showOptionsSideBar }
391 className={
392 'wcf-store-flow-importer__list wcf-items-list wcf-flow-row relative flex gap-6 flex-wrap justify-center'
393 }
394 >
395 { filteredFlows.map( ( item ) => (
396 <RadioGroup.Option
397 key={ item.ID }
398 value={ item.ID }
399 data-key={ item.ID }
400 className={ ( {
401 checked,
402 active,
403 } ) =>
404 classNames(
405 `wcf-item hover:translate-y-[-1px] rounded transition-all flex-grow-0 flex-shrink-0 basis-[calc(33.33%-7rem)] relative`,
406 checked
407 ? 'border-0'
408 : 'border-gray-300',
409 active
410 ? 'border-0'
411 : 'border-gray-300'
412 )
413 }
414 >
415 { ( { checked, active } ) => (
416 <>
417 <GlobalFlowItem
418 key={ item.ID }
419 item={ item }
420 isChecked={
421 checked
422 }
423 isActive={ active }
424 />
425 { /* <div
426 className={ classNames(
427 active
428 ? 'border-2'
429 : 'border-2',
430 checked
431 ? 'border-orange-500'
432 : 'border-transparent',
433 'absolute -inset-px rounded pointer-events-none'
434 ) }
435 aria-hidden="true"
436 /> */ }
437 </>
438 ) }
439 </RadioGroup.Option>
440 ) ) }
441 </RadioGroup>
442
443 <span
444 id={
445 'wcf-selected-store-checkout-template'
446 }
447 data-selected-flow={ selectedStoreFlow }
448 data-selected-flow-info={ JSON.stringify(
449 filteredFlows[ selectedStoreFlow ]
450 ) }
451 ></span>
452 </>
453 ) }
454 </div>
455 </div>
456 </div>
457 </div>
458
459 { /* Sidemenu */ }
460 { ShowSideBar && (
461 <div className="wcf-bg--overlay w-full h-full bg-white absolute before:block top-0 right-0 left-0 z-50">
462 <div className="wcf-sidebar bg-[#F7F7F9] shadow w-full">
463 <div className="wcf-sidebar--header">
464 <div className="wcf-template-info flex gap-3 items-center">
465 <button
466 type="button"
467 className="p-3 border border-slate-200 rounded hover:border-slate-500 text-gray-400 hover:text-gray-800 "
468 onClick={ showOptionsSideBar }
469 >
470 <ArrowLeftIcon
471 className="w-4 h-4 stroke-2"
472 aria-hidden="true"
473 />
474 </button>
475
476 <div className="wcf-template-name">
477 <p className="text-[#6B7280]">
478 { __(
479 'Selected Template:',
480 'cartflows'
481 ) }
482 </p>
483 <h3 className="font-semibold text-gray-600 text-base">
484 { selectedStoreFlowTitle }
485 </h3>
486 </div>
487 </div>
488 <div className="wcf-header-action--buttons flex gap-4">
489 <div className="wcf-design--options flex gap-6">
490 <UploadSiteLogo
491 defaultPageBuilder={ page_builder }
492 />
493 <ColorPickerField
494 id={ 'primary_color' }
495 name={ 'primary_color' }
496 label={ __(
497 'Change Primary Color',
498 'cartflows'
499 ) }
500 value={ '' }
501 displayAs={ 'button' }
502 handleOnchange={ onPaletteChange }
503 />
504 </div>
505 </div>
506 </div>
507 </div>
508
509 <div className="wcf-sidebar-template-preview w-full h-screen relative">
510 { previewProcessing ? (
511 <TemplateLoadingSkeleton />
512 ) : null }
513
514 { '' !== previewUrl && (
515 <iframe
516 id="cartflows-templates-preview"
517 title="Website Preview"
518 height="100%"
519 width="100%"
520 src={ previewUrl }
521 onLoad={ handleIframeOnLoad }
522 // eslint-disable-next-line react/no-unknown-property
523 allowpaymentrequest="true"
524 sandbox="allow-scripts allow-same-origin"
525 />
526 ) }
527
528 <div
529 className={ `wcf-sidebar--footer fixed bottom-0 bg-white p-3.5 w-full border-t border-slate-200 ${
530 previewProcessing ? 'wcf-content-blocked' : ''
531 }` }
532 >
533 <div className="wcf-options--row flex justify-between items-center">
534 <div className="wcf-footer--info-wrapper">
535 <span className="text-sm font-medium text-primary-600 text-center block tracking-[.24em] uppercase">
536 { __( 'Step 4 of 6', 'cartflows' ) }
537 </span>
538 </div>
539 <div className="wcf-footer--action-buttons flex items-center gap-5">
540 { hasError && (
541 <p className="wcf-import-error-wrapper flex bg-red-100 p-3 rounded items-center gap-5">
542 <h3 className="wcf-import-error--heading font-normal text-red-700 text-sm">
543 { parse( errorMessage ) }
544 </h3>
545 <span className="wcf-import-error--message text-sm text-slate-800">
546 { parse( callToAction ) }
547 </span>
548 </p>
549 ) }
550
551 { 'pro' ===
552 filteredFlows[ selectedStoreFlow ]
553 .type && ! wcfCartflowsTypePro() ? (
554 <>
555 <div className="font-medium text-sm text-red-700">
556 { __(
557 'Access all of our pro templates when you upgrade your plan to CartFlows Pro today.',
558 'cartflows'
559 ) }
560 </div>
561 <a
562 className={ `wcf-wizard--button px-5 py-2 text-sm hover:text-white` }
563 href="https://cartflows.com/?utm_source=dashboard&utm_medium=free-cartflows&utm_campaign=go-pro"
564 target="_blank"
565 rel="noreferrer"
566 >
567 { __(
568 'Get CartFlows Pro',
569 'cartflows'
570 ) }
571 </a>
572 </>
573 ) : (
574 <button
575 className={ `wcf-wizard--button px-5 py-2 text-sm ${
576 action_button.button_class
577 ? action_button.button_class
578 : ''
579 }` }
580 >
581 { action_button.button_text }
582 <ArrowRightIcon
583 className="w-5 mt-0.5 ml-1.5 stroke-2"
584 aria-hidden="true"
585 />
586 </button>
587 ) }
588 </div>
589 </div>
590 </div>
591 </div>
592 </div>
593 ) }
594 </div>
595 );
596 }
597
598 export default GlobalCheckout;
599