From 2a02fcca61869289a9f77f3895bfdd02e2d2d501 Mon Sep 17 00:00:00 2001 From: Tim Schultz Date: Tue, 8 Mar 2022 07:35:27 -0700 Subject: [PATCH 1/3] 10097 setup enhanced ecommerce 1 (#10270) * added add to cart events and GA4 tracking code * added begin checkout event * adding payment info analytics event * added purchase event * added cart removal event * added view_item event * added remove from cart events to buttons * linting * remove debug Co-authored-by: Tim Schultz --- app/assets/v2/js/cart-ethereum-polygon.js | 1 + app/assets/v2/js/cart-ethereum-zksync.js | 1 + app/assets/v2/js/cart.js | 76 +++++++++++++++++++ app/assets/v2/js/grants/_detail-component.js | 44 +++++++++++ app/assets/v2/js/grants/components.js | 28 +++++++ .../templates/grants/analytics_manager.html | 9 +++ app/grants/templates/grants/cart-vue.html | 2 +- .../templates/grants/detail/_index.html | 2 +- app/grants/templates/grants/explorer.html | 2 + 9 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 app/grants/templates/grants/analytics_manager.html diff --git a/app/assets/v2/js/cart-ethereum-polygon.js b/app/assets/v2/js/cart-ethereum-polygon.js index f2e3ba4c466..3f651eb9eb5 100644 --- a/app/assets/v2/js/cart-ethereum-polygon.js +++ b/app/assets/v2/js/cart-ethereum-polygon.js @@ -324,6 +324,7 @@ Vue.component('grantsCartEthereumPolygon', { }, async sendDonationTx(userAddress) { + appCart.$refs.cart.sendPaymentInfoEvent('polygon'); const bulkCheckoutAddressPolygon = this.getBulkCheckoutAddress(); // Get our donation inputs diff --git a/app/assets/v2/js/cart-ethereum-zksync.js b/app/assets/v2/js/cart-ethereum-zksync.js index 796c699f45e..d03d3c1d838 100644 --- a/app/assets/v2/js/cart-ethereum-zksync.js +++ b/app/assets/v2/js/cart-ethereum-zksync.js @@ -195,6 +195,7 @@ Vue.component('grantsCartEthereumZksync', { // Send a batch transfer based on donation inputs async checkoutWithZksync() { + appCart.$refs.cart.sendPaymentInfoEvent('zksync'); // Prompt web3 login if not connected if (!provider) { await onConnect(); diff --git a/app/assets/v2/js/cart.js b/app/assets/v2/js/cart.js index 97154d72af6..46387b0d339 100644 --- a/app/assets/v2/js/cart.js +++ b/app/assets/v2/js/cart.js @@ -48,6 +48,9 @@ Vue.component('grants-cart', { data: function() { return { // Checkout, shared + grantAnalyticsItems: [], + cartTotal: 0, + analyticsHash: null, selectedZcashPayment: 'taddress', optionsZcashPayment: [ { text: 'Wallet t-address', value: 'taddress' }, @@ -482,6 +485,22 @@ Vue.component('grants-cart', { }, methods: { + formatAnalyticsItems(grants) { + return grants.map((grant) => ({ + item_id: grant.grant_id, + item_name: grant.grant_title, + item_category: grant.clr_round_num, + item_brand: grant.grant_admin_address + })); + }, + setCartTotal(grants) { + let cartTotal = 0; + + grants.forEach((grant) => { + cartTotal += grant.grant_donation_amount; + }); + this.cartTotal = cartTotal; + }, // Array of objects containing all donations and associated data computeDonationInputs(destGitcoinAddress) { let isPolygon = destGitcoinAddress == gitcoinAddressPolygon; @@ -626,6 +645,18 @@ Vue.component('grants-cart', { return vm.isPolkadotExtInstalled; }); }, + sendPaymentInfoEvent(payment_type) { + if (this.grantData.length) { + const currency = this.grantData[0].grant_donation_currency; + + gtag('event', 'add_payment_info', { + currency, + value: this.cartTotal, + payment_type, + items: this.grantAnalyticsItems + }); + } + }, // When the cart-ethereum-zksync component is updated, it emits an event with new data as the // payload. This component listens for that event and uses the data to show the user details // and suggestions about their checkout (gas cost estimates and why zkSync may not be @@ -821,9 +852,28 @@ Vue.component('grants-cart', { updateCartData(e) { this.grantData = (e && e.detail && e.detail.list && e.detail.list) || []; update_cart_title(); + this.grantAnalyticsItems = this.formatAnalyticsItems(this.grantData); }, removeGrantFromCart(id) { + const removal = this.grantData.find(grant => grant.grant_id === id); + + if (removal) { + gtag('event', 'remove_from_cart', { + currency: this.selectedETHCartToken, + value: removal.grant_donation_amount, + items: [ + { + item_id: id, + item_name: removal.grant_title, + quantity: 1, + item_category: removal.clr_round_num, + item_brand: removal.grant_admin_address + } + ] + }); + } + CartData.removeIdFromCart(id); this.grantData = CartData.loadCart(); update_cart_title(); @@ -1004,6 +1054,7 @@ Vue.component('grants-cart', { // Must be called at the beginning of the standard L1 bulk checkout flow async initializeStandardCheckout() { + this.sendPaymentInfoEvent('eth'); // Prompt web3 login if not connected if (!provider) { await await onConnect(); @@ -1301,6 +1352,10 @@ Vue.component('grants-cart', { // If standard checkout, stretch it so there's one hash for each donation (required for `for` loop below) const txHashes = checkout_type === 'eth_zksync' ? this.formatZkSyncTx(txHash) : new Array(donations.length).fill(txHash[0]); + if (txHashes) { + this.analyticsHash = txHashes[0]; + } + // Configure template payload const saveSubscriptionPayload = { // Values that are constant for all donations @@ -1499,6 +1554,13 @@ Vue.component('grants-cart', { CartData.setCheckedOut(this.grantsByTenant); + gtag('event', 'purchase', { + currency: this.selectedETHCartToken, + transaction_id: this.analyticsHash, + value: this.cartTotal, + items: this.grantAnalyticsItems + }); + // Remove each grant from the cart which has just been checkout this.grantsByTenant.forEach((grant) => { CartData.removeIdFromCart(grant.grant_id); @@ -1754,6 +1816,7 @@ Vue.component('grants-cart', { } else { this.grantData = []; } + // Load needed scripts based on tenants this.setChainScripts(); @@ -1766,6 +1829,19 @@ Vue.component('grants-cart', { // Show user cart now this.isLoading = false; + if (grantData.length) { + const currency = grantData[0].grant_donation_currency; + + const cartTotal = this.setCartTotal(grantData); + const items = this.formatAnalyticsItems(grantData); + + vm.grantAnalyticsItems; + gtag('event', 'begin_checkout', { + currency, + value: cartTotal, + items + }); + } }, beforeDestroy() { diff --git a/app/assets/v2/js/grants/_detail-component.js b/app/assets/v2/js/grants/_detail-component.js index 697e3df2f46..09c8d91464e 100644 --- a/app/assets/v2/js/grants/_detail-component.js +++ b/app/assets/v2/js/grants/_detail-component.js @@ -31,10 +31,40 @@ Vue.mixin({ vm.$set(vm.grant, 'isInCart', true); CartData.addToCart(response.grant); + + gtag('event', 'add_to_cart', { + // value, currency are set when checking out, but required + value: 0, + currency: 'USD', + items: [ + { + item_id: vm.grant.id, + item_name: vm.grant.title, + item_category: vm.grant?.active_round_names?.toString(), + item_brand: vm.grant?.admin_profile?.handle, + quantity: 1 + } + ] + }); }, removeFromCart: function() { const vm = this; + gtag('event', 'remove_from_cart', { + // value, currency are set when checking out, but required + value: 0, + currency: 'USD', + items: [ + { + item_id: vm.grant.id, + item_name: vm.grant.title, + item_category: vm.grant?.active_round_names?.toString(), + item_brand: vm.grant?.admin_profile?.handle, + quantity: 1 + } + ] + }); + vm.$set(vm.grant, 'isInCart', false); CartData.removeIdFromCart(vm.grant.id); }, @@ -591,6 +621,20 @@ Vue.component('grant-details', { // watch for cartUpdates window.addEventListener('cartDataUpdated', vm.updateCartData); + + gtag('event', 'view_item', { + // currency and value are required items but value is not known until cart + currency: 'USD', + value: 0, + items: [ + { + item_id: vm.grant.id, + item_name: vm.grant.title, + item_category: vm.grant.clr_round_num, + item_brand: vm.grant.admin_address + } + ] + }); }, beforeDestroy() { const vm = this; diff --git a/app/assets/v2/js/grants/components.js b/app/assets/v2/js/grants/components.js index a43b70382a6..b5a47bfc0ed 100644 --- a/app/assets/v2/js/grants/components.js +++ b/app/assets/v2/js/grants/components.js @@ -85,10 +85,38 @@ Vue.component('grant-card', { vm.$set(vm.grant, 'isInCart', true); CartData.addToCart(response.grant); + gtag('event', 'add_to_cart', { + // value, currency are set when checking out, but required + currency: 'USD', + value: 0, + items: [ + { + item_id: vm.grant.id, + item_name: vm.grant.title, + item_category: vm.grant.active_round_names.toString(), + item_brand: vm.grant?.admin_profile?.handle, + quantity: 1 + } + ] + }); }, removeFromCart: function() { let vm = this; + gtag('event', 'remove_from_cart', { + // value, currency are set when checking out, but required + value: 0, + currency: 'USD', + items: [ + { + item_id: vm.grant.id, + item_name: vm.grant.title, + item_category: vm.grant.active_round_names.toString(), + item_brand: vm.grant?.admin_profile?.handle, + quantity: 1 + } + ] + }); vm.$set(vm.grant, 'isInCart', false); CartData.removeIdFromCart(vm.grant.id); }, diff --git a/app/grants/templates/grants/analytics_manager.html b/app/grants/templates/grants/analytics_manager.html new file mode 100644 index 00000000000..e745179e427 --- /dev/null +++ b/app/grants/templates/grants/analytics_manager.html @@ -0,0 +1,9 @@ + + + diff --git a/app/grants/templates/grants/cart-vue.html b/app/grants/templates/grants/cart-vue.html index 581738c9660..e928d35e208 100644 --- a/app/grants/templates/grants/cart-vue.html +++ b/app/grants/templates/grants/cart-vue.html @@ -40,7 +40,7 @@ integrity="sha384-3IaxTgktbWGiqPkr5oZQMM4H99ziYzoEUb6sznk03coGR2Cdf1r0I1GWPUL37iu8" crossorigin="anonymous"> - + {% include './analytics_manager.html' %} + {% include './analytics_manager.html' %} + From 0943df2e5bdb8685cd89e58c9c8548d38b901e74 Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Tue, 8 Mar 2022 20:44:52 +0530 Subject: [PATCH 2/3] remove trailing , (#10267) --- app/grants/templates/grants/landing/landing_qf_active.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/grants/templates/grants/landing/landing_qf_active.html b/app/grants/templates/grants/landing/landing_qf_active.html index 2f2db50c4e9..43f827337ac 100644 --- a/app/grants/templates/grants/landing/landing_qf_active.html +++ b/app/grants/templates/grants/landing/landing_qf_active.html @@ -47,8 +47,7 @@

{% trans 'Create a Grant' %}

- {% trans "Join the funder's league" %}, - + {% trans "Join the funder's league" %}

From ebe81feb017c043d544b6d0c7935d941ab7f9599 Mon Sep 17 00:00:00 2001 From: nutrina Date: Tue, 8 Mar 2022 23:03:41 +0200 Subject: [PATCH 3/3] Have created a custom CSRF failure page (#10275) --- app/app/settings.py | 1 + app/retail/views.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/app/settings.py b/app/app/settings.py index 202c16ad23a..6b482ef3c4a 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -573,6 +573,7 @@ def callback(request): CSRF_COOKIE_SECURE = env.bool('CSRF_COOKIE_SECURE', default=False) CSRF_COOKIE_HTTPONLY = env.bool('CSRF_COOKIE_HTTPONLY', default=True) +CSRF_FAILURE_VIEW = 'retail.views.csrf_failure' SESSION_COOKIE_SECURE = env.bool('SESSION_COOKIE_SECURE', default=False) SECURE_BROWSER_XSS_FILTER = env.bool('SECURE_BROWSER_XSS_FILTER', default=True) SECURE_CONTENT_TYPE_NOSNIFF = env.bool('SECURE_CONTENT_TYPE_NOSNIFF', default=True) diff --git a/app/retail/views.py b/app/retail/views.py index de65643d88c..9fd7019f12c 100644 --- a/app/retail/views.py +++ b/app/retail/views.py @@ -1153,11 +1153,12 @@ def presskit(request): def handler403(request, exception=None): return error(request, 403) +def csrf_failure(request, reason=""): + return error(request, 403) def handler404(request, exception=None): return error(request, 404) - def handler500(request, exception=None): return error(request, 500)