@useinsider/guido 3.12.0-beta.0c5c36f → 3.12.0-beta.19da4fb

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 (72) hide show
  1. package/dist/@types/config/schemas.js +9 -4
  2. package/dist/components/Guido.vue.js +8 -8
  3. package/dist/components/Guido.vue2.js +58 -56
  4. package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue.js +38 -0
  5. package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue2.js +271 -0
  6. package/dist/components/organisms/extensions/recommendation/StrategyInfoBox.vue.js +20 -0
  7. package/dist/components/organisms/extensions/recommendation/StrategyInfoBox.vue2.js +16 -0
  8. package/dist/components/organisms/extensions/recommendation/StrategyPicker.vue.js +20 -0
  9. package/dist/components/organisms/extensions/recommendation/StrategyPicker.vue2.js +80 -0
  10. package/dist/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue.js +18 -0
  11. package/dist/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue2.js +42 -0
  12. package/dist/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue.js +20 -0
  13. package/dist/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue2.js +18 -0
  14. package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue.js +20 -0
  15. package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue2.js +60 -0
  16. package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue.js +19 -0
  17. package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue2.js +19 -0
  18. package/dist/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue.js +22 -0
  19. package/dist/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue2.js +29 -0
  20. package/dist/composables/useRecommendation.js +82 -51
  21. package/dist/composables/useStrategyFilters.js +16 -0
  22. package/dist/config/migrator/recommendationMigrator.js +7 -4
  23. package/dist/enums/date.js +4 -3
  24. package/dist/enums/extensions/filteringV2.js +1024 -0
  25. package/dist/enums/extensions/recommendationBlock.js +43 -29
  26. package/dist/enums/extensions/strategyDetail.js +148 -0
  27. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +12 -11
  28. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +137 -119
  29. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +57 -0
  30. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +392 -112
  31. package/dist/extensions/Blocks/Recommendation/utils/strategyHumanizer.js +97 -0
  32. package/dist/extensions/Blocks/Recommendation/utils/strategyInfoBox.js +24 -0
  33. package/dist/extensions/Blocks/Recommendation/utils/strategyNavigation.js +8 -0
  34. package/dist/extensions/Blocks/Recommendation/utils/strategySummary.js +95 -0
  35. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +24 -10
  36. package/dist/guido.css +1 -1
  37. package/dist/node_modules/@vueuse/core/index.js +48 -0
  38. package/dist/node_modules/@vueuse/shared/index.js +57 -27
  39. package/dist/services/recommendationApi.js +86 -29
  40. package/dist/src/@types/config/schemas.d.ts +10 -0
  41. package/dist/src/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue.d.ts +2 -0
  42. package/dist/src/components/organisms/extensions/recommendation/StrategyInfoBox.vue.d.ts +26 -0
  43. package/dist/src/components/organisms/extensions/recommendation/StrategyPicker.vue.d.ts +2 -0
  44. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue.d.ts +43 -0
  45. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue.d.ts +29 -0
  46. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue.d.ts +40 -0
  47. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue.d.ts +38 -0
  48. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue.d.ts +51 -0
  49. package/dist/src/composables/useConfig.d.ts +2 -0
  50. package/dist/src/composables/useRecommendation.d.ts +2 -0
  51. package/dist/src/composables/useStrategyFilters.d.ts +24 -0
  52. package/dist/src/enums/extensions/filteringV2.d.ts +72 -0
  53. package/dist/src/enums/extensions/recommendationBlock.d.ts +21 -0
  54. package/dist/src/enums/extensions/strategyDetail.d.ts +90 -0
  55. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +10 -4
  56. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +16 -0
  57. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +894 -1
  58. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +7 -0
  59. package/dist/src/extensions/Blocks/Recommendation/utils/strategyHumanizer.d.ts +50 -0
  60. package/dist/src/extensions/Blocks/Recommendation/utils/strategyInfoBox.d.ts +33 -0
  61. package/dist/src/extensions/Blocks/Recommendation/utils/strategyNavigation.d.ts +10 -0
  62. package/dist/src/extensions/Blocks/Recommendation/utils/strategySummary.d.ts +34 -0
  63. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +8 -1
  64. package/dist/src/mock/api/recommendation-strategies.d.ts +2 -0
  65. package/dist/src/services/recommendationApi.d.ts +8 -1
  66. package/dist/src/stores/config.d.ts +18 -0
  67. package/dist/src/utils/genericUtil.d.ts +9 -0
  68. package/dist/static/styles/base.css.js +25 -0
  69. package/dist/utils/dateUtil.js +23 -10
  70. package/dist/utils/genericUtil.js +10 -1
  71. package/dist/utils/templatePreparation.js +70 -57
  72. package/package.json +1 -1
@@ -1,20 +1,25 @@
1
- import { getRecommendationFeedSourceMaps as g, getOperatorOptions as R, PriceAttributes as C } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as y } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as G } from "../../../../stores/config.js";
4
- import { defineStore as F } from "pinia";
1
+ import { useConfig as P } from "../../../../composables/useConfig.js";
2
+ import { useTranslations as u } from "../../../../composables/useTranslations.js";
3
+ import { getRecommendationFeedSourceMaps as C, getOperatorOptions as G, PriceAttributes as v } from "../../../../enums/extensions/recommendationBlock.js";
4
+ import { ToasterTypeOptions as f } from "../../../../enums/toaster.js";
5
+ import { useRecommendationApi as U } from "../../../../services/recommendationApi.js";
6
+ import { useConfigStore as L } from "../../../../stores/config.js";
7
+ import { useToasterStore as g } from "../../../../stores/toaster.js";
8
+ import { defineStore as T } from "pinia";
5
9
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as P, DEFAULT_CARDS_IN_ROW as D } from "../constants/layout.js";
7
- import { EXCLUDED_ALGORITHM_IDS as w } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as S } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as b } from "../utils/filterUtil.js";
10
- import { getPartnerRecommendationParams as v } from "../utils/partnerCustomizations.js";
11
- import { isFilterValid as N } from "../validation/filterSchema.js";
12
- import { isConfigValid as x } from "../validation/requiredFields.js";
13
- const h = y();
14
- let m = null, u = null, d = null;
15
- function k() {
10
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as N, DEFAULT_CARDS_IN_ROW as x } from "../constants/layout.js";
11
+ import { EXCLUDED_ALGORITHM_IDS as B } from "../constants/defaultConfig.js";
12
+ import { getDefaultProducts as I } from "../templates/utils.js";
13
+ import { generateCompleteFilterQuery as w } from "../utils/filterUtil.js";
14
+ import { getPartnerRecommendationParams as _ } from "../utils/partnerCustomizations.js";
15
+ import { isFilterValid as V } from "../validation/filterSchema.js";
16
+ import { isConfigValid as O } from "../validation/requiredFields.js";
17
+ const m = U();
18
+ let h = null, p = null, S = null, d = null, k = 0;
19
+ const y = {};
20
+ function F() {
16
21
  return {
17
- cardsInRow: D,
22
+ cardsInRow: x,
18
23
  currencySettings: {
19
24
  name: "USD",
20
25
  value: "USD",
@@ -28,13 +33,14 @@ function k() {
28
33
  productIds: [],
29
34
  id: 1,
30
35
  language: "en_US",
31
- mobileCardsInRow: P,
36
+ mobileCardsInRow: N,
32
37
  mobileLayoutEnabled: !1,
33
38
  orientation: "grid",
34
39
  recommendedProducts: [],
35
40
  sendProductRequestFlag: !1,
36
41
  shuffleProducts: !1,
37
42
  strategy: "mostPopular",
43
+ strategyId: "",
38
44
  textTrimming: !0,
39
45
  unresponsive: !1,
40
46
  size: "6",
@@ -43,28 +49,38 @@ function k() {
43
49
  priceHideIfSameAsDiscounted: !1
44
50
  };
45
51
  }
46
- function I() {
52
+ function D() {
47
53
  return {
48
- recommendationConfigs: k(),
54
+ recommendationConfigs: F(),
49
55
  recommendationProducts: [],
50
56
  filterStatus: !1,
51
57
  filterSelectionDrawerStatus: !1,
58
+ strategyDetailDrawerStatus: !1,
52
59
  filterGroup: 1,
53
60
  isInitialized: !1,
54
61
  filterSnapshot: null
55
62
  };
56
63
  }
57
- const L = () => ({
64
+ const A = () => ({
58
65
  recommendationCampaignUrls: {},
59
66
  activePredictiveAlgorithms: [],
60
67
  languages: {},
61
68
  currencyList: [],
62
69
  filterList: {},
70
+ strategies: [],
71
+ strategiesLoading: !1,
72
+ strategyCampaigns: [],
73
+ strategyCampaignsLoading: !1,
74
+ strategyCampaignsFetchedFor: null,
75
+ strategyUrlTemplates: {},
76
+ strategyPickerVisible: !1,
77
+ strategyPickerVersion: 0,
78
+ strategyDuplicating: !1,
63
79
  blockStates: {},
64
80
  currentRecommendationId: null,
65
81
  configVersion: 0
66
- }), $ = F("guidoRecommendationExtension", {
67
- state: () => L(),
82
+ }), rt = T("guidoRecommendationExtension", {
83
+ state: () => A(),
68
84
  getters: {
69
85
  // ====================================================================
70
86
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -74,7 +90,7 @@ const L = () => ({
74
90
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
75
91
  */
76
92
  recommendationConfigs(t) {
77
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : k();
93
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : F();
78
94
  },
79
95
  /**
80
96
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -88,6 +104,12 @@ const L = () => ({
88
104
  filterSelectionDrawerStatus(t) {
89
105
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].filterSelectionDrawerStatus : !1;
90
106
  },
107
+ /**
108
+ * Proxy getter: delegates to blockStates[currentRecommendationId].strategyDetailDrawerStatus
109
+ */
110
+ strategyDetailDrawerStatus(t) {
111
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].strategyDetailDrawerStatus : !1;
112
+ },
91
113
  /**
92
114
  * Proxy getter: delegates to blockStates[currentRecommendationId].filterStatus
93
115
  */
@@ -119,14 +141,62 @@ const L = () => ({
119
141
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
120
142
  },
121
143
  getActivePredictiveAlgorithms: (t) => {
122
- const e = g(), r = [];
123
- return t.activePredictiveAlgorithms.filter((n) => !w.includes(n)).forEach((n) => {
124
- r.push(...e.filter((c) => c.id === n));
125
- }), r.map((n) => ({
126
- text: n.name,
127
- value: n.key
144
+ const e = C(), r = [];
145
+ return t.activePredictiveAlgorithms.filter((i) => !B.includes(i)).forEach((i) => {
146
+ r.push(...e.filter((a) => a.id === i));
147
+ }), r.map((i) => ({
148
+ text: i.name,
149
+ value: i.key
128
150
  }));
129
151
  },
152
+ /**
153
+ * Reusable Recommendation Strategies as dropdown options.
154
+ *
155
+ * Unlike algorithms, strategy ids come straight from Smart Recommender —
156
+ * there is no local id→key table to join against.
157
+ */
158
+ getStrategyOptions: (t) => t.strategies.map((e) => ({
159
+ text: e.name,
160
+ value: String(e.strategy_id)
161
+ })),
162
+ /**
163
+ * The current block's selected strategy, or null when none is selected
164
+ * or the selected id is no longer in the listing.
165
+ *
166
+ * A null return with a non-empty `strategyId` means the strategy was
167
+ * deleted in Smart Recommender after this block was configured — the
168
+ * panel flags that rather than silently clearing the selection.
169
+ */
170
+ getSelectedStrategy() {
171
+ const { strategyId: t } = this.recommendationConfigs;
172
+ return t ? this.strategies.find((e) => String(e.strategy_id) === t) ?? null : null;
173
+ },
174
+ /**
175
+ * Whether the RRS feature is enabled for this editor.
176
+ *
177
+ * Read through a getter so `hasInvalidBlock` can pass the store straight to
178
+ * the required-field descriptors, which need the flag to decide whether a
179
+ * missing strategy blocks the save.
180
+ */
181
+ isRrsEnabled() {
182
+ return P().isFeatureEnabled("reusableRecommendationStrategy");
183
+ },
184
+ /** Strategy ids currently offered for email, for the save-time deleted-strategy check. */
185
+ strategyIds: (t) => t.strategies.map((e) => String(e.strategy_id)),
186
+ /**
187
+ * Whether the listing is trustworthy enough to judge an assigned id against.
188
+ * Mid-fetch it is empty, and every assigned id would look deleted.
189
+ */
190
+ canValidateStrategy: (t) => !t.strategiesLoading && t.strategies.length > 0,
191
+ /**
192
+ * True when a strategy id is set but absent from the fetched listing.
193
+ *
194
+ * Guarded on the listing having loaded — mid-fetch every id looks missing.
195
+ */
196
+ hasMissingStrategy() {
197
+ const { strategyId: t } = this.recommendationConfigs;
198
+ return !t || this.strategiesLoading || !this.strategies.length ? !1 : this.getSelectedStrategy === null;
199
+ },
130
200
  getLanguages: (t) => Object.entries(t.languages).map(([e, r]) => ({
131
201
  text: r,
132
202
  value: e
@@ -138,7 +208,7 @@ const L = () => ({
138
208
  getFilterList() {
139
209
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
140
210
  let e;
141
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : C.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
211
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : v.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
142
212
  text: t.displayName,
143
213
  value: e,
144
214
  type: t.attributeType
@@ -160,7 +230,7 @@ const L = () => ({
160
230
  setCurrentBlock(t) {
161
231
  this.blockStates[t] || (this.blockStates = {
162
232
  ...this.blockStates,
163
- [t]: I()
233
+ [t]: D()
164
234
  }), this.currentRecommendationId = t;
165
235
  },
166
236
  /**
@@ -170,13 +240,13 @@ const L = () => ({
170
240
  removeBlockState(t) {
171
241
  const e = t.toString();
172
242
  if (this.recommendationCampaignUrls[e]) {
173
- const n = { ...this.recommendationCampaignUrls };
174
- delete n[e], this.recommendationCampaignUrls = n;
243
+ const i = { ...this.recommendationCampaignUrls };
244
+ delete i[e], this.recommendationCampaignUrls = i;
175
245
  }
176
246
  const r = { ...this.blockStates };
177
247
  if (delete r[t], this.blockStates = r, this.currentRecommendationId === t) {
178
- const n = Object.keys(this.blockStates).map(Number);
179
- this.currentRecommendationId = n.length > 0 ? n[0] : null;
248
+ const i = Object.keys(this.blockStates).map(Number);
249
+ this.currentRecommendationId = i.length > 0 ? i[0] : null;
180
250
  }
181
251
  },
182
252
  /**
@@ -186,16 +256,16 @@ const L = () => ({
186
256
  cloneBlockState(t, e) {
187
257
  const r = this.blockStates[t];
188
258
  if (r) {
189
- const o = structuredClone(r);
190
- o.recommendationConfigs.id = e, this.blockStates = {
259
+ const s = structuredClone(r);
260
+ s.recommendationConfigs.id = e, this.blockStates = {
191
261
  ...this.blockStates,
192
- [e]: o
262
+ [e]: s
193
263
  };
194
264
  }
195
- const n = t.toString(), c = e.toString(), i = this.recommendationCampaignUrls[n];
196
- i && (this.recommendationCampaignUrls = {
265
+ const i = t.toString(), a = e.toString(), n = this.recommendationCampaignUrls[i];
266
+ n && (this.recommendationCampaignUrls = {
197
267
  ...this.recommendationCampaignUrls,
198
- [c]: i
268
+ [a]: n
199
269
  });
200
270
  },
201
271
  /**
@@ -220,7 +290,7 @@ const L = () => ({
220
290
  * `isInitialized`) alone.
221
291
  */
222
292
  seedBlockUrlConfig(t, e) {
223
- const r = (o, s) => o === "." || o === "," || o === " " ? o : s, n = {
293
+ const r = (s, o) => s === "." || s === "," || s === " " ? s : o, i = {
224
294
  name: e.currencyCode,
225
295
  value: e.currencyCode,
226
296
  symbol: e.currencyCode,
@@ -228,19 +298,20 @@ const L = () => ({
228
298
  decimalCount: String(e.currencyDecimalCount),
229
299
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
230
300
  thousandSeparator: r(e.currencyThousandSeparator, ".")
231
- }, c = !this.blockStates[t], i = c ? I() : this.blockStates[t];
232
- i.recommendationConfigs = {
233
- ...i.recommendationConfigs,
301
+ }, a = !this.blockStates[t], n = a ? D() : this.blockStates[t];
302
+ n.recommendationConfigs = {
303
+ ...n.recommendationConfigs,
234
304
  strategy: e.strategy,
305
+ strategyId: e.strategyId,
235
306
  language: e.language,
236
307
  size: e.size,
237
308
  productIds: e.productIds,
238
309
  filters: e.filters,
239
310
  shuffleProducts: e.shuffleProducts,
240
- currencySettings: n
241
- }, c && (this.blockStates = {
311
+ currencySettings: i
312
+ }, a && (this.blockStates = {
242
313
  ...this.blockStates,
243
- [t]: i
314
+ [t]: n
244
315
  });
245
316
  },
246
317
  /**
@@ -259,18 +330,18 @@ const L = () => ({
259
330
  ...t.currencySettings || {}
260
331
  }
261
332
  };
262
- const { triggerRefetch: n = !0 } = e;
263
- n && this.configVersion++;
333
+ const { triggerRefetch: i = !0 } = e;
334
+ i && this.configVersion++;
264
335
  },
265
336
  /**
266
337
  * Creates a filter with the first available attribute and operator pre-selected.
267
338
  */
268
339
  createDefaultFilter(t, e) {
269
- const [r] = this.getFilterList, [n] = R(r == null ? void 0 : r.type);
340
+ const [r] = this.getFilterList, [i] = G(r == null ? void 0 : r.type);
270
341
  return {
271
342
  type: "standardFilter",
272
343
  attribute: (r == null ? void 0 : r.value) ?? "",
273
- operator: (n == null ? void 0 : n.value) ?? "",
344
+ operator: (i == null ? void 0 : i.value) ?? "",
274
345
  innerGroupOperator: "*",
275
346
  outerGroupOperator: "*",
276
347
  filterNumber: e,
@@ -303,6 +374,176 @@ const L = () => ({
303
374
  const t = this.blockStates[this.currentRecommendationId];
304
375
  t.filterSnapshot = null, t.filterSelectionDrawerStatus = !1;
305
376
  },
377
+ /** The strategy control mounted (or re-rendered): let the overlay re-attach. */
378
+ showStrategyPicker() {
379
+ this.strategyPickerVisible = !0, this.strategyPickerVersion += 1;
380
+ },
381
+ /** The strategy control went away — hide the overlay with it. */
382
+ hideStrategyPicker() {
383
+ this.strategyPickerVisible = !1;
384
+ },
385
+ /**
386
+ * Assign a strategy to the current block.
387
+ *
388
+ * Only patches Pinia: the `configVersion` bump makes the main control persist
389
+ * `strategyId` into the block's node config (see `_persistStoreConfigToNode`),
390
+ * which is the same route filters take. Resolving the URL template here rather
391
+ * than at compile time matters — `buildCampaignUrl` is synchronous and would
392
+ * otherwise silently fall back to the algorithm URL.
393
+ */
394
+ async selectStrategy(t) {
395
+ if (this.recommendationConfigs.strategyId === t)
396
+ return;
397
+ this.patchCurrentBlockConfig({ strategyId: t }, { triggerRefetch: !0 });
398
+ const e = await this.fetchStrategyUrlTemplate(t), r = this.strategies.find((i) => String(i.strategy_id) === t);
399
+ if (!e) {
400
+ g().showToaster({
401
+ type: f.Error,
402
+ message: u()("discovery-strategy.error-description")
403
+ });
404
+ return;
405
+ }
406
+ g().showToaster({
407
+ type: f.Success,
408
+ message: u()("campaign-builder.variation-strategy-assign", {
409
+ strategyName: (r == null ? void 0 : r.name) ?? t
410
+ })
411
+ });
412
+ },
413
+ /**
414
+ * Duplicate the selected strategy and assign the copy to this block.
415
+ *
416
+ * Runs inline — the marketer never leaves the editor. Two things the web flow
417
+ * does that we deliberately skip: its follow-up `POST /{id}/assign` (that binds
418
+ * a strategy to a campaign *variation*, a concept email blocks do not have —
419
+ * here the assignment is the block's own config), and displaying the copy's
420
+ * name from the response, which only carries the new id. Hence the refetch.
421
+ */
422
+ async duplicateAndAssignStrategy() {
423
+ const t = this.getSelectedStrategy;
424
+ if (!(!t || this.strategyDuplicating)) {
425
+ this.strategyDuplicating = !0;
426
+ try {
427
+ const e = await m.duplicateStrategy(String(t.strategy_id));
428
+ if (!e) {
429
+ g().showToaster({
430
+ type: f.Error,
431
+ message: u()("discovery-strategy.error-description")
432
+ });
433
+ return;
434
+ }
435
+ await this.refreshRecommendationStrategies(), this.patchCurrentBlockConfig({ strategyId: e }, { triggerRefetch: !0 });
436
+ const r = await this.fetchStrategyUrlTemplate(e);
437
+ if (this.closeStrategyDrawer(), !r) {
438
+ g().showToaster({
439
+ type: f.Error,
440
+ message: u()("discovery-strategy.error-description")
441
+ });
442
+ return;
443
+ }
444
+ g().showToaster({
445
+ type: f.Success,
446
+ message: u()("recommendation-strategies.duplicate-and-assign-text", {
447
+ strategyName: t.name
448
+ })
449
+ });
450
+ } finally {
451
+ this.strategyDuplicating = !1;
452
+ }
453
+ }
454
+ },
455
+ /**
456
+ * Opens the RRS detail drawer for the current block.
457
+ *
458
+ * Unlike the filter drawer this takes no snapshot — the drawer is
459
+ * read-only, so there is nothing to revert on close.
460
+ */
461
+ openStrategyDrawer() {
462
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].strategyDetailDrawerStatus = !0);
463
+ },
464
+ /**
465
+ * Closes the RRS detail drawer for the current block and drops the campaign
466
+ * list, so reopening on a different strategy never flashes stale rows.
467
+ */
468
+ closeStrategyDrawer() {
469
+ this.clearStrategyCampaigns(), !(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId]) && (this.blockStates[this.currentRecommendationId].strategyDetailDrawerStatus = !1);
470
+ },
471
+ /** Drops the campaign list and lets the next open refetch. */
472
+ clearStrategyCampaigns() {
473
+ this.strategyCampaigns = [], this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = null, k += 1;
474
+ },
475
+ /**
476
+ * Resolve and cache a strategy's recommendation-URL template.
477
+ *
478
+ * Must be awaited before `buildCampaignUrl` runs for a strategy-driven block,
479
+ * because that builder is synchronous and can only read what is already
480
+ * cached. Safe to call repeatedly: a resolved id short-circuits, and
481
+ * concurrent callers for the same id share one request.
482
+ *
483
+ * Only a *settled* answer is cached. An id the server rejects caches as '',
484
+ * so the block falls back instead of retrying on every compile. A request
485
+ * that merely failed to complete leaves the key unset and resolves to false,
486
+ * so the next selection or save tries again — caching a network blip as "no
487
+ * URL" would strand the block on its old algorithm feed for the session while
488
+ * the panel still showed the strategy as assigned.
489
+ * @returns whether the strategy now has a USABLE url — not merely a cache
490
+ * entry. A rejected id caches as '' to stop the retry storm, but reporting
491
+ * that as success would let the caller confirm an assignment the compiled
492
+ * email cannot honour.
493
+ */
494
+ async fetchStrategyUrlTemplate(t) {
495
+ if (!t)
496
+ return !1;
497
+ if (t in this.strategyUrlTemplates)
498
+ return !!this.strategyUrlTemplates[t];
499
+ const e = y[t];
500
+ if (e)
501
+ return await e, !!this.strategyUrlTemplates[t];
502
+ y[t] = (async () => {
503
+ try {
504
+ const r = await m.fetchStrategyRecommendationUrl(t);
505
+ this.strategyUrlTemplates = {
506
+ ...this.strategyUrlTemplates,
507
+ [t]: (r == null ? void 0 : r.full_path) ?? ""
508
+ };
509
+ } catch (r) {
510
+ console.error("fetchStrategyUrlTemplate error:", r);
511
+ }
512
+ })();
513
+ try {
514
+ await y[t];
515
+ } finally {
516
+ delete y[t];
517
+ }
518
+ return !!this.strategyUrlTemplates[t];
519
+ },
520
+ /**
521
+ * Wait for every strategy-URL request still in flight.
522
+ *
523
+ * The compiler is synchronous: whatever is not cached by the time it runs
524
+ * compiles as the block's old algorithm URL. Selection and template hydrate
525
+ * both await their own request, but a save fired inside that round trip would
526
+ * still race — so the save path drains the queue first.
527
+ */
528
+ async awaitPendingStrategyUrls() {
529
+ await Promise.all(Object.values(y).filter(Boolean));
530
+ },
531
+ /**
532
+ * Fetch the campaigns a strategy is used in, for the drawer's second tab.
533
+ *
534
+ * Lazy and once-per-strategy: the caller invokes this only while the drawer is
535
+ * open on that tab, and a repeat call for the same id is a no-op. A version
536
+ * counter discards a response that arrives after the user switched strategies,
537
+ * which would otherwise show one strategy's campaigns under another's name.
538
+ */
539
+ async fetchStrategyCampaigns(t) {
540
+ if (!t || this.strategyCampaignsFetchedFor === t)
541
+ return;
542
+ const e = ++k;
543
+ this.strategyCampaignsLoading = !0;
544
+ const r = await m.fetchStrategyCampaigns(t, u());
545
+ e === k && (this.strategyCampaigns = r, this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = t);
546
+ },
306
547
  /**
307
548
  * Cancels the filter selection drawer and reverts filters
308
549
  * to the snapshot taken when the drawer was opened.
@@ -318,43 +559,82 @@ const L = () => ({
318
559
  // ====================================================================
319
560
  async fetchRecommendationCreateData() {
320
561
  if (!this.activePredictiveAlgorithms.length) {
321
- if (m) {
322
- await m;
562
+ if (h) {
563
+ await h;
323
564
  return;
324
565
  }
325
- m = (async () => {
566
+ h = (async () => {
326
567
  const {
327
568
  activePredictiveAlgorithms: t,
328
569
  languages: e,
329
570
  currencies: r
330
- } = await h.fetchRecommendationCreateData();
571
+ } = await m.fetchRecommendationCreateData();
331
572
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
332
- const n = this.blockStates[this.currentRecommendationId];
333
- n.filterStatus = !!n.recommendationConfigs.filters.length;
573
+ const i = this.blockStates[this.currentRecommendationId];
574
+ i.filterStatus = !!i.recommendationConfigs.filters.length;
334
575
  }
335
576
  this.currencyList = r;
336
577
  })();
337
578
  try {
338
- await m;
579
+ await h;
339
580
  } finally {
340
- m = null;
581
+ h = null;
341
582
  }
342
583
  }
343
584
  },
585
+ /**
586
+ * Fetch the partner's Reusable Recommendation Strategies for the EMAIL channel.
587
+ *
588
+ * Same cache-guard + in-flight dedupe shape as fetchRecommendationCreateData.
589
+ * A failure leaves `strategies` empty so the dropdown renders empty rather
590
+ * than blocking the panel — the block keeps working on its algorithm.
591
+ */
592
+ async fetchRecommendationStrategies() {
593
+ if (!this.strategies.length) {
594
+ if (d) {
595
+ await d;
596
+ return;
597
+ }
598
+ this.strategiesLoading = !0, d = (async () => {
599
+ try {
600
+ this.strategies = await m.fetchRecommendationStrategies();
601
+ } catch {
602
+ this.strategies = [];
603
+ } finally {
604
+ this.strategiesLoading = !1;
605
+ }
606
+ })();
607
+ try {
608
+ await d;
609
+ } finally {
610
+ d = null;
611
+ }
612
+ }
613
+ },
614
+ /**
615
+ * Refetch the listing, ignoring the once-per-session cache.
616
+ *
617
+ * Needed after Duplicate and Assign: the copy exists server-side but not in
618
+ * `strategies`, so without this the panel would read the freshly assigned id
619
+ * back as a deleted strategy.
620
+ */
621
+ async refreshRecommendationStrategies() {
622
+ this.strategies = [], d = null, await this.fetchRecommendationStrategies();
623
+ },
344
624
  async fetchRecommendationFilters() {
345
625
  if (!Object.keys(this.filterList).length) {
346
- if (u) {
347
- await u;
626
+ if (p) {
627
+ await p;
348
628
  return;
349
629
  }
350
- u = (async () => {
351
- const t = await h.fetchRecommendationFilters();
630
+ p = (async () => {
631
+ const t = await m.fetchRecommendationFilters();
352
632
  this.filterList = t;
353
633
  })();
354
634
  try {
355
- await u;
635
+ await p;
356
636
  } finally {
357
- u = null;
637
+ p = null;
358
638
  }
359
639
  }
360
640
  },
@@ -369,59 +649,59 @@ const L = () => ({
369
649
  deleteFilterGroup(t) {
370
650
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
371
651
  return;
372
- const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.filter((i) => i.filterGroup !== t), n = [...new Set(r.map((i) => i.filterGroup))].sort((i, o) => i - o), c = new Map(n.map((i, o) => [i, o + 1]));
373
- e.recommendationConfigs.filters = r.map((i) => ({
374
- ...i,
375
- filterGroup: c.get(i.filterGroup) ?? i.filterGroup
652
+ const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.filter((n) => n.filterGroup !== t), i = [...new Set(r.map((n) => n.filterGroup))].sort((n, s) => n - s), a = new Map(i.map((n, s) => [n, s + 1]));
653
+ e.recommendationConfigs.filters = r.map((n) => ({
654
+ ...n,
655
+ filterGroup: a.get(n.filterGroup) ?? n.filterGroup
376
656
  }));
377
657
  },
378
658
  updateFilter(t) {
379
659
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
380
660
  return;
381
- const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
661
+ const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.findIndex((i) => i.filterNumber === t.filterNumber && i.filterGroup === t.filterGroup);
382
662
  if (r !== -1) {
383
- const n = [...e.recommendationConfigs.filters];
384
- n[r] = {
663
+ const i = [...e.recommendationConfigs.filters];
664
+ i[r] = {
385
665
  ...t,
386
- isValid: N(t)
387
- }, e.recommendationConfigs.filters = n;
666
+ isValid: V(t)
667
+ }, e.recommendationConfigs.filters = i;
388
668
  }
389
669
  },
390
670
  deleteFilter(t) {
391
671
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
392
672
  return;
393
- const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters].findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
673
+ const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters].findIndex((i) => i.filterNumber === t.filterNumber && i.filterGroup === t.filterGroup);
394
674
  if (r !== -1) {
395
- let n = [...e.recommendationConfigs.filters];
396
- if (n.splice(r, 1), n.some((i) => i.filterGroup === t.filterGroup)) {
397
- let i = 1;
398
- n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
675
+ let i = [...e.recommendationConfigs.filters];
676
+ if (i.splice(r, 1), i.some((n) => n.filterGroup === t.filterGroup)) {
677
+ let n = 1;
678
+ i = i.map((s) => s.filterGroup === t.filterGroup ? { ...s, filterNumber: n++ } : s);
399
679
  } else {
400
- const i = [...new Set(n.map((s) => s.filterGroup))].sort((s, a) => s - a), o = new Map(i.map((s, a) => [s, a + 1]));
401
- n = n.map((s) => ({
402
- ...s,
403
- filterGroup: o.get(s.filterGroup) ?? s.filterGroup
680
+ const n = [...new Set(i.map((o) => o.filterGroup))].sort((o, c) => o - c), s = new Map(n.map((o, c) => [o, c + 1]));
681
+ i = i.map((o) => ({
682
+ ...o,
683
+ filterGroup: s.get(o.filterGroup) ?? o.filterGroup
404
684
  }));
405
685
  }
406
- e.recommendationConfigs.filters = n;
686
+ e.recommendationConfigs.filters = i;
407
687
  }
408
688
  },
409
689
  addFilter(t) {
410
690
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
411
691
  return;
412
- const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters], c = r.filter(
413
- (o) => o.filterGroup === t.filterGroup
414
- ).length + 1, i = r.findLastIndex((o) => o.filterGroup === t.filterGroup);
415
- i !== -1 ? r.splice(i + 1, 0, {
692
+ const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters], a = r.filter(
693
+ (s) => s.filterGroup === t.filterGroup
694
+ ).length + 1, n = r.findLastIndex((s) => s.filterGroup === t.filterGroup);
695
+ n !== -1 ? r.splice(n + 1, 0, {
416
696
  ...t,
417
- filterNumber: c
697
+ filterNumber: a
418
698
  }) : r.push({
419
699
  ...t,
420
- filterNumber: c
700
+ filterNumber: a
421
701
  }), e.recommendationConfigs.filters = r;
422
702
  },
423
703
  generateFilterQuery() {
424
- return b(this.recommendationConfigs.filters);
704
+ return w(this.recommendationConfigs.filters);
425
705
  },
426
706
  /**
427
707
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -429,55 +709,55 @@ const L = () => ({
429
709
  * every block's recommendationConfigs across user edits.
430
710
  */
431
711
  hasInvalidBlock() {
432
- return Object.values(this.blockStates).some((t) => !x(t.recommendationConfigs, this));
712
+ return Object.values(this.blockStates).some((t) => !O(t.recommendationConfigs, this));
433
713
  },
434
714
  // ====================================================================
435
715
  // Per-Block Product Fetching
436
716
  // ====================================================================
437
717
  async fetchRecommendationProducts() {
438
718
  if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
439
- if (d) {
440
- await d;
719
+ if (S) {
720
+ await S;
441
721
  return;
442
722
  }
443
- d = this._doFetchProducts();
723
+ S = this._doFetchProducts();
444
724
  try {
445
- await d;
725
+ await S;
446
726
  } finally {
447
- d = null;
727
+ S = null;
448
728
  }
449
729
  }
450
730
  },
451
731
  async _doFetchProducts() {
452
- var p;
453
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = r.filters.filter((l) => l.isValid), c = b(n), i = ((p = g().find((l) => l.key === r.strategy)) == null ? void 0 : p.path) || "", o = G(), s = parseInt(r.size) || 6, a = {
732
+ var R;
733
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, i = r.filters.filter((l) => l.isValid), a = w(i), n = ((R = C().find((l) => l.key === r.strategy)) == null ? void 0 : R.path) || "", s = L(), o = parseInt(r.size) || 6, c = {
454
734
  locale: r.language,
455
735
  currency: r.currencySettings.value,
456
- partnerName: o.partnerName,
736
+ partnerName: s.partnerName,
457
737
  size: r.size,
458
738
  details: !0,
459
- campaignId: o.variationId
739
+ campaignId: s.variationId
460
740
  };
461
- r.strategy === "manualMerchandising" ? a.productId = r.productIds.join(",") : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), c && (a.filter = c), r.shuffleProducts && (a.shuffle = !0), Object.assign(
462
- a,
463
- v(o.partnerName, r.strategy)
741
+ r.strategy === "manualMerchandising" ? c.productId = r.productIds.join(",") : r.strategy === "similarViewed" && (c.productId = "{itemId}"), r.strategy === "userBased" && (c.userId = "{user_id}"), a && (c.filter = a), r.shuffleProducts && (c.shuffle = !0), Object.assign(
742
+ c,
743
+ _(s.partnerName, r.strategy)
464
744
  );
465
- let f;
745
+ let b;
466
746
  try {
467
- f = await h.fetchRecommendationProducts(i, a);
747
+ b = await m.fetchRecommendationProducts(n, c);
468
748
  } catch {
469
- f = [];
749
+ b = [];
470
750
  }
471
751
  if (this.blockStates[t]) {
472
- const l = f.length > 0 ? f : S(s);
473
- l.length < s ? this.blockStates[t].recommendationProducts = [
752
+ const l = b.length > 0 ? b : I(o);
753
+ l.length < o ? this.blockStates[t].recommendationProducts = [
474
754
  ...l,
475
- ...S(s - l.length)
476
- ] : l.length > s ? this.blockStates[t].recommendationProducts = l.slice(0, s) : this.blockStates[t].recommendationProducts = l;
755
+ ...I(o - l.length)
756
+ ] : l.length > o ? this.blockStates[t].recommendationProducts = l.slice(0, o) : this.blockStates[t].recommendationProducts = l;
477
757
  }
478
758
  }
479
759
  }
480
760
  });
481
761
  export {
482
- $ as useRecommendationExtensionStore
762
+ rt as useRecommendationExtensionStore
483
763
  };