@designcrowd/library.brand-page 6.2.0 → 6.2.2-live-preview-1

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 (186) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-VQCQvlMg.js → CreatedOnBrandCrowd-de-de-Cn0M-eZ1.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DSf3BjCG.js → CreatedOnBrandCrowd-en-us-DRLMSDZr.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DGcdM9Ec.js → CreatedOnBrandCrowd-es-es-BglTu73U.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-C9g1fc39.js → CreatedOnBrandCrowd-fr-ca-Bj6YZ_el.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-C9g1fc39.js → CreatedOnBrandCrowd-fr-fr-Bj6YZ_el.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-DSXr7Bxl.js → CreatedOnBrandCrowd-pt-br-DEoOmQ1F.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-DSXr7Bxl.js → CreatedOnBrandCrowd-pt-pt-DEoOmQ1F.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-BPdCL6wS.js → CreatedOnDcom-de-de-BZ7gTt4Z.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-CLkSTQRu.js → CreatedOnDcom-en-us-CD6Km5sH.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-CRknMAGv.js → CreatedOnDcom-es-es-CNpZHvEl.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-ClAY_dHI.js → CreatedOnDcom-fr-ca-URS-ydJ1.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DQtv9rR-.js → CreatedOnDcom-fr-fr-D2AVeA3H.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-BbIxvmxv.js → CreatedOnDcom-pt-br-C32frnFs.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-BOfOesBj.js → CreatedOnDcom-pt-pt-TDYGjRXS.js} +1 -1
  15. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +16 -2
  16. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +17 -0
  17. package/lib/src/brand-page/components/page/Page.mixin.d.ts +2 -1
  18. package/lib/src/brand-page/components/page/Page.mixin.js +12 -7
  19. package/lib/src/brand-page/components/page-header/HeaderLayout.stories.d.ts +1 -0
  20. package/lib/src/brand-page/components/page-header/HeaderLayout.stories.js +9 -0
  21. package/lib/src/brand-page/components/page-header/PageHeader.mixin.d.ts +3 -1
  22. package/lib/src/brand-page/components/page-header/PageHeader.mixin.js +10 -4
  23. package/lib/src/brand-page/constants/sections/header-display.constants.d.ts +3 -0
  24. package/lib/src/brand-page/constants/sections/header-display.constants.js +15 -0
  25. package/lib/src/brand-page/models/brand-page-context.model.d.ts +0 -2
  26. package/lib/src/brand-page/models/brand-page-context.model.js +0 -12
  27. package/lib/src/brand-page/models/index.d.ts +12 -0
  28. package/lib/src/brand-page/models/index.js +4 -0
  29. package/lib/src/brand-page/utils/context.util.d.ts +1 -0
  30. package/lib/src/brand-page/utils/context.util.js +9 -0
  31. package/lib/src/brand-page/utils/route.util.d.ts +1 -0
  32. package/lib/src/brand-page/utils/route.util.js +9 -0
  33. package/lib/src/brand-page/utils/view-model.util.js +53 -1
  34. package/lib/src/brand-page/view-models.d.ts +3 -1
  35. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +0 -1
  36. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.js +0 -9
  37. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +9 -44
  38. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +8 -43
  39. package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +0 -14
  40. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +0 -42
  41. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +0 -42
  42. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +0 -42
  43. package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +0 -42
  44. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +0 -42
  45. package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +0 -42
  46. package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +3 -0
  47. package/lib/src/brand-page-maker/composables/useEventTracker.js +10 -0
  48. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +1626 -0
  49. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +0 -42
  50. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +2 -4
  51. package/lib/src/brand-page-maker/validators/section-button.validator.d.ts +5 -2
  52. package/lib/src/brand-page-maker/validators/section-button.validator.js +11 -15
  53. package/lib/src/{bundled-translations-B2q5GWTQ.js → bundled-translations-BYUfWBT3.js} +17 -17
  54. package/lib/src/{bundled-translations.de-DE-FhH2H_rd.js → bundled-translations.de-DE-MmzUu1tt.js} +21 -21
  55. package/lib/src/{bundled-translations.es-ES-_ZExxMi7.js → bundled-translations.es-ES-D-tG33XL.js} +5 -5
  56. package/lib/src/{bundled-translations.fr-CA-Tg-ODIiC.js → bundled-translations.fr-CA-CNc1H1h3.js} +1 -1
  57. package/lib/src/{bundled-translations.fr-FR-C0sU51Mz.js → bundled-translations.fr-FR-DX0-yepT.js} +9 -9
  58. package/lib/src/{bundled-translations.pt-BR-MYFIhA4u.js → bundled-translations.pt-BR-ih1-LRgy.js} +9 -9
  59. package/lib/src/{bundled-translations.pt-PT-DeUBSzCB.js → bundled-translations.pt-PT-Dq2CmD1p.js} +22 -22
  60. package/lib/src/bundles/bundled-translations.de-DE.json +9 -2
  61. package/lib/src/bundles/bundled-translations.es-ES.json +9 -2
  62. package/lib/src/bundles/bundled-translations.fr-CA.json +9 -2
  63. package/lib/src/bundles/bundled-translations.fr-FR.json +9 -2
  64. package/lib/src/bundles/bundled-translations.json +9 -2
  65. package/lib/src/bundles/bundled-translations.pt-BR.json +9 -2
  66. package/lib/src/bundles/bundled-translations.pt-PT.json +9 -2
  67. package/lib/src/composables/useTranslate.d.ts +1 -1
  68. package/lib/src/composables/useTranslate.js +1 -1
  69. package/lib/src/css/library.brand-page-brandCrowd.min.css +577 -139
  70. package/lib/src/css/library.brand-page-brandPage.min.css +577 -139
  71. package/lib/src/css/library.brand-page-designCom.min.css +577 -139
  72. package/lib/src/css/library.brand-page-preview.min.css +577 -139
  73. package/lib/src/index.mjs +2 -2
  74. package/lib/src/{lib-BMVF4uNy.js → lib-Dm9AuOJL.js} +22487 -21900
  75. package/lib/src/metadata-builder/builders/item-id-utils.d.ts +2 -0
  76. package/lib/src/metadata-builder/builders/item-id-utils.js +8 -2
  77. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +10 -2
  78. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +7 -1
  79. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +1 -0
  80. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +6 -1
  81. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +6 -43
  82. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +14 -2
  83. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.js +14 -0
  84. package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.d.ts +9 -1
  85. package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.js +31 -9
  86. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +1 -0
  87. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +6 -43
  88. package/lib/src/shared/page-navigation/store/page-navigation-store.d.ts +3 -1
  89. package/lib/src/shared/page-navigation/store/page-navigation-store.js +34 -9
  90. package/lib/src/shared/page-settings/components/delete-page-modal/DeletePageModal.mixin.d.ts +1 -1
  91. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +9 -2
  92. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +200 -44
  93. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.js +12 -35
  94. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +190 -44
  95. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +34 -2
  96. package/lib/src/shared/page-settings/duplicate-page.util.d.ts +4 -0
  97. package/lib/src/shared/page-settings/duplicate-page.util.js +82 -0
  98. package/lib/src/shared/page-settings/models.d.ts +1 -0
  99. package/lib/src/shared/page-settings/store/page-settings-store.d.ts +7 -2
  100. package/lib/src/shared/page-settings/store/page-settings-store.js +90 -7
  101. package/lib/src/shared/page-settings/utils.d.ts +3 -0
  102. package/lib/src/shared/page-settings/utils.js +6 -0
  103. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -56
  104. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +0 -42
  105. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +1 -0
  106. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +4 -42
  107. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +1 -0
  108. package/lib/src/tracker.mjs +1 -1
  109. package/lib/src/{useEventTracker-C5Ql5p7u.js → useEventTracker-DwPTmzpa.js} +13 -3
  110. package/package.json +2 -2
  111. package/src/brand-page/components/navigation/hamburger/HamburgerNavigation.vue +1 -0
  112. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +18 -1
  113. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +73 -3
  114. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts +181 -0
  115. package/src/brand-page/components/page/Page.mixin.ts +21 -11
  116. package/src/brand-page/components/page/Page.vue +2 -0
  117. package/src/brand-page/components/page-header/HeaderLayout.stories.ts +10 -0
  118. package/src/brand-page/components/page-header/HeaderLayout.vue +415 -71
  119. package/src/brand-page/components/page-header/PageHeader.mixin.ts +14 -10
  120. package/src/brand-page/components/page-header/PageHeader.vue +16 -1
  121. package/src/brand-page/components/page-header/PageHeader.vue.test.ts +339 -0
  122. package/src/brand-page/constants/sections/header-display.constants.test.ts +44 -0
  123. package/src/brand-page/constants/sections/header-display.constants.ts +35 -0
  124. package/src/brand-page/models/brand-page-context.model.ts +0 -23
  125. package/src/brand-page/models/header-button.model.test.ts +85 -0
  126. package/src/brand-page/models/index.ts +19 -0
  127. package/src/brand-page/utils/context.util.ts +15 -0
  128. package/src/brand-page/utils/route.util.ts +13 -0
  129. package/src/brand-page/utils/view-model.util.test.ts +151 -1
  130. package/src/brand-page/utils/view-model.util.ts +73 -1
  131. package/src/brand-page/view-models.ts +3 -0
  132. package/src/brand-page-maker/components/action-bar/ActionBar.mixin.ts +0 -10
  133. package/src/brand-page-maker/components/action-bar/ActionBarNavigation.test.ts +8 -49
  134. package/src/brand-page-maker/components/action-bar/ActionBarNavigation.vue +1 -1
  135. package/src/brand-page-maker/composables/useEventTracker.ts +13 -0
  136. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +2 -4
  137. package/src/brand-page-maker/validators/section-button.validator.ts +23 -19
  138. package/src/bundles/bundled-translations.de-DE.json +9 -2
  139. package/src/bundles/bundled-translations.es-ES.json +9 -2
  140. package/src/bundles/bundled-translations.fr-CA.json +9 -2
  141. package/src/bundles/bundled-translations.fr-FR.json +9 -2
  142. package/src/bundles/bundled-translations.json +9 -2
  143. package/src/bundles/bundled-translations.pt-BR.json +9 -2
  144. package/src/bundles/bundled-translations.pt-PT.json +9 -2
  145. package/src/composables/useTranslate.ts +2 -1
  146. package/src/metadata-builder/builders/item-id-utils.ts +9 -2
  147. package/src/shared/content-settings/components/text-field/TextField.mixin.ts +15 -0
  148. package/src/shared/content-settings/components/url-field/UrlField.vue +1 -0
  149. package/src/shared/ecommerce-settings/components/product-config-field/ProductConfigOption.vue +0 -2
  150. package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.ts +32 -10
  151. package/src/shared/page-navigation/store/page-navigation-store.ts +57 -13
  152. package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.button-section.test.ts +131 -0
  153. package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +65 -10
  154. package/src/shared/page-settings/components/page-settings/NestedDraggable.vue +3 -0
  155. package/src/shared/page-settings/components/page-settings/PageElement.vue +2 -0
  156. package/src/shared/page-settings/components/page-settings/PageSettingMenu.test.ts +105 -0
  157. package/src/shared/page-settings/components/page-settings/PageSettingMenu.vue +16 -0
  158. package/src/shared/page-settings/components/page-settings/PageSettings.duplicate.test.ts +108 -0
  159. package/src/shared/page-settings/components/page-settings/PageSettings.mixin.ts +22 -50
  160. package/src/shared/page-settings/components/page-settings/PageSettings.vue +1 -0
  161. package/src/shared/page-settings/components/page-settings/i18n/page-settings.de-DE.json +3 -1
  162. package/src/shared/page-settings/components/page-settings/i18n/page-settings.es-ES.json +3 -1
  163. package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-CA.json +3 -1
  164. package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-FR.json +3 -1
  165. package/src/shared/page-settings/components/page-settings/i18n/page-settings.json +3 -1
  166. package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-BR.json +3 -1
  167. package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-PT.json +3 -1
  168. package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.test.ts +305 -0
  169. package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.vue +265 -0
  170. package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.test.ts +354 -0
  171. package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.vue +48 -9
  172. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.header-button.test.ts +114 -0
  173. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +38 -1
  174. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.de-DE.json +6 -1
  175. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.es-ES.json +6 -1
  176. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.fr-CA.json +6 -1
  177. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.fr-FR.json +6 -1
  178. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.json +6 -1
  179. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.pt-BR.json +6 -1
  180. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.pt-PT.json +6 -1
  181. package/src/shared/page-settings/duplicate-page.util.test.ts +382 -0
  182. package/src/shared/page-settings/duplicate-page.util.ts +124 -0
  183. package/src/shared/page-settings/models.ts +1 -0
  184. package/src/shared/page-settings/store/page-settings-store.test.ts +441 -1
  185. package/src/shared/page-settings/store/page-settings-store.ts +147 -11
  186. package/src/shared/page-settings/utils.ts +10 -0
@@ -64,6 +64,7 @@
64
64
  :on-select="handleHeaderDisplayStyleChange"
65
65
  :viewport="viewport"
66
66
  :is-logo-visible="isLogoVisible"
67
+ :show-header-button="isHeaderButtonVisible"
67
68
  />
68
69
 
69
70
  <NavigationStyleSettings
@@ -144,27 +145,63 @@
144
145
  </div>
145
146
  </CollapsiblePanel>
146
147
 
147
- <!-- Button section hidden for now (BP-5854) — re-enable once BP-5852 implements
148
- the header button. Left commented rather than removed since the shell
149
- (CollapsiblePanel + collapsedSections.button state) is already wired up.
150
- <CollapsiblePanel
151
- data-component="collapsible-panel"
152
- :title="pageSettingsTr('buttonSectionTitle')"
153
- :is-collapsed="collapsedSections.button"
154
- @changed="toggleSectionCollapsed('button')"
155
- />
156
- -->
148
+ <div
149
+ v-if="isBrandSite"
150
+ data-test-header-button-section
151
+ class="tw-bg-white tw-border tw-border-grayscale-500 tw-border-solid tw-flex tw-flex-col tw-mb-4 tw-p-4 tw-rounded-md"
152
+ >
153
+ <div
154
+ data-test-header-button-section-header
155
+ class="tw-cursor-pointer tw-text-secondary-500 tw-flex tw-items-center tw-select-none"
156
+ :class="{
157
+ 'tw-mb-2': !collapsedSections.button && isHeaderButtonVisible,
158
+ }"
159
+ @click="onButtonSectionChanged"
160
+ >
161
+ <div
162
+ class="tw-grow tw-text-md tw-font-sans tw-font-bold tw-text-secondary-800"
163
+ >
164
+ {{ pageSettingsTr('buttonSectionTitle') }}
165
+ </div>
166
+ <div class="tw-ml-2" @click.stop>
167
+ <Toggle
168
+ id="header-button-visible"
169
+ data-test-header-button-toggle
170
+ color="primary"
171
+ :model-value="isHeaderButtonVisible"
172
+ @update:model-value="onHeaderButtonToggle"
173
+ />
174
+ </div>
175
+ <Icon
176
+ class="tw-ml-2 tw-text-grayscale-600"
177
+ :name="collapsedSections.button ? 'chevron-down' : 'chevron-up'"
178
+ size="sm"
179
+ />
180
+ </div>
181
+ <HeaderButtonSettings
182
+ v-if="!collapsedSections.button && isHeaderButtonVisible"
183
+ :value="headerButton"
184
+ :is-admin-maker="isAdminMaker"
185
+ :logo-theme="logoTheme"
186
+ @on-change="onHeaderButtonChange"
187
+ />
188
+ </div>
189
+
190
+ <!-- The Page dropdown's ADD PAGE opens this; every panel hosting a PageDropdownField mounts its own. -->
191
+ <NewPageModal />
157
192
  </div>
158
193
  </template>
159
194
  <script>
160
195
  import {
161
196
  CollapsiblePanel,
162
197
  HelloBar,
198
+ Icon,
163
199
  Slider,
164
200
  TabMenu,
165
201
  Toggle,
166
202
  } from '@designcrowd/fe-shared-lib';
167
203
  import PageSettingsSectionStyleMixin from '../page-settings-section-styles/PageSettingsSectionStyles.mixin.ts';
204
+ import HeaderButtonSettings from '../page-settings-section-styles/HeaderButtonSettings.vue';
168
205
  import HeaderDisplayStyle from '../page-settings-section-styles/HeaderDisplayStyle.vue';
169
206
  import HeaderNavigationType from '../page-settings-section-styles/HeaderNavigationType.vue';
170
207
  import HeaderNavigationStyle from '../page-settings-section-styles/HeaderNavigationStyle.vue';
@@ -173,16 +210,20 @@ import ColorField from '../../../content-settings/components/color-field/ColorFi
173
210
  import LogoSelection from '@shared/content-settings/components/logo-selection-field/LogoSelection.vue';
174
211
  import LogoSizeSlider from '@shared/content-settings/components/logo-size-field/LogoSizeSlider.vue';
175
212
  import SideBarSettingsHeader from '../../../common/SideBarSettingsHeader.vue';
213
+ import NewPageModal from '../new-page-modal/NewPageModal.vue';
176
214
  import { tr, commonTr, pageSettingsTr } from '@src/composables/useTranslate';
177
215
 
178
216
  export default {
179
217
  components: {
218
+ NewPageModal,
180
219
  CollapsiblePanel,
181
220
  ColorField,
182
221
  HelloBar,
222
+ Icon,
183
223
  Slider,
184
224
  TabMenu,
185
225
  Toggle,
226
+ HeaderButtonSettings,
186
227
  HeaderDisplayStyle,
187
228
  HeaderNavigationType,
188
229
  HeaderNavigationStyle,
@@ -222,6 +263,20 @@ export default {
222
263
  toggleSectionCollapsed(section) {
223
264
  this.collapsedSections[section] = !this.collapsedSections[section];
224
265
  },
266
+ onHeaderButtonToggle(isVisible) {
267
+ this.onHeaderButtonChange({ isVisible });
268
+
269
+ this.collapsedSections.button = !isVisible;
270
+ },
271
+ onButtonSectionChanged() {
272
+ const isExpanding = this.collapsedSections.button;
273
+
274
+ this.toggleSectionCollapsed('button');
275
+
276
+ if (isExpanding && !this.isHeaderButtonVisible) {
277
+ this.onHeaderButtonChange({ isVisible: true });
278
+ }
279
+ },
225
280
  },
226
281
  };
227
282
  </script>
@@ -28,6 +28,7 @@
28
28
  :delete-page-selected="onDeletePageSelected"
29
29
  :change-page-visibility="onChangePageVisibility"
30
30
  :on-add-sub-page-selected="onAddSubPageSelected"
31
+ :on-duplicate-page-selected="onDuplicatePageSelected"
31
32
  :total-pages="totalPages"
32
33
  :visible-page-count="visiblePageCount"
33
34
  />
@@ -41,6 +42,7 @@
41
42
  :on-delete-page-selected="onDeletePageSelected"
42
43
  :on-change-page-visibility="onChangePageVisibility"
43
44
  :on-add-sub-page-selected="onAddSubPageSelected"
45
+ :on-duplicate-page-selected="onDuplicatePageSelected"
44
46
  :move="handleMove"
45
47
  @drag-start="handleStart"
46
48
  @drag-end="handleEnd"
@@ -92,6 +94,7 @@ interface Props {
92
94
  onDeletePageSelected: (pageId: string) => void;
93
95
  onChangePageVisibility: (pageId: string) => void;
94
96
  onAddSubPageSelected: (pageId: string) => void;
97
+ onDuplicatePageSelected: (pageId: string) => void;
95
98
  }
96
99
 
97
100
  const props = defineProps<Props>();
@@ -45,6 +45,7 @@
45
45
  :on-delete-page-selected="deletePageSelected"
46
46
  :on-change-page-visibility="changePageVisibility"
47
47
  :on-add-sub-page-selected="onAddSubPageSelected"
48
+ :on-duplicate-page-selected="onDuplicatePageSelected"
48
49
  :is-root-page="isRootPage"
49
50
  :total-pages="totalPages"
50
51
  :visible-page-count="visiblePageCount"
@@ -80,6 +81,7 @@ interface Props {
80
81
  deletePageSelected: (pageId: string) => void;
81
82
  changePageVisibility: (pageId: string) => void;
82
83
  onAddSubPageSelected: (pageId: string) => void;
84
+ onDuplicatePageSelected: (pageId: string) => void;
83
85
  }
84
86
 
85
87
  defineProps<Props>();
@@ -0,0 +1,105 @@
1
+ import { describe, test, expect, beforeAll, vi } from 'vitest';
2
+ import { createPinia, setActivePinia } from 'pinia';
3
+ import { mount } from '@vue/test-utils';
4
+
5
+ import { setLocaleAsync } from '@src/composables/useTranslate';
6
+ import PageSettingMenu from './PageSettingMenu.vue';
7
+
8
+ // Menu order is a product requirement, so it is asserted rather than just
9
+ // membership.
10
+ const ITEM_TAGS = [
11
+ 'add-subpage',
12
+ 'duplicate-page',
13
+ 'toggle-page-visibility',
14
+ 'delete-page',
15
+ ];
16
+
17
+ const mountMenu = (overrides = {}) =>
18
+ mount(PageSettingMenu, {
19
+ props: {
20
+ index: 1,
21
+ pageId: 'page-1',
22
+ isHidden: false,
23
+ isDesignCom: false,
24
+ isRootPage: true,
25
+ hasSubPages: false,
26
+ totalPages: 3,
27
+ visiblePageCount: 3,
28
+ onDeletePageSelected: vi.fn(),
29
+ onChangePageVisibility: vi.fn(),
30
+ onAddSubPageSelected: vi.fn(),
31
+ onDuplicatePageSelected: vi.fn(),
32
+ ...overrides,
33
+ },
34
+ });
35
+
36
+ const openMenu = async (wrapper: ReturnType<typeof mountMenu>) => {
37
+ await wrapper.find('[data-test-tag="page-menu-trigger"]').trigger('click');
38
+
39
+ return wrapper;
40
+ };
41
+
42
+ const renderedTags = (wrapper: ReturnType<typeof mountMenu>) =>
43
+ wrapper
44
+ .findAll('li')
45
+ .map((li) => li.attributes('data-test-tag'))
46
+ .filter((tag): tag is string => Boolean(tag));
47
+
48
+ describe('PageSettingMenu', () => {
49
+ beforeAll(async () => {
50
+ setActivePinia(createPinia());
51
+
52
+ await setLocaleAsync('en-US');
53
+ });
54
+
55
+ test('a root page shows every action in order', async () => {
56
+ const wrapper = await openMenu(mountMenu());
57
+
58
+ expect(renderedTags(wrapper)).toEqual(ITEM_TAGS);
59
+ });
60
+
61
+ test('a sub-page shows duplicate but not add-subpage', async () => {
62
+ const wrapper = await openMenu(mountMenu({ isRootPage: false }));
63
+
64
+ expect(renderedTags(wrapper)).toEqual([
65
+ 'duplicate-page',
66
+ 'toggle-page-visibility',
67
+ 'delete-page',
68
+ ]);
69
+ });
70
+
71
+ test('the home page can be duplicated but not hidden or deleted', async () => {
72
+ const wrapper = await openMenu(mountMenu({ index: 0 }));
73
+
74
+ expect(renderedTags(wrapper)).toEqual(['add-subpage', 'duplicate-page']);
75
+ });
76
+
77
+ test('a parent page can be duplicated even though it cannot be deleted', async () => {
78
+ const wrapper = await openMenu(mountMenu({ hasSubPages: true }));
79
+
80
+ expect(renderedTags(wrapper)).toContain('duplicate-page');
81
+ expect(renderedTags(wrapper)).not.toContain('delete-page');
82
+ });
83
+
84
+ test('clicking duplicate calls back with the page id and closes the menu', async () => {
85
+ const onDuplicatePageSelected = vi.fn();
86
+
87
+ const wrapper = await openMenu(mountMenu({ onDuplicatePageSelected }));
88
+
89
+ await wrapper.find('[data-test-tag="duplicate-page"]').trigger('click');
90
+
91
+ expect(onDuplicatePageSelected).toHaveBeenCalledTimes(1);
92
+ expect(onDuplicatePageSelected).toHaveBeenCalledWith('page-1');
93
+ expect(wrapper.find('[data-test-tag="duplicate-page"]').exists()).toBe(
94
+ false,
95
+ );
96
+ });
97
+
98
+ test('the duplicate row is labelled from the translation bundle', async () => {
99
+ const wrapper = await openMenu(mountMenu());
100
+
101
+ expect(wrapper.find('[data-test-tag="duplicate-page"]').text()).toEqual(
102
+ 'Duplicate page',
103
+ );
104
+ });
105
+ });
@@ -29,6 +29,16 @@
29
29
  pageSettingsTr('addSubPage')
30
30
  }}</span>
31
31
  </li>
32
+ <li
33
+ data-test-tag="duplicate-page"
34
+ class="tw-cursor-pointer tw-flex tw-gap-2 tw-items-center tw-px-5 hover:tw-bg-grayscale-400 tw-mt-3"
35
+ @click="onDuplicateClick"
36
+ >
37
+ <Icon name="duplicate" :class="{ 'tw-pt-1': !isDesignCom }" />
38
+ <span class="tw-whitespace-nowrap">{{
39
+ pageSettingsTr('duplicatePage')
40
+ }}</span>
41
+ </li>
32
42
  <li
33
43
  v-if="props.index !== 0"
34
44
  data-test-tag="toggle-page-visibility"
@@ -74,6 +84,7 @@ interface Props {
74
84
  onDeletePageSelected: (pageId: string) => void;
75
85
  onChangePageVisibility: (pageId: string) => void;
76
86
  onAddSubPageSelected: (pageId: string) => void;
87
+ onDuplicatePageSelected: (pageId: string) => void;
77
88
  }
78
89
 
79
90
  const props = defineProps<Props>();
@@ -100,6 +111,11 @@ const onAddSubPageClick = () => {
100
111
  closeMenu();
101
112
  };
102
113
 
114
+ const onDuplicateClick = () => {
115
+ props.onDuplicatePageSelected(props.pageId);
116
+ closeMenu();
117
+ };
118
+
103
119
  const onChangePageVisibility = () => {
104
120
  props.onChangePageVisibility(props.pageId);
105
121
  closeMenu();
@@ -0,0 +1,108 @@
1
+ import { describe, test, expect, beforeAll, vi } from 'vitest';
2
+ import { createPinia, setActivePinia } from 'pinia';
3
+ import { shallowMount } from '@vue/test-utils';
4
+
5
+ import { setLocaleAsync } from '@src/composables/useTranslate';
6
+ import {
7
+ INITIALISE_PAYLOAD,
8
+ USER_FEATURES,
9
+ } from '@brand-page-maker/components/brand-page-maker/__fixtures__';
10
+ import {
11
+ UNLIMITED_MAX_NUM_OF_PAGES,
12
+ useBrandPageMakerStore,
13
+ useBrandPageMakerUserStore,
14
+ } from '@brand-page-maker/stores';
15
+ import { FabricRenderClient } from '../../../../clients/fabric-render-client';
16
+ import { useUiPageSettingsStore } from '../../store';
17
+ import { usePageNavigationStore } from '../../../page-navigation/store/page-navigation-store';
18
+ import PageSettings from './PageSettings.vue';
19
+
20
+ vi.mock('../../../../clients/brand-page-client', () => ({
21
+ default: {
22
+ getWebsiteContextualUpgradeTestBucketAsync: vi
23
+ .fn()
24
+ .mockResolvedValue(undefined),
25
+ },
26
+ }));
27
+
28
+ // The analytics contract lives in the mixin, not the store: the store returns
29
+ // the copy, the mixin decides what gets tracked. Nothing else covers it.
30
+ describe('PageSettings - duplicate page handler', () => {
31
+ const setupAsync = async () => {
32
+ const makerStore = useBrandPageMakerStore();
33
+ const userStore = useBrandPageMakerUserStore();
34
+
35
+ userStore.setUserState({
36
+ features: { ...USER_FEATURES, maxNumOfPages: UNLIMITED_MAX_NUM_OF_PAGES },
37
+ });
38
+
39
+ await makerStore.initialise(INITIALISE_PAYLOAD);
40
+
41
+ const wrapper = shallowMount(PageSettings);
42
+
43
+ return {
44
+ wrapper,
45
+ pageSettingsStore: useUiPageSettingsStore(),
46
+ pageNavigationStore: usePageNavigationStore(),
47
+ spy: vi.spyOn(
48
+ wrapper.vm.eventTracker,
49
+ 'sendWebsiteManageDuplicatePageEvent',
50
+ ),
51
+ };
52
+ };
53
+
54
+ beforeAll(async () => {
55
+ FabricRenderClient.setInstance({ baseUrl: 'http://localhost:3000' });
56
+ setActivePinia(createPinia());
57
+
58
+ await setLocaleAsync('en-US');
59
+ });
60
+
61
+ test('tracks the source page title, not the copy', async () => {
62
+ const { wrapper, pageSettingsStore, spy } = await setupAsync();
63
+
64
+ const source = pageSettingsStore.addNewPage({ title: 'About' });
65
+
66
+ await wrapper.vm.onDuplicatePageSelected(source.id);
67
+
68
+ expect(spy).toHaveBeenCalledWith('About', false);
69
+ });
70
+
71
+ test('flags a duplicated sub-page', async () => {
72
+ const { wrapper, pageSettingsStore, spy } = await setupAsync();
73
+
74
+ const parent = pageSettingsStore.addNewPage({ title: 'Portfolio' });
75
+ const child = pageSettingsStore.addNewPage({
76
+ title: 'Portfolio 1',
77
+ newPageParentId: parent.id,
78
+ });
79
+
80
+ await wrapper.vm.onDuplicatePageSelected(child.id);
81
+
82
+ expect(spy).toHaveBeenCalledWith('Portfolio 1', true);
83
+ });
84
+
85
+ test('tracks nothing when the page limit blocked the duplicate', async () => {
86
+ const { wrapper, pageSettingsStore, spy } = await setupAsync();
87
+ const userStore = useBrandPageMakerUserStore();
88
+
89
+ userStore.setUserState({
90
+ features: {
91
+ ...USER_FEATURES,
92
+ maxNumOfPages: pageSettingsStore.pages.length,
93
+ },
94
+ });
95
+
96
+ await wrapper.vm.onDuplicatePageSelected(pageSettingsStore.pages[0].id);
97
+
98
+ expect(spy).not.toHaveBeenCalled();
99
+ });
100
+
101
+ test('tracks nothing for an unknown page id', async () => {
102
+ const { wrapper, spy } = await setupAsync();
103
+
104
+ await wrapper.vm.onDuplicatePageSelected('does-not-exist');
105
+
106
+ expect(spy).not.toHaveBeenCalled();
107
+ });
108
+ });
@@ -124,55 +124,6 @@ export default defineComponent({
124
124
 
125
125
  const isDraggingPage = ref(false);
126
126
 
127
- const syncPagesWithOrders = (): void => {
128
- const pageIdOrder: string[] = [];
129
-
130
- const collectPageIds = (orders: NavOrder[]): void => {
131
- for (const order of orders) {
132
- const route = pageNavigationStore.routes.find(
133
- (r) => r.id === order.routeId,
134
- );
135
-
136
- if (route?.pageId) {
137
- pageIdOrder.push(route.pageId);
138
- }
139
-
140
- if (order.subRoutes && order.subRoutes.length > 0) {
141
- collectPageIds(order.subRoutes);
142
- }
143
- }
144
- };
145
-
146
- collectPageIds(pageNavigationStore.orders);
147
-
148
- if (!pageIdOrder.length) {
149
- return;
150
- }
151
-
152
- const pageMap = new Map<string, ContentPage>();
153
-
154
- for (const page of pageSettingsStore.pages) {
155
- pageMap.set(page.id, page);
156
- }
157
-
158
- const orderedPages: ContentPage[] = [];
159
-
160
- for (const pageId of pageIdOrder) {
161
- const page = pageMap.get(pageId);
162
-
163
- if (page) {
164
- orderedPages.push(page);
165
- pageMap.delete(pageId);
166
- }
167
- }
168
-
169
- for (const remainingPage of pageMap.values()) {
170
- orderedPages.push(remainingPage);
171
- }
172
-
173
- pageSettingsStore.setPages({ pages: orderedPages });
174
- };
175
-
176
127
  const onDraggingStart = (): void => {
177
128
  isDraggingPage.value = true;
178
129
  };
@@ -200,7 +151,7 @@ export default defineComponent({
200
151
  }
201
152
  }
202
153
 
203
- syncPagesWithOrders();
154
+ pageSettingsStore.syncPagesWithOrders();
204
155
 
205
156
  // Recalc the moved route's slug to ensure uniqueness at its new depth
206
157
  if (routeId) {
@@ -371,6 +322,27 @@ export default defineComponent({
371
322
  isNewPageModalVisible: true,
372
323
  });
373
324
  },
325
+ async onDuplicatePageSelected(pageId: string) {
326
+ const sourcePage = this.pageSettingsStore.pages.find(
327
+ (p) => p.id === pageId,
328
+ );
329
+ const isSubPage = Boolean(
330
+ this.pageNavigationStore.getParentRouteIdForPage(pageId),
331
+ );
332
+
333
+ const newPage = await this.pageSettingsStore.duplicatePage({ pageId });
334
+
335
+ // Nothing was created - paywalled, or the page is gone. The upsell is
336
+ // already tracked through the bridge's cta_location.
337
+ if (!newPage || !sourcePage) {
338
+ return;
339
+ }
340
+
341
+ this.eventTracker.sendWebsiteManageDuplicatePageEvent(
342
+ sourcePage.title,
343
+ isSubPage,
344
+ );
345
+ },
374
346
  onChangePageVisibility(pageId: string) {
375
347
  const page = this.pageSettingsStore.pages.find((p) => p.id === pageId);
376
348
 
@@ -38,6 +38,7 @@
38
38
  :on-delete-page-selected="onDeletePageSelected"
39
39
  :on-change-page-visibility="onChangePageVisibility"
40
40
  :on-add-sub-page-selected="onAddSubPageSelected"
41
+ :on-duplicate-page-selected="onDuplicatePageSelected"
41
42
  @drag-start="onDraggingStart"
42
43
  @drag-end="onDraggingEnd"
43
44
  />
@@ -19,6 +19,8 @@
19
19
  "showPage": "Seite anzeigen",
20
20
  "hidePage": "Seite ausblenden",
21
21
  "delete": "Seite löschen",
22
- "addSubPage": "Unterseite hinzufügen"
22
+ "addSubPage": "Unterseite hinzufügen",
23
+ "duplicatePage": "Seite duplizieren",
24
+ "duplicatePageTitle": "{{title}} Kopieren"
23
25
  }
24
26
  }
@@ -19,6 +19,8 @@
19
19
  "showPage": "Mostrar página",
20
20
  "hidePage": "Ocultar página",
21
21
  "delete": "Eliminar página",
22
- "addSubPage": "Añadir subpágina"
22
+ "addSubPage": "Añadir subpágina",
23
+ "duplicatePage": "Duplicar página",
24
+ "duplicatePageTitle": "{{title}} copia"
23
25
  }
24
26
  }
@@ -19,6 +19,8 @@
19
19
  "showPage": "Afficher la page",
20
20
  "hidePage": "Masquez la page",
21
21
  "delete": "Supprimer la page",
22
- "addSubPage": "Ajouter une sous-page"
22
+ "addSubPage": "Ajouter une sous-page",
23
+ "duplicatePage": "Dupliquer la page",
24
+ "duplicatePageTitle": "{{title}} Copie"
23
25
  }
24
26
  }
@@ -19,6 +19,8 @@
19
19
  "showPage": "Afficher la page",
20
20
  "hidePage": "Masquez la page",
21
21
  "delete": "Supprimer la page",
22
- "addSubPage": "Ajouter une sous-page"
22
+ "addSubPage": "Ajouter une sous-page",
23
+ "duplicatePage": "Dupliquer la page",
24
+ "duplicatePageTitle": "Copie de {{title}}"
23
25
  }
24
26
  }
@@ -19,6 +19,8 @@
19
19
  "showPage": "Show page",
20
20
  "hidePage": "Hide page",
21
21
  "delete": "Delete page",
22
- "addSubPage": "Add subpage"
22
+ "addSubPage": "Add subpage",
23
+ "duplicatePage": "Duplicate page",
24
+ "duplicatePageTitle": "{{title}} Copy"
23
25
  }
24
26
  }
@@ -19,6 +19,8 @@
19
19
  "showPage": "Mostrar página",
20
20
  "hidePage": "Ocultar página",
21
21
  "delete": "Eliminar página",
22
- "addSubPage": "Adicionar subpágina"
22
+ "addSubPage": "Adicionar subpágina",
23
+ "duplicatePage": "Duplicar página",
24
+ "duplicatePageTitle": "{{title}} Copiar"
23
25
  }
24
26
  }
@@ -19,6 +19,8 @@
19
19
  "showPage": "Mostrar página",
20
20
  "hidePage": "Ocultar página",
21
21
  "delete": "Eliminar página",
22
- "addSubPage": "Adicionar subpágina"
22
+ "addSubPage": "Adicionar subpágina",
23
+ "duplicatePage": "Duplicar página",
24
+ "duplicatePageTitle": "{{title}} Cópia"
23
25
  }
24
26
  }