@designcrowd/library.brand-page 6.1.7 → 6.1.8-ast-header

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 (115) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-Dh3Paq4X.js → CreatedOnBrandCrowd-de-de-CmXTpyAA.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DLWYV4xe.js → CreatedOnBrandCrowd-en-us-FUfcPskv.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-CcjzRbm_.js → CreatedOnBrandCrowd-es-es-BkRUovXn.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-UuUNeB_L.js → CreatedOnBrandCrowd-fr-ca-xsLZEhPK.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-UuUNeB_L.js → CreatedOnBrandCrowd-fr-fr-xsLZEhPK.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-ZVYZ5AnV.js → CreatedOnBrandCrowd-pt-br-BJ4DkilI.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-ZVYZ5AnV.js → CreatedOnBrandCrowd-pt-pt-BJ4DkilI.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-BPVyqOPL.js → CreatedOnDcom-de-de-D7f4iceH.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-BUSRV01e.js → CreatedOnDcom-en-us-BHbqZcVl.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-BuABJnvQ.js → CreatedOnDcom-es-es-BT_eFhYg.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-sjHceLRY.js → CreatedOnDcom-fr-ca-DCWCvJTS.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-D_qGXL3E.js → CreatedOnDcom-fr-fr-DpLretAn.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-D3Sb_YEi.js → CreatedOnDcom-pt-br-DmyQqot9.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-BI47Hv58.js → CreatedOnDcom-pt-pt-CRLWHfnY.js} +1 -1
  15. package/lib/src/brand-page/components/page-footer/PageFooter.mixin.d.ts +0 -1
  16. package/lib/src/brand-page/components/page-footer/PageFooter.mixin.js +1 -4
  17. package/lib/src/brand-page/utils/route.util.d.ts +12 -0
  18. package/lib/src/brand-page/utils/route.util.js +56 -0
  19. package/lib/src/brand-page-maker/components/index.d.ts +1 -0
  20. package/lib/src/brand-page-maker/components/index.js +1 -0
  21. package/lib/src/bundled-translations-BlUyLdcg.js +179 -0
  22. package/lib/src/bundled-translations.de-DE-DZm_F46B.js +179 -0
  23. package/lib/src/bundled-translations.es-ES-DiOMEwZz.js +179 -0
  24. package/lib/src/bundled-translations.fr-CA-BPBNxV69.js +179 -0
  25. package/lib/src/bundled-translations.fr-FR-bCNdWkJW.js +179 -0
  26. package/lib/src/bundled-translations.pt-BR-CC6okekB.js +179 -0
  27. package/lib/src/bundled-translations.pt-PT-CSgMSLFn.js +179 -0
  28. package/lib/src/bundles/bundled-translations.de-DE.json +5 -4
  29. package/lib/src/bundles/bundled-translations.es-ES.json +5 -4
  30. package/lib/src/bundles/bundled-translations.fr-CA.json +5 -4
  31. package/lib/src/bundles/bundled-translations.fr-FR.json +5 -4
  32. package/lib/src/bundles/bundled-translations.json +9 -4
  33. package/lib/src/bundles/bundled-translations.pt-BR.json +5 -4
  34. package/lib/src/bundles/bundled-translations.pt-PT.json +5 -4
  35. package/lib/src/css/library.brand-page-brandCrowd.min.css +159 -222
  36. package/lib/src/css/library.brand-page-brandPage.min.css +159 -222
  37. package/lib/src/css/library.brand-page-designCom.min.css +159 -222
  38. package/lib/src/css/library.brand-page-preview.min.css +159 -222
  39. package/lib/src/index.d.ts +1 -1
  40. package/lib/src/index.mjs +110 -109
  41. package/lib/src/{lib-BBX3eBci.js → lib-CZMytXbN.js} +19842 -19669
  42. package/lib/src/lib.js +1 -1
  43. package/lib/src/shared/components/live-preview-modal/live-preview-modal.utils.d.ts +4 -0
  44. package/lib/src/shared/components/live-preview-modal/live-preview-modal.utils.js +3 -0
  45. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +3 -1
  46. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.js +23 -11
  47. package/package.json +1 -1
  48. package/src/brand-page/components/button/Button.vue +41 -18
  49. package/src/brand-page/components/button/Button.vue.test.ts +211 -0
  50. package/src/brand-page/components/images-section/ImagesSection.test.ts +91 -0
  51. package/src/brand-page/components/images-section/ImagesSection.vue +29 -10
  52. package/src/brand-page/components/page-footer/PageFooter.mixin.ts +1 -4
  53. package/src/brand-page/components/page-footer/PageFooter.vue +0 -1
  54. package/src/brand-page/components/payment/Payment.vue +0 -3
  55. package/src/brand-page/components/payment/request-payment/RequestPayment.vue +0 -1
  56. package/src/brand-page/components/payment/support-me/SupportMe.vue +0 -3
  57. package/src/brand-page/components/save-contact/SaveContactWidget.vue +1 -0
  58. package/src/brand-page/components/save-contact/popup-card/PopupCard.vue +1 -0
  59. package/src/brand-page/components/shop/CartModal.test.ts +51 -0
  60. package/src/brand-page/components/shop/CartModal.vue +1 -0
  61. package/src/brand-page/components/shop/FeaturedDisplay.vue +8 -3
  62. package/src/brand-page/components/shop/Shop.vue +5 -5
  63. package/src/brand-page/components/shop/Shop.vue.test.ts +104 -2
  64. package/src/brand-page/components/social-links/SocialLinks.vue +1 -9
  65. package/src/brand-page/components/social-links/SocialLinks.vue.test.ts +28 -8
  66. package/src/brand-page/components/social-links/SocialLinksItems.vue +6 -2
  67. package/src/brand-page/components/testimonial-section/TestimonialSection.vue +2 -2
  68. package/src/brand-page/components/video/Video.vue +0 -3
  69. package/src/brand-page/utils/route.util.test.ts +116 -0
  70. package/src/brand-page/utils/route.util.ts +82 -0
  71. package/src/brand-page-maker/components/action-bar/ActionBarNavigation.test.ts +23 -0
  72. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.preview-gate.test.ts +68 -3
  73. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +1 -0
  74. package/src/brand-page-maker/components/index.ts +1 -0
  75. package/src/brand-page-maker/components/preview-modal/PreviewModalWithState.test.ts +61 -41
  76. package/src/brand-page-maker/components/preview-modal/PreviewModalWithState.vue +37 -42
  77. package/src/bundles/bundled-translations.de-DE.json +5 -4
  78. package/src/bundles/bundled-translations.es-ES.json +5 -4
  79. package/src/bundles/bundled-translations.fr-CA.json +5 -4
  80. package/src/bundles/bundled-translations.fr-FR.json +5 -4
  81. package/src/bundles/bundled-translations.json +9 -4
  82. package/src/bundles/bundled-translations.pt-BR.json +5 -4
  83. package/src/bundles/bundled-translations.pt-PT.json +5 -4
  84. package/src/lib.ts +1 -0
  85. package/src/{brand-page-maker/components/preview-modal/PreviewModal.test.ts → shared/components/live-preview-modal/LivePreviewModal.test.ts} +96 -3
  86. package/src/{brand-page-maker/components/preview-modal/PreviewModal.vue → shared/components/live-preview-modal/LivePreviewModal.vue} +103 -89
  87. package/src/shared/components/live-preview-modal/i18n/preview-modal.de-DE.json +7 -0
  88. package/src/shared/components/live-preview-modal/i18n/preview-modal.es-ES.json +7 -0
  89. package/src/shared/components/live-preview-modal/i18n/preview-modal.fr-CA.json +7 -0
  90. package/src/shared/components/live-preview-modal/i18n/preview-modal.fr-FR.json +7 -0
  91. package/src/shared/components/live-preview-modal/i18n/preview-modal.json +7 -0
  92. package/src/shared/components/live-preview-modal/i18n/preview-modal.pt-BR.json +7 -0
  93. package/src/shared/components/live-preview-modal/i18n/preview-modal.pt-PT.json +7 -0
  94. package/src/shared/components/live-preview-modal/live-preview-modal.utils.ts +11 -0
  95. package/src/shared/ecommerce-settings/store/cart-store.test.ts +38 -0
  96. package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +205 -0
  97. package/src/shared/page-settings/components/page-settings/PageSettings.header-settings-panel.test.ts +97 -0
  98. package/src/shared/page-settings/components/page-settings/PageSettings.mixin.ts +27 -12
  99. package/src/shared/page-settings/components/page-settings/PageSettings.vue +113 -92
  100. package/src/shared/page-settings/components/page-settings/i18n/page-settings.json +4 -0
  101. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.vue +1 -57
  102. package/lib/src/bundled-translations-D42tE0D2.js +0 -179
  103. package/lib/src/bundled-translations.de-DE-D9aWGgFt.js +0 -179
  104. package/lib/src/bundled-translations.es-ES-lk4B0xQ7.js +0 -179
  105. package/lib/src/bundled-translations.fr-CA-DhOBNt6q.js +0 -179
  106. package/lib/src/bundled-translations.fr-FR-CnpafQeG.js +0 -179
  107. package/lib/src/bundled-translations.pt-BR-C4PnKrd9.js +0 -179
  108. package/lib/src/bundled-translations.pt-PT-CfKDVQC-.js +0 -179
  109. package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.de-DE.json +0 -6
  110. package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.es-ES.json +0 -6
  111. package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.fr-CA.json +0 -6
  112. package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.fr-FR.json +0 -6
  113. package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.json +0 -6
  114. package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.pt-BR.json +0 -6
  115. package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.pt-PT.json +0 -6
@@ -7,16 +7,21 @@ import { EVENTS } from '../../../constants/brand-page-maker';
7
7
  // flow. Post-purchase: the modal saves through the maker store itself, so it can open
8
8
  // first and show its loader.
9
9
  //
10
- // BP-5873 ships POST-PURCHASE ONLY, so the pre-purchase branch of `onPreview` and its
11
- // `livePreviewUrl` watcher are unreachable and deliberately left uncovered here.
10
+ // Pre-purchase: the HOST owns the save, so nothing opens until that save hands back a
11
+ // signed URL — the `livePreviewUrl` watcher is what opens the modal.
12
+ //
12
13
  // Mounting the whole editor to reach these branches would dwarf what is tested, so the
13
- // mixin's methods are driven directly.
14
+ // mixin's methods and watcher are driven directly.
14
15
  const onPreview = (BrandPageMakerMixin as any).methods.onPreview;
16
+ const onLivePreviewUrlChanged = (BrandPageMakerMixin as any).watch[
17
+ 'containerStore.livePreviewUrl'
18
+ ];
15
19
 
16
20
  const createContext = (
17
21
  overrides: {
18
22
  isUserAuthenticated?: boolean;
19
23
  isPreviewModalVisible?: boolean;
24
+ containerStore?: { isPrePurchase: boolean; livePreviewUrl?: string };
20
25
  } = {},
21
26
  ) => ({
22
27
  isUserAuthenticated: true,
@@ -45,4 +50,64 @@ describe('BrandPageMaker.onPreview', () => {
45
50
  expect(context.$emit).toHaveBeenCalledWith(EVENTS.Preview);
46
51
  expect(context.$emit).not.toHaveBeenCalledWith(EVENTS.LivePreviewClicked);
47
52
  });
53
+
54
+ // Opening here would mean waiting inside the modal on a host save it can't see the
55
+ // outcome of — which is how the loader used to hang forever (BP-5870).
56
+ test('pre-purchase asks the host to save and stays closed', () => {
57
+ const context = createContext({
58
+ containerStore: { isPrePurchase: true, livePreviewUrl: 'https://stale' },
59
+ });
60
+
61
+ onPreview.call(context);
62
+
63
+ expect(context.isPreviewModalVisible).toBe(false);
64
+ expect(context.$emit).toHaveBeenCalledWith(EVENTS.LivePreviewClicked);
65
+ expect(context.$emit).not.toHaveBeenCalledWith(EVENTS.Preview);
66
+ // A stale URL from an earlier preview must not open the frame on the next click.
67
+ expect(context.containerStore.livePreviewUrl).toBeUndefined();
68
+ });
69
+ });
70
+
71
+ describe('BrandPageMaker livePreviewUrl watcher', () => {
72
+ test('opens the preview once the host save hands back a URL', () => {
73
+ const context = createContext({
74
+ containerStore: { isPrePurchase: true, livePreviewUrl: undefined },
75
+ });
76
+
77
+ onLivePreviewUrlChanged.call(
78
+ context,
79
+ 'https://brand.site/_p/request?token=x',
80
+ );
81
+
82
+ expect(context.isPreviewModalVisible).toBe(true);
83
+ expect(context.$emit).toHaveBeenCalledWith(EVENTS.Preview);
84
+ });
85
+
86
+ // A failed save or a customer sent to register never sets a URL, and the editor has
87
+ // already surfaced that — so nothing should open.
88
+ test('stays closed when the save produced no URL', () => {
89
+ const context = createContext({
90
+ containerStore: { isPrePurchase: true, livePreviewUrl: undefined },
91
+ });
92
+
93
+ onLivePreviewUrlChanged.call(context, undefined);
94
+
95
+ expect(context.isPreviewModalVisible).toBe(false);
96
+ expect(context.$emit).not.toHaveBeenCalledWith(EVENTS.Preview);
97
+ });
98
+
99
+ // Post-purchase opens the modal itself, so the watcher must not double-open it.
100
+ test('ignores post-purchase, which opens the modal itself', () => {
101
+ const context = createContext({
102
+ containerStore: { isPrePurchase: false, livePreviewUrl: undefined },
103
+ });
104
+
105
+ onLivePreviewUrlChanged.call(
106
+ context,
107
+ 'https://brand.site/_p/request?token=x',
108
+ );
109
+
110
+ expect(context.isPreviewModalVisible).toBe(false);
111
+ expect(context.$emit).not.toHaveBeenCalledWith(EVENTS.Preview);
112
+ });
48
113
  });
@@ -152,6 +152,7 @@
152
152
 
153
153
  <PreviewModalWithState
154
154
  :visible="isPreviewModalVisible"
155
+ @edit="onClosePreview"
155
156
  @publish="onPreviewPublish"
156
157
  @close="onClosePreview"
157
158
  />
@@ -5,3 +5,4 @@ export { default as SensitiveContent } from './sensitive-content/SensitiveConten
5
5
  export { default as SeoPageSettings } from './seo-settings/SeoPageSettings.vue';
6
6
  export { default as GoogleAnalytics } from './google-analytics/GoogleAnalytics.vue';
7
7
  export { default as PublishBrandPageModalWithState } from '@shared/publish-brand-page-with-state/PublishBrandPageModalWithState.vue';
8
+ export { default as PreviewModalWithState } from './preview-modal/PreviewModalWithState.vue';
@@ -2,12 +2,11 @@ import { describe, test, expect, beforeEach, vi } from 'vitest';
2
2
  import { createPinia, setActivePinia } from 'pinia';
3
3
  import { flushPromises, shallowMount } from '@vue/test-utils';
4
4
  import PreviewModalWithState from './PreviewModalWithState.vue';
5
- import PreviewModal from './PreviewModal.vue';
5
+ import LivePreviewModal from '@shared/components/live-preview-modal/LivePreviewModal.vue';
6
6
  import {
7
7
  useBrandPageContainerStore,
8
- useUiStore,
8
+ useBrandPageMakerStore,
9
9
  } from '@brand-page-maker/stores';
10
- import { UI_SCREENSIZE_MODES } from '@shared/models';
11
10
 
12
11
  const PREVIEW_URL = 'https://brand.site/_p/request?token=signed';
13
12
 
@@ -17,7 +16,7 @@ const mountModal = () =>
17
16
  });
18
17
 
19
18
  const modalProps = (wrapper: ReturnType<typeof mountModal>) =>
20
- wrapper.findComponent(PreviewModal).props();
19
+ wrapper.findComponent(LivePreviewModal).props();
21
20
 
22
21
  describe('PreviewModalWithState', () => {
23
22
  let containerStore: ReturnType<typeof useBrandPageContainerStore>;
@@ -44,7 +43,6 @@ describe('PreviewModalWithState', () => {
44
43
 
45
44
  const props = modalProps(wrapper);
46
45
 
47
- expect(props.loading).toBe(false);
48
46
  expect(props.errored).toBe(false);
49
47
  expect(props.previewUrl).toBe(PREVIEW_URL);
50
48
  });
@@ -98,9 +96,9 @@ describe('PreviewModalWithState', () => {
98
96
  expect(props.previewUrl).toBe('');
99
97
  });
100
98
 
101
- test('on a phone the preview fills the screen and the device toggle is gone', async () => {
102
- useUiStore().screenSizeMode = UI_SCREENSIZE_MODES.mobile;
103
-
99
+ // The shell now owns device state (toggle visibility + the small-screen desktop
100
+ // force); the wrapper only seeds the page type's natural default.
101
+ test('seeds the shell with the page type default device mode', async () => {
104
102
  vi.spyOn(containerStore, 'saveBrandPage').mockResolvedValue({
105
103
  saved: true,
106
104
  canPublish: true,
@@ -111,37 +109,8 @@ describe('PreviewModalWithState', () => {
111
109
  await wrapper.setProps({ visible: true });
112
110
  await flushPromises();
113
111
 
114
- const props = modalProps(wrapper);
115
-
116
- // Not 'mobile' — that mode draws a phone bezel, which on a phone would be a
117
- // device frame inside a device.
118
- expect(props.deviceMode).toBe('desktop');
119
- expect(props.showDeviceToggle).toBe(false);
120
- });
121
-
122
- // The mobile work must not reach desktop — the toggle is still there and it
123
- // still drives the frame.
124
- test('desktop keeps the device toggle and honours the viewport it picks', async () => {
125
- useUiStore().screenSizeMode = UI_SCREENSIZE_MODES.desktop;
126
-
127
- vi.spyOn(containerStore, 'saveBrandPage').mockResolvedValue({
128
- saved: true,
129
- canPublish: true,
130
- } as any);
131
-
132
- const wrapper = mountModal();
133
-
134
- await wrapper.setProps({ visible: true });
135
- await flushPromises();
136
-
137
- expect(modalProps(wrapper).showDeviceToggle).toBe(true);
138
-
139
- await wrapper
140
- .findComponent(PreviewModal)
141
- .vm.$emit('update:deviceMode', 'mobile');
142
- await flushPromises();
143
-
144
- expect(modalProps(wrapper).deviceMode).toBe('mobile');
112
+ // A link-in-bio (default) page is mobile-first.
113
+ expect(modalProps(wrapper).defaultDeviceMode).toBe('mobile');
145
114
  });
146
115
 
147
116
  // The preview sits at z-1000 and drops to z-40 for a host flow modal. Tracking
@@ -180,7 +149,7 @@ describe('PreviewModalWithState', () => {
180
149
  await wrapper.setProps({ visible: true });
181
150
  await flushPromises();
182
151
 
183
- await wrapper.findComponent(PreviewModal).vm.$emit('publish');
152
+ await wrapper.findComponent(LivePreviewModal).vm.$emit('publish');
184
153
  await flushPromises();
185
154
 
186
155
  // The host (BrandPageMaker → bc-net) runs the actual publish flow so
@@ -207,13 +176,42 @@ describe('PreviewModalWithState', () => {
207
176
 
208
177
  expect(showSaveSuccessMessage).not.toHaveBeenCalled();
209
178
 
210
- await wrapper.findComponent(PreviewModal).vm.$emit('close');
179
+ await wrapper.findComponent(LivePreviewModal).vm.$emit('close');
211
180
  await flushPromises();
212
181
 
213
182
  expect(wrapper.emitted('close')).toHaveLength(1);
214
183
  expect(showSaveSuccessMessage).toHaveBeenCalledOnce();
215
184
  });
216
185
 
186
+ // Pre-purchase skips saveBrandPage in openPreview (the host already saved), so
187
+ // containerStore.triggerSaveSuccessMessage is never armed for showSaveSuccessMessage
188
+ // to gate on — announce the host's save directly instead, same as it does elsewhere.
189
+ test('raises the save banner directly on close when pre-purchase', async () => {
190
+ containerStore.isPrePurchase = true;
191
+
192
+ const showSaveSuccessMessage = vi.spyOn(
193
+ containerStore,
194
+ 'showSaveSuccessMessage',
195
+ );
196
+ const enqueueSaveSuccessMessage = vi
197
+ .spyOn(useBrandPageMakerStore(), 'enqueueSaveSuccessMessage')
198
+ .mockResolvedValue(undefined);
199
+
200
+ const wrapper = mountModal();
201
+
202
+ await wrapper.setProps({ visible: true });
203
+ await flushPromises();
204
+
205
+ await wrapper.findComponent(LivePreviewModal).vm.$emit('close');
206
+ await flushPromises();
207
+
208
+ expect(wrapper.emitted('close')).toHaveLength(1);
209
+ expect(enqueueSaveSuccessMessage).toHaveBeenCalledWith({
210
+ noDescription: true,
211
+ });
212
+ expect(showSaveSuccessMessage).not.toHaveBeenCalled();
213
+ });
214
+
217
215
  // BP-5957: the modal uppercases its labels on BrandCrowd, not Design.com.
218
216
  test.each([true, false])(
219
217
  'passes isDesignCom=%s to the modal',
@@ -223,4 +221,26 @@ describe('PreviewModalWithState', () => {
223
221
  expect(modalProps(mountModal()).isDesignCom).toBe(isDesignCom);
224
222
  },
225
223
  );
224
+
225
+ // The editor keeps its publish CTA with no prop; other surfaces override it.
226
+ test('defaults the CTA to publish so the editor is unchanged', () => {
227
+ expect(modalProps(mountModal()).cta).toBe('publish');
228
+ });
229
+
230
+ test('passes the surface CTA through to the shell', () => {
231
+ const wrapper = shallowMount(PreviewModalWithState, {
232
+ props: { visible: false, cta: 'edit' },
233
+ });
234
+
235
+ expect(wrapper.findComponent(LivePreviewModal).props().cta).toBe('edit');
236
+ });
237
+
238
+ // Manage surface: the shell's Edit CTA is relayed so the host navigates to the editor.
239
+ test('relays the shell edit CTA to the host', async () => {
240
+ const wrapper = mountModal();
241
+
242
+ await wrapper.findComponent(LivePreviewModal).vm.$emit('edit');
243
+
244
+ expect(wrapper.emitted('edit')).toHaveLength(1);
245
+ });
226
246
  });
@@ -1,19 +1,15 @@
1
1
  <template>
2
- <PreviewModal
2
+ <LivePreviewModal
3
3
  data-component="preview-modal-with-state"
4
4
  :visible="visible"
5
5
  :preview-url="previewUrl"
6
- :edit-label="previewModalTr('edit')"
7
6
  :cta="cta"
8
- :device-mode="effectiveDeviceMode"
9
- :show-device-toggle="!isMobileScreenSize"
7
+ :default-device-mode="defaultDeviceMode"
10
8
  :is-design-com="containerStore.isDesignCom"
11
- :loading="loading"
12
9
  :errored="errored"
13
10
  :publishing="publishing"
14
11
  :flow-modal-open="isFlowModalOpen"
15
- @update:device-mode="deviceMode = $event"
16
- @edit="onClose"
12
+ @edit="onEdit"
17
13
  @publish="onPublish"
18
14
  @retry="openPreview"
19
15
  @close="onClose"
@@ -22,57 +18,42 @@
22
18
 
23
19
  <script setup lang="ts">
24
20
  import { ref, computed, watch, type PropType } from 'vue';
25
- import PreviewModal from './PreviewModal.vue';
21
+ import LivePreviewModal from '@shared/components/live-preview-modal/LivePreviewModal.vue';
22
+ import {
23
+ getDefaultPreviewDeviceMode,
24
+ type LivePreviewCta,
25
+ } from '@shared/components/live-preview-modal/live-preview-modal.utils';
26
26
  import {
27
27
  useBrandPageContainerStore,
28
28
  useBrandPageMakerStore,
29
- useUiStore,
30
29
  } from '@brand-page-maker/stores';
31
- import { BRAND_PAGE_TYPES } from '@brand-page';
32
- import { previewModalTr } from '@src/composables/useTranslate';
33
- import { UI_SCREENSIZE_MODES, type UiDeviceMode } from '@shared/models';
30
+ import type { UiDeviceMode } from '@shared/models';
34
31
 
35
32
  const props = defineProps({
36
33
  visible: { type: Boolean, default: false },
37
- cta: {
38
- type: String as PropType<'publish' | 'edit' | 'none'>,
39
- default: 'publish',
40
- },
34
+ // Per-surface header CTA; defaults to 'publish' so the editor is unchanged.
35
+ cta: { type: String as PropType<LivePreviewCta>, default: 'publish' },
41
36
  });
42
37
 
43
- const emit = defineEmits<{ close: []; publish: [] }>();
38
+ const emit = defineEmits<{ close: []; publish: []; edit: [] }>();
44
39
 
45
40
  const containerStore = useBrandPageContainerStore();
46
41
  const makerStore = useBrandPageMakerStore();
47
- const uiStore = useUiStore();
48
-
49
- const isMobileScreenSize = computed(
50
- () => uiStore.screenSizeMode === UI_SCREENSIZE_MODES.mobile,
51
- );
52
42
 
53
43
  // The preview must step back for any modal opening over the editor.
54
44
  const isFlowModalOpen = computed(() => containerStore.isAnyModalVisible);
55
45
 
56
- // brandSite is desktop-primary; brand-page and brand-contact are mobile-first.
57
- const defaultDeviceMode = (): UiDeviceMode =>
58
- makerStore.brandPageType === BRAND_PAGE_TYPES.brandSite
59
- ? 'desktop'
60
- : 'mobile';
61
-
62
- const deviceMode = ref<UiDeviceMode>(defaultDeviceMode());
63
-
64
- const effectiveDeviceMode = computed<UiDeviceMode>(() =>
65
- isMobileScreenSize.value ? 'desktop' : deviceMode.value,
46
+ // Page type's default; the shell handles the small-screen case itself.
47
+ const defaultDeviceMode = computed<UiDeviceMode>(() =>
48
+ getDefaultPreviewDeviceMode(makerStore.brandPageType),
66
49
  );
67
50
 
68
51
  const previewUrl = ref('');
69
- const loading = ref(false);
70
52
  const errored = ref(false);
71
53
  const publishing = ref(false);
72
54
 
73
55
  // Save first so the preview is never stale; saveOnly stops a live site republishing.
74
56
  const openPreview = async (): Promise<void> => {
75
- loading.value = true;
76
57
  errored.value = false;
77
58
  previewUrl.value = '';
78
59
 
@@ -82,7 +63,6 @@ const openPreview = async (): Promise<void> => {
82
63
 
83
64
  if (!result || !result.saved) {
84
65
  errored.value = true;
85
- loading.value = false;
86
66
 
87
67
  return;
88
68
  }
@@ -93,22 +73,38 @@ const openPreview = async (): Promise<void> => {
93
73
 
94
74
  if (!url) {
95
75
  errored.value = true;
96
- loading.value = false;
97
76
 
98
77
  return;
99
78
  }
100
79
 
101
80
  previewUrl.value = url;
102
- loading.value = false;
103
81
  };
104
82
 
105
- // The preview covers the editor, so its save is announced on the way out. No-ops
106
- // when the save wasn't ours (pre-purchase) or was already announced (publish).
83
+ // The preview covers the editor, so its save is announced on the way out — from either
84
+ // exit. Pre-purchase already saved before the preview opened (host-owned), so there's
85
+ // nothing armed on containerStore for it to gate — announce that save directly instead.
86
+ // Post-purchase routes through the store's own armed flag (no-ops if it wasn't ours, or
87
+ // was already announced by publish).
88
+ const announceExit = async (): Promise<void> => {
89
+ if (containerStore.isPrePurchase) {
90
+ await makerStore.enqueueSaveSuccessMessage({ noDescription: true });
91
+ } else {
92
+ await containerStore.showSaveSuccessMessage();
93
+ }
94
+
95
+ await makerStore.showNextNotificationMessage();
96
+ };
97
+
107
98
  const onClose = async (): Promise<void> => {
108
99
  emit('close');
109
100
 
110
- await containerStore.showSaveSuccessMessage();
111
- await makerStore.showNextNotificationMessage();
101
+ await announceExit();
102
+ };
103
+
104
+ const onEdit = async (): Promise<void> => {
105
+ emit('edit');
106
+
107
+ await announceExit();
112
108
  };
113
109
 
114
110
  const onPublish = (): void => {
@@ -121,7 +117,6 @@ watch(
121
117
  () => props.visible,
122
118
  (isVisible) => {
123
119
  if (isVisible) {
124
- deviceMode.value = defaultDeviceMode();
125
120
  openPreview();
126
121
  } else {
127
122
  previewUrl.value = '';
@@ -1126,10 +1126,6 @@
1126
1126
  "linkInBioFriendlyName": "Link in der Biografie",
1127
1127
  "digitalBusinessCardFriendlyName": "Digitale Visitenkarte"
1128
1128
  },
1129
- "previewModal": {
1130
- "edit": "Bearbeiten",
1131
- "retry": "Erneut versuchen"
1132
- },
1133
1129
  "quickLinkBar": {
1134
1130
  "myWebPageLabel": "Meine Webseite:",
1135
1131
  "copyButton": "Kopieren",
@@ -1429,6 +1425,11 @@
1429
1425
  "contactFormFieldRequired": "Please fill out this field.",
1430
1426
  "contactFormFieldTooLong": "The field you entered is too long. The maximum length is {{MAX_LENGTH}}."
1431
1427
  },
1428
+ "previewModal": {
1429
+ "edit": "Website bearbeiten",
1430
+ "backToEditor": "Zurück zum Editor",
1431
+ "retry": "Erneut versuchen"
1432
+ },
1432
1433
  "confirmModal": {
1433
1434
  "confirmMessageTitle": "Sind Sie sicher?",
1434
1435
  "noLabel": "Nein",
@@ -1126,10 +1126,6 @@
1126
1126
  "linkInBioFriendlyName": "Enlace en la bio",
1127
1127
  "digitalBusinessCardFriendlyName": "Tarjeta de visita digital"
1128
1128
  },
1129
- "previewModal": {
1130
- "edit": "Editar",
1131
- "retry": "Vuelve a intentarlo"
1132
- },
1133
1129
  "quickLinkBar": {
1134
1130
  "myWebPageLabel": "Mi página web:",
1135
1131
  "copyButton": "Copiar",
@@ -1429,6 +1425,11 @@
1429
1425
  "contactFormFieldRequired": "Please fill out this field.",
1430
1426
  "contactFormFieldTooLong": "The field you entered is too long. The maximum length is {{MAX_LENGTH}}."
1431
1427
  },
1428
+ "previewModal": {
1429
+ "edit": "Edita el sitio web",
1430
+ "backToEditor": "Volver al editor",
1431
+ "retry": "Vuelve a intentarlo"
1432
+ },
1432
1433
  "confirmModal": {
1433
1434
  "confirmMessageTitle": "¿Estás seguro?",
1434
1435
  "noLabel": "No",
@@ -1126,10 +1126,6 @@
1126
1126
  "linkInBioFriendlyName": "Lien dans la bio",
1127
1127
  "digitalBusinessCardFriendlyName": "Carte de visite numérique"
1128
1128
  },
1129
- "previewModal": {
1130
- "edit": "Éditer",
1131
- "retry": "Réessayez"
1132
- },
1133
1129
  "quickLinkBar": {
1134
1130
  "myWebPageLabel": "Ma page Web :",
1135
1131
  "copyButton": "Copier",
@@ -1429,6 +1425,11 @@
1429
1425
  "contactFormFieldRequired": "Please fill out this field.",
1430
1426
  "contactFormFieldTooLong": "The field you entered is too long. The maximum length is {{MAX_LENGTH}}."
1431
1427
  },
1428
+ "previewModal": {
1429
+ "edit": "Modifier le site Web",
1430
+ "backToEditor": "Retour à l'éditeur",
1431
+ "retry": "Réessayez"
1432
+ },
1432
1433
  "confirmModal": {
1433
1434
  "confirmMessageTitle": "Êtes-vous sûr ?",
1434
1435
  "noLabel": "Non",
@@ -1126,10 +1126,6 @@
1126
1126
  "linkInBioFriendlyName": "Lien dans la bio",
1127
1127
  "digitalBusinessCardFriendlyName": "Carte de visite numérique"
1128
1128
  },
1129
- "previewModal": {
1130
- "edit": "Éditer",
1131
- "retry": "Réessayez"
1132
- },
1133
1129
  "quickLinkBar": {
1134
1130
  "myWebPageLabel": "Ma page Web :",
1135
1131
  "copyButton": "Copier",
@@ -1429,6 +1425,11 @@
1429
1425
  "contactFormFieldRequired": "Please fill out this field.",
1430
1426
  "contactFormFieldTooLong": "The field you entered is too long. The maximum length is {{MAX_LENGTH}}."
1431
1427
  },
1428
+ "previewModal": {
1429
+ "edit": "Modifier le site Web",
1430
+ "backToEditor": "Retour à l'éditeur",
1431
+ "retry": "Réessayez"
1432
+ },
1432
1433
  "confirmModal": {
1433
1434
  "confirmMessageTitle": "Êtes-vous sûr ?",
1434
1435
  "noLabel": "Non",
@@ -1126,10 +1126,6 @@
1126
1126
  "linkInBioFriendlyName": "Link in Bio",
1127
1127
  "digitalBusinessCardFriendlyName": "Digital Business Card"
1128
1128
  },
1129
- "previewModal": {
1130
- "edit": "Edit",
1131
- "retry": "Retry"
1132
- },
1133
1129
  "quickLinkBar": {
1134
1130
  "myWebPageLabel": "My web page:",
1135
1131
  "copyButton": "Copy",
@@ -1429,6 +1425,11 @@
1429
1425
  "contactFormFieldRequired": "Please fill out this field.",
1430
1426
  "contactFormFieldTooLong": "The field you entered is too long. The maximum length is {{MAX_LENGTH}}."
1431
1427
  },
1428
+ "previewModal": {
1429
+ "edit": "Edit Website",
1430
+ "backToEditor": "Back to Editor",
1431
+ "retry": "Retry"
1432
+ },
1432
1433
  "confirmModal": {
1433
1434
  "confirmMessageTitle": "Are you sure?",
1434
1435
  "noLabel": "No",
@@ -1832,6 +1833,10 @@
1832
1833
  },
1833
1834
  "pageSettings": {
1834
1835
  "headerSettings": "Header Settings",
1836
+ "navigationSectionTitle": "Navigation",
1837
+ "headerSectionTitle": "Header",
1838
+ "logoSectionTitle": "Logo",
1839
+ "buttonSectionTitle": "Button",
1835
1840
  "footerSettings": "Footer Settings",
1836
1841
  "managePages": "Manage Pages",
1837
1842
  "pages": "Pages",
@@ -1126,10 +1126,6 @@
1126
1126
  "linkInBioFriendlyName": "Link na Bio",
1127
1127
  "digitalBusinessCardFriendlyName": "Cartão de visita digital"
1128
1128
  },
1129
- "previewModal": {
1130
- "edit": "Editar",
1131
- "retry": "Tente novamente"
1132
- },
1133
1129
  "quickLinkBar": {
1134
1130
  "myWebPageLabel": "Minha página web:",
1135
1131
  "copyButton": "Copiar",
@@ -1429,6 +1425,11 @@
1429
1425
  "contactFormFieldRequired": "Please fill out this field.",
1430
1426
  "contactFormFieldTooLong": "The field you entered is too long. The maximum length is {{MAX_LENGTH}}."
1431
1427
  },
1428
+ "previewModal": {
1429
+ "edit": "Editar site",
1430
+ "backToEditor": "Voltar para o editor",
1431
+ "retry": "Tente novamente"
1432
+ },
1432
1433
  "confirmModal": {
1433
1434
  "confirmMessageTitle": "Tem a certeza?",
1434
1435
  "noLabel": "Não",
@@ -1126,10 +1126,6 @@
1126
1126
  "linkInBioFriendlyName": "Link na biografia",
1127
1127
  "digitalBusinessCardFriendlyName": "Cartão de visita digital"
1128
1128
  },
1129
- "previewModal": {
1130
- "edit": "Editar",
1131
- "retry": "Repetir"
1132
- },
1133
1129
  "quickLinkBar": {
1134
1130
  "myWebPageLabel": "A minha página Web:",
1135
1131
  "copyButton": "Copiar",
@@ -1429,6 +1425,11 @@
1429
1425
  "contactFormFieldRequired": "Please fill out this field.",
1430
1426
  "contactFormFieldTooLong": "The field you entered is too long. The maximum length is {{MAX_LENGTH}}."
1431
1427
  },
1428
+ "previewModal": {
1429
+ "edit": "Editar Website",
1430
+ "backToEditor": "Voltar ao Editor",
1431
+ "retry": "Repetir"
1432
+ },
1432
1433
  "confirmModal": {
1433
1434
  "confirmMessageTitle": "Tem a certeza?",
1434
1435
  "noLabel": "Não",
package/src/lib.ts CHANGED
@@ -57,6 +57,7 @@ export {
57
57
  UrlSettings,
58
58
  GoogleAnalytics,
59
59
  PublishBrandPageModalWithState,
60
+ PreviewModalWithState,
60
61
  SensitiveContent,
61
62
  UseExistingDomainModal,
62
63
  } from './brand-page-maker/components';