| 1 |
<template> |
| 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. |
| 13 |
</p> |
| 14 |
|
| 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 |
| 23 |
</button> |
| 24 |
|
| 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" /> |
| 34 |
</svg> |
| 35 |
</button> |
| 36 |
</div> |
| 37 |
|
| 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> |
| 68 |
</div> |
| 69 |
</div> |
| 70 |
</template> |
| 71 |
|
| 72 |
<script> |
| 73 |
import apiFetch from '@wordpress/api-fetch'; |
| 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 |
|
| 147 |
export default { |
| 148 |
name: 'Complete', |
| 149 |
props: { |
| 150 |
funnelId: { |
| 151 |
type: [ Number, String ], |
| 152 |
default: null, |
| 153 |
}, |
| 154 |
firstStepLink: { |
| 155 |
type: String, |
| 156 |
default: '', |
| 157 |
}, |
| 158 |
}, |
| 159 |
emits: [ 'nav' ], |
| 160 |
data() { |
| 161 |
return { |
| 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 |
}; |
| 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 |
}, |
| 198 |
mounted() { |
| 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(); |
| 210 |
}, |
| 211 |
beforeUnmount() { |
| 212 |
this.stopConfetti(); |
| 213 |
}, |
| 214 |
methods: { |
| 215 |
startConfetti() { |
| 216 |
const canvas = this.$refs.confetti; |
| 217 |
if ( ! canvas ) { |
| 218 |
return; |
| 219 |
} |
| 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 ) { |
| 240 |
const wizardObj = window.setup_wizard_obj || {}; |
| 241 |
const restApiUrl = wizardObj.rest_api_url || ''; |
| 242 |
|
| 243 |
if ( ! restApiUrl ) { |
| 244 |
return Promise.resolve(); |
| 245 |
} |
| 246 |
|
| 247 |
const base = restApiUrl.endsWith( '/' ) ? restApiUrl : `${ restApiUrl }/`; |
| 248 |
|
| 249 |
return apiFetch( { |
| 250 |
url: `${ base }wpfunnels/v1/setup-wizard/complete-step`, |
| 251 |
method: 'POST', |
| 252 |
data: { |
| 253 |
funnelId: this.funnelId, |
| 254 |
action, |
| 255 |
goal: 'improve-checkout', |
| 256 |
total_steps: 4, |
| 257 |
}, |
| 258 |
} ).catch( () => {} ); |
| 259 |
}, |
| 260 |
|
| 261 |
async viewFunnel() { |
| 262 |
await this.completeStep( 'viewFunnel' ); |
| 263 |
const wizardObj = window.setup_wizard_obj || {}; |
| 264 |
const adminUrl = wizardObj.admin_url || ''; |
| 265 |
|
| 266 |
if ( this.funnelId ) { |
| 267 |
window.location.href = `${ adminUrl }admin.php?page=edit_funnel&id=${ this.funnelId }`; |
| 268 |
return; |
| 269 |
} |
| 270 |
if ( this.firstStepLink ) { |
| 271 |
window.open( this.firstStepLink, '_blank' ); |
| 272 |
return; |
| 273 |
} |
| 274 |
window.location.href = wizardObj.dashboard_url || `${ adminUrl }admin.php?page=wpfunnels`; |
| 275 |
}, |
| 276 |
|
| 277 |
async goToDashboard() { |
| 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`; |
| 281 |
}, |
| 282 |
}, |
| 283 |
}; |
| 284 |
</script> |
| 285 |
|