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
← All changes | admin/modules/setup-wizard/components/Content/Complete.vue +234 -176 3.12.3 → 3.13.3 View file →
@@ -1,67 +1,71 @@
1 1 <template>
2 - <div class="wpfnl-mm-complete">
3 - <!-- Header -->
4 - <div class="wpfnl-mm-complete-header">
5 - <h2 class="wpfnl-mm-complete-title">
6 - {{ titleText }}
7 - </h2>
8 - <p class="wpfnl-mm-complete-subtitle">
9 - {{ subtitleText }}
2 + <div class="wpfnl-wz-success" role="main">
3 + <!-- Sits behind the content; purely decorative -->
4 + <canvas ref="confetti" class="wpfnl-wz-success__confetti" aria-hidden="true"></canvas>
5 +
6 + <div class="wpfnl-wz-success__body">
7 + <div class="wpfnl-wz-success__emoji" aria-hidden="true">🎉</div>
8 +
9 + <h1 class="wpfnl-wz-success__heading">Congratulations, You Did it!</h1>
10 + <p class="wpfnl-wz-success__sub">
11 + WPFunnels is set up on your website! You're now ready to build
12 + high-converting sales funnels.
10 13 </p>
11 - </div>
12 14
13 - <!-- Video/Preview Section -->
14 - <div class="wpfnl-mm-complete-video-wrapper">
15 - <div class="wpfnl-mm-complete-video">
16 - <!-- Poster Image -->
17 - <div class="wpfnl-mm-complete-video-bg" v-show="!isVideoPlaying">
18 - <img
19 - :src="posterImage"
20 - alt="Video Poster"
21 - class="wpfnl-mm-complete-video-image"
22 - />
23 - <div class="wpfnl-mm-complete-video-overlay" @click="playVideo"></div>
24 - </div>
25 - <!-- YouTube iframe -->
26 - <iframe
27 - v-show="isVideoPlaying"
28 - class="wpfnl-mm-complete-video-iframe"
29 - :src="videoUrl"
30 - title="WPFunnels Tutorial"
31 - frameborder="0"
32 - allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
33 - allowfullscreen
34 - ></iframe>
35 - <div class="wpfnl-mm-complete-video-player" v-show="!isVideoPlaying">
36 - <button class="wpfnl-mm-complete-play-btn" @click="playVideo">
37 - <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
38 - <path d="M4 2L14 8L4 14V2Z" fill="white"/>
39 - </svg>
40 - </button>
41 - <div class="wpfnl-mm-complete-progress-bar">
42 - <div class="wpfnl-mm-complete-progress-fill"></div>
43 - </div>
44 - </div>
45 - </div>
46 - </div>
47 -
48 - <!-- Buttons Section -->
49 - <div class="wpfnl-mm-complete-actions">
50 - <div class="wpfnl-mm-complete-buttons">
51 - <button class="wpfnl-mm-btn wpfnl-mm-btn-secondary" @click="goToDashboard">
52 - Go to Dashboard
15 + <div class="wpfnl-wz-success__actions">
16 + <button
17 + type="button"
18 + class="wpfnl-wz-btn wpfnl-wz-btn--lg wpfnl-wz-btn--pill"
19 + :class="hasFunnel ? 'wpfnl-wz-btn--ghost' : 'wpfnl-wz-btn--primary'"
20 + @click="goToDashboard"
21 + >
22 + Explore dashboard
53 23 </button>
54 24
55 - <button class="wpfnl-mm-btn wpfnl-mm-btn-primary" @click="viewFunnel">
56 - {{ isStoreCheckout ? 'Go to Editor' : 'Test the Funnel' }}
57 - <svg width="17" height="12" viewBox="0 0 17 12" fill="none" xmlns="http://www.w3.org/2000/svg">
58 - <path d="M1 6H16M16 6L11 1M16 6L11 11" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
25 + <button
26 + v-if="hasFunnel"
27 + type="button"
28 + class="wpfnl-wz-btn wpfnl-wz-btn--primary wpfnl-wz-btn--lg wpfnl-wz-btn--pill"
29 + @click="viewFunnel"
30 + >
31 + Customize your checkout
32 + <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
33 + <path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
59 34 </svg>
60 35 </button>
61 36 </div>
62 37
63 -
38 + <div class="wpfnl-wz-success__resources">
39 + <p class="wpfnl-wz-success__resources-title">Want to get more out of WPFunnels?</p>
40 +
41 + <div class="wpfnl-wz-success__resources-grid">
42 + <a
43 + v-for="item in resources"
44 + :key="item.label"
45 + :href="item.url"
46 + target="_blank"
47 + rel="noreferrer noopener"
48 + class="wpfnl-wz-success__resource-card"
49 + >
50 + <span class="wpfnl-wz-success__resource-icon" aria-hidden="true" v-html="item.icon"></span>
51 + <span class="wpfnl-wz-success__resource-label">{{ item.label }}</span>
52 + </a>
53 + </div>
54 +
55 + <div class="wpfnl-wz-success__resources-footer">
56 + <p>These are always available from your dashboard.</p>
57 + <p>
58 + Want more power?
59 + <a
60 + href="https://getwpfunnels.com/pricing/"
61 + target="_blank"
62 + rel="noreferrer noopener"
63 + class="wpfnl-wz-success__pro-link"
64 + >See what Pro unlocks</a>
65 + </p>
66 + </div>
67 + </div>
64 68 </div>
65 69 </div>
66 70 </template>
67 71
@@ -67,160 +71,214 @@
67 71
68 72 <script>
69 73 import apiFetch from '@wordpress/api-fetch';
70 74
75 +/**
76 + * Canvas confetti for the completion screen.
77 + *
78 + * Returns handles so the caller can fade it out and then stop the loop —
79 + * leaving a rAF running after the component unmounts would leak.
80 + *
81 + * @param {HTMLCanvasElement} canvas Target canvas.
82 + * @return {{ startFade: Function, stop: Function }} Animation controls.
83 + */
84 +function runConfetti( canvas ) {
85 + const ctx = canvas.getContext( '2d' );
86 + const COLORS = [ '#6E42D3', '#a78bfa', '#fbbf24', '#34d399', '#f87171', '#60a5fa', '#f472b6' ];
87 +
88 + let globalAlpha = 1;
89 + let fading = false;
90 + let animId;
91 +
92 + const pieces = Array.from( { length: 150 }, () => ( {
93 + x: Math.random() * canvas.width,
94 + y: Math.random() * canvas.height - canvas.height,
95 + w: Math.random() * 10 + 5,
96 + h: Math.random() * 5 + 3,
97 + color: COLORS[ Math.floor( Math.random() * COLORS.length ) ],
98 + speed: Math.random() * 3 + 1.5,
99 + angle: Math.random() * Math.PI * 2,
100 + spin: ( Math.random() - 0.5 ) * 0.1,
101 + drift: ( Math.random() - 0.5 ) * 1.5,
102 + } ) );
103 +
104 + const draw = () => {
105 + ctx.clearRect( 0, 0, canvas.width, canvas.height );
106 +
107 + if ( fading ) {
108 + globalAlpha = Math.max( 0, globalAlpha - 0.018 );
109 + }
110 +
111 + if ( globalAlpha <= 0 ) {
112 + return;
113 + }
114 +
115 + pieces.forEach( ( p ) => {
116 + ctx.save();
117 + ctx.globalAlpha = globalAlpha;
118 + ctx.translate( p.x + p.w / 2, p.y + p.h / 2 );
119 + ctx.rotate( p.angle );
120 + ctx.fillStyle = p.color;
121 + ctx.fillRect( -p.w / 2, -p.h / 2, p.w, p.h );
122 + ctx.restore();
123 +
124 + p.y += p.speed;
125 + p.x += p.drift;
126 + p.angle += p.spin;
127 +
128 + if ( p.y > canvas.height + p.h ) {
129 + p.y = -p.h;
130 + p.x = Math.random() * canvas.width;
131 + }
132 + } );
133 +
134 + animId = requestAnimationFrame( draw );
135 + };
136 +
137 + draw();
138 +
139 + return {
140 + startFade: () => {
141 + fading = true;
142 + },
143 + stop: () => cancelAnimationFrame( animId ),
144 + };
145 +}
146 +
71 147 export default {
72 148 name: 'Complete',
73 149 props: {
74 150 funnelId: {
75 - type: [Number, String],
76 - default: null
151 + type: [ Number, String ],
152 + default: null,
77 153 },
78 154 firstStepLink: {
79 155 type: String,
80 - default: ''
156 + default: '',
81 157 },
82 - selectedGoal: {
83 - type: String,
84 - default: 'sales'
85 - },
86 - agreeToShare: {
87 - type: Boolean,
88 - default: false
89 - }
90 158 },
159 + emits: [ 'nav' ],
91 160 data() {
92 161 return {
93 - isVideoPlaying: false,
94 - posterImage: this.getPosterImage(),
95 - videoUrl: '',
96 - contactRequestInFlight: false,
97 - contactCreated: false,
98 - }
162 + fadeTimer: null,
163 + confetti: null,
164 + resources: [
165 + {
166 + label: 'Blog & tutorials',
167 + url: 'https://www.youtube.com/@WPFunnels',
168 + icon: '<svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="8" fill="#fee2e2"/><path d="M11 10l7 4-7 4V10z" fill="#ef4444"/></svg>',
169 + },
170 + {
171 + label: 'Join the community',
172 + url: 'https://www.facebook.com/groups/wpfunnels',
173 + icon: '<svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="8" fill="#dbeafe"/><circle cx="12" cy="12" r="3" stroke="#3b82f6" stroke-width="1.4"/><circle cx="18" cy="12" r="3" stroke="#3b82f6" stroke-width="1.4"/><path d="M7 22c0-2.8 2-4.5 5-4.5M21 22c0-2.8-2-4.5-5-4.5" stroke="#3b82f6" stroke-width="1.4" stroke-linecap="round"/></svg>',
174 + },
175 + {
176 + label: 'Get support',
177 + url: 'https://wordpress.org/support/plugin/wpfunnels/',
178 + icon: '<svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="8" fill="#d1fae5"/><path d="M10 14v-2a4 4 0 018 0v2" stroke="#059669" stroke-width="1.4" stroke-linecap="round"/><rect x="8" y="14" width="3" height="4" rx="1.5" fill="#059669"/><rect x="17" y="14" width="3" height="4" rx="1.5" fill="#059669"/></svg>',
179 + },
180 + {
181 + label: 'Docs',
182 + url: 'https://getwpfunnels.com/docs/getting-started-with-wpfunnels/',
183 + icon: '<svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="8" fill="#e0f2fe"/><rect x="8" y="7" width="12" height="14" rx="2" stroke="#0284c7" stroke-width="1.4"/><path d="M11 12h6M11 15.5h4" stroke="#0284c7" stroke-width="1.4" stroke-linecap="round"/></svg>',
184 + },
185 + ],
186 + };
99 187 },
188 + computed: {
189 + /**
190 + * Whether the template step actually built something. False when the
191 + * user skipped it, in which case there is no funnel to open and the
192 + * editor CTA is hidden.
193 + */
194 + hasFunnel() {
195 + return !! this.funnelId;
196 + },
197 + },
100 198 mounted() {
101 - this.maybeCreateContact();
199 + // The completion screen is terminal — the footer offers no way onward.
200 + this.$emit( 'nav', {
201 + showBack: false,
202 + showNext: false,
203 + showSkip: false,
204 + canProceed: false,
205 + busy: false,
206 + label: '',
207 + } );
208 +
209 + this.startConfetti();
102 210 },
103 - computed: {
104 - isStoreCheckout() {
105 - return this.selectedGoal === 'improve-checkout';
106 - },
107 - titleText() {
108 - if (this.isStoreCheckout) {
109 - return `Your store checkout is ready! 🎉`;
110 - }
111 - return `Your funnel is live 🎉`;
112 - },
113 - subtitleText() {
114 - if (this.isStoreCheckout) {
115 - return `Your optimized checkout is set up. Customers will now experience a streamlined checkout flow designed to reduce cart abandonment and boost conversions.`;
116 - }
117 - if (this.selectedGoal === 'leads') {
118 - return `Your first funnel is set up. Visitors opting in here will now see your next offer. Once leads start coming in, we'll show you exactly how this funnel is growing your list.`;
119 - }
120 - return `Your first funnel is set up. Customers who purchase this product will now see your order bump and upsell offers. Once real orders start coming in, we'll show you exactly how much additional revenue this funnel generates.`;
121 - },
122 - infoText() {
123 - if (this.isStoreCheckout) {
124 - return `You can customize your checkout page further from the funnel editor.`;
125 - }
126 - if (this.selectedGoal === 'leads') {
127 - return `Once this funnel starts bringing in leads, you can clone it for your other offers in just a few clicks.`;
128 - }
129 - return `Once you see this funnel working, you can clone it for your other products in a few clicks.`;
130 - }
211 + beforeUnmount() {
212 + this.stopConfetti();
131 213 },
132 214 methods: {
133 - maybeCreateContact() {
134 - if (!this.agreeToShare || this.contactCreated || this.contactRequestInFlight) {
215 + startConfetti() {
216 + const canvas = this.$refs.confetti;
217 + if ( ! canvas ) {
135 218 return;
136 219 }
137 220
221 + canvas.width = window.innerWidth;
222 + canvas.height = window.innerHeight;
223 +
224 + this.confetti = runConfetti( canvas );
225 + this.fadeTimer = setTimeout( this.confetti.startFade, 3200 );
226 + },
227 +
228 + stopConfetti() {
229 + clearTimeout( this.fadeTimer );
230 + this.confetti?.stop();
231 + },
232 +
233 + /**
234 + * Report the outcome before leaving the wizard.
235 + *
236 + * @param {string} action Which exit the user took.
237 + * @return {Promise} Always resolves.
238 + */
239 + completeStep( action ) {
138 240 const wizardObj = window.setup_wizard_obj || {};
139 - const restApiUrl = wizardObj.rest_api_url;
140 - if (!restApiUrl) {
141 - return;
241 + const restApiUrl = wizardObj.rest_api_url || '';
242 +
243 + if ( ! restApiUrl ) {
244 + return Promise.resolve();
142 245 }
143 246
144 - const payload = {
145 - email: wizardObj.admin_email,
146 - name: wizardObj.admin_name
147 - };
247 + const base = restApiUrl.endsWith( '/' ) ? restApiUrl : `${ restApiUrl }/`;
148 248
149 - this.contactRequestInFlight = true;
150 - apiFetch({
151 - path: `${restApiUrl}wpfunnels/v1/settings/create-contact/`,
249 + return apiFetch( {
250 + url: `${ base }wpfunnels/v1/setup-wizard/complete-step`,
152 251 method: 'POST',
153 - data: payload
154 - })
155 - .then(() => {
156 - this.contactCreated = true;
157 - })
158 - .catch(error => {
159 - console.error('Error creating contact:', error);
160 - })
161 - .finally(() => {
162 - this.contactRequestInFlight = false;
163 - });
164 - },
165 - getPosterImage() {
166 - // Use the video poster from setup wizard object
167 - const wizardObj = window.setup_wizard_obj || {}
168 - return wizardObj.wizard_video_poster || ''
169 - },
170 - handleCompleteStep(action) {
171 - const wizardObj = window.setup_wizard_obj || {}
172 - const restApiUrl = wizardObj.rest_api_url || wizardObj.rest_url || ''
173 - const totalSteps = this.selectedGoal === 'sales' ? 7
174 - : (this.selectedGoal === 'order-value' || this.selectedGoal === 'improve-checkout') ? 6
175 - : 5;
176 - const requestArgs = {
177 - method: 'POST',
178 252 data: {
179 253 funnelId: this.funnelId,
180 - action: action,
181 - goal: this.selectedGoal,
182 - total_steps: totalSteps,
183 - }
184 - }
254 + action,
255 + goal: 'improve-checkout',
256 + total_steps: 4,
257 + },
258 + } ).catch( () => {} );
259 + },
185 260
186 - if (restApiUrl) {
187 - const normalizedRestUrl = restApiUrl.endsWith('/') ? restApiUrl : `${restApiUrl}/`
188 - requestArgs.url = `${normalizedRestUrl}wpfunnels/v1/setup-wizard/complete-step`
189 - } else {
190 - requestArgs.path = '/wpfunnels/v1/setup-wizard/complete-step'
191 - }
261 + async viewFunnel() {
262 + await this.completeStep( 'viewFunnel' );
263 + const wizardObj = window.setup_wizard_obj || {};
264 + const adminUrl = wizardObj.admin_url || '';
192 265
193 - return apiFetch(requestArgs).catch(() => {})
194 - },
195 - async viewFunnel() {
196 - this.maybeCreateContact();
197 - await this.handleCompleteStep('viewFunnel')
198 - const wizardObj = window.setup_wizard_obj || {}
199 - if (this.isStoreCheckout) {
200 - const adminUrl = wizardObj.admin_url || ''
201 - window.location.href = `${adminUrl}admin.php?page=edit_funnel&id=${this.funnelId}`
202 - return
266 + if ( this.funnelId ) {
267 + window.location.href = `${ adminUrl }admin.php?page=edit_funnel&id=${ this.funnelId }`;
268 + return;
203 269 }
204 - if (this.firstStepLink) {
205 - window.open(this.firstStepLink, '_blank')
206 - } else {
207 - window.location.href = wizardObj.dashboard_url || `${wizardObj.admin_url}admin.php?page=wpfunnels`
270 + if ( this.firstStepLink ) {
271 + window.open( this.firstStepLink, '_blank' );
272 + return;
208 273 }
274 + window.location.href = wizardObj.dashboard_url || `${ adminUrl }admin.php?page=wpfunnels`;
209 275 },
276 +
210 277 async goToDashboard() {
211 - this.maybeCreateContact();
212 - await this.handleCompleteStep('goToDashboard')
213 - const wizardObj = window.setup_wizard_obj || {}
214 - window.location.href = wizardObj.dashboard_url || `${wizardObj.admin_url}admin.php?page=wpfunnels`
278 + await this.completeStep( 'goToDashboard' );
279 + const wizardObj = window.setup_wizard_obj || {};
280 + window.location.href = wizardObj.dashboard_url || `${ wizardObj.admin_url }admin.php?page=wpfunnels`;
215 281 },
216 - playVideo() {
217 - // Set video URL with autoplay and show the iframe
218 - this.videoUrl = 'https://www.youtube.com/embed/GrzIRl5jfBE?autoplay=1&rel=0'
219 - this.isVideoPlaying = true
220 - },
221 - showHelp() {
222 - window.open('https://getwpfunnels.com/docs/', '_blank')
223 - }
224 - }
225 -}
282 + },
283 +};
226 284 </script>