@openeventkit/event-site 2.0.103 → 2.0.105

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/env.template CHANGED
@@ -33,3 +33,4 @@ GATSBY_METADATA_DESCRIPTION=
33
33
  GATSBY_CMS_BACKEND_REPO=
34
34
  GATSBY_CMS_BACKEND_BRANCH=
35
35
  GATSBY_SITE_URL=
36
+ GATSBY_GOOGLE_TAGMANAGER_ID=
package/gatsby-browser.js CHANGED
@@ -2,6 +2,9 @@ import * as Sentry from "@sentry/gatsby";
2
2
  import { RewriteFrames as RewriteFramesIntegration } from "@sentry/integrations";
3
3
  import ReduxWrapper from "./src/state/ReduxWrapper";
4
4
 
5
+ import AnalyticsManager from "./src/utils/analytics/AnalyticsManager";
6
+ import GoogleTagManagerProvider from "./src/utils/analytics/providers/GoogleTagManagerProvider";
7
+
5
8
  import smoothscroll from "smoothscroll-polyfill";
6
9
  import "what-input";
7
10
 
@@ -15,6 +18,9 @@ import marketingSettings from "data/marketing-settings.json";
15
18
  // smooth scroll polyfill needed for Safari
16
19
  smoothscroll.polyfill();
17
20
 
21
+ const googleTagManagerProvider = new GoogleTagManagerProvider();
22
+ const analyticsManager = new AnalyticsManager(googleTagManagerProvider);
23
+
18
24
  export const wrapRootElement = ReduxWrapper;
19
25
 
20
26
  export const onClientEntry = () => {
package/gatsby-config.js CHANGED
@@ -39,6 +39,16 @@ const manifestPlugin = faviconAsset ? [
39
39
  }
40
40
  ] : [];
41
41
 
42
+ const googleTagManagerPlugin = process.env.GATSBY_GOOGLE_TAGMANAGER_ID ? [
43
+ {
44
+ resolve: "gatsby-plugin-google-tagmanager",
45
+ options: {
46
+ id: process.env.GATSBY_GOOGLE_TAGMANAGER_ID,
47
+ includeInDevelopment: true
48
+ }
49
+ }
50
+ ] : [];
51
+
42
52
  const plugins = [
43
53
  ...manifestPlugin,
44
54
  {
@@ -177,6 +187,7 @@ const plugins = [
177
187
  }
178
188
  }
179
189
  },
190
+ ...googleTagManagerPlugin,
180
191
  "gatsby-plugin-netlify", // make sure to keep it last in the array
181
192
  ];
182
193
 
package/gatsby-node.js CHANGED
@@ -87,7 +87,8 @@ const SSR_getEvents = async (baseUrl, summitId, accessToken) => {
87
87
  access_token: accessToken,
88
88
  per_page: 50,
89
89
  page: 1,
90
- expand: "slides, links, videos, media_uploads, type, track, track.subtracks, track.allowed_access_levels, location, location.venue, location.floor, speakers, moderator, sponsors, current_attendance, groups, rsvp_template, tags",
90
+ expand: "slides,links,videos,media_uploads,type,track,track.subtracks,track.allowed_access_levels,location,location.venue,location.floor,speakers,moderator,sponsors,current_attendance,groups,rsvp_template,tags",
91
+ relations: "speakers.badge_features,speakers.affiliations,speakers.languages,speakers.other_presentation_links,speakers.areas_of_expertise,speakers.travel_preferences,speakers.organizational_roles,speakers.all_presentations,speakers.all_moderated_presentations",
91
92
  }
92
93
 
93
94
  return await axios.get(endpoint, { params }).then(async ({data}) => {
@@ -155,6 +156,7 @@ const SSR_getSpeakers = async (baseUrl, summitId, accessToken, filter = null) =>
155
156
  access_token: accessToken,
156
157
  per_page: 30,
157
158
  page: 1,
159
+ relations: 'badge_features,affiliations,languages,other_presentation_links,areas_of_expertise,travel_preferences,organizational_roles,all_presentations,all_moderated_presentations',
158
160
  };
159
161
 
160
162
  const endpoint = `${baseUrl}/api/v1/summits/${summitId}/speakers/on-schedule`;
@@ -180,7 +182,7 @@ const SSR_getSpeakers = async (baseUrl, summitId, accessToken, filter = null) =>
180
182
  const SSR_getSummit = async (baseUrl, summitId) => {
181
183
 
182
184
  const params = {
183
- expand: "event_types,tracks, tracks.subtracks,track_groups,presentation_levels,locations.rooms,locations.floors,order_extra_questions.values,schedule_settings,schedule_settings.filters,schedule_settings.pre_filters",
185
+ expand: "event_types,tracks,tracks.subtracks,track_groups,presentation_levels,locations.rooms,locations.floors,order_extra_questions.values,schedule_settings,schedule_settings.filters,schedule_settings.pre_filters",
184
186
  t: Date.now()
185
187
  };
186
188
 
@@ -201,7 +203,7 @@ const SSR_getVoteablePresentations = async (baseUrl, summitId, accessToken) => {
201
203
  per_page: 50,
202
204
  page: 1,
203
205
  filter: "published==1",
204
- expand: "slides, links, videos, media_uploads, type, track, track.allowed_access_levels, location, location.venue, location.floor, speakers, moderator, sponsors, current_attendance, groups, rsvp_template, tags",
206
+ expand: "slides,links,videos,media_uploads,type,track,track.allowed_access_levels,location,location.venue,location.floor,speakers,moderator,sponsors,current_attendance,groups,rsvp_template,tags",
205
207
  };
206
208
 
207
209
  return await axios.get(endpoint,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openeventkit/event-site",
3
3
  "description": "Event Site",
4
- "version": "2.0.103",
4
+ "version": "2.0.105",
5
5
  "author": "Tipit LLC",
6
6
  "dependencies": {
7
7
  "@mui/base": "^5.0.0-alpha.114",
@@ -46,6 +46,7 @@
46
46
  "full-schedule-widget": "3.0.5",
47
47
  "gatsby": "^5.8.1",
48
48
  "gatsby-alias-imports": "^1.0.6",
49
+ "gatsby-plugin-google-tagmanager": "^5.13.1",
49
50
  "gatsby-plugin-image": "^3.8.0",
50
51
  "gatsby-plugin-manifest": "^5.12.3",
51
52
  "gatsby-plugin-netlify": "^5.1.0",
@@ -77,7 +78,7 @@
77
78
  "moment-timezone": "^0.5.31",
78
79
  "netlify-cms-app": "^2.15.72",
79
80
  "netlify-cms-lib-widgets": "^1.8.0",
80
- "openstack-uicore-foundation": "4.1.65",
81
+ "openstack-uicore-foundation": "4.1.76",
81
82
  "path-browserify": "^1.0.1",
82
83
  "prop-types": "^15.6.0",
83
84
  "react": "^18.2.0",
@@ -121,7 +122,7 @@
121
122
  "stream-browserify": "^3.0.0",
122
123
  "stream-chat": "^2.7.2",
123
124
  "stream-chat-react": "3.1.7",
124
- "summit-registration-lite": "5.0.24",
125
+ "summit-registration-lite": "5.0.26",
125
126
  "superagent": "8.0.9",
126
127
  "sweetalert2": "^9.17.0",
127
128
  "upcoming-events-widget": "3.0.5",
@@ -148,12 +149,58 @@
148
149
  "build": "NODE_ENV=production NODE_OPTIONS=--max-old-space-size=10240 cross-env node --trace-warnings node_modules/.bin/gatsby build --log-pages",
149
150
  "develop": "NODE_OPTIONS=--max-old-space-size=8192 npm run gatsby-clean && node --trace-warnings node_modules/.bin/gatsby develop --S -H 0.0.0.0",
150
151
  "format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"{gatsby-*.js,src/**/*.js}\"",
151
- "test": "echo \"Error: no test specified\" && exit 1",
152
+ "test": "jest",
152
153
  "update-libs": "yarn upgrade live-event-widget openstack-uicore-foundation schedule-lite simple-chat-widget speakers-widget attendee-to-attendee-widget"
153
154
  },
154
155
  "devDependencies": {
156
+ "@testing-library/dom": "^10.0.0",
157
+ "@testing-library/jest-dom": "^6.4.2",
158
+ "@testing-library/react": "^15.0.2",
159
+ "@testing-library/user-event": "^14.5.2",
160
+ "babel-jest": "^29.7.0",
155
161
  "devcert": "1.1.0",
156
162
  "gatsby-plugin-webpack-speed-measure": "^0.1.1",
163
+ "identity-obj-proxy": "^3.0.0",
164
+ "jest": "^28.1.0",
165
+ "jest-environment-jsdom": "^28.1.0",
166
+ "jest-transform-stub": "^2.0.0",
167
+ "js-yaml": "^4.1.0",
168
+ "js-yaml-loader": "^1.2.2",
157
169
  "prettier": "^2.0.5"
170
+ },
171
+ "jest": {
172
+ "collectCoverageFrom": [
173
+ "src/**/*.{js,jsx,mjs}"
174
+ ],
175
+ "moduleNameMapper": {
176
+ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
177
+ "\\.(css|scss)$": "identity-obj-proxy"
178
+ },
179
+ "testMatch": [
180
+ "<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}",
181
+ "<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}"
182
+ ],
183
+ "transform": {
184
+ "\\.[jt]sx?$": "babel-jest",
185
+ ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub"
186
+ },
187
+ "moduleDirectories": [
188
+ "node_modules",
189
+ "src"
190
+ ],
191
+ "moduleFileExtensions": [
192
+ "web.js",
193
+ "js",
194
+ "json",
195
+ "web.jsx",
196
+ "jsx",
197
+ "node",
198
+ "mjs"
199
+ ],
200
+ "globals": {
201
+ "window": {},
202
+ "console": {}
203
+ },
204
+ "testEnvironment": "jsdom"
158
205
  }
159
206
  }
@@ -0,0 +1 @@
1
+ module.exports = {};
@@ -16,6 +16,8 @@ export const fetchEventById = async (summitId, eventId, accessToken = null) => {
16
16
 
17
17
  apiUrl.addQuery('expand', 'slides, links, videos, media_uploads, type, track, track.allowed_access_levels, location, location.venue, location.floor, speakers, moderator, sponsors, current_attendance, groups, rsvp_template, tags');
18
18
  apiUrl.addQuery('evict_cache', 1);
19
+ apiUrl.addQuery('relations', "speakers.badge_features,speakers.affiliations,speakers.languages,speakers.other_presentation_links,speakers.areas_of_expertise,speakers.travel_preferences,speakers.organizational_roles,speakers.all_presentations,speakers.all_moderated_presentations");
20
+
19
21
  return fetch(apiUrl.toString(), {
20
22
  method: 'GET'
21
23
  }).then(async (response) => {
@@ -73,6 +75,7 @@ export const fetchSpeakerById = async(summitId, speakerId, accessToken = null) =
73
75
  apiUrl.addQuery('access_token', accessToken);
74
76
  }
75
77
 
78
+ apiUrl.addQuery('relations', 'badge_features,affiliations,languages,other_presentation_links,areas_of_expertise,travel_preferences,organizational_roles,all_presentations,all_moderated_presentations');
76
79
  apiUrl.addQuery('evict_cache', 1);
77
80
 
78
81
  return fetch(apiUrl.toString(), {
@@ -176,6 +176,27 @@ const siteSettings = {
176
176
  }),
177
177
  ]
178
178
  }),
179
+ objectField({
180
+ label: "IDP Logo",
181
+ name: "idpLogo",
182
+ fields: [
183
+ imageField({
184
+ label: "Logo Dark",
185
+ name: "idpLogoDark",
186
+ required: false
187
+ }),
188
+ imageField({
189
+ label: "Logo Light",
190
+ name: "idpLogoLight",
191
+ required: false
192
+ }),
193
+ stringField({
194
+ label: "Logo Alt",
195
+ name: "idpLogoAlt",
196
+ required: false
197
+ }),
198
+ ]
199
+ }),
179
200
  listField({
180
201
  label: "Identity Provider Buttons",
181
202
  name: "identityProviderButtons",
@@ -21,6 +21,11 @@ module.exports = `
21
21
  schedule: Schedule
22
22
  chat: Chat
23
23
  }
24
+ type IdpLogo {
25
+ idpLogoDark: File @fileByRelativePath
26
+ idpLogoLight: File @fileByRelativePath
27
+ idpLogoAlt: String
28
+ }
24
29
  type IdentityProviderButton {
25
30
  buttonColor: String
26
31
  buttonBorderColor: String
@@ -33,6 +38,7 @@ module.exports = `
33
38
  siteMetadata: SiteMetadata
34
39
  favicon: Favicon
35
40
  widgets: Widgets
41
+ idpLogo: IdpLogo
36
42
  identityProviderButtons: [IdentityProviderButton]
37
43
  }
38
44
  `;
@@ -23,7 +23,10 @@ import { PHASES } from "../utils/phasesUtils";
23
23
  import "attendee-to-attendee-widget/dist/index.css";
24
24
 
25
25
  import { SentryFallbackFunction } from "./SentryErrorComponent";
26
- import { onInitLogoutEvent } from "../utils/customEvents";
26
+ import {
27
+ useCustomEvent,
28
+ INIT_LOGOUT_EVENT
29
+ } from "../utils/customEvents";
27
30
 
28
31
  const sbAuthProps = {
29
32
  supabaseUrl: getEnvVariable(SUPABASE_URL),
@@ -187,18 +190,15 @@ const AccessTracker = ({ user, isLoggedUser, summitPhase, chatSettings }) => {
187
190
  const trackerRef = useRef();
188
191
 
189
192
  const handleLogout = useCallback(() => {
190
- if(trackerRef.current)
193
+ if (trackerRef.current)
191
194
  trackerRef.current.signOut();
192
195
  },[]);
193
196
 
194
- useEffect(()=>{
195
- window.addEventListener(onInitLogoutEvent, handleLogout)
196
- return () => window.removeEventListener(onInitLogoutEvent, handleLogout);
197
- },[]);
197
+ useCustomEvent(INIT_LOGOUT_EVENT, handleLogout);
198
198
 
199
199
  useEffect(() => {
200
200
  if (!isLoggedUser) {
201
- if(trackerRef.current)
201
+ if (trackerRef.current)
202
202
  trackerRef.current.signOut();
203
203
  }
204
204
  }, [isLoggedUser]);
@@ -143,7 +143,7 @@ const AuthComponent = ({
143
143
  allowsOtpAuth: allowsOtpAuth,
144
144
  initialEmailValue: initialEmailValue,
145
145
  title: 'Sign in using the email associated with your account:',
146
- summitData: summit
146
+ summitData: summit,
147
147
  };
148
148
 
149
149
  const passwordlessLoginProps = {
@@ -158,6 +158,9 @@ const AuthComponent = ({
158
158
  codeError: otpError,
159
159
  goToLogin: () => setOtpLogin(false),
160
160
  getLoginCode: (email) => sendCode(email),
161
+ idpLogoLight: siteSettings?.idpLogo?.idpLogoLight?.publicURL,
162
+ idpLogoDark: siteSettings?.idpLogo?.idpLogoDark?.publicURL,
163
+ idpLogoAlt: siteSettings?.idpLogo?.idpLogoAlt
161
164
  }
162
165
 
163
166
  const { loginButton } = marketingPageSettings.hero.buttons;
@@ -242,4 +245,4 @@ AuthComponent.defaultProps = {
242
245
  AuthComponent.propTypes = {
243
246
  location: PropTypes.object.isRequired,
244
247
  ignoreAutoOpen: PropTypes.bool,
245
- }
248
+ }
@@ -25,6 +25,9 @@ import useMarketingSettings, { MARKETING_SETTINGS_KEYS } from "@utils/useMarket
25
25
  import useSiteSettings from "@utils/useSiteSettings";
26
26
  import { SentryFallbackFunction } from "./SentryErrorComponent";
27
27
 
28
+ import { triggerAnalyticsTrackEvent } from "@utils/customEvents";
29
+ import { PURCHASE_COMPLETE } from "@utils/analytics/events";
30
+
28
31
  import styles from "../styles/marketing-hero.module.scss";
29
32
 
30
33
  const RegistrationLiteComponent = ({
@@ -153,11 +156,12 @@ const RegistrationLiteComponent = ({
153
156
  goToRegistration: () => navigate(`${getEnvVariable(REGISTRATION_BASE_URL)}/a/${summit.slug}`),
154
157
  goToMyOrders: () => navigate("/a/my-tickets"),
155
158
  completedExtraQuestions: async (attendee) => {
156
- if(!attendee) return true;
159
+ if (!attendee) return true;
157
160
  await getExtraQuestions(attendee?.id);
158
161
  return checkRequireExtraQuestionsByAttendee(attendee);
159
162
  },
160
163
  onPurchaseComplete: (order) => {
164
+ triggerAnalyticsTrackEvent(PURCHASE_COMPLETE, { order });
161
165
  // check if it"s necessary to update profile
162
166
  setUserOrder(order).then(()=> checkOrderData(order));
163
167
  },
@@ -191,6 +195,9 @@ const RegistrationLiteComponent = ({
191
195
  noAllowedTicketsMessage: noAllowedTicketsMessage,
192
196
  showCompanyInput: showCompanyInput,
193
197
  showCompanyInputDefaultOptions: showCompanyInputDefaultOptions,
198
+ idpLogoLight: siteSettings?.idpLogo?.idpLogoLight?.publicURL,
199
+ idpLogoDark: siteSettings?.idpLogo?.idpLogoDark?.publicURL,
200
+ idpLogoAlt: siteSettings?.idpLogo?.idpLogoAlt
194
201
  };
195
202
 
196
203
  const { registerButton } = marketingPageSettings.hero.buttons;
@@ -13,6 +13,11 @@
13
13
  "favicon": {
14
14
  "asset": "icon.png"
15
15
  },
16
+ "idpLogo": {
17
+ "idpLogoDark": "",
18
+ "idpLogoLight": "",
19
+ "idpLogoAlt": ""
20
+ },
16
21
  "identityProviderButtons": [
17
22
  {
18
23
  "buttonColor": "#082238",
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ import React from "react";
5
+ import { normalizeData } from "../dataNormalization";
6
+
7
+ const testOrder = {
8
+ "id": 43545,
9
+ "created": 1713391444,
10
+ "last_edited": 1713391444,
11
+ "number": "ORDER_2023OCPGLOBALSUMMIT2023_662047548D72D773046574",
12
+ "status": "Paid",
13
+ "payment_method": "Online",
14
+ "owner_first_name": "Sebastian",
15
+ "owner_last_name": "Marcet",
16
+ "owner_email": "test@gmail.com",
17
+ "owner_company": "Tipit",
18
+ "owner_company_id": 3496,
19
+ "owner_id": 26391,
20
+ "summit_id": 50,
21
+ "currency": "USD",
22
+ "currency_symbol": "$",
23
+ "extra_questions": [],
24
+ "tickets": [{
25
+ "id": 50175,
26
+ "created": 1713391444,
27
+ "last_edited": 1713409486,
28
+ "number": "TICKET_2023OCPGLOBALSUMMIT2023_6620475493A78111038846",
29
+ "status": "Paid",
30
+ "external_order_id": null,
31
+ "external_attendee_id": null,
32
+ "bought_date": 1713391485,
33
+ "order_id": 43545,
34
+ "promo_code_id": 0,
35
+ "raw_cost": 900,
36
+ "net_selling_cost": 900,
37
+ "raw_cost_in_cents": 90000,
38
+ "final_amount": 900,
39
+ "final_amount_in_cents": 90000,
40
+ "discount": 0,
41
+ "discount_rate": 0,
42
+ "discount_in_cents": 0,
43
+ "refunded_amount": 0,
44
+ "refunded_amount_in_cents": 0,
45
+ "total_refunded_amount": 0,
46
+ "total_refunded_amount_in_cents": 0,
47
+ "currency": "USD",
48
+ "currency_symbol": "$",
49
+ "taxes_amount": 0,
50
+ "taxes_amount_in_cents": 0,
51
+ "is_active": true,
52
+ "qr_code": "TICKET_2023OCPGLOBALSUMMIT2023|TICKET_2023OCPGLOBALSUMMIT2023_6620475493A78111038846|smarcet@gmail.com",
53
+ "owner": {
54
+ "id": 28608,
55
+ "created": 1685574339,
56
+ "last_edited": 1713409445,
57
+ "summit_hall_checked_in": false,
58
+ "summit_hall_checked_in_date": null,
59
+ "summit_virtual_checked_in_date": null,
60
+ "shared_contact_info": false,
61
+ "member_id": 26391,
62
+ "summit_id": 50,
63
+ "first_name": "Sebastian",
64
+ "last_name": "Marcet",
65
+ "email": "test@gmail.com",
66
+ "company": "Tipit",
67
+ "company_id": 3496,
68
+ "disclaimer_accepted_date": null,
69
+ "disclaimer_accepted": false,
70
+ "status": "Incomplete",
71
+ "tickets": [50175],
72
+ "presentation_votes": [],
73
+ "votes_count": 0,
74
+ "ticket_types": [{"type_id": 109, "qty": 1, "type_name": "Standard Ticket"}],
75
+ "allowed_access_levels": [149],
76
+ "allowed_features": [],
77
+ "tags": [],
78
+ "extra_questions": []
79
+ },
80
+ "badge": {
81
+ "id": 50183,
82
+ "created": 1713391444,
83
+ "last_edited": 1713391444,
84
+ "print_date": null,
85
+ "qr_code": null,
86
+ "is_void": false,
87
+ "ticket_id": 50175,
88
+ "printed_times": 0,
89
+ "features": [],
90
+ "print_excerpt": [],
91
+ "type": {
92
+ "id": 112,
93
+ "created": 1685036153,
94
+ "last_edited": 1685036153,
95
+ "name": "General Attendee",
96
+ "description": "This is for all in-person general attendees. This Badge Type can have various Badge Features (icons/titles). Badge Features may be dictated by promo codes or can be added manually by Admins. The badge needs a QR code for LR. This badge needs T-Shirt Size Indicator from extra question answer (dots/dashes) on the design.",
97
+ "template_content": "",
98
+ "is_default": true,
99
+ "summit_id": 50,
100
+ "badge_features": [],
101
+ "allowed_view_types": [49],
102
+ "access_levels": [{
103
+ "id": 149,
104
+ "created": 1683216137,
105
+ "last_edited": 1683216137,
106
+ "name": "IN_PERSON",
107
+ "description": "Allows in person show access.",
108
+ "template_content": "",
109
+ "is_default": true,
110
+ "summit_id": 50
111
+ }]
112
+ }
113
+ },
114
+ "ticket_type": {
115
+ "id": 109,
116
+ "created": 1685036568,
117
+ "last_edited": 1713391444,
118
+ "name": "Standard Ticket",
119
+ "description": "This is for general admission. Needs QR Code on Badge. Symposium included (first come, first served until capacity is met). Early bird pricing ended 7/31/23. In-Person Ticket: Standard, is on sale 8/1/23- 10/15/23",
120
+ "external_id": null,
121
+ "summit_id": 50,
122
+ "cost": 900,
123
+ "currency": "USD",
124
+ "currency_symbol": "$",
125
+ "quantity_2_sell": 10000,
126
+ "max_quantity_per_order": 100,
127
+ "sales_start_date": 1690873200,
128
+ "sales_end_date": 1722581940,
129
+ "badge_type_id": 112,
130
+ "quantity_sold": 3289,
131
+ "audience": "All",
132
+ "applied_taxes": [],
133
+ "sub_type": "Regular"
134
+ },
135
+ "applied_taxes": [],
136
+ "refund_requests": []
137
+ }]
138
+ };
139
+
140
+ const testArrayOrderData = [testOrder];
141
+
142
+ describe("data normalization", () => {
143
+ test("remove email from order data", () => {
144
+ expect(testOrder.hasOwnProperty("owner_email")).toBeTruthy();
145
+ expect(testOrder.tickets[0].owner.hasOwnProperty("email") ).toBeTruthy();
146
+ const data = normalizeData(testOrder);
147
+ expect(data.hasOwnProperty("owner_email")).toBeFalsy();
148
+ expect(data.tickets[0].owner.hasOwnProperty("email") ).toBeFalsy();
149
+ });
150
+ test("remove qr from order data", () => {
151
+ expect(testOrder.tickets[0].hasOwnProperty("qr_code") ).toBeTruthy();
152
+ const data = normalizeData(testOrder);
153
+ expect(data.tickets[0].hasOwnProperty("qr_code") ).toBeFalsy();
154
+ });
155
+ test("remove qr from array of orders", () => {
156
+ expect(Array.isArray(testArrayOrderData)).toBeTruthy();
157
+ expect(testArrayOrderData[0].tickets[0].hasOwnProperty("qr_code") ).toBeTruthy();
158
+ const data = normalizeData(testArrayOrderData);
159
+ expect(Array.isArray(data)).toBeTruthy();
160
+ expect(data[0].tickets[0].hasOwnProperty("qr_code") ).toBeFalsy();
161
+ });
162
+ })
163
+
@@ -0,0 +1,28 @@
1
+ import AnalyticsProvider from "./AnalyticsProvider";
2
+ import { CustomEventManager, ANALYTICS_TRACK_EVENT } from "../customEvents";
3
+ import { normalizeData } from "@utils/dataNormalization";
4
+
5
+ class AnalyticsManager {
6
+ constructor(analyticsProvider) {
7
+ if (!(analyticsProvider instanceof AnalyticsProvider)) {
8
+ throw new Error("An instance of AnalyticsProvider is required.");
9
+ }
10
+ this.analyticsProvider = analyticsProvider;
11
+ CustomEventManager.addEventListener(ANALYTICS_TRACK_EVENT, this.handleTrackEvent.bind(this));
12
+ }
13
+
14
+ get provider() {
15
+ return this.analyticsProvider;
16
+ }
17
+
18
+ handleTrackEvent = (event) => {
19
+ const { eventName, eventParams } = event.detail;
20
+ this.trackEvent(eventName, eventParams);
21
+ }
22
+
23
+ trackEvent = (eventName, eventParams) => {
24
+ this.analyticsProvider.trackEvent(eventName, normalizeData(eventParams));
25
+ }
26
+ }
27
+
28
+ export default AnalyticsManager;
@@ -0,0 +1,7 @@
1
+ class AnalyticsProvider {
2
+ trackEvent(eventName, eventParams) {
3
+ throw new Error("trackEvent method must be implemented");
4
+ }
5
+ }
6
+
7
+ export default AnalyticsProvider;
@@ -0,0 +1 @@
1
+ export const PURCHASE_COMPLETE = "purchase_complete";
@@ -0,0 +1,38 @@
1
+ import AnalyticsProvider from "../AnalyticsProvider";
2
+ import { getEnvVariable, GOOGLE_TAGMANAGER_ID } from "@utils/envVariables";
3
+
4
+ class GoogleTagManagerProvider extends AnalyticsProvider {
5
+ constructor() {
6
+ super();
7
+ if (!getEnvVariable(GOOGLE_TAGMANAGER_ID)) {
8
+ console.warn("GoogleTagManagerProvider: GOOGLE_TAGMANAGER_ID environment variable is not set. Tracking will be disabled.");
9
+ }
10
+ this.dataLayer = (typeof window !== "undefined" && window.dataLayer) || [];
11
+ }
12
+
13
+ gtag() {
14
+ this.dataLayer.push(arguments);
15
+ };
16
+
17
+ trackEvent = (eventName, eventParams) => {
18
+ this.gtag("event", eventName, eventParams);
19
+ };
20
+
21
+ config = (targetId, additionalConfig) => {
22
+ this.gtag("config", targetId, additionalConfig);
23
+ };
24
+
25
+ set = (parameters) => {
26
+ this.gtag("set", parameters);
27
+ };
28
+
29
+ get = (target, fieldName, callback) => {
30
+ this.gtag("get", target, fieldName, callback);
31
+ };
32
+
33
+ consent = (consentArg, consentParams) => {
34
+ this.gtag("consent", consentArg, consentParams);
35
+ };
36
+ }
37
+
38
+ export default GoogleTagManagerProvider;
@@ -0,0 +1,22 @@
1
+ class CustomEventManager {
2
+ static dispatchEvent = (eventName, eventData, target = window) => {
3
+ if (typeof target !== "undefined") {
4
+ const event = new CustomEvent(eventName, { detail: eventData });
5
+ target.dispatchEvent(event);
6
+ }
7
+ };
8
+
9
+ static addEventListener = (eventName, callback, target = window) => {
10
+ if (typeof target !== "undefined") {
11
+ target.addEventListener(eventName, callback);
12
+ }
13
+ };
14
+
15
+ static removeEventListener = (eventName, callback, target = window) => {
16
+ if (typeof target !== "undefined") {
17
+ target.removeEventListener(eventName, callback);
18
+ }
19
+ };
20
+ }
21
+
22
+ export default CustomEventManager;
@@ -0,0 +1,15 @@
1
+ import useCustomEvent from "./useCustomEvent";
2
+ import CustomEventManager from "./CustomEventManager";
3
+
4
+ export { useCustomEvent, CustomEventManager };
5
+
6
+ export const INIT_LOGOUT_EVENT = "site_logout";
7
+ export const ANALYTICS_TRACK_EVENT = "analytics_track_event";
8
+
9
+ export const triggerOnInitLogout = () => {
10
+ CustomEventManager.dispatchEvent(INIT_LOGOUT_EVENT);
11
+ };
12
+
13
+ export const triggerAnalyticsTrackEvent = (eventName, eventParams) => {
14
+ CustomEventManager.dispatchEvent(ANALYTICS_TRACK_EVENT, { eventName, eventParams });
15
+ };
@@ -0,0 +1,13 @@
1
+ import { useEffect } from "react";
2
+ import CustomEventManager from "./CustomEventManager";
3
+
4
+ const useCustomEvent = (eventName, callback) => {
5
+ useEffect(() => {
6
+ CustomEventManager.addEventListener(eventName, callback);
7
+ return () => {
8
+ CustomEventManager.removeEventListener(eventName, callback);
9
+ };
10
+ }, [eventName, callback]);
11
+ };
12
+
13
+ export default useCustomEvent;
@@ -0,0 +1,36 @@
1
+ import _ from "lodash";
2
+
3
+ const FIRST_NAME_KEY = "first_name";
4
+ const LAST_NAME_KEY = "last_name";
5
+ const EMAIL_KEY = "email";
6
+ const OWNER_FIRST_NAME_KEY = `owner_${FIRST_NAME_KEY}`;
7
+ const OWNER_LAST_NAME_KEY = `owner_${LAST_NAME_KEY}`;
8
+ const OWNER_EMAIL_KEY = `owner_${EMAIL_KEY}`;
9
+ const QR_CODE_KEY = "qr_code";
10
+
11
+ const excludeKeys = [
12
+ FIRST_NAME_KEY,
13
+ LAST_NAME_KEY,
14
+ EMAIL_KEY,
15
+ OWNER_FIRST_NAME_KEY,
16
+ OWNER_LAST_NAME_KEY,
17
+ OWNER_EMAIL_KEY,
18
+ QR_CODE_KEY,
19
+ ];
20
+
21
+ const deepOmit = (obj, keysToOmit) => {
22
+ let keysToOmitIndex = _.keyBy(Array.isArray(keysToOmit) ? keysToOmit : [keysToOmit] ); // create an index object of the keys that should be omitted
23
+
24
+ const omitFromObject = (obj) => { // the inner function which will be called recursively
25
+ return _.transform(obj, function(result, value, key) { // transform to a new object
26
+ if (key in keysToOmitIndex) { // if the key is in the index skip it
27
+ return;
28
+ }
29
+ result[key] = _.isObject(value) ? omitFromObject(value) : value; // if the key is an object run it through the inner function - omitFromObject
30
+ })
31
+ }
32
+
33
+ return omitFromObject(obj); // return the inner function result
34
+ }
35
+
36
+ export const normalizeData = (data) => deepOmit(data, excludeKeys);
@@ -25,6 +25,7 @@ export const WS_PUB_SERVER_URL = 'WS_PUB_SERVER_URL';
25
25
  export const REAL_TIME_UPDATES_STRATEGY = 'REAL_TIME_UPDATES_STRATEGY';
26
26
  export const TIMEINTERVALSINCE1970_API_URL = 'TIMEINTERVALSINCE1970_API_URL';
27
27
  export const ABLY_API_KEY = 'ABLY_API_KEY';
28
+ export const GOOGLE_TAGMANAGER_ID = 'GOOGLE_TAGMANAGER_ID';
28
29
 
29
30
  const processEnv = {
30
31
  /**
@@ -61,6 +62,7 @@ const processEnv = {
61
62
  REAL_TIME_UPDATES_STRATEGY: process.env.GATSBY_REAL_TIME_UPDATES_STRATEGY,
62
63
  TIMEINTERVALSINCE1970_API_URL: process.env.GATSBY_TIMEINTERVALSINCE1970_API_URL,
63
64
  ABLY_API_KEY: process.env.GATSBY_ABLY_API_KEY,
65
+ GOOGLE_TAGMANAGER_ID: process.env.GATSBY_GOOGLE_TAGMANAGER_ID,
64
66
  }
65
67
 
66
68
  export const getEnvVariable = (name) => {
@@ -97,4 +99,5 @@ if (typeof window === 'object') {
97
99
  window.REAL_TIME_UPDATES_STRATEGY = processEnv[REAL_TIME_UPDATES_STRATEGY];
98
100
  window.TIMEINTERVALSINCE1970_API_URL = processEnv[TIMEINTERVALSINCE1970_API_URL];
99
101
  window.ABLY_API_KEY = processEnv[ABLY_API_KEY];
102
+ window.GOOGLE_TAGMANAGER_ID = processEnv[GOOGLE_TAGMANAGER_ID];
100
103
  }
@@ -28,6 +28,15 @@ const siteSettingsQuery = graphql`
28
28
  allowClick
29
29
  }
30
30
  }
31
+ idpLogo {
32
+ idpLogoDark {
33
+ publicURL
34
+ }
35
+ idpLogoLight {
36
+ publicURL
37
+ }
38
+ idpLogoAlt
39
+ }
31
40
  identityProviderButtons {
32
41
  buttonColor
33
42
  buttonBorderColor
@@ -1,8 +0,0 @@
1
- export const onInitLogoutEvent = 'site_logout';
2
-
3
- export const triggerOnInitLogout = () => {
4
- if(typeof window !== 'undefined') {
5
- const event = new Event(onInitLogoutEvent);
6
- window.dispatchEvent(event);
7
- }
8
- }