@useinsider/guido 3.19.0 → 3.20.0-beta.0849ec7

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 (148) hide show
  1. package/README.md +22 -3
  2. package/dist/@types/config/schemas.js +76 -53
  3. package/dist/components/Guido.vue.js +4 -4
  4. package/dist/components/Guido.vue2.js +102 -86
  5. package/dist/components/organisms/AutoSaveController.vue2.js +9 -8
  6. package/dist/components/organisms/base/AuthErrorModal.vue.js +18 -0
  7. package/dist/components/organisms/base/AuthErrorModal.vue2.js +42 -0
  8. package/dist/components/organisms/base/Toaster.vue2.js +13 -12
  9. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue.js +13 -11
  10. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue2.js +78 -57
  11. package/dist/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue.js +18 -0
  12. package/dist/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue2.js +25 -0
  13. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue2.js +28 -42
  14. package/dist/components/organisms/header/EditorActions.vue.js +5 -5
  15. package/dist/components/organisms/header/EditorActions.vue2.js +90 -57
  16. package/dist/components/organisms/header/LeftSlot.vue.js +5 -5
  17. package/dist/components/organisms/header/LeftSlot.vue2.js +16 -12
  18. package/dist/components/organisms/onboarding/OnboardingWrapper.vue2.js +28 -24
  19. package/dist/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue.js +21 -0
  20. package/dist/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue2.js +46 -0
  21. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.js +21 -0
  22. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue2.js +47 -0
  23. package/dist/components/organisms/validation/ValidationErrorDrawer.vue.js +20 -0
  24. package/dist/components/organisms/validation/ValidationErrorDrawer.vue2.js +49 -0
  25. package/dist/components/wrappers/WpDrawer.vue.js +9 -7
  26. package/dist/components/wrappers/WpDrawer.vue2.js +1 -1
  27. package/dist/components/wrappers/WpModal.vue.js +1 -1
  28. package/dist/components/wrappers/WpModal.vue2.js +4 -3
  29. package/dist/composables/useActionsApi.js +52 -47
  30. package/dist/composables/useAuthError.js +52 -0
  31. package/dist/composables/useAutoSave.js +11 -11
  32. package/dist/composables/useCodeEditorApi.js +23 -9
  33. package/dist/composables/useCortexBlueprintBridge.js +122 -54
  34. package/dist/composables/useCustomInterfaceAppearance.js +65 -51
  35. package/dist/composables/useEmailTemplateApplier.js +111 -50
  36. package/dist/composables/useHtmlCompiler.js +37 -26
  37. package/dist/composables/useHtmlValidator.js +89 -82
  38. package/dist/composables/useHttp.js +64 -54
  39. package/dist/composables/useLegacyStrategyConversion.js +65 -0
  40. package/dist/composables/useRecommendation.js +40 -35
  41. package/dist/composables/useSave.js +90 -45
  42. package/dist/composables/useSaveValidation.js +282 -0
  43. package/dist/composables/useSaveValidationRollout.js +9 -0
  44. package/dist/composables/useStripo.js +43 -39
  45. package/dist/composables/useToaster.js +18 -22
  46. package/dist/composables/useTranslations.js +3 -2
  47. package/dist/composables/useVersionHistoryApi.js +33 -24
  48. package/dist/composables/validators/useCouponBlockValidator.js +24 -21
  49. package/dist/composables/validators/useLiquidValidator.js +15 -15
  50. package/dist/composables/validators/useUnsubscribeBlockValidator.js +34 -27
  51. package/dist/config/migrator/index.js +14 -13
  52. package/dist/config/migrator/itemsBlockMigrator.js +236 -248
  53. package/dist/config/migrator/recommendationMigrator.js +28 -21
  54. package/dist/config/migrator/recommendationPartnerMigrator.js +56 -0
  55. package/dist/enums/extensions/recommendationBlock.js +51 -34
  56. package/dist/enums/save-validation.js +4 -0
  57. package/dist/extensions/Blocks/Items/constants/cardLayout.js +20 -0
  58. package/dist/extensions/Blocks/Items/controls/name/trimming.js +18 -17
  59. package/dist/extensions/Blocks/Items/controls/settingsControl.js +114 -105
  60. package/dist/extensions/Blocks/Items/enums/productEnums.js +14 -9
  61. package/dist/extensions/Blocks/Items/layouts/horizontal.html.js +11 -9
  62. package/dist/extensions/Blocks/Items/template.js +219 -204
  63. package/dist/extensions/Blocks/Recommendation/block.js +65 -45
  64. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +21 -20
  65. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +103 -90
  66. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +150 -52
  67. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +74 -74
  68. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +274 -92
  69. package/dist/extensions/Blocks/Recommendation/utils/legacyStrategyMigration.js +42 -0
  70. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +26 -16
  71. package/dist/extensions/Blocks/Recommendation/utils/partnerCustomizations.js +3 -1
  72. package/dist/extensions/Blocks/Recommendation/utils/partnerScope.js +25 -0
  73. package/dist/extensions/Blocks/Recommendation/utils/productIds.js +5 -3
  74. package/dist/extensions/Blocks/Recommendation/utils/strategyCommit.js +24 -0
  75. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +21 -12
  76. package/dist/guido.css +1 -1
  77. package/dist/services/recommendationApi.js +46 -37
  78. package/dist/src/@types/config/schemas.d.ts +36 -0
  79. package/dist/src/@types/save-validation.d.ts +19 -0
  80. package/dist/src/components/Guido.vue.d.ts +17 -1
  81. package/dist/src/components/organisms/base/AuthErrorModal.vue.d.ts +2 -0
  82. package/dist/src/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue.d.ts +2 -0
  83. package/dist/src/components/organisms/header/EditorActions.vue.d.ts +17 -1
  84. package/dist/src/components/organisms/header/HeaderWrapper.vue.d.ts +17 -1
  85. package/dist/src/components/organisms/header/RightSlot.vue.d.ts +17 -1
  86. package/dist/src/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue.d.ts +2 -0
  87. package/dist/src/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.d.ts +2 -0
  88. package/dist/src/components/organisms/validation/ValidationErrorDrawer.vue.d.ts +2 -0
  89. package/dist/src/components/wrappers/WpDrawer.vue.d.ts +4 -3
  90. package/dist/src/components/wrappers/WpModal.vue.d.ts +5 -1
  91. package/dist/src/composables/useAuthError.d.ts +12 -0
  92. package/dist/src/composables/useConfig.d.ts +8 -0
  93. package/dist/src/composables/useCortexBlueprintBridge.d.ts +2 -3
  94. package/dist/src/composables/useEmailTemplateApplier.d.ts +18 -5
  95. package/dist/src/composables/useGuidoActions.d.ts +2 -0
  96. package/dist/src/composables/useHtmlValidator.d.ts +21 -0
  97. package/dist/src/composables/useHttp.d.ts +2 -0
  98. package/dist/src/composables/useLegacyStrategyConversion.d.ts +3 -0
  99. package/dist/src/composables/useSave.d.ts +6 -1
  100. package/dist/src/composables/useSaveValidation.d.ts +6 -0
  101. package/dist/src/composables/useSaveValidationRollout.d.ts +3 -0
  102. package/dist/src/composables/validators/useCouponBlockValidator.d.ts +3 -0
  103. package/dist/src/composables/validators/useUnsubscribeBlockValidator.d.ts +10 -0
  104. package/dist/src/config/migrator/index.d.ts +2 -1
  105. package/dist/src/config/migrator/recommendationPartnerMigrator.d.ts +19 -0
  106. package/dist/src/enums/extensions/recommendationBlock.d.ts +7 -1
  107. package/dist/src/enums/save-validation.d.ts +27 -0
  108. package/dist/src/extensions/Blocks/Items/constants/cardLayout.d.ts +44 -0
  109. package/dist/src/extensions/Blocks/Items/enums/productEnums.d.ts +3 -0
  110. package/dist/src/extensions/Blocks/Items/template.d.ts +19 -0
  111. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +10 -0
  112. package/dist/src/extensions/Blocks/Recommendation/constants/defaultConfig.d.ts +1 -0
  113. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +1 -1
  114. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +2 -0
  115. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +10 -0
  116. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +7 -2
  117. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +162 -1
  118. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +6 -0
  119. package/dist/src/extensions/Blocks/Recommendation/utils/legacyStrategyMigration.d.ts +32 -0
  120. package/dist/src/extensions/Blocks/Recommendation/utils/partnerScope.d.ts +33 -0
  121. package/dist/src/extensions/Blocks/Recommendation/utils/productIds.d.ts +2 -0
  122. package/dist/src/extensions/Blocks/Recommendation/utils/strategyCommit.d.ts +14 -0
  123. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +4 -0
  124. package/dist/src/mock/cortexChatStore.d.ts +12 -0
  125. package/dist/src/services/recommendationApi.d.ts +2 -1
  126. package/dist/src/stores/config.d.ts +374 -0
  127. package/dist/src/stores/guido-email-editor.d.ts +20 -10
  128. package/dist/src/stores/onboarding.d.ts +812 -10
  129. package/dist/src/stores/recommendation.d.ts +5 -0
  130. package/dist/src/stores/save-validation.d.ts +22 -0
  131. package/dist/src/utils/dateUtil.d.ts +10 -0
  132. package/dist/src/utils/genericUtil.d.ts +3 -0
  133. package/dist/src/utils/stripoEditorApi.d.ts +4 -0
  134. package/dist/src/utils/templatePreparation.d.ts +30 -0
  135. package/dist/static/assets/icons/filled-arrow-rising.svg.js +5 -0
  136. package/dist/static/assets/icons/line-info.svg.js +5 -0
  137. package/dist/static/styles/components/recommendation-strategy.css.js +113 -9
  138. package/dist/static/styles/components/skeleton.css.js +23 -0
  139. package/dist/static/styles/components/version-history.css.js +2 -6
  140. package/dist/stores/config.js +55 -24
  141. package/dist/stores/guido-email-editor.js +3 -1
  142. package/dist/stores/onboarding.js +50 -36
  143. package/dist/stores/save-validation.js +30 -0
  144. package/dist/stores/unsubscribe.js +3 -3
  145. package/dist/utils/pairProductVariables.js +71 -71
  146. package/dist/utils/stripoEditorApi.js +11 -0
  147. package/dist/utils/templatePreparation.js +54 -51
  148. package/package.json +2 -1
@@ -1,22 +1,27 @@
1
- import { getRecommendationFeedSourceMaps as b, getOperatorOptions as G, PriceAttributes as v } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as D } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as F } from "../../../../stores/config.js";
4
- import { defineStore as P } from "pinia";
1
+ import { useToaster as _ } from "../../../../composables/useToaster.js";
2
+ import { useTranslations as L } from "../../../../composables/useTranslations.js";
3
+ import { getRecommendationFeedSourceMaps as F, strategyEditPath as U, strategyCreatePath as N, getOperatorOptions as M, PriceAttributes as E } from "../../../../enums/extensions/recommendationBlock.js";
4
+ import { ToasterTypeOptions as B } from "../../../../enums/toaster.js";
5
+ import { useRecommendationApi as z } from "../../../../services/recommendationApi.js";
6
+ import { useConfigStore as R } from "../../../../stores/config.js";
7
+ import { defineStore as A } from "pinia";
5
8
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as _, DEFAULT_CARDS_IN_ROW as U } from "../constants/layout.js";
7
- import { DEFAULT_NODE_CONFIG as N, EXCLUDED_ALGORITHM_IDS as L } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as R } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as k } from "../utils/filterUtil.js";
10
- import { getPartnerRecommendationParams as x } from "../utils/partnerCustomizations.js";
11
- import { sanitizeProductIds as A } from "../utils/productIds.js";
12
- import { isFilterValid as B } from "../validation/filterSchema.js";
13
- import { isConfigValid as M } from "../validation/requiredFields.js";
14
- const u = D();
15
- let f = null, g = null, h = null, S = null;
16
- const p = /* @__PURE__ */ new Map();
17
- function w() {
9
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as O, DEFAULT_CARDS_IN_ROW as x } from "../constants/layout.js";
10
+ import { CURRENT_CONFIG_VERSION as C, DEFAULT_NODE_CONFIG as T, EXCLUDED_ALGORITHM_IDS as V } from "../constants/defaultConfig.js";
11
+ import { getDefaultProducts as w } from "../templates/utils.js";
12
+ import { generateCompleteFilterQuery as P } from "../utils/filterUtil.js";
13
+ import { isLegacyStrategyConfig as j } from "../utils/legacyStrategyMigration.js";
14
+ import { getPartnerRecommendationParams as $ } from "../utils/partnerCustomizations.js";
15
+ import { sanitizeProductIds as q } from "../utils/productIds.js";
16
+ import { isFilterValid as K } from "../validation/filterSchema.js";
17
+ import { getInvalidFields as H, isConfigValid as J } from "../validation/requiredFields.js";
18
+ const m = z();
19
+ let S = null, p = null, y = null, I = "", k = null, b = 0;
20
+ const g = /* @__PURE__ */ new Map();
21
+ let l = null;
22
+ function D() {
18
23
  return {
19
- cardsInRow: U,
24
+ cardsInRow: x,
20
25
  currencySettings: {
21
26
  name: "USD",
22
27
  value: "USD",
@@ -30,14 +35,19 @@ function w() {
30
35
  productIds: [],
31
36
  id: 1,
32
37
  language: "en_US",
33
- mobileCardsInRow: _,
38
+ mobileCardsInRow: O,
34
39
  mobileLayoutEnabled: !1,
35
40
  orientation: "grid",
36
41
  recommendedProducts: [],
37
42
  sendProductRequestFlag: !1,
38
43
  shuffleProducts: !1,
39
- strategy: "mostPopular",
44
+ // With RRS on, a block that has not been synced yet must not claim an algorithm:
45
+ // that default is what reached buildCampaignUrl and baked a most-popular feed URL
46
+ // for a block the user never configured (SD-149916). Legacy blocks still get their real
47
+ // algorithm from the node-config sync or the save-time seed.
48
+ strategy: R().isFeatureEnabled("reusableRecommendationStrategy") ? "" : "mostPopular",
40
49
  strategyId: "",
50
+ nodeConfigVersion: C,
41
51
  textTrimming: !0,
42
52
  unresponsive: !1,
43
53
  size: "6",
@@ -46,21 +56,23 @@ function w() {
46
56
  priceHideIfSameAsDiscounted: !1
47
57
  };
48
58
  }
49
- function C() {
59
+ function G() {
50
60
  return {
51
- recommendationConfigs: w(),
61
+ recommendationConfigs: D(),
52
62
  recommendationProducts: [],
53
63
  filterStatus: !1,
54
64
  filterSelectionDrawerStatus: !1,
55
65
  assignStrategyDrawerStatus: !1,
56
66
  removeStrategyModalStatus: !1,
67
+ legacyStrategyUpdatedModalStatus: !1,
57
68
  filterGroup: 1,
58
69
  isInitialized: !1,
59
70
  filterSnapshot: null,
60
- syncedConfigFingerprint: null
71
+ syncedConfigFingerprint: null,
72
+ legacyConversionLoading: !1
61
73
  };
62
74
  }
63
- const z = () => ({
75
+ const Q = () => ({
64
76
  recommendationCampaignUrls: {},
65
77
  activePredictiveAlgorithms: [],
66
78
  languages: {},
@@ -69,11 +81,14 @@ const z = () => ({
69
81
  strategies: [],
70
82
  strategiesLoading: !1,
71
83
  strategyUrlTemplates: {},
84
+ strategiesFetched: !1,
85
+ strategiesSettled: !1,
72
86
  blockStates: {},
73
87
  currentRecommendationId: null,
88
+ strategyPanelOpen: !1,
74
89
  configVersion: 0
75
- }), Y = P("guidoRecommendationExtension", {
76
- state: () => z(),
90
+ }), ft = A("guidoRecommendationExtension", {
91
+ state: () => Q(),
77
92
  getters: {
78
93
  // ====================================================================
79
94
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -83,7 +98,7 @@ const z = () => ({
83
98
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
84
99
  */
85
100
  recommendationConfigs(t) {
86
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : w();
101
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : D();
87
102
  },
88
103
  /**
89
104
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -103,6 +118,15 @@ const z = () => ({
103
118
  removeStrategyModalStatus(t) {
104
119
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].removeStrategyModalStatus : !1;
105
120
  },
121
+ isLegacyStrategyBlock() {
122
+ return R().isFeatureEnabled("reusableRecommendationStrategy") && j(this.recommendationConfigs);
123
+ },
124
+ legacyConversionLoading(t) {
125
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyConversionLoading ?? !1 : !1;
126
+ },
127
+ legacyStrategyUpdatedModalStatus(t) {
128
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyStrategyUpdatedModalStatus : !1;
129
+ },
106
130
  assignedStrategy(t) {
107
131
  const { strategyId: e } = this.recommendationConfigs;
108
132
  return e ? t.strategies.find((r) => String(r.strategy_id) === e) ?? null : null;
@@ -138,8 +162,8 @@ const z = () => ({
138
162
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
139
163
  },
140
164
  getActivePredictiveAlgorithms: (t) => {
141
- const e = b(), r = [];
142
- return t.activePredictiveAlgorithms.filter((n) => !L.includes(n)).forEach((n) => {
165
+ const e = F(), r = [];
166
+ return t.activePredictiveAlgorithms.filter((n) => !V.includes(n)).forEach((n) => {
143
167
  r.push(...e.filter((s) => s.id === n));
144
168
  }), r.map((n) => ({
145
169
  text: n.name,
@@ -157,7 +181,7 @@ const z = () => ({
157
181
  getFilterList() {
158
182
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
159
183
  let e;
160
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : v.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
184
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : E.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
161
185
  text: t.displayName,
162
186
  value: e,
163
187
  type: t.attributeType
@@ -179,7 +203,7 @@ const z = () => ({
179
203
  setCurrentBlock(t) {
180
204
  this.blockStates[t] || (this.blockStates = {
181
205
  ...this.blockStates,
182
- [t]: C()
206
+ [t]: G()
183
207
  }), this.currentRecommendationId = t;
184
208
  },
185
209
  /**
@@ -201,12 +225,16 @@ const z = () => ({
201
225
  /**
202
226
  * Deep-clones a block's state and mirrors its campaign URL under a new id.
203
227
  * Invoked from `onCreated` when a duplicate is detected (SD-142352).
228
+ *
229
+ * The clone keeps the source's data but drops its "already fetched / already
230
+ * synced" claims for strategy blocks, so it fetches again like a fresh drop
231
+ * (SD-149916).
204
232
  */
205
233
  cloneBlockState(t, e) {
206
234
  const r = this.blockStates[t];
207
235
  if (r) {
208
236
  const o = structuredClone(r);
209
- o.recommendationConfigs.id = e, this.blockStates = {
237
+ o.recommendationConfigs.id = e, o.recommendationConfigs.strategyId && R().isFeatureEnabled("reusableRecommendationStrategy") && (o.isInitialized = !1, o.syncedConfigFingerprint = null), this.blockStates = {
210
238
  ...this.blockStates,
211
239
  [e]: o
212
240
  };
@@ -239,7 +267,7 @@ const z = () => ({
239
267
  * `isInitialized`) alone.
240
268
  */
241
269
  seedBlockUrlConfig(t, e) {
242
- const r = (o, c) => o === "." || o === "," || o === " " ? o : c, n = {
270
+ const r = (o, a) => o === "." || o === "," || o === " " ? o : a, n = {
243
271
  name: e.currencyCode,
244
272
  value: e.currencyCode,
245
273
  symbol: e.currencySymbol,
@@ -247,7 +275,7 @@ const z = () => ({
247
275
  decimalCount: String(e.currencyDecimalCount),
248
276
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
249
277
  thousandSeparator: r(e.currencyThousandSeparator, ".")
250
- }, s = !this.blockStates[t], i = s ? C() : this.blockStates[t];
278
+ }, s = !this.blockStates[t], i = s ? G() : this.blockStates[t];
251
279
  i.recommendationConfigs = {
252
280
  ...i.recommendationConfigs,
253
281
  strategy: e.strategy,
@@ -286,7 +314,7 @@ const z = () => ({
286
314
  * Creates a filter with the first available attribute and operator pre-selected.
287
315
  */
288
316
  createDefaultFilter(t, e) {
289
- const [r] = this.getFilterList, [n] = G(r == null ? void 0 : r.type);
317
+ const [r] = this.getFilterList, [n] = M(r == null ? void 0 : r.type);
290
318
  return {
291
319
  type: "standardFilter",
292
320
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -339,6 +367,12 @@ const z = () => ({
339
367
  closeAssignStrategyDrawer() {
340
368
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].assignStrategyDrawerStatus = !1);
341
369
  },
370
+ openLegacyStrategyUpdatedModal() {
371
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].legacyStrategyUpdatedModalStatus = !0);
372
+ },
373
+ closeLegacyStrategyUpdatedModal() {
374
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].legacyStrategyUpdatedModalStatus = !1);
375
+ },
342
376
  openRemoveStrategyModal() {
343
377
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !0);
344
378
  },
@@ -346,29 +380,111 @@ const z = () => ({
346
380
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !1);
347
381
  },
348
382
  async fetchStrategies() {
349
- if (S) {
350
- await S;
383
+ for (; k; )
384
+ try {
385
+ await k;
386
+ } catch {
387
+ }
388
+ if (this.strategiesFetched)
351
389
  return;
352
- }
353
- S = (async () => {
390
+ const t = b, e = (async () => {
354
391
  this.strategiesLoading = !0;
355
392
  try {
356
- this.strategies = await u.fetchRecommendationStrategies();
393
+ const r = await m.fetchRecommendationStrategies();
394
+ if (t !== b)
395
+ return;
396
+ this.strategies = r, this.strategiesFetched = !0;
357
397
  } finally {
358
- this.strategiesLoading = !1;
398
+ this.strategiesLoading = !1, t === b && (this.strategiesSettled = !0);
359
399
  }
360
400
  })();
401
+ k = e;
361
402
  try {
362
- await S;
403
+ await e;
363
404
  } finally {
364
- S = null;
405
+ k = null;
365
406
  }
366
407
  },
408
+ /**
409
+ * Clears what a block inherited from the id it arrived holding, so the panel fetches for it.
410
+ * A re-dropped saved module can keep the source's id — and its "already fetched" entry —
411
+ * while the strategy's product count has moved on (SD-149916).
412
+ */
413
+ resetArrivedBlock(t) {
414
+ const e = this.blockStates[t];
415
+ e != null && e.recommendationConfigs.strategyId && (e.isInitialized = !1, e.syncedConfigFingerprint = null, this.invalidateStrategies(), this.fetchStrategies().catch(() => {
416
+ }));
417
+ },
418
+ invalidateStrategies() {
419
+ this.strategiesFetched = !1, this.strategiesSettled = !1, b += 1;
420
+ },
421
+ /**
422
+ * Opens a Smart Recommender strategy page in a new tab and refreshes the list every time
423
+ * the user comes back. `visibilitychange` only: a `focus` listener would also fire when
424
+ * focus merely returns from the Stripo canvas iframe to the panel, refetching and
425
+ * regenerating the canvas while the user never left the editor (SD-149916).
426
+ *
427
+ * Deliberately NOT one-shot: the strategy tab stays open, so a user can create a second
428
+ * strategy in it and come back again — a listener that disarmed on the first return would
429
+ * leave that one missing from the list. It disarms on the first return AFTER that tab is
430
+ * closed instead, so an ordinary alt-tab later in the session costs nothing. Armed only
431
+ * once the user actually opens a strategy page, so anyone who never does pays nothing.
432
+ * @returns whether a tab actually opened, so callers can skip work that assumes one did
433
+ */
434
+ _armStrategyPageReturn(t) {
435
+ const e = window.open(t, "_blank");
436
+ if (!e)
437
+ return _().showToaster({
438
+ type: B.Error,
439
+ message: L()("discovery-strategy.error-description")
440
+ }), !1;
441
+ e.opener = null, l == null || l();
442
+ let r = !1;
443
+ const n = () => {
444
+ document.hidden || r || (e != null && e.closed && (l == null || l()), r = !0, this.invalidateStrategies(), this.fetchStrategies().then(() => this._refreshAssignedStrategyProducts()).catch(() => {
445
+ }).finally(() => {
446
+ r = !1;
447
+ }));
448
+ };
449
+ return document.addEventListener("visibilitychange", n), l = () => {
450
+ document.removeEventListener("visibilitychange", n), l = null;
451
+ }, !0;
452
+ },
453
+ /**
454
+ * Reloads the products of the block the user is on, so an edit made in the strategy tab
455
+ * lands on the canvas instead of waiting for the assign drawer to be reopened.
456
+ *
457
+ * Only the selected block: the canvas regeneration is driven off the active block's
458
+ * product array, so refreshing an unselected block would fetch without redrawing it.
459
+ */
460
+ async _refreshAssignedStrategyProducts() {
461
+ var e;
462
+ const t = this.currentRecommendationId;
463
+ t === null || !((e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId) || await this.fetchRecommendationProducts();
464
+ },
465
+ /** Lets the editor tear the return listeners down when it unmounts. */
466
+ disposeStrategyPageReturn() {
467
+ l == null || l();
468
+ },
469
+ /**
470
+ * Opens Smart Recommender's strategy creation page. Every return refreshes the list so the
471
+ * new strategy is offered; the user picks it themselves.
472
+ */
473
+ openStrategyCreation() {
474
+ return this._armStrategyPageReturn(N());
475
+ },
476
+ /**
477
+ * Opens the strategy's edit page. Every return refreshes the list so the block reflects the
478
+ * edited configuration.
479
+ */
480
+ openStrategyEdit(t) {
481
+ return this._armStrategyPageReturn(U(t));
482
+ },
367
483
  async resolveStrategyUrlTemplate(t, e) {
368
484
  const r = typeof t.updated_at == "string" ? t.updated_at : "", n = r ? `${t.strategy_id}:${r}:${e}` : "", s = n ? this.strategyUrlTemplates[n] : void 0;
369
485
  if (s)
370
486
  return s;
371
- const { full_path: i = "" } = await u.generateStrategyEndpoint(t, e) ?? {};
487
+ const { full_path: i = "" } = await m.generateStrategyEndpoint(t, e) ?? {};
372
488
  return n && i && (this.strategyUrlTemplates = {
373
489
  ...this.strategyUrlTemplates,
374
490
  [n]: i
@@ -380,24 +496,62 @@ const z = () => ({
380
496
  ...t.size !== void 0 ? { size: String(t.size) } : {}
381
497
  });
382
498
  },
499
+ async convertLegacyStrategy(t, e = { scope: "campaign" }) {
500
+ const r = this.currentRecommendationId;
501
+ if (r === null || !this.blockStates[r])
502
+ throw new Error("[Guido] convertLegacyStrategy called without an active block");
503
+ this.blockStates[r].legacyConversionLoading = !0;
504
+ try {
505
+ const n = await m.migrateLegacyStrategy(t, e);
506
+ this.invalidateStrategies();
507
+ try {
508
+ await this.fetchStrategies();
509
+ } catch (o) {
510
+ console.error("[Guido] strategy listing refresh after conversion failed", o);
511
+ }
512
+ const s = this.strategies.find((o) => String(o.strategy_id) === n), i = this.blockStates[r];
513
+ return i && (i.recommendationConfigs = {
514
+ ...i.recommendationConfigs,
515
+ strategyId: n,
516
+ nodeConfigVersion: C,
517
+ ...(s == null ? void 0 : s.size) !== void 0 ? { size: String(s.size) } : {}
518
+ }), this.currentRecommendationId === r && this.configVersion++, n;
519
+ } finally {
520
+ this.blockStates[r] && (this.blockStates[r].legacyConversionLoading = !1);
521
+ }
522
+ },
523
+ /**
524
+ * Undoes the config half of a conversion whose document write never landed, so the
525
+ * panel and the drawer fall back to "Legacy Strategy" instead of showing an assignment
526
+ * the document never received.
527
+ */
528
+ revertLegacyConversion(t, e) {
529
+ const r = this.blockStates[t];
530
+ r && (r.recommendationConfigs = { ...r.recommendationConfigs, ...e }, this.currentRecommendationId === t && this.configVersion++);
531
+ },
383
532
  removeStrategy() {
384
- this.patchCurrentBlockConfig({ strategyId: "", size: N.size });
533
+ this.patchCurrentBlockConfig({
534
+ strategyId: "",
535
+ strategy: "",
536
+ size: T.size,
537
+ nodeConfigVersion: C
538
+ });
385
539
  },
386
540
  // ====================================================================
387
541
  // Shared Data Fetching (fetched once, used by all blocks)
388
542
  // ====================================================================
389
543
  async fetchRecommendationCreateData() {
390
544
  if (!this.activePredictiveAlgorithms.length) {
391
- if (f) {
392
- await f;
545
+ if (S) {
546
+ await S;
393
547
  return;
394
548
  }
395
- f = (async () => {
549
+ S = (async () => {
396
550
  const {
397
551
  activePredictiveAlgorithms: t,
398
552
  languages: e,
399
553
  currencies: r
400
- } = await u.fetchRecommendationCreateData();
554
+ } = await m.fetchRecommendationCreateData();
401
555
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
402
556
  const n = this.blockStates[this.currentRecommendationId];
403
557
  n.filterStatus = !!n.recommendationConfigs.filters.length;
@@ -405,26 +559,26 @@ const z = () => ({
405
559
  this.currencyList = r;
406
560
  })();
407
561
  try {
408
- await f;
562
+ await S;
409
563
  } finally {
410
- f = null;
564
+ S = null;
411
565
  }
412
566
  }
413
567
  },
414
568
  async fetchRecommendationFilters() {
415
569
  if (!Object.keys(this.filterList).length) {
416
- if (g) {
417
- await g;
570
+ if (p) {
571
+ await p;
418
572
  return;
419
573
  }
420
- g = (async () => {
421
- const t = await u.fetchRecommendationFilters();
574
+ p = (async () => {
575
+ const t = await m.fetchRecommendationFilters();
422
576
  this.filterList = t;
423
577
  })();
424
578
  try {
425
- await g;
579
+ await p;
426
580
  } finally {
427
- g = null;
581
+ p = null;
428
582
  }
429
583
  }
430
584
  },
@@ -453,7 +607,7 @@ const z = () => ({
453
607
  const n = [...e.recommendationConfigs.filters];
454
608
  n[r] = {
455
609
  ...t,
456
- isValid: B(t)
610
+ isValid: K(t)
457
611
  }, e.recommendationConfigs.filters = n;
458
612
  }
459
613
  },
@@ -467,10 +621,10 @@ const z = () => ({
467
621
  let i = 1;
468
622
  n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
469
623
  } else {
470
- const i = [...new Set(n.map((c) => c.filterGroup))].sort((c, a) => c - a), o = new Map(i.map((c, a) => [c, a + 1]));
471
- n = n.map((c) => ({
472
- ...c,
473
- filterGroup: o.get(c.filterGroup) ?? c.filterGroup
624
+ const i = [...new Set(n.map((a) => a.filterGroup))].sort((a, d) => a - d), o = new Map(i.map((a, d) => [a, d + 1]));
625
+ n = n.map((a) => ({
626
+ ...a,
627
+ filterGroup: o.get(a.filterGroup) ?? a.filterGroup
474
628
  }));
475
629
  }
476
630
  e.recommendationConfigs.filters = n;
@@ -491,7 +645,7 @@ const z = () => ({
491
645
  }), e.recommendationConfigs.filters = r;
492
646
  },
493
647
  generateFilterQuery() {
494
- return k(this.recommendationConfigs.filters);
648
+ return P(this.recommendationConfigs.filters);
495
649
  },
496
650
  /**
497
651
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -499,33 +653,52 @@ const z = () => ({
499
653
  * every block's recommendationConfigs across user edits.
500
654
  */
501
655
  hasInvalidBlock() {
502
- return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
656
+ return Object.values(this.blockStates).some((t) => !J(t.recommendationConfigs, this));
657
+ },
658
+ /**
659
+ * Which required fields are missing across the design's blocks, so the save
660
+ * toaster can name the cause instead of always showing the generic message.
661
+ */
662
+ invalidBlockFields() {
663
+ return Object.values(this.blockStates).flatMap((t) => H(t.recommendationConfigs, this));
503
664
  },
504
665
  // ====================================================================
505
666
  // Per-Block Product Fetching
506
667
  // ====================================================================
507
668
  async fetchRecommendationProducts() {
508
- if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
509
- if (h) {
510
- await h;
511
- return;
512
- }
513
- h = this._doFetchProducts();
514
- try {
515
- await h;
516
- } finally {
517
- h = null;
518
- }
669
+ if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
670
+ return;
671
+ const t = [
672
+ this.currentRecommendationId,
673
+ this.configVersion,
674
+ b
675
+ ].join(":");
676
+ if (y && I === t) {
677
+ await y;
678
+ return;
679
+ }
680
+ I = t, y = this._doFetchProducts();
681
+ try {
682
+ await y;
683
+ } finally {
684
+ I === t && (y = null, I = "");
519
685
  }
520
686
  },
521
687
  async _doFetchProducts() {
522
- var d;
523
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = F();
688
+ var h;
689
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = R();
524
690
  if (r.strategyId && n.isFeatureEnabled("reusableRecommendationStrategy")) {
525
691
  await this._doFetchStrategyProducts(t);
526
692
  return;
527
693
  }
528
- const s = r.filters.filter((m) => m.isValid), i = k(s), o = ((d = b().find((m) => m.key === r.strategy)) == null ? void 0 : d.path) || "", c = parseInt(r.size) || 6, a = {
694
+ const s = (g.get(t) ?? 0) + 1;
695
+ g.set(t, s);
696
+ const i = parseInt(r.size) || 6;
697
+ if (!r.strategy) {
698
+ this._setBlockProducts(t, [], i);
699
+ return;
700
+ }
701
+ const o = r.filters.filter((f) => f.isValid), a = P(o), d = ((h = F().find((f) => f.key === r.strategy)) == null ? void 0 : h.path) || "", c = {
529
702
  locale: r.language,
530
703
  currency: r.currencySettings.value,
531
704
  partnerName: n.partnerName,
@@ -533,21 +706,30 @@ const z = () => ({
533
706
  details: !0,
534
707
  campaignId: n.variationId
535
708
  };
536
- r.strategy === "manualMerchandising" ? a.productId = A(r.productIds) : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), i && (a.filter = i), r.shuffleProducts && (a.shuffle = !0), Object.assign(
537
- a,
538
- x(n.partnerName, r.strategy)
709
+ r.strategy === "manualMerchandising" ? c.productId = q(r.productIds) : r.strategy === "similarViewed" && (c.productId = "{itemId}"), r.strategy === "userBased" && (c.userId = "{user_id}"), a && (c.filter = a), r.shuffleProducts && (c.shuffle = !0), Object.assign(
710
+ c,
711
+ $(n.partnerName, r.strategy)
539
712
  );
540
- let l;
713
+ let u;
541
714
  try {
542
- l = await u.fetchRecommendationProducts(o, a);
715
+ u = await m.fetchRecommendationProducts(d, c);
543
716
  } catch {
544
- l = [];
717
+ u = [];
545
718
  }
546
- this._setBlockProducts(t, l, c);
719
+ g.get(t) === s && this._setBlockProducts(t, u, i);
720
+ },
721
+ /**
722
+ * Block-scoped counterpart of `fetchRecommendationProducts`, which only ever serves the
723
+ * selected block. The legacy conversion must refresh the products of the block that
724
+ * started it even after the user selected another one.
725
+ */
726
+ async fetchStrategyProductsForBlock(t) {
727
+ var e;
728
+ (e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId && await this._doFetchStrategyProducts(t);
547
729
  },
548
730
  async _doFetchStrategyProducts(t) {
549
- const e = (p.get(t) ?? 0) + 1;
550
- p.set(t, e);
731
+ const e = (g.get(t) ?? 0) + 1;
732
+ g.set(t, e);
551
733
  const { recommendationConfigs: r } = this.blockStates[t];
552
734
  let n = [], s = parseInt(r.size) || 6;
553
735
  try {
@@ -557,25 +739,25 @@ const z = () => ({
557
739
  );
558
740
  if (i) {
559
741
  s = parseInt(String(i.size ?? "")) || s;
560
- const o = r.currencySettings.value, a = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), l = a.indexOf("?"), d = l === -1 ? a : a.slice(0, l), m = (l === -1 ? "" : a.slice(l + 1)).split("&").filter((I) => I && !I.startsWith("user_id=")), y = m.length ? `${d}?${m.join("&")}` : d;
561
- y && (n = await u.fetchRecommendationProductsByUrl(y));
742
+ const o = r.currencySettings.value, d = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), c = d.indexOf("?"), u = c === -1 ? d : d.slice(0, c), h = (c === -1 ? "" : d.slice(c + 1)).split("&").filter((v) => v && !v.startsWith("user_id=")), f = h.length ? `${u}?${h.join("&")}` : u;
743
+ f && (n = await m.fetchRecommendationProductsByUrl(f));
562
744
  }
563
745
  } catch {
564
746
  n = [];
565
747
  }
566
- p.get(t) === e && this._setBlockProducts(t, n, s);
748
+ g.get(t) === e && this._setBlockProducts(t, n, s);
567
749
  },
568
750
  _setBlockProducts(t, e, r) {
569
751
  if (!this.blockStates[t])
570
752
  return;
571
- const n = e.length > 0 ? e : R(r);
753
+ const n = e.length > 0 ? e : w(r);
572
754
  this.blockStates[t].recommendationProducts = [
573
755
  ...n.slice(0, r),
574
- ...R(Math.max(0, r - n.length))
756
+ ...w(Math.max(0, r - n.length))
575
757
  ];
576
758
  }
577
759
  }
578
760
  });
579
761
  export {
580
- Y as useRecommendationExtensionStore
762
+ ft as useRecommendationExtensionStore
581
763
  };
@@ -0,0 +1,42 @@
1
+ import "../constants/selectors.js";
2
+ import { RRS_CONFIG_VERSION as m } from "../constants/defaultConfig.js";
3
+ import { sanitizeProductIds as p, PRODUCT_ID_SEPARATOR as d } from "./productIds.js";
4
+ const u = "manualMerchandising";
5
+ function f({ strategyId: e, nodeConfigVersion: t }) {
6
+ return !e && t < m;
7
+ }
8
+ const o = (e) => {
9
+ const t = String(e ?? "").trim();
10
+ return /^[1-9]\d*$/.test(t) ? t : null;
11
+ };
12
+ function R({ campaignId: e, templateLibraryId: t }) {
13
+ if (o(e))
14
+ return { scope: "campaign" };
15
+ const n = o(t);
16
+ return n ? { scope: "template", templateId: n } : { scope: "campaign" };
17
+ }
18
+ function S({
19
+ blockId: e,
20
+ url: t,
21
+ campaignId: n,
22
+ variationId: a,
23
+ strategy: c,
24
+ productIds: s,
25
+ target: r
26
+ }) {
27
+ const i = c === u ? p(s).split(d).filter(Boolean) : [];
28
+ return {
29
+ ...r.scope === "template" ? { templateId: Number.parseInt(r.templateId) } : {
30
+ campaignId: Number.parseInt(n) || 0,
31
+ variationId: Number.parseInt(a) || 0
32
+ },
33
+ blockId: String(e),
34
+ url: t,
35
+ ...i.length ? { productIds: i } : {}
36
+ };
37
+ }
38
+ export {
39
+ S as buildLegacyMigrationPayload,
40
+ f as isLegacyStrategyConfig,
41
+ R as resolveMigrationTarget
42
+ };