merchi_cart 1.4.5 → 1.4.7
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.
- package/esm/CartProvider.js +52 -44
- package/esm/forms/FormShipmentAddressAndNotes.js +24 -3
- package/esm/utilities/index.js +1 -0
- package/esm/utilities/job_source.js +139 -0
- package/esm/utilities/job_source.test.js +75 -0
- package/esm/utilities/shipment.js +22 -0
- package/esm/utilities/shipment.test.js +60 -0
- package/lib/CartProvider.js +52 -44
- package/lib/forms/FormShipmentAddressAndNotes.js +22 -1
- package/lib/utilities/index.d.ts +1 -0
- package/lib/utilities/index.js +11 -0
- package/lib/utilities/job_source.d.ts +23 -0
- package/lib/utilities/job_source.js +152 -0
- package/lib/utilities/job_source.test.d.ts +1 -0
- package/lib/utilities/shipment.d.ts +15 -0
- package/lib/utilities/shipment.js +24 -0
- package/lib/utilities/shipment.test.d.ts +1 -0
- package/package.json +1 -1
- package/src/CartProvider.tsx +31 -14
- package/src/forms/FormShipmentAddressAndNotes.tsx +34 -2
- package/src/utilities/index.ts +1 -0
- package/src/utilities/job_source.test.ts +86 -0
- package/src/utilities/job_source.ts +165 -0
- package/src/utilities/shipment.test.ts +68 -0
- package/src/utilities/shipment.ts +34 -0
package/esm/CartProvider.js
CHANGED
|
@@ -32,6 +32,7 @@ import { makeProduct } from './utilities/product';
|
|
|
32
32
|
import { getSavedCheckoutCustomer, saveCheckoutAddress } from './utilities/local_storage';
|
|
33
33
|
import { makeUser } from './utilities/user';
|
|
34
34
|
import { persistTestCheckoutFlag, readTestCheckoutFlag } from './utilities/test_checkout';
|
|
35
|
+
import { appendJobSourceToFormData, captureAndReadJobSource, cartNeedsSourceUpdate, jobSourceFieldsForApi } from './utilities/job_source';
|
|
35
36
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
36
37
|
var CartContext = /*#__PURE__*/createContext({
|
|
37
38
|
cart: {},
|
|
@@ -500,66 +501,57 @@ var CartProvider = function CartProvider(_ref) {
|
|
|
500
501
|
}
|
|
501
502
|
function _createCartAndCookie() {
|
|
502
503
|
_createCartAndCookie = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(callback) {
|
|
503
|
-
var isTest, _cart, created, _cartJson;
|
|
504
|
+
var isTest, jobSource, _cart, query, created, _cartJson;
|
|
504
505
|
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
|
|
505
506
|
while (1) switch (_context2.prev = _context2.next) {
|
|
506
507
|
case 0:
|
|
507
508
|
setFetchingCart(true);
|
|
508
509
|
_context2.prev = 1;
|
|
509
510
|
isTest = readTestCheckoutFlag();
|
|
511
|
+
jobSource = jobSourceFieldsForApi(captureAndReadJobSource());
|
|
510
512
|
_cart = makeCart({
|
|
511
513
|
domain: {
|
|
512
514
|
id: domainId
|
|
513
515
|
},
|
|
514
516
|
isTest: isTest
|
|
515
517
|
}, true);
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
_context2.next = 7;
|
|
518
|
+
query = [['embed', JSON.stringify(cartEmbed)], ['skip_rights', 'y']];
|
|
519
|
+
if (isTest) query.push(['is_test', 'true']);
|
|
520
|
+
_context2.next = 9;
|
|
521
521
|
return merchi.authenticatedFetch('/carts/', {
|
|
522
522
|
method: 'POST',
|
|
523
|
-
body: _cart.toFormData(),
|
|
524
|
-
query:
|
|
523
|
+
body: appendJobSourceToFormData(_cart.toFormData(), jobSource),
|
|
524
|
+
query: query
|
|
525
525
|
});
|
|
526
|
-
case
|
|
526
|
+
case 9:
|
|
527
527
|
created = _context2.sent;
|
|
528
528
|
_cart.fromJson(created.cart);
|
|
529
529
|
_context2.next = 13;
|
|
530
|
-
break;
|
|
531
|
-
case 11:
|
|
532
|
-
_context2.next = 13;
|
|
533
|
-
return _cart.create({
|
|
534
|
-
embed: cartEmbed
|
|
535
|
-
});
|
|
536
|
-
case 13:
|
|
537
|
-
_context2.next = 15;
|
|
538
530
|
return _cart.toJson();
|
|
539
|
-
case
|
|
531
|
+
case 13:
|
|
540
532
|
_cartJson = _context2.sent;
|
|
541
533
|
if (domainId) setCartCookie(Number(domainId), _cartJson, undefined, isTest);
|
|
542
|
-
_context2.next =
|
|
534
|
+
_context2.next = 17;
|
|
543
535
|
return attachSavedClientIfNeeded(_cartJson);
|
|
544
|
-
case
|
|
536
|
+
case 17:
|
|
545
537
|
_cartJson = _context2.sent;
|
|
546
538
|
setCart(_objectSpread({}, _cartJson));
|
|
547
539
|
if (callback) callback();
|
|
548
|
-
_context2.next =
|
|
540
|
+
_context2.next = 25;
|
|
549
541
|
break;
|
|
550
|
-
case
|
|
551
|
-
_context2.prev =
|
|
542
|
+
case 22:
|
|
543
|
+
_context2.prev = 22;
|
|
552
544
|
_context2.t0 = _context2["catch"](1);
|
|
553
545
|
alertError(_context2.t0.errorMessage || _context2.t0.message || 'Unable to fetch cart.');
|
|
554
|
-
case
|
|
555
|
-
_context2.prev =
|
|
546
|
+
case 25:
|
|
547
|
+
_context2.prev = 25;
|
|
556
548
|
setFetchingCart(false);
|
|
557
|
-
return _context2.finish(
|
|
558
|
-
case
|
|
549
|
+
return _context2.finish(25);
|
|
550
|
+
case 28:
|
|
559
551
|
case "end":
|
|
560
552
|
return _context2.stop();
|
|
561
553
|
}
|
|
562
|
-
}, _callee2, null, [[1,
|
|
554
|
+
}, _callee2, null, [[1, 22, 25, 28]]);
|
|
563
555
|
}));
|
|
564
556
|
return _createCartAndCookie.apply(this, arguments);
|
|
565
557
|
}
|
|
@@ -568,7 +560,7 @@ var CartProvider = function CartProvider(_ref) {
|
|
|
568
560
|
}
|
|
569
561
|
function _getCart() {
|
|
570
562
|
_getCart = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(cartIdAndToken) {
|
|
571
|
-
var id, _cart2, _cartJson2, isTest;
|
|
563
|
+
var id, _cart2, _cartJson2, isTest, jobSource, saved;
|
|
572
564
|
return _regeneratorRuntime().wrap(function _callee3$(_context3) {
|
|
573
565
|
while (1) switch (_context3.prev = _context3.next) {
|
|
574
566
|
case 0:
|
|
@@ -596,37 +588,53 @@ var CartProvider = function CartProvider(_ref) {
|
|
|
596
588
|
case 14:
|
|
597
589
|
return _context3.abrupt("return");
|
|
598
590
|
case 15:
|
|
599
|
-
|
|
591
|
+
jobSource = jobSourceFieldsForApi(captureAndReadJobSource());
|
|
592
|
+
if (!cartNeedsSourceUpdate(_cartJson2, jobSource)) {
|
|
600
593
|
_context3.next = 22;
|
|
601
594
|
break;
|
|
602
595
|
}
|
|
603
|
-
_context3.next =
|
|
596
|
+
_context3.next = 19;
|
|
597
|
+
return merchi.authenticatedFetch("/carts/".concat(_cart2.id, "/"), {
|
|
598
|
+
method: 'PATCH',
|
|
599
|
+
body: appendJobSourceToFormData(_cart2.toFormData(), jobSource),
|
|
600
|
+
query: [['embed', JSON.stringify(cartEmbed)], ['skip_rights', 'y']]
|
|
601
|
+
});
|
|
602
|
+
case 19:
|
|
603
|
+
saved = _context3.sent;
|
|
604
|
+
_cart2.fromJson(saved.cart);
|
|
605
|
+
_cartJson2 = _cart2.toJson();
|
|
606
|
+
case 22:
|
|
607
|
+
if (!(stripeIsValidAndActive(_cartJson2) || isTest)) {
|
|
608
|
+
_context3.next = 29;
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
_context3.next = 25;
|
|
604
612
|
return attachSavedClientIfNeeded(_cartJson2);
|
|
605
|
-
case
|
|
613
|
+
case 25:
|
|
606
614
|
_cartJson2 = _context3.sent;
|
|
607
615
|
setCart(_objectSpread({}, _cartJson2));
|
|
608
|
-
_context3.next =
|
|
616
|
+
_context3.next = 31;
|
|
609
617
|
break;
|
|
610
|
-
case
|
|
618
|
+
case 29:
|
|
611
619
|
console.error("MErhci cart error: Stripe payment " + "options have not been correctly set up. " + "Check the company profile payment options tab " + "to set and edit stripe payment options.");
|
|
612
620
|
setCartSettingsInvalid(true);
|
|
613
|
-
case
|
|
614
|
-
_context3.next =
|
|
621
|
+
case 31:
|
|
622
|
+
_context3.next = 37;
|
|
615
623
|
break;
|
|
616
|
-
case
|
|
617
|
-
_context3.prev =
|
|
624
|
+
case 33:
|
|
625
|
+
_context3.prev = 33;
|
|
618
626
|
_context3.t0 = _context3["catch"](3);
|
|
619
|
-
_context3.next =
|
|
627
|
+
_context3.next = 37;
|
|
620
628
|
return createCartAndCookie();
|
|
621
|
-
case
|
|
622
|
-
_context3.prev =
|
|
629
|
+
case 37:
|
|
630
|
+
_context3.prev = 37;
|
|
623
631
|
setFetchingCart(false);
|
|
624
|
-
return _context3.finish(
|
|
625
|
-
case
|
|
632
|
+
return _context3.finish(37);
|
|
633
|
+
case 40:
|
|
626
634
|
case "end":
|
|
627
635
|
return _context3.stop();
|
|
628
636
|
}
|
|
629
|
-
}, _callee3, null, [[3,
|
|
637
|
+
}, _callee3, null, [[3, 33, 37, 40]]);
|
|
630
638
|
}));
|
|
631
639
|
return _getCart.apply(this, arguments);
|
|
632
640
|
}
|
|
@@ -17,10 +17,10 @@ import { useForm } from "react-hook-form";
|
|
|
17
17
|
import InputsAddress from "./InputsAddress";
|
|
18
18
|
import { CheckoutContainer, InnerContainer } from "../components/containers";
|
|
19
19
|
import { Title } from "../components";
|
|
20
|
-
import { shipmentFormId } from "../utilities/shipment";
|
|
20
|
+
import { checkoutShipmentHydrationAction, shipmentFormId } from "../utilities/shipment";
|
|
21
21
|
import { faMapMarkerAlt } from "@fortawesome/free-solid-svg-icons";
|
|
22
22
|
import { useCartContext } from "../CartProvider";
|
|
23
|
-
import { useState } from "react";
|
|
23
|
+
import { useEffect, useRef, useState } from "react";
|
|
24
24
|
import { getCartCookieToken } from "../utilities/cookie";
|
|
25
25
|
import { makeAddress, sanitizeAddressFields } from "../utilities/address";
|
|
26
26
|
import { makeCart } from "../utilities/cart";
|
|
@@ -122,7 +122,9 @@ export function ActiveFormShipmentAddressAndNotes() {
|
|
|
122
122
|
cart = _useCartContext2.cart,
|
|
123
123
|
setCart = _useCartContext2.setCart,
|
|
124
124
|
setLoadingTotals = _useCartContext2.setLoadingTotals,
|
|
125
|
-
setActiveTabAndEditDisabled = _useCartContext2.setActiveTabAndEditDisabled
|
|
125
|
+
setActiveTabAndEditDisabled = _useCartContext2.setActiveTabAndEditDisabled,
|
|
126
|
+
updateCartShipmentAddress = _useCartContext2.updateCartShipmentAddress,
|
|
127
|
+
getCartShipmentOptions = _useCartContext2.getCartShipmentOptions;
|
|
126
128
|
var saved = getSavedCheckoutAddress(domainId);
|
|
127
129
|
var cartAddress = cart === null || cart === void 0 ? void 0 : cart.receiverAddress;
|
|
128
130
|
var receiverAddress = addressHasContent(cartAddress) ? cartAddress : saved.address || {};
|
|
@@ -131,6 +133,25 @@ export function ActiveFormShipmentAddressAndNotes() {
|
|
|
131
133
|
_useState2 = _slicedToArray(_useState, 2),
|
|
132
134
|
addressFieldsOpen = _useState2[0],
|
|
133
135
|
setAddressFieldsOpen = _useState2[1];
|
|
136
|
+
var hydratedShipmentOptionsRef = useRef(false);
|
|
137
|
+
useEffect(function () {
|
|
138
|
+
if (hydratedShipmentOptionsRef.current || !(cart !== null && cart !== void 0 && cart.id)) return;
|
|
139
|
+
var draft = getSavedCheckoutAddress(domainId);
|
|
140
|
+
var action = checkoutShipmentHydrationAction({
|
|
141
|
+
cartAddress: cart === null || cart === void 0 ? void 0 : cart.receiverAddress,
|
|
142
|
+
savedAddress: draft.address,
|
|
143
|
+
shipmentGroups: cart === null || cart === void 0 ? void 0 : cart.shipmentGroups
|
|
144
|
+
});
|
|
145
|
+
if (!action) return;
|
|
146
|
+
hydratedShipmentOptionsRef.current = true;
|
|
147
|
+
if (action === "apply-saved-address" && draft.address) {
|
|
148
|
+
updateCartShipmentAddress(draft.address);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (action === "refresh-quotes") {
|
|
152
|
+
getCartShipmentOptions === null || getCartShipmentOptions === void 0 || getCartShipmentOptions();
|
|
153
|
+
}
|
|
154
|
+
}, [cart === null || cart === void 0 ? void 0 : cart.id, cart === null || cart === void 0 ? void 0 : cart.receiverAddress, cart === null || cart === void 0 ? void 0 : cart.shipmentGroups, domainId, getCartShipmentOptions, updateCartShipmentAddress]);
|
|
134
155
|
function saveCartShipmentAddressAndGoToNextTab(_x) {
|
|
135
156
|
return _saveCartShipmentAddressAndGoToNextTab.apply(this, arguments);
|
|
136
157
|
}
|
package/esm/utilities/index.js
CHANGED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
2
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
3
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
4
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
5
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
6
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
7
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
8
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
9
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
10
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
|
|
11
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
12
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
13
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
14
|
+
export var JOB_SOURCE_STORAGE_KEY = 'merchi_job_source';
|
|
15
|
+
var CLICK_ID_PARAMS = ['gclid', 'gbraid', 'wbraid', 'fbclid', 'msclkid'];
|
|
16
|
+
var UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
|
|
17
|
+
function trimTo(value, limit) {
|
|
18
|
+
if (!value) return undefined;
|
|
19
|
+
var text = value.trim().slice(0, limit);
|
|
20
|
+
return text || undefined;
|
|
21
|
+
}
|
|
22
|
+
function readSearchParams() {
|
|
23
|
+
if (typeof window === 'undefined') return new URLSearchParams();
|
|
24
|
+
try {
|
|
25
|
+
return new URLSearchParams(window.location.search);
|
|
26
|
+
} catch (_unused) {
|
|
27
|
+
return new URLSearchParams();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function readStoredSource() {
|
|
31
|
+
if (typeof window === 'undefined') return null;
|
|
32
|
+
try {
|
|
33
|
+
var raw = window.localStorage.getItem(JOB_SOURCE_STORAGE_KEY);
|
|
34
|
+
if (!raw) return null;
|
|
35
|
+
var parsed = JSON.parse(raw);
|
|
36
|
+
if (!parsed || _typeof(parsed) !== 'object') return null;
|
|
37
|
+
return parsed;
|
|
38
|
+
} catch (_unused2) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeStoredSource(source) {
|
|
43
|
+
if (typeof window === 'undefined') return;
|
|
44
|
+
try {
|
|
45
|
+
window.localStorage.setItem(JOB_SOURCE_STORAGE_KEY, JSON.stringify(source));
|
|
46
|
+
} catch (_unused3) {
|
|
47
|
+
// Ignore quota / private-mode failures.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function currentLanding() {
|
|
51
|
+
if (typeof window === 'undefined') return undefined;
|
|
52
|
+
try {
|
|
53
|
+
var path = "".concat(window.location.pathname || '/').concat(window.location.search || '');
|
|
54
|
+
return trimTo(path, 512);
|
|
55
|
+
} catch (_unused4) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function currentReferrer() {
|
|
60
|
+
if (typeof document === 'undefined') return undefined;
|
|
61
|
+
try {
|
|
62
|
+
return trimTo(document.referrer, 512);
|
|
63
|
+
} catch (_unused5) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function sourceFromLocation() {
|
|
68
|
+
var params = readSearchParams();
|
|
69
|
+
var source = {
|
|
70
|
+
utmSource: trimTo(params.get('utm_source'), 256),
|
|
71
|
+
utmMedium: trimTo(params.get('utm_medium'), 256),
|
|
72
|
+
utmCampaign: trimTo(params.get('utm_campaign'), 256),
|
|
73
|
+
utmContent: trimTo(params.get('utm_content'), 256),
|
|
74
|
+
utmTerm: trimTo(params.get('utm_term'), 256),
|
|
75
|
+
sourceLanding: currentLanding(),
|
|
76
|
+
sourceReferrer: currentReferrer()
|
|
77
|
+
};
|
|
78
|
+
var _iterator = _createForOfIteratorHelper(CLICK_ID_PARAMS),
|
|
79
|
+
_step;
|
|
80
|
+
try {
|
|
81
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
82
|
+
var key = _step.value;
|
|
83
|
+
var value = trimTo(params.get(key), 256);
|
|
84
|
+
if (value) {
|
|
85
|
+
source.sourceClickId = value;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
_iterator.e(err);
|
|
91
|
+
} finally {
|
|
92
|
+
_iterator.f();
|
|
93
|
+
}
|
|
94
|
+
return source;
|
|
95
|
+
}
|
|
96
|
+
export function hasPaidClick(source) {
|
|
97
|
+
return Boolean(source === null || source === void 0 ? void 0 : source.sourceClickId);
|
|
98
|
+
}
|
|
99
|
+
export function hasSourceData(source) {
|
|
100
|
+
if (!source) return false;
|
|
101
|
+
return Boolean(source.utmSource || source.utmMedium || source.utmCampaign || source.utmContent || source.utmTerm || source.sourceClickId || source.sourceLanding || source.sourceReferrer);
|
|
102
|
+
}
|
|
103
|
+
export function captureAndReadJobSource() {
|
|
104
|
+
var incoming = sourceFromLocation();
|
|
105
|
+
var stored = readStoredSource();
|
|
106
|
+
if (hasPaidClick(incoming) || !hasSourceData(stored)) {
|
|
107
|
+
var next = _objectSpread(_objectSpread({}, stored || {}), Object.fromEntries(Object.entries(incoming).filter(function (_ref) {
|
|
108
|
+
var _ref2 = _slicedToArray(_ref, 2),
|
|
109
|
+
value = _ref2[1];
|
|
110
|
+
return Boolean(value);
|
|
111
|
+
})));
|
|
112
|
+
if (hasSourceData(next)) writeStoredSource(next);
|
|
113
|
+
return next;
|
|
114
|
+
}
|
|
115
|
+
return stored || incoming;
|
|
116
|
+
}
|
|
117
|
+
export function jobSourceFieldsForApi(source) {
|
|
118
|
+
var resolved = source || captureAndReadJobSource();
|
|
119
|
+
var out = {};
|
|
120
|
+
Object.keys(resolved).forEach(function (key) {
|
|
121
|
+
var value = resolved[key];
|
|
122
|
+
if (value) out[key] = value;
|
|
123
|
+
});
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
export function cartNeedsSourceUpdate(cart, source) {
|
|
127
|
+
if (!hasSourceData(source)) return false;
|
|
128
|
+
if (!(cart !== null && cart !== void 0 && cart.utmSource) && !(cart !== null && cart !== void 0 && cart.sourceClickId) && !(cart !== null && cart !== void 0 && cart.sourceChannel)) return true;
|
|
129
|
+
return Boolean(source.sourceClickId && !(cart !== null && cart !== void 0 && cart.sourceClickId));
|
|
130
|
+
}
|
|
131
|
+
export var JOB_SOURCE_URL_KEYS = [].concat(UTM_PARAMS, CLICK_ID_PARAMS);
|
|
132
|
+
export function appendJobSourceToFormData(data, source) {
|
|
133
|
+
var fields = jobSourceFieldsForApi(source);
|
|
134
|
+
Object.keys(fields).forEach(function (key) {
|
|
135
|
+
var value = fields[key];
|
|
136
|
+
if (value) data.append(key, value);
|
|
137
|
+
});
|
|
138
|
+
return data;
|
|
139
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { JOB_SOURCE_STORAGE_KEY, captureAndReadJobSource, cartNeedsSourceUpdate, hasPaidClick } from './job_source';
|
|
2
|
+
describe('job source capture', function () {
|
|
3
|
+
var originalLocation = window.location;
|
|
4
|
+
var originalReferrer = document.referrer;
|
|
5
|
+
beforeEach(function () {
|
|
6
|
+
window.localStorage.clear();
|
|
7
|
+
delete window.location;
|
|
8
|
+
window.location = {
|
|
9
|
+
search: '',
|
|
10
|
+
pathname: '/tyvek-wristbands'
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(document, 'referrer', {
|
|
13
|
+
configurable: true,
|
|
14
|
+
value: ''
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
afterEach(function () {
|
|
18
|
+
window.location = originalLocation;
|
|
19
|
+
Object.defineProperty(document, 'referrer', {
|
|
20
|
+
configurable: true,
|
|
21
|
+
value: originalReferrer
|
|
22
|
+
});
|
|
23
|
+
window.localStorage.clear();
|
|
24
|
+
});
|
|
25
|
+
it('captures google ads click ids and landing path', function () {
|
|
26
|
+
window.location = {
|
|
27
|
+
search: '?utm_source=google&utm_medium=cpc&gclid=EAIa&utm_campaign=bands',
|
|
28
|
+
pathname: '/tyvek-wristbands'
|
|
29
|
+
};
|
|
30
|
+
var source = captureAndReadJobSource();
|
|
31
|
+
expect(source.utmSource).toBe('google');
|
|
32
|
+
expect(source.utmMedium).toBe('cpc');
|
|
33
|
+
expect(source.utmCampaign).toBe('bands');
|
|
34
|
+
expect(source.sourceClickId).toBe('EAIa');
|
|
35
|
+
expect(source.sourceLanding).toBe('/tyvek-wristbands?utm_source=google&utm_medium=cpc&gclid=EAIa&utm_campaign=bands');
|
|
36
|
+
expect(hasPaidClick(source)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
it('keeps first-touch UTMs unless a paid click arrives later', function () {
|
|
39
|
+
window.location = {
|
|
40
|
+
search: '?utm_source=newsletter&utm_medium=email',
|
|
41
|
+
pathname: '/'
|
|
42
|
+
};
|
|
43
|
+
captureAndReadJobSource();
|
|
44
|
+
window.location = {
|
|
45
|
+
search: '?utm_source=google&utm_medium=cpc',
|
|
46
|
+
pathname: '/product'
|
|
47
|
+
};
|
|
48
|
+
expect(captureAndReadJobSource().utmSource).toBe('newsletter');
|
|
49
|
+
window.location = {
|
|
50
|
+
search: '?gclid=NEWCLICK&utm_source=google',
|
|
51
|
+
pathname: '/product'
|
|
52
|
+
};
|
|
53
|
+
expect(captureAndReadJobSource().sourceClickId).toBe('NEWCLICK');
|
|
54
|
+
expect(window.localStorage.getItem(JOB_SOURCE_STORAGE_KEY)).toContain('NEWCLICK');
|
|
55
|
+
});
|
|
56
|
+
it('knows when an existing cart still needs source fields', function () {
|
|
57
|
+
expect(cartNeedsSourceUpdate({}, {
|
|
58
|
+
utmSource: 'google',
|
|
59
|
+
sourceClickId: 'x'
|
|
60
|
+
})).toBe(true);
|
|
61
|
+
expect(cartNeedsSourceUpdate({
|
|
62
|
+
utmSource: 'google'
|
|
63
|
+
}, {
|
|
64
|
+
utmSource: 'google',
|
|
65
|
+
sourceClickId: 'x'
|
|
66
|
+
})).toBe(true);
|
|
67
|
+
expect(cartNeedsSourceUpdate({
|
|
68
|
+
utmSource: 'google',
|
|
69
|
+
sourceClickId: 'x'
|
|
70
|
+
}, {
|
|
71
|
+
utmSource: 'google',
|
|
72
|
+
sourceClickId: 'x'
|
|
73
|
+
})).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { addressHasContent } from './local_storage';
|
|
1
2
|
export var shipmentFormId = '_shipment-address-form-id';
|
|
2
3
|
export function cartItemsNeedShipment(cart) {
|
|
3
4
|
var _cart$cartItems = cart.cartItems,
|
|
@@ -8,4 +9,25 @@ export function cartItemsNeedShipment(cart) {
|
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
return false;
|
|
12
|
+
}
|
|
13
|
+
export function shipmentGroupsHaveQuotes(groups) {
|
|
14
|
+
if (!(groups !== null && groups !== void 0 && groups.length)) return false;
|
|
15
|
+
return groups.some(function (group) {
|
|
16
|
+
return Array.isArray(group === null || group === void 0 ? void 0 : group.quotes) && group.quotes.length > 0;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* When checkout hydrates a saved address (localStorage) the form shows it,
|
|
21
|
+
* but quotes are only fetched after the user edits the field. Returning
|
|
22
|
+
* `apply-saved-address` means the cart still needs that address applied so
|
|
23
|
+
* quotes can be generated; `refresh-quotes` means the cart already has an
|
|
24
|
+
* address but no usable shipment options.
|
|
25
|
+
*/
|
|
26
|
+
export function checkoutShipmentHydrationAction(options) {
|
|
27
|
+
var hasCartAddress = addressHasContent(options.cartAddress);
|
|
28
|
+
var hasSavedAddress = addressHasContent(options.savedAddress);
|
|
29
|
+
var hasQuotes = shipmentGroupsHaveQuotes(options.shipmentGroups);
|
|
30
|
+
if (!hasCartAddress && hasSavedAddress) return 'apply-saved-address';
|
|
31
|
+
if (hasCartAddress && !hasQuotes) return 'refresh-quotes';
|
|
32
|
+
return null;
|
|
11
33
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { checkoutShipmentHydrationAction, shipmentGroupsHaveQuotes } from './shipment';
|
|
2
|
+
var savedAddress = {
|
|
3
|
+
lineOne: '32 Bridge St',
|
|
4
|
+
city: 'Brighton',
|
|
5
|
+
state: 'VIC',
|
|
6
|
+
postcode: '3186',
|
|
7
|
+
country: 'AU'
|
|
8
|
+
};
|
|
9
|
+
describe('shipmentGroupsHaveQuotes', function () {
|
|
10
|
+
it('is false when groups are missing or empty', function () {
|
|
11
|
+
expect(shipmentGroupsHaveQuotes(undefined)).toBe(false);
|
|
12
|
+
expect(shipmentGroupsHaveQuotes([])).toBe(false);
|
|
13
|
+
expect(shipmentGroupsHaveQuotes([{
|
|
14
|
+
quotes: []
|
|
15
|
+
}])).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it('is true when any group has quotes', function () {
|
|
18
|
+
expect(shipmentGroupsHaveQuotes([{
|
|
19
|
+
quotes: []
|
|
20
|
+
}, {
|
|
21
|
+
quotes: [{
|
|
22
|
+
id: 1
|
|
23
|
+
}]
|
|
24
|
+
}])).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('checkoutShipmentHydrationAction', function () {
|
|
28
|
+
it('applies a localStorage address when the cart has none', function () {
|
|
29
|
+
expect(checkoutShipmentHydrationAction({
|
|
30
|
+
cartAddress: {},
|
|
31
|
+
savedAddress: savedAddress,
|
|
32
|
+
shipmentGroups: []
|
|
33
|
+
})).toBe('apply-saved-address');
|
|
34
|
+
});
|
|
35
|
+
it('refreshes quotes when the cart already has an address but no options', function () {
|
|
36
|
+
expect(checkoutShipmentHydrationAction({
|
|
37
|
+
cartAddress: savedAddress,
|
|
38
|
+
savedAddress: savedAddress,
|
|
39
|
+
shipmentGroups: []
|
|
40
|
+
})).toBe('refresh-quotes');
|
|
41
|
+
});
|
|
42
|
+
it('does nothing when quotes are already available', function () {
|
|
43
|
+
expect(checkoutShipmentHydrationAction({
|
|
44
|
+
cartAddress: savedAddress,
|
|
45
|
+
savedAddress: savedAddress,
|
|
46
|
+
shipmentGroups: [{
|
|
47
|
+
quotes: [{
|
|
48
|
+
id: 9
|
|
49
|
+
}]
|
|
50
|
+
}]
|
|
51
|
+
})).toBe(null);
|
|
52
|
+
});
|
|
53
|
+
it('does nothing when neither cart nor localStorage has an address', function () {
|
|
54
|
+
expect(checkoutShipmentHydrationAction({
|
|
55
|
+
cartAddress: {},
|
|
56
|
+
savedAddress: {},
|
|
57
|
+
shipmentGroups: []
|
|
58
|
+
})).toBe(null);
|
|
59
|
+
});
|
|
60
|
+
});
|