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.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 2.8.18 All 259 releases
wpfunnels / admin / modules / setup-wizard / components / Content / BuildFunnel.vue

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

1,956 lines 57.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="wpfnl-mm-build-funnel">
3 <!-- Header -->
4 <div class="wpfnl-mm-build-funnel-header">
5 <h2 class="wpfnl-mm-build-funnel-title">Create Your Revenue-Boosting Funnel</h2>
6 <p class="wpfnl-mm-build-funnel-subtitle">
7 Choose products and add upsells or order bumps to increase your average order value.
8 </p>
9 </div>
10
11 <!-- Main Container -->
12 <div class="wpfnl-mm-build-funnel-container">
13 <!-- Left Panel - Product Selection -->
14 <div class="wpfnl-mm-build-funnel-form" v-if="hasCheckout || hasUpsell">
15 <div class="wpfnl-mm-build-funnel-fields">
16 <!-- Main Product -->
17 <div class="wpfnl-mm-build-funnel-field" v-if="hasCheckout">
18 <label class="wpfnl-mm-build-funnel-label">Main Product</label>
19 <div class="wpfnl-mm-build-funnel-dropdown" :class="{ 'open': showMainProductDropdown, 'has-value': selectedMainProduct }">
20 <div class="wpfnl-mm-build-funnel-dropdown-trigger" @click="toggleMainProductDropdown">
21 <span v-if="!selectedMainProduct" class="placeholder">Choose product</span>
22 <span v-else class="selected-value">
23 {{ selectedMainProduct.name }} <span v-html="formatPrice(selectedMainProduct.priceHtml)"></span>
24 </span>
25 <svg class="dropdown-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
26 <path d="M4 6L8 10L12 6" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
27 </svg>
28 </div>
29 <div class="wpfnl-mm-build-funnel-dropdown-menu" v-if="showMainProductDropdown" @click.stop>
30 <div class="wpfnl-mm-build-funnel-search">
31 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
32 <path d="M7.33333 12.6667C10.2789 12.6667 12.6667 10.2789 12.6667 7.33333C12.6667 4.38781 10.2789 2 7.33333 2C4.38781 2 2 4.38781 2 7.33333C2 10.2789 4.38781 12.6667 7.33333 12.6667Z" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
33 <path d="M14 14L11.1 11.1" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
34 </svg>
35 <input
36 type="text"
37 v-model="mainProductSearch"
38 @input="searchMainProducts"
39 placeholder="Search products..."
40 ref="mainProductSearchInput"
41 />
42 </div>
43 <div class="wpfnl-mm-build-funnel-dropdown-list">
44 <div v-if="loadingMainProducts" class="wpfnl-mm-build-funnel-dropdown-loading">
45 <svg class="wpfnl-spinner" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">>
46 <circle cx="10" cy="10" r="8" stroke="#6E42D3" stroke-width="2" stroke-dasharray="40 15" stroke-linecap="round"/>
47 </svg>
48 <span>Searching...</span>
49 </div>
50 <template v-else>
51 <div v-if="mainProducts.length === 0" class="wpfnl-mm-build-funnel-dropdown-empty">
52 <p>{{ mainProductSearch ? 'No products found' : 'No products available' }}</p>
53 <button class="wpfnl-mm-create-product-btn" @click="showCreateProductModal">
54 <svg width="12" height="12" fill="none" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">>
55 <path stroke="#6E42D3" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M5.918.9v10.013M.9 5.907h10.032"/>
56 </svg>
57 Create New Product
58 </button>
59 </div>
60 <div
61 v-for="product in mainProducts"
62 :key="product.id"
63 class="wpfnl-mm-build-funnel-dropdown-item"
64 :class="{ 'selected': selectedMainProduct && selectedMainProduct.id === product.id }"
65 @click="selectMainProduct(product)"
66 >
67 <span class="product-name">{{ product.name }}</span>
68 <span class="product-price" v-html="formatPrice(product.priceHtml)"></span>
69 <svg v-if="selectedMainProduct && selectedMainProduct.id === product.id" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
70 <path d="M13.3334 4L6.00002 11.3333L2.66669 8" stroke="#6E42D3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
71 </svg>
72 </div>
73 </template>
74 </div>
75 </div>
76 </div>
77 <p class="wpfnl-mm-build-funnel-hint">Select a product that’s already a top seller to maximize conversions.</p>
78 </div>
79
80 <!-- Order Bump -->
81 <div class="wpfnl-mm-build-funnel-field" v-if="hasCheckout">
82 <label class="wpfnl-mm-build-funnel-label">Order Bump</label>
83 <div class="wpfnl-mm-build-funnel-dropdown" :class="{ 'open': showOrderBumpDropdown, 'has-value': selectedOrderBump }">
84 <div class="wpfnl-mm-build-funnel-dropdown-trigger" @click="toggleOrderBumpDropdown">
85 <span v-if="!selectedOrderBump" class="placeholder">Choose product</span>
86 <span v-else class="selected-value">
87 {{ selectedOrderBump.name }} <span v-html="formatPrice(selectedOrderBump.priceHtml)"></span>
88 </span>
89 <svg class="dropdown-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
90 <path d="M4 6L8 10L12 6" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
91 </svg>
92 </div>
93 <div class="wpfnl-mm-build-funnel-dropdown-menu" v-if="showOrderBumpDropdown" @click.stop>
94 <div class="wpfnl-mm-build-funnel-search">
95 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
96 <path d="M7.33333 12.6667C10.2789 12.6667 12.6667 10.2789 12.6667 7.33333C12.6667 4.38781 10.2789 2 7.33333 2C4.38781 2 2 4.38781 2 7.33333C2 10.2789 4.38781 12.6667 7.33333 12.6667Z" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
97 <path d="M14 14L11.1 11.1" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
98 </svg>
99 <input
100 type="text"
101 v-model="orderBumpSearch"
102 @input="searchOrderBumpProducts"
103 placeholder="Search products..."
104 ref="orderBumpSearchInput"
105 />
106 </div>
107 <div class="wpfnl-mm-build-funnel-dropdown-list">
108 <div v-if="loadingOrderBumpProducts" class="wpfnl-mm-build-funnel-dropdown-loading">
109 <svg class="wpfnl-spinner" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">>
110 <circle cx="10" cy="10" r="8" stroke="#6E42D3" stroke-width="2" stroke-dasharray="40 15" stroke-linecap="round"/>
111 </svg>
112 <span>Searching...</span>
113 </div>
114 <template v-else>
115 <div v-if="orderBumpProducts.length === 0" class="wpfnl-mm-build-funnel-dropdown-empty">
116 <p>{{ orderBumpSearch ? 'No products found' : 'No products available' }}</p>
117 <button class="wpfnl-mm-create-product-btn" @click="showCreateProductModal">
118 <svg width="12" height="12" fill="none" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">>
119 <path stroke="#6E42D3" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M5.918.9v10.013M.9 5.907h10.032"/>
120 </svg>
121 Create New Product
122 </button>
123 </div>
124 <div
125 v-for="product in orderBumpProducts"
126 :key="product.id"
127 class="wpfnl-mm-build-funnel-dropdown-item"
128 :class="{ 'selected': selectedOrderBump && selectedOrderBump.id === product.id }"
129 @click="selectOrderBump(product)"
130 >
131 <span class="product-name">{{ product.name }}</span>
132 <span class="product-price" v-html="formatPrice(product.priceHtml)"></span>
133 <svg v-if="selectedOrderBump && selectedOrderBump.id === product.id" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
134 <path d="M13.3334 4L6.00002 11.3333L2.66669 8" stroke="#6E42D3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
135 </svg>
136 </div>
137 </template>
138 </div>
139 </div>
140 </div>
141 <p class="wpfnl-mm-build-funnel-hint">Add a complementary product with one click at checkout.</p>
142 </div>
143
144 <!-- Upsell Product -->
145 <div class="wpfnl-mm-build-funnel-field" v-if="hasUpsell">
146 <label class="wpfnl-mm-build-funnel-label">Upsell Product <span class="optional">(optional)</span></label>
147 <div class="wpfnl-mm-build-funnel-dropdown" :class="{ 'open': showUpsellDropdown, 'has-value': selectedUpsell }">
148 <div class="wpfnl-mm-build-funnel-dropdown-trigger" @click="toggleUpsellDropdown">
149 <span v-if="!selectedUpsell" class="placeholder">Choose product</span>
150 <span v-else class="selected-value">
151 {{ selectedUpsell.name }} <span v-html="formatPrice(selectedUpsell.priceHtml)"></span>
152 </span>
153 <svg class="dropdown-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
154 <path d="M4 6L8 10L12 6" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
155 </svg>
156 </div>
157 <div class="wpfnl-mm-build-funnel-dropdown-menu" v-if="showUpsellDropdown" @click.stop>
158 <div class="wpfnl-mm-build-funnel-search">
159 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
160 <path d="M7.33333 12.6667C10.2789 12.6667 12.6667 10.2789 12.6667 7.33333C12.6667 4.38781 10.2789 2 7.33333 2C4.38781 2 2 4.38781 2 7.33333C2 10.2789 4.38781 12.6667 7.33333 12.6667Z" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
161 <path d="M14 14L11.1 11.1" stroke="#7A8C9A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
162 </svg>
163 <input
164 type="text"
165 v-model="upsellSearch"
166 @input="searchUpsellProducts"
167 placeholder="Search products..."
168 ref="upsellSearchInput"
169 />
170 </div>
171 <div class="wpfnl-mm-build-funnel-dropdown-list">
172 <div v-if="loadingUpsellProducts" class="wpfnl-mm-build-funnel-dropdown-loading">
173 <svg class="wpfnl-spinner" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">>
174 <circle cx="10" cy="10" r="8" stroke="#6E42D3" stroke-width="2" stroke-dasharray="40 15" stroke-linecap="round"/>
175 </svg>
176 <span>Searching...</span>
177 </div>
178 <template v-else>
179 <div v-if="upsellProducts.length === 0" class="wpfnl-mm-build-funnel-dropdown-empty">
180 <p>{{ upsellSearch ? 'No products found' : 'No products available' }}</p>
181 <button class="wpfnl-mm-create-product-btn" @click="showCreateProductModal">
182 <svg width="12" height="12" fill="none" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">>
183 <path stroke="#6E42D3" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M5.918.9v10.013M.9 5.907h10.032"/>
184 </svg>
185 Create New Product
186 </button>
187 </div>
188 <div
189 v-for="product in upsellProducts"
190 :key="product.id"
191 class="wpfnl-mm-build-funnel-dropdown-item"
192 :class="{ 'selected': selectedUpsell && selectedUpsell.id === product.id }"
193 @click="selectUpsell(product)"
194 >
195 <span class="product-name">{{ product.name }}</span>
196 <span class="product-price" v-html="formatPrice(product.priceHtml)"></span>
197 <svg v-if="selectedUpsell && selectedUpsell.id === product.id" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
198 <path d="M13.3334 4L6.00002 11.3333L2.66669 8" stroke="#6E42D3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
199 </svg>
200 </div>
201 </template>
202 </div>
203 </div>
204 </div>
205 <p class="wpfnl-mm-build-funnel-hint">Offer an extra product after checkout to increase order value.</p>
206 </div>
207 </div>
208
209 <!-- Potential Revenue -->
210 <div class="wpfnl-mm-build-funnel-revenue" v-if="hasCheckout && selectedMainProduct">
211 <div class="wpfnl-mm-revenue-header">
212 <p class="wpfnl-mm-build-funnel-revenue-label">Potential Revenue per Customer</p>
213 </div>
214
215 <div class="wpfnl-mm-revenue-breakdown">
216 <!-- Main Product -->
217 <div class="wpfnl-mm-revenue-item">
218 <div class="wpfnl-mm-revenue-item-content">
219 <div class="wpfnl-mm-revenue-item-label">Main Product</div>
220 <div class="wpfnl-mm-revenue-item-name">{{ selectedMainProduct.text }}</div>
221 </div>
222 <div class="wpfnl-mm-revenue-item-price">{{ formatSimplePrice(selectedMainProduct.numericPrice) }}</div>
223 </div>
224
225 <!-- Order Bump -->
226 <div class="wpfnl-mm-revenue-item wpfnl-mm-revenue-item-bonus" v-if="selectedOrderBump">
227 <div class="wpfnl-mm-revenue-item-content">
228 <div class="wpfnl-mm-revenue-item-label">Order Bump <span class="wpfnl-mm-revenue-badge">+Bonus</span></div>
229 <div class="wpfnl-mm-revenue-item-name">{{ selectedOrderBump.text }}</div>
230 </div>
231 <div class="wpfnl-mm-revenue-item-price wpfnl-mm-revenue-bonus">+{{ formatSimplePrice(selectedOrderBump.numericPrice) }}</div>
232 </div>
233
234 <!-- Upsell -->
235 <div class="wpfnl-mm-revenue-item wpfnl-mm-revenue-item-bonus" v-if="selectedUpsell">
236 <div class="wpfnl-mm-revenue-item-content">
237 <div class="wpfnl-mm-revenue-item-label">Upsell Product <span class="wpfnl-mm-revenue-badge">+Bonus</span></div>
238 <div class="wpfnl-mm-revenue-item-name">{{ selectedUpsell.text }}</div>
239 </div>
240 <div class="wpfnl-mm-revenue-item-price wpfnl-mm-revenue-bonus">+{{ formatSimplePrice(selectedUpsell.numericPrice) }}</div>
241 </div>
242
243 <!-- Separator -->
244 <div class="wpfnl-mm-revenue-separator"></div>
245
246 <!-- Total -->
247 <div class="wpfnl-mm-revenue-total">
248 <div class="wpfnl-mm-revenue-total-content">
249 <div class="wpfnl-mm-revenue-total-label">Total Potential Revenue</div>
250 <div class="wpfnl-mm-revenue-total-value">{{ formatSimplePrice(potentialRevenue) }}</div>
251 </div>
252 <div class="wpfnl-mm-revenue-boost-inline" v-if="selectedOrderBump || selectedUpsell">
253 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
254 <path d="M8 1.33331V14.6666M8 1.33331L3.33334 5.99998M8 1.33331L12.6667 5.99998" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
255 </svg>
256 <span>+{{ calculateRevenueBoost }}%</span>
257 </div>
258 </div>
259 </div>
260 </div>
261 </div>
262
263 <!-- Right Panel - Preview -->
264 <div class="wpfnl-mm-build-funnel-preview">
265 <!-- Sub-step Tabs -->
266 <div class="wpfnl-mm-build-funnel-tabs">
267 <template v-for="(step, index) in availableSteps" :key="step">
268 <div
269 class="wpfnl-mm-build-funnel-tab"
270 :class="{ 'active': activeSubStep === step }"
271 @click="setActiveSubStep(step)"
272 >
273 <div class="wpfnl-mm-build-funnel-tab-icon">
274 <svg v-if="step === 'landing'" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">>
275 <rect x="3" y="3" width="14" height="14" rx="2" stroke="currentColor" stroke-width="1.5"/>
276 <path d="M3 7H17" stroke="currentColor" stroke-width="1.5"/>
277 <path d="M7 7V17" stroke="currentColor" stroke-width="1.5"/>
278 </svg>
279 <svg v-else-if="step === 'checkout'" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">>
280 <path d="M5 5H17L15.5 13H6.5L5 5Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
281 <circle cx="7" cy="16" r="1.5" stroke="currentColor" stroke-width="1.5"/>
282 <circle cx="15" cy="16" r="1.5" stroke="currentColor" stroke-width="1.5"/>
283 <path d="M5 5L4 2H2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
284 </svg>
285 <svg v-else-if="step === 'upsell'" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">>
286 <path d="M10 4V16M10 4L6 8M10 4L14 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
287 </svg>
288 <svg v-else-if="step === 'thankyou'" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">>
289 <path d="M10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18Z" stroke="currentColor" stroke-width="1.5"/>
290 <path d="M7 10L9 12L13 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
291 </svg>
292 </div>
293 <span class="wpfnl-mm-build-funnel-tab-label">
294 {{ step === 'landing' ? 'Landing' : step === 'checkout' ? 'Checkout' : step === 'upsell' ? 'Upsell' : 'Thank You' }}
295 </span>
296 </div>
297 <div v-if="index < availableSteps.length - 1" class="wpfnl-mm-build-funnel-tab-line"></div>
298 </template>
299 </div>
300
301 <!-- Preview Content -->
302 <div class="wpfnl-mm-build-funnel-preview-content">
303 <!-- Landing Preview -->
304 <div v-if="activeSubStep === 'landing'" class="wpfnl-mm-build-funnel-preview-image">
305 <img v-if="templateImages.landing" :src="templateImages.landing" alt="Landing Page Preview" />
306 <div v-else class="wpfnl-mm-build-funnel-preview-placeholder">
307 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">>
308 <rect x="6" y="6" width="36" height="36" rx="4" stroke="#C4C4C4" stroke-width="2"/>
309 <path d="M6 14H42" stroke="#C4C4C4" stroke-width="2"/>
310 <path d="M14 14V42" stroke="#C4C4C4" stroke-width="2"/>
311 </svg>
312 <p>Landing page preview</p>
313 </div>
314 </div>
315
316 <!-- Checkout Preview -->
317 <div v-if="activeSubStep === 'checkout'" class="wpfnl-mm-build-funnel-preview-image">
318 <img v-if="templateImages.checkout" :src="templateImages.checkout" alt="Checkout Page Preview" />
319 <div v-else class="wpfnl-mm-build-funnel-preview-placeholder">
320 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">>
321 <path d="M10 10H42L38 26H14L10 10Z" stroke="#C4C4C4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
322 <circle cx="16" cy="36" r="3" stroke="#C4C4C4" stroke-width="2"/>
323 <circle cx="36" cy="36" r="3" stroke="#C4C4C4" stroke-width="2"/>
324 </svg>
325 <p>Checkout page preview</p>
326 </div>
327 </div>
328
329 <!-- Upsell Preview -->
330 <div v-if="activeSubStep === 'upsell'" class="wpfnl-mm-build-funnel-preview-image">
331 <template v-if="selectedUpsell">
332 <img v-if="templateImages.upsell" :src="templateImages.upsell" alt="Upsell Page Preview" />
333 <div v-else class="wpfnl-mm-build-funnel-preview-placeholder">
334 <p>Upsell page preview</p>
335 </div>
336 </template>
337 <div v-else class="wpfnl-mm-build-funnel-preview-empty">
338 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">>
339 <path d="M24 8V40M24 8L16 16M24 8L32 16" stroke="#C4C4C4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
340 </svg>
341 <h4>Upsell</h4>
342 <p>Select a upsell product to preview your funnel</p>
343 </div>
344 </div>
345
346 <!-- Thank You Preview -->
347 <div v-if="activeSubStep === 'thankyou'" class="wpfnl-mm-build-funnel-preview-image">
348 <img v-if="templateImages.thankyou" :src="templateImages.thankyou" alt="Thank You Page Preview" />
349 <div v-else class="wpfnl-mm-build-funnel-preview-placeholder">
350 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">>
351 <circle cx="24" cy="24" r="18" stroke="#C4C4C4" stroke-width="2"/>
352 <path d="M16 24L22 30L32 18" stroke="#C4C4C4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
353 </svg>
354 <p>Thank you page preview</p>
355 </div>
356 </div>
357 </div>
358 </div>
359 </div>
360
361 <!-- Buttons -->
362 <div class="wpfnl-mm-build-funnel-buttons wpfnl-mm-buttons-container">
363 <button class="wpfnl-mm-btn wpfnl-mm-btn-secondary" @click="goBack" :disabled="isProcessing">
364 Back
365 </button>
366 <div class="wpfnl-mm-btn-group">
367 <button
368 class="wpfnl-mm-btn wpfnl-mm-btn-primary"
369 @click="handleContinue"
370 :disabled="isContinueDisabled"
371 >
372 <span v-if="!isProcessing && !isGeneratingFunnel">Continue</span>
373 <span v-else-if="isGeneratingFunnel">{{ funnelStatusMessage || 'Creating funnel...' }}</span>
374 <span v-else>Processing...</span>
375 <svg v-if="!isProcessing && !isGeneratingFunnel" width="17" height="12" viewBox="0 0 17 12" fill="none" xmlns="http://www.w3.org/2000/svg">>
376 <path d="M1 6H16M16 6L11 1M16 6L11 11" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
377 </svg>
378 <svg v-else class="wpfnl-spinner" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
379 <circle cx="8" cy="8" r="6" stroke="white" stroke-width="2" stroke-dasharray="30 10" stroke-linecap="round"/>
380 </svg>
381 </button>
382 </div>
383 </div>
384
385 <!-- Create Product Modal -->
386 <div class="wpfnl-modal-overlay" v-if="isCreateProduct">
387 <div class="wpfnl-modal-wrapper">
388 <div class="wpfnl-modal-close">
389 <span class="wpfnl-modal-close-btn" @click="removeCreateProductModal">
390 <svg width="12" height="13" fill="none" viewBox="0 0 12 13" xmlns="http://www.w3.org/2000/svg">>
391 <path stroke="#7A8B9A" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 1.969l-10 9.99m0-9.99l10 9.99"></path>
392 </svg>
393 </span>
394 </div>
395
396 <span class="wpfnl-loader" v-if="isShowModalLoader"></span>
397 <div id="wpfnl-create-product" :style="{ display: !isShowModalLoader ? 'block' : 'none' }">
398 </div>
399 </div>
400 </div>
401
402 <!-- Help Button -->
403 <div class="wpfnl-mm-help-btn">
404 <button class="wpfnl-mm-help-btn-circle" @click="showHelp">
405 <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">>
406 <path d="M6.06 5.99999C6.2167 5.55443 6.52627 5.17872 6.93329 4.93941C7.34031 4.7001 7.81926 4.61252 8.28478 4.69247C8.75031 4.77242 9.17254 5.01433 9.47671 5.37567C9.78087 5.73702 9.94736 6.19433 9.94667 6.66666C9.94667 7.99999 7.94667 8.66666 7.94667 8.66666M8 11.3333H8.00667M14.6667 8C14.6667 11.6819 11.6819 14.6667 8 14.6667C4.31811 14.6667 1.33334 11.6819 1.33334 8C1.33334 4.31809 4.31811 1.33333 8 1.33333C11.6819 1.33333 14.6667 4.31809 14.6667 8Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
407 </svg>
408 </button>
409 </div>
410 </div>
411 </template>
412
413 <script>
414 import { __ } from '@wordpress/i18n'
415 import apiFetch from '@wordpress/api-fetch'
416
417 export default {
418 name: 'BuildFunnel',
419 props: {
420 template: {
421 type: Object,
422 default: null
423 },
424 goal: {
425 type: String,
426 default: 'sales'
427 },
428 builder: {
429 type: String,
430 default: 'gutenberg'
431 }
432 },
433 data() {
434 return {
435 activeSubStep: 'landing',
436 isProcessing: false,
437 isCreateProduct: false,
438 isShowModalLoader: false,
439
440 // Dropdown states
441 showMainProductDropdown: false,
442 showOrderBumpDropdown: false,
443 showUpsellDropdown: false,
444
445 // Search queries
446 mainProductSearch: '',
447 orderBumpSearch: '',
448 upsellSearch: '',
449
450 // Loading states
451 loadingMainProducts: false,
452 loadingOrderBumpProducts: false,
453 loadingUpsellProducts: false,
454
455 // Search results
456 mainProducts: [],
457 orderBumpProducts: [],
458 upsellProducts: [],
459
460 // Selected products
461 selectedMainProduct: null,
462 selectedOrderBump: null,
463 selectedUpsell: null,
464
465 // Debounce timers
466 mainProductDebounce: null,
467 orderBumpDebounce: null,
468 upsellDebounce: null,
469
470 // Template images
471 templateImages: {
472 landing: '',
473 checkout: '',
474 upsell: '',
475 thankyou: ''
476 },
477
478 // Funnel creation state
479 isGeneratingFunnel: false,
480 funnelStatusMessage: '',
481 createdFunnelId: null,
482 createdFirstStepLink: ''
483 }
484 },
485 computed: {
486 potentialRevenue() {
487 let total = 0
488 if (this.selectedMainProduct) {
489 total += parseFloat(this.selectedMainProduct.numericPrice) || 0
490 }
491 if (this.selectedOrderBump) {
492 total += parseFloat(this.selectedOrderBump.numericPrice) || 0
493 }
494 if (this.selectedUpsell) {
495 total += parseFloat(this.selectedUpsell.numericPrice) || 0
496 }
497 return total
498 },
499 calculateRevenueBoost() {
500 if (!this.selectedMainProduct) return 0
501
502 const mainPrice = this.selectedMainProduct.numericPrice || 0
503 const bumpPrice = this.selectedOrderBump ? (this.selectedOrderBump.numericPrice || 0) : 0
504 const upsellPrice = this.selectedUpsell ? (this.selectedUpsell.numericPrice || 0) : 0
505
506 if (mainPrice === 0) return 0
507
508 const additionalRevenue = bumpPrice + upsellPrice
509 const boost = (additionalRevenue / mainPrice) * 100
510 return Math.round(boost)
511 },
512 hasLanding() {
513 if (!this.template || !this.template.steps) return false
514 return this.template.steps.some(step => step.step_type === 'landing')
515 },
516 hasCheckout() {
517 if (!this.template || !this.template.steps) return false
518 return this.template.steps.some(step => step.step_type === 'checkout')
519 },
520 hasUpsell() {
521 if (!this.template || !this.template.steps) return false
522 return this.template.steps.some(step => step.step_type === 'upsell')
523 },
524 hasThankyou() {
525 if (!this.template || !this.template.steps) return false
526 return this.template.steps.some(step => step.step_type === 'thankyou')
527 },
528 availableSteps() {
529 const steps = []
530 if (this.hasLanding) steps.push('landing')
531 if (this.hasCheckout) steps.push('checkout')
532 if (this.hasUpsell) steps.push('upsell')
533 if (this.hasThankyou) steps.push('thankyou')
534 return steps
535 },
536 isContinueDisabled() {
537 // For order-value goal, both main product and order bump must be selected
538 if (this.goal === 'order-value') {
539 return !this.selectedMainProduct || !this.selectedOrderBump || this.isProcessing || this.isGeneratingFunnel
540 }
541 return this.isProcessing || this.isGeneratingFunnel
542 }
543 },
544 mounted() {
545 this.loadTemplateImages()
546 document.addEventListener('click', this.closeDropdowns)
547 // Set active tab to first available step
548 if (this.availableSteps.length > 0) {
549 this.activeSubStep = this.availableSteps[0]
550 }
551 },
552 beforeDestroy() {
553 document.removeEventListener('click', this.closeDropdowns)
554 // Clear debounce timers
555 if (this.mainProductDebounce) clearTimeout(this.mainProductDebounce)
556 if (this.orderBumpDebounce) clearTimeout(this.orderBumpDebounce)
557 if (this.upsellDebounce) clearTimeout(this.upsellDebounce)
558 },
559 methods: {
560 stripHtml(html) {
561 if (!html) return ''
562 const tmp = document.createElement('DIV')
563 tmp.innerHTML = html
564 return tmp.textContent || tmp.innerText || ''
565 },
566
567 extractNumericPrice(priceString) {
568 if (!priceString) return 0
569
570 // If it's already a number, return it
571 if (typeof priceString === 'number') {
572 return priceString
573 }
574
575 // Convert to string if not already
576 const priceStr = String(priceString)
577
578 // Strip HTML tags first
579 const cleanPrice = this.stripHtml(priceStr)
580
581 // Remove all non-numeric characters except dots and commas
582 // Then handle different decimal separator formats
583 let numericStr = cleanPrice.replace(/[^\d.,]/g, '')
584
585 // Handle European format (1.234,56) vs US format (1,234.56)
586 // If there's both comma and dot, determine which is the decimal separator
587 if (numericStr.includes(',') && numericStr.includes('.')) {
588 // If comma comes after dot, comma is decimal separator (European)
589 if (numericStr.lastIndexOf(',') > numericStr.lastIndexOf('.')) {
590 numericStr = numericStr.replace(/\./g, '').replace(',', '.')
591 } else {
592 // US format - just remove commas
593 numericStr = numericStr.replace(/,/g, '')
594 }
595 } else if (numericStr.includes(',')) {
596 // Only comma - could be decimal or thousand separator
597 // If there are 2 digits after the last comma, it's a decimal separator
598 const parts = numericStr.split(',')
599 if (parts.length === 2 && parts[1].length <= 2) {
600 numericStr = numericStr.replace(',', '.')
601 } else {
602 numericStr = numericStr.replace(/,/g, '')
603 }
604 }
605
606 const result = parseFloat(numericStr)
607 return isNaN(result) ? 0 : result
608 },
609
610 formatPrice(priceString) {
611 if (!priceString) return this.getCurrencySymbol() + '0.00'
612 // If it contains HTML, return as is for v-html
613 if (priceString.includes('<')) {
614 return priceString
615 }
616 // Otherwise format as simple price with currency
617 const symbol = this.getCurrencySymbol()
618 const position = window.setup_wizard_obj?.currency_position || 'left'
619
620 if (position === 'right') {
621 return `${priceString}${symbol}`
622 } else if (position === 'right_space') {
623 return `${priceString} ${symbol}`
624 } else if (position === 'left_space') {
625 return `${symbol} ${priceString}`
626 }
627 // Default: left
628 return `${symbol}${priceString}`
629 },
630
631 getCurrencySymbol() {
632 return window.setup_wizard_obj?.currency_symbol || '$'
633 },
634
635 formatSimplePrice(amount) {
636 const symbol = this.getCurrencySymbol()
637 const position = window.setup_wizard_obj?.currency_position || 'left'
638
639 // Ensure amount is a valid number
640 let numericAmount = 0
641 if (typeof amount === 'number') {
642 numericAmount = amount
643 } else if (typeof amount === 'string') {
644 numericAmount = parseFloat(amount) || 0
645 }
646
647 const price = numericAmount.toFixed(2)
648
649 if (position === 'right') {
650 return `${price}${symbol}`
651 } else if (position === 'right_space') {
652 return `${price} ${symbol}`
653 } else if (position === 'left_space') {
654 return `${symbol} ${price}`
655 }
656 // Default: left
657 return `${symbol}${price}`
658 },
659
660 async searchProducts(query, limit = 0) {
661 try {
662 const params = new URLSearchParams({
663 action: 'wpfnl_product_search',
664 term: query || '',
665 security: window.setup_wizard_obj.admin_nonce,
666 isLms: 'false'
667 })
668
669 if (limit > 0) {
670 params.append('limit', limit)
671 }
672
673 const response = await fetch(`${window.setup_wizard_obj.ajax_url}?${params.toString()}`, {
674 method: 'GET'
675 })
676
677 const data = await response.json()
678
679 if (data && typeof data === 'object') {
680 // Convert object to array format
681 return Object.keys(data).map(id => {
682 const product = data[id]
683 // Try to get price from various possible fields
684 const priceValue = product.price || product.regular_price || product.sale_price || '0'
685
686 return {
687 id: id,
688 name: product.name || product.text || '',
689 price: priceValue,
690 priceHtml: priceValue,
691 numericPrice: this.extractNumericPrice(priceValue)
692 }
693 })
694 }
695 return []
696 } catch (error) {
697 console.error('Error searching products:', error)
698 return []
699 }
700 },
701
702 searchMainProducts() {
703 if (this.mainProductDebounce) clearTimeout(this.mainProductDebounce)
704
705 this.mainProductDebounce = setTimeout(async () => {
706 if (!this.mainProductSearch || this.mainProductSearch.length < 2) {
707 return
708 }
709
710 this.loadingMainProducts = true
711 this.mainProducts = await this.searchProducts(this.mainProductSearch)
712 this.loadingMainProducts = false
713 }, 300)
714 },
715
716 async loadInitialProducts(type) {
717 if (type === 'main') {
718 if (this.mainProducts.length > 0) {
719 return
720 }
721 this.loadingMainProducts = true
722 // Fetch recent products (empty term + limit returns latest products)
723 const products = await this.searchProducts('', 10)
724 this.mainProducts = products
725 this.loadingMainProducts = false
726 } else if (type === 'orderbump') {
727 if (this.orderBumpProducts.length > 0) {
728 return
729 }
730 this.loadingOrderBumpProducts = true
731 const products = await this.searchProducts('', 10)
732 this.orderBumpProducts = products
733 this.loadingOrderBumpProducts = false
734 } else if (type === 'upsell') {
735 if (this.upsellProducts.length > 0) {
736 return
737 }
738 this.loadingUpsellProducts = true
739 const products = await this.searchProducts('', 10)
740 this.upsellProducts = products
741 this.loadingUpsellProducts = false
742 }
743 },
744
745 searchOrderBumpProducts() {
746 if (this.orderBumpDebounce) clearTimeout(this.orderBumpDebounce)
747
748 this.orderBumpDebounce = setTimeout(async () => {
749 if (!this.orderBumpSearch || this.orderBumpSearch.length < 2) {
750 return
751 }
752
753 this.loadingOrderBumpProducts = true
754 this.orderBumpProducts = await this.searchProducts(this.orderBumpSearch)
755 this.loadingOrderBumpProducts = false
756 }, 300)
757 },
758
759 searchUpsellProducts() {
760 if (this.upsellDebounce) clearTimeout(this.upsellDebounce)
761
762 this.upsellDebounce = setTimeout(async () => {
763 if (!this.upsellSearch || this.upsellSearch.length < 2) {
764 return
765 }
766
767 this.loadingUpsellProducts = true
768 this.upsellProducts = await this.searchProducts(this.upsellSearch)
769 this.loadingUpsellProducts = false
770 }, 300)
771 },
772
773 loadTemplateImages() {
774 if (this.template && this.template.steps) {
775 this.template.steps.forEach(step => {
776 if (step.step_type === 'landing') {
777 this.templateImages.landing = step.featured_image || step.preview_image || ''
778 } else if (step.step_type === 'checkout') {
779 this.templateImages.checkout = step.featured_image || step.preview_image || ''
780 } else if (step.step_type === 'upsell') {
781 this.templateImages.upsell = step.featured_image || step.preview_image || ''
782 } else if (step.step_type === 'thankyou') {
783 this.templateImages.thankyou = step.featured_image || step.preview_image || ''
784 }
785 })
786 }
787
788 // Use template featured image as fallback for landing
789 if (!this.templateImages.landing && this.template && this.template.featured_image) {
790 this.templateImages.landing = this.template.featured_image
791 }
792 },
793
794 closeDropdowns(e) {
795 if (!e.target.closest('.wpfnl-mm-build-funnel-dropdown')) {
796 this.showMainProductDropdown = false
797 this.showOrderBumpDropdown = false
798 this.showUpsellDropdown = false
799 }
800 },
801
802 toggleMainProductDropdown() {
803 this.showMainProductDropdown = !this.showMainProductDropdown
804 this.showOrderBumpDropdown = false
805 this.showUpsellDropdown = false
806
807 if (this.showMainProductDropdown) {
808 this.loadInitialProducts('main')
809 this.$nextTick(() => {
810 if (this.$refs.mainProductSearchInput) {
811 this.$refs.mainProductSearchInput.focus()
812 }
813 })
814 }
815 },
816
817 toggleOrderBumpDropdown() {
818 this.showOrderBumpDropdown = !this.showOrderBumpDropdown
819 this.showMainProductDropdown = false
820 this.showUpsellDropdown = false
821
822 if (this.showOrderBumpDropdown) {
823 this.loadInitialProducts('orderbump')
824 this.$nextTick(() => {
825 if (this.$refs.orderBumpSearchInput) {
826 this.$refs.orderBumpSearchInput.focus()
827 }
828 })
829 }
830 },
831
832 toggleUpsellDropdown() {
833 this.showUpsellDropdown = !this.showUpsellDropdown
834 this.showMainProductDropdown = false
835 this.showOrderBumpDropdown = false
836
837 if (this.showUpsellDropdown) {
838 this.loadInitialProducts('upsell')
839 this.$nextTick(() => {
840 if (this.$refs.upsellSearchInput) {
841 this.$refs.upsellSearchInput.focus()
842 }
843 })
844 }
845 },
846
847 selectMainProduct(product) {
848 this.selectedMainProduct = product
849 this.showMainProductDropdown = false
850 this.mainProductSearch = ''
851 this.mainProducts = []
852 // Auto-switch to checkout preview if available
853 if (this.hasCheckout) {
854 this.activeSubStep = 'checkout'
855 }
856 },
857
858 selectOrderBump(product) {
859 this.selectedOrderBump = product
860 this.showOrderBumpDropdown = false
861 this.orderBumpSearch = ''
862 this.orderBumpProducts = []
863 // Auto-switch to checkout preview if available
864 if (this.hasCheckout) {
865 this.activeSubStep = 'checkout'
866 }
867 },
868
869 selectUpsell(product) {
870 this.selectedUpsell = product
871 this.showUpsellDropdown = false
872 this.upsellSearch = ''
873 this.upsellProducts = []
874 // Auto-switch to upsell preview if available
875 if (this.hasUpsell) {
876 this.activeSubStep = 'upsell'
877 }
878 },
879
880 setActiveSubStep(step) {
881 this.activeSubStep = step
882 },
883
884 showCreateProductModal() {
885 this.isCreateProduct = true
886 this.isShowModalLoader = true
887
888 setTimeout(() => {
889 const container = document.getElementById('wpfnl-create-product')
890 const productUrl = window?.setup_wizard_obj?.product_url
891
892 // Create an iframe element
893 const iframe = document.createElement('iframe')
894
895 // Set the attributes of the iframe
896 iframe.setAttribute('src', productUrl)
897 iframe.setAttribute('width', '100%')
898 iframe.setAttribute('height', '100%')
899 iframe.setAttribute('frameborder', '0')
900
901 iframe.addEventListener('load', () => {
902 this.isShowModalLoader = false
903 })
904
905 container.appendChild(iframe)
906 }, 100)
907 },
908
909 removeCreateProductModal() {
910 this.isCreateProduct = false
911 const container = document.getElementById('wpfnl-create-product')
912 if (container) {
913 container.innerHTML = ''
914 }
915
916 // Clear cached product lists so dropdowns reload with fresh data
917 // (including any product just created via iframe)
918 this.mainProducts = []
919 this.orderBumpProducts = []
920 this.upsellProducts = []
921
922 // Reload products for any currently open dropdown
923 if (this.showMainProductDropdown) {
924 this.loadInitialProducts('main')
925 }
926 if (this.showOrderBumpDropdown) {
927 this.loadInitialProducts('orderbump')
928 }
929 if (this.showUpsellDropdown) {
930 this.loadInitialProducts('upsell')
931 }
932 },
933
934 goBack() {
935 this.$emit('prev-step')
936 },
937
938 async handleContinue() {
939 // For order-value goal, create the funnel directly
940 if (this.goal === 'order-value') {
941 await this.createFunnelForOrderValue();
942 return;
943 }
944
945 // For other goals, emit product selections to the next step (GenerateFunnel)
946 this.$emit('next-step', {
947 mainProduct: this.selectedMainProduct,
948 orderBump: this.selectedOrderBump,
949 upsell: this.selectedUpsell
950 })
951 },
952
953 async createFunnelForOrderValue() {
954 if (!this.template || !this.template.steps) {
955 alert('No template selected. Please go back and select a template.');
956 return;
957 }
958
959 this.isGeneratingFunnel = true;
960 this.funnelStatusMessage = 'Creating your funnel...';
961
962 try {
963 const templateType = 'wc';
964
965 // Update general settings
966 await this.updateGeneralSettings();
967
968 // Create funnel
969 this.funnelStatusMessage = 'Creating funnel structure...';
970 const funnelResponse = await this.createFunnel(templateType);
971 const funnelId = funnelResponse?.funnelID;
972
973 if (!funnelId) {
974 throw new Error('Funnel could not be created. Please try again.');
975 }
976
977 // Import steps
978 this.funnelStatusMessage = 'Importing template steps...';
979 const { importedSteps, firstStepLink } = await this.importSteps(funnelId);
980
981 // Assign products
982 this.funnelStatusMessage = 'Assigning products...';
983 await this.assignProductsToSteps(importedSteps);
984
985 // Finalize
986 this.funnelStatusMessage = 'Finalizing funnel...';
987 await this.afterFunnelCreation(funnelId, importedSteps);
988
989 this.createdFunnelId = funnelId;
990 this.createdFirstStepLink = firstStepLink;
991 this.isGeneratingFunnel = false;
992 this.funnelStatusMessage = 'Funnel created successfully!';
993
994 // Proceed to Complete step
995 this.$emit('next-step', {
996 funnelId: this.createdFunnelId,
997 firstStepLink: this.createdFirstStepLink
998 });
999
1000 } catch (error) {
1001 console.error('Funnel creation failed:', error);
1002 this.isGeneratingFunnel = false;
1003 this.funnelStatusMessage = '';
1004 alert(error && error.message ? error.message : 'Unable to create the funnel. Please try again.');
1005 }
1006 },
1007
1008 updateGeneralSettings() {
1009 return new Promise((resolve, reject) => {
1010 wpAjaxHelperRequest('update-general-settings', {
1011 builder: this.builder || 'gutenberg'
1012 })
1013 .success(() => resolve())
1014 .error(error => reject(error));
1015 });
1016 },
1017
1018 createFunnel(templateType) {
1019 const steps = this.filterStepsForImport(this.template?.steps || []);
1020 const data = {
1021 steps: steps,
1022 name: this.template?.title || 'My Order Value Funnel',
1023 source: 'remote',
1024 remoteID: this.template?.ID || this.template?.id,
1025 type: templateType,
1026 status: 'draft'
1027 };
1028
1029 return new Promise((resolve, reject) => {
1030 wpAjaxHelperRequest('wpfunnel-import-funnel', data)
1031 .success(response => resolve(response || {}))
1032 .error(error => reject(error));
1033 });
1034 },
1035
1036 filterStepsForImport(steps) {
1037 const isProActive = window?.setup_wizard_obj?.is_pro_active === 'yes';
1038 return (steps || []).filter(step => {
1039 const stepType = step?.step_type || step?.stepType || step?.type;
1040
1041 // For order-value, include all steps except downsell if pro is not active
1042 if (stepType === 'downsell' && !isProActive) {
1043 return false;
1044 }
1045
1046 return true;
1047 });
1048 },
1049
1050 async importSteps(funnelId) {
1051 const stepsToImport = this.filterStepsForImport(this.template?.steps || []);
1052 const importedSteps = {};
1053 let firstStepLink = '';
1054
1055 for (let index = 0; index < stepsToImport.length; index++) {
1056 const step = stepsToImport[index];
1057 try {
1058 const response = await apiFetch({
1059 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/steps/wpfunnel-import-step`,
1060 method: 'POST',
1061 data: {
1062 step,
1063 funnelID: funnelId,
1064 source: 'remote',
1065 importType: 'templates'
1066 }
1067 });
1068
1069 if (response?.stepID && step?.step_type) {
1070 importedSteps[step.step_type] = response.stepID;
1071 }
1072
1073 if (index === 0 && response?.stepViewLink) {
1074 firstStepLink = response.stepViewLink;
1075 }
1076 } catch (error) {
1077 console.error(`Error importing step ${step?.step_type || index}:`, error);
1078 }
1079 }
1080
1081 if (!Object.keys(importedSteps).length) {
1082 throw new Error('Unable to import the selected template steps.');
1083 }
1084
1085 return { importedSteps, firstStepLink };
1086 },
1087
1088 async assignProductsToSteps(importedSteps) {
1089 // Assign main product to checkout step
1090 if (importedSteps.checkout && this.selectedMainProduct && this.selectedMainProduct.id) {
1091 try {
1092 await apiFetch({
1093 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/checkout/wpfnl-add-product`,
1094 method: 'POST',
1095 data: {
1096 id: this.selectedMainProduct.id,
1097 step_id: importedSteps.checkout,
1098 quantity: 1
1099 }
1100 });
1101 } catch (error) {
1102 console.error('Error assigning main product:', error);
1103 }
1104 }
1105
1106 // Assign order bump to checkout step
1107 if (importedSteps.checkout && this.selectedOrderBump && this.selectedOrderBump.id) {
1108 try {
1109 let productImage = {
1110 id: 0,
1111 url: window.setup_wizard_obj.plugin_url ? window.setup_wizard_obj.plugin_url + 'admin/assets/images/placeholder.png' : ''
1112 };
1113
1114 try {
1115 const productData = await apiFetch({
1116 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/updateSelectedProduct?product=${this.selectedOrderBump.id}`,
1117 method: 'GET'
1118 });
1119
1120 if (productData && productData.img) {
1121 const imgTag = productData.img;
1122 const srcMatch = imgTag.match(/src="([^"]+)"/);
1123 if (srcMatch) {
1124 productImage.url = srcMatch[1];
1125 }
1126 const idMatch = imgTag.match(/wp-image-(\d+)/);
1127 if (idMatch) {
1128 productImage.id = parseInt(idMatch[1]);
1129 }
1130 }
1131 } catch (imgError) {
1132 console.error('Could not fetch product image, using placeholder:', imgError);
1133 }
1134
1135 const orderBumpData = [{
1136 name: 'Order Bump',
1137 selectedStyle: 'style1',
1138 isEnabled: true,
1139 position: 'after-order',
1140 product: this.selectedOrderBump.id,
1141 productName: this.selectedOrderBump.name,
1142 chooseVariantName: 'Choose an Option',
1143 productSearchName: this.selectedOrderBump.name,
1144 productType: '',
1145 price: '',
1146 salePrice: '',
1147 quantity: '1',
1148 htmlPrice: this.selectedOrderBump.priceHtml || '',
1149 numericRegularPrice: '',
1150 numericSalePrice: '',
1151 discountPrice: '',
1152 discountPriceHtml: '',
1153 productImage: productImage,
1154 highLightText: this.selectedOrderBump.name,
1155 checkBoxLabel: 'Yes, I want this!',
1156 productDescriptionText: 'Add this special offer to your order',
1157 discountOption: 'original',
1158 discountapply: 'regular',
1159 discountValue: '',
1160 couponName: '',
1161 obNextStep: 'default',
1162 isReplace: 'no',
1163 replaceSettings: {
1164 isAllReplace: 'yes',
1165 selectedProducts: []
1166 },
1167 replace: '',
1168 obArrowColor: '#EE8134',
1169 obPrimaryColor: '#6E42D2',
1170 obBgColor: '',
1171 obTitleColor: '#363B4E',
1172 obHighlightColor: '#6E42D3',
1173 obCheckboxTitleColor: '#d9d9d9',
1174 obDescriptionColor: '#7A8B9A',
1175 isRemoveObBgColor: '',
1176 obPriceColor: '#E86F2C',
1177 obChooseVariantColor: '#F34D01',
1178 prePurchaseUpsell: 'no'
1179 }];
1180
1181 await apiFetch({
1182 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/order-bump`,
1183 method: 'POST',
1184 data: {
1185 value: orderBumpData,
1186 stepID: importedSteps.checkout
1187 }
1188 });
1189 } catch (error) {
1190 console.error('Error creating order bump:', error);
1191 }
1192 }
1193
1194 // Assign upsell product to upsell step
1195 if (importedSteps.upsell && this.selectedUpsell && this.selectedUpsell.id) {
1196 try {
1197 const upsellData = {
1198 id: this.selectedUpsell.id,
1199 quantity: 1
1200 };
1201
1202 await apiFetch({
1203 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/offer/saveUpsellData/`,
1204 method: 'POST',
1205 data: {
1206 step_id: importedSteps.upsell,
1207 product: JSON.stringify(upsellData)
1208 }
1209 });
1210 } catch (error) {
1211 console.error('Error assigning upsell product:', error);
1212 }
1213 }
1214 },
1215
1216 afterFunnelCreation(funnelId, importedSteps) {
1217 const payload = {
1218 funnelID: funnelId,
1219 source: 'remote',
1220 importedSteps: Object.values(importedSteps || {})
1221 };
1222
1223 return new Promise(resolve => {
1224 wpAjaxHelperRequest('wpfunnel-after-funnel-creation', payload)
1225 .success(response => resolve(response))
1226 .error(error => {
1227 console.error('After funnel creation error:', error);
1228 resolve();
1229 });
1230 });
1231 },
1232 }
1233 }
1234 </script>
1235
1236 <style scoped>
1237 .wpfnl-mm-build-funnel {
1238 width: 100%;
1239 max-width: 1049px;
1240 display: flex;
1241 flex-direction: column;
1242 gap: 15px;
1243 position: relative;
1244 }
1245
1246 /* Header */
1247 .wpfnl-mm-build-funnel-header {
1248 text-align: center;
1249 margin-bottom: 5px;
1250 }
1251
1252 .wpfnl-mm-build-funnel-title {
1253 font-size: 24px;
1254 font-weight: 700;
1255 font-family: 'DM Sans', sans-serif;
1256 color: #363B4E;
1257 line-height: 35px;
1258 letter-spacing: -1px;
1259 margin: 0 0 8px 0;
1260 }
1261
1262 .wpfnl-mm-build-funnel-subtitle {
1263 font-size: 15px;
1264 font-weight: 400;
1265 font-family: 'DM Sans', sans-serif;
1266 color: #6E7A85;
1267 line-height: 15px;
1268 margin: 0;
1269 }
1270
1271 /* Main Container */
1272 .wpfnl-mm-build-funnel-container {
1273 display: flex;
1274 gap: 3px;
1275 }
1276
1277 /* Left Panel - Form */
1278 .wpfnl-mm-build-funnel-form {
1279 width: 461px;
1280 background: #FFF;
1281 border-radius: 16px;
1282 padding: 40px;
1283 display: flex;
1284 flex-direction: column;
1285 min-height: 556px;
1286 }
1287
1288 .wpfnl-mm-build-funnel-fields {
1289 display: flex;
1290 flex-direction: column;
1291 gap: 40px;
1292 flex: 1;
1293 }
1294
1295 .wpfnl-mm-build-funnel-field {
1296 display: flex;
1297 flex-direction: column;
1298 gap: 9px;
1299 }
1300
1301 .wpfnl-mm-build-funnel-label {
1302 font-size: 15px;
1303 font-weight: 500;
1304 font-family: 'DM Sans', sans-serif;
1305 color: #363B4E;
1306 letter-spacing: -0.44px;
1307 line-height: 16px;
1308 }
1309
1310 .wpfnl-mm-build-funnel-label .required {
1311 color: red;
1312 }
1313
1314 .wpfnl-mm-build-funnel-label .optional {
1315 font-weight: 400;
1316 color: #7A8C9A;
1317 }
1318
1319 /* Dropdown */
1320 .wpfnl-mm-build-funnel-dropdown {
1321 position: relative;
1322 }
1323
1324 .wpfnl-mm-build-funnel-dropdown-trigger {
1325 height: 46px;
1326 background: #FFF;
1327 border: 1px solid #F1EFF7;
1328 border-radius: 8px;
1329 padding: 0 16px;
1330 display: flex;
1331 align-items: center;
1332 justify-content: space-between;
1333 cursor: pointer;
1334 transition: all 0.3s ease;
1335 }
1336
1337 .wpfnl-mm-build-funnel-dropdown.open .wpfnl-mm-build-funnel-dropdown-trigger {
1338 border-color: #6E42D3;
1339 }
1340
1341 .wpfnl-mm-build-funnel-dropdown-trigger .placeholder {
1342 font-size: 14px;
1343 font-family: 'DM Sans', sans-serif;
1344 color: #7A8C9A;
1345 }
1346
1347 .wpfnl-mm-build-funnel-dropdown-trigger .selected-value {
1348 font-size: 14px;
1349 font-family: 'DM Sans', sans-serif;
1350 color: #363B4E;
1351 }
1352
1353 .wpfnl-mm-build-funnel-dropdown-trigger .dropdown-icon {
1354 transition: transform 0.3s ease;
1355 }
1356
1357 .wpfnl-mm-build-funnel-dropdown.open .dropdown-icon {
1358 transform: rotate(180deg);
1359 }
1360
1361 .wpfnl-mm-build-funnel-dropdown-menu {
1362 position: absolute;
1363 top: 100%;
1364 left: 0;
1365 right: 0;
1366 background: #FFF;
1367 border: 1px solid #F1EFF7;
1368 border-radius: 8px;
1369 margin-top: 4px;
1370 z-index: 100;
1371 box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
1372 overflow: hidden;
1373 }
1374
1375 /* Search Input */
1376 .wpfnl-mm-build-funnel-search {
1377 display: flex;
1378 align-items: center;
1379 gap: 10px;
1380 padding: 12px 16px;
1381 border-bottom: 1px solid #F1EFF7;
1382 }
1383
1384 .wpfnl-mm-build-funnel-search svg {
1385 flex-shrink: 0;
1386 }
1387
1388 .wpfnl-mm-build-funnel-search input {
1389 flex: 1;
1390 border: none;
1391 outline: none;
1392 font-size: 14px;
1393 font-family: 'DM Sans', sans-serif;
1394 color: #363B4E;
1395 background: transparent;
1396 }
1397
1398 .wpfnl-mm-build-funnel-search input::placeholder {
1399 color: #7A8C9A;
1400 }
1401
1402 .wpfnl-mm-build-funnel-dropdown-list {
1403 max-height: 200px;
1404 overflow-y: auto;
1405 }
1406
1407 .wpfnl-mm-build-funnel-dropdown-loading,
1408 .wpfnl-mm-build-funnel-dropdown-empty {
1409 padding: 16px;
1410 text-align: center;
1411 font-size: 14px;
1412 font-family: 'DM Sans', sans-serif;
1413 color: #7A8C9A;
1414 display: flex;
1415 flex-direction: column;
1416 align-items: center;
1417 justify-content: center;
1418 gap: 12px;
1419 }
1420
1421 .wpfnl-mm-build-funnel-dropdown-empty p {
1422 margin: 0;
1423 }
1424
1425 .wpfnl-mm-create-product-btn {
1426 display: inline-flex;
1427 align-items: center;
1428 gap: 8px;
1429 padding: 8px 16px;
1430 background: #FFF;
1431 border: 1px solid #6E42D3;
1432 border-radius: 6px;
1433 font-size: 13px;
1434 font-weight: 500;
1435 font-family: 'DM Sans', sans-serif;
1436 color: #6E42D3;
1437 cursor: pointer;
1438 transition: all 0.3s ease;
1439 }
1440
1441 .wpfnl-mm-create-product-btn:hover {
1442 background: #6E42D3;
1443 color: #FFF;
1444 }
1445
1446 .wpfnl-mm-create-product-btn:hover svg path {
1447 stroke: #FFF;
1448 }
1449
1450 .wpfnl-mm-create-product-btn svg {
1451 width: 12px;
1452 height: 12px;
1453 }
1454
1455 .wpfnl-mm-build-funnel-dropdown-item {
1456 padding: 12px 16px;
1457 font-size: 14px;
1458 font-family: 'DM Sans', sans-serif;
1459 color: #363B4E;
1460 cursor: pointer;
1461 display: flex;
1462 align-items: center;
1463 gap: 8px;
1464 transition: background 0.2s ease;
1465 }
1466
1467 .wpfnl-mm-build-funnel-dropdown-item:hover {
1468 background: #F6F5FA;
1469 }
1470
1471 .wpfnl-mm-build-funnel-dropdown-item.selected {
1472 background: #F6F5FA;
1473 color: #6E42D3;
1474 }
1475
1476 .wpfnl-mm-build-funnel-dropdown-item .product-name {
1477 flex: 1;
1478 }
1479
1480 .wpfnl-mm-build-funnel-dropdown-item .product-price {
1481 color: #6E42D3;
1482 font-weight: 500;
1483 }
1484
1485 .wpfnl-mm-build-funnel-hint {
1486 font-size: 12px;
1487 font-family: 'Inter', sans-serif;
1488 color: #7A8C9A;
1489 line-height: 16px;
1490 margin: 0;
1491 }
1492
1493 /* Potential Revenue */
1494 .wpfnl-mm-build-funnel-revenue {
1495 margin-top: auto;
1496 padding: 20px;
1497 background: #FFFFFF;
1498 border: 1px solid #ECEBF0;
1499 border-radius: 12px;
1500 }
1501
1502 .wpfnl-mm-revenue-header {
1503 margin-bottom: 16px;
1504 }
1505
1506 .wpfnl-mm-build-funnel-revenue-label {
1507 font-size: 13px;
1508 font-family: 'DM Sans', sans-serif;
1509 font-weight: 600;
1510 color: #363B4E;
1511 margin: 0;
1512 letter-spacing: -0.2px;
1513 }
1514
1515 .wpfnl-mm-revenue-breakdown {
1516 display: flex;
1517 flex-direction: column;
1518 gap: 10px;
1519 }
1520
1521 .wpfnl-mm-revenue-item {
1522 display: flex;
1523 justify-content: space-between;
1524 align-items: center;
1525 border: none;
1526 padding: 10px 0 0;
1527 border-radius: 8px;
1528 gap: 12px;
1529 }
1530
1531 .wpfnl-mm-revenue-item-bonus {
1532 background: #F6F5FA;
1533 }
1534
1535 .wpfnl-mm-revenue-item-content {
1536 display: flex;
1537 flex-direction: column;
1538 gap: 4px;
1539 flex: 1;
1540 min-width: 0;
1541 }
1542
1543 .wpfnl-mm-revenue-item-label {
1544 font-size: 11px;
1545 font-family: 'DM Sans', sans-serif;
1546 color: #7A8C9A;
1547 font-weight: 500;
1548 text-transform: uppercase;
1549 letter-spacing: 0.5px;
1550 display: flex;
1551 align-items: center;
1552 gap: 6px;
1553 }
1554
1555 .wpfnl-mm-revenue-item-name {
1556 font-size: 14px;
1557 font-family: 'DM Sans', sans-serif;
1558 color: #363B4E;
1559 font-weight: 500;
1560 white-space: nowrap;
1561 overflow: hidden;
1562 text-overflow: ellipsis;
1563 }
1564
1565 .wpfnl-mm-revenue-badge {
1566 display: inline-block;
1567 padding: 2px 6px;
1568 background: #6E42D3;
1569 color: #FFFFFF;
1570 font-size: 9px;
1571 font-weight: 600;
1572 border-radius: 3px;
1573 text-transform: uppercase;
1574 letter-spacing: 0.3px;
1575 }
1576
1577 .wpfnl-mm-revenue-item-price {
1578 font-size: 16px;
1579 font-family: 'DM Sans', sans-serif;
1580 font-weight: 600;
1581 color: #363B4E;
1582 flex-shrink: 0;
1583 }
1584
1585 .wpfnl-mm-revenue-bonus {
1586 color: #6E42D3;
1587 font-weight: 700;
1588 }
1589
1590 .wpfnl-mm-revenue-separator {
1591 height: 1px;
1592 background: #ECEBF0;
1593 margin: 6px 0;
1594 }
1595
1596 .wpfnl-mm-revenue-total {
1597 display: flex;
1598 justify-content: space-between;
1599 align-items: center;
1600 padding: 16px;
1601 background: #FFFFFF;
1602 border-radius: 8px;
1603 gap: 12px;
1604 }
1605
1606 .wpfnl-mm-revenue-total-content {
1607 display: flex;
1608 flex-direction: column;
1609 gap: 4px;
1610 flex: 1;
1611 }
1612
1613 .wpfnl-mm-revenue-total-label {
1614 font-size: 11px;
1615 font-family: 'DM Sans', sans-serif;
1616 font-weight: 500;
1617 color: #000;
1618 text-transform: uppercase;
1619 letter-spacing: 0.5px;
1620 }
1621
1622 .wpfnl-mm-revenue-total-value {
1623 font-size: 24px;
1624 font-family: 'DM Sans', sans-serif;
1625 font-weight: 700;
1626 color: #000;
1627 letter-spacing: -0.5px;
1628 }
1629
1630 .wpfnl-mm-revenue-boost-inline {
1631 display: flex;
1632 gap: 2px;
1633 border-radius: 6px;
1634 background: #33a6461a;
1635 flex-direction: row;
1636 height: auto;
1637 justify-content: center;
1638 align-items: center;
1639 padding: 16px 12px;
1640 line-height: 1;
1641 }
1642
1643 .wpfnl-mm-revenue-boost-inline svg {
1644 color: #33A646;
1645 width: 12px;
1646 height: 12px;
1647 }
1648
1649 .wpfnl-mm-revenue-boost-inline span {
1650 font-size: 12px;
1651 font-family: 'DM Sans', sans-serif;
1652 font-weight: 700;
1653 color: #33A646;
1654 letter-spacing: 0.4px;
1655 }
1656
1657 /* Tabs */
1658 .wpfnl-mm-build-funnel-tabs {
1659 display: flex;
1660 align-items: center;
1661 justify-content: center;
1662 padding: 20px 20px 0;
1663 gap: 0;
1664 flex-wrap: wrap;
1665 }
1666
1667 .wpfnl-mm-build-funnel-tab {
1668 display: flex;
1669 flex-direction: column;
1670 align-items: center;
1671 gap: 8px;
1672 cursor: pointer;
1673 flex-shrink: 0;
1674 }
1675
1676 .wpfnl-mm-build-funnel-tab-icon {
1677 width: 48px;
1678 height: 48px;
1679 border-radius: 50%;
1680 border: 1px solid #F1EFF7;
1681 display: flex;
1682 align-items: center;
1683 justify-content: center;
1684 color: #6E7A85;
1685 transition: all 0.3s ease;
1686 }
1687
1688 .wpfnl-mm-build-funnel-tab.active .wpfnl-mm-build-funnel-tab-icon {
1689 border: 1.5px solid #6E42D3;
1690 color: #6E42D3;
1691 box-shadow: 0px 20px 30px rgba(199, 191, 217, 0.3);
1692 }
1693
1694 .wpfnl-mm-build-funnel-tab-label {
1695 font-size: 12px;
1696 font-family: 'Inter', sans-serif;
1697 font-weight: 500;
1698 color: #6E7A85;
1699 text-align: center;
1700 white-space: nowrap;
1701 }
1702
1703 .wpfnl-mm-build-funnel-tab.active .wpfnl-mm-build-funnel-tab-label {
1704 color: #363B4E;
1705 }
1706
1707 .wpfnl-mm-build-funnel-tab-line {
1708 width: 16px;
1709 height: 1px;
1710 background: #E5E5E5;
1711 margin: 0 6px;
1712 margin-bottom: 24px;
1713 flex-shrink: 0;
1714 }
1715
1716 /* Preview Content */
1717 .wpfnl-mm-build-funnel-preview-content {
1718 flex: 1;
1719 padding: 20px;
1720 }
1721
1722
1723 .wpfnl-mm-build-funnel-preview-placeholder,
1724 .wpfnl-mm-build-funnel-preview-empty {
1725 display: flex;
1726 flex-direction: column;
1727 align-items: center;
1728 justify-content: center;
1729 height: 100%;
1730 min-height: 400px;
1731 color: #7A8C9A;
1732 text-align: center;
1733 padding: 40px;
1734 }
1735
1736 .wpfnl-mm-build-funnel-preview-placeholder svg,
1737 .wpfnl-mm-build-funnel-preview-empty svg {
1738 margin-bottom: 16px;
1739 opacity: 0.5;
1740 }
1741
1742 .wpfnl-mm-build-funnel-preview-placeholder p,
1743 .wpfnl-mm-build-funnel-preview-empty p {
1744 font-size: 14px;
1745 margin: 0;
1746 }
1747
1748 .wpfnl-mm-build-funnel-preview-empty h4 {
1749 font-size: 18px;
1750 font-weight: 600;
1751 color: #363B4E;
1752 margin: 0 0 8px 0;
1753 }
1754
1755 /* Buttons */
1756 .wpfnl-mm-build-funnel-buttons {
1757 display: flex;
1758 justify-content: space-between;
1759 align-items: center;
1760 width: 100%;
1761 }
1762
1763 .wpfnl-mm-btn {
1764 padding: 10px 24px;
1765 border-radius: 10px;
1766 font-size: 16px;
1767 font-weight: 700;
1768 font-family: 'DM Sans', sans-serif;
1769 line-height: 24px;
1770 cursor: pointer;
1771 transition: all 0.3s ease;
1772 border: none;
1773 display: inline-flex;
1774 align-items: center;
1775 justify-content: center;
1776 gap: 8px;
1777 height: 44px;
1778 }
1779
1780 .wpfnl-mm-btn-secondary {
1781 background: #FFF;
1782 color: #363B4E;
1783 border: 1px solid #ECEBF0;
1784 min-width: 87px;
1785 }
1786
1787 .wpfnl-mm-btn-secondary:hover {
1788 border-color: #6E42D3;
1789 color: #6E42D3;
1790 }
1791
1792 .wpfnl-mm-btn-skip {
1793 background: transparent;
1794 color: #7A8C9A;
1795 font-weight: 500;
1796 font-size: 15px;
1797 padding: 10px;
1798 }
1799
1800 .wpfnl-mm-btn-skip:hover {
1801 color: #6E42D3;
1802 }
1803
1804 .wpfnl-mm-btn-primary {
1805 background: #6E42D3;
1806 color: #FFF;
1807 font-weight: 600;
1808 letter-spacing: 0.2px;
1809 min-width: 146px;
1810 }
1811
1812 .wpfnl-mm-btn-primary svg {
1813 width: 17px;
1814 height: 12px;
1815 }
1816
1817 .wpfnl-mm-btn-primary:hover {
1818 background: #5c36b3;
1819 }
1820
1821 .wpfnl-mm-btn-primary:disabled {
1822 background: #ccc;
1823 cursor: not-allowed;
1824 }
1825
1826 .wpfnl-mm-btn-group {
1827 display: flex;
1828 gap: 10px;
1829 align-items: center;
1830 }
1831
1832 /* Spinner */
1833 @keyframes spin {
1834 from { transform: rotate(0deg); }
1835 to { transform: rotate(360deg); }
1836 }
1837
1838 .wpfnl-spinner {
1839 animation: spin 1s linear infinite;
1840 }
1841
1842 /* Help Button */
1843 .wpfnl-mm-help-btn {
1844 position: fixed;
1845 bottom: 40px;
1846 right: 54px;
1847 z-index: 100;
1848 }
1849
1850 .wpfnl-mm-help-btn-circle {
1851 width: 34px;
1852 height: 34px;
1853 border-radius: 100px;
1854 background: #201F22;
1855 border: none;
1856 cursor: pointer;
1857 display: flex;
1858 align-items: center;
1859 justify-content: center;
1860 box-shadow: 0px 1px 2px rgba(190, 190, 215, 0.16);
1861 transition: all 0.3s ease;
1862 }
1863
1864 .wpfnl-mm-help-btn-circle svg {
1865 width: 16px;
1866 height: 16px;
1867 }
1868
1869 .wpfnl-mm-help-btn-circle:hover {
1870 background: #363B4E;
1871 transform: scale(1.05);
1872 }
1873
1874 /* Create Product Modal */
1875 .wpfnl-modal-overlay {
1876 position: fixed;
1877 top: 0;
1878 left: 0;
1879 right: 0;
1880 bottom: 0;
1881 background: rgba(0, 0, 0, 0.5);
1882 display: flex;
1883 align-items: center;
1884 justify-content: center;
1885 z-index: 10000;
1886 }
1887
1888 .wpfnl-modal-wrapper {
1889 position: relative;
1890 width: 90%;
1891 max-width: 1400px;
1892 height: 90vh;
1893 background: #FFF;
1894 border-radius: 12px;
1895 overflow: hidden;
1896 box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.2);
1897 }
1898
1899 .wpfnl-modal-close {
1900 position: absolute;
1901 top: 16px;
1902 right: 16px;
1903 z-index: 10;
1904 }
1905
1906 .wpfnl-modal-close-btn {
1907 width: 32px;
1908 height: 32px;
1909 border-radius: 50%;
1910 background: #FFF;
1911 border: 1px solid #E5E5E5;
1912 display: flex;
1913 align-items: center;
1914 justify-content: center;
1915 cursor: pointer;
1916 transition: all 0.3s ease;
1917 }
1918
1919 .wpfnl-modal-close-btn:hover {
1920 background: #F6F5FA;
1921 border-color: #6E42D3;
1922 }
1923
1924 .wpfnl-modal-close-btn svg path {
1925 transition: stroke 0.3s ease;
1926 }
1927
1928 .wpfnl-modal-close-btn:hover svg path {
1929 stroke: #6E42D3;
1930 }
1931
1932 .wpfnl-loader {
1933 position: absolute;
1934 top: 50%;
1935 left: 50%;
1936 transform: translate(-50%, -50%);
1937 width: 40px;
1938 height: 40px;
1939 border: 4px solid #F6F5FA;
1940 border-top-color: #6E42D3;
1941 border-radius: 50%;
1942 animation: spin 1s linear infinite;
1943 }
1944
1945 #wpfnl-create-product {
1946 width: 100%;
1947 height: 100%;
1948 }
1949
1950 #wpfnl-create-product iframe {
1951 width: 100%;
1952 height: 100%;
1953 border: none;
1954 }
1955 </style>
1956