@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.
- package/lib/src/{CreatedOnBrandCrowd-de-de-BmeMc9oz.js → CreatedOnBrandCrowd-de-de-Ekmn0O3s.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CQNdm_6P.js → CreatedOnBrandCrowd-en-us-BjeXlKTS.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-BrmHWeAM.js → CreatedOnBrandCrowd-es-es-rpNINAMY.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-CaIyjTXl.js → CreatedOnBrandCrowd-fr-ca-BL4nSaLj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-CaIyjTXl.js → CreatedOnBrandCrowd-fr-fr-BL4nSaLj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BClE0Nd9.js → CreatedOnBrandCrowd-pt-br-K0r4FyYq.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BClE0Nd9.js → CreatedOnBrandCrowd-pt-pt-K0r4FyYq.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-CkrdlPqK.js → CreatedOnDcom-de-de-0XYyEKYc.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-D33J9qky.js → CreatedOnDcom-en-us-E8hvwiMd.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-BsmInngw.js → CreatedOnDcom-es-es-pRuCd7xX.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DOXlsRea.js → CreatedOnDcom-fr-ca-Bu4V3jpn.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-SfFYLtyn.js → CreatedOnDcom-fr-fr-CCcrGNz2.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DcWeuZUy.js → CreatedOnDcom-pt-br-1GTvaLKd.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-DZsZZ4iX.js → CreatedOnDcom-pt-pt-CTIYW10X.js} +1 -1
- package/lib/src/brand-page/models/index.d.ts +1 -0
- package/lib/src/brand-page/models/index.js +3 -0
- package/lib/src/brand-page/utils/personalisation.util.js +72 -14
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/admin-background-settings/AdminBackgroundSettings.mixin.d.ts +6 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +12 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +2 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.js +1 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +2 -3
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +16 -5
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +6 -0
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +1 -0
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +3 -0
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +3 -1
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +5 -1
- package/lib/src/brand-page-maker/stores/ui/ui-store.d.ts +6 -0
- package/lib/src/brand-page-maker/stores/ui/ui-store.js +8 -0
- package/lib/src/clients/design-template-client.d.ts +3 -0
- package/lib/src/clients/models.d.ts +2 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +19 -7
- package/lib/src/css/library.brand-page-brandPage.min.css +19 -7
- package/lib/src/css/library.brand-page-designCom.min.css +19 -7
- package/lib/src/css/library.brand-page-preview.min.css +19 -7
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-C5vGkjfs.js → lib-BfgUqu0B.js} +22777 -22678
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +7 -1
- package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +9 -0
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +1 -1
- package/lib/src/shared/logo-versions/LogoVersions.mixin.d.ts +6 -0
- package/lib/src/shared/models.d.ts +2 -0
- package/lib/src/shared/my-designs/MyDesigns.mixin.d.ts +6 -0
- package/lib/src/shared/my-uploads/MyMedia.mixin.d.ts +6 -0
- package/lib/src/shared/my-uploads/MyUploads.mixin.d.ts +6 -0
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +7 -1
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +7 -1
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -1
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +7 -1
- package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +10 -439
- package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.js +2 -2
- package/lib/src/shared/template-settings/models.d.ts +1 -0
- package/lib/src/shared/template-settings/store/template-settings-store.d.ts +4 -0
- package/lib/src/shared/template-settings/store/template-settings-store.js +79 -11
- package/lib/src/shared/unsplash-library/UnsplashLibrary.mixin.d.ts +6 -0
- package/package.json +1 -1
- package/src/brand-page/models/index.ts +4 -0
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -0
- package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +1 -0
- package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +2 -0
- package/src/brand-page/utils/personalisation.util.test.ts +35 -0
- package/src/brand-page/utils/personalisation.util.ts +119 -23
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +4 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.ts +1 -0
- package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +22 -8
- package/src/brand-page-maker/components/template-preview/TemplatePreview.vue +5 -10
- package/src/brand-page-maker/components/template-preview/TemplatePreviewAiBadge.vue +12 -0
- package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +5 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +10 -0
- package/src/brand-page-maker/stores/ui/ui-store.ts +16 -0
- package/src/clients/design-template-client.ts +3 -0
- package/src/clients/models.ts +2 -0
- package/src/shared/models.ts +6 -0
- package/src/shared/template-settings/components/TemplatesSettings.mixin.ts +2 -2
- package/src/shared/template-settings/models.ts +1 -0
- 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
|
@@ -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,
|
|
@@ -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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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,
|
|
@@ -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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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) =>
|
package/src/clients/models.ts
CHANGED
|
@@ -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
|
|
package/src/shared/models.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
158
|
+
return this.showAiTemplatesTab
|
|
159
159
|
? this.tabs
|
|
160
160
|
: [...this.tabs].splice(
|
|
161
161
|
0,
|