@4site/engrid-scripts 0.22.13 → 0.22.15

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.
@@ -33,6 +33,8 @@ export class DonationAmount {
33
33
  this.amount = ENGrid.cleanAmount(otherField.value);
34
34
  });
35
35
  }
36
+ // Load the current amount
37
+ this.load();
36
38
  }
37
39
  static getInstance(radios = "transaction.donationAmt", other = "transaction.donationAmt.other") {
38
40
  if (!DonationAmount.instance) {
@@ -8,6 +8,13 @@ export declare class FrequencyUpsell {
8
8
  private _form;
9
9
  private modalSeen;
10
10
  constructor();
11
+ /**
12
+ * Select the proper options (single config or A/B variant) and return a concrete FrequencyUpsellOptions object.
13
+ * If an A/B test config is provided (abTest: true, options: [...]) a random variant is chosen and stored
14
+ * in a 1-day cookie so subsequent visits get the same variant.
15
+ */
16
+ private selectOptions;
17
+ private randomIndex;
11
18
  /**
12
19
  * Check if the FrequencyUpsell should run:
13
20
  * - Check if the FrequencyUpsell is enabled in the window object
@@ -5,6 +5,7 @@
5
5
  * See FrequencyUpsellOptions for more details.
6
6
  */
7
7
  import { DonationAmount, DonationFrequency, EnForm, EngridLogger, FrequencyUpsellModal, FrequencyUpsellOptionsDefaults, ProcessingFees, } from ".";
8
+ import * as cookie from "./cookie";
8
9
  export class FrequencyUpsell {
9
10
  constructor() {
10
11
  this.logger = new EngridLogger("FrequencyUpsell", "lightgray", "darkblue", "🏦");
@@ -19,12 +20,55 @@ export class FrequencyUpsell {
19
20
  this.logger.log("FrequencyUpsell not running");
20
21
  return;
21
22
  }
22
- this.options = Object.assign(Object.assign({}, FrequencyUpsellOptionsDefaults), window.EngridFrequencyUpsell);
23
+ this.options = this.selectOptions(window.EngridFrequencyUpsell);
23
24
  this.logger.log("FrequencyUpsell initialized", this.options);
24
25
  this.upsellModal = new FrequencyUpsellModal(this.options);
25
26
  this.createFrequencyField();
26
27
  this.addEventListeners();
27
28
  }
29
+ /**
30
+ * Select the proper options (single config or A/B variant) and return a concrete FrequencyUpsellOptions object.
31
+ * If an A/B test config is provided (abTest: true, options: [...]) a random variant is chosen and stored
32
+ * in a 1-day cookie so subsequent visits get the same variant.
33
+ */
34
+ selectOptions(config) {
35
+ // Simple (non AB) case
36
+ if (!config.abTest) {
37
+ return Object.assign(Object.assign({}, FrequencyUpsellOptionsDefaults), config);
38
+ }
39
+ const abConfig = config;
40
+ const cookieName = abConfig.cookieName || "engrid_frequency_upsell_variant";
41
+ const existing = cookie.get(cookieName);
42
+ let index;
43
+ if (existing !== undefined) {
44
+ const parsed = parseInt(existing, 10);
45
+ if (!isNaN(parsed) && parsed >= 0 && parsed < abConfig.options.length) {
46
+ index = parsed;
47
+ }
48
+ else {
49
+ index = this.randomIndex(abConfig.options.length);
50
+ }
51
+ }
52
+ else {
53
+ index = this.randomIndex(abConfig.options.length);
54
+ }
55
+ // Persist for configured duration
56
+ const duration = abConfig.cookieDurationDays || 1;
57
+ cookie.set(cookieName, index.toString(), { expires: duration });
58
+ const chosen = abConfig.options[index];
59
+ // Push variant info to dataLayer if available
60
+ if (window.dataLayer) {
61
+ window.dataLayer.push({
62
+ event: "frequency_upsell_ab_variant",
63
+ frequencyUpsellVariantIndex: index,
64
+ frequencyUpsellVariantTitle: chosen.title,
65
+ });
66
+ }
67
+ return Object.assign(Object.assign({}, FrequencyUpsellOptionsDefaults), chosen);
68
+ }
69
+ randomIndex(length) {
70
+ return Math.floor(Math.random() * length);
71
+ }
28
72
  /**
29
73
  * Check if the FrequencyUpsell should run:
30
74
  * - Check if the FrequencyUpsell is enabled in the window object
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { Options, OptionsDefaults } from "./interfaces/options";
3
3
  export { UpsellOptions, UpsellOptionsDefaults, } from "./interfaces/upsell-options";
4
4
  export { TranslateOptions, TranslateOptionsDefaults, } from "./interfaces/translate-options";
5
5
  export { ExitIntentOptions, ExitIntentOptionsDefaults, } from "./interfaces/exit-intent-options";
6
- export { FrequencyUpsellOptions, FrequencyUpsellOptionsDefaults, } from "./interfaces/frequency-upsell-options";
6
+ export { FrequencyUpsellOptions, FrequencyUpsellOptionsDefaults, FrequencyUpsellABTestConfig, FrequencyUpsellConfig, } from "./interfaces/frequency-upsell-options";
7
7
  export * from "./loader";
8
8
  export * from "./app";
9
9
  export * from "./amount-label";
@@ -11,4 +11,11 @@ export interface FrequencyUpsellOptions {
11
11
  onAccept: () => void;
12
12
  onDecline: () => void;
13
13
  }
14
+ export interface FrequencyUpsellABTestConfig {
15
+ abTest: true;
16
+ options: FrequencyUpsellOptions[];
17
+ cookieName?: string;
18
+ cookieDurationDays?: number;
19
+ }
20
+ export type FrequencyUpsellConfig = FrequencyUpsellOptions | FrequencyUpsellABTestConfig;
14
21
  export declare const FrequencyUpsellOptionsDefaults: FrequencyUpsellOptions;
@@ -33,14 +33,10 @@ export declare class SwapAmounts {
33
33
  constructor();
34
34
  loadAmountsFromUrl(): void;
35
35
  swapAmounts(): void;
36
- loadEnAmounts(amountArray: {
37
- amounts: [string, number];
38
- default: number;
39
- }): {
40
- selected: boolean;
41
- label: string;
42
- value: string;
43
- }[];
36
+ /**
37
+ * Convert the internal config object into the structure Engaging Networks expects
38
+ */
39
+ private toEnAmountList;
44
40
  shouldRun(): boolean;
45
41
  ignoreCurrentValue(): boolean;
46
42
  }
@@ -34,79 +34,94 @@ export class SwapAmounts {
34
34
  this.logger = new EngridLogger("SwapAmounts", "purple", "white", "💰");
35
35
  this._amount = DonationAmount.getInstance();
36
36
  this._frequency = DonationFrequency.getInstance();
37
- this.defaultChange = false;
38
- this.swapped = false;
37
+ this.defaultChange = false; // Tracks if user changed away from default after swap
38
+ this.swapped = false; // Tracks if we've already executed at least one swap
39
39
  this.loadAmountsFromUrl();
40
40
  if (!this.shouldRun())
41
41
  return;
42
+ // Respond when frequency changes
42
43
  this._frequency.onFrequencyChange.subscribe(() => this.swapAmounts());
44
+ // Track if donor moves away from the swapped default amount
43
45
  this._amount.onAmountChange.subscribe(() => {
44
- if (this._frequency.frequency in window.EngridAmounts === false)
46
+ const configs = window.EngridAmounts;
47
+ if (!configs)
45
48
  return;
46
- this.defaultChange = false;
47
- if (!this.swapped)
49
+ const freq = this._frequency.frequency;
50
+ if (!(freq in configs))
48
51
  return;
49
- // Check if the amount is not default amount for the frequency
50
- if (this._amount.amount !=
51
- window.EngridAmounts[this._frequency.frequency].default) {
52
- this.defaultChange = true;
53
- }
52
+ if (!this.swapped)
53
+ return; // ignore early changes before initial swap
54
+ const currentConfig = configs[freq];
55
+ this.defaultChange = this._amount.amount !== currentConfig.default;
54
56
  });
55
57
  }
56
58
  loadAmountsFromUrl() {
57
59
  const urlParams = new URLSearchParams(window.location.search);
58
60
  const amounts = urlParams.get("engrid-amounts");
59
61
  if (amounts) {
60
- this.defaultChange = true;
61
- const amountArray = amounts.split(",").map((amt) => amt.trim());
62
- const defaultAmount = parseFloat(ENGrid.getUrlParameter("transaction.donationAmt")) || parseFloat(amountArray[0]);
62
+ this.defaultChange = true; // if amounts come from URL, treat as user-set
63
+ const amountArray = amounts
64
+ .split(",")
65
+ .map((amt) => amt.trim())
66
+ .filter(Boolean);
67
+ if (!amountArray.length)
68
+ return;
69
+ const urlDefaultParam = ENGrid.getUrlParameter("transaction.donationAmt");
70
+ const parsedFirst = parseFloat(amountArray[0]);
71
+ const defaultAmount = (urlDefaultParam && parseFloat(urlDefaultParam)) ||
72
+ parsedFirst;
63
73
  const amountsObj = {};
64
- for (let i = 0; i < amountArray.length; i++) {
65
- amountsObj[amountArray[i].toString()] = isNaN(parseFloat(amountArray[i]))
66
- ? amountArray[i]
67
- : parseFloat(amountArray[i]);
68
- }
74
+ amountArray.forEach((raw) => {
75
+ const numeric = parseFloat(raw);
76
+ amountsObj[raw] = isNaN(numeric) ? raw : numeric;
77
+ });
78
+ // Ensure Other choice always present at the end
69
79
  amountsObj["Other"] = "other";
80
+ const config = {
81
+ amounts: amountsObj,
82
+ default: defaultAmount,
83
+ };
70
84
  window.EngridAmounts = {
71
- onetime: { amounts: amountsObj, default: defaultAmount },
72
- monthly: { amounts: amountsObj, default: defaultAmount },
85
+ onetime: config,
86
+ monthly: config,
73
87
  };
74
88
  }
75
89
  }
76
90
  swapAmounts() {
77
- if (this._frequency.frequency in window.EngridAmounts) {
78
- window.EngagingNetworks.require._defined.enjs.swapList("donationAmt", this.loadEnAmounts(window.EngridAmounts[this._frequency.frequency]), {
79
- ignoreCurrentValue: this.ignoreCurrentValue(),
80
- });
81
- this._amount.load();
82
- this.logger.log("Amounts Swapped To", window.EngridAmounts[this._frequency.frequency], {
83
- ignoreCurrentValue: this.ignoreCurrentValue(),
84
- });
85
- this.swapped = true;
86
- }
91
+ const configs = window.EngridAmounts;
92
+ if (!configs)
93
+ return;
94
+ const freq = this._frequency.frequency;
95
+ const config = configs[freq];
96
+ if (!config)
97
+ return;
98
+ const ignoreCurrentValue = this.ignoreCurrentValue();
99
+ window.EngagingNetworks.require._defined.enjs.swapList("donationAmt", this.toEnAmountList(config), { ignoreCurrentValue });
100
+ this._amount.load();
101
+ this.logger.log("Amounts Swapped To", config, { ignoreCurrentValue });
102
+ this.swapped = true;
87
103
  }
88
- loadEnAmounts(amountArray) {
89
- let ret = [];
90
- for (let amount in amountArray.amounts) {
91
- ret.push({
92
- selected: amountArray.amounts[amount] === amountArray.default,
93
- label: amount,
94
- value: amountArray.amounts[amount].toString(),
95
- });
96
- }
97
- return ret;
104
+ /**
105
+ * Convert the internal config object into the structure Engaging Networks expects
106
+ */
107
+ toEnAmountList(config) {
108
+ return Object.entries(config.amounts).map(([label, value]) => ({
109
+ selected: value === config.default,
110
+ label,
111
+ value: value.toString(),
112
+ }));
98
113
  }
99
114
  shouldRun() {
100
- return "EngridAmounts" in window;
115
+ return !!window.EngridAmounts;
101
116
  }
102
117
  ignoreCurrentValue() {
103
- if (ENGrid.getUrlParameter("transaction.donationAmt") !== null) {
104
- return this._amount.amount ===
105
- parseFloat(ENGrid.getUrlParameter("transaction.donationAmt"))
106
- ? false
107
- : true;
118
+ const urlParam = ENGrid.getUrlParameter("transaction.donationAmt");
119
+ if (urlParam !== null) {
120
+ const urlAmount = parseFloat(urlParam);
121
+ return this._amount.amount !== urlAmount;
108
122
  }
109
- return !(window.EngagingNetworks.require._defined.enjs.checkSubmissionFailed() ||
110
- this.defaultChange);
123
+ // If submission failed or donor manually changed away from default, respect current value
124
+ const submissionFailed = window.EngagingNetworks.require._defined.enjs.checkSubmissionFailed();
125
+ return !(submissionFailed || this.defaultChange);
111
126
  }
112
127
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const AppVersion = "0.22.13";
1
+ export declare const AppVersion = "0.22.15";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const AppVersion = "0.22.13";
1
+ export const AppVersion = "0.22.15";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4site/engrid-scripts",
3
- "version": "0.22.13",
3
+ "version": "0.22.15",
4
4
  "description": "Scripts for the ENGRID Project",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "strongly-typed-events": "^2.0.9"
26
26
  },
27
27
  "homepage": "https://github.com/4site-interactive-studios/engrid#readme",
28
- "gitHead": "631db2b96fc7e475d6f85b926700d8a5fd1eb03b",
28
+ "gitHead": "d7b50bd7dee71910c245dc7508132b69ddcc6f91",
29
29
  "devDependencies": {
30
30
  "nodemon": "^2.0.20"
31
31
  }