@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 +50 -42
- 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 +50 -42
- 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 +4 -4
- 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
|
@@ -33,6 +33,7 @@ import { makeProduct } from './utilities/product';
|
|
|
33
33
|
import { getSavedCheckoutCustomer, saveCheckoutAddress } from './utilities/local_storage';
|
|
34
34
|
import { makeUser } from './utilities/user';
|
|
35
35
|
import { persistTestCheckoutFlag, readTestCheckoutFlag } from './utilities/test_checkout';
|
|
36
|
+
import { appendJobSourceToFormData, captureAndReadJobSource, cartNeedsSourceUpdate, jobSourceFieldsForApi } from './utilities/job_source';
|
|
36
37
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
37
38
|
var CartContext = /*#__PURE__*/createContext({
|
|
38
39
|
cart: {},
|
|
@@ -498,65 +499,56 @@ var CartProvider = function CartProvider(_ref) {
|
|
|
498
499
|
}
|
|
499
500
|
function _createCartAndCookie() {
|
|
500
501
|
_createCartAndCookie = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(callback) {
|
|
501
|
-
var isTest, _cart, created, cartJson, _t3;
|
|
502
|
+
var isTest, jobSource, _cart, query, created, cartJson, _t3;
|
|
502
503
|
return _regenerator().w(function (_context2) {
|
|
503
504
|
while (1) switch (_context2.p = _context2.n) {
|
|
504
505
|
case 0:
|
|
505
506
|
setFetchingCart(true);
|
|
506
507
|
_context2.p = 1;
|
|
507
508
|
isTest = readTestCheckoutFlag();
|
|
509
|
+
jobSource = jobSourceFieldsForApi(captureAndReadJobSource());
|
|
508
510
|
_cart = makeCart({
|
|
509
511
|
domain: {
|
|
510
512
|
id: domainId
|
|
511
513
|
},
|
|
512
514
|
isTest: isTest
|
|
513
515
|
}, true);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
516
|
+
query = [['embed', JSON.stringify(cartEmbed)], ['skip_rights', 'y']];
|
|
517
|
+
if (isTest) query.push(['is_test', 'true']);
|
|
518
518
|
_context2.n = 2;
|
|
519
519
|
return merchi.authenticatedFetch('/carts/', {
|
|
520
520
|
method: 'POST',
|
|
521
|
-
body: _cart.toFormData(),
|
|
522
|
-
query:
|
|
521
|
+
body: appendJobSourceToFormData(_cart.toFormData(), jobSource),
|
|
522
|
+
query: query
|
|
523
523
|
});
|
|
524
524
|
case 2:
|
|
525
525
|
created = _context2.v;
|
|
526
526
|
_cart.fromJson(created.cart);
|
|
527
|
-
_context2.n =
|
|
528
|
-
break;
|
|
529
|
-
case 3:
|
|
530
|
-
_context2.n = 4;
|
|
531
|
-
return _cart.create({
|
|
532
|
-
embed: cartEmbed
|
|
533
|
-
});
|
|
534
|
-
case 4:
|
|
535
|
-
_context2.n = 5;
|
|
527
|
+
_context2.n = 3;
|
|
536
528
|
return _cart.toJson();
|
|
537
|
-
case
|
|
529
|
+
case 3:
|
|
538
530
|
cartJson = _context2.v;
|
|
539
531
|
if (domainId) setCartCookie(Number(domainId), cartJson, undefined, isTest);
|
|
540
|
-
_context2.n =
|
|
532
|
+
_context2.n = 4;
|
|
541
533
|
return attachSavedClientIfNeeded(cartJson);
|
|
542
|
-
case
|
|
534
|
+
case 4:
|
|
543
535
|
cartJson = _context2.v;
|
|
544
536
|
setCart(_objectSpread({}, cartJson));
|
|
545
537
|
if (callback) callback();
|
|
546
|
-
_context2.n =
|
|
538
|
+
_context2.n = 6;
|
|
547
539
|
break;
|
|
548
|
-
case
|
|
549
|
-
_context2.p =
|
|
540
|
+
case 5:
|
|
541
|
+
_context2.p = 5;
|
|
550
542
|
_t3 = _context2.v;
|
|
551
543
|
alertError(_t3.errorMessage || _t3.message || 'Unable to fetch cart.');
|
|
552
|
-
case
|
|
553
|
-
_context2.p =
|
|
544
|
+
case 6:
|
|
545
|
+
_context2.p = 6;
|
|
554
546
|
setFetchingCart(false);
|
|
555
|
-
return _context2.f(
|
|
556
|
-
case
|
|
547
|
+
return _context2.f(6);
|
|
548
|
+
case 7:
|
|
557
549
|
return _context2.a(2);
|
|
558
550
|
}
|
|
559
|
-
}, _callee2, null, [[1,
|
|
551
|
+
}, _callee2, null, [[1, 5, 6, 7]]);
|
|
560
552
|
}));
|
|
561
553
|
return _createCartAndCookie.apply(this, arguments);
|
|
562
554
|
}
|
|
@@ -565,7 +557,7 @@ var CartProvider = function CartProvider(_ref) {
|
|
|
565
557
|
}
|
|
566
558
|
function _getCart() {
|
|
567
559
|
_getCart = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(cartIdAndToken) {
|
|
568
|
-
var id, _cart2, cartJson, isTest, _t4;
|
|
560
|
+
var id, _cart2, cartJson, isTest, jobSource, saved, _t4;
|
|
569
561
|
return _regenerator().w(function (_context3) {
|
|
570
562
|
while (1) switch (_context3.p = _context3.n) {
|
|
571
563
|
case 0:
|
|
@@ -593,36 +585,52 @@ var CartProvider = function CartProvider(_ref) {
|
|
|
593
585
|
case 4:
|
|
594
586
|
return _context3.a(2);
|
|
595
587
|
case 5:
|
|
596
|
-
|
|
588
|
+
jobSource = jobSourceFieldsForApi(captureAndReadJobSource());
|
|
589
|
+
if (!cartNeedsSourceUpdate(cartJson, jobSource)) {
|
|
597
590
|
_context3.n = 7;
|
|
598
591
|
break;
|
|
599
592
|
}
|
|
600
593
|
_context3.n = 6;
|
|
601
|
-
return
|
|
594
|
+
return merchi.authenticatedFetch("/carts/".concat(_cart2.id, "/"), {
|
|
595
|
+
method: 'PATCH',
|
|
596
|
+
body: appendJobSourceToFormData(_cart2.toFormData(), jobSource),
|
|
597
|
+
query: [['embed', JSON.stringify(cartEmbed)], ['skip_rights', 'y']]
|
|
598
|
+
});
|
|
602
599
|
case 6:
|
|
600
|
+
saved = _context3.v;
|
|
601
|
+
_cart2.fromJson(saved.cart);
|
|
602
|
+
cartJson = _cart2.toJson();
|
|
603
|
+
case 7:
|
|
604
|
+
if (!(stripeIsValidAndActive(cartJson) || isTest)) {
|
|
605
|
+
_context3.n = 9;
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
_context3.n = 8;
|
|
609
|
+
return attachSavedClientIfNeeded(cartJson);
|
|
610
|
+
case 8:
|
|
603
611
|
cartJson = _context3.v;
|
|
604
612
|
setCart(_objectSpread({}, cartJson));
|
|
605
|
-
_context3.n =
|
|
613
|
+
_context3.n = 10;
|
|
606
614
|
break;
|
|
607
|
-
case
|
|
615
|
+
case 9:
|
|
608
616
|
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.");
|
|
609
617
|
setCartSettingsInvalid(true);
|
|
610
|
-
case
|
|
611
|
-
_context3.n =
|
|
618
|
+
case 10:
|
|
619
|
+
_context3.n = 12;
|
|
612
620
|
break;
|
|
613
|
-
case
|
|
614
|
-
_context3.p =
|
|
621
|
+
case 11:
|
|
622
|
+
_context3.p = 11;
|
|
615
623
|
_t4 = _context3.v;
|
|
616
|
-
_context3.n =
|
|
624
|
+
_context3.n = 12;
|
|
617
625
|
return createCartAndCookie();
|
|
618
|
-
case
|
|
619
|
-
_context3.p =
|
|
626
|
+
case 12:
|
|
627
|
+
_context3.p = 12;
|
|
620
628
|
setFetchingCart(false);
|
|
621
|
-
return _context3.f(
|
|
622
|
-
case
|
|
629
|
+
return _context3.f(12);
|
|
630
|
+
case 13:
|
|
623
631
|
return _context3.a(2);
|
|
624
632
|
}
|
|
625
|
-
}, _callee3, null, [[1,
|
|
633
|
+
}, _callee3, null, [[1, 11, 12, 13]]);
|
|
626
634
|
}));
|
|
627
635
|
return _getCart.apply(this, arguments);
|
|
628
636
|
}
|
|
@@ -18,10 +18,10 @@ import { useForm } from "react-hook-form";
|
|
|
18
18
|
import InputsAddress from "./InputsAddress";
|
|
19
19
|
import { CheckoutContainer, InnerContainer } from "../components/containers";
|
|
20
20
|
import { Title } from "../components";
|
|
21
|
-
import { shipmentFormId } from "../utilities/shipment";
|
|
21
|
+
import { checkoutShipmentHydrationAction, shipmentFormId } from "../utilities/shipment";
|
|
22
22
|
import { faMapMarkerAlt } from "@fortawesome/free-solid-svg-icons";
|
|
23
23
|
import { useCartContext } from "../CartProvider";
|
|
24
|
-
import { useState } from "react";
|
|
24
|
+
import { useEffect, useRef, useState } from "react";
|
|
25
25
|
import { getCartCookieToken } from "../utilities/cookie";
|
|
26
26
|
import { makeAddress, sanitizeAddressFields } from "../utilities/address";
|
|
27
27
|
import { makeCart } from "../utilities/cart";
|
|
@@ -121,7 +121,9 @@ export function ActiveFormShipmentAddressAndNotes() {
|
|
|
121
121
|
cart = _useCartContext2.cart,
|
|
122
122
|
setCart = _useCartContext2.setCart,
|
|
123
123
|
setLoadingTotals = _useCartContext2.setLoadingTotals,
|
|
124
|
-
setActiveTabAndEditDisabled = _useCartContext2.setActiveTabAndEditDisabled
|
|
124
|
+
setActiveTabAndEditDisabled = _useCartContext2.setActiveTabAndEditDisabled,
|
|
125
|
+
updateCartShipmentAddress = _useCartContext2.updateCartShipmentAddress,
|
|
126
|
+
getCartShipmentOptions = _useCartContext2.getCartShipmentOptions;
|
|
125
127
|
var saved = getSavedCheckoutAddress(domainId);
|
|
126
128
|
var cartAddress = cart === null || cart === void 0 ? void 0 : cart.receiverAddress;
|
|
127
129
|
var receiverAddress = addressHasContent(cartAddress) ? cartAddress : saved.address || {};
|
|
@@ -130,6 +132,25 @@ export function ActiveFormShipmentAddressAndNotes() {
|
|
|
130
132
|
_useState2 = _slicedToArray(_useState, 2),
|
|
131
133
|
addressFieldsOpen = _useState2[0],
|
|
132
134
|
setAddressFieldsOpen = _useState2[1];
|
|
135
|
+
var hydratedShipmentOptionsRef = useRef(false);
|
|
136
|
+
useEffect(function () {
|
|
137
|
+
if (hydratedShipmentOptionsRef.current || !(cart !== null && cart !== void 0 && cart.id)) return;
|
|
138
|
+
var draft = getSavedCheckoutAddress(domainId);
|
|
139
|
+
var action = checkoutShipmentHydrationAction({
|
|
140
|
+
cartAddress: cart === null || cart === void 0 ? void 0 : cart.receiverAddress,
|
|
141
|
+
savedAddress: draft.address,
|
|
142
|
+
shipmentGroups: cart === null || cart === void 0 ? void 0 : cart.shipmentGroups
|
|
143
|
+
});
|
|
144
|
+
if (!action) return;
|
|
145
|
+
hydratedShipmentOptionsRef.current = true;
|
|
146
|
+
if (action === "apply-saved-address" && draft.address) {
|
|
147
|
+
updateCartShipmentAddress(draft.address);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (action === "refresh-quotes") {
|
|
151
|
+
getCartShipmentOptions === null || getCartShipmentOptions === void 0 || getCartShipmentOptions();
|
|
152
|
+
}
|
|
153
|
+
}, [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]);
|
|
133
154
|
function saveCartShipmentAddressAndGoToNextTab(_x) {
|
|
134
155
|
return _saveCartShipmentAddressAndGoToNextTab.apply(this, arguments);
|
|
135
156
|
}
|
package/esm/utilities/index.js
CHANGED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _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(r) { if (Array.isArray(r)) return r; }
|
|
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(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
8
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : 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(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, 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 o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
11
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
12
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
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
|
+
});
|