PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.12.11
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.12.11
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 / StoreCheckout.vue

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

695 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="wpfnl-store-checkout-step">
3
4 <!-- Phase: Select Template -->
5 <div v-if="phase === 'select'" class="wpfnl-mm-choose-template">
6 <div class="wpfnl-mm-setup-header">
7 <h2 class="wpfnl-mm-setup-title">Choose Your Store Checkout Template</h2>
8 <p class="wpfnl-mm-setup-subtitle">
9 Pick a ready-made checkout designed to reduce cart abandonment and boost conversions.
10 </p>
11 </div>
12
13 <div class="wpfnl-mm-templates-grid" v-if="!loading">
14 <div
15 v-for="template in templates"
16 :key="template.ID"
17 class="wpfnl-mm-template-card"
18 :class="{ 'wpfnl-mm-template-card--suggested': isSuggestedTemplate(template) }"
19 @click="showPreview(template)"
20 >
21 <div class="wpfnl-mm-template-card-preview">
22 <img :src="getCardImage(template)" :alt="template.title" v-if="getCardImage(template)">
23 <div class="wpfnl-mm-template-card-overlay">
24 <button class="wpfnl-mm-btn-preview" @click.stop="showPreview(template)">Preview</button>
25 </div>
26 <!-- Suggested badge -->
27 <div class="wpfnl-mm-template-card-suggested" v-if="isSuggestedTemplate(template)">
28 <svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">>
29 <path d="M5 1L6.18 3.4L8.8 3.78L6.9 5.64L7.36 8.25L5 6.99L2.64 8.25L3.1 5.64L1.2 3.78L3.82 3.4L5 1Z" fill="white" stroke="white" stroke-width="0.5" stroke-linejoin="round"/>
30 </svg>
31 Suggested
32 </div>
33 </div>
34 <div class="wpfnl-mm-template-card-divider"></div>
35 <div class="wpfnl-mm-template-card-body">
36 <h3 class="wpfnl-mm-template-card-title">{{ template.title }}</h3>
37 <span class="wpfnl-mm-template-card-steps" v-if="template.steps">{{ template.steps.length }} Steps</span>
38 </div>
39 </div>
40 </div>
41
42 <div v-else class="wpfnl-mm-templates-grid wpfnl-mm-templates-skeleton">
43 <div v-for="n in 6" :key="n" class="wpfnl-mm-template-card wpfnl-mm-template-card--skeleton">
44 <div class="wpfnl-mm-template-card-preview wpfnl-mm-skeleton-block"></div>
45 <div class="wpfnl-mm-template-card-divider"></div>
46 <div class="wpfnl-mm-template-card-body">
47 <div class="wpfnl-mm-skeleton-block wpfnl-mm-skeleton-title"></div>
48 <div class="wpfnl-mm-skeleton-block wpfnl-mm-skeleton-steps"></div>
49 </div>
50 </div>
51 </div>
52
53 <div class="wpfnl-mm-choose-goal-buttons wpfnl-mm-buttons-container wpfnl-sc-footer-actions">
54 <button class="wpfnl-mm-btn wpfnl-mm-btn-secondary" @click="goBack">
55 <svg width="17" height="12" viewBox="0 0 17 12" fill="none" xmlns="http://www.w3.org/2000/svg">>
56 <path d="M16 6H1M1 6L6 1M1 6L6 11" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
57 </svg>
58 Back
59 </button>
60 <button class="wpfnl-mm-btn wpfnl-mm-btn-skip" @click="skipCheckout">
61 Skip
62 </button>
63 </div>
64
65 <PreviewTemplate
66 v-if="showPreviewModal"
67 :template="previewTemplate"
68 :isStoreCheckout="true"
69 :isSalesFunnel="false"
70 @close="closePreview"
71 @import="importFromPreview"
72 />
73 </div>
74
75 <!-- Phase: Generating -->
76 <div v-else-if="phase === 'generating'" class="wpfnl-mm-setup">
77 <div class="wpfnl-mm-setup-header">
78 <h2 class="wpfnl-mm-setup-title">Setting Up Your Store Checkout</h2>
79 <p class="wpfnl-mm-setup-subtitle">Creating an optimized checkout experience for your store.</p>
80 </div>
81
82 <div class="wpfnl-generate-funnel-container generating">
83 <figure class="wpfnl-generate-funnel-canvas">
84 <img :src="generateStoreImage" alt="Store checkout setup" width="728" height="250" />
85 </figure>
86 <div class="generate-funnel-overlay">
87 <div class="generate-loader"></div>
88 <span class="generated-notice" v-if="statusMessage">
89 <span>{{ statusMessage }}</span>
90 </span>
91 <span class="generated-notice is-error" v-if="errorMessage">
92 {{ errorMessage }}
93 </span>
94 </div>
95 </div>
96 </div>
97
98 <!-- Phase: Complete (Celebration) -->
99 <div v-else-if="phase === 'complete'" class="wpfnl-store-celebration">
100 <canvas ref="confettiCanvas" class="wpfnl-confetti-canvas"></canvas>
101
102 <div class="wpfnl-celebration-content">
103 <div class="wpfnl-celebration-icon">
104 <svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">>
105 <circle cx="36" cy="36" r="36" fill="#EDE5FF"/>
106 <circle cx="36" cy="36" r="26" fill="#6E42D3"/>
107 <path d="M26 36L33 43L46 29" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
108 </svg>
109 </div>
110
111 <h2 class="wpfnl-celebration-title">Setup Complete! 🎉</h2>
112 <p class="wpfnl-celebration-subtitle">
113 <template v-if="skipped">
114 Your setup is complete. You can configure Store Checkout anytime from the WPFunnels settings.
115 </template>
116 <template v-else>
117 Your store checkout is ready. Start converting visitors into customers.
118 </template>
119 </p>
120
121 <div class="wpfnl-celebration-actions">
122 <button class="wpfnl-mm-btn wpfnl-mm-btn-secondary" @click="goToDashboard">
123 Go to Dashboard
124 </button>
125 <button v-if="!skipped" class="wpfnl-mm-btn wpfnl-mm-btn-primary" @click="goToEditor">
126 Go to Editor
127 <svg width="17" height="12" viewBox="0 0 17 12" fill="none" xmlns="http://www.w3.org/2000/svg">>
128 <path d="M1 6H16M16 6L11 1M16 6L11 11" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
129 </svg>
130 </button>
131 </div>
132 </div>
133 </div>
134
135 </div>
136 </template>
137
138 <script>
139 import apiFetch from '@wordpress/api-fetch';
140 import { addQueryArgs } from '@wordpress/url';
141 import PreviewTemplate from './PreviewTemplate.vue';
142
143 export default {
144 name: 'StoreCheckout',
145 components: { PreviewTemplate },
146 props: {
147 builder: {
148 type: String,
149 default: 'gutenberg'
150 },
151 prefetchedTemplates: {
152 type: Array,
153 default: () => []
154 },
155 agreeToShare: {
156 type: Boolean,
157 default: false
158 }
159 },
160 data() {
161 return {
162 phase: 'select',
163 skipped: false,
164 loading: true,
165 templates: [],
166 selectedTemplateId: null,
167 selectedTemplate: null,
168 showPreviewModal: false,
169 previewTemplate: null,
170 statusMessage: '',
171 errorMessage: '',
172 createdFunnelId: null,
173 generateStoreImage: window?.setup_wizard_obj?.generate_store_image || '',
174 };
175 },
176 mounted() {
177 const cached = this.prefetchedTemplates;
178 if (cached && cached.length > 0) {
179 this.templates = this.filterTemplates(cached);
180 this.loading = false;
181 } else {
182 this.fetchTemplates();
183 }
184 },
185 watch: {
186 prefetchedTemplates(val) {
187 if (!this.loading || !val || !val.length) return;
188 this.templates = this.filterTemplates(val);
189 this.loading = false;
190 },
191 },
192 methods: {
193 fetchTemplates() {
194 this.loading = true;
195 let builderParam = this.builder === 'divi' ? 'divi-builder' : (this.builder || 'gutenberg');
196
197 const path = addQueryArgs(
198 `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/templates/get_templates`,
199 { builder: builderParam, type: 'store_checkout' }
200 );
201
202 apiFetch({ path })
203 .then(response => {
204 if (response.success && response.templates && response.templates.length > 0) {
205 this.templates = this.filterTemplates(response.templates);
206 } else if (builderParam !== 'gutenberg') {
207 this.fetchFallback();
208 return;
209 }
210 })
211 .catch(() => {
212 if (this.builder !== 'gutenberg') this.fetchFallback();
213 })
214 .finally(() => { this.loading = false; });
215 },
216
217 fetchFallback() {
218 const path = addQueryArgs(
219 `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/templates/get_templates`,
220 { builder: 'gutenberg', type: 'store_checkout' }
221 );
222 apiFetch({ path })
223 .then(response => {
224 if (response.success && response.templates) {
225 this.templates = this.filterTemplates(response.templates);
226 }
227 })
228 .catch(() => {})
229 .finally(() => { this.loading = false; });
230 },
231
232 isSuggestedTemplate(template) {
233 return (template.title || '').toLowerCase().includes('instant checkout');
234 },
235
236 filterTemplates(templates) {
237 const filtered = templates
238 .filter(t => t.templateType !== 'pro')
239 .filter(t => {
240 if (!t.steps || !Array.isArray(t.steps)) return false;
241 const types = t.steps.map(s => s.step_type);
242 return types.includes('checkout') && types.includes('thankyou');
243 })
244 .map(t => {
245 const checkout = t.steps.find(s => s.step_type === 'checkout');
246 const thankyou = t.steps.find(s => s.step_type === 'thankyou');
247 return { ...t, steps: [checkout, thankyou].filter(Boolean) };
248 })
249 .sort((a, b) => a.ID - b.ID);
250
251 // Pin the suggested template first
252 const suggestedIdx = filtered.findIndex(t => this.isSuggestedTemplate(t));
253 if (suggestedIdx > 0) {
254 const [suggested] = filtered.splice(suggestedIdx, 1);
255 filtered.unshift(suggested);
256 }
257
258 return filtered;
259 },
260
261 getCardImage(template) {
262 if (template.steps) {
263 const checkoutStep = template.steps.find(s => s.step_type === 'checkout');
264 if (checkoutStep && checkoutStep.featured_image) return checkoutStep.featured_image;
265 }
266 return template.featured_image;
267 },
268
269 selectTemplate(template) {
270 this.selectedTemplateId = template.ID;
271 this.selectedTemplate = template;
272 },
273
274 showPreview(template) {
275 this.previewTemplate = template;
276 this.showPreviewModal = true;
277 },
278
279 closePreview() {
280 this.showPreviewModal = false;
281 this.previewTemplate = null;
282 },
283
284 importFromPreview(template) {
285 this.closePreview();
286 this.selectedTemplateId = template.ID || template.id;
287 this.selectedTemplate = template;
288 this.startImport();
289 },
290
291 startImport() {
292 if (!this.selectedTemplateId) return;
293 this.phase = 'generating';
294 this.$emit('store-checkout-phase', 'generating');
295 this.generateFunnel();
296 },
297
298 async generateFunnel() {
299 this.statusMessage = 'Configuring settings...';
300 this.errorMessage = '';
301
302 try {
303 await this.updateGeneralSettings('sales');
304
305 this.statusMessage = 'Creating store checkout...';
306 const funnelResponse = await this.createFunnel();
307 const funnelId = funnelResponse?.funnelID;
308
309 if (!funnelId) throw new Error('Funnel could not be created. Please try again.');
310
311 this.statusMessage = 'Importing template steps...';
312 const { importedSteps } = await this.importSteps(funnelId);
313
314 this.statusMessage = 'Finalizing...';
315 await this.afterFunnelCreation(funnelId, importedSteps);
316
317 this.createdFunnelId = funnelId;
318 this.maybeCreateContact();
319 this.handleCompleteTracking();
320 this.phase = 'complete';
321 this.$emit('store-checkout-phase', 'complete');
322 this.$nextTick(() => this.launchConfetti());
323 } catch (error) {
324 this.errorMessage = error?.message || 'Unable to generate the funnel. Please try again.';
325 this.statusMessage = '';
326 }
327 },
328
329 updateGeneralSettings(funnelType) {
330 return new Promise((resolve, reject) => {
331 wpAjaxHelperRequest('update-general-settings', {
332 funnel_type: funnelType,
333 builder: this.builder || 'gutenberg'
334 })
335 .success(() => resolve())
336 .error(err => reject(err));
337 });
338 },
339
340 createFunnel() {
341 const steps = this.filterStepsForImport(this.selectedTemplate?.steps || []);
342 const data = {
343 steps,
344 name: 'Store Checkout',
345 source: 'remote',
346 type: 'wc',
347 status: 'publish',
348 remoteID: this.selectedTemplate?.ID || this.selectedTemplate?.id,
349 is_store_checkout: true,
350 };
351
352 return new Promise((resolve, reject) => {
353 wpAjaxHelperRequest('wpfunnel-import-funnel', data)
354 .success(response => resolve(response || {}))
355 .error(err => reject(err));
356 });
357 },
358
359 filterStepsForImport(steps) {
360 return (steps || []).filter(step => {
361 const type = step?.step_type || step?.stepType || step?.type;
362 return type === 'checkout' || type === 'thankyou';
363 });
364 },
365
366 async importSteps(funnelId) {
367 const stepsToImport = this.filterStepsForImport(this.selectedTemplate?.steps || []);
368 const importedSteps = {};
369
370 for (const step of stepsToImport) {
371 try {
372 const response = await apiFetch({
373 path: `${window.setup_wizard_obj.rest_api_url}wpfunnels/v1/steps/wpfunnel-import-step`,
374 method: 'POST',
375 data: { step, funnelID: funnelId, source: 'remote', importType: 'templates' }
376 });
377
378 if (response?.stepID && step?.step_type) {
379 importedSteps[step.step_type] = response.stepID;
380 }
381 } catch (err) {
382 console.error(`Error importing step ${step?.step_type}:`, err);
383 }
384 }
385
386 if (!Object.keys(importedSteps).length) {
387 throw new Error('Unable to import the selected template steps.');
388 }
389
390 return { importedSteps };
391 },
392
393 afterFunnelCreation(funnelId, importedSteps) {
394 return new Promise(resolve => {
395 wpAjaxHelperRequest('wpfunnel-after-funnel-creation', {
396 funnelID: funnelId,
397 source: 'remote',
398 importedSteps: Object.values(importedSteps || {}),
399 goal: 'improve-checkout',
400 is_store_checkout: true,
401 })
402 .success(response => resolve(response))
403 .error(() => resolve());
404 });
405 },
406
407 handleCompleteTracking() {
408 const wizardObj = window.setup_wizard_obj || {};
409 const restApiUrl = wizardObj.rest_api_url || '';
410 if (!restApiUrl) return;
411
412 const normalizedUrl = restApiUrl.endsWith('/') ? restApiUrl : `${restApiUrl}/`;
413 apiFetch({
414 url: `${normalizedUrl}wpfunnels/v1/setup-wizard/complete-step`,
415 method: 'POST',
416 data: { funnelId: this.createdFunnelId, action: 'completed', goal: 'improve-checkout', total_steps: 3 },
417 }).catch(() => {});
418 },
419
420 maybeCreateContact() {
421 if (!this.agreeToShare) return;
422 const wizardObj = window.setup_wizard_obj || {};
423 if (!wizardObj.rest_api_url) return;
424
425 apiFetch({
426 path: `${wizardObj.rest_api_url}wpfunnels/v1/settings/create-contact/`,
427 method: 'POST',
428 data: { email: wizardObj.admin_email, name: wizardObj.admin_name },
429 }).catch(() => {});
430 },
431
432 async goToEditor() {
433 const wizardObj = window.setup_wizard_obj || {};
434 const adminUrl = wizardObj.admin_url || '';
435 window.location.href = `${adminUrl}admin.php?page=edit_funnel&id=${this.createdFunnelId}`;
436 },
437
438 async goToDashboard() {
439 const wizardObj = window.setup_wizard_obj || {};
440 window.location.href = wizardObj.dashboard_url || `${wizardObj.admin_url}admin.php?page=wpfunnels`;
441 },
442
443 skipCheckout() {
444 this.skipped = true;
445 this.phase = 'complete';
446 this.$emit('store-checkout-phase', 'skipped');
447 this.$nextTick(() => this.launchConfetti());
448 this.maybeCreateContact();
449 this.handleSkipTracking();
450 },
451
452 handleSkipTracking() {
453 const wizardObj = window.setup_wizard_obj || {};
454 const restApiUrl = wizardObj.rest_api_url || '';
455 if (!restApiUrl) return;
456
457 const normalizedUrl = restApiUrl.endsWith('/') ? restApiUrl : `${restApiUrl}/`;
458 apiFetch({
459 url: `${normalizedUrl}wpfunnels/v1/setup-wizard/complete-step`,
460 method: 'POST',
461 data: { action: 'skipped', goal: 'improve-checkout', total_steps: 3 },
462 }).catch(() => {});
463 },
464
465 goBack() {
466 this.$emit('prev-step');
467 },
468
469 launchConfetti() {
470 const canvas = this.$refs.confettiCanvas;
471 if (!canvas) return;
472
473 const ctx = canvas.getContext('2d');
474 canvas.width = window.innerWidth;
475 canvas.height = window.innerHeight;
476
477 const colors = ['#6E42D3', '#F59E0B', '#10B981', '#EF4444', '#3B82F6', '#EC4899', '#8B5CF6', '#F97316', '#06B6D4'];
478 const pieces = Array.from({ length: 220 }, () => ({
479 x: Math.random() * canvas.width,
480 y: Math.random() * -canvas.height * 0.5,
481 w: Math.random() * 12 + 5,
482 h: Math.random() * 6 + 3,
483 color: colors[Math.floor(Math.random() * colors.length)],
484 vy: Math.random() * 3.5 + 1.5,
485 vx: (Math.random() - 0.5) * 2.5,
486 angle: Math.random() * Math.PI * 2,
487 va: (Math.random() - 0.5) * 0.18,
488 opacity: 1,
489 }));
490
491 const startTime = Date.now();
492 const duration = 5000;
493
494 const draw = () => {
495 const elapsed = Date.now() - startTime;
496 if (elapsed > duration) {
497 ctx.clearRect(0, 0, canvas.width, canvas.height);
498 return;
499 }
500
501 ctx.clearRect(0, 0, canvas.width, canvas.height);
502
503 pieces.forEach(p => {
504 p.y += p.vy;
505 p.x += p.vx;
506 p.angle += p.va;
507 p.opacity = elapsed > 3500 ? Math.max(0, 1 - (elapsed - 3500) / 1500) : 1;
508
509 ctx.save();
510 ctx.globalAlpha = p.opacity;
511 ctx.translate(p.x + p.w / 2, p.y + p.h / 2);
512 ctx.rotate(p.angle);
513 ctx.fillStyle = p.color;
514 ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
515 ctx.restore();
516 });
517
518 requestAnimationFrame(draw);
519 };
520
521 draw();
522 },
523 }
524 };
525 </script>
526
527 <style scoped>
528 .wpfnl-store-checkout-step {
529 width: 100%;
530 display: contents;
531 }
532
533 /* Celebration Phase */
534 .wpfnl-store-celebration {
535 display: flex;
536 align-items: center;
537 justify-content: center;
538 width: 100%;
539 align-self: stretch;
540 position: relative;
541 }
542
543 .wpfnl-confetti-canvas {
544 position: fixed;
545 top: 0;
546 left: 0;
547 width: 100%;
548 height: 100%;
549 pointer-events: none;
550 z-index: 50;
551 }
552
553 .wpfnl-celebration-content {
554 display: flex;
555 flex-direction: column;
556 align-items: center;
557 text-align: center;
558 gap: 20px;
559 max-width: 520px;
560 padding: 60px 40px;
561 background: #FFFFFF;
562 border-radius: 24px;
563 box-shadow: 0 20px 60px rgba(110, 66, 211, 0.12), 0 8px 20px rgba(0, 0, 0, 0.06);
564 position: relative;
565 z-index: 100;
566 animation: celebrationPop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
567 }
568
569 @keyframes celebrationPop {
570 0% { transform: scale(0.85); opacity: 0; }
571 100% { transform: scale(1); opacity: 1; }
572 }
573
574 .wpfnl-celebration-icon {
575 animation: iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0.3s both;
576 }
577
578 @keyframes iconBounce {
579 0% { transform: scale(0) rotate(-20deg); opacity: 0; }
580 60% { transform: scale(1.15) rotate(5deg); }
581 100% { transform: scale(1) rotate(0deg); opacity: 1; }
582 }
583
584 .wpfnl-celebration-title {
585 font-size: 28px;
586 font-weight: 700;
587 font-family: 'DM Sans', sans-serif;
588 color: #363B4E;
589 margin: 0;
590 letter-spacing: -0.5px;
591 }
592
593 .wpfnl-celebration-subtitle {
594 font-size: 15px;
595 font-weight: 400;
596 font-family: 'DM Sans', sans-serif;
597 color: #6E7A85;
598 line-height: 1.6;
599 margin: 0;
600 }
601
602 .wpfnl-celebration-actions {
603 display: flex;
604 gap: 12px;
605 margin-top: 8px;
606 }
607
608 .wpfnl-celebration-actions .wpfnl-mm-btn {
609 padding: 12px 28px;
610 border-radius: 10px;
611 font-size: 15px;
612 font-weight: 600;
613 font-family: 'DM Sans', sans-serif;
614 cursor: pointer;
615 transition: all 0.3s ease;
616 border: none;
617 display: inline-flex;
618 align-items: center;
619 gap: 8px;
620 height: 48px;
621 }
622
623 .wpfnl-celebration-actions .wpfnl-mm-btn-secondary {
624 background: #F6F5FA;
625 color: #363B4E;
626 border: 1px solid #ECEBF0;
627 }
628
629 .wpfnl-celebration-actions .wpfnl-mm-btn-secondary:hover {
630 border-color: #6E42D3;
631 color: #6E42D3;
632 }
633
634 .wpfnl-celebration-actions .wpfnl-mm-btn-primary {
635 background: #6E42D3;
636 color: #FFFFFF;
637 }
638
639 .wpfnl-celebration-actions .wpfnl-mm-btn-primary:hover {
640 background: #5c36b3;
641 }
642
643 /* Footer actions row (Back + Skip) */
644 .wpfnl-sc-footer-actions {
645 display: flex;
646 align-items: center;
647 justify-content: space-between;
648 }
649
650 .wpfnl-mm-btn-skip {
651 background: transparent;
652 border: none;
653 color: #9CA3AF;
654 font-size: 14px;
655 font-weight: 500;
656 font-family: 'DM Sans', sans-serif;
657 cursor: pointer;
658 padding: 8px 16px;
659 border-radius: 8px;
660 transition: color 0.2s ease;
661 }
662
663 .wpfnl-mm-btn-skip:hover {
664 color: #6E42D3;
665 }
666
667 /* Generating phase styles */
668 .generate-funnel-overlay {
669 display: flex;
670 flex-direction: column;
671 align-items: center;
672 justify-content: center;
673 gap: 14px;
674 text-align: center;
675 }
676
677 .generate-loader {
678 order: 1;
679 }
680
681 .generated-notice {
682 display: inline-flex;
683 align-items: center;
684 gap: 8px;
685 color: #6E42D3;
686 font-size: 14px;
687 font-weight: 600;
688 order: 2;
689 }
690
691 .generated-notice.is-error {
692 color: #d14343;
693 }
694 </style>
695