PluginProbe
Extendify / trunk
Extendify vtrunk
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
← All changes | src/AutoLaunch/hooks/useCreateSite.js +117 -81 3.1.1 → trunk View file →
@@ -19,11 +19,13 @@
19 19 updateSinglePageLinksToSections,
20 20 } from '@auto-launch/functions/links';
21 21 import {
22 22 addPageLinksToNav,
23 + addSectionLinksFromDesign,
23 24 addSectionLinksToNav,
24 25 createNavigation,
25 26 injectNavExtras,
27 + injectWooCommerceIcons,
26 28 updateNavAttributes,
27 29 } from '@auto-launch/functions/nav';
28 30 import {
29 31 addImprintPage,
@@ -28,8 +30,9 @@
28 30 import {
29 31 addImprintPage,
30 32 createWpPages,
31 33 getPagesToCreate,
34 + isBlogPage,
32 35 PLUGIN_OWNED_PAGES,
33 36 setHelloWorldFeaturedImage,
34 37 updatePageTitlePattern,
35 38 } from '@auto-launch/functions/pages';
@@ -34,24 +37,26 @@
34 37 updatePageTitlePattern,
35 38 } from '@auto-launch/functions/pages';
36 39 import { generatePageContent } from '@auto-launch/functions/patterns';
37 40 import {
38 - activatePlugin,
39 41 alreadyActive,
42 + ensurePluginsActive,
40 43 getActivePlugins,
41 - installPlugin,
42 44 replacePlaceholderPatterns,
45 + reportFailedDependencies,
46 + reportInactivePlugins,
43 47 } from '@auto-launch/functions/plugins';
44 48 import {
45 49 postLaunchFunctions,
46 50 prefetchAssistData,
47 51 } from '@auto-launch/functions/setup';
52 +import { applySocialProfiles } from '@auto-launch/functions/social-links';
48 53 import {
49 54 setThemeRenderingMode,
50 55 updateTemplatePart,
51 56 updateVariation,
52 57 } from '@auto-launch/functions/theme';
53 -import { computeVibeAdjustedBlocks } from '@auto-launch/functions/vibes';
58 +import { computeVibeAdjustments } from '@auto-launch/functions/vibes';
54 59 import {
55 60 createBlogSampleData,
56 61 getOption,
57 62 getPageById,
@@ -59,21 +64,21 @@
59 64 updateOption,
60 65 } from '@auto-launch/functions/wp';
61 66 import { useWarnOnLeave } from '@auto-launch/hooks/useWarnOnLeave';
62 67 import { useLaunchDataStore } from '@auto-launch/state/launch-data';
68 +import { launchStrings } from '@auto-launch/strings';
63 69 import { digest } from '@shared/api/digest';
70 +import { siteImageUrls } from '@shared/lib/site-images';
64 71 import { useAIConsentStore } from '@shared/state/ai-consent';
65 72 import { useEffect, useRef, useState } from '@wordpress/element';
66 73 import { __ } from '@wordpress/i18n';
67 74 import useSWRImmutable from 'swr/immutable';
68 75
69 -const { homeUrl, showImprint, wpLanguage, installedPluginsSlugs } =
70 - window.extSharedData;
76 +const { homeUrl, showImprint, wpLanguage } = window.extSharedData;
71 77
72 78 // TODO: I think a good strategy is "if something fails, try to refetch some state"
73 79
74 80 export const useCreateSite = () => {
75 - // All the data we need to finish
76 81 const { setErrorMessage, addStatusMessage, needToStall, setData, ...data } =
77 82 useLaunchDataStore();
78 83 const { setUserGaveConsent } = useAIConsentStore();
79 84 const homeStretch = useRef(false);
@@ -115,8 +120,11 @@
115 120 useRunStep(
116 121 'siteLogo',
117 122 () => {
118 123 if (!data.siteProfile?.title) return null;
124 + // Logo was already uploaded by handleDesignBuild;
125 + // generating an AI logo here is not necessary.
126 + if (data.designBuild?.logoUrl) return null;
119 127 return data;
120 128 },
121 129 async (params) => {
122 130 checkIn({ stage: 'get_logo' });
@@ -124,13 +132,12 @@
124 132 },
125 133 );
126 134
127 135 // needs: siteProfile
128 - // provides:sitePlugins: [{name, wordpressSlug}]
136 + // provides: sitePlugins: [{name, wordpressSlug}]
129 137 useRunStep(
130 138 'sitePlugins',
131 139 () => {
132 - // We just need the site profile, which has this
133 140 if (!data.siteProfile?.title) return null;
134 141 return data;
135 142 },
136 143 async (params) => {
@@ -144,19 +151,13 @@
144 151 if (!data.sitePlugins?.length) return;
145 152
146 153 setStatus(
147 154 // translators: this is for a action log UI. Keep it short
148 - __('Setting up functionality for your website', 'extendify-local'),
155 + launchStrings().statusFunctionality,
149 156 );
150 - (async function install() {
151 - for (const { wordpressSlug: slug } of data.sitePlugins) {
152 - let plugin;
153 - if (!installedPluginsSlugs?.includes(slug)) {
154 - plugin = await installPlugin(slug);
155 - }
156 - await activatePlugin(plugin?.plugin ?? slug);
157 - }
158 - })();
157 + ensurePluginsActive(
158 + data.sitePlugins.map(({ wordpressSlug }) => wordpressSlug),
159 + );
159 160 }, [data.sitePlugins]);
160 161
161 162 // needs: siteProfile
162 163 // provides: style: {}
@@ -162,9 +163,8 @@
162 163 // provides: style: {}
163 164 useRunStep(
164 165 'siteStyle',
165 166 () => {
166 - // We just need the site profile, which has this
167 167 if (!data.siteProfile?.title) return null;
168 168 return data;
169 169 },
170 170 async (params) => {
@@ -177,9 +177,8 @@
177 177 // provides: aiHeaders: [], aiBlogTitles: []
178 178 useRunStep(
179 179 'siteStrings',
180 180 () => {
181 - // We just need the site profile, which has this
182 181 if (!data.siteProfile?.title) return null;
183 182 return data;
184 183 },
185 184 async (params) => {
@@ -192,9 +191,8 @@
192 191 // provides: siteImages: []
193 192 useRunStep(
194 193 'siteImages',
195 194 () => {
196 - // We just need the site profile, which has this
197 195 if (!data.siteProfile?.title) return null;
198 196 return data;
199 197 },
200 198 async (params) => {
@@ -221,9 +219,8 @@
221 219 // provides: home: { id, slug, patterns, siteStyle }
222 220 useRunStep(
223 221 'home',
224 222 () => {
225 - // Checking various data from calls above
226 223 const ok = [
227 224 data.siteProfile,
228 225 data.siteStyle,
229 226 data.siteImages,
@@ -237,14 +234,13 @@
237 234 return await handleHome(params);
238 235 },
239 236 );
240 237
241 - // siteProfile, sitePlugins, siteStyle, siteImages
238 + // needs: siteProfile, sitePlugins, siteStyle, siteImages
242 239 // provides: pages: [{ id, slug, name, patterns, siteStyle }]
243 240 useRunStep(
244 241 'pages',
245 242 () => {
246 - // Checking various data from calls above
247 243 const ok = [
248 244 data.siteProfile,
249 245 data.siteStyle,
250 246 data.siteImages,
@@ -271,14 +267,11 @@
271 267 checkIn({ stage: 'set_general', siteProfile, sitePlugins, siteStyle });
272 268
273 269 const { title } = siteProfile;
274 270 // translators: this is for a action log UI. Keep it short
275 - addStatusMessage(__('Adding admin configurations', 'extendify-local'));
276 - // update permalinks
271 + addStatusMessage(launchStrings().statusAdmin);
277 272 await updateOption('permalink_structure', '/%postname%/');
278 - // make sure consent is set
279 273 setUserGaveConsent(true);
280 - // Update title
281 274 if (title) await updateOption('blogname', title);
282 275 },
283 276 );
284 277
@@ -324,10 +317,23 @@
324 317 if (homeStretch.current) return;
325 318 homeStretch.current = true;
326 319 (async () => {
327 320 const { objective, structure, category } = siteProfile;
321 + const builtHome = designBuild?.builtPages?.find((p) => p.slug === 'home');
322 + // Must match get-home.js's full-page test; if they drift, the home is
323 + // built one way and planted another.
324 + const isSinglePageDesign =
325 + structure === 'single-page' &&
326 + Boolean(builtHome?.fullPage && builtHome.patterns?.length) &&
327 + Boolean(designBuild?.pages?.length);
328 + const imageUrls = siteImageUrls(siteImages);
329 + const intendedPlugins = (sitePlugins ?? []).map(
330 + ({ wordpressSlug }) => wordpressSlug,
331 + );
328 332
329 - // Do they need an imprint page?
333 + // Guarantee plugins are active before the pattern imports below rely on them.
334 + await ensurePluginsActive(intendedPlugins);
335 +
330 336 const needsImprint = Array.isArray(showImprint)
331 337 ? showImprint.includes(wpLanguage ?? '') && category === 'Business'
332 338 : false;
333 339
@@ -336,9 +342,9 @@
336 342 let variation = siteStyle?.variation;
337 343 if (customFonts?.length) {
338 344 checkIn({ stage: 'install_fonts' });
339 345 // translators: this is for a action log UI. Keep it short
340 - addStatusMessage(__('Installing fonts locally', 'extendify-local'));
346 + addStatusMessage(launchStrings().statusFonts);
341 347 const installed = await installFontFamilies(customFonts).catch(
342 348 () => [],
343 349 );
344 350 variation = mergeFontsIntoVariation(siteStyle.variation, installed);
@@ -345,19 +351,15 @@
345 351 }
346 352
347 353 if (siteStyle?.vibe && siteStyle.vibe !== 'natural-1') {
348 354 // translators: vibe in this context is a noun - the feeling of their site design.
349 - addStatusMessage(__('Setting the website style', 'extendify-local'));
355 + addStatusMessage(launchStrings().statusSiteStyle);
350 356 checkIn({ stage: 'compute_vibe' });
351 - const vibeBlocks = await computeVibeAdjustedBlocks(
357 + const vibe = await computeVibeAdjustments(
352 358 siteStyle.vibe,
359 + variation,
353 360 ).catch(() => null);
354 - if (vibeBlocks) {
355 - variation = {
356 - ...variation,
357 - styles: { ...variation.styles, blocks: vibeBlocks },
358 - };
359 - }
361 + if (vibe) variation = { ...variation, ...vibe };
360 362 }
361 363
362 364 checkIn({ stage: 'set_vibe' });
363 365 await updateVariation(variation);
@@ -362,9 +364,9 @@
362 364 checkIn({ stage: 'set_vibe' });
363 365 await updateVariation(variation);
364 366
365 367 // navigation menu
366 - addStatusMessage(__('Working on the navigation', 'extendify-local'));
368 + addStatusMessage(launchStrings().statusNavigation);
367 369 const { id: headerNavId } = await createNavigation({
368 370 title: __('Header Navigation', 'extendify-local'),
369 371 slug: 'site-navigation',
370 372 });
@@ -374,9 +376,9 @@
374 376 headerCode = injectNavExtras(headerCode, launchDecisions);
375 377 // remove the header navigation from the landing page
376 378 if (objective === 'landing-page') {
377 379 // translators: this is for a action log UI. Keep it short
378 - addStatusMessage(__('Perfecting a landing page', 'extendify-local'));
380 + addStatusMessage(launchStrings().statusLanding);
379 381 const social =
380 382 /<!--\s*wp:social-links\b[^>]*>.*?<!--\s*\/wp:social-links\s*-->/gis;
381 383 headerCode = headerCode
382 384 .replace(/<!--\s*wp:navigation\b[^>]*.*\/-->/gis, '')
@@ -393,8 +395,15 @@
393 395
394 396 // footer
395 397 let footerNavId = null;
396 398 let footerCode = home.footerCode || '';
399 + // The logo already carries the brand, so the site title would be redundant.
400 + if (designBuild?.hasExternalLogo && footerCode.includes('wp:site-logo')) {
401 + footerCode = footerCode.replace(
402 + /\s*<!--\s*wp:site-title[\s\S]*?\/-->/g,
403 + '',
404 + );
405 + }
397 406 if (needsImprint) {
398 407 const nav = await createNavigation({
399 408 title: __('Footer Navigation', 'extendify-local'),
400 409 slug: 'footer-navigation',
@@ -401,14 +410,15 @@
401 410 });
402 411 footerNavId = nav.id;
403 412 footerCode = updateNavAttributes(footerCode, { ref: footerNavId });
404 413 }
414 + footerCode = applySocialProfiles(footerCode, siteProfile.socialProfiles);
405 415 checkIn({ stage: 'set_footer' });
406 416 await updateTemplatePart('extendable/footer', footerCode);
407 417
408 418 // pages
409 419 // translators: this is for a action log UI. Keep it short
410 - addStatusMessage(__('Creating pages', 'extendify-local'));
420 + addStatusMessage(launchStrings().statusCreatingPages);
411 421 const pagesToCreate = getPagesToCreate(data);
412 422 const titlePattern = pages?.[0]?.patterns?.find((p) =>
413 423 p.patternTypes?.includes('page-title'),
414 424 );
@@ -417,9 +427,9 @@
417 427 await updatePageTitlePattern(titlePattern.code);
418 428 }
419 429
420 430 const activePlugins = await getActivePlugins();
421 - // This lets us keep plugin pages in th enav but skip making the page
431 + // Keep plugin pages in the nav but skip creating the page itself.
422 432 const reservedSlugs = new Set(
423 433 PLUGIN_OWNED_PAGES.filter(
424 434 ({ plugin }) =>
425 435 sitePlugins.some((p) => p.wordpressSlug === plugin) ||
@@ -450,25 +460,36 @@
450 460 p.patternTypes?.includes('hero-header'),
451 461 );
452 462 const pMatch = heroPattern?.code?.match(/<p[^>]*>([\s\S]*?)<\/p>/);
453 463 const heroDesc = pMatch?.[1]?.replace(/<[^>]+>/g, '').trim();
454 - setData('heroDescription', heroDesc || data.heroDescription);
464 + const heroDescription = heroDesc || data.heroDescription;
465 + setData('heroDescription', heroDescription);
466 + if (heroDescription) {
467 + await updateOption('extendify_hero_description', heroDescription).catch(
468 + () => null,
469 + );
470 + }
455 471
456 - const createdPagesWP = await createWpPages(customPages);
472 + const createdPagesWP = await createWpPages(customPages, {
473 + skipSectionIds: isSinglePageDesign,
474 + });
457 475 // Aux pages
458 - const hasBlogPattern = home?.patterns?.some((pattern) =>
476 + const blogPattern = home?.patterns?.find((pattern) =>
459 477 pattern.patternTypes.includes('blog-section'),
460 478 );
461 - if (objective === 'blog' || hasBlogPattern) {
479 + if (siteProfile.blog || blogPattern) {
462 480 checkIn({ stage: 'create_blog_sample_data' });
463 481 // translators: this is for a action log UI. Keep it short
464 - addStatusMessage(__('Creating blog sample data', 'extendify-local'));
465 - await createBlogSampleData({ aiBlogTitles }, siteImages);
482 + addStatusMessage(launchStrings().statusBlog);
483 + await createBlogSampleData(
484 + { aiBlogTitles },
485 + imageUrls,
486 + blogPattern?.blogImages,
487 + );
466 488 }
467 - // If we have site images then set up the hello world image
468 - if (siteImages?.length) {
489 + if (imageUrls.length) {
469 490 checkIn({ stage: 'set_hello_world_image' });
470 - await setHelloWorldFeaturedImage(siteImages);
491 + await setHelloWorldFeaturedImage(imageUrls);
471 492 }
472 493
473 494 let imprint = {};
474 495 if (needsImprint) {
@@ -480,9 +501,9 @@
480 501 if (alreadyActive(activePlugins, 'woocommerce')) {
481 502 checkIn({ stage: 'import_woocommerce_products' });
482 503 addStatusMessage(
483 504 // translators: this is for a action log UI. Keep it short
484 - __('Setting up your online store', 'extendify-local'),
505 + launchStrings().statusStore,
485 506 );
486 507 await apiFetchWithTimeout({
487 508 path: '/extendify/v1/auto-launch/import-woocommerce',
488 509 }).catch(() => null);
@@ -497,25 +518,35 @@
497 518 link: `${homeUrl}/events`,
498 519 });
499 520 }
500 521
522 + // The design's page list omits the posts page like it omits shop, so the
523 + // nav needs it here — but it's ours to create, not a PLUGIN_OWNED_PAGES.
524 + pluginPages.push(...createdPagesWP.filter(isBlogPage));
525 +
501 526 // Adding pages to the nav
502 527 checkIn({ stage: 'set_page_links' });
503 - const linksResult =
504 - structure === 'single-page'
505 - ? await updateSinglePageLinksToSections(
506 - createdPagesWP,
507 - customPages,
508 - {
509 - objective,
510 - activePlugins,
511 - landingPageCTALink: siteProfile.landingPageCTALink,
512 - },
513 - headerCode,
514 - )
515 - : await updateButtonLinks(createdPagesWP, pluginPages, headerCode);
528 + let linksResult = { wpPages: createdPagesWP, headerCode };
529 + if (!isSinglePageDesign) {
530 + linksResult =
531 + structure === 'single-page'
532 + ? await updateSinglePageLinksToSections(
533 + createdPagesWP,
534 + customPages,
535 + {
536 + objective,
537 + activePlugins,
538 + landingPageCTALink: siteProfile.landingPageCTALink,
539 + },
540 + headerCode,
541 + )
542 + : await updateButtonLinks(createdPagesWP, pluginPages, headerCode);
543 + }
516 544 const pagesWithLinksUpdated = linksResult.wpPages;
517 545 headerCode = linksResult.headerCode;
546 + if (alreadyActive(activePlugins, 'woocommerce')) {
547 + headerCode = injectWooCommerceIcons(headerCode);
548 + }
518 549 await updateTemplatePart('extendable/header', headerCode);
519 550 const footerNavPages = [];
520 551 if (footerNavId && imprint?.title) {
521 552 const { originalSlug, title } = imprint;
@@ -529,9 +560,20 @@
529 560
530 561 checkIn({ stage: 'set_navigation_links' });
531 562 if (objective !== 'landing-page') {
532 563 const orderedSlugs = designBuild?.pages?.map((p) => p.slug) ?? [];
533 - if (structure === 'single-page') {
564 + // The tag only exists where the sections came back matched 1:1 to our list;
565 + // read it off what was planted, so menu and section ids can't disagree.
566 + const designOwnsNav =
567 + isSinglePageDesign ||
568 + Boolean(homePage?.patterns?.some(({ navSlug }) => navSlug));
569 + if (designOwnsNav) {
570 + await addSectionLinksFromDesign(
571 + headerNavId,
572 + designBuild.pages,
573 + pluginPages,
574 + );
575 + } else if (structure === 'single-page') {
534 576 await addSectionLinksToNav(
535 577 headerNavId,
536 578 home?.patterns,
537 579 pluginPages,
@@ -563,14 +605,18 @@
563 605 await prefetchAssistData();
564 606 checkIn({ stage: 'final_steps' });
565 607 await setThemeRenderingMode('template-locked');
566 608 await postLaunchFunctions();
567 - if (siteImages?.length) {
609 + if (imageUrls.length) {
568 610 await storeSiteImages(siteImages).catch(() => null);
569 611 }
570 612 // translators: this is for a action log UI. Keep it short
571 - addStatusMessage(__('All done!', 'extendify-local'));
572 - await checkIn({ stage: 'finished', siteProfile, sitePlugins, siteStyle });
613 + addStatusMessage(launchStrings().statusDone);
614 + await Promise.all([
615 + reportInactivePlugins(intendedPlugins).catch(() => null),
616 + reportFailedDependencies(pagesReplaced),
617 + checkIn({ stage: 'finished', siteProfile, sitePlugins, siteStyle }),
618 + ]);
573 619 setWarnOnReload(false);
574 620 setDone(true);
575 621 })().catch((error) => {
576 622 console.error(error);
@@ -580,14 +626,9 @@
580 626 });
581 627 // if we error here we can try again by resetting the home stretch and stalling again to refetch data
582 628 homeStretch.current = false;
583 629 needToStall(true);
584 - setErrorMessage(
585 - __(
586 - 'Something went wrong during the final steps. We will try again but you may need to refresh the page.',
587 - 'extendify-local',
588 - ),
589 - );
630 + setErrorMessage(launchStrings().errorFinalSteps);
590 631 });
591 632 }, [data, needToStall, setUserGaveConsent]);
592 633
593 634 return { done };
@@ -611,12 +652,7 @@
611 652 useEffect(() => {
612 653 if (!error || needToStall()) return;
613 654 console.error(error);
614 655 digest({ error, details: { source: 'auto-launch', caller: 'run-step' } });
615 - setErrorMessage(
616 - __(
617 - 'Having some trouble with this step. Trying again...',
618 - 'extendify-local',
619 - ),
620 - );
656 + setErrorMessage(launchStrings().errorStep);
621 657 }, [error, setErrorMessage, needToStall]);
622 658 };