PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.3
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.3
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / admin / modules / setup-wizard / js / funnel-generator.js

funnel-generator.js in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.3, at admin/modules/setup-wizard/js/funnel-generator.js

194 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Funnel generation core for the setup wizard's store-checkout step.
3 *
4 * Extracted from the generation logic that used to live duplicated inside
5 * StoreCheckout.vue and GenerateFunnel.vue.
6 *
7 * @since 3.4.0
8 */
9 import apiFetch from '@wordpress/api-fetch';
10
11 /**
12 * Store checkout replaces the native WooCommerce checkout, so only these two
13 * step types are ever imported.
14 */
15 const ALLOWED_STEPS = [ 'checkout', 'thankyou' ];
16
17 /**
18 * Read a step's type regardless of which casing the template API used.
19 *
20 * @param {Object} step Template step.
21 * @return {string} Step type slug, or an empty string.
22 */
23 const stepType = ( step ) => step?.step_type || step?.stepType || step?.type || '';
24
25 /**
26 * Normalise the REST root so we can safely concatenate a route onto it.
27 *
28 * @return {string} REST root with exactly one trailing slash.
29 */
30 const restRoot = () => {
31 const url = window?.setup_wizard_obj?.rest_api_url || '';
32 return url.endsWith( '/' ) ? url : `${ url }/`;
33 };
34
35 /**
36 * Reduce a template's steps to just the checkout and thank-you page.
37 *
38 * @param {Array} steps Template steps.
39 * @return {Array} Steps to import.
40 */
41 export function filterStepsForImport( steps ) {
42 return ( steps || [] ).filter( ( step ) => ALLOWED_STEPS.includes( stepType( step ) ) );
43 }
44
45 /**
46 * Persist the builder picked earlier in the wizard.
47 *
48 * @param {string} builder Builder slug.
49 * @return {Promise} Resolves once settings are saved.
50 */
51 function updateGeneralSettings( builder ) {
52 return new Promise( ( resolve, reject ) => {
53 wpAjaxHelperRequest( 'update-general-settings', {
54 builder: builder || 'gutenberg',
55 } )
56 .success( () => resolve() )
57 .error( ( error ) => reject( error ) );
58 } );
59 }
60
61 /**
62 * Create the empty funnel that template steps are imported into.
63 *
64 * Published immediately — a store-checkout funnel takes over the native
65 * checkout the moment it exists.
66 *
67 * @param {Object} template Selected template.
68 * @return {Promise<Object>} Funnel creation response.
69 */
70 function createFunnel( template ) {
71 const data = {
72 steps: filterStepsForImport( template?.steps || [] ),
73 name: 'Store Checkout',
74 source: 'remote',
75 type: 'wc',
76 status: 'publish',
77 remoteID: template?.ID || template?.id,
78 is_store_checkout: true,
79 };
80
81 return new Promise( ( resolve, reject ) => {
82 wpAjaxHelperRequest( 'wpfunnel-import-funnel', data )
83 .success( ( response ) => resolve( response || {} ) )
84 .error( ( error ) => reject( error ) );
85 } );
86 }
87
88 /**
89 * Import each template step into the newly created funnel.
90 *
91 * Individual step failures are tolerated so one bad step does not abort the
92 * whole funnel, but importing zero steps is treated as a hard failure.
93 *
94 * @param {number} funnelId Funnel ID.
95 * @param {Object} template Selected template.
96 * @return {Promise<Object>} Imported step IDs keyed by step type, plus the first step link.
97 */
98 async function importSteps( funnelId, template ) {
99 const stepsToImport = filterStepsForImport( template?.steps || [] );
100 const importedSteps = {};
101 let firstStepLink = '';
102
103 for ( let index = 0; index < stepsToImport.length; index++ ) {
104 const step = stepsToImport[ index ];
105
106 try {
107 const response = await apiFetch( {
108 path: `${ restRoot() }wpfunnels/v1/steps/wpfunnel-import-step`,
109 method: 'POST',
110 data: {
111 step,
112 funnelID: funnelId,
113 source: 'remote',
114 importType: 'templates',
115 },
116 } );
117
118 if ( response?.stepID && step?.step_type ) {
119 importedSteps[ step.step_type ] = response.stepID;
120 }
121
122 if ( 0 === index && response?.stepViewLink ) {
123 firstStepLink = response.stepViewLink;
124 }
125 } catch ( error ) {
126 console.error( `Error importing step ${ stepType( step ) || index }:`, error );
127 }
128 }
129
130 if ( ! Object.keys( importedSteps ).length ) {
131 throw new Error( 'Unable to import the selected template steps.' );
132 }
133
134 return { importedSteps, firstStepLink };
135 }
136
137 /**
138 * Run WPFunnels' post-creation bookkeeping.
139 *
140 * Failures here are non-fatal: the funnel and its steps already exist, so a
141 * rejected call should not present the user with a failed setup.
142 *
143 * @param {number} funnelId Funnel ID.
144 * @param {Object} importedSteps Imported step IDs keyed by step type.
145 * @return {Promise} Always resolves.
146 */
147 function afterFunnelCreation( funnelId, importedSteps ) {
148 return new Promise( ( resolve ) => {
149 wpAjaxHelperRequest( 'wpfunnel-after-funnel-creation', {
150 funnelID: funnelId,
151 source: 'remote',
152 importedSteps: Object.values( importedSteps || {} ),
153 goal: 'improve-checkout',
154 is_store_checkout: true,
155 } )
156 .success( ( response ) => resolve( response ) )
157 .error( () => resolve() );
158 } );
159 }
160
161 /**
162 * Generate the store-checkout funnel from the selected template.
163 *
164 * @param {Object} options Generation options.
165 * @param {string} options.builder Selected builder slug.
166 * @param {Object} options.template Selected template.
167 * @param {Function} options.onStatus Called with a human-readable progress string.
168 * @return {Promise<Object>} The created funnel ID and its first step link.
169 */
170 export async function generateFunnel( { builder, template, onStatus = () => {} } ) {
171 if ( ! template || ! Array.isArray( template.steps ) || ! template.steps.length ) {
172 throw new Error( 'Unable to generate a funnel because no template was selected.' );
173 }
174
175 onStatus( 'Configuring settings...' );
176 await updateGeneralSettings( builder );
177
178 onStatus( 'Creating store checkout...' );
179 const funnelResponse = await createFunnel( template );
180 const funnelId = funnelResponse?.funnelID;
181
182 if ( ! funnelId ) {
183 throw new Error( 'Funnel could not be created. Please try again.' );
184 }
185
186 onStatus( 'Importing template steps...' );
187 const { importedSteps, firstStepLink } = await importSteps( funnelId, template );
188
189 onStatus( 'Finalizing...' );
190 await afterFunnelCreation( funnelId, importedSteps );
191
192 return { funnelId, firstStepLink };
193 }
194