@designcrowd/library.brand-page 6.0.21 → 6.0.23-ast-remove

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 (92) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-C52IyXZu.js → CreatedOnBrandCrowd-de-de-C2IaS_Mb.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DhTy_dEV.js → CreatedOnBrandCrowd-en-us-AtykytDu.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BJelIAQ4.js → CreatedOnBrandCrowd-es-es-CBzMnp_n.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-DwJohy0T.js → CreatedOnBrandCrowd-fr-ca-Cz7WzWc3.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-DwJohy0T.js → CreatedOnBrandCrowd-fr-fr-Cz7WzWc3.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-CMzKbxdG.js → CreatedOnBrandCrowd-pt-br-DE3vjPyC.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-CMzKbxdG.js → CreatedOnBrandCrowd-pt-pt-DE3vjPyC.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-DAy1GHrw.js → CreatedOnDcom-de-de-DlcGgBF6.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-CgBDAkJN.js → CreatedOnDcom-en-us-D5jdeN_E.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-Ds9d5rem.js → CreatedOnDcom-es-es-BBB7ZE8B.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-CZL0BQ-u.js → CreatedOnDcom-fr-ca-odHmWYoP.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-BVYh5Wvo.js → CreatedOnDcom-fr-fr-DKr11T_P.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-1hsWNXfq.js → CreatedOnDcom-pt-br-s8blQRF7.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-DvM0N-3M.js → CreatedOnDcom-pt-pt-Dga4RM96.js} +1 -1
  15. package/lib/src/brand-page/components/collapsible/Collapsible.mixin.d.ts +11 -0
  16. package/lib/src/brand-page/components/collapsible/Collapsible.mixin.js +13 -1
  17. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +1 -0
  18. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +12 -3
  19. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +5 -0
  20. package/lib/src/brand-page/components/payment/Payment.mixin.js +5 -0
  21. package/lib/src/brand-page/models/index.d.ts +1 -1
  22. package/lib/src/brand-page/models/index.js +0 -3
  23. package/lib/src/brand-page/models/payment.model.d.ts +2 -0
  24. package/lib/src/brand-page/utils/button.util.d.ts +5 -0
  25. package/lib/src/brand-page/utils/button.util.js +13 -0
  26. package/lib/src/brand-page/utils/migration/migration-19.d.ts +2 -0
  27. package/lib/src/brand-page/utils/migration/migration-19.js +41 -0
  28. package/lib/src/brand-page/utils/migration.util.js +11 -0
  29. package/lib/src/brand-page/utils/view-model.util.js +1 -0
  30. package/lib/src/brand-page-maker/assessors/contact-form-visibility.assessor.d.ts +1 -0
  31. package/lib/src/brand-page-maker/assessors/contact-form-visibility.assessor.js +16 -7
  32. package/lib/src/brand-page-maker/constants/config-settings-constant.js +40 -3
  33. package/lib/src/brand-page-maker/default-getters/contact-form-card-color.default-getter.d.ts +8 -0
  34. package/lib/src/brand-page-maker/default-getters/contact-form-card-color.default-getter.js +16 -0
  35. package/lib/src/brand-page-maker/default-getters/index.d.ts +1 -0
  36. package/lib/src/brand-page-maker/default-getters/index.js +1 -0
  37. package/lib/src/brand-page-maker/models/button-styles-config.js +15 -3
  38. package/lib/src/brand-page-maker/models/content-settings-configs.d.ts +2 -1
  39. package/lib/src/brand-page-maker/models/content-settings.js +15 -3
  40. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +0 -6
  41. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +0 -2
  42. package/lib/src/css/library.brand-page-brandCrowd.min.css +96 -68
  43. package/lib/src/css/library.brand-page-brandPage.min.css +96 -68
  44. package/lib/src/css/library.brand-page-designCom.min.css +96 -68
  45. package/lib/src/css/library.brand-page-preview.min.css +96 -68
  46. package/lib/src/index.mjs +1 -1
  47. package/lib/src/{lib-CK2FPBv5.js → lib-aLiBOhnh.js} +18776 -18430
  48. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +5 -0
  49. package/lib/src/shared/content-settings/models.d.ts +1 -0
  50. package/lib/src/shared/content-settings/store/content-settings-store.js +3 -0
  51. package/lib/src/shared/template-settings/store/template-settings-store.js +4 -4
  52. package/package.json +2 -2
  53. package/src/brand-page/components/button/ButtonStyleWrapper.vue +112 -44
  54. package/src/brand-page/components/button/ButtonStyleWrapper.vue.test.ts +167 -0
  55. package/src/brand-page/components/collapsible/Collapsible.mixin.ts +14 -1
  56. package/src/brand-page/components/contact-form/ContactForm.mixin.test.ts +18 -0
  57. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +15 -3
  58. package/src/brand-page/components/contact-form/ContactForm.vue +97 -36
  59. package/src/brand-page/components/mailing-list/MailingList.mixin.ts +6 -0
  60. package/src/brand-page/components/mailing-list/MailingList.vue +18 -10
  61. package/src/brand-page/components/payment/Payment.mixin.ts +6 -0
  62. package/src/brand-page/components/payment/Payment.vue +43 -24
  63. package/src/brand-page/components/payment/RequestPaymentStripe.vue +26 -22
  64. package/src/brand-page/components/shop/PaymentModal.vue +3 -3
  65. package/src/brand-page/components/shop/Shop.vue +17 -8
  66. package/src/brand-page/components/shop/Shop.vue.test.ts +209 -0
  67. package/src/brand-page/models/index.ts +1 -4
  68. package/src/brand-page/models/payment.model.ts +2 -0
  69. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -6
  70. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +0 -1
  71. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +0 -2
  72. package/src/brand-page/utils/button.util.ts +17 -0
  73. package/src/brand-page/utils/migration/migration-19.test.ts +172 -0
  74. package/src/brand-page/utils/migration/migration-19.ts +65 -0
  75. package/src/brand-page/utils/migration.util.ts +12 -0
  76. package/src/brand-page/utils/view-model.util.ts +1 -0
  77. package/src/brand-page-maker/assessors/contact-form-visibility.assessor.ts +34 -26
  78. package/src/brand-page-maker/constants/config-settings-constant.ts +43 -2
  79. package/src/brand-page-maker/default-getters/contact-form-card-color.default-getter.ts +23 -0
  80. package/src/brand-page-maker/default-getters/index.ts +1 -0
  81. package/src/brand-page-maker/models/button-styles-config.ts +15 -3
  82. package/src/brand-page-maker/models/content-settings-configs.ts +4 -1
  83. package/src/brand-page-maker/models/content-settings.ts +16 -3
  84. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +0 -2
  85. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +6 -0
  86. package/src/shared/content-settings/models.ts +3 -0
  87. package/src/shared/content-settings/store/content-settings-store.ts +3 -0
  88. package/src/shared/style-settings/components/StyleSettings.vue +1 -0
  89. package/src/shared/template-settings/store/template-settings-store.ts +6 -4
  90. package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +0 -13
  91. package/lib/src/metadata-builder/builders/reorder-pages-builder.js +0 -49
  92. package/src/metadata-builder/builders/reorder-pages-builder.ts +0 -80
@@ -0,0 +1,209 @@
1
+ import { vi } from 'vitest';
2
+ import { shallowMount } from '@vue/test-utils';
3
+ import Shop from './Shop.vue';
4
+ import PaymentModal from './PaymentModal.vue';
5
+ import { createPinia, setActivePinia } from 'pinia';
6
+ import {
7
+ BRAND_PAGE_TYPES,
8
+ ButtonStyles,
9
+ MEDIA_SIZES,
10
+ type BrandPageContext,
11
+ } from '@brand-page/models';
12
+ import type { ShopViewModel } from '@brand-page/view-models';
13
+
14
+ vi.mock('../../clients/brand-page.client', () => ({
15
+ default: {
16
+ getPaymentConfigStatusByPaymentConfigId: vi.fn(),
17
+ createBrandPageCart: vi.fn(),
18
+ },
19
+ }));
20
+
21
+ vi.mock('@brand-page/utils/raygun.util', () => ({
22
+ default: { safeSendRaygunError: vi.fn() },
23
+ }));
24
+
25
+ const createContext = (): BrandPageContext => ({
26
+ brandPageSlug: 'test',
27
+ brandPageToken: '',
28
+ isMobile: false,
29
+ isPreview: true,
30
+ isProduction: false,
31
+ isServerRender: false,
32
+ options: { mediaSizes: MEDIA_SIZES.sizeThumb },
33
+ isTemplate: false,
34
+ isDesignCom: false,
35
+ pageTitle: 'Test',
36
+ runtimeEnvironment: '',
37
+ stripePublishableKey: undefined,
38
+ customDomain: undefined,
39
+ brandPageType: BRAND_PAGE_TYPES.brandSite,
40
+ isBrandPage: false,
41
+ isBrandPageOrIsBrandContact: false,
42
+ isBrandSite: true,
43
+ isBrandContact: false,
44
+ pages: [],
45
+ isCapturingPreview: false,
46
+ recaptchaSiteKey: undefined,
47
+ nav: { navItems: [] },
48
+ });
49
+
50
+ const PRODUCT = {
51
+ id: '1',
52
+ name: 'Test',
53
+ price: 1,
54
+ description: '',
55
+ imageUrl: '',
56
+ } as any;
57
+
58
+ const createViewModel = (
59
+ overrides: Partial<ShopViewModel> = {},
60
+ ): ShopViewModel =>
61
+ ({
62
+ id: '1',
63
+ type: 'shop-section',
64
+ role: 'shop-section',
65
+ title: 'Shop',
66
+ description: '',
67
+ successMessage: 'Thanks!',
68
+ products: [],
69
+ displayStyle: 'text',
70
+ aspectRatio: '1:1',
71
+ fillColor: {},
72
+ defaultBorderColor: {},
73
+ itemTextStyle: {},
74
+ backgroundStyle: {},
75
+ buttonStyles: new ButtonStyles(),
76
+ isFullWidth: true,
77
+ isShippingEnabled: false,
78
+ shopPaymentConfig: {
79
+ paymentConfig: {},
80
+ paymentOptionDisplayType: 'text',
81
+ paymentOptionImageType: 'none',
82
+ isCustomerRequestDetailsEnabled: false,
83
+ isShippingEnabled: false,
84
+ },
85
+ ...overrides,
86
+ }) as ShopViewModel;
87
+
88
+ const mountShop = (viewModel: ShopViewModel) =>
89
+ shallowMount(Shop, {
90
+ props: {
91
+ context: createContext(),
92
+ viewModel,
93
+ contentIndex: 0,
94
+ },
95
+ });
96
+
97
+ describe('Shop', () => {
98
+ beforeEach(() => {
99
+ setActivePinia(createPinia());
100
+ });
101
+
102
+ describe('buttonStyles defensive access', () => {
103
+ it('does not throw when buttonStyles has no style property', () => {
104
+ const vm = createViewModel({
105
+ buttonStyles: { selectedButtonStyleId: 'BUTTON_STYLE_1' } as any,
106
+ products: [PRODUCT],
107
+ });
108
+
109
+ expect(() => mountShop(vm)).not.toThrow();
110
+ });
111
+
112
+ it('does not throw when buttonStyles is undefined', () => {
113
+ const vm = createViewModel({
114
+ buttonStyles: undefined as any,
115
+ products: [PRODUCT],
116
+ });
117
+
118
+ expect(() => mountShop(vm)).not.toThrow();
119
+ });
120
+
121
+ it('renders correctly with fully populated buttonStyles', () => {
122
+ const vm = createViewModel({
123
+ buttonStyles: new ButtonStyles({
124
+ selectedButtonStyleId: 'BUTTON_STYLE_1',
125
+ style: {
126
+ color: 'rgb(255, 0, 0)',
127
+ backgroundColor: 'rgb(0, 0, 255)',
128
+ },
129
+ }),
130
+ products: [PRODUCT],
131
+ });
132
+
133
+ const wrapper = mountShop(vm);
134
+
135
+ expect(
136
+ wrapper.find('[data-component="button-style-wrapper"]').exists() ||
137
+ wrapper.findComponent({ name: 'ButtonStyleWrapper' }).exists(),
138
+ ).toBe(true);
139
+ });
140
+ });
141
+ });
142
+
143
+ describe('PaymentModal', () => {
144
+ beforeEach(() => {
145
+ setActivePinia(createPinia());
146
+ });
147
+
148
+ const mountPaymentModal = (buttonStyle: any) =>
149
+ shallowMount(PaymentModal, {
150
+ props: {
151
+ context: createContext(),
152
+ isVisible: true,
153
+ buttonStyle,
154
+ shopPaymentConfig: { paymentConfig: {} } as any,
155
+ descriptionStyle: undefined,
156
+ successMessage: 'Thanks!',
157
+ isShippingEnabled: false,
158
+ },
159
+ });
160
+
161
+ it('does not throw when given a ButtonStyles instance', () => {
162
+ const buttonStyles = new ButtonStyles({
163
+ selectedButtonStyleId: 'BUTTON_STYLE_1',
164
+ style: { color: 'rgb(255, 0, 0)', backgroundColor: 'rgb(0, 0, 255)' },
165
+ });
166
+
167
+ expect(() => mountPaymentModal(buttonStyles)).not.toThrow();
168
+ });
169
+
170
+ it('receives a ButtonStyles with .style (not flat CSS)', () => {
171
+ const buttonStyles = new ButtonStyles({
172
+ selectedButtonStyleId: 'BUTTON_STYLE_1',
173
+ style: { color: 'rgb(255, 0, 0)', backgroundColor: 'rgb(0, 0, 255)' },
174
+ });
175
+
176
+ const wrapper = mountPaymentModal(buttonStyles);
177
+ const prop = wrapper.props('buttonStyle');
178
+
179
+ expect(prop).toHaveProperty('style');
180
+ expect(prop).toHaveProperty('selectedButtonStyleId');
181
+ expect(prop.style.color).toBe('rgb(255, 0, 0)');
182
+ expect(prop.style.backgroundColor).toBe('rgb(0, 0, 255)');
183
+ });
184
+
185
+ it('passes buttonStyles through to RequestPaymentStripe', () => {
186
+ const buttonStyles = new ButtonStyles({
187
+ selectedButtonStyleId: 'BUTTON_STYLE_1',
188
+ style: { color: 'rgb(255, 0, 0)', backgroundColor: 'rgb(0, 0, 255)' },
189
+ });
190
+
191
+ const wrapper = mountPaymentModal(buttonStyles);
192
+ const stripe = wrapper.findComponent({
193
+ name: 'RequestPaymentStripe',
194
+ });
195
+
196
+ if (stripe.exists()) {
197
+ const stripeProp = stripe.props('buttonStyles');
198
+
199
+ expect(stripeProp).toHaveProperty('style');
200
+ expect(stripeProp.style.color).toBe('rgb(255, 0, 0)');
201
+ }
202
+ });
203
+
204
+ it('would fail with a flat CSS object — .style is undefined', () => {
205
+ const flatCss = { color: 'rgb(255,0,0)', backgroundColor: 'rgb(0,0,255)' };
206
+
207
+ expect((flatCss as any).style).toBeUndefined();
208
+ });
209
+ });
@@ -331,6 +331,7 @@ export interface MailingListContent extends Content {
331
331
  buttonText: string;
332
332
  titleStyle: TextStyleDeclaration;
333
333
  buttonTextStyle: TextStyleDeclaration;
334
+ backgroundStyle?: BrandPageCssStyle;
334
335
  }
335
336
 
336
337
  export const FORM_FIELD_TYPES = {
@@ -946,10 +947,6 @@ export class BrandPageModel {
946
947
  // Model version
947
948
  v: number;
948
949
 
949
- // AI personalisation key persisted with the draft so previews survive login/checkout-back/
950
- // refresh - content is retrieved by this token (user-agnostic), not regenerated (bp-5742).
951
- userPromptContentKey?: string;
952
-
953
950
  styles: PageStyles;
954
951
 
955
952
  /** @deprecated */
@@ -147,6 +147,7 @@ export interface RequestPaymentContent extends Content {
147
147
  descriptionStyle?: TextStyleDeclaration;
148
148
  itemTextStyle: TextStyleDeclaration;
149
149
  buttonTextStyle?: TextStyleDeclaration;
150
+ backgroundStyle?: BrandPageCssStyle;
150
151
  }
151
152
 
152
153
  export interface ShopContentConfig extends PaymentConfig {
@@ -191,6 +192,7 @@ export interface SupportMeContent extends Content {
191
192
  descriptionStyle: TextStyleDeclaration;
192
193
  buttonTextStyle: TextStyleDeclaration;
193
194
  itemTextStyle: TextStyleDeclaration;
195
+ backgroundStyle?: BrandPageCssStyle;
194
196
  }
195
197
 
196
198
  export interface CreateStripePaymentIntentResponse {
@@ -462,8 +462,7 @@ exports[`migration.util > Banner Contents - text Align and Font Family should no
462
462
  },
463
463
  },
464
464
  },
465
- "userPromptContentKey": undefined,
466
- "v": 18,
465
+ "v": 19,
467
466
  },
468
467
  "useOldLogoRenderSize": true,
469
468
  }
@@ -915,8 +914,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
915
914
  },
916
915
  },
917
916
  },
918
- "userPromptContentKey": undefined,
919
- "v": 18,
917
+ "v": 19,
920
918
  },
921
919
  "useOldLogoRenderSize": true,
922
920
  }
@@ -1375,8 +1373,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
1375
1373
  },
1376
1374
  },
1377
1375
  },
1378
- "userPromptContentKey": undefined,
1379
- "v": 18,
1376
+ "v": 19,
1380
1377
  },
1381
1378
  "useOldLogoRenderSize": true,
1382
1379
  }
@@ -478,7 +478,6 @@ exports[`migration02.ts > migration02 should return converted letterspacing 1`]
478
478
  },
479
479
  },
480
480
  },
481
- "userPromptContentKey": undefined,
482
481
  "v": 0,
483
482
  },
484
483
  "useOldLogoRenderSize": false,
@@ -452,7 +452,6 @@ BrandPageModel {
452
452
  },
453
453
  },
454
454
  },
455
- "userPromptContentKey": undefined,
456
455
  "v": 0,
457
456
  }
458
457
  `;
@@ -924,7 +923,6 @@ BrandPageModel {
924
923
  },
925
924
  },
926
925
  },
927
- "userPromptContentKey": undefined,
928
926
  "v": 0,
929
927
  }
930
928
  `;
@@ -37,3 +37,20 @@ export const getBoxShadowValue = (buttonStyles: ButtonStyles): string => {
37
37
 
38
38
  return boxShadowValue;
39
39
  };
40
+
41
+ // The "decorative" button styles — brushstroke / arrow / torn-paper — whose visual
42
+ // is drawn by ButtonStyleWrapper rather than plain CSS. Their banner-like SVG
43
+ // decoration needs the button to be reasonably wide to render legibly, so narrow
44
+ // (w-fit) hosts should widen when one of these is active (BP-5800).
45
+ export const BUTTON_STYLE_BRUSHSTROKE = 'BUTTON_STYLE_19';
46
+ export const BUTTON_STYLE_ARROW = 'BUTTON_STYLE_20';
47
+ export const BUTTON_STYLE_PAPER = 'BUTTON_STYLE_21';
48
+
49
+ export const DECORATIVE_BUTTON_STYLE_IDS = [
50
+ BUTTON_STYLE_BRUSHSTROKE,
51
+ BUTTON_STYLE_ARROW,
52
+ BUTTON_STYLE_PAPER,
53
+ ];
54
+
55
+ export const isDecorativeButtonStyle = (styleId?: string): boolean =>
56
+ !!styleId && DECORATIVE_BUTTON_STYLE_IDS.includes(styleId);
@@ -0,0 +1,172 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { migration19 } from './migration-19';
3
+ import type { MigrationData } from '../migration.util';
4
+ import { BRAND_PAGE_TYPES } from '../../models';
5
+
6
+ const BUTTON_FILL = 'rgb(255, 222, 89)';
7
+ const USER_CARD = 'rgb(1, 2, 3)';
8
+
9
+ const createMigrationData = (brandPageModel: any): MigrationData => ({
10
+ brandPageModel,
11
+ useOldLogoRenderSize: false,
12
+ });
13
+
14
+ const modelWith = (
15
+ contents: any[],
16
+ buttonFill: string | undefined = BUTTON_FILL,
17
+ brandPageType: string = BRAND_PAGE_TYPES.default,
18
+ ) => ({
19
+ brandPageType,
20
+ styles: { buttonStyles: { style: { backgroundColor: buttonFill } } },
21
+ pages: [{ contents }],
22
+ });
23
+
24
+ describe('migration19', () => {
25
+ test('seeds backgroundColor from the button fill for each container-backed block when unset', async () => {
26
+ const model = modelWith([
27
+ { type: 'mailing-list' },
28
+ { type: 'request-payment' },
29
+ { type: 'support-me' },
30
+ ]);
31
+
32
+ const result = await migration19(createMigrationData(model));
33
+
34
+ (result.brandPageModel as any).pages[0].contents.forEach((c: any) => {
35
+ expect(c.backgroundStyle.backgroundColor).toBe(BUTTON_FILL);
36
+ });
37
+ });
38
+
39
+ test('does not override a backgroundColor that is already set', async () => {
40
+ const model = modelWith([
41
+ {
42
+ type: 'mailing-list',
43
+ backgroundStyle: { backgroundColor: 'rgb(1, 2, 3)' },
44
+ },
45
+ ]);
46
+
47
+ const result = await migration19(createMigrationData(model));
48
+
49
+ expect(
50
+ (result.brandPageModel as any).pages[0].contents[0].backgroundStyle
51
+ .backgroundColor,
52
+ ).toBe('rgb(1, 2, 3)');
53
+ });
54
+
55
+ test('seeds contact-form via cardStyle (not backgroundStyle) so its container colour is detached from the button', async () => {
56
+ const model = modelWith([{ type: 'contact-form' }]);
57
+
58
+ const result = await migration19(createMigrationData(model));
59
+
60
+ const block = (result.brandPageModel as any).pages[0].contents[0];
61
+
62
+ expect(block.backgroundStyle).toBeUndefined();
63
+ expect(block.cardStyle.backgroundColor).toBe(BUTTON_FILL);
64
+ });
65
+
66
+ test('still seeds mailing-list on brand-site (button-fill container on every type)', async () => {
67
+ const model = modelWith(
68
+ [{ type: 'mailing-list' }],
69
+ BUTTON_FILL,
70
+ BRAND_PAGE_TYPES.brandSite,
71
+ );
72
+
73
+ const result = await migration19(createMigrationData(model));
74
+
75
+ expect(
76
+ (result.brandPageModel as any).pages[0].contents[0].backgroundStyle
77
+ .backgroundColor,
78
+ ).toBe(BUTTON_FILL);
79
+ });
80
+
81
+ test('leaves out-of-scope content types untouched', async () => {
82
+ const model = modelWith([{ type: 'banner-section' }, { type: 'text' }]);
83
+
84
+ const result = await migration19(createMigrationData(model));
85
+
86
+ (result.brandPageModel as any).pages[0].contents.forEach((c: any) => {
87
+ expect(c.backgroundStyle).toBeUndefined();
88
+ });
89
+ });
90
+
91
+ test('is a no-op when the button fill colour is undefined', async () => {
92
+ const model = {
93
+ styles: { buttonStyles: { style: {} } },
94
+ pages: [{ contents: [{ type: 'mailing-list' }] }],
95
+ };
96
+
97
+ const result = await migration19(createMigrationData(model));
98
+
99
+ expect(
100
+ (result.brandPageModel as any).pages[0].contents[0].backgroundStyle,
101
+ ).toBeUndefined();
102
+ });
103
+
104
+ test('preserves other backgroundStyle keys when seeding', async () => {
105
+ const model = modelWith([
106
+ { type: 'mailing-list', backgroundStyle: { opacity: '0.5' } },
107
+ ]);
108
+
109
+ const result = await migration19(createMigrationData(model));
110
+
111
+ const bg = (result.brandPageModel as any).pages[0].contents[0]
112
+ .backgroundStyle;
113
+
114
+ expect(bg.backgroundColor).toBe(BUTTON_FILL);
115
+ expect(bg.opacity).toBe('0.5');
116
+ });
117
+
118
+ test('seeds contact-form cardStyle from the button fill on brand-contact', async () => {
119
+ const model = modelWith(
120
+ [{ type: 'contact-form' }],
121
+ BUTTON_FILL,
122
+ BRAND_PAGE_TYPES.brandContact,
123
+ );
124
+
125
+ const result = await migration19(createMigrationData(model));
126
+
127
+ expect(
128
+ (result.brandPageModel as any).pages[0].contents[0].cardStyle
129
+ .backgroundColor,
130
+ ).toBe(BUTTON_FILL);
131
+ });
132
+
133
+ test('is idempotent — does NOT overwrite a contact-form cardStyle colour that is already set', async () => {
134
+ const model = modelWith([
135
+ { type: 'contact-form', cardStyle: { backgroundColor: USER_CARD } },
136
+ ]);
137
+
138
+ const result = await migration19(createMigrationData(model));
139
+
140
+ expect(
141
+ (result.brandPageModel as any).pages[0].contents[0].cardStyle
142
+ .backgroundColor,
143
+ ).toBe(USER_CARD);
144
+ });
145
+
146
+ test('preserves other cardStyle keys while seeding the contact-form colour', async () => {
147
+ const model = modelWith([
148
+ { type: 'contact-form', cardStyle: { borderRadius: '8px' } },
149
+ ]);
150
+
151
+ const result = await migration19(createMigrationData(model));
152
+
153
+ const card = (result.brandPageModel as any).pages[0].contents[0].cardStyle;
154
+
155
+ expect(card.backgroundColor).toBe(BUTTON_FILL);
156
+ expect(card.borderRadius).toBe('8px');
157
+ });
158
+
159
+ test('does NOT touch contact forms on brand-site (keeps its own layout/card behaviour)', async () => {
160
+ const model = modelWith(
161
+ [{ type: 'contact-form' }],
162
+ BUTTON_FILL,
163
+ BRAND_PAGE_TYPES.brandSite,
164
+ );
165
+
166
+ const result = await migration19(createMigrationData(model));
167
+
168
+ expect(
169
+ (result.brandPageModel as any).pages[0].contents[0].cardStyle,
170
+ ).toBeUndefined();
171
+ });
172
+ });
@@ -0,0 +1,65 @@
1
+ import type { MigrationData } from '../migration.util';
2
+ import { BRAND_PAGE_TYPES } from '../../models';
3
+
4
+ const CONTAINER_BACKGROUND_TYPES = new Set<string>([
5
+ 'mailing-list',
6
+ 'request-payment',
7
+ 'support-me',
8
+ ]);
9
+
10
+ type WithBackgroundStyle = {
11
+ backgroundStyle?: { backgroundColor?: string };
12
+ };
13
+
14
+ type WithCardStyle = {
15
+ cardStyle?: { backgroundColor?: string };
16
+ };
17
+
18
+ export const migration19 = async (
19
+ migrationResult: MigrationData,
20
+ ): Promise<MigrationData> => {
21
+ const { brandPageModel } = migrationResult;
22
+
23
+ const buttonFill =
24
+ brandPageModel.styles?.buttonStyles?.style?.backgroundColor;
25
+
26
+ if (buttonFill === undefined) {
27
+ return migrationResult;
28
+ }
29
+
30
+ const isBrandSite =
31
+ brandPageModel.brandPageType === BRAND_PAGE_TYPES.brandSite;
32
+
33
+ brandPageModel.pages.forEach((page) => {
34
+ page.contents.forEach((content) => {
35
+ // Container-backed blocks: seed their own backgroundStyle on every type.
36
+ if (CONTAINER_BACKGROUND_TYPES.has(content.type)) {
37
+ const block = content as WithBackgroundStyle;
38
+
39
+ if (block.backgroundStyle?.backgroundColor === undefined) {
40
+ block.backgroundStyle = {
41
+ ...block.backgroundStyle,
42
+ backgroundColor: buttonFill,
43
+ };
44
+ }
45
+
46
+ return;
47
+ }
48
+
49
+ // Contact form: colour moves to cardStyle — skip on brand-site, which
50
+ // keeps its own layout/card behaviour.
51
+ if (content.type === 'contact-form' && !isBrandSite) {
52
+ const block = content as WithCardStyle;
53
+
54
+ if (block.cardStyle?.backgroundColor === undefined) {
55
+ block.cardStyle = {
56
+ ...block.cardStyle,
57
+ backgroundColor: buttonFill,
58
+ };
59
+ }
60
+ }
61
+ });
62
+ });
63
+
64
+ return migrationResult;
65
+ };
@@ -21,6 +21,7 @@ import { migration15 } from './migration/migration-15';
21
21
  import { migration16 } from './migration/migration-16';
22
22
  import { migration17 } from './migration/migration-17';
23
23
  import { migration18 } from './migration/migration-18';
24
+ import { migration19 } from './migration/migration-19';
24
25
 
25
26
  export interface MigrationData {
26
27
  brandPageModel: BrandPageModel;
@@ -167,6 +168,17 @@ const MIGRATION_TABLE = [
167
168
  description:
168
169
  'BP-5603: backfill stable item ids on all nested items (gallery/section/testimonial/text items, section + footer social links) — the item id is now non-nullable',
169
170
  },
171
+ {
172
+ version: 19,
173
+ migrateFn: migration19,
174
+ // runAlways: re-apply on every load so already-published sites pick up the seed
175
+ // without a version bump. Idempotent — only seeds a colour when unset, so a user
176
+ // colour is never clobbered. Single pass over the model seeds both container types
177
+ // and the contact form.
178
+ runAlways: true,
179
+ description:
180
+ 'BP-5800: seed the button fill colour as each block’s own container colour so it is independent of the site button (existing sites render unchanged) — backgroundStyle for mailing-list, request-payment and support-me (all types); cardStyle for the BP/BC contact form (skipped on brand-site)',
181
+ },
170
182
  ] as MigrationTableItem[];
171
183
 
172
184
  export const GetLatestModelVersion = () =>
@@ -887,6 +887,7 @@ const mapToShopViewModel = (
887
887
  styles: PageStyles,
888
888
  ): ShopViewModel => {
889
889
  const buttonStyles = new ButtonStyles({
890
+ selectedButtonStyleId: styles.buttonStyles.selectedButtonStyleId,
890
891
  style: {
891
892
  ...styles.buttonStyles.style,
892
893
  },
@@ -11,50 +11,58 @@ import {
11
11
 
12
12
  export const contactCardDisplayTypeVisibilityAssessor: VisibilityAssessor<
13
13
  ContactFormContent | undefined
14
+ > = (content, context) => {
15
+ return Boolean(
16
+ content?.layoutStyle &&
17
+ [
18
+ ALIGN_CARD_CENTER_IMAGE,
19
+ ALIGN_CARD_CENTER,
20
+ ALIGN_LEFT_FULL_IMAGE,
21
+ ALIGN_RIGHT_FULL_IMAGE,
22
+ ].includes(content?.layoutStyle) &&
23
+ context?.isBrandSite,
24
+ );
25
+ };
26
+
27
+ // Card colour picker: on brand-page/brand-contact it's the collapsible container
28
+ // colour (shown for every layout); on brand-site it's the card in card layouts.
29
+ export const contactCardColorVisibilityAssessor: VisibilityAssessor<
30
+ ContactFormContent | undefined
14
31
  > = (content, context) => {
15
32
  return (
16
- Boolean(
17
- content?.layoutStyle &&
18
- [
19
- ALIGN_CARD_CENTER_IMAGE,
20
- ALIGN_CARD_CENTER,
21
- ALIGN_LEFT_FULL_IMAGE,
22
- ALIGN_RIGHT_FULL_IMAGE,
23
- ].includes(content?.layoutStyle),
24
- ) && context!.isBrandSite
33
+ !context?.isBrandSite ||
34
+ contactCardDisplayTypeVisibilityAssessor(content, context)
25
35
  );
26
36
  };
27
37
 
28
38
  export const displayTypeVisibilityAssessor: VisibilityAssessor<
29
39
  ContactFormContent | undefined
30
40
  > = (content, context) => {
31
- return Boolean(content?.layoutStyle === '' || context!.isBrandPage);
41
+ return Boolean(content?.layoutStyle === '' || context?.isBrandPage);
32
42
  };
33
43
 
34
44
  export const imageUrlVisibilityAssessor: VisibilityAssessor<
35
45
  ContactFormContent | undefined
36
46
  > = (content, context) => {
37
- return (
38
- Boolean(
39
- content?.layoutStyle &&
40
- [
41
- ALIGN_CARD_CENTER_IMAGE,
42
- ALIGN_LEFT_FULL_IMAGE,
43
- ALIGN_RIGHT_FULL_IMAGE,
44
- ].includes(content?.layoutStyle),
45
- ) && context!.isBrandSite
47
+ return Boolean(
48
+ content?.layoutStyle &&
49
+ [
50
+ ALIGN_CARD_CENTER_IMAGE,
51
+ ALIGN_LEFT_FULL_IMAGE,
52
+ ALIGN_RIGHT_FULL_IMAGE,
53
+ ].includes(content?.layoutStyle) &&
54
+ context?.isBrandSite,
46
55
  );
47
56
  };
48
57
 
49
58
  export const contactBackgroundDisplayTypeVisibilityAssessor: VisibilityAssessor<
50
59
  ContactFormContent | undefined
51
60
  > = (content, context) => {
52
- return (
53
- Boolean(
54
- content?.layoutStyle &&
55
- [ALIGN_CARD_CENTER, ALIGN_CENTERED, TEXT_ONLY].includes(
56
- content?.layoutStyle,
57
- ),
58
- ) && context!.isBrandSite
61
+ return Boolean(
62
+ content?.layoutStyle &&
63
+ [ALIGN_CARD_CENTER, ALIGN_CENTERED, TEXT_ONLY].includes(
64
+ content?.layoutStyle,
65
+ ) &&
66
+ context?.isBrandSite,
59
67
  );
60
68
  };