PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.12.12
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.12.12
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.12.12, at admin/modules/setup-wizard/js/funnel-generator.js

195 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 funnel_type: 'sales',
55 builder: builder || 'gutenberg',
56 } )
57 .success( () => resolve() )
58 .error( ( error ) => reject( error ) );
59 } );
60 }
61
62 /**
63 * Create the empty funnel that template steps are imported into.
64 *
65 * Published immediately — a store-checkout funnel takes over the native
66 * checkout the moment it exists.
67 *
68 * @param {Object} template Selected template.
69 * @return {Promise<Object>} Funnel creation response.
70 */
71 function createFunnel( template ) {
72 const data = {
73 steps: filterStepsForImport( template?.steps || [] ),
74 name: 'Store Checkout',
75 source: 'remote',
76 type: 'wc',
77 status: 'publish',
78 remoteID: template?.ID || template?.id,
79 is_store_checkout: true,
80 };
81
82 return new Promise( ( resolve, reject ) => {
83 wpAjaxHelperRequest( 'wpfunnel-import-funnel', data )
84 .success( ( response ) => resolve( response || {} ) )
85 .error( ( error ) => reject( error ) );
86 } );
87 }
88
89 /**
90 * Import each template step into the newly created funnel.
91 *
92 * Individual step failures are tolerated so one bad step does not abort the
93 * whole funnel, but importing zero steps is treated as a hard failure.
94 *
95 * @param {number} funnelId Funnel ID.
96 * @param {Object} template Selected template.
97 * @return {Promise<Object>} Imported step IDs keyed by step type, plus the first step link.
98 */
99 async function importSteps( funnelId, template ) {
100 const stepsToImport = filterStepsForImport( template?.steps || [] );
101 const importedSteps = {};
102 let firstStepLink = '';
103
104 for ( let index = 0; index < stepsToImport.length; index++ ) {
105 const step = stepsToImport[ index ];
106
107 try {
108 const response = await apiFetch( {
109 path: `${ restRoot() }wpfunnels/v1/steps/wpfunnel-import-step`,
110 method: 'POST',
111 data: {
112 step,
113 funnelID: funnelId,
114 source: 'remote',
115 importType: 'templates',
116 },
117 } );
118
119 if ( response?.stepID && step?.step_type ) {
120 importedSteps[ step.step_type ] = response.stepID;
121 }
122
123 if ( 0 === index && response?.stepViewLink ) {
124 firstStepLink = response.stepViewLink;
125 }
126 } catch ( error ) {
127 console.error( `Error importing step ${ stepType( step ) || index }:`, error );
128 }
129 }
130
131 if ( ! Object.keys( importedSteps ).length ) {
132 throw new Error( 'Unable to import the selected template steps.' );
133 }
134
135 return { importedSteps, firstStepLink };
136 }
137
138 /**
139 * Run WPFunnels' post-creation bookkeeping.
140 *
141 * Failures here are non-fatal: the funnel and its steps already exist, so a
142 * rejected call should not present the user with a failed setup.
143 *
144 * @param {number} funnelId Funnel ID.
145 * @param {Object} importedSteps Imported step IDs keyed by step type.
146 * @return {Promise} Always resolves.
147 */
148 function afterFunnelCreation( funnelId, importedSteps ) {
149 return new Promise( ( resolve ) => {
150 wpAjaxHelperRequest( 'wpfunnel-after-funnel-creation', {
151 funnelID: funnelId,
152 source: 'remote',
153 importedSteps: Object.values( importedSteps || {} ),
154 goal: 'improve-checkout',
155 is_store_checkout: true,
156 } )
157 .success( ( response ) => resolve( response ) )
158 .error( () => resolve() );
159 } );
160 }
161
162 /**
163 * Generate the store-checkout funnel from the selected template.
164 *
165 * @param {Object} options Generation options.
166 * @param {string} options.builder Selected builder slug.
167 * @param {Object} options.template Selected template.
168 * @param {Function} options.onStatus Called with a human-readable progress string.
169 * @return {Promise<Object>} The created funnel ID and its first step link.
170 */
171 export async function generateFunnel( { builder, template, onStatus = () => {} } ) {
172 if ( ! template || ! Array.isArray( template.steps ) || ! template.steps.length ) {
173 throw new Error( 'Unable to generate a funnel because no template was selected.' );
174 }
175
176 onStatus( 'Configuring settings...' );
177 await updateGeneralSettings( builder );
178
179 onStatus( 'Creating store checkout...' );
180 const funnelResponse = await createFunnel( template );
181 const funnelId = funnelResponse?.funnelID;
182
183 if ( ! funnelId ) {
184 throw new Error( 'Funnel could not be created. Please try again.' );
185 }
186
187 onStatus( 'Importing template steps...' );
188 const { importedSteps, firstStepLink } = await importSteps( funnelId, template );
189
190 onStatus( 'Finalizing...' );
191 await afterFunnelCreation( funnelId, importedSteps );
192
193 return { funnelId, firstStepLink };
194 }
195