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 / components / Wizard.vue

Wizard.vue in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.3, at admin/modules/setup-wizard/components/Wizard.vue

274 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="wpfnl-wz">
3 <TopBar
4 :currentStep="currentStep"
5 :totalSteps="totalSteps"
6 @exit="requestExit"
7 />
8
9 <Content
10 ref="content"
11 :currentStep="currentStep"
12 :builder="builder"
13 :funnelId="funnelId"
14 :firstStepLink="firstStepLink"
15 :agreeToShare="agreeToShare"
16 :prefetchedTemplates="prefetchedTemplates"
17 @nav="onNav"
18 @next-step="handleNextStep"
19 @prev-step="handlePrevStep"
20 @exit="requestExit"
21 />
22
23 <!-- Welcome carries its own CTA; the footer takes over from step 2. -->
24 <WizardFooter
25 v-if="currentStep > 1"
26 :label="nav.label"
27 :canProceed="nav.canProceed"
28 :busy="nav.busy"
29 :showBack="nav.showBack && currentStep > 1"
30 :showNext="nav.showNext"
31 :showSkip="nav.showSkip"
32 @back="handlePrevStep"
33 @next="submitActiveStep"
34 @skip="skipActiveStep"
35 />
36 </div>
37 </template>
38
39 <script>
40 import apiFetch from '@wordpress/api-fetch';
41 import { addQueryArgs } from '@wordpress/url';
42 import Content from './Content/Content.vue';
43 import TopBar from './Chrome/TopBar.vue';
44 import WizardFooter from './Chrome/WizardFooter.vue';
45
46 const nonce = window.setup_wizard_obj.nonce;
47 apiFetch.use( apiFetch.createNonceMiddleware( nonce ) );
48
49 const TOTAL_STEPS = 4;
50
51 /**
52 * Step slugs, indexed by their 1-based position. Used for telemetry so the
53 * reported step names stay stable even if the labels change.
54 */
55 const STEP_NAMES = {
56 1: 'welcome',
57 2: 'required_installation',
58 3: 'choose_template',
59 4: 'complete',
60 };
61
62 const DEFAULT_NAV = {
63 label: 'Next',
64 canProceed: true,
65 busy: false,
66 showBack: true,
67 showNext: true,
68 showSkip: false,
69 };
70
71 export default {
72 name: 'Wizard',
73 components: {
74 Content,
75 TopBar,
76 WizardFooter,
77 },
78
79 data() {
80 return {
81 currentStep: 1,
82 totalSteps: TOTAL_STEPS,
83
84 // Collected across the flow.
85 agreeToShare: true,
86 builder: 'gutenberg',
87 funnelId: null,
88 firstStepLink: '',
89
90 // Footer state, published by whichever step is active.
91 nav: { ...DEFAULT_NAV },
92
93 prefetchedTemplates: [],
94 stepEnteredAt: Date.now(),
95 consentSaved: false,
96 };
97 },
98
99 mounted() {
100 this.trackStep( 'viewed', this.currentStep );
101 this.prefetchTemplates();
102 },
103
104 methods: {
105 /**
106 * Receive footer state from the active step.
107 *
108 * @param {Object} state Partial nav state.
109 */
110 onNav( state ) {
111 this.nav = { ...DEFAULT_NAV, ...( state || {} ) };
112 },
113
114 /**
115 * Ask the active step to advance. Steps own their own validation and
116 * side effects (installing plugins, generating a funnel), so the footer
117 * never advances the wizard directly.
118 */
119 submitActiveStep() {
120 const step = this.$refs.content?.activeStep();
121 if ( step && 'function' === typeof step.submit ) {
122 step.submit();
123 return;
124 }
125 this.handleNextStep();
126 },
127
128 /**
129 * Let the active step handle being skipped. Falls through to a plain
130 * advance for steps that offer no skip of their own.
131 */
132 skipActiveStep() {
133 const step = this.$refs.content?.activeStep();
134 if ( step && 'function' === typeof step.skip ) {
135 step.skip();
136 return;
137 }
138 this.handleNextStep();
139 },
140
141 requestExit() {
142 this.$refs.content?.showExitModal();
143 },
144
145 /**
146 * Warm the store-checkout template grid so the template step renders
147 * instantly. Re-run whenever the builder choice changes, since the API
148 * result is builder-specific.
149 */
150 prefetchTemplates() {
151 const builder = 'divi' === this.builder ? 'divi-builder'
152 : 'others' === this.builder ? 'gutenberg'
153 : ( this.builder || 'gutenberg' );
154
155 const baseUrl = ( window.setup_wizard_obj?.rest_api_url || '' ).replace( /\/$/, '' );
156 const path = addQueryArgs( `${ baseUrl }/wpfunnels/v1/templates/get_templates`, {
157 builder,
158 type: 'store_checkout',
159 } );
160
161 apiFetch( { path } )
162 .then( ( response ) => {
163 if ( response.success && response.templates?.length ) {
164 this.prefetchedTemplates = response.templates;
165 }
166 } )
167 .catch( () => {} );
168 },
169
170 getStepName( stepIndex ) {
171 return STEP_NAMES[ stepIndex ] || 'unknown';
172 },
173
174 trackStep( eventType, stepIndex ) {
175 const restApiUrl = window.setup_wizard_obj?.rest_api_url || '';
176 if ( ! restApiUrl ) {
177 return;
178 }
179
180 const base = restApiUrl.endsWith( '/' ) ? restApiUrl : `${ restApiUrl }/`;
181
182 apiFetch( {
183 url: `${ base }wpfunnels/v1/setup-wizard/track-step`,
184 method: 'POST',
185 data: {
186 event_type: eventType,
187 step_name: this.getStepName( stepIndex ),
188 step_index: stepIndex,
189 goal: 'improve-checkout',
190 time_on_step: 'viewed' === eventType
191 ? 0
192 : Math.round( ( Date.now() - this.stepEnteredAt ) / 1000 ),
193 total_steps: TOTAL_STEPS,
194 },
195 } ).catch( () => {} );
196 },
197
198 /**
199 * Persist the telemetry opt-in from the welcome step. Every later
200 * track-step call is gated on this, so it runs before step 2.
201 *
202 * @param {boolean} consented Whether the user opted in.
203 */
204 saveConsent( consented ) {
205 if ( this.consentSaved ) {
206 return;
207 }
208 this.consentSaved = true;
209
210 const restApiUrl = window.setup_wizard_obj?.rest_api_url || '';
211 if ( ! restApiUrl ) {
212 return;
213 }
214
215 const base = restApiUrl.endsWith( '/' ) ? restApiUrl : `${ restApiUrl }/`;
216
217 apiFetch( {
218 url: `${ base }wpfunnels/v1/setup-wizard/save-consent`,
219 method: 'POST',
220 data: { consented: !! consented },
221 } ).catch( () => {} );
222 },
223
224 /**
225 * Advance, folding whatever the finished step collected into wizard state.
226 *
227 * @param {Object} data Payload emitted by the step.
228 */
229 handleNextStep( data ) {
230 if ( data?.agreeToShare !== undefined ) {
231 this.agreeToShare = data.agreeToShare;
232 }
233 if ( data?.builder ) {
234 const builderChanged = data.builder !== this.builder;
235 this.builder = data.builder;
236 if ( builderChanged ) {
237 this.prefetchTemplates();
238 }
239 }
240 if ( data?.funnelId ) {
241 this.funnelId = data.funnelId;
242 }
243 if ( data?.firstStepLink ) {
244 this.firstStepLink = data.firstStepLink;
245 }
246
247 if ( 1 === this.currentStep ) {
248 this.saveConsent( this.agreeToShare );
249 }
250
251 this.trackStep( 'completed', this.currentStep );
252
253 if ( this.currentStep < TOTAL_STEPS ) {
254 this.currentStep++;
255 this.stepEnteredAt = Date.now();
256 this.nav = { ...DEFAULT_NAV };
257 this.trackStep( 'viewed', this.currentStep );
258 }
259 },
260
261 handlePrevStep() {
262 if ( this.currentStep <= 1 ) {
263 return;
264 }
265 this.trackStep( 'completed', this.currentStep );
266 this.currentStep--;
267 this.stepEnteredAt = Date.now();
268 this.nav = { ...DEFAULT_NAV };
269 this.trackStep( 'viewed', this.currentStep );
270 },
271 },
272 };
273 </script>
274