@designcrowd/library.brand-page 6.2.9 → 6.2.10

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 (46) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-9uC6t5HU.js → CreatedOnBrandCrowd-de-de-CTEj6D-p.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-P6TzcklZ.js → CreatedOnBrandCrowd-en-us-ByFNuMoA.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DimkGb1_.js → CreatedOnBrandCrowd-es-es-lA_3mWIj.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CL_iFuJw.js → CreatedOnBrandCrowd-fr-ca-CS_YkUz3.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CL_iFuJw.js → CreatedOnBrandCrowd-fr-fr-CS_YkUz3.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-DSIjEoMv.js → CreatedOnBrandCrowd-pt-br-BoduQ7zJ.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-DSIjEoMv.js → CreatedOnBrandCrowd-pt-pt-BoduQ7zJ.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-CKRvl9Ky.js → CreatedOnDcom-de-de-Co1OO4_u.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-ChplPDfS.js → CreatedOnDcom-en-us-DqFyqxOJ.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-DCIXsW9X.js → CreatedOnDcom-es-es-DhQWyb7C.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-lakt1nLU.js → CreatedOnDcom-fr-ca-DIxstLTf.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-RsGK1y5g.js → CreatedOnDcom-fr-fr-B-z8V1cH.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-DyA4XodM.js → CreatedOnDcom-pt-br-B7AMR1sc.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-BK51dTyw.js → CreatedOnDcom-pt-pt-Cdci4yr3.js} +1 -1
  15. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +6 -5
  16. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +6 -5
  17. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +6 -5
  18. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +7 -5
  19. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.js +5 -0
  20. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +6 -5
  21. package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +6 -5
  22. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +6 -5
  23. package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +6 -5
  24. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +7 -6
  25. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +52 -5
  26. package/lib/src/css/library.brand-page-brandCrowd.min.css +1 -1
  27. package/lib/src/css/library.brand-page-brandPage.min.css +1 -1
  28. package/lib/src/css/library.brand-page-designCom.min.css +1 -1
  29. package/lib/src/css/library.brand-page-preview.min.css +1 -1
  30. package/lib/src/index.mjs +1 -1
  31. package/lib/src/{lib-Ca7GMpcR.js → lib-Y-hakSU_.js} +265 -233
  32. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +6 -5
  33. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +6 -5
  34. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +6 -5
  35. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +6 -5
  36. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +6 -5
  37. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +6 -5
  38. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +6 -5
  39. package/package.json +1 -1
  40. package/src/brand-page-maker/components/brandpage-settings/BrandPageSettings.test.ts +323 -0
  41. package/src/brand-page-maker/components/brandpage-settings/BrandPageSettings.vue +3 -3
  42. package/src/brand-page-maker/components/sensitive-content/SensitiveContent.vue +23 -3
  43. package/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.ts +5 -0
  44. package/src/brand-page-maker/components/seo-settings/SeoPageSettings.vue +2 -0
  45. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.save.test.ts +332 -1
  46. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +83 -5
@@ -351,13 +351,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
351
351
  startBrandPageSlugEditMode(): void;
352
352
  triggerLockedContentClickedEvent(contentType: string): Promise<void>;
353
353
  unpublishBrandPage(): Promise<void>;
354
- updateBrandPageSettings({ description, title }: {
355
- description: any;
356
- title: any;
354
+ updateBrandPageSettings(payload: {
355
+ description: string;
356
+ title: string;
357
357
  }): Promise<void>;
358
- updateSensitiveContent({ sensitiveContent }: {
359
- sensitiveContent: any;
358
+ updateSensitiveContent(payload: {
359
+ sensitiveContent: import("@brand-page").SensitiveContent;
360
360
  }): Promise<void>;
361
+ saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
361
362
  createBrandPageCustomDomain({ domainName }: {
362
363
  domainName: any;
363
364
  }): Promise<false | undefined>;
@@ -647,13 +647,14 @@ declare const _default: import("vue").DefineComponent<{}, {
647
647
  startBrandPageSlugEditMode(): void;
648
648
  triggerLockedContentClickedEvent(contentType: string): Promise<void>;
649
649
  unpublishBrandPage(): Promise<void>;
650
- updateBrandPageSettings({ description, title }: {
651
- description: any;
652
- title: any;
650
+ updateBrandPageSettings(payload: {
651
+ description: string;
652
+ title: string;
653
653
  }): Promise<void>;
654
- updateSensitiveContent({ sensitiveContent }: {
655
- sensitiveContent: any;
654
+ updateSensitiveContent(payload: {
655
+ sensitiveContent: import("../../../brand-page").SensitiveContent;
656
656
  }): Promise<void>;
657
+ saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
657
658
  createBrandPageCustomDomain({ domainName }: {
658
659
  domainName: any;
659
660
  }): Promise<false | undefined>;
@@ -5083,13 +5083,14 @@ declare const _default: import("vue").DefineComponent<{}, {
5083
5083
  startBrandPageSlugEditMode(): void;
5084
5084
  triggerLockedContentClickedEvent(contentType: string): Promise<void>;
5085
5085
  unpublishBrandPage(): Promise<void>;
5086
- updateBrandPageSettings({ description, title }: {
5087
- description: any;
5088
- title: any;
5086
+ updateBrandPageSettings(payload: {
5087
+ description: string;
5088
+ title: string;
5089
5089
  }): Promise<void>;
5090
- updateSensitiveContent({ sensitiveContent }: {
5091
- sensitiveContent: any;
5090
+ updateSensitiveContent(payload: {
5091
+ sensitiveContent: import("@brand-page").SensitiveContent;
5092
5092
  }): Promise<void>;
5093
+ saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
5093
5094
  createBrandPageCustomDomain({ domainName }: {
5094
5095
  domainName: any;
5095
5096
  }): Promise<false | undefined>;
@@ -4434,13 +4434,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
4434
4434
  startBrandPageSlugEditMode(): void;
4435
4435
  triggerLockedContentClickedEvent(contentType: string): Promise<void>;
4436
4436
  unpublishBrandPage(): Promise<void>;
4437
- updateBrandPageSettings({ description, title }: {
4438
- description: any;
4439
- title: any;
4437
+ updateBrandPageSettings(payload: {
4438
+ description: string;
4439
+ title: string;
4440
4440
  }): Promise<void>;
4441
- updateSensitiveContent({ sensitiveContent }: {
4442
- sensitiveContent: any;
4441
+ updateSensitiveContent(payload: {
4442
+ sensitiveContent: import("@brand-page").SensitiveContent;
4443
4443
  }): Promise<void>;
4444
+ saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
4444
4445
  createBrandPageCustomDomain({ domainName }: {
4445
4446
  domainName: any;
4446
4447
  }): Promise<false | undefined>;
@@ -554,13 +554,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
554
554
  startBrandPageSlugEditMode(): void;
555
555
  triggerLockedContentClickedEvent(contentType: string): Promise<void>;
556
556
  unpublishBrandPage(): Promise<void>;
557
- updateBrandPageSettings({ description, title }: {
558
- description: any;
559
- title: any;
557
+ updateBrandPageSettings(payload: {
558
+ description: string;
559
+ title: string;
560
560
  }): Promise<void>;
561
- updateSensitiveContent({ sensitiveContent }: {
562
- sensitiveContent: any;
561
+ updateSensitiveContent(payload: {
562
+ sensitiveContent: import("../../brand-page").SensitiveContent;
563
563
  }): Promise<void>;
564
+ saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
564
565
  createBrandPageCustomDomain({ domainName }: {
565
566
  domainName: any;
566
567
  }): Promise<false | undefined>;
@@ -344,13 +344,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
344
344
  startBrandPageSlugEditMode(): void;
345
345
  triggerLockedContentClickedEvent(contentType: string): Promise<void>;
346
346
  unpublishBrandPage(): Promise<void>;
347
- updateBrandPageSettings({ description, title }: {
348
- description: any;
349
- title: any;
347
+ updateBrandPageSettings(payload: {
348
+ description: string;
349
+ title: string;
350
350
  }): Promise<void>;
351
- updateSensitiveContent({ sensitiveContent }: {
352
- sensitiveContent: any;
351
+ updateSensitiveContent(payload: {
352
+ sensitiveContent: import("../../../brand-page").SensitiveContent;
353
353
  }): Promise<void>;
354
+ saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
354
355
  createBrandPageCustomDomain({ domainName }: {
355
356
  domainName: any;
356
357
  }): Promise<false | undefined>;
@@ -852,13 +852,14 @@ declare const _default: import("vue").DefineComponent<{}, {
852
852
  startBrandPageSlugEditMode(): void;
853
853
  triggerLockedContentClickedEvent(contentType: string): Promise<void>;
854
854
  unpublishBrandPage(): Promise<void>;
855
- updateBrandPageSettings({ description, title }: {
856
- description: any;
857
- title: any;
855
+ updateBrandPageSettings(payload: {
856
+ description: string;
857
+ title: string;
858
858
  }): Promise<void>;
859
- updateSensitiveContent({ sensitiveContent }: {
860
- sensitiveContent: any;
859
+ updateSensitiveContent(payload: {
860
+ sensitiveContent: import("../../../brand-page").SensitiveContent;
861
861
  }): Promise<void>;
862
+ saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
862
863
  createBrandPageCustomDomain({ domainName }: {
863
864
  domainName: any;
864
865
  }): Promise<false | undefined>;
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": "6.2.9",
4
+ "version": "6.2.10",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -0,0 +1,323 @@
1
+ import { mount, DOMWrapper } from '@vue/test-utils';
2
+ import { createPinia, setActivePinia } from 'pinia';
3
+ import { describe, test, expect, beforeEach, vi } from 'vitest';
4
+ import { TextInput, Textarea } from '@designcrowd/fe-shared-lib';
5
+ import BrandPageSettings from './BrandPageSettings.vue';
6
+ import SeoPageSettings from '../seo-settings/SeoPageSettings.vue';
7
+ import SensitiveContent from '../sensitive-content/SensitiveContent.vue';
8
+ import ImageField from '@shared/content-settings/components/image-field/ImageField.vue';
9
+ import {
10
+ useBrandPageContainerStore,
11
+ useBrandPageMakerStore,
12
+ } from '@brand-page-maker/stores';
13
+ import { SENSITIVE_CONTENT_TYPES } from '@brand-page';
14
+
15
+ // CollapsiblePanel is imported directly in a `<script setup>` template, so
16
+ // `global.stubs` can't intercept it by name (no `resolveComponent` call is
17
+ // emitted for it). It starts collapsed and unmounts its slot when collapsed
18
+ // (maker-settings-panels.md), so open every panel for real by clicking its
19
+ // header, the same way ColorStrategyPanel.test.ts does.
20
+ const mountSettings = async () => {
21
+ const wrapper = mount(BrandPageSettings);
22
+
23
+ for (const header of wrapper.findAll(
24
+ '[data-component="collapsible-panel"] > div',
25
+ )) {
26
+ await header.trigger('click');
27
+ }
28
+
29
+ return wrapper;
30
+ };
31
+
32
+ type Wrapper = Awaited<ReturnType<typeof mountSettings>>;
33
+
34
+ const radios = (wrapper: Wrapper) =>
35
+ wrapper.findAll('input[type="radio"][name="sensitiveContent"]');
36
+
37
+ // GoogleAnalytics and SocialShare each render their own TextInput in a
38
+ // DIFFERENT collapsible panel, so scope to the panel holding the SEO
39
+ // Textarea (unique to SeoPageSettings) to find the SEO title input alone.
40
+ const seoTitleInput = (wrapper: Wrapper) => {
41
+ const textarea = wrapper.findComponent(Textarea);
42
+
43
+ if (!textarea.exists()) return undefined;
44
+
45
+ const panel = textarea.element.closest(
46
+ '[data-component="collapsible-panel"]',
47
+ ) as HTMLElement;
48
+
49
+ const input = new DOMWrapper(panel).findComponent(TextInput);
50
+
51
+ return input.exists() ? input : undefined;
52
+ };
53
+
54
+ // The Sensitive Content panel is the only rendered panel with these radios;
55
+ // its Update button is the nearest one in the same collapsible panel.
56
+ const sensitiveContentUpdateButton = (wrapper: Wrapper) => {
57
+ const panel = radios(wrapper)[0].element.closest(
58
+ '[data-component="collapsible-panel"]',
59
+ ) as HTMLElement;
60
+
61
+ return new DOMWrapper(panel).find('button');
62
+ };
63
+
64
+ const faviconField = (wrapper: Wrapper) =>
65
+ wrapper
66
+ .findAllComponents(ImageField)
67
+ .find((field) => field.props('name') === 'favicon');
68
+
69
+ describe('BrandPageSettings — SEO and Sensitive Content before the first save', () => {
70
+ let containerStore: ReturnType<typeof useBrandPageContainerStore>;
71
+ let brandPageMakerStore: ReturnType<typeof useBrandPageMakerStore>;
72
+
73
+ beforeEach(() => {
74
+ setActivePinia(createPinia());
75
+
76
+ containerStore = useBrandPageContainerStore();
77
+ brandPageMakerStore = useBrandPageMakerStore();
78
+ });
79
+
80
+ test.each([
81
+ ['post-purchase, never saved', {}],
82
+ ['pre-purchase, never saved', { isPrePurchase: true }],
83
+ ['saved site', { designDraftToken: 'draft-123' }],
84
+ ])(
85
+ 'shows both settings (%s)',
86
+ async (_case: string, flags: Record<string, boolean | string>) => {
87
+ Object.assign(containerStore, flags);
88
+
89
+ const wrapper = await mountSettings();
90
+
91
+ expect(seoTitleInput(wrapper)).toBeDefined();
92
+ expect(wrapper.findComponent(Textarea).exists()).toBe(true);
93
+ expect(wrapper.find('[data-test-btn-update-seo-settings]').exists()).toBe(
94
+ true,
95
+ );
96
+ expect(radios(wrapper)).toHaveLength(4);
97
+ expect(faviconField(wrapper)).toBeDefined();
98
+ },
99
+ );
100
+
101
+ test('disabled while saving', async () => {
102
+ // isPublishing disables both panels' Update buttons and inputs, and blocks
103
+ // a row click from changing the choice already made — the same as isSaving.
104
+ const publishingWrapper = await mountSettings();
105
+
106
+ // Dirty both panels first — an already-disabled (clean) button can't
107
+ // distinguish "isPublishing disables it" from "it was never enabled".
108
+ await seoTitleInput(publishingWrapper)!.find('input').setValue('A title');
109
+ await new DOMWrapper(
110
+ radios(publishingWrapper)[1].element.parentElement!,
111
+ ).trigger('click');
112
+
113
+ containerStore.isPublishing = true;
114
+ await publishingWrapper.vm.$nextTick();
115
+
116
+ expect(
117
+ sensitiveContentUpdateButton(publishingWrapper).attributes('disabled'),
118
+ ).toBeDefined();
119
+ expect(radios(publishingWrapper)[0].attributes('disabled')).toBeDefined();
120
+ expect(
121
+ publishingWrapper
122
+ .find('[data-test-btn-update-seo-settings]')
123
+ .attributes('disabled'),
124
+ ).toBeDefined();
125
+ expect(seoTitleInput(publishingWrapper)!.props('disabled')).toBe(true);
126
+ expect(publishingWrapper.findComponent(Textarea).props('disabled')).toBe(
127
+ true,
128
+ );
129
+
130
+ // A click on a different row must not change the choice already made
131
+ // above while a publish is in flight.
132
+ await new DOMWrapper(
133
+ radios(publishingWrapper)[2].element.parentElement!,
134
+ ).trigger('click');
135
+ expect(
136
+ (radios(publishingWrapper)[1].element as HTMLInputElement).checked,
137
+ ).toBe(true);
138
+
139
+ containerStore.isPublishing = false;
140
+
141
+ // isSaving disables both panels' Update buttons and inputs, and blocks a
142
+ // row click from changing the choice already made.
143
+ const wrapper = await mountSettings();
144
+
145
+ // Dirty both panels first — an already-disabled (clean) button can't
146
+ // distinguish "isSaving disables it" from "it was never enabled". The
147
+ // radio's own state changes only through the row's `@click` handler, not
148
+ // a native `change`, so click the row rather than `setValue` the input.
149
+ await seoTitleInput(wrapper)!.find('input').setValue('A title');
150
+ await new DOMWrapper(radios(wrapper)[1].element.parentElement!).trigger(
151
+ 'click',
152
+ );
153
+
154
+ expect(
155
+ sensitiveContentUpdateButton(wrapper).attributes('disabled'),
156
+ ).toBeUndefined();
157
+ expect(
158
+ wrapper
159
+ .find('[data-test-btn-update-seo-settings]')
160
+ .attributes('disabled'),
161
+ ).toBeUndefined();
162
+
163
+ containerStore.isSaving = true;
164
+ await wrapper.vm.$nextTick();
165
+
166
+ expect(
167
+ sensitiveContentUpdateButton(wrapper).attributes('disabled'),
168
+ ).toBeDefined();
169
+ expect(radios(wrapper)[0].attributes('disabled')).toBeDefined();
170
+ expect(
171
+ wrapper
172
+ .find('[data-test-btn-update-seo-settings]')
173
+ .attributes('disabled'),
174
+ ).toBeDefined();
175
+ expect(seoTitleInput(wrapper)!.props('disabled')).toBe(true);
176
+ expect(wrapper.findComponent(Textarea).props('disabled')).toBe(true);
177
+
178
+ // A click on a different row must not change the choice already made
179
+ // above while a save is in flight.
180
+ await new DOMWrapper(radios(wrapper)[2].element.parentElement!).trigger(
181
+ 'click',
182
+ );
183
+ expect((radios(wrapper)[1].element as HTMLInputElement).checked).toBe(true);
184
+
185
+ containerStore.isSaving = false;
186
+ await wrapper.vm.$nextTick();
187
+
188
+ expect(
189
+ sensitiveContentUpdateButton(wrapper).attributes('disabled'),
190
+ ).toBeUndefined();
191
+ expect(radios(wrapper)[0].attributes('disabled')).toBeUndefined();
192
+ expect(
193
+ wrapper
194
+ .find('[data-test-btn-update-seo-settings]')
195
+ .attributes('disabled'),
196
+ ).toBeUndefined();
197
+ expect(seoTitleInput(wrapper)!.props('disabled')).toBe(false);
198
+ expect(wrapper.findComponent(Textarea).props('disabled')).toBe(false);
199
+ });
200
+
201
+ test('Manage page', async () => {
202
+ containerStore.isSaving = true;
203
+ vi.spyOn(brandPageMakerStore, 'updateBrandPageSettings').mockResolvedValue(
204
+ undefined,
205
+ );
206
+ vi.spyOn(
207
+ brandPageMakerStore,
208
+ 'updateSensitiveContentSettings',
209
+ ).mockResolvedValue(undefined);
210
+
211
+ const seoWrapper = mount(SeoPageSettings, { props: { isMaker: false } });
212
+
213
+ await seoWrapper.find('input').setValue('New title');
214
+ const seoUpdateButton = seoWrapper.find(
215
+ '[data-test-btn-update-seo-settings]',
216
+ );
217
+
218
+ expect(seoUpdateButton.attributes('disabled')).toBeUndefined();
219
+
220
+ await seoUpdateButton.trigger('click');
221
+ await seoWrapper.vm.$nextTick();
222
+
223
+ expect(brandPageMakerStore.updateBrandPageSettings).toHaveBeenCalledWith(
224
+ expect.objectContaining({ title: 'New title' }),
225
+ );
226
+
227
+ const sensitiveWrapper = mount(SensitiveContent, {
228
+ props: { isMaker: false, value: SENSITIVE_CONTENT_TYPES.off },
229
+ });
230
+ const sensitiveRadios = sensitiveWrapper.findAll(
231
+ 'input[type="radio"][name="sensitiveContent"]',
232
+ );
233
+
234
+ expect(sensitiveRadios[1].attributes('disabled')).toBeUndefined();
235
+
236
+ // The radio has no v-model/@change of its own — a real click lands on
237
+ // the row's wrapping div, same as SensitiveContent's own click handler.
238
+ await new DOMWrapper(sensitiveRadios[1].element.parentElement!).trigger(
239
+ 'click',
240
+ );
241
+ await sensitiveWrapper.vm.$nextTick();
242
+
243
+ const sensitiveUpdateButton = sensitiveWrapper.find('button');
244
+
245
+ expect(sensitiveUpdateButton.attributes('disabled')).toBeUndefined();
246
+
247
+ await sensitiveUpdateButton.trigger('click');
248
+ await sensitiveWrapper.vm.$nextTick();
249
+
250
+ expect(
251
+ brandPageMakerStore.updateSensitiveContentSettings,
252
+ ).toHaveBeenCalledWith({
253
+ sensitiveContent: SENSITIVE_CONTENT_TYPES.plus18,
254
+ shouldRefresh: true,
255
+ });
256
+
257
+ // isPublishing is the maker's own in-flight state (set by
258
+ // ManageBrandPageContainer's onPublishClicked) — it must not lock the
259
+ // Manage page's radios either.
260
+ containerStore.isPublishing = true;
261
+ await sensitiveWrapper.vm.$nextTick();
262
+
263
+ expect(sensitiveRadios[2].attributes('disabled')).toBeUndefined();
264
+
265
+ await new DOMWrapper(sensitiveRadios[2].element.parentElement!).trigger(
266
+ 'click',
267
+ );
268
+ await sensitiveWrapper.vm.$nextTick();
269
+
270
+ expect((sensitiveRadios[2].element as HTMLInputElement).checked).toBe(true);
271
+ });
272
+
273
+ test('SEO inputs stay editable on the Manage page while publishing', async () => {
274
+ // The Manage form is dirty when the store's title or description differs
275
+ // from the props, so match them to start clean.
276
+ containerStore.brandPageTitle = 'Old title';
277
+ containerStore.brandPageDescription = 'Old description';
278
+ containerStore.isPublishing = true;
279
+
280
+ const wrapper = mount(SeoPageSettings, {
281
+ props: {
282
+ isMaker: false,
283
+ pageTitle: 'Old title',
284
+ pageDescription: 'Old description',
285
+ },
286
+ });
287
+ const titleInput = wrapper.findComponent(TextInput);
288
+ const descriptionInput = wrapper.findComponent(Textarea);
289
+
290
+ expect(titleInput.props('disabled')).toBe(false);
291
+ expect(descriptionInput.props('disabled')).toBe(false);
292
+
293
+ await titleInput.find('input').setValue('New title');
294
+ await descriptionInput.find('textarea').setValue('New description');
295
+
296
+ expect(titleInput.props('modelValue')).toBe('New title');
297
+ expect(descriptionInput.props('modelValue')).toBe('New description');
298
+
299
+ const updateButton = wrapper.find('[data-test-btn-update-seo-settings]');
300
+
301
+ // The Update button keeps its unscoped isPublishing gate from develop;
302
+ // the form started clean, so releasing it shows the typing made it dirty.
303
+ expect(updateButton.attributes('disabled')).toBeDefined();
304
+
305
+ containerStore.isPublishing = false;
306
+ await wrapper.vm.$nextTick();
307
+
308
+ expect(updateButton.attributes('disabled')).toBeUndefined();
309
+ });
310
+
311
+ test('admin template editor', async () => {
312
+ containerStore.isAdminMaker = true;
313
+
314
+ const wrapper = await mountSettings();
315
+
316
+ expect(wrapper.find('[data-test-btn-update-seo-settings]').exists()).toBe(
317
+ false,
318
+ );
319
+ expect(wrapper.findComponent(Textarea).exists()).toBe(false);
320
+ expect(radios(wrapper)).toHaveLength(0);
321
+ expect(faviconField(wrapper)).toBeDefined();
322
+ });
323
+ });
@@ -31,14 +31,14 @@
31
31
  :is-collapsed="true"
32
32
  >
33
33
  <PageSettings
34
- v-if="designDraftToken"
34
+ v-if="!isAdminMaker"
35
35
  :is-maker="true"
36
36
  @update-settings="onUpdateSettings"
37
37
  />
38
38
  <div
39
39
  class="tw-border-0 tw-border-solid tw-border-grayscale-400 tw-mt-6"
40
40
  :class="{
41
- 'tw-border-t tw-py-8': designDraftToken,
41
+ 'tw-border-t tw-py-8': !isAdminMaker,
42
42
  }"
43
43
  >
44
44
  <h3 class="tw-text-left tw-font-bold tw-mb-2">
@@ -117,7 +117,7 @@
117
117
  </div>
118
118
  </CollapsiblePanel>
119
119
  <CollapsiblePanel
120
- v-if="designDraftToken"
120
+ v-if="!isAdminMaker"
121
121
  data-component="collapsible-panel"
122
122
  :title="commonTr('sensitiveContent')"
123
123
  :is-collapsed="true"
@@ -40,6 +40,7 @@
40
40
  name="sensitiveContent"
41
41
  :checked="data.selectedSensitiveSettings === opt.value"
42
42
  :value="opt.value"
43
+ :disabled="isSaving || isPublishing"
43
44
  />
44
45
  <input
45
46
  hidden
@@ -68,7 +69,7 @@
68
69
  :label="commonTr('updateSettings')"
69
70
  variant="primary"
70
71
  type="submit"
71
- :disabled="isNoChange"
72
+ :disabled="isNoChange || isPublishing || isSaving"
72
73
  @click="onSensitiveContentUpdate"
73
74
  />
74
75
  </div>
@@ -97,7 +98,10 @@ import {
97
98
  SENSITIVE_CONTENT_TYPES,
98
99
  type SensitiveContent,
99
100
  } from '@brand-page/models/brand-page-context.model';
100
- import { useBrandPageMakerStore } from '@brand-page-maker/stores';
101
+ import {
102
+ useBrandPageContainerStore,
103
+ useBrandPageMakerStore,
104
+ } from '@brand-page-maker/stores';
101
105
  import { sensitiveSettingTr, commonTr } from '@src/composables/useTranslate';
102
106
 
103
107
  interface Props {
@@ -106,6 +110,7 @@ interface Props {
106
110
  designDraftToken?: string;
107
111
  }
108
112
  const brandPageMakerStore = useBrandPageMakerStore();
113
+ const brandPageContainerStore = useBrandPageContainerStore();
109
114
 
110
115
  const props = defineProps<Props>();
111
116
 
@@ -137,12 +142,27 @@ const isNoChange = computed(() => {
137
142
  return data.selectedSensitiveSettings === props.value;
138
143
  });
139
144
 
145
+ // Scoped to isMaker: the Manage page's own publish/save must never lock the maker's radios, and vice versa.
146
+ const isPublishing = computed<boolean>(
147
+ () => props.isMaker && brandPageContainerStore.isPublishing,
148
+ );
149
+ const isSaving = computed<boolean>(
150
+ () => props.isMaker && brandPageContainerStore.isSaving,
151
+ );
152
+
140
153
  const emit = defineEmits([
141
154
  'change-sensitive-settings',
142
155
  'pre-submit-edit-sensitive-content',
143
156
  ]);
144
157
 
145
- const onSensitiveSettingsClick = (value) => {
158
+ const onSensitiveSettingsClick = (value: SensitiveContent) => {
159
+ // The radio input's own `disabled` doesn't stop this handler — it's on the
160
+ // wrapping div so the whole row is clickable — so gate it here too, or a
161
+ // click would still change the choice while a save is in flight.
162
+ if (isSaving.value || isPublishing.value) {
163
+ return;
164
+ }
165
+
146
166
  data.selectedSensitiveSettings = value;
147
167
  };
148
168
 
@@ -105,12 +105,17 @@ export default defineComponent({
105
105
  isPublishing() {
106
106
  return this.brandPageContainerStore.isPublishing;
107
107
  },
108
+ // Scoped to isMaker: the Manage page's save must never gate on the maker's own in-flight state.
109
+ isSaving(): boolean {
110
+ return this.isMaker && this.brandPageContainerStore.isSaving;
111
+ },
108
112
  isUpdateButtonEnabled(): any {
109
113
  return (
110
114
  (this.myPageDescription?.length ?? 0) <=
111
115
  BRANDPAGE_DESCRIPTION_CHARACTER_LIMIT &&
112
116
  (this.myPageTitle?.length ?? 0) <= BRANDPAGE_TITLE_CHARACTER_LIMIT &&
113
117
  !this.brandPageContainerStore.isPublishing &&
118
+ !this.isSaving &&
114
119
  this.isDirty &&
115
120
  this.isInternalDescriptionValid &&
116
121
  this.isInternalTitleValid
@@ -69,6 +69,7 @@
69
69
  v-model="internalTitle"
70
70
  :label="commonTr('pageTitle')"
71
71
  :error="!!internalTitleErrorMessage"
72
+ :disabled="isSaving || isPublishing"
72
73
  />
73
74
  <p
74
75
  v-show="!isInternalTitleValid"
@@ -83,6 +84,7 @@
83
84
  :placeholder="seoSettingsTr('websiteDescription')"
84
85
  :error="!!internalDescriptionErrorMessage"
85
86
  :resize="false"
87
+ :disabled="isSaving || isPublishing"
86
88
  />
87
89
  <p
88
90
  v-show="!isInternalDescriptionValid"