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 +155 -90 3.0.6 → trunk View file →
@@ -1,7 +1,8 @@
1 1 import { handleDesignBuild } from '@auto-launch/fetchers/get-design-build';
2 2 import { handleHome } from '@auto-launch/fetchers/get-home';
3 3 import { handleSiteImages } from '@auto-launch/fetchers/get-images';
4 +import { handleLaunchDecisions } from '@auto-launch/fetchers/get-launch-decisions';
4 5 import { handleSiteLogo } from '@auto-launch/fetchers/get-logo';
5 6 import { handlePages } from '@auto-launch/fetchers/get-pages';
6 7 import { handleSitePlugins } from '@auto-launch/fetchers/get-plugins';
7 8 import { handleSiteProfile } from '@auto-launch/fetchers/get-profile';
@@ -18,10 +19,13 @@
18 19 updateSinglePageLinksToSections,
19 20 } from '@auto-launch/functions/links';
20 21 import {
21 22 addPageLinksToNav,
23 + addSectionLinksFromDesign,
22 24 addSectionLinksToNav,
23 25 createNavigation,
26 + injectNavExtras,
27 + injectWooCommerceIcons,
24 28 updateNavAttributes,
25 29 } from '@auto-launch/functions/nav';
26 30 import {
27 31 addImprintPage,
@@ -26,8 +30,9 @@
26 30 import {
27 31 addImprintPage,
28 32 createWpPages,
29 33 getPagesToCreate,
34 + isBlogPage,
30 35 PLUGIN_OWNED_PAGES,
31 36 setHelloWorldFeaturedImage,
32 37 updatePageTitlePattern,
33 38 } from '@auto-launch/functions/pages';
@@ -32,24 +37,26 @@
32 37 updatePageTitlePattern,
33 38 } from '@auto-launch/functions/pages';
34 39 import { generatePageContent } from '@auto-launch/functions/patterns';
35 40 import {
36 - activatePlugin,
37 41 alreadyActive,
42 + ensurePluginsActive,
38 43 getActivePlugins,
39 - installPlugin,
40 44 replacePlaceholderPatterns,
45 + reportFailedDependencies,
46 + reportInactivePlugins,
41 47 } from '@auto-launch/functions/plugins';
42 48 import {
43 49 postLaunchFunctions,
44 50 prefetchAssistData,
45 51 } from '@auto-launch/functions/setup';
52 +import { applySocialProfiles } from '@auto-launch/functions/social-links';
46 53 import {
47 54 setThemeRenderingMode,
48 55 updateTemplatePart,
49 56 updateVariation,
50 57 } from '@auto-launch/functions/theme';
51 -import { computeVibeAdjustedBlocks } from '@auto-launch/functions/vibes';
58 +import { computeVibeAdjustments } from '@auto-launch/functions/vibes';
52 59 import {
53 60 createBlogSampleData,
54 61 getOption,
55 62 getPageById,
@@ -57,22 +64,22 @@
57 64 updateOption,
58 65 } from '@auto-launch/functions/wp';
59 66 import { useWarnOnLeave } from '@auto-launch/hooks/useWarnOnLeave';
60 67 import { useLaunchDataStore } from '@auto-launch/state/launch-data';
68 +import { launchStrings } from '@auto-launch/strings';
61 69 import { digest } from '@shared/api/digest';
70 +import { siteImageUrls } from '@shared/lib/site-images';
62 71 import { useAIConsentStore } from '@shared/state/ai-consent';
63 72 import { useEffect, useRef, useState } from '@wordpress/element';
64 73 import { __ } from '@wordpress/i18n';
65 74 import useSWRImmutable from 'swr/immutable';
66 75
67 -const { homeUrl, showImprint, wpLanguage, installedPluginsSlugs } =
68 - window.extSharedData;
76 +const { homeUrl, showImprint, wpLanguage } = window.extSharedData;
69 77
70 78 // TODO: I think a good strategy is "if something fails, try to refetch some state"
71 79
72 80 export const useCreateSite = () => {
73 - // All the data we need to finish
74 - const { setErrorMessage, addStatusMessage, needToStall, ...data } =
81 + const { setErrorMessage, addStatusMessage, needToStall, setData, ...data } =
75 82 useLaunchDataStore();
76 83 const { setUserGaveConsent } = useAIConsentStore();
77 84 const homeStretch = useRef(false);
78 85 const [warnOnReload, setWarnOnReload] = useState(!needToStall());
@@ -113,8 +120,11 @@
113 120 useRunStep(
114 121 'siteLogo',
115 122 () => {
116 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;
117 127 return data;
118 128 },
119 129 async (params) => {
120 130 checkIn({ stage: 'get_logo' });
@@ -122,13 +132,12 @@
122 132 },
123 133 );
124 134
125 135 // needs: siteProfile
126 - // provides:sitePlugins: [{name, wordpressSlug}]
136 + // provides: sitePlugins: [{name, wordpressSlug}]
127 137 useRunStep(
128 138 'sitePlugins',
129 139 () => {
130 - // We just need the site profile, which has this
131 140 if (!data.siteProfile?.title) return null;
132 141 return data;
133 142 },
134 143 async (params) => {
@@ -138,24 +147,17 @@
138 147 );
139 148
140 149 useEffect(() => {
141 150 // Start installing the partner plugins asap
142 - if (!data.sitePlugins) return;
151 + if (!data.sitePlugins?.length) return;
143 152
144 - const pluginsToInstall = data.sitePlugins.filter(
145 - ({ wordpressSlug: slug }) => !installedPluginsSlugs?.includes(slug),
146 - );
147 - if (pluginsToInstall.length === 0) return;
148 153 setStatus(
149 154 // translators: this is for a action log UI. Keep it short
150 - __('Setting up functionality for your website', 'extendify-local'),
155 + launchStrings().statusFunctionality,
151 156 );
152 - (async function install() {
153 - for (const { wordpressSlug: slug } of pluginsToInstall) {
154 - const p = await installPlugin(slug);
155 - await activatePlugin(p?.plugin ?? slug);
156 - }
157 - })();
157 + ensurePluginsActive(
158 + data.sitePlugins.map(({ wordpressSlug }) => wordpressSlug),
159 + );
158 160 }, [data.sitePlugins]);
159 161
160 162 // needs: siteProfile
161 163 // provides: style: {}
@@ -161,9 +163,8 @@
161 163 // provides: style: {}
162 164 useRunStep(
163 165 'siteStyle',
164 166 () => {
165 - // We just need the site profile, which has this
166 167 if (!data.siteProfile?.title) return null;
167 168 return data;
168 169 },
169 170 async (params) => {
@@ -176,9 +177,8 @@
176 177 // provides: aiHeaders: [], aiBlogTitles: []
177 178 useRunStep(
178 179 'siteStrings',
179 180 () => {
180 - // We just need the site profile, which has this
181 181 if (!data.siteProfile?.title) return null;
182 182 return data;
183 183 },
184 184 async (params) => {
@@ -191,9 +191,8 @@
191 191 // provides: siteImages: []
192 192 useRunStep(
193 193 'siteImages',
194 194 () => {
195 - // We just need the site profile, which has this
196 195 if (!data.siteProfile?.title) return null;
197 196 return data;
198 197 },
199 198 async (params) => {
@@ -201,14 +200,27 @@
201 200 return await handleSiteImages(params);
202 201 },
203 202 );
204 203
204 + // needs: siteProfile
205 + // provides: launchDecisions: { navExtras, navButtonLabel }
206 + useRunStep(
207 + 'launchDecisions',
208 + () => {
209 + if (!data.siteProfile?.title) return null;
210 + return data;
211 + },
212 + async (params) => {
213 + checkIn({ stage: 'get_launch_decisions' });
214 + return await handleLaunchDecisions(params);
215 + },
216 + );
217 +
205 218 // needs: siteProfile, sitePlugins, siteStyle, siteImages, aiHeaders
206 219 // provides: home: { id, slug, patterns, siteStyle }
207 220 useRunStep(
208 221 'home',
209 222 () => {
210 - // Checking various data from calls above
211 223 const ok = [
212 224 data.siteProfile,
213 225 data.siteStyle,
214 226 data.siteImages,
@@ -222,14 +234,13 @@
222 234 return await handleHome(params);
223 235 },
224 236 );
225 237
226 - // siteProfile, sitePlugins, siteStyle, siteImages
238 + // needs: siteProfile, sitePlugins, siteStyle, siteImages
227 239 // provides: pages: [{ id, slug, name, patterns, siteStyle }]
228 240 useRunStep(
229 241 'pages',
230 242 () => {
231 - // Checking various data from calls above
232 243 const ok = [
233 244 data.siteProfile,
234 245 data.siteStyle,
235 246 data.siteImages,
@@ -256,14 +267,11 @@
256 267 checkIn({ stage: 'set_general', siteProfile, sitePlugins, siteStyle });
257 268
258 269 const { title } = siteProfile;
259 270 // translators: this is for a action log UI. Keep it short
260 - addStatusMessage(__('Adding admin configurations', 'extendify-local'));
261 - // update permalinks
271 + addStatusMessage(launchStrings().statusAdmin);
262 272 await updateOption('permalink_structure', '/%postname%/');
263 - // make sure consent is set
264 273 setUserGaveConsent(true);
265 - // Update title
266 274 if (title) await updateOption('blogname', title);
267 275 },
268 276 );
269 277
@@ -301,8 +309,9 @@
301 309 siteStyle,
302 310 aiBlogTitles,
303 311 siteImages,
304 312 designBuild,
313 + launchDecisions,
305 314 } = data;
306 315 // pages could be [] and pass here, that's ok
307 316 if (!home || !pages) return;
308 317 if (homeStretch.current) return;
@@ -308,10 +317,23 @@
308 317 if (homeStretch.current) return;
309 318 homeStretch.current = true;
310 319 (async () => {
311 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 + );
312 332
313 - // Do they need an imprint page?
333 + // Guarantee plugins are active before the pattern imports below rely on them.
334 + await ensurePluginsActive(intendedPlugins);
335 +
314 336 const needsImprint = Array.isArray(showImprint)
315 337 ? showImprint.includes(wpLanguage ?? '') && category === 'Business'
316 338 : false;
317 339
@@ -320,9 +342,9 @@
320 342 let variation = siteStyle?.variation;
321 343 if (customFonts?.length) {
322 344 checkIn({ stage: 'install_fonts' });
323 345 // translators: this is for a action log UI. Keep it short
324 - addStatusMessage(__('Installing fonts locally', 'extendify-local'));
346 + addStatusMessage(launchStrings().statusFonts);
325 347 const installed = await installFontFamilies(customFonts).catch(
326 348 () => [],
327 349 );
328 350 variation = mergeFontsIntoVariation(siteStyle.variation, installed);
@@ -329,19 +351,15 @@
329 351 }
330 352
331 353 if (siteStyle?.vibe && siteStyle.vibe !== 'natural-1') {
332 354 // translators: vibe in this context is a noun - the feeling of their site design.
333 - addStatusMessage(__('Setting the website style', 'extendify-local'));
355 + addStatusMessage(launchStrings().statusSiteStyle);
334 356 checkIn({ stage: 'compute_vibe' });
335 - const vibeBlocks = await computeVibeAdjustedBlocks(
357 + const vibe = await computeVibeAdjustments(
336 358 siteStyle.vibe,
359 + variation,
337 360 ).catch(() => null);
338 - if (vibeBlocks) {
339 - variation = {
340 - ...variation,
341 - styles: { ...variation.styles, blocks: vibeBlocks },
342 - };
343 - }
361 + if (vibe) variation = { ...variation, ...vibe };
344 362 }
345 363
346 364 checkIn({ stage: 'set_vibe' });
347 365 await updateVariation(variation);
@@ -346,9 +364,9 @@
346 364 checkIn({ stage: 'set_vibe' });
347 365 await updateVariation(variation);
348 366
349 367 // navigation menu
350 - addStatusMessage(__('Working on the navigation', 'extendify-local'));
368 + addStatusMessage(launchStrings().statusNavigation);
351 369 const { id: headerNavId } = await createNavigation({
352 370 title: __('Header Navigation', 'extendify-local'),
353 371 slug: 'site-navigation',
354 372 });
@@ -354,12 +372,13 @@
354 372 });
355 373 let headerCode = updateNavAttributes(home.headerCode || '', {
356 374 ref: headerNavId,
357 375 });
376 + headerCode = injectNavExtras(headerCode, launchDecisions);
358 377 // remove the header navigation from the landing page
359 378 if (objective === 'landing-page') {
360 379 // translators: this is for a action log UI. Keep it short
361 - addStatusMessage(__('Perfecting a landing page', 'extendify-local'));
380 + addStatusMessage(launchStrings().statusLanding);
362 381 const social =
363 382 /<!--\s*wp:social-links\b[^>]*>.*?<!--\s*\/wp:social-links\s*-->/gis;
364 383 headerCode = headerCode
365 384 .replace(/<!--\s*wp:navigation\b[^>]*.*\/-->/gis, '')
@@ -364,23 +383,27 @@
364 383 headerCode = headerCode
365 384 .replace(/<!--\s*wp:navigation\b[^>]*.*\/-->/gis, '')
366 385 .replace(social, '');
367 386 }
368 - if (typeof siteProfile.phoneNumber === 'string') {
369 - headerCode = headerCode.replaceAll(
370 - // Hardcoded in the template
371 - '206-555-0100',
372 - siteProfile.phoneNumber ||
373 - // translators: Use a number that is appropriate for the locale. It does not need to be this exact number. This is a placeholder phone number. For example, in pt_BR you could use (11) 91234-5678.
374 - __('206-555-0100', 'extendify-local'),
375 - );
376 - }
387 + headerCode = headerCode.replaceAll(
388 + '206-555-0100',
389 + (typeof siteProfile.phoneNumber === 'string' &&
390 + siteProfile.phoneNumber) ||
391 + // translators: Use a number that is appropriate for the locale. It does not need to be this exact number. This is a placeholder phone number. For example, in pt_BR you could use (11) 91234-5678.
392 + __('206-555-0100', 'extendify-local'),
393 + );
377 394 checkIn({ stage: 'set_navigation' });
378 - await updateTemplatePart('extendable/header', headerCode);
379 395
380 396 // footer
381 397 let footerNavId = null;
382 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 + }
383 406 if (needsImprint) {
384 407 const nav = await createNavigation({
385 408 title: __('Footer Navigation', 'extendify-local'),
386 409 slug: 'footer-navigation',
@@ -387,14 +410,15 @@
387 410 });
388 411 footerNavId = nav.id;
389 412 footerCode = updateNavAttributes(footerCode, { ref: footerNavId });
390 413 }
414 + footerCode = applySocialProfiles(footerCode, siteProfile.socialProfiles);
391 415 checkIn({ stage: 'set_footer' });
392 416 await updateTemplatePart('extendable/footer', footerCode);
393 417
394 418 // pages
395 419 // translators: this is for a action log UI. Keep it short
396 - addStatusMessage(__('Creating pages', 'extendify-local'));
420 + addStatusMessage(launchStrings().statusCreatingPages);
397 421 const pagesToCreate = getPagesToCreate(data);
398 422 const titlePattern = pages?.[0]?.patterns?.find((p) =>
399 423 p.patternTypes?.includes('page-title'),
400 424 );
@@ -403,9 +427,9 @@
403 427 await updatePageTitlePattern(titlePattern.code);
404 428 }
405 429
406 430 const activePlugins = await getActivePlugins();
407 - // 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.
408 432 const reservedSlugs = new Set(
409 433 PLUGIN_OWNED_PAGES.filter(
410 434 ({ plugin }) =>
411 435 sitePlugins.some((p) => p.wordpressSlug === plugin) ||
@@ -429,25 +453,43 @@
429 453 }
430 454 checkIn({ stage: 'generate_page_content' });
431 455 const customPages = await generatePageContent(pagesReplaced, data);
432 456
433 - const stickyNav =
434 - structure === 'single-page' && objective !== 'landing-page';
435 - const createdPagesWP = await createWpPages(customPages, { stickyNav });
457 + // Update heroDescription to the actual AI-rewritten hero content
458 + const homePage = customPages.find((p) => p.slug === 'home');
459 + const heroPattern = homePage?.patterns?.find((p) =>
460 + p.patternTypes?.includes('hero-header'),
461 + );
462 + const pMatch = heroPattern?.code?.match(/<p[^>]*>([\s\S]*?)<\/p>/);
463 + const heroDesc = pMatch?.[1]?.replace(/<[^>]+>/g, '').trim();
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 + }
471 +
472 + const createdPagesWP = await createWpPages(customPages, {
473 + skipSectionIds: isSinglePageDesign,
474 + });
436 475 // Aux pages
437 - const hasBlogPattern = home?.patterns?.some((pattern) =>
476 + const blogPattern = home?.patterns?.find((pattern) =>
438 477 pattern.patternTypes.includes('blog-section'),
439 478 );
440 - if (objective === 'blog' || hasBlogPattern) {
479 + if (siteProfile.blog || blogPattern) {
441 480 checkIn({ stage: 'create_blog_sample_data' });
442 481 // translators: this is for a action log UI. Keep it short
443 - addStatusMessage(__('Creating blog sample data', 'extendify-local'));
444 - await createBlogSampleData({ aiBlogTitles }, siteImages);
482 + addStatusMessage(launchStrings().statusBlog);
483 + await createBlogSampleData(
484 + { aiBlogTitles },
485 + imageUrls,
486 + blogPattern?.blogImages,
487 + );
445 488 }
446 - // If we have site images then set up the hello world image
447 - if (siteImages?.length) {
489 + if (imageUrls.length) {
448 490 checkIn({ stage: 'set_hello_world_image' });
449 - await setHelloWorldFeaturedImage(siteImages);
491 + await setHelloWorldFeaturedImage(imageUrls);
450 492 }
451 493
452 494 let imprint = {};
453 495 if (needsImprint) {
@@ -459,9 +501,9 @@
459 501 if (alreadyActive(activePlugins, 'woocommerce')) {
460 502 checkIn({ stage: 'import_woocommerce_products' });
461 503 addStatusMessage(
462 504 // translators: this is for a action log UI. Keep it short
463 - __('Setting up your online store', 'extendify-local'),
505 + launchStrings().statusStore,
464 506 );
465 507 await apiFetchWithTimeout({
466 508 path: '/extendify/v1/auto-launch/import-woocommerce',
467 509 }).catch(() => null);
@@ -476,18 +518,36 @@
476 518 link: `${homeUrl}/events`,
477 519 });
478 520 }
479 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 +
480 526 // Adding pages to the nav
481 527 checkIn({ stage: 'set_page_links' });
482 - const pagesWithLinksUpdated =
483 - structure === 'single-page'
484 - ? await updateSinglePageLinksToSections(createdPagesWP, customPages, {
485 - objective,
486 - activePlugins,
487 - landingPageCTALink: siteProfile.landingPageCTALink,
488 - })
489 - : await updateButtonLinks(createdPagesWP, pluginPages);
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 + }
544 + const pagesWithLinksUpdated = linksResult.wpPages;
545 + headerCode = linksResult.headerCode;
546 + if (alreadyActive(activePlugins, 'woocommerce')) {
547 + headerCode = injectWooCommerceIcons(headerCode);
548 + }
549 + await updateTemplatePart('extendable/header', headerCode);
490 550 const footerNavPages = [];
491 551 if (footerNavId && imprint?.title) {
492 552 const { originalSlug, title } = imprint;
493 553 footerNavPages.push({
@@ -500,9 +560,20 @@
500 560
501 561 checkIn({ stage: 'set_navigation_links' });
502 562 if (objective !== 'landing-page') {
503 563 const orderedSlugs = designBuild?.pages?.map((p) => p.slug) ?? [];
504 - 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') {
505 576 await addSectionLinksToNav(
506 577 headerNavId,
507 578 home?.patterns,
508 579 pluginPages,
@@ -534,14 +605,18 @@
534 605 await prefetchAssistData();
535 606 checkIn({ stage: 'final_steps' });
536 607 await setThemeRenderingMode('template-locked');
537 608 await postLaunchFunctions();
538 - if (siteImages?.length) {
609 + if (imageUrls.length) {
539 610 await storeSiteImages(siteImages).catch(() => null);
540 611 }
541 612 // translators: this is for a action log UI. Keep it short
542 - addStatusMessage(__('All done!', 'extendify-local'));
543 - 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 + ]);
544 619 setWarnOnReload(false);
545 620 setDone(true);
546 621 })().catch((error) => {
547 622 console.error(error);
@@ -551,14 +626,9 @@
551 626 });
552 627 // if we error here we can try again by resetting the home stretch and stalling again to refetch data
553 628 homeStretch.current = false;
554 629 needToStall(true);
555 - setErrorMessage(
556 - __(
557 - 'Something went wrong during the final steps. We will try again but you may need to refresh the page.',
558 - 'extendify-local',
559 - ),
560 - );
630 + setErrorMessage(launchStrings().errorFinalSteps);
561 631 });
562 632 }, [data, needToStall, setUserGaveConsent]);
563 633
564 634 return { done };
@@ -582,12 +652,7 @@
582 652 useEffect(() => {
583 653 if (!error || needToStall()) return;
584 654 console.error(error);
585 655 digest({ error, details: { source: 'auto-launch', caller: 'run-step' } });
586 - setErrorMessage(
587 - __(
588 - 'Having some trouble with this step. Trying again...',
589 - 'extendify-local',
590 - ),
591 - );
656 + setErrorMessage(launchStrings().errorStep);
592 657 }, [error, setErrorMessage, needToStall]);
593 658 };