PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.12.12
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.12.12
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / admin / modules / setup-wizard / components / Content / BuildFunnel.vue

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

1,958 lines 58.0 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 funnelType = 'sales';
964 const templateType = 'wc';
965
966 // Update general settings
967 await this.updateGeneralSettings(funnelType);
968
969 // Create funnel
970 this.funnelStatusMessage = 'Creating funnel structure...';
971 const funnelResponse = await this.createFunnel(templateType);
972 const funnelId = funnelResponse?.funnelID;
973
974 if (!funnelId) {
975 throw new Error('Funnel could not be created. Please try again.');
976 }
977
978 // Import steps
979 this.funnelStatusMessage = 'Importing template steps...';
980 const { importedSteps, firstStepLink } = await this.importSteps(funnelId);
981
982 // Assign products
983 this.funnelStatusMessage = 'Assigning products...';
984 await this.assignProductsToSteps(importedSteps);
985
986 // Finalize
987 this.funnelStatusMessage = 'Finalizing funnel...';
988 await this.afterFunnelCreation(funnelId, importedSteps);
989
990 this.createdFunnelId = funnelId;
991 this.createdFirstStepLink = firstStepLink;
992 this.isGeneratingFunnel = false;
993 this.funnelStatusMessage = 'Funnel created successfully!';
994
995 // Proceed to Complete step
996 this.$emit('next-step', {
997 funnelId: this.createdFunnelId,
998 firstStepLink: this.createdFirstStepLink
999 });
1000
1001 } catch (error) {
1002 console.error('Funnel creation failed:', error);
1003 this.isGeneratingFunnel = false;
1004 this.funnelStatusMessage = '';
1005 alert(error && error.message ? error.message : 'Unable to create the funnel. Please try again.');
1006 }
1007 },
1008
1009 updateGeneralSettings(funnelType) {
1010 return new Promise((resolve, reject) => {
1011 wpAjaxHelperRequest('update-general-settings', {
1012 funnel_type: funnelType,
1013 builder: this.builder || 'gutenberg'
1014 })
1015 .success(() => resolve())
1016 .error(error => reject(error));
1017 });
1018 },
1019
1020 createFunnel(templateType) {
1021 const steps = this.filterStepsForImport(this.template?.steps || []);
1022 const data = {
1023 steps: steps,
1024 name: this.template?.title || 'My Order Value Funnel',
1025 source: 'remote',
1026 remoteID: this.template?.ID || this.template?.id,
1027 type: templateType,
1028 status: 'draft'
1029 };
1030
1031 return new Promise((resolve, reject) => {
1032 wpAjaxHelperRequest('wpfunnel-import-funnel', data)
1033 .success(response => resolve(response || {}))
1034 .error(error => reject(error));
1035 });
1036 },
1037
1038 filterStepsForImport(steps) {
1039 const isProActive = window?.setup_wizard_obj?.is_pro_active === 'yes';
1040 return (steps || []).filter(step => {
1041 const stepType = step?.step_type || step?.stepType || step?.type;
1042
1043 // For order-value, include all steps except downsell if pro is not active
1044 if (stepType === 'downsell' && !isProActive) {
1045 return false;
1046 }
1047
1048 return true;
1049 });
1050 },
1051
1052 async importSteps(funnelId) {
1053 const stepsToImport = this.filterStepsForImport(this.template?.steps || []);
1054 const importedSteps = {};
1055 let firstStepLink = '';
1056
1057 for (let index = 0; index < stepsToImport.length; index++) {
1058 const step = stepsToImport[index];
1059 try {
1060 const response = await apiFetch({
1061 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/steps/wpfunnel-import-step`,
1062 method: 'POST',
1063 data: {
1064 step,
1065 funnelID: funnelId,
1066 source: 'remote',
1067 importType: 'templates'
1068 }
1069 });
1070
1071 if (response?.stepID && step?.step_type) {
1072 importedSteps[step.step_type] = response.stepID;
1073 }
1074
1075 if (index === 0 && response?.stepViewLink) {
1076 firstStepLink = response.stepViewLink;
1077 }
1078 } catch (error) {
1079 console.error(`Error importing step ${step?.step_type || index}:`, error);
1080 }
1081 }
1082
1083 if (!Object.keys(importedSteps).length) {
1084 throw new Error('Unable to import the selected template steps.');
1085 }
1086
1087 return { importedSteps, firstStepLink };
1088 },
1089
1090 async assignProductsToSteps(importedSteps) {
1091 // Assign main product to checkout step
1092 if (importedSteps.checkout && this.selectedMainProduct && this.selectedMainProduct.id) {
1093 try {
1094 await apiFetch({
1095 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/checkout/wpfnl-add-product`,
1096 method: 'POST',
1097 data: {
1098 id: this.selectedMainProduct.id,
1099 step_id: importedSteps.checkout,
1100 quantity: 1
1101 }
1102 });
1103 } catch (error) {
1104 console.error('Error assigning main product:', error);
1105 }
1106 }
1107
1108 // Assign order bump to checkout step
1109 if (importedSteps.checkout && this.selectedOrderBump && this.selectedOrderBump.id) {
1110 try {
1111 let productImage = {
1112 id: 0,
1113 url: window.setup_wizard_obj.plugin_url ? window.setup_wizard_obj.plugin_url + 'admin/assets/images/placeholder.png' : ''
1114 };
1115
1116 try {
1117 const productData = await apiFetch({
1118 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/updateSelectedProduct?product=${this.selectedOrderBump.id}`,
1119 method: 'GET'
1120 });
1121
1122 if (productData && productData.img) {
1123 const imgTag = productData.img;
1124 const srcMatch = imgTag.match(/src="([^"]+)"/);
1125 if (srcMatch) {
1126 productImage.url = srcMatch[1];
1127 }
1128 const idMatch = imgTag.match(/wp-image-(\d+)/);
1129 if (idMatch) {
1130 productImage.id = parseInt(idMatch[1]);
1131 }
1132 }
1133 } catch (imgError) {
1134 console.error('Could not fetch product image, using placeholder:', imgError);
1135 }
1136
1137 const orderBumpData = [{
1138 name: 'Order Bump',
1139 selectedStyle: 'style1',
1140 isEnabled: true,
1141 position: 'after-order',
1142 product: this.selectedOrderBump.id,
1143 productName: this.selectedOrderBump.name,
1144 chooseVariantName: 'Choose an Option',
1145 productSearchName: this.selectedOrderBump.name,
1146 productType: '',
1147 price: '',
1148 salePrice: '',
1149 quantity: '1',
1150 htmlPrice: this.selectedOrderBump.priceHtml || '',
1151 numericRegularPrice: '',
1152 numericSalePrice: '',
1153 discountPrice: '',
1154 discountPriceHtml: '',
1155 productImage: productImage,
1156 highLightText: this.selectedOrderBump.name,
1157 checkBoxLabel: 'Yes, I want this!',
1158 productDescriptionText: 'Add this special offer to your order',
1159 discountOption: 'original',
1160 discountapply: 'regular',
1161 discountValue: '',
1162 couponName: '',
1163 obNextStep: 'default',
1164 isReplace: 'no',
1165 replaceSettings: {
1166 isAllReplace: 'yes',
1167 selectedProducts: []
1168 },
1169 replace: '',
1170 obArrowColor: '#EE8134',
1171 obPrimaryColor: '#6E42D2',
1172 obBgColor: '',
1173 obTitleColor: '#363B4E',
1174 obHighlightColor: '#6E42D3',
1175 obCheckboxTitleColor: '#d9d9d9',
1176 obDescriptionColor: '#7A8B9A',
1177 isRemoveObBgColor: '',
1178 obPriceColor: '#E86F2C',
1179 obChooseVariantColor: '#F34D01',
1180 prePurchaseUpsell: 'no'
1181 }];
1182
1183 await apiFetch({
1184 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/order-bump`,
1185 method: 'POST',
1186 data: {
1187 value: orderBumpData,
1188 stepID: importedSteps.checkout
1189 }
1190 });
1191 } catch (error) {
1192 console.error('Error creating order bump:', error);
1193 }
1194 }
1195
1196 // Assign upsell product to upsell step
1197 if (importedSteps.upsell && this.selectedUpsell && this.selectedUpsell.id) {
1198 try {
1199 const upsellData = {
1200 id: this.selectedUpsell.id,
1201 quantity: 1
1202 };
1203
1204 await apiFetch({
1205 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/offer/saveUpsellData/`,
1206 method: 'POST',
1207 data: {
1208 step_id: importedSteps.upsell,
1209 product: JSON.stringify(upsellData)
1210 }
1211 });
1212 } catch (error) {
1213 console.error('Error assigning upsell product:', error);
1214 }
1215 }
1216 },
1217
1218 afterFunnelCreation(funnelId, importedSteps) {
1219 const payload = {
1220 funnelID: funnelId,
1221 source: 'remote',
1222 importedSteps: Object.values(importedSteps || {})
1223 };
1224
1225 return new Promise(resolve => {
1226 wpAjaxHelperRequest('wpfunnel-after-funnel-creation', payload)
1227 .success(response => resolve(response))
1228 .error(error => {
1229 console.error('After funnel creation error:', error);
1230 resolve();
1231 });
1232 });
1233 },
1234 }
1235 }
1236 </script>
1237
1238 <style scoped>
1239 .wpfnl-mm-build-funnel {
1240 width: 100%;
1241 max-width: 1049px;
1242 display: flex;
1243 flex-direction: column;
1244 gap: 15px;
1245 position: relative;
1246 }
1247
1248 /* Header */
1249 .wpfnl-mm-build-funnel-header {
1250 text-align: center;
1251 margin-bottom: 5px;
1252 }
1253
1254 .wpfnl-mm-build-funnel-title {
1255 font-size: 24px;
1256 font-weight: 700;
1257 font-family: 'DM Sans', sans-serif;
1258 color: #363B4E;
1259 line-height: 35px;
1260 letter-spacing: -1px;
1261 margin: 0 0 8px 0;
1262 }
1263
1264 .wpfnl-mm-build-funnel-subtitle {
1265 font-size: 15px;
1266 font-weight: 400;
1267 font-family: 'DM Sans', sans-serif;
1268 color: #6E7A85;
1269 line-height: 15px;
1270 margin: 0;
1271 }
1272
1273 /* Main Container */
1274 .wpfnl-mm-build-funnel-container {
1275 display: flex;
1276 gap: 3px;
1277 }
1278
1279 /* Left Panel - Form */
1280 .wpfnl-mm-build-funnel-form {
1281 width: 461px;
1282 background: #FFF;
1283 border-radius: 16px;
1284 padding: 40px;
1285 display: flex;
1286 flex-direction: column;
1287 min-height: 556px;
1288 }
1289
1290 .wpfnl-mm-build-funnel-fields {
1291 display: flex;
1292 flex-direction: column;
1293 gap: 40px;
1294 flex: 1;
1295 }
1296
1297 .wpfnl-mm-build-funnel-field {
1298 display: flex;
1299 flex-direction: column;
1300 gap: 9px;
1301 }
1302
1303 .wpfnl-mm-build-funnel-label {
1304 font-size: 15px;
1305 font-weight: 500;
1306 font-family: 'DM Sans', sans-serif;
1307 color: #363B4E;
1308 letter-spacing: -0.44px;
1309 line-height: 16px;
1310 }
1311
1312 .wpfnl-mm-build-funnel-label .required {
1313 color: red;
1314 }
1315
1316 .wpfnl-mm-build-funnel-label .optional {
1317 font-weight: 400;
1318 color: #7A8C9A;
1319 }
1320
1321 /* Dropdown */
1322 .wpfnl-mm-build-funnel-dropdown {
1323 position: relative;
1324 }
1325
1326 .wpfnl-mm-build-funnel-dropdown-trigger {
1327 height: 46px;
1328 background: #FFF;
1329 border: 1px solid #F1EFF7;
1330 border-radius: 8px;
1331 padding: 0 16px;
1332 display: flex;
1333 align-items: center;
1334 justify-content: space-between;
1335 cursor: pointer;
1336 transition: all 0.3s ease;
1337 }
1338
1339 .wpfnl-mm-build-funnel-dropdown.open .wpfnl-mm-build-funnel-dropdown-trigger {
1340 border-color: #6E42D3;
1341 }
1342
1343 .wpfnl-mm-build-funnel-dropdown-trigger .placeholder {
1344 font-size: 14px;
1345 font-family: 'DM Sans', sans-serif;
1346 color: #7A8C9A;
1347 }
1348
1349 .wpfnl-mm-build-funnel-dropdown-trigger .selected-value {
1350 font-size: 14px;
1351 font-family: 'DM Sans', sans-serif;
1352 color: #363B4E;
1353 }
1354
1355 .wpfnl-mm-build-funnel-dropdown-trigger .dropdown-icon {
1356 transition: transform 0.3s ease;
1357 }
1358
1359 .wpfnl-mm-build-funnel-dropdown.open .dropdown-icon {
1360 transform: rotate(180deg);
1361 }
1362
1363 .wpfnl-mm-build-funnel-dropdown-menu {
1364 position: absolute;
1365 top: 100%;
1366 left: 0;
1367 right: 0;
1368 background: #FFF;
1369 border: 1px solid #F1EFF7;
1370 border-radius: 8px;
1371 margin-top: 4px;
1372 z-index: 100;
1373 box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
1374 overflow: hidden;
1375 }
1376
1377 /* Search Input */
1378 .wpfnl-mm-build-funnel-search {
1379 display: flex;
1380 align-items: center;
1381 gap: 10px;
1382 padding: 12px 16px;
1383 border-bottom: 1px solid #F1EFF7;
1384 }
1385
1386 .wpfnl-mm-build-funnel-search svg {
1387 flex-shrink: 0;
1388 }
1389
1390 .wpfnl-mm-build-funnel-search input {
1391 flex: 1;
1392 border: none;
1393 outline: none;
1394 font-size: 14px;
1395 font-family: 'DM Sans', sans-serif;
1396 color: #363B4E;
1397 background: transparent;
1398 }
1399
1400 .wpfnl-mm-build-funnel-search input::placeholder {
1401 color: #7A8C9A;
1402 }
1403
1404 .wpfnl-mm-build-funnel-dropdown-list {
1405 max-height: 200px;
1406 overflow-y: auto;
1407 }
1408
1409 .wpfnl-mm-build-funnel-dropdown-loading,
1410 .wpfnl-mm-build-funnel-dropdown-empty {
1411 padding: 16px;
1412 text-align: center;
1413 font-size: 14px;
1414 font-family: 'DM Sans', sans-serif;
1415 color: #7A8C9A;
1416 display: flex;
1417 flex-direction: column;
1418 align-items: center;
1419 justify-content: center;
1420 gap: 12px;
1421 }
1422
1423 .wpfnl-mm-build-funnel-dropdown-empty p {
1424 margin: 0;
1425 }
1426
1427 .wpfnl-mm-create-product-btn {
1428 display: inline-flex;
1429 align-items: center;
1430 gap: 8px;
1431 padding: 8px 16px;
1432 background: #FFF;
1433 border: 1px solid #6E42D3;
1434 border-radius: 6px;
1435 font-size: 13px;
1436 font-weight: 500;
1437 font-family: 'DM Sans', sans-serif;
1438 color: #6E42D3;
1439 cursor: pointer;
1440 transition: all 0.3s ease;
1441 }
1442
1443 .wpfnl-mm-create-product-btn:hover {
1444 background: #6E42D3;
1445 color: #FFF;
1446 }
1447
1448 .wpfnl-mm-create-product-btn:hover svg path {
1449 stroke: #FFF;
1450 }
1451
1452 .wpfnl-mm-create-product-btn svg {
1453 width: 12px;
1454 height: 12px;
1455 }
1456
1457 .wpfnl-mm-build-funnel-dropdown-item {
1458 padding: 12px 16px;
1459 font-size: 14px;
1460 font-family: 'DM Sans', sans-serif;
1461 color: #363B4E;
1462 cursor: pointer;
1463 display: flex;
1464 align-items: center;
1465 gap: 8px;
1466 transition: background 0.2s ease;
1467 }
1468
1469 .wpfnl-mm-build-funnel-dropdown-item:hover {
1470 background: #F6F5FA;
1471 }
1472
1473 .wpfnl-mm-build-funnel-dropdown-item.selected {
1474 background: #F6F5FA;
1475 color: #6E42D3;
1476 }
1477
1478 .wpfnl-mm-build-funnel-dropdown-item .product-name {
1479 flex: 1;
1480 }
1481
1482 .wpfnl-mm-build-funnel-dropdown-item .product-price {
1483 color: #6E42D3;
1484 font-weight: 500;
1485 }
1486
1487 .wpfnl-mm-build-funnel-hint {
1488 font-size: 12px;
1489 font-family: 'Inter', sans-serif;
1490 color: #7A8C9A;
1491 line-height: 16px;
1492 margin: 0;
1493 }
1494
1495 /* Potential Revenue */
1496 .wpfnl-mm-build-funnel-revenue {
1497 margin-top: auto;
1498 padding: 20px;
1499 background: #FFFFFF;
1500 border: 1px solid #ECEBF0;
1501 border-radius: 12px;
1502 }
1503
1504 .wpfnl-mm-revenue-header {
1505 margin-bottom: 16px;
1506 }
1507
1508 .wpfnl-mm-build-funnel-revenue-label {
1509 font-size: 13px;
1510 font-family: 'DM Sans', sans-serif;
1511 font-weight: 600;
1512 color: #363B4E;
1513 margin: 0;
1514 letter-spacing: -0.2px;
1515 }
1516
1517 .wpfnl-mm-revenue-breakdown {
1518 display: flex;
1519 flex-direction: column;
1520 gap: 10px;
1521 }
1522
1523 .wpfnl-mm-revenue-item {
1524 display: flex;
1525 justify-content: space-between;
1526 align-items: center;
1527 border: none;
1528 padding: 10px 0 0;
1529 border-radius: 8px;
1530 gap: 12px;
1531 }
1532
1533 .wpfnl-mm-revenue-item-bonus {
1534 background: #F6F5FA;
1535 }
1536
1537 .wpfnl-mm-revenue-item-content {
1538 display: flex;
1539 flex-direction: column;
1540 gap: 4px;
1541 flex: 1;
1542 min-width: 0;
1543 }
1544
1545 .wpfnl-mm-revenue-item-label {
1546 font-size: 11px;
1547 font-family: 'DM Sans', sans-serif;
1548 color: #7A8C9A;
1549 font-weight: 500;
1550 text-transform: uppercase;
1551 letter-spacing: 0.5px;
1552 display: flex;
1553 align-items: center;
1554 gap: 6px;
1555 }
1556
1557 .wpfnl-mm-revenue-item-name {
1558 font-size: 14px;
1559 font-family: 'DM Sans', sans-serif;
1560 color: #363B4E;
1561 font-weight: 500;
1562 white-space: nowrap;
1563 overflow: hidden;
1564 text-overflow: ellipsis;
1565 }
1566
1567 .wpfnl-mm-revenue-badge {
1568 display: inline-block;
1569 padding: 2px 6px;
1570 background: #6E42D3;
1571 color: #FFFFFF;
1572 font-size: 9px;
1573 font-weight: 600;
1574 border-radius: 3px;
1575 text-transform: uppercase;
1576 letter-spacing: 0.3px;
1577 }
1578
1579 .wpfnl-mm-revenue-item-price {
1580 font-size: 16px;
1581 font-family: 'DM Sans', sans-serif;
1582 font-weight: 600;
1583 color: #363B4E;
1584 flex-shrink: 0;
1585 }
1586
1587 .wpfnl-mm-revenue-bonus {
1588 color: #6E42D3;
1589 font-weight: 700;
1590 }
1591
1592 .wpfnl-mm-revenue-separator {
1593 height: 1px;
1594 background: #ECEBF0;
1595 margin: 6px 0;
1596 }
1597
1598 .wpfnl-mm-revenue-total {
1599 display: flex;
1600 justify-content: space-between;
1601 align-items: center;
1602 padding: 16px;
1603 background: #FFFFFF;
1604 border-radius: 8px;
1605 gap: 12px;
1606 }
1607
1608 .wpfnl-mm-revenue-total-content {
1609 display: flex;
1610 flex-direction: column;
1611 gap: 4px;
1612 flex: 1;
1613 }
1614
1615 .wpfnl-mm-revenue-total-label {
1616 font-size: 11px;
1617 font-family: 'DM Sans', sans-serif;
1618 font-weight: 500;
1619 color: #000;
1620 text-transform: uppercase;
1621 letter-spacing: 0.5px;
1622 }
1623
1624 .wpfnl-mm-revenue-total-value {
1625 font-size: 24px;
1626 font-family: 'DM Sans', sans-serif;
1627 font-weight: 700;
1628 color: #000;
1629 letter-spacing: -0.5px;
1630 }
1631
1632 .wpfnl-mm-revenue-boost-inline {
1633 display: flex;
1634 gap: 2px;
1635 border-radius: 6px;
1636 background: #33a6461a;
1637 flex-direction: row;
1638 height: auto;
1639 justify-content: center;
1640 align-items: center;
1641 padding: 16px 12px;
1642 line-height: 1;
1643 }
1644
1645 .wpfnl-mm-revenue-boost-inline svg {
1646 color: #33A646;
1647 width: 12px;
1648 height: 12px;
1649 }
1650
1651 .wpfnl-mm-revenue-boost-inline span {
1652 font-size: 12px;
1653 font-family: 'DM Sans', sans-serif;
1654 font-weight: 700;
1655 color: #33A646;
1656 letter-spacing: 0.4px;
1657 }
1658
1659 /* Tabs */
1660 .wpfnl-mm-build-funnel-tabs {
1661 display: flex;
1662 align-items: center;
1663 justify-content: center;
1664 padding: 20px 20px 0;
1665 gap: 0;
1666 flex-wrap: wrap;
1667 }
1668
1669 .wpfnl-mm-build-funnel-tab {
1670 display: flex;
1671 flex-direction: column;
1672 align-items: center;
1673 gap: 8px;
1674 cursor: pointer;
1675 flex-shrink: 0;
1676 }
1677
1678 .wpfnl-mm-build-funnel-tab-icon {
1679 width: 48px;
1680 height: 48px;
1681 border-radius: 50%;
1682 border: 1px solid #F1EFF7;
1683 display: flex;
1684 align-items: center;
1685 justify-content: center;
1686 color: #6E7A85;
1687 transition: all 0.3s ease;
1688 }
1689
1690 .wpfnl-mm-build-funnel-tab.active .wpfnl-mm-build-funnel-tab-icon {
1691 border: 1.5px solid #6E42D3;
1692 color: #6E42D3;
1693 box-shadow: 0px 20px 30px rgba(199, 191, 217, 0.3);
1694 }
1695
1696 .wpfnl-mm-build-funnel-tab-label {
1697 font-size: 12px;
1698 font-family: 'Inter', sans-serif;
1699 font-weight: 500;
1700 color: #6E7A85;
1701 text-align: center;
1702 white-space: nowrap;
1703 }
1704
1705 .wpfnl-mm-build-funnel-tab.active .wpfnl-mm-build-funnel-tab-label {
1706 color: #363B4E;
1707 }
1708
1709 .wpfnl-mm-build-funnel-tab-line {
1710 width: 16px;
1711 height: 1px;
1712 background: #E5E5E5;
1713 margin: 0 6px;
1714 margin-bottom: 24px;
1715 flex-shrink: 0;
1716 }
1717
1718 /* Preview Content */
1719 .wpfnl-mm-build-funnel-preview-content {
1720 flex: 1;
1721 padding: 20px;
1722 }
1723
1724
1725 .wpfnl-mm-build-funnel-preview-placeholder,
1726 .wpfnl-mm-build-funnel-preview-empty {
1727 display: flex;
1728 flex-direction: column;
1729 align-items: center;
1730 justify-content: center;
1731 height: 100%;
1732 min-height: 400px;
1733 color: #7A8C9A;
1734 text-align: center;
1735 padding: 40px;
1736 }
1737
1738 .wpfnl-mm-build-funnel-preview-placeholder svg,
1739 .wpfnl-mm-build-funnel-preview-empty svg {
1740 margin-bottom: 16px;
1741 opacity: 0.5;
1742 }
1743
1744 .wpfnl-mm-build-funnel-preview-placeholder p,
1745 .wpfnl-mm-build-funnel-preview-empty p {
1746 font-size: 14px;
1747 margin: 0;
1748 }
1749
1750 .wpfnl-mm-build-funnel-preview-empty h4 {
1751 font-size: 18px;
1752 font-weight: 600;
1753 color: #363B4E;
1754 margin: 0 0 8px 0;
1755 }
1756
1757 /* Buttons */
1758 .wpfnl-mm-build-funnel-buttons {
1759 display: flex;
1760 justify-content: space-between;
1761 align-items: center;
1762 width: 100%;
1763 }
1764
1765 .wpfnl-mm-btn {
1766 padding: 10px 24px;
1767 border-radius: 10px;
1768 font-size: 16px;
1769 font-weight: 700;
1770 font-family: 'DM Sans', sans-serif;
1771 line-height: 24px;
1772 cursor: pointer;
1773 transition: all 0.3s ease;
1774 border: none;
1775 display: inline-flex;
1776 align-items: center;
1777 justify-content: center;
1778 gap: 8px;
1779 height: 44px;
1780 }
1781
1782 .wpfnl-mm-btn-secondary {
1783 background: #FFF;
1784 color: #363B4E;
1785 border: 1px solid #ECEBF0;
1786 min-width: 87px;
1787 }
1788
1789 .wpfnl-mm-btn-secondary:hover {
1790 border-color: #6E42D3;
1791 color: #6E42D3;
1792 }
1793
1794 .wpfnl-mm-btn-skip {
1795 background: transparent;
1796 color: #7A8C9A;
1797 font-weight: 500;
1798 font-size: 15px;
1799 padding: 10px;
1800 }
1801
1802 .wpfnl-mm-btn-skip:hover {
1803 color: #6E42D3;
1804 }
1805
1806 .wpfnl-mm-btn-primary {
1807 background: #6E42D3;
1808 color: #FFF;
1809 font-weight: 600;
1810 letter-spacing: 0.2px;
1811 min-width: 146px;
1812 }
1813
1814 .wpfnl-mm-btn-primary svg {
1815 width: 17px;
1816 height: 12px;
1817 }
1818
1819 .wpfnl-mm-btn-primary:hover {
1820 background: #5c36b3;
1821 }
1822
1823 .wpfnl-mm-btn-primary:disabled {
1824 background: #ccc;
1825 cursor: not-allowed;
1826 }
1827
1828 .wpfnl-mm-btn-group {
1829 display: flex;
1830 gap: 10px;
1831 align-items: center;
1832 }
1833
1834 /* Spinner */
1835 @keyframes spin {
1836 from { transform: rotate(0deg); }
1837 to { transform: rotate(360deg); }
1838 }
1839
1840 .wpfnl-spinner {
1841 animation: spin 1s linear infinite;
1842 }
1843
1844 /* Help Button */
1845 .wpfnl-mm-help-btn {
1846 position: fixed;
1847 bottom: 40px;
1848 right: 54px;
1849 z-index: 100;
1850 }
1851
1852 .wpfnl-mm-help-btn-circle {
1853 width: 34px;
1854 height: 34px;
1855 border-radius: 100px;
1856 background: #201F22;
1857 border: none;
1858 cursor: pointer;
1859 display: flex;
1860 align-items: center;
1861 justify-content: center;
1862 box-shadow: 0px 1px 2px rgba(190, 190, 215, 0.16);
1863 transition: all 0.3s ease;
1864 }
1865
1866 .wpfnl-mm-help-btn-circle svg {
1867 width: 16px;
1868 height: 16px;
1869 }
1870
1871 .wpfnl-mm-help-btn-circle:hover {
1872 background: #363B4E;
1873 transform: scale(1.05);
1874 }
1875
1876 /* Create Product Modal */
1877 .wpfnl-modal-overlay {
1878 position: fixed;
1879 top: 0;
1880 left: 0;
1881 right: 0;
1882 bottom: 0;
1883 background: rgba(0, 0, 0, 0.5);
1884 display: flex;
1885 align-items: center;
1886 justify-content: center;
1887 z-index: 10000;
1888 }
1889
1890 .wpfnl-modal-wrapper {
1891 position: relative;
1892 width: 90%;
1893 max-width: 1400px;
1894 height: 90vh;
1895 background: #FFF;
1896 border-radius: 12px;
1897 overflow: hidden;
1898 box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.2);
1899 }
1900
1901 .wpfnl-modal-close {
1902 position: absolute;
1903 top: 16px;
1904 right: 16px;
1905 z-index: 10;
1906 }
1907
1908 .wpfnl-modal-close-btn {
1909 width: 32px;
1910 height: 32px;
1911 border-radius: 50%;
1912 background: #FFF;
1913 border: 1px solid #E5E5E5;
1914 display: flex;
1915 align-items: center;
1916 justify-content: center;
1917 cursor: pointer;
1918 transition: all 0.3s ease;
1919 }
1920
1921 .wpfnl-modal-close-btn:hover {
1922 background: #F6F5FA;
1923 border-color: #6E42D3;
1924 }
1925
1926 .wpfnl-modal-close-btn svg path {
1927 transition: stroke 0.3s ease;
1928 }
1929
1930 .wpfnl-modal-close-btn:hover svg path {
1931 stroke: #6E42D3;
1932 }
1933
1934 .wpfnl-loader {
1935 position: absolute;
1936 top: 50%;
1937 left: 50%;
1938 transform: translate(-50%, -50%);
1939 width: 40px;
1940 height: 40px;
1941 border: 4px solid #F6F5FA;
1942 border-top-color: #6E42D3;
1943 border-radius: 50%;
1944 animation: spin 1s linear infinite;
1945 }
1946
1947 #wpfnl-create-product {
1948 width: 100%;
1949 height: 100%;
1950 }
1951
1952 #wpfnl-create-product iframe {
1953 width: 100%;
1954 height: 100%;
1955 border: none;
1956 }
1957 </style>
1958