@designcrowd/library.brand-page 5.19.1 → 5.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-BmeMc9oz.js → CreatedOnBrandCrowd-de-de-Ekmn0O3s.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CQNdm_6P.js → CreatedOnBrandCrowd-en-us-BjeXlKTS.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BrmHWeAM.js → CreatedOnBrandCrowd-es-es-rpNINAMY.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CaIyjTXl.js → CreatedOnBrandCrowd-fr-ca-BL4nSaLj.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CaIyjTXl.js → CreatedOnBrandCrowd-fr-fr-BL4nSaLj.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-BClE0Nd9.js → CreatedOnBrandCrowd-pt-br-K0r4FyYq.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-BClE0Nd9.js → CreatedOnBrandCrowd-pt-pt-K0r4FyYq.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-CkrdlPqK.js → CreatedOnDcom-de-de-0XYyEKYc.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-D33J9qky.js → CreatedOnDcom-en-us-E8hvwiMd.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-BsmInngw.js → CreatedOnDcom-es-es-pRuCd7xX.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-DOXlsRea.js → CreatedOnDcom-fr-ca-Bu4V3jpn.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-SfFYLtyn.js → CreatedOnDcom-fr-fr-CCcrGNz2.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-DcWeuZUy.js → CreatedOnDcom-pt-br-1GTvaLKd.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-DZsZZ4iX.js → CreatedOnDcom-pt-pt-CTIYW10X.js} +1 -1
  15. package/lib/src/brand-page/models/index.d.ts +1 -0
  16. package/lib/src/brand-page/models/index.js +3 -0
  17. package/lib/src/brand-page/utils/personalisation.util.js +72 -14
  18. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +1 -1
  19. package/lib/src/brand-page-maker/components/admin-background-settings/AdminBackgroundSettings.mixin.d.ts +6 -0
  20. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +12 -1
  21. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +2 -0
  22. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.js +1 -0
  23. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +1 -1
  24. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -1
  25. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +1 -1
  26. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
  27. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +1 -1
  28. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +1 -1
  29. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +2 -3
  30. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +16 -5
  31. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +6 -0
  32. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +1 -0
  33. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +3 -0
  34. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +3 -1
  35. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +5 -1
  36. package/lib/src/brand-page-maker/stores/ui/ui-store.d.ts +6 -0
  37. package/lib/src/brand-page-maker/stores/ui/ui-store.js +8 -0
  38. package/lib/src/clients/design-template-client.d.ts +3 -0
  39. package/lib/src/clients/models.d.ts +2 -0
  40. package/lib/src/css/library.brand-page-brandCrowd.min.css +19 -7
  41. package/lib/src/css/library.brand-page-brandPage.min.css +19 -7
  42. package/lib/src/css/library.brand-page-designCom.min.css +19 -7
  43. package/lib/src/css/library.brand-page-preview.min.css +19 -7
  44. package/lib/src/index.mjs +1 -1
  45. package/lib/src/{lib-C5vGkjfs.js → lib-BfgUqu0B.js} +22777 -22678
  46. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
  47. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
  48. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +7 -1
  49. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +9 -0
  50. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +1 -1
  51. package/lib/src/shared/logo-versions/LogoVersions.mixin.d.ts +6 -0
  52. package/lib/src/shared/models.d.ts +2 -0
  53. package/lib/src/shared/my-designs/MyDesigns.mixin.d.ts +6 -0
  54. package/lib/src/shared/my-uploads/MyMedia.mixin.d.ts +6 -0
  55. package/lib/src/shared/my-uploads/MyUploads.mixin.d.ts +6 -0
  56. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +7 -1
  57. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +7 -1
  58. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -1
  59. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +7 -1
  60. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +10 -439
  61. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.js +2 -2
  62. package/lib/src/shared/template-settings/models.d.ts +1 -0
  63. package/lib/src/shared/template-settings/store/template-settings-store.d.ts +4 -0
  64. package/lib/src/shared/template-settings/store/template-settings-store.js +79 -11
  65. package/lib/src/shared/unsplash-library/UnsplashLibrary.mixin.d.ts +6 -0
  66. package/package.json +1 -1
  67. package/src/brand-page/models/index.ts +4 -0
  68. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -0
  69. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +1 -0
  70. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +2 -0
  71. package/src/brand-page/utils/personalisation.util.test.ts +35 -0
  72. package/src/brand-page/utils/personalisation.util.ts +119 -23
  73. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +4 -0
  74. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.ts +1 -0
  75. package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +22 -8
  76. package/src/brand-page-maker/components/template-preview/TemplatePreview.vue +5 -10
  77. package/src/brand-page-maker/components/template-preview/TemplatePreviewAiBadge.vue +12 -0
  78. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +5 -0
  79. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +10 -0
  80. package/src/brand-page-maker/stores/ui/ui-store.ts +16 -0
  81. package/src/clients/design-template-client.ts +3 -0
  82. package/src/clients/models.ts +2 -0
  83. package/src/shared/models.ts +6 -0
  84. package/src/shared/template-settings/components/TemplatesSettings.mixin.ts +2 -2
  85. package/src/shared/template-settings/models.ts +1 -0
  86. package/src/shared/template-settings/store/template-settings-store.ts +105 -18
@@ -38,6 +38,12 @@ declare const _default: import("vue").DefineComponent<{}, {
38
38
  setUserPersonalisationData({ personalisationData, }: {
39
39
  personalisationData: import("../../brand-page").UserPersonalisationContent[];
40
40
  }): void;
41
+ setUserPromptContentKey({ userPromptContentKey, }: {
42
+ userPromptContentKey: string | undefined;
43
+ }): void;
44
+ setShowAiTemplatesTab({ showAiTemplatesTab, }: {
45
+ showAiTemplatesTab: boolean;
46
+ }): void;
41
47
  onBackgroundChanged(): void;
42
48
  onContentChanged(): void;
43
49
  onStyleChanged(): void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@designcrowd/library.brand-page",
3
3
  "description": "A collection of Maker/Designer applications",
4
- "version": "5.19.1",
4
+ "version": "5.19.2",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -929,6 +929,10 @@ export class BrandPageModel {
929
929
  // Model version
930
930
  v: number;
931
931
 
932
+ // AI personalisation key persisted with the draft so previews survive login/checkout-back/
933
+ // refresh - content is retrieved by this token (user-agnostic), not regenerated (bp-5742).
934
+ userPromptContentKey?: string;
935
+
932
936
  styles: PageStyles;
933
937
 
934
938
  /** @deprecated */
@@ -456,6 +456,7 @@ exports[`migration.util > Banner Contents - text Align and Font Family should no
456
456
  },
457
457
  },
458
458
  },
459
+ "userPromptContentKey": undefined,
459
460
  "v": 17,
460
461
  },
461
462
  "useOldLogoRenderSize": true,
@@ -901,6 +902,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
901
902
  },
902
903
  },
903
904
  },
905
+ "userPromptContentKey": undefined,
904
906
  "v": 17,
905
907
  },
906
908
  "useOldLogoRenderSize": true,
@@ -1353,6 +1355,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
1353
1355
  },
1354
1356
  },
1355
1357
  },
1358
+ "userPromptContentKey": undefined,
1356
1359
  "v": 17,
1357
1360
  },
1358
1361
  "useOldLogoRenderSize": true,
@@ -472,6 +472,7 @@ exports[`migration02.ts > migration02 should return converted letterspacing 1`]
472
472
  },
473
473
  },
474
474
  },
475
+ "userPromptContentKey": undefined,
475
476
  "v": 0,
476
477
  },
477
478
  "useOldLogoRenderSize": false,
@@ -446,6 +446,7 @@ BrandPageModel {
446
446
  },
447
447
  },
448
448
  },
449
+ "userPromptContentKey": undefined,
449
450
  "v": 0,
450
451
  }
451
452
  `;
@@ -911,6 +912,7 @@ BrandPageModel {
911
912
  },
912
913
  },
913
914
  },
915
+ "userPromptContentKey": undefined,
914
916
  "v": 0,
915
917
  }
916
918
  `;
@@ -701,6 +701,41 @@ describe('personalisation.util', async () => {
701
701
  ).toBe(title);
702
702
  });
703
703
 
704
+ test('applyUserPersonalisationToBrandPageModelAsync should fall back to a populated section when the block section was not generated', async () => {
705
+ // Arrange - simulate userPrompt AI content that only generated the core sections
706
+ // (profile/about/information/media); email/website/phone are never generated.
707
+ const personalisation = {
708
+ ...PERSONALISATION,
709
+ websiteSection: undefined,
710
+ emailSection: undefined,
711
+ phoneSection: undefined,
712
+ } as unknown as typeof PERSONALISATION;
713
+
714
+ // A block whose role points at the ungenerated website section - without the fallback
715
+ // this renders the designer's placeholder instead of personalised content.
716
+ const model = new BrandPageModel({
717
+ brandPageType: BRAND_PAGE_TYPES.brandSite,
718
+ styles: {
719
+ backgroundStyles: {
720
+ backgroundImageUrl: faker.internet.url(),
721
+ personalisationRole: 'website',
722
+ },
723
+ },
724
+ });
725
+
726
+ // Act
727
+ const result = await applyUserPersonalisationToBrandPageModelAsync(
728
+ personalisation,
729
+ model,
730
+ );
731
+
732
+ // Assert - it borrowed the first populated fallback section (profile).
733
+ expect(result.isModified).toBe(true);
734
+ expect(result.model.styles.backgroundStyles.backgroundImageUrl).toBe(
735
+ PERSONALISATION.profileSection.imageUrls?.[0].imageUrl,
736
+ );
737
+ });
738
+
704
739
  test('applyUserPersonalisationToBrandPageModelAsync should return modified text items when input has text with multiple items', async () => {
705
740
  // Arrange
706
741
  const textContent = {
@@ -326,6 +326,84 @@ const getPersonalisationValueAsync = async (
326
326
  return { value: propertyValue, section };
327
327
  };
328
328
 
329
+ // The userPrompt AI content generation only populates these content-bearing sections
330
+ // (see GenerateAiDesignMakerContentService / GenerateWebsiteContentService - About,
331
+ // Information, Profile, Media). Sections like email/website/phone are never generated, so
332
+ // blocks whose role points at an ungenerated section fall back to this pool.
333
+ const PERSONALISATION_FALLBACK_ROLES: PersonalisationRole[] = [
334
+ 'profile',
335
+ 'about',
336
+ 'information',
337
+ 'media',
338
+ ];
339
+
340
+ // "Effectively empty" = no renderable content (ungenerated userPrompt sections like
341
+ // email/website/phone). A section that has data but lacks one property is NOT empty - it
342
+ // intentionally keeps its original content.
343
+ const isSectionEffectivelyEmpty = (
344
+ section: UserPersonalisationSectionWithIndices | undefined,
345
+ ): boolean => {
346
+ if (!section) {
347
+ return true;
348
+ }
349
+
350
+ return (
351
+ !section.title &&
352
+ !section.name &&
353
+ !section.url &&
354
+ !section.description &&
355
+ !section.descriptionShort &&
356
+ !section.ctaTitle &&
357
+ !section.imageUrls?.length &&
358
+ !section.shapes?.length &&
359
+ !section.items?.length &&
360
+ !section.mainShapesCollection?.tall?.length &&
361
+ !section.mainShapesCollection?.square?.length &&
362
+ !section.mainShapesCollection?.wide?.length
363
+ );
364
+ };
365
+
366
+ // Resolve a property from a specific role's section. Returns null when that section is
367
+ // ungenerated (effectively empty) or cannot supply the property, so the caller can try the
368
+ // next role.
369
+ const resolvePersonalisationValueForRoleAsync = async (
370
+ role: PersonalisationRole,
371
+ property: keyof UserPersonalisationSection | keyof UserPersonalisationItem,
372
+ personalisation: UserPersonalisationWithIndices,
373
+ ): Promise<{
374
+ value: any;
375
+ personalisation: UserPersonalisationWithIndices;
376
+ valuesToUpdate?: Record<string, any>;
377
+ } | null> => {
378
+ const key = `${role}Section` as keyof UserPersonalisationWithIndices;
379
+ const section = personalisation[key] as
380
+ | UserPersonalisationSectionWithIndices
381
+ | undefined;
382
+
383
+ if (isSectionEffectivelyEmpty(section)) {
384
+ return null;
385
+ }
386
+
387
+ const {
388
+ value,
389
+ section: updatedSection,
390
+ valuesToUpdate,
391
+ } = await getPersonalisationValueAsync(
392
+ section as UserPersonalisationSectionWithIndices,
393
+ property,
394
+ );
395
+
396
+ if (value === undefined || value === null) {
397
+ return null;
398
+ }
399
+
400
+ return {
401
+ value,
402
+ personalisation: { ...personalisation, [key]: updatedSection },
403
+ valuesToUpdate,
404
+ };
405
+ };
406
+
329
407
  const getPersonalisationValueFromPropertyAsync = async (
330
408
  propertyKey: string,
331
409
  propertyValue: any,
@@ -342,16 +420,6 @@ const getPersonalisationValueFromPropertyAsync = async (
342
420
  return { value: undefined, personalisation };
343
421
  }
344
422
 
345
- const matchingValue = Object.entries(personalisation).find(
346
- ([key]) => key === `${personalisationRole}Section`,
347
- );
348
-
349
- if (!matchingValue) {
350
- return { value: undefined, personalisation };
351
- }
352
-
353
- const section = matchingValue[1] as UserPersonalisationSectionWithIndices;
354
-
355
423
  const mapping = getPersonalisationMappingForContentType(contentType);
356
424
 
357
425
  const matchingProperty = Object.entries(mapping).find(([, value]) =>
@@ -372,20 +440,48 @@ const getPersonalisationValueFromPropertyAsync = async (
372
440
  typeof propertyValue === 'string' && propertyValue.endsWith('.svg'),
373
441
  });
374
442
 
375
- const {
376
- value,
377
- section: updatedSection,
378
- valuesToUpdate,
379
- } = await getPersonalisationValueAsync(section, property);
443
+ const primaryKey =
444
+ `${personalisationRole}Section` as keyof UserPersonalisationWithIndices;
445
+ const primarySection = personalisation[primaryKey] as
446
+ | UserPersonalisationSectionWithIndices
447
+ | undefined;
448
+
449
+ // When the block's own section has content, use it as-is - this preserves existing
450
+ // behaviour, including legitimately keeping the original when that section lacks a
451
+ // specific property (e.g. no items, or an invalid shape).
452
+ if (!isSectionEffectivelyEmpty(primarySection)) {
453
+ const {
454
+ value,
455
+ section: updatedSection,
456
+ valuesToUpdate,
457
+ } = await getPersonalisationValueAsync(
458
+ primarySection as UserPersonalisationSectionWithIndices,
459
+ property,
460
+ );
380
461
 
381
- return {
382
- value,
383
- personalisation: {
384
- ...personalisation,
385
- [`${personalisationRole}Section`]: updatedSection,
386
- },
387
- valuesToUpdate,
388
- };
462
+ return {
463
+ value,
464
+ personalisation: { ...personalisation, [primaryKey]: updatedSection },
465
+ valuesToUpdate,
466
+ };
467
+ }
468
+
469
+ // The block's section was never generated - fall back to the first populated section that
470
+ // can supply this property, so it doesn't render the "should be AI generated" placeholder.
471
+ // Never reached when the section exists (above) or all are populated (AI Website Builder).
472
+ for (const role of PERSONALISATION_FALLBACK_ROLES) {
473
+ const resolved = await resolvePersonalisationValueForRoleAsync(
474
+ role,
475
+ property,
476
+ personalisation,
477
+ );
478
+
479
+ if (resolved) {
480
+ return resolved;
481
+ }
482
+ }
483
+
484
+ return { value: undefined, personalisation };
389
485
  };
390
486
 
391
487
  type ContentProperty = Record<string, string | any>;
@@ -70,6 +70,8 @@ export interface BrandPageMakerViewModel {
70
70
  initialCategories: InitialCategories;
71
71
  personalisation?: UserPersonalisationContent[];
72
72
  isPersonalisationEnabled?: boolean;
73
+ userPromptContentKey?: string;
74
+ showAiTemplatesTab?: boolean;
73
75
  brandPageType?: BrandPageType;
74
76
  businessName?: string;
75
77
  industry?: string;
@@ -227,6 +229,8 @@ export default defineComponent({
227
229
  personalisationData: this.viewModel.personalisation,
228
230
  isPersonalisationEnabled:
229
231
  this.viewModel.isPersonalisationEnabled || false,
232
+ userPromptContentKey: this.viewModel.userPromptContentKey,
233
+ showAiTemplatesTab: this.viewModel.showAiTemplatesTab || false,
230
234
  brandPageType: this.viewModel.brandPageType,
231
235
  businessName: this.viewModel.businessName,
232
236
  industry: this.viewModel.industry,
@@ -270,6 +270,7 @@ const InitialiseMakerStores = (options: {
270
270
  isContextualUpgradeModalVisible: false,
271
271
  gettyVideos: [],
272
272
  templateSearchTerm: undefined,
273
+ aiTemplateKeywords: undefined,
273
274
  });
274
275
 
275
276
  userStore.setUserState({
@@ -15,10 +15,7 @@ import {
15
15
  getBrandPageViewModelFromBrandPageModel,
16
16
  } from '@brand-page/utils';
17
17
  import { STYLE_THEME_KEYS } from '@brand-page/constants';
18
- import {
19
- TEMPLATE_TABS,
20
- type TemplateSettingsTemplate,
21
- } from '@shared/template-settings/models';
18
+ import type { TemplateSettingsTemplate } from '@shared/template-settings/models';
22
19
  import type { DebounceHandle } from 'lodash.debounce';
23
20
  import debounce from 'lodash.debounce';
24
21
  import { MOBILE_HEIGHT, MOBILE_WIDTH } from '@brand-page-maker/constants';
@@ -74,7 +71,6 @@ export default defineComponent({
74
71
  isBrandSite: computed(
75
72
  () => brandPageMakerStore.brandPageType === BRAND_PAGE_TYPES.brandSite,
76
73
  ),
77
- selectedTab: computed(() => templateSettingsStore.selectedTab.key),
78
74
  renderAsImage: computed(() => templateSettingsStore.renderAsImage),
79
75
  brandPageMakerUserStore,
80
76
  brandPageContainerStore,
@@ -241,10 +237,28 @@ export default defineComponent({
241
237
  return '';
242
238
  }
243
239
 
244
- if (this.selectedTab === TEMPLATE_TABS.AI && this.template.contentKey) {
245
- if (this.userToken) {
246
- previewImageUrl += `&contentKey=${this.template.contentKey}&userToken=${this.userToken}`;
240
+ const isAlreadyPersonalised =
241
+ previewImageUrl.includes('contentKey=') ||
242
+ previewImageUrl.includes('userPromptContentKey=');
243
+
244
+ if (
245
+ this.isPersonalisable &&
246
+ this.template.contentKey &&
247
+ !isAlreadyPersonalised
248
+ ) {
249
+ if (this.userToken && !this.template.usesUserPromptContentKey) {
250
+ previewImageUrl += `&contentKey=${this.template.contentKey}`;
251
+
252
+ // The backend already bakes userToken into brand-site preview URLs. Only add
253
+ // it when missing - two userToken params parse as an array and the preview
254
+ // renderer rejects it ("userToken must be a string type") (bp-5765).
255
+ if (!previewImageUrl.includes('userToken=')) {
256
+ previewImageUrl += `&userToken=${this.userToken}`;
257
+ }
247
258
  } else {
259
+ // userPromptContentKey resolves from the userPrompt S3 bucket (real AI images).
260
+ // Post-purchase designs with a userPrompt key route here too, NOT through the
261
+ // userToken/regular-bucket branch above which holds placeholders (bp-5454).
248
262
  previewImageUrl += `&userPromptContentKey=${this.template.contentKey}`;
249
263
  }
250
264
  }
@@ -2,7 +2,7 @@
2
2
  <div
3
3
  ref="container"
4
4
  data-component="template-preview"
5
- class="template-preview-container hover:!tw-shadow-[0_0_0_3px] hover:!tw-shadow-info-500 tw-overflow-hidden"
5
+ class="template-preview-container hover:!tw-shadow-[0_0_0_3px] hover:!tw-shadow-info-500 tw-overflow-hidden tw-relative tw-isolate"
6
6
  :style="containerStyle"
7
7
  :class="{
8
8
  '!tw-shadow-[0_0_0_3px] !tw-shadow-info-500':
@@ -55,17 +55,10 @@
55
55
  />
56
56
  </div>
57
57
  <!-- End Preview Image -->
58
- <!-- Start personalisation tag -->
59
- <div
60
- v-if="isPersonalisable"
61
- class="tw-absolute tw-bottom-0 tw-right-0 tw-p-2 tw-z-50 tw-text-white"
62
- style="background: lightgreen"
63
- >
64
- <span>Personalisable</span>
65
- </div>
66
- <!-- End personalisation tag -->
67
58
  </div>
68
59
  </div>
60
+ <!-- AI badge outside scaled container so it renders at natural size -->
61
+ <TemplatePreviewAiBadge v-if="isPersonalisable" />
69
62
  </div>
70
63
  </template>
71
64
  <script>
@@ -73,11 +66,13 @@ import { defineComponent } from 'vue';
73
66
  import TemplatePreviewMixin from './TemplatePreview.mixin.ts';
74
67
  import Page from '@brand-page/components/page/Page.vue';
75
68
  import { Loader } from '@designcrowd/fe-shared-lib';
69
+ import TemplatePreviewAiBadge from './TemplatePreviewAiBadge.vue';
76
70
 
77
71
  export default defineComponent({
78
72
  components: {
79
73
  Page,
80
74
  Loader,
75
+ TemplatePreviewAiBadge,
81
76
  },
82
77
  mixins: [TemplatePreviewMixin],
83
78
  setup(props, context) {
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <div
3
+ data-test-ai-design
4
+ class="tw-flex tw-flex-row tw-absolute tw-top-2.5 tw-left-2.5 tw-rounded-full tw-py-1 tw-px-2 tw-text-white tw-text-xs tw-font-bold tw-z-[25] tw-bg-gradient-to-t tw-from-[#d946ef] tw-to-[#8b5cf6]"
5
+ >
6
+ <Icon name="ai" class="tw-text-white tw-pr-1" size="sm" />
7
+ <span>AI</span>
8
+ </div>
9
+ </template>
10
+ <script setup lang="ts">
11
+ import { Icon } from '@designcrowd/fe-shared-lib';
12
+ </script>
@@ -123,6 +123,7 @@ export type InitialisePayload = Omit<
123
123
  logoDraftAssets: LogoDraftAsset[];
124
124
  gettyVideos: GettyVideoEditorViewModelResponse[];
125
125
  templateSearchTerm: string | undefined;
126
+ aiTemplateKeywords: string | undefined;
126
127
  onSearchTermChanged?: OnSearchTermChangedCallback;
127
128
  };
128
129
 
@@ -266,6 +267,10 @@ export const useBrandPageContainerStore = defineStore('brandPageContainer', {
266
267
  onSearchTermChanged: payload.onSearchTermChanged,
267
268
  });
268
269
 
270
+ templateSettingsStore.setAiTemplateKeywords({
271
+ aiTemplateKeywords: payload.aiTemplateKeywords ?? '',
272
+ });
273
+
269
274
  brandPageMakerStore.setSelectedTemplateId({
270
275
  templateId: this.designTemplateToken,
271
276
  });
@@ -125,6 +125,8 @@ export interface InitialisePayload {
125
125
  templateCategories: TemplateCategory[];
126
126
  personalisationData?: UserPersonalisationContent[];
127
127
  isPersonalisationEnabled: boolean;
128
+ userPromptContentKey?: string;
129
+ showAiTemplatesTab?: boolean;
128
130
  businessName: string | undefined;
129
131
  industry: string | undefined;
130
132
  layout?: string;
@@ -1230,6 +1232,8 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
1230
1232
  templateCategories,
1231
1233
  personalisationData,
1232
1234
  isPersonalisationEnabled,
1235
+ userPromptContentKey,
1236
+ showAiTemplatesTab = false,
1233
1237
  businessName,
1234
1238
  industry,
1235
1239
  layout,
@@ -1353,6 +1357,10 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
1353
1357
  });
1354
1358
  }
1355
1359
 
1360
+ uiStore.setUserPromptContentKey({ userPromptContentKey });
1361
+
1362
+ uiStore.setShowAiTemplatesTab({ showAiTemplatesTab });
1363
+
1356
1364
  templateSettingsStore.setTemplateCategories({
1357
1365
  categories: templateCategories,
1358
1366
  });
@@ -1800,6 +1808,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
1800
1808
  const navigationStyleSettingsStore = useUiNavigationStyleSettingsStore();
1801
1809
  const shopStore = useShopConfigStore();
1802
1810
  const pageNavigationStore = usePageNavigationStore();
1811
+ const uiStore = useUiStore();
1803
1812
 
1804
1813
  const pages = pageSettingsStore.pages.map((p) => ({
1805
1814
  ...p,
@@ -1817,6 +1826,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
1817
1826
 
1818
1827
  return {
1819
1828
  v: GetLatestModelVersion(),
1829
+ userPromptContentKey: uiStore.userPromptContentKey,
1820
1830
  pages,
1821
1831
  styles: {
1822
1832
  logoStyles,
@@ -48,6 +48,8 @@ export const useUiStore = defineStore('ui', {
48
48
  },
49
49
  personalisationData: undefined,
50
50
  isPersonalisationEnabled: false,
51
+ userPromptContentKey: undefined,
52
+ showAiTemplatesTab: false,
51
53
  }),
52
54
  actions: {
53
55
  setLogoTheme(payload: { logoTheme: LogoTheme }) {
@@ -78,6 +80,20 @@ export const useUiStore = defineStore('ui', {
78
80
  }) {
79
81
  this.personalisationData = personalisationData;
80
82
  },
83
+ setUserPromptContentKey({
84
+ userPromptContentKey,
85
+ }: {
86
+ userPromptContentKey: string | undefined;
87
+ }) {
88
+ this.userPromptContentKey = userPromptContentKey;
89
+ },
90
+ setShowAiTemplatesTab({
91
+ showAiTemplatesTab,
92
+ }: {
93
+ showAiTemplatesTab: boolean;
94
+ }) {
95
+ this.showAiTemplatesTab = showAiTemplatesTab;
96
+ },
81
97
  onBackgroundChanged() {
82
98
  const brandPageMakerStore = useBrandPageMakerStore();
83
99
 
@@ -11,6 +11,7 @@ interface DesignTemplateClientOptions {}
11
11
  interface GetTemplatesAsyncOptions {
12
12
  categoryName?: string;
13
13
  searchTerm?: string;
14
+ keywords?: string;
14
15
  side?: string;
15
16
  logoDraftToken?: string;
16
17
  logoVariation?: number;
@@ -22,6 +23,8 @@ interface GetTemplatesAsyncOptions {
22
23
  locale?: string;
23
24
  logoS3Token?: string;
24
25
  logoTemplateToken?: string;
26
+ pageSection?: string;
27
+ pageName?: string;
25
28
  }
26
29
 
27
30
  export const getTemplatesEndpoint = (templateTypeName: string) =>
@@ -4,6 +4,7 @@ export interface Paginated<T> {
4
4
  pageSize: number;
5
5
  pageCount: number;
6
6
  totalResultCount: number;
7
+ abTestValuesToBePushed?: Record<string, string>;
7
8
  }
8
9
 
9
10
  export interface DesignTemplate {
@@ -14,6 +15,7 @@ export interface DesignTemplate {
14
15
 
15
16
  export interface PaginatedDesignTemplateItem {
16
17
  designTemplateToken: string;
18
+ isPersonalisable?: boolean;
17
19
  previewImageUrl?: string;
18
20
  }
19
21
 
@@ -19,6 +19,12 @@ export interface UIRootState {
19
19
  thirdPartySettings: UIRootStateThirdPartySettings;
20
20
  personalisationData: UserPersonalisationContent[] | undefined;
21
21
  isPersonalisationEnabled: boolean;
22
+ // Pre-purchase AI content key used to personalise AI template previews when
23
+ // there is no post-purchase personalisationData (bp-5742).
24
+ userPromptContentKey: string | undefined;
25
+ // Controls visibility of the dedicated AI templates tab. Decoupled from personalisationData
26
+ // so the slug flow can personalise previews/apply without surfacing the AI tab (bp-5742).
27
+ showAiTemplatesTab: boolean;
22
28
  }
23
29
 
24
30
  export interface Endpoints {
@@ -60,7 +60,7 @@ export default defineComponent({
60
60
  templateCategories: computed(
61
61
  () => templateSettingsStore.templateCategories,
62
62
  ),
63
- personalisationData: computed(() => uiStore.personalisationData),
63
+ showAiTemplatesTab: computed(() => uiStore.showAiTemplatesTab),
64
64
  templateSettingsStore,
65
65
  uiStore,
66
66
  isDesignCom: computed(() => containerStore.isDesignCom),
@@ -155,7 +155,7 @@ export default defineComponent({
155
155
  },
156
156
 
157
157
  visibleTabs() {
158
- return this.personalisationData
158
+ return this.showAiTemplatesTab
159
159
  ? this.tabs
160
160
  : [...this.tabs].splice(
161
161
  0,
@@ -7,6 +7,7 @@ export interface TemplateSettingsTemplate {
7
7
  personalisationContentIndex?: number;
8
8
  brandPageModel: BrandPageModel;
9
9
  contentKey?: string;
10
+ usesUserPromptContentKey?: boolean;
10
11
  previewImageUrl?: string;
11
12
  }
12
13