PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.1
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.1
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 2.8.17 All 260 releases
wpfunnels / admin / modules / setup-wizard / components / Content / Setup.vue

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

415 lines 15.8 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-center wpfnl-wz-install">
3
4 <h1 class="wpfnl-wz-h1">{{ pluginsSectionTitle }}</h1>
5 <p class="wpfnl-wz-sub">{{ pluginsSectionSubtitle }}</p>
6
7 <!-- Single unified white card — matches .wpfnl-mm-setup-section pattern -->
8 <div class="wpfnl-ri-card">
9
10 <!-- Builder Selection -->
11 <div class="wpfnl-ri-builder-section">
12 <p class="wpfnl-ri-builder-label">What page builder are you using?</p>
13 <div class="wpfnl-ri-builder-options">
14 <button
15 v-for="builder in builders"
16 :key="builder.id"
17 class="wpfnl-ri-builder-btn"
18 :class="{
19 'active': selectedBuilder === builder.id,
20 'disabled': isProBuilder(builder.id) && !isBuilderInstalled(builder.id)
21 }"
22 @click="selectBuilder(builder.id)"
23 :disabled="isProBuilder(builder.id) && !isBuilderInstalled(builder.id)"
24 >
25 <img :src="builder.image" :alt="builder.name" />
26 <span>{{ builder.name }}</span>
27 <span class="wpfnl-ri-not-installed" v-if="isProBuilder(builder.id) && !isBuilderInstalled(builder.id)">Not Installed</span>
28 </button>
29 </div>
30 </div>
31
32 <!-- Divider -->
33 <div class="wpfnl-ri-divider"></div>
34
35 <!-- Plugins Section -->
36 <div class="wpfnl-ri-plugins-section">
37 <div class="wpfnl-ri-plugin-cards">
38
39 <!-- WooCommerce — required -->
40 <div class="wpfnl-ri-plugin-card" :class="pluginCardClass('woocommerce')">
41 <div class="wpfnl-ri-plugin-card-icon">
42 <img :src="wooLogo" alt="WooCommerce" />
43 </div>
44 <div class="wpfnl-ri-plugin-card-body">
45 <div class="wpfnl-ri-plugin-card-header">
46 <span class="wpfnl-ri-plugin-card-name">WooCommerce</span>
47 <span class="wpfnl-ri-plugin-card-required">Required</span>
48 </div>
49 <p class="wpfnl-ri-plugin-card-desc">Process payments and manage orders for your funnels.</p>
50 </div>
51 <div class="wpfnl-ri-plugin-card-status">
52 <span v-if="isPluginActive('woocommerce')" class="wpfnl-ri-status wpfnl-ri-status--active">
53 <svg width="10" height="10" viewBox="0 0 10 10" fill="none">><path d="M8.5 2.5L4 7L1.5 4.5" stroke="#059669" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
54 Active
55 </span>
56 <span v-else-if="isPluginInstalled('woocommerce')" class="wpfnl-ri-status wpfnl-ri-status--installed">
57 <svg width="8" height="8" viewBox="0 0 8 8">><circle cx="4" cy="4" r="4" fill="#F59E0B"/></svg>
58 Installed
59 </span>
60 <span v-else class="wpfnl-ri-status wpfnl-ri-status--missing">
61 <svg width="10" height="10" viewBox="0 0 10 10" fill="none">><path d="M5 1v4M5 7.5v.5" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round"/></svg>
62 Not Installed
63 </span>
64 </div>
65 </div>
66
67 <!-- Mail Mint — optional, toggleable. Hidden when already active. -->
68 <div
69 v-if="!isPluginActive('mail-mint')"
70 class="wpfnl-ri-plugin-card wpfnl-ri-plugin-card--optional"
71 :class="mailMintCardClass"
72 @click="toggleMailMint"
73 role="checkbox"
74 :aria-checked="mailMintSelected"
75 tabindex="0"
76 @keydown.space.prevent="toggleMailMint"
77 @keydown.enter.prevent="toggleMailMint"
78 >
79 <div class="wpfnl-ri-plugin-card-icon">
80 <img :src="mailMintLogo" alt="Mail Mint" />
81 </div>
82 <div class="wpfnl-ri-plugin-card-body">
83 <div class="wpfnl-ri-plugin-card-header">
84 <span class="wpfnl-ri-plugin-card-name">Mail Mint</span>
85 <span class="wpfnl-ri-plugin-card-optional">Optional</span>
86 </div>
87 <p class="wpfnl-ri-plugin-card-desc">Email marketing and abandoned cart recovery for your store.</p>
88 </div>
89 <div class="wpfnl-ri-plugin-card-status">
90 <span v-if="mailMintSelected && isPluginActive('mail-mint')" class="wpfnl-ri-status wpfnl-ri-status--active">
91 <svg width="10" height="10" viewBox="0 0 10 10" fill="none">><path d="M8.5 2.5L4 7L1.5 4.5" stroke="#059669" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
92 Active
93 </span>
94 <span v-else-if="mailMintSelected && isPluginInstalled('mail-mint')" class="wpfnl-ri-status wpfnl-ri-status--installed">
95 <svg width="8" height="8" viewBox="0 0 8 8">><circle cx="4" cy="4" r="4" fill="#F59E0B"/></svg>
96 Installed
97 </span>
98 <span v-else-if="mailMintSelected" class="wpfnl-ri-status wpfnl-ri-status--missing">
99 <svg width="10" height="10" viewBox="0 0 10 10" fill="none">><path d="M5 1v4M5 7.5v.5" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round"/></svg>
100 Not Installed
101 </span>
102 </div>
103 <div class="wpfnl-ri-plugin-card-toggle">
104 <div class="mint-toggle" :class="{ 'mint-toggle--on': mailMintSelected }">
105 <div class="mint-toggle__knob"></div>
106 </div>
107 </div>
108 </div>
109
110 </div>
111 </div>
112
113 </div>
114
115 </div>
116 </template>
117
118 <script>
119 export default {
120 name: 'Setup',
121 emits: [ 'nav', 'next-step' ],
122 data() {
123 return {
124 selectedBuilder: 'gutenberg',
125 builderStatuses: {},
126 pluginStatuses: {},
127 isProcessing: false,
128 installStatusText: '',
129 mailMintSelected: false,
130 builders: [
131 { id: 'gutenberg', name: 'Gutenberg', image: window?.setup_wizard_obj?.gb_builder_img || '', isBuiltIn: true },
132 { id: 'elementor', name: 'Elementor', image: window?.setup_wizard_obj?.elementor_img || '' },
133 { id: 'bricks', name: 'Bricks', image: window?.setup_wizard_obj?.bricks_img || '', isTheme: true },
134 { id: 'divi', name: 'Divi', image: window?.setup_wizard_obj?.divi_img || '', isTheme: true },
135 { id: 'oxygen', name: 'Oxygen', image: window?.setup_wizard_obj?.oxygen_img || '' },
136 { id: 'others', name: 'Others', image: window?.setup_wizard_obj?.others_builder_img || '', isBuiltIn: true },
137 ],
138 proBuilders: ['oxygen', 'bricks', 'divi'],
139 plugins: [
140 { id: 'woocommerce', name: 'WooCommerce', slug: 'woocommerce', basename: 'woocommerce/woocommerce.php', installedKey: 'is_woo_installed', activeKey: 'is_woo_active' },
141 { id: 'mail-mint', name: 'Mail Mint', slug: 'mail-mint', basename: 'mail-mint/mail-mint.php', installedKey: 'is_mrm_installed', activeKey: 'is_mrm_active' },
142 ],
143 }
144 },
145 computed: {
146 wooLogo() {
147 return window?.setup_wizard_obj?.wc_logo || '';
148 },
149 mailMintLogo() {
150 return window?.setup_wizard_obj?.mail_mint_logo || '';
151 },
152
153 wooStatus() {
154 const s = this.pluginStatuses['woocommerce'] || {};
155 if (s.active) return 'active';
156 if (s.installed) return 'installed';
157 return 'missing';
158 },
159
160 pluginsSectionTitle() {
161 if (this.wooStatus === 'active') {
162 return 'Get essential plugins installed with one-click';
163 }
164 if (this.wooStatus === 'installed') {
165 return 'WooCommerce is installed but not active';
166 }
167 return 'WooCommerce is required to use WPFunnels';
168 },
169
170 pluginsSectionSubtitle() {
171 const mintAlreadyActive = this.isPluginActive('mail-mint');
172 const mintPart = !mintAlreadyActive
173 ? ' You can also add Mail Mint for email marketing and abandoned cart recovery.'
174 : '';
175 if (this.wooStatus === 'active') {
176 return mintAlreadyActive
177 ? 'WooCommerce and Mail Mint are already active. You\'re all set!'
178 : 'WooCommerce is already active. Optionally install Mail Mint for email marketing and abandoned cart recovery.';
179 }
180 if (this.wooStatus === 'installed') {
181 return 'We will activate WooCommerce to process payments.' + mintPart;
182 }
183 return 'WPFunnels uses WooCommerce to process payments. We will install and activate it for you.' + mintPart;
184 },
185
186 installButtonLabel() {
187 if (this.isProcessing) {
188 return this.installStatusText || 'Installing and Activating...';
189 }
190 const wooActive = this.isPluginActive('woocommerce');
191 const wooInstalled = this.isPluginInstalled('woocommerce');
192 // If Mail Mint is already active, treat it as not needing action
193 const mintAlreadyActive = this.isPluginActive('mail-mint');
194 const mintActive = mintAlreadyActive || !this.mailMintSelected || this.isPluginActive('mail-mint');
195 const mintInstalled = mintAlreadyActive || !this.mailMintSelected || this.isPluginInstalled('mail-mint');
196
197 if (wooActive && mintActive) return 'Continue';
198
199 const needsInstall = (!wooInstalled && !wooActive)
200 || (!mintAlreadyActive && this.mailMintSelected && !mintInstalled && !mintActive);
201
202 return needsInstall ? 'Install and Activate' : 'Activate';
203 },
204
205 // Mail Mint card class lives in computed so Vue tracks mailMintSelected reactively
206 mailMintCardClass() {
207 if (!this.mailMintSelected) return 'wpfnl-ri-plugin-card--unchecked';
208 if (this.isPluginActive('mail-mint')) return 'wpfnl-ri-plugin-card--active';
209 if (this.isPluginInstalled('mail-mint')) return 'wpfnl-ri-plugin-card--installed';
210 return '';
211 },
212 },
213 watch: {
214 // The footer label tracks what the button will actually do, which
215 // depends on the builder and Mail Mint choices plus install progress.
216 installButtonLabel: 'publishNav',
217 isProcessing: 'publishNav',
218 },
219 mounted() {
220 this.detectBuilder();
221 this.checkPluginStatuses();
222 this.publishNav();
223 },
224 methods: {
225 publishNav() {
226 this.$emit( 'nav', {
227 label: this.installButtonLabel,
228 canProceed: ! this.isProcessing,
229 busy: this.isProcessing,
230 showBack: true,
231 showNext: true,
232 } );
233 },
234
235 /**
236 * Called by the wizard footer's Next button.
237 */
238 submit() {
239 this.handleContinue();
240 },
241
242 detectBuilder() {
243 const getPlugins = window?.setup_wizard_obj?.getPlugins || [];
244 const oxygenData = getPlugins.find(p => p.name === 'oxygen');
245 const bricksData = getPlugins.find(p => p.name === 'bricks');
246 const diviData = getPlugins.find(p => p.name === 'divi');
247
248 const checks = [
249 { id: 'elementor', active: window?.setup_wizard_obj?.is_elementor_active === 'yes', installed: window?.setup_wizard_obj?.is_elementor_installed === 'yes' },
250 { id: 'bricks', active: bricksData?.status === 'activated', installed: bricksData?.status === 'activated' || bricksData?.status === 'installed' },
251 { id: 'divi', active: diviData?.status === 'activated', installed: diviData?.status === 'activated' || diviData?.status === 'installed' },
252 { id: 'oxygen', active: oxygenData?.status === 'activated', installed: oxygenData?.status === 'activated' || oxygenData?.status === 'installed' },
253 ];
254
255 const statuses = { gutenberg: { installed: true, active: true }, others: { installed: true, active: true } };
256 checks.forEach(b => { statuses[b.id] = { installed: b.installed, active: b.active }; });
257 this.builderStatuses = statuses;
258
259 // 1. Use previously saved builder setting if it exists
260 const savedBuilder = window?.setup_wizard_obj?.defaultSettings?.builder;
261 if (savedBuilder && savedBuilder !== 'gutenberg') {
262 // Validate the saved builder is actually installed/active before trusting it
263 const savedStatus = statuses[savedBuilder];
264 if (savedStatus?.installed || savedStatus?.active) {
265 this.selectedBuilder = savedBuilder;
266 return;
267 }
268 }
269
270 // 2. If saved is gutenberg (or no valid saved setting), default to gutenberg
271 this.selectedBuilder = 'gutenberg';
272 },
273
274 checkPluginStatuses() {
275 const statuses = {};
276 this.plugins.forEach(p => {
277 statuses[p.id] = {
278 installed: p.installedKey ? window?.setup_wizard_obj?.[p.installedKey] === 'yes' : false,
279 active: p.activeKey ? window?.setup_wizard_obj?.[p.activeKey] === 'yes' : false,
280 };
281 });
282 this.pluginStatuses = statuses;
283 },
284
285 isProBuilder(id) { return this.proBuilders.includes(id); },
286 isBuilderInstalled(id) { return !!this.builderStatuses[id]?.installed; },
287 isPluginActive(id) { return !!this.pluginStatuses[id]?.active; },
288 isPluginInstalled(id) { return !!this.pluginStatuses[id]?.installed; },
289
290 pluginCardClass(id) {
291 if (this.isPluginActive(id)) return 'wpfnl-ri-plugin-card--active';
292 if (this.isPluginInstalled(id)) return 'wpfnl-ri-plugin-card--installed';
293 return '';
294 },
295
296 toggleMailMint() {
297 this.mailMintSelected = !this.mailMintSelected;
298 },
299
300 selectBuilder(id) {
301 if (this.isProBuilder(id) && !this.isBuilderInstalled(id)) return;
302 this.selectedBuilder = id;
303 },
304
305 async installPlugin(slug) {
306 return new Promise(resolve => {
307 wp.updates.ajax('install-plugin', { slug, success: () => resolve(true), error: () => resolve(false) });
308 });
309 },
310
311 async activatePlugin(basename) {
312 return new Promise(resolve => {
313 const ajaxUrl = window.ajaxurl || window.location.origin + '/wp-admin/admin-ajax.php';
314 const action = basename === 'mail-mint/mail-mint.php' ? 'wpfnl_activate_mail_mint' : 'wpfnl_activate_plugin';
315 const data = { action };
316 if (action === 'wpfnl_activate_plugin') data.plugin = basename;
317 jQuery.ajax({ url: ajaxUrl, type: 'POST', data, success: r => resolve(!!r.success), error: () => resolve(false) });
318 });
319 },
320
321 async installAndActivatePlugin(slug, basename, isInstalled = false) {
322 try {
323 if (!isInstalled) {
324 this.installStatusText = 'Installing and Activating...';
325 const ok = await this.installPlugin(slug);
326 if (!ok) return false;
327 await new Promise(r => setTimeout(r, 1500));
328 }
329 this.installStatusText = 'Installing and Activating...';
330 return await this.activatePlugin(basename);
331 } catch { return false; }
332 },
333
334 async installBuilder() {
335 if (this.selectedBuilder === 'gutenberg' || this.selectedBuilder === 'others') return true;
336 const map = {
337 elementor: { slug: 'elementor', basename: 'elementor/elementor.php', installedKey: 'is_elementor_installed', activeKey: 'is_elementor_active' },
338 divi: { slug: 'divi-builder', basename: 'divi-builder/divi-builder.php', installedKey: null, activeKey: null },
339 oxygen: { slug: 'oxygen', basename: 'oxygen/functions.php', installedKey: null, activeKey: null },
340 bricks: { slug: 'bricks', basename: 'bricks', isTheme: true },
341 };
342 const b = map[this.selectedBuilder];
343 if (!b || b.isTheme) return true;
344 const isInstalled = b.installedKey ? window?.setup_wizard_obj?.[b.installedKey] === 'yes' : false;
345 const isActive = b.activeKey ? window?.setup_wizard_obj?.[b.activeKey] === 'yes' : false;
346 if (isInstalled && isActive) return true;
347 return this.installAndActivatePlugin(b.slug, b.basename, isInstalled);
348 },
349
350 async installPlugins() {
351 for (const plugin of this.plugins) {
352 if (plugin.id === 'mail-mint' && (!this.mailMintSelected || this.isPluginActive('mail-mint'))) continue;
353 const isInstalled = plugin.installedKey ? window?.setup_wizard_obj?.[plugin.installedKey] === 'yes' : false;
354 const isActive = plugin.activeKey ? window?.setup_wizard_obj?.[plugin.activeKey] === 'yes' : false;
355 if (isInstalled && isActive) continue;
356 await this.installAndActivatePlugin(plugin.slug, plugin.basename, isInstalled);
357 }
358 },
359
360 async handleContinue() {
361 this.isProcessing = true;
362 this.installStatusText = '';
363 try {
364 await this.installBuilder();
365 await this.installPlugins();
366 const builder = this.selectedBuilder === 'others' ? 'gutenberg' : this.selectedBuilder;
367 this.$emit('next-step', { builder });
368 } catch {
369 alert('An error occurred during setup. Please try again.');
370 } finally {
371 this.isProcessing = false;
372 this.installStatusText = '';
373 }
374 },
375
376 }
377 }
378 </script>
379
380 <style scoped>
381 /* Toggle switch — scoped to this component, no specificity conflicts */
382 .mint-toggle {
383 position: relative;
384 display: inline-block;
385 width: 36px;
386 height: 20px;
387 border-radius: 100px;
388 background: #D1D5DB;
389 transition: background 0.25s ease;
390 cursor: pointer;
391 flex-shrink: 0;
392 }
393
394 .mint-toggle--on {
395 background: #6E42D3;
396 }
397
398 .mint-toggle__knob {
399 position: absolute;
400 top: 2px;
401 left: 2px;
402 width: 16px;
403 height: 16px;
404 border-radius: 50%;
405 background: #FFF;
406 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
407 transition: transform 0.25s ease;
408 transform: translateX(0);
409 }
410
411 .mint-toggle--on .mint-toggle__knob {
412 transform: translateX(16px);
413 }
414 </style>
415