PluginProbe
Extendify / 3.1.0
Extendify v3.1.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Launch / pages / CreatingSite.jsx

CreatingSite.jsx in Extendify 3.1.0, at src/Launch/pages/CreatingSite.jsx

772 lines 22.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { Transition } from '@headlessui/react';
2 import { getPageTemplates } from '@launch/api/DataApi';
3 import { importTemporaryProducts } from '@launch/api/WooCommerce';
4 import {
5 addPageLinksToNav,
6 addSectionLinksToNav,
7 createNavigation,
8 getActivePlugins,
9 getOption,
10 getPageById,
11 installFontFamilies,
12 postLaunchFunctions,
13 prefetchAssistData,
14 updateNavAttributes,
15 updateOption,
16 updatePageTitlePattern,
17 updateTemplatePart,
18 } from '@launch/api/WPApi';
19 import { PagesSkeleton } from '@launch/components/CreatingSite/PageSkeleton';
20 import { useConfetti } from '@launch/hooks/useConfetti';
21 import { useSiteLogo } from '@launch/hooks/useSiteLogo';
22 import { useWarnOnLeave } from '@launch/hooks/useWarnOnLeave';
23 import {
24 updateButtonLinks,
25 updateSinglePageLinksToSections,
26 } from '@launch/lib/linkPages';
27 import { uploadLogo } from '@launch/lib/logo';
28 import {
29 addImprintPage,
30 createBlogSampleData,
31 createWpPages,
32 generateCustomPageContent,
33 replacePlaceholderPatterns,
34 setHelloWorldFeaturedImage,
35 updateGlobalStyleVariant,
36 updateNaturalVibeStyles,
37 } from '@launch/lib/wp';
38 import { usePagesStore } from '@launch/state/Pages';
39 import { usePagesSelectionStore } from '@launch/state/pages-selections';
40 import { useUserSelectionStore } from '@launch/state/user-selections';
41 import { Logo, Spinner } from '@launch/svg';
42 import { buildRecommendedPagesParams } from '@launch/utils/buildRecommendedPagesParams';
43 import { getPartnerPlugins, recordPluginActivity } from '@shared/api/DataApi';
44 import { activatePlugin, installPlugin } from '@shared/api/wp';
45 import { pageNames } from '@shared/lib/pages';
46 import {
47 deepMerge,
48 retryOperation,
49 waitFor200Response,
50 wasPluginInstalled,
51 } from '@shared/lib/utils';
52 import { useAIConsentStore } from '@shared/state/ai-consent';
53 import { dispatch, select } from '@wordpress/data';
54 import { useCallback, useEffect, useState } from '@wordpress/element';
55 import { __ } from '@wordpress/i18n';
56 import { colord } from 'colord';
57
58 const {
59 homeUrl,
60 adminUrl,
61 partnerLogo,
62 partnerName,
63 showImprint,
64 wpLanguage,
65 installedPluginsSlugs,
66 } = window.extSharedData;
67
68 export const CreatingSite = () => {
69 const [isShowing] = useState(true);
70 const [confettiReady, setConfettiReady] = useState(false);
71 const [confettiColors, setConfettiColors] = useState(['#ffffff']);
72 const [warnOnLeaveReady, setWarnOnLeaveReady] = useState(true);
73 const [loadAdmin, setLoadAdmin] = useState(false);
74 const {
75 siteType,
76 siteInformation,
77 siteStructure,
78 sitePlugins,
79 variation,
80 siteProfile,
81 siteStrings,
82 siteImages,
83 CTALink,
84 siteObjective,
85 siteQA,
86 urlParameters,
87 setSiteStrings,
88 } = useUserSelectionStore();
89 const { pages, style, removeAll, add } = usePagesSelectionStore();
90 const [info, setInfo] = useState([
91 __('Preparing to build your site', 'extendify-local'),
92 ]);
93 const [infoDesc, setInfoDesc] = useState([
94 // Because useSiteLogo causes a noticeable delay, we set this initial desc to match
95 __('Generating a site logo', 'extendify-local'),
96 ]);
97 const inform = (msg) => setInfo((info) => [msg, ...info]);
98 const informDesc = (msg) => setInfoDesc((infoDesc) => [msg, ...infoDesc]);
99 const [pagesToAnimate, setPagesToAnimate] = useState([]);
100 const { setPage } = usePagesStore();
101 const customFontFamilies =
102 variation?.settings?.typography?.fontFamilies?.custom;
103 const { setUserGaveConsent } = useAIConsentStore();
104 const { loading: logoLoading, logoUrl } = useSiteLogo();
105 const redirectUrl =
106 // on landing pages for some users, we redirect to home_url
107 (window.extOnbData?.redirectToWebsite &&
108 siteObjective === 'landing-page') ||
109 window.extSharedData?.showAIAgents
110 ? `${homeUrl}?extendify-launch-success=1`
111 : `${adminUrl}admin.php?page=extendify-assist&extendify-launch-success=1`;
112 const shouldLoadPages =
113 urlParameters?.skip?.includes('pages') && siteStructure === 'multi-page';
114 const [pagesLoaded, setPagesLoaded] = useState(false);
115
116 useWarnOnLeave(warnOnLeaveReady);
117
118 const loadRecommendedPages = useCallback(async () => {
119 const recommended = await getPageTemplates(buildRecommendedPagesParams());
120 removeAll('pages');
121 recommended.recommended.forEach((page) => {
122 add('pages', page);
123 });
124 setPagesLoaded(true);
125 }, [removeAll, add]);
126
127 useEffect(() => {
128 if (!shouldLoadPages) return;
129 if (pagesLoaded) return;
130 loadRecommendedPages().catch(console.error);
131 }, [shouldLoadPages, pagesLoaded, loadRecommendedPages]);
132
133 const doEverything = useCallback(async () => {
134 try {
135 const blogQuestion = siteQA?.questions?.find(
136 (question) => question.id === 'blog',
137 );
138 const hasBlogGoal = blogQuestion
139 ? (blogQuestion?.answerUser ?? blogQuestion?.answerAI) === 'yes'
140 : siteObjective === 'blog' || false;
141 const needsImprintPage = Array.isArray(showImprint)
142 ? showImprint.includes(wpLanguage ?? '') &&
143 siteProfile?.aiSiteCategory === 'Business'
144 : false;
145
146 await uploadLogo(logoUrl, { forceReplace: true });
147
148 await updateOption('permalink_structure', '/%postname%/');
149 await waitFor200Response();
150 inform(__('Applying your website styles', 'extendify-local'));
151 informDesc(__('Creating a beautiful website', 'extendify-local'));
152 await new Promise((resolve) => setTimeout(resolve, 1000));
153
154 // If they are launching the site, it means they agreed to the terms
155 setUserGaveConsent(true);
156
157 if (siteInformation.title) {
158 await updateOption('blogname', siteInformation.title);
159 }
160
161 await waitFor200Response();
162 // TODO: Refactor to assume 0default for site type
163 const siteTypeUpdated = {
164 ...(siteType ?? {}),
165 // Override with the ai site type if it exists
166 name: siteProfile?.aiSiteType ?? siteType.name,
167 };
168
169 await updateOption(
170 'extendify_siteType',
171 // Only persist the site type if the slug exists
172 siteType?.slug ? siteTypeUpdated : {},
173 );
174
175 await waitFor200Response();
176 // Install font families that are not in the theme.
177 if (customFontFamilies?.length) {
178 const installedFontFamilies =
179 await installFontFamilies(customFontFamilies);
180 await updateGlobalStyleVariant(
181 deepMerge(
182 variation,
183 // We set to null first to reset the field.
184 { settings: { typography: { fontFamilies: { custom: null } } } },
185 // We add the installed font families here to activate them.
186 {
187 settings: {
188 typography: {
189 fontFamilies: {
190 custom: installedFontFamilies.filter(Boolean),
191 },
192 },
193 },
194 },
195 ) ?? {},
196 );
197 } else {
198 await updateGlobalStyleVariant(variation);
199 }
200
201 await waitFor200Response();
202
203 const selectedVibe = style?.siteStyle?.vibe;
204 const selectedAnimation = style?.siteStyle?.animation;
205 if (selectedVibe && selectedVibe !== 'natural-1') {
206 inform(
207 // translators: "site style" refers to the structural aesthetic style for the site.
208 __('Applying site style', 'extendify-local'),
209 );
210 informDesc(
211 // translators: "site style" refers to the structural aesthetic style for the site.
212 __('Customizing your site style', 'extendify-local'),
213 );
214 await updateNaturalVibeStyles(selectedVibe);
215 await new Promise((resolve) => setTimeout(resolve, 1000));
216 }
217
218 const navigationId = await createNavigation();
219
220 let headerCode = updateNavAttributes(style?.headerCode, {
221 ref: navigationId,
222 });
223 if (siteObjective === 'landing-page') {
224 // remove the header navigation from the landing page
225 headerCode = headerCode
226 .replace(/<!--\s*wp:navigation\b[^>]*.*\/-->/gis, '')
227 .replace(
228 /<!--\s*wp:social-links\b[^>]*>.*?<!--\s*\/wp:social-links\s*-->/gis,
229 '',
230 );
231 }
232
233 let footerCode = style?.footerCode;
234 let footerNavigationId = null;
235 let footerNavPages = [];
236
237 if (needsImprintPage) {
238 footerNavigationId = await createNavigation(
239 'content',
240 __('Footer Navigation', 'extendify-local'),
241 'footer-navigation',
242 );
243 footerCode = updateNavAttributes(footerCode, {
244 ref: footerNavigationId,
245 });
246 }
247
248 await waitFor200Response();
249 await updateTemplatePart('extendable/header', headerCode);
250
251 await waitFor200Response();
252 await updateTemplatePart('extendable/footer', footerCode);
253
254 inform(__('Populating data', 'extendify-local'));
255 informDesc(__('Personalizing your experience', 'extendify-local'));
256 await prefetchAssistData();
257 await waitFor200Response();
258 await new Promise((resolve) => setTimeout(resolve, 1000));
259
260 // Store the site vibes, colorPalette and fonts
261 await updateOption(
262 'extendify_siteStyle',
263 style?.siteStyle || {
264 vibe: 'standard',
265 fonts: { heading: {}, body: {} },
266 colorPalette: null,
267 },
268 );
269
270 // Store animation settings if present
271 if (selectedAnimation) {
272 await waitFor200Response();
273 // Handle both string and object formats
274 const animationSettings =
275 typeof selectedAnimation === 'string'
276 ? { type: selectedAnimation, speed: 'medium' }
277 : selectedAnimation;
278 await updateOption('extendify_animation_settings', animationSettings);
279 }
280
281 // Add required plugins to the end of the list to give them lower priority
282 // when filtering out duplicates.
283 const sortedPlugins = [...sitePlugins]
284 // Remove duplicates
285 .reduce((acc, plugin) => {
286 const found = acc.find(
287 ({ wordpressSlug: s }) => s === plugin.wordpressSlug,
288 );
289 if (!found) acc.push(plugin);
290 return acc;
291 }, [])
292 // We add give to the front. See here why:
293 // https://github.com/extendify/company-product/issues/713
294 .sort(({ wordpressSlug }) => (wordpressSlug === 'give' ? -1 : 1));
295
296 if (sortedPlugins?.length) {
297 inform(__('Preparing site functionality', 'extendify-local'));
298 await new Promise((resolve) => setTimeout(resolve, 1000));
299
300 const pluginInstallMessages = [
301 __('Getting everything ready', 'extendify-local'),
302 __('Enhancing your site', 'extendify-local'),
303 __('Setting up essential tools', 'extendify-local'),
304 ];
305 informDesc(pluginInstallMessages?.at(-1));
306 for (const [index, plugin] of sortedPlugins.entries()) {
307 const slug = plugin?.wordpressSlug;
308 // Don't install if already installed
309 if (!installedPluginsSlugs?.includes(slug)) {
310 await retryOperation(() => installPlugin(slug), {
311 maxAttempts: 2,
312 }).catch(console.error);
313
314 recordPluginActivity({ slug, source: 'launch' });
315 if (index % 2 === 1) {
316 // skip first message
317 const i = Math.floor(index / 2) % pluginInstallMessages.length;
318 informDesc(pluginInstallMessages[i]);
319 }
320 }
321
322 await retryOperation(() => activatePlugin(slug), {
323 maxAttempts: 2,
324 }).catch(console.error);
325 }
326 }
327
328 const homePage = {
329 name: pageNames.home.title,
330 id: 'home',
331 patterns: style.patterns,
332 slug: 'home',
333 };
334 const blogPage = {
335 name: pageNames.blog.title,
336 id: 'blog',
337 patterns: [],
338 slug: 'blog',
339 };
340
341 await waitFor200Response();
342
343 inform(__('Adding page content', 'extendify-local'));
344 informDesc(__('Starting off with a full website', 'extendify-local'));
345 await new Promise((resolve) => setTimeout(resolve, 1000));
346 await waitFor200Response();
347
348 if (siteProfile.aiDescription) {
349 informDesc(__('Creating pages with custom content', 'extendify-local'));
350 [homePage, ...pages].forEach((page) => {
351 setPagesToAnimate((previous) => [...previous, page.name]);
352 });
353 }
354
355 const pagesWithoutPageTitlePattern = pages.map((page) => ({
356 ...page,
357 patterns: page.patterns.filter(
358 (p) => !p.patternTypes?.includes('page-title'),
359 ),
360 }));
361
362 // Update the page-with-title template with the selected page-title pattern
363 const firstPageTitlePattern = pages?.[0]?.patterns?.find((p) =>
364 p.patternTypes?.includes('page-title'),
365 );
366
367 const hasPageWithTitleTemplate = firstPageTitlePattern
368 ? await updatePageTitlePattern(firstPageTitlePattern.code)
369 : false;
370
371 const pagesToUse = hasPageWithTitleTemplate
372 ? pagesWithoutPageTitlePattern
373 : pages;
374
375 const pagesToCreate = [
376 ...pagesToUse,
377 homePage,
378 hasBlogGoal ? blogPage : null,
379 ].filter(Boolean);
380
381 const pagesWithReplacedPatterns = [];
382 // Run these one page at a time so we don't end up with duplicate dependency issues
383 for (const page of pagesToCreate) {
384 const updatedPage = {
385 ...page,
386 patterns: await replacePlaceholderPatterns(page.patterns),
387 };
388 pagesWithReplacedPatterns.push(updatedPage);
389 }
390
391 const pagesWithCustomContent = await generateCustomPageContent(
392 pagesWithReplacedPatterns,
393 {
394 sitePlugins,
395 siteType: siteTypeUpdated.name,
396 siteInformation,
397 },
398 siteProfile,
399 );
400
401 const createdHomePage = pagesWithCustomContent.find(
402 (p) => p.slug === 'home',
403 );
404 const heroPattern = createdHomePage?.patterns?.find((p) =>
405 p.patternTypes?.includes('hero-header'),
406 );
407 const pMatch = heroPattern?.code?.match(/<p[^>]*>([\s\S]*?)<\/p>/);
408 const heroDesc = pMatch?.[1]?.replace(/<[^>]+>/g, '').trim();
409 setSiteStrings({
410 ...siteStrings,
411 heroDescription: heroDesc || siteStrings?.heroDescription,
412 });
413
414 const createdPages = await createWpPages(pagesWithCustomContent, {
415 stickyNav:
416 siteStructure === 'single-page' && siteObjective !== 'landing-page',
417 });
418
419 const hasBlogPattern = homePage?.patterns?.some((pattern) =>
420 pattern.patternTypes.includes('blog-section'),
421 );
422
423 if (hasBlogGoal || hasBlogPattern) {
424 informDesc(__('Creating blog sample data', 'extendify-local'));
425 await createBlogSampleData(siteStrings, siteImages);
426 }
427
428 await waitFor200Response();
429 if (siteImages?.siteImages) {
430 await setHelloWorldFeaturedImage(siteImages.siteImages);
431 }
432
433 if (needsImprintPage) {
434 informDesc(__('Adding imprint page', 'extendify-local'));
435 const createdImprintPage = await addImprintPage(style?.siteStyle);
436 if (createdImprintPage) {
437 createdPages.push(createdImprintPage);
438 footerNavPages = [
439 {
440 name: createdImprintPage.title.rendered,
441 slug: createdImprintPage.originalSlug,
442 id: createdImprintPage.originalSlug,
443 patterns: [],
444 },
445 ];
446 }
447 }
448
449 setPagesToAnimate([]);
450 await waitFor200Response();
451 informDesc(__('Setting up site layout', 'extendify-local'));
452
453 const navPagesMultiPageSite = [
454 ...pages,
455 hasBlogGoal ? blogPage : null,
456 homePage,
457 ].filter(Boolean);
458
459 const pluginPages = [];
460
461 // Fetch active plugins after installing plugins
462 const { data: activePlugins } = await getActivePlugins();
463
464 // Add plugin related pages only if plugin is active
465 if (wasPluginInstalled(activePlugins, 'woocommerce')) {
466 const shopPageId = await getOption('woocommerce_shop_page_id');
467 const shopPage = shopPageId
468 ? await getPageById(shopPageId).catch(() => null)
469 : null;
470
471 if (shopPage) {
472 pluginPages.push(shopPage);
473 }
474
475 informDesc(__('Importing shop sample data', 'extendify-local'));
476 try {
477 await importTemporaryProducts();
478 } catch (e) {
479 console.error('Error importing temporary products', e);
480 }
481
482 // If we installed any plugins above, and a partner has supported plugins
483 // linked to those plugins, we should install them here. For example:
484 // A German specific WooCommerce plugin in case WooCommerce is installed.
485 const partnerPlugins = await getPartnerPlugins('products').catch(
486 () => null,
487 );
488
489 if (partnerPlugins) {
490 informDesc(__('Installing supporting plugins', 'extendify-local'));
491 for (const plugin of partnerPlugins) {
492 if (!wasPluginInstalled(activePlugins, plugin)) {
493 const maxAttempts = 2;
494 await retryOperation(() => installPlugin(plugin), {
495 maxAttempts,
496 }).catch(console.error);
497
498 recordPluginActivity({ slug: plugin, source: 'launch' });
499
500 await retryOperation(() => activatePlugin(plugin), {
501 maxAttempts,
502 }).catch(console.error);
503 }
504 }
505 }
506 }
507
508 if (wasPluginInstalled(activePlugins, 'the-events-calendar')) {
509 const eventsPage = {
510 title: {
511 rendered: __('Events', 'extendify-local'),
512 },
513 slug: 'events',
514 link: `${homeUrl}/events`,
515 };
516
517 pluginPages.push(eventsPage);
518 }
519
520 if (wasPluginInstalled(activePlugins, 'wpforms-lite')) {
521 await updateOption('wpforms_activation_redirect', 'skip');
522 }
523
524 if (wasPluginInstalled(activePlugins, 'all-in-one-seo-pack')) {
525 await updateOption('aioseo_activation_redirect', 'skip');
526 }
527
528 if (wasPluginInstalled(activePlugins, 'google-analytics-for-wordpress')) {
529 await updateOption(
530 '_transient__monsterinsights_activation_redirect',
531 null,
532 );
533 }
534
535 const pagesWithLinksUpdated =
536 siteStructure === 'single-page'
537 ? await updateSinglePageLinksToSections(
538 createdPages,
539 pagesWithCustomContent,
540 {
541 linkOverride: CTALink,
542 siteObjective,
543 },
544 )
545 : await updateButtonLinks(createdPages, pluginPages);
546
547 if (siteObjective !== 'landing-page') {
548 if (siteStructure === 'single-page') {
549 await addSectionLinksToNav(
550 navigationId,
551 homePage?.patterns,
552 pluginPages,
553 createdPages,
554 );
555 } else {
556 await addPageLinksToNav(
557 navigationId,
558 navPagesMultiPageSite,
559 pagesWithLinksUpdated,
560 pluginPages,
561 );
562 }
563 if (footerNavigationId) {
564 await addPageLinksToNav(
565 footerNavigationId,
566 footerNavPages,
567 pagesWithLinksUpdated,
568 [],
569 );
570 }
571 }
572
573 await waitFor200Response();
574
575 const renderingModes =
576 select('core/preferences').get('core', 'renderingModes') || {};
577
578 if (renderingModes?.extendable?.page !== 'template-locked') {
579 dispatch('core/preferences').set('core', 'renderingModes', {
580 ...renderingModes,
581 extendable: {
582 ...(renderingModes.extendable || {}),
583 page: 'template-locked',
584 },
585 });
586 }
587
588 inform(__('Setting up your Site Assistant', 'extendify-local'));
589 informDesc(__('Helping you to succeed', 'extendify-local'));
590 await new Promise((resolve) => setTimeout(resolve, 1000));
591 await waitFor200Response();
592 inform(__('Your website has been created!', 'extendify-local'));
593 informDesc(__('Redirecting in 3, 2, 1...', 'extendify-local'));
594 // fire confetti here
595 setConfettiReady(true);
596 setWarnOnLeaveReady(false);
597 await new Promise((resolve) => setTimeout(resolve, 2500));
598
599 await waitFor200Response();
600 } catch (e) {
601 console.error(e);
602 // if the error is 4xx, we should stop trying and prompt them to reload
603 if (e.status >= 400 && e.status < 500) {
604 setWarnOnLeaveReady(false);
605 const alertMsg = __(
606 'We encountered a server error we cannot recover from. Please reload the page and try again.',
607 'extendify-local',
608 );
609 alert(alertMsg);
610 location.href = adminUrl;
611 }
612 await new Promise((resolve) => setTimeout(resolve, 2000));
613 return doEverything();
614 }
615 }, [
616 pages,
617 style,
618 siteType,
619 siteInformation,
620 setPagesToAnimate,
621 siteStructure,
622 variation,
623 siteProfile,
624 siteStrings,
625 siteImages,
626 customFontFamilies,
627 setUserGaveConsent,
628 siteObjective,
629 CTALink,
630 logoUrl,
631 sitePlugins,
632 siteQA,
633 setSiteStrings,
634 ]);
635
636 useEffect(() => {
637 if (logoLoading) return;
638 if (shouldLoadPages && !pagesLoaded) return;
639 doEverything().then(async () => {
640 setPage(0);
641 // This will trigger the post launch php functions.
642 await postLaunchFunctions();
643 // This loads the admin in the background to run php functions in admin context
644 setLoadAdmin(true);
645 await waitFor200Response();
646 window.location.replace(redirectUrl);
647 });
648 }, [
649 doEverything,
650 setPage,
651 logoLoading,
652 redirectUrl,
653 shouldLoadPages,
654 pagesLoaded,
655 ]);
656
657 useEffect(() => {
658 const documentStyles = window.getComputedStyle(document.body);
659 const partnerBg = documentStyles?.getPropertyValue('--ext-banner-main');
660 const partnerText = documentStyles?.getPropertyValue('--ext-banner-text');
661 if (partnerBg) {
662 setConfettiColors([
663 colord(partnerBg).darken(0.3).toHex(),
664 colord(partnerText).alpha(0.5).toHex(),
665 colord(partnerBg).lighten(0.2).toHex(),
666 ]);
667 }
668 }, []);
669
670 useConfetti(
671 {
672 particleCount: 3,
673 angle: 320,
674 spread: 220,
675 origin: { x: 0, y: 0 },
676 colors: confettiColors,
677 },
678 2500,
679 confettiReady,
680 );
681
682 return (
683 <>
684 <Transition
685 as="div"
686 show={isShowing}
687 appear={true}
688 enter="transition-all ease-in-out duration-500"
689 enterFrom="md:w-40vw md:max-w-md"
690 enterTo="md:w-full md:max-w-full"
691 className="flex shrink-0 flex-col justify-between bg-banner-main px-10 py-12 text-banner-text md:h-screen"
692 >
693 <div className="max-w-prose">
694 <div className="md:min-h-48">
695 {partnerLogo ? (
696 <div className="mb-8">
697 <img
698 style={{ maxWidth: '200px' }}
699 src={partnerLogo}
700 alt={partnerName ?? ''}
701 />
702 </div>
703 ) : (
704 <Logo className="logo mb-8 w-32 text-banner-text sm:w-40" />
705 )}
706 <div data-test="message-area">
707 {info.map((step, index) => {
708 if (index) return null;
709 return (
710 <Transition
711 as="div"
712 appear={true}
713 show={isShowing}
714 enter="transition-opacity duration-1000"
715 enterFrom="opacity-0"
716 enterTo="opacity-100"
717 leave="transition-opacity duration-1000"
718 leaveFrom="opacity-100"
719 leaveTo="opacity-0"
720 className="flex items-center space-x-4 text-4xl"
721 key={step}
722 >
723 {step}
724 </Transition>
725 );
726 })}
727 <div className="mt-6 flex items-center space-x-4">
728 <Spinner className="spin rtl:ml-3" />
729 {infoDesc.map((step, index) => {
730 if (index) return null;
731 return (
732 <Transition
733 as="div"
734 appear={true}
735 show={isShowing}
736 enter="transition-opacity duration-1000"
737 enterFrom="opacity-0"
738 enterTo="opacity-100"
739 leave="transition-opacity duration-1000"
740 leaveFrom="opacity-100"
741 leaveTo="opacity-0"
742 className="text-lg"
743 key={step}
744 >
745 {step}
746 </Transition>
747 );
748 })}
749 </div>
750 {pagesToAnimate.length > 0 ? (
751 <PagesSkeleton pages={pagesToAnimate} />
752 ) : null}
753 </div>
754 </div>
755 </div>
756 </Transition>
757 {loadAdmin ? <AdminLoader /> : null}
758 </>
759 );
760 };
761
762 // iframe that loads the admin in the background to make sure
763 // all php functions that require admin context work properly.
764 const AdminLoader = () => (
765 <iframe
766 title="Admin Loader"
767 src={adminUrl}
768 style={{ display: 'none' }}
769 sandbox="allow-same-origin allow-scripts allow-forms"
770 />
771 );
772