PluginProbe
Extendify / 3.0.6
Extendify v3.0.6
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.0.6, at src/Launch/pages/CreatingSite.jsx

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