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 +111 -76 3.1.2 → 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';
@@ -38,20 +41,22 @@
38 41 alreadyActive,
39 42 ensurePluginsActive,
40 43 getActivePlugins,
41 44 replacePlaceholderPatterns,
42 - verifyPluginsActive,
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,13 +151,12 @@
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 157 ensurePluginsActive(
151 158 data.sitePlugins.map(({ wordpressSlug }) => wordpressSlug),
152 - { installedSlugs: installedPluginsSlugs },
153 159 );
154 160 }, [data.sitePlugins]);
155 161
156 162 // needs: siteProfile
@@ -157,9 +163,8 @@
157 163 // provides: style: {}
158 164 useRunStep(
159 165 'siteStyle',
160 166 () => {
161 - // We just need the site profile, which has this
162 167 if (!data.siteProfile?.title) return null;
163 168 return data;
164 169 },
165 170 async (params) => {
@@ -172,9 +177,8 @@
172 177 // provides: aiHeaders: [], aiBlogTitles: []
173 178 useRunStep(
174 179 'siteStrings',
175 180 () => {
176 - // We just need the site profile, which has this
177 181 if (!data.siteProfile?.title) return null;
178 182 return data;
179 183 },
180 184 async (params) => {
@@ -187,9 +191,8 @@
187 191 // provides: siteImages: []
188 192 useRunStep(
189 193 'siteImages',
190 194 () => {
191 - // We just need the site profile, which has this
192 195 if (!data.siteProfile?.title) return null;
193 196 return data;
194 197 },
195 198 async (params) => {
@@ -216,9 +219,8 @@
216 219 // provides: home: { id, slug, patterns, siteStyle }
217 220 useRunStep(
218 221 'home',
219 222 () => {
220 - // Checking various data from calls above
221 223 const ok = [
222 224 data.siteProfile,
223 225 data.siteStyle,
224 226 data.siteImages,
@@ -232,14 +234,13 @@
232 234 return await handleHome(params);
233 235 },
234 236 );
235 237
236 - // siteProfile, sitePlugins, siteStyle, siteImages
238 + // needs: siteProfile, sitePlugins, siteStyle, siteImages
237 239 // provides: pages: [{ id, slug, name, patterns, siteStyle }]
238 240 useRunStep(
239 241 'pages',
240 242 () => {
241 - // Checking various data from calls above
242 243 const ok = [
243 244 data.siteProfile,
244 245 data.siteStyle,
245 246 data.siteImages,
@@ -266,14 +267,11 @@
266 267 checkIn({ stage: 'set_general', siteProfile, sitePlugins, siteStyle });
267 268
268 269 const { title } = siteProfile;
269 270 // translators: this is for a action log UI. Keep it short
270 - addStatusMessage(__('Adding admin configurations', 'extendify-local'));
271 - // update permalinks
271 + addStatusMessage(launchStrings().statusAdmin);
272 272 await updateOption('permalink_structure', '/%postname%/');
273 - // make sure consent is set
274 273 setUserGaveConsent(true);
275 - // Update title
276 274 if (title) await updateOption('blogname', title);
277 275 },
278 276 );
279 277
@@ -319,16 +317,23 @@
319 317 if (homeStretch.current) return;
320 318 homeStretch.current = true;
321 319 (async () => {
322 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 + );
323 332
324 333 // Guarantee plugins are active before the pattern imports below rely on them.
325 - await verifyPluginsActive(
326 - (sitePlugins ?? []).map(({ wordpressSlug }) => wordpressSlug),
327 - { installedSlugs: installedPluginsSlugs },
328 - );
334 + await ensurePluginsActive(intendedPlugins);
329 335
330 - // Do they need an imprint page?
331 336 const needsImprint = Array.isArray(showImprint)
332 337 ? showImprint.includes(wpLanguage ?? '') && category === 'Business'
333 338 : false;
334 339
@@ -337,9 +342,9 @@
337 342 let variation = siteStyle?.variation;
338 343 if (customFonts?.length) {
339 344 checkIn({ stage: 'install_fonts' });
340 345 // translators: this is for a action log UI. Keep it short
341 - addStatusMessage(__('Installing fonts locally', 'extendify-local'));
346 + addStatusMessage(launchStrings().statusFonts);
342 347 const installed = await installFontFamilies(customFonts).catch(
343 348 () => [],
344 349 );
345 350 variation = mergeFontsIntoVariation(siteStyle.variation, installed);
@@ -346,19 +351,15 @@
346 351 }
347 352
348 353 if (siteStyle?.vibe && siteStyle.vibe !== 'natural-1') {
349 354 // translators: vibe in this context is a noun - the feeling of their site design.
350 - addStatusMessage(__('Setting the website style', 'extendify-local'));
355 + addStatusMessage(launchStrings().statusSiteStyle);
351 356 checkIn({ stage: 'compute_vibe' });
352 - const vibeBlocks = await computeVibeAdjustedBlocks(
357 + const vibe = await computeVibeAdjustments(
353 358 siteStyle.vibe,
359 + variation,
354 360 ).catch(() => null);
355 - if (vibeBlocks) {
356 - variation = {
357 - ...variation,
358 - styles: { ...variation.styles, blocks: vibeBlocks },
359 - };
360 - }
361 + if (vibe) variation = { ...variation, ...vibe };
361 362 }
362 363
363 364 checkIn({ stage: 'set_vibe' });
364 365 await updateVariation(variation);
@@ -363,9 +364,9 @@
363 364 checkIn({ stage: 'set_vibe' });
364 365 await updateVariation(variation);
365 366
366 367 // navigation menu
367 - addStatusMessage(__('Working on the navigation', 'extendify-local'));
368 + addStatusMessage(launchStrings().statusNavigation);
368 369 const { id: headerNavId } = await createNavigation({
369 370 title: __('Header Navigation', 'extendify-local'),
370 371 slug: 'site-navigation',
371 372 });
@@ -375,9 +376,9 @@
375 376 headerCode = injectNavExtras(headerCode, launchDecisions);
376 377 // remove the header navigation from the landing page
377 378 if (objective === 'landing-page') {
378 379 // translators: this is for a action log UI. Keep it short
379 - addStatusMessage(__('Perfecting a landing page', 'extendify-local'));
380 + addStatusMessage(launchStrings().statusLanding);
380 381 const social =
381 382 /<!--\s*wp:social-links\b[^>]*>.*?<!--\s*\/wp:social-links\s*-->/gis;
382 383 headerCode = headerCode
383 384 .replace(/<!--\s*wp:navigation\b[^>]*.*\/-->/gis, '')
@@ -394,8 +395,15 @@
394 395
395 396 // footer
396 397 let footerNavId = null;
397 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 + }
398 406 if (needsImprint) {
399 407 const nav = await createNavigation({
400 408 title: __('Footer Navigation', 'extendify-local'),
401 409 slug: 'footer-navigation',
@@ -402,14 +410,15 @@
402 410 });
403 411 footerNavId = nav.id;
404 412 footerCode = updateNavAttributes(footerCode, { ref: footerNavId });
405 413 }
414 + footerCode = applySocialProfiles(footerCode, siteProfile.socialProfiles);
406 415 checkIn({ stage: 'set_footer' });
407 416 await updateTemplatePart('extendable/footer', footerCode);
408 417
409 418 // pages
410 419 // translators: this is for a action log UI. Keep it short
411 - addStatusMessage(__('Creating pages', 'extendify-local'));
420 + addStatusMessage(launchStrings().statusCreatingPages);
412 421 const pagesToCreate = getPagesToCreate(data);
413 422 const titlePattern = pages?.[0]?.patterns?.find((p) =>
414 423 p.patternTypes?.includes('page-title'),
415 424 );
@@ -418,9 +427,9 @@
418 427 await updatePageTitlePattern(titlePattern.code);
419 428 }
420 429
421 430 const activePlugins = await getActivePlugins();
422 - // 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.
423 432 const reservedSlugs = new Set(
424 433 PLUGIN_OWNED_PAGES.filter(
425 434 ({ plugin }) =>
426 435 sitePlugins.some((p) => p.wordpressSlug === plugin) ||
@@ -451,25 +460,36 @@
451 460 p.patternTypes?.includes('hero-header'),
452 461 );
453 462 const pMatch = heroPattern?.code?.match(/<p[^>]*>([\s\S]*?)<\/p>/);
454 463 const heroDesc = pMatch?.[1]?.replace(/<[^>]+>/g, '').trim();
455 - 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 + }
456 471
457 - const createdPagesWP = await createWpPages(customPages);
472 + const createdPagesWP = await createWpPages(customPages, {
473 + skipSectionIds: isSinglePageDesign,
474 + });
458 475 // Aux pages
459 - const hasBlogPattern = home?.patterns?.some((pattern) =>
476 + const blogPattern = home?.patterns?.find((pattern) =>
460 477 pattern.patternTypes.includes('blog-section'),
461 478 );
462 - if (objective === 'blog' || hasBlogPattern) {
479 + if (siteProfile.blog || blogPattern) {
463 480 checkIn({ stage: 'create_blog_sample_data' });
464 481 // translators: this is for a action log UI. Keep it short
465 - addStatusMessage(__('Creating blog sample data', 'extendify-local'));
466 - await createBlogSampleData({ aiBlogTitles }, siteImages);
482 + addStatusMessage(launchStrings().statusBlog);
483 + await createBlogSampleData(
484 + { aiBlogTitles },
485 + imageUrls,
486 + blogPattern?.blogImages,
487 + );
467 488 }
468 - // If we have site images then set up the hello world image
469 - if (siteImages?.length) {
489 + if (imageUrls.length) {
470 490 checkIn({ stage: 'set_hello_world_image' });
471 - await setHelloWorldFeaturedImage(siteImages);
491 + await setHelloWorldFeaturedImage(imageUrls);
472 492 }
473 493
474 494 let imprint = {};
475 495 if (needsImprint) {
@@ -481,9 +501,9 @@
481 501 if (alreadyActive(activePlugins, 'woocommerce')) {
482 502 checkIn({ stage: 'import_woocommerce_products' });
483 503 addStatusMessage(
484 504 // translators: this is for a action log UI. Keep it short
485 - __('Setting up your online store', 'extendify-local'),
505 + launchStrings().statusStore,
486 506 );
487 507 await apiFetchWithTimeout({
488 508 path: '/extendify/v1/auto-launch/import-woocommerce',
489 509 }).catch(() => null);
@@ -498,25 +518,35 @@
498 518 link: `${homeUrl}/events`,
499 519 });
500 520 }
501 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 +
502 526 // Adding pages to the nav
503 527 checkIn({ stage: 'set_page_links' });
504 - const linksResult =
505 - structure === 'single-page'
506 - ? await updateSinglePageLinksToSections(
507 - createdPagesWP,
508 - customPages,
509 - {
510 - objective,
511 - activePlugins,
512 - landingPageCTALink: siteProfile.landingPageCTALink,
513 - },
514 - headerCode,
515 - )
516 - : 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 + }
517 544 const pagesWithLinksUpdated = linksResult.wpPages;
518 545 headerCode = linksResult.headerCode;
546 + if (alreadyActive(activePlugins, 'woocommerce')) {
547 + headerCode = injectWooCommerceIcons(headerCode);
548 + }
519 549 await updateTemplatePart('extendable/header', headerCode);
520 550 const footerNavPages = [];
521 551 if (footerNavId && imprint?.title) {
522 552 const { originalSlug, title } = imprint;
@@ -530,9 +560,20 @@
530 560
531 561 checkIn({ stage: 'set_navigation_links' });
532 562 if (objective !== 'landing-page') {
533 563 const orderedSlugs = designBuild?.pages?.map((p) => p.slug) ?? [];
534 - 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') {
535 576 await addSectionLinksToNav(
536 577 headerNavId,
537 578 home?.patterns,
538 579 pluginPages,
@@ -564,14 +605,18 @@
564 605 await prefetchAssistData();
565 606 checkIn({ stage: 'final_steps' });
566 607 await setThemeRenderingMode('template-locked');
567 608 await postLaunchFunctions();
568 - if (siteImages?.length) {
609 + if (imageUrls.length) {
569 610 await storeSiteImages(siteImages).catch(() => null);
570 611 }
571 612 // translators: this is for a action log UI. Keep it short
572 - addStatusMessage(__('All done!', 'extendify-local'));
573 - 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 + ]);
574 619 setWarnOnReload(false);
575 620 setDone(true);
576 621 })().catch((error) => {
577 622 console.error(error);
@@ -581,14 +626,9 @@
581 626 });
582 627 // if we error here we can try again by resetting the home stretch and stalling again to refetch data
583 628 homeStretch.current = false;
584 629 needToStall(true);
585 - setErrorMessage(
586 - __(
587 - 'Something went wrong during the final steps. We will try again but you may need to refresh the page.',
588 - 'extendify-local',
589 - ),
590 - );
630 + setErrorMessage(launchStrings().errorFinalSteps);
591 631 });
592 632 }, [data, needToStall, setUserGaveConsent]);
593 633
594 634 return { done };
@@ -612,12 +652,7 @@
612 652 useEffect(() => {
613 653 if (!error || needToStall()) return;
614 654 console.error(error);
615 655 digest({ error, details: { source: 'auto-launch', caller: 'run-step' } });
616 - setErrorMessage(
617 - __(
618 - 'Having some trouble with this step. Trying again...',
619 - 'extendify-local',
620 - ),
621 - );
656 + setErrorMessage(launchStrings().errorStep);
622 657 }, [error, setErrorMessage, needToStall]);
623 658 };