@phygitallabs/tapquest-core 6.7.8 → 6.7.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phygitallabs/tapquest-core",
3
- "version": "6.7.8",
3
+ "version": "6.7.10",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import { usePhygitalConsent } from "@phygitallabs/phygital-consent";
4
+ import { useEffect } from "react";
5
+
6
+ type GtagConsent = (
7
+ command: "consent",
8
+ action: "default" | "update",
9
+ params: Record<string, string>
10
+ ) => void;
11
+
12
+ /** Consent Mode v2: block analytics and ad tags until user accepts. */
13
+ const GTAG_CONSENT_DENIED = {
14
+ analytics_storage: "denied",
15
+ ad_storage: "denied",
16
+ ad_user_data: "denied",
17
+ ad_personalization: "denied",
18
+ } as const;
19
+
20
+ const GTAG_CONSENT_GRANTED_ANALYTICS = {
21
+ analytics_storage: "granted",
22
+ } as const;
23
+
24
+ /**
25
+ * Sets gtag/GTM consent default to denied on mount and updates to granted
26
+ * when user has accepted analytics (must be under PhygitalConsentProvider).
27
+ *
28
+ * When using GTM: add the script from getGtmConsentDefaultSnippet() in your
29
+ * HTML before the GTM container script (gtm.js) so consent is denied before
30
+ * any tags run. This component then pushes consent update when the user accepts.
31
+ * Renders nothing.
32
+ */
33
+ export function GtagConsentSync() {
34
+ const consent = usePhygitalConsent();
35
+ const isAnalyticsAllowed =
36
+ (consent as { isAnalyticsAllowed?: boolean }).isAnalyticsAllowed ?? false;
37
+
38
+ useEffect(() => {
39
+ if (typeof window === "undefined") return;
40
+ const w = window as Window & { dataLayer?: unknown[]; gtag?: GtagConsent };
41
+ w.dataLayer = w.dataLayer || [];
42
+ const gtag = w.gtag;
43
+ if (typeof gtag === "function") {
44
+ gtag("consent", "default", { ...GTAG_CONSENT_DENIED });
45
+ } else {
46
+ w.dataLayer.push(["consent", "default", { ...GTAG_CONSENT_DENIED }]);
47
+ }
48
+ }, []);
49
+
50
+ useEffect(() => {
51
+ if (!isAnalyticsAllowed || typeof window === "undefined") return;
52
+ const w = window as Window & { gtag?: GtagConsent; dataLayer?: unknown[] };
53
+ const gtag = w.gtag;
54
+ if (typeof gtag === "function") {
55
+ gtag("consent", "update", { ...GTAG_CONSENT_GRANTED_ANALYTICS });
56
+ } else if (Array.isArray(w.dataLayer)) {
57
+ w.dataLayer.push(["consent", "update", { ...GTAG_CONSENT_GRANTED_ANALYTICS }]);
58
+ }
59
+ }, [isAnalyticsAllowed]);
60
+
61
+ return null;
62
+ }
@@ -1,92 +1,97 @@
1
+ import { usePhygitalConsent } from "@phygitallabs/phygital-consent";
1
2
  import posthog from "posthog-js";
2
3
  import { useSessionReplay } from "../../../modules/session-replay";
3
4
 
4
5
  declare global {
5
- interface Window {
6
- dataLayer: Record<string, any>[];
7
- }
6
+ interface Window {
7
+ dataLayer: unknown[];
8
+ }
8
9
  }
9
10
 
10
11
  declare function gtag(
11
- command: "event",
12
- eventName: string,
13
- eventParameters: Record<string, any>
12
+ command: "event",
13
+ eventName: string,
14
+ eventParameters: Record<string, any>
14
15
  ): void;
15
16
 
16
-
17
17
  // GTM
18
18
  const pushEventToDataLayer = (event: string, data: Record<string, any>) => {
19
- try {
20
- window.dataLayer = window.dataLayer || [];
21
-
22
- window.dataLayer.push({
23
- event,
24
- ...data,
25
- });
26
-
27
- } catch (error) {
28
- console.error(error);
29
- }
19
+ try {
20
+ window.dataLayer = window.dataLayer || [];
21
+
22
+ (window.dataLayer as Record<string, unknown>[]).push({
23
+ event,
24
+ ...data,
25
+ });
26
+ } catch (error) {
27
+ console.error(error);
28
+ }
30
29
  };
31
30
 
32
31
  // GA
33
32
  const pushEventToGA = (eventName: string, eventData: Record<string, any>) => {
34
- if (typeof gtag != "function") {
35
- console.error("gtag is not a function");
36
- return;
37
- }
38
- gtag("event", eventName, eventData);
33
+ if (typeof gtag != "function") {
34
+ console.error("gtag is not a function");
35
+ return;
36
+ }
37
+ gtag("event", eventName, eventData);
39
38
  };
40
39
 
41
40
  // Posthog
42
41
  const pushEventToPosthog = (eventName: string, eventData: Record<string, any>) => {
43
- posthog.capture(eventName, eventData);
42
+ posthog.capture(eventName, eventData);
44
43
  };
45
44
 
46
45
  function useDataTracking() {
47
- const { setUserId, setMetadata } = useSessionReplay();
48
-
49
- const trackEvent = (eventName: string, eventData: Record<string, any>, useTools?: ("gtm" | "ga" | "posthog")[]) => {
50
- console.log("trackEvent ", eventName, eventData);
51
-
52
- useTools = useTools || ["gtm"];
53
-
54
- if (useTools.includes("gtm") && typeof window !== "undefined") {
55
- pushEventToDataLayer(eventName, eventData);
56
- }
57
- if (useTools.includes("ga") && typeof gtag == "function") {
58
- pushEventToGA(eventName, eventData);
59
- }
60
- if (useTools.includes("posthog") && typeof posthog == "function") {
61
- pushEventToPosthog(eventName, eventData);
62
- }
63
- };
64
-
65
- const trackUserIdentify = (userInfo: Record<string, any>) => {
66
- posthog.identify(userInfo.email, {
67
- email: userInfo.email,
68
- name: userInfo.name,
69
- avatar: userInfo.avatar,
70
- uid: userInfo.uid,
71
- });
72
-
73
- setUserId(userInfo.id);
74
-
75
- setMetadata({
76
- user_email: userInfo.email
77
- })
78
- }
46
+ const { setUserId, setMetadata } = useSessionReplay();
47
+ const consent = usePhygitalConsent();
48
+ const isAnalyticsAllowed =
49
+ (consent as { isAnalyticsAllowed?: boolean }).isAnalyticsAllowed ?? false;
79
50
 
80
- const trackLogoutEvent = () => {
81
- posthog.capture("user_signed_out");
82
- }
51
+ const trackEvent = (
52
+ eventName: string,
53
+ eventData: Record<string, any>,
54
+ useTools?: ("gtm" | "ga" | "posthog")[]
55
+ ) => {
56
+ console.log("trackEvent ", eventName, eventData);
83
57
 
84
- return {
85
- trackEvent,
86
- trackUserIdentify,
87
- trackLogoutEvent
88
- };
58
+ useTools = useTools || ["gtm"];
59
+
60
+ if (useTools.includes("gtm") && typeof window !== "undefined") {
61
+ pushEventToDataLayer(eventName, eventData);
62
+ }
63
+ if (useTools.includes("ga") && isAnalyticsAllowed && typeof gtag == "function") {
64
+ pushEventToGA(eventName, eventData);
65
+ }
66
+ if (useTools.includes("posthog") && typeof posthog == "function") {
67
+ pushEventToPosthog(eventName, eventData);
68
+ }
69
+ };
70
+
71
+ const trackUserIdentify = (userInfo: Record<string, any>) => {
72
+ posthog.identify(userInfo.email, {
73
+ email: userInfo.email,
74
+ name: userInfo.name,
75
+ avatar: userInfo.avatar,
76
+ uid: userInfo.uid,
77
+ });
78
+
79
+ setUserId(userInfo.id);
80
+
81
+ setMetadata({
82
+ user_email: userInfo.email,
83
+ });
84
+ };
85
+
86
+ const trackLogoutEvent = () => {
87
+ posthog.capture("user_signed_out");
88
+ };
89
+
90
+ return {
91
+ trackEvent,
92
+ trackUserIdentify,
93
+ trackLogoutEvent,
94
+ };
89
95
  }
90
96
 
91
97
  export { useDataTracking };
92
-
@@ -1 +1,2 @@
1
- export * from "./hooks";
1
+ export * from "./GtagConsentSync";
2
+ export * from "./hooks";
@@ -5,6 +5,7 @@ import { RewardServiceProvider } from "@phygitallabs/reward";
5
5
  import { AchievementServiceProvider } from "@phygitallabs/achievement";
6
6
  import { GenerateCertificateServiceProvider } from "@phygitallabs/generate-certificate";
7
7
  import { PhygitalConsentProvider } from "@phygitallabs/phygital-consent";
8
+ import { GtagConsentSync } from "../modules/data-tracking";
8
9
 
9
10
  import serviceApiUrl from "../constants/service";
10
11
  import { APIConfig, ServiceConfig } from "../types/service";
@@ -20,142 +21,142 @@ import { tokenStorage } from "@phygitallabs/authentication";
20
21
  import axios from "axios";
21
22
 
22
23
  interface ServicesProviderProps {
23
- children: React.ReactNode;
24
- queryClient: QueryClient;
25
- apiConfig: APIConfig;
26
- firebaseConfig?: any;
24
+ children: React.ReactNode;
25
+ queryClient: QueryClient;
26
+ apiConfig: APIConfig;
27
+ firebaseConfig?: any;
27
28
  }
28
29
 
29
30
  export const ServicesProvider: React.FC<ServicesProviderProps> = ({
30
- children,
31
- queryClient,
32
- apiConfig = {
33
- environment: "dev",
34
- version: "v1"
35
- },
31
+ children,
32
+ queryClient,
33
+ apiConfig = {
34
+ environment: "dev",
35
+ version: "v1",
36
+ },
36
37
  }) => {
37
- const { signOut, refreshToken } = useAuth();
38
- const { environment, version } = apiConfig;
39
- const [commonServiceConfig, setCommonServiceConfig] = useState<ServiceConfig | null>(null);
40
-
41
- const [deviceUid, setDeviceUid] = useState<string>("");
42
-
43
- useEffect(() => {
44
- const initDeviceUid = async () => {
45
- const deviceUid = await checkDeviceUid();
46
- setDeviceUid(deviceUid);
47
- }
48
- initDeviceUid();
49
- }, []);
50
-
51
- // Init client
52
- useEffect(() => {
53
- const initClient = async () => {
54
- try {
55
- const deviceUid = await checkDeviceUid();
56
-
57
- const responseInterceptors = ({
58
- onFulfilled: (response: any) => response,
59
- onRejected: async (error: any) => {
60
- const originalRequest = error.config;
61
- const token = tokenStorage.getAuthToken();
62
-
63
- if (error.response?.status === 401 && !originalRequest._retry) {
64
- const retryAttempts = parseInt(getFromLS(retryAttemptsRefreshToken) || "0", 10);
65
- if (retryAttempts >= httpMaxRetries) {
66
- await signOut();
67
- setToLS(retryAttemptsRefreshToken, 0);
68
- return Promise.reject(error);
69
- }
70
-
71
- setToLS(retryAttemptsRefreshToken, `${retryAttempts + 1}`);
72
- originalRequest._retry = true;
73
-
74
- if (token) {
75
- try {
76
- const result = await refreshToken();
77
- if (result?.data?.idToken) {
78
- originalRequest.headers.Authorization = `Bearer ${result?.data?.idToken}`;
79
- return axios(originalRequest);
80
- }
81
- } catch (refreshError) {
82
- console.error("Failed to refresh token:", refreshError);
83
- await signOut();
84
- return Promise.reject(refreshError);
85
- }
86
- }
87
- }
88
- }
89
- })
90
-
91
- const requestInterceptors = ({
92
- onFulfilled: (config: any) => config,
93
- onRejected: (error: any) => Promise.reject(error),
94
- })
95
-
96
- const axiosConfig = {
97
- headers: {
98
- "Content-Type": "application/json",
99
- "Device-UID": deviceUid,
100
- }
38
+ const { signOut, refreshToken } = useAuth();
39
+ const { environment, version } = apiConfig;
40
+ const [commonServiceConfig, setCommonServiceConfig] = useState<ServiceConfig | null>(null);
41
+
42
+ const [deviceUid, setDeviceUid] = useState<string>("");
43
+
44
+ useEffect(() => {
45
+ const initDeviceUid = async () => {
46
+ const deviceUid = await checkDeviceUid();
47
+ setDeviceUid(deviceUid);
48
+ };
49
+ initDeviceUid();
50
+ }, []);
51
+
52
+ // Init client
53
+ useEffect(() => {
54
+ const initClient = async () => {
55
+ try {
56
+ const deviceUid = await checkDeviceUid();
57
+
58
+ const responseInterceptors = {
59
+ onFulfilled: (response: any) => response,
60
+ onRejected: async (error: any) => {
61
+ const originalRequest = error.config;
62
+ const token = tokenStorage.getAuthToken();
63
+
64
+ if (error.response?.status === 401 && !originalRequest._retry) {
65
+ const retryAttempts = parseInt(getFromLS(retryAttemptsRefreshToken) || "0", 10);
66
+ if (retryAttempts >= httpMaxRetries) {
67
+ await signOut();
68
+ setToLS(retryAttemptsRefreshToken, 0);
69
+ return Promise.reject(error);
70
+ }
71
+
72
+ setToLS(retryAttemptsRefreshToken, `${retryAttempts + 1}`);
73
+ originalRequest._retry = true;
74
+
75
+ if (token) {
76
+ try {
77
+ const result = await refreshToken();
78
+ if (result?.data?.idToken) {
79
+ originalRequest.headers.Authorization = `Bearer ${result?.data?.idToken}`;
80
+ return axios(originalRequest);
81
+ }
82
+ } catch (refreshError) {
83
+ console.error("Failed to refresh token:", refreshError);
84
+ await signOut();
85
+ return Promise.reject(refreshError);
101
86
  }
102
-
103
- const config: ServiceConfig = {
104
- queryClient,
105
- axiosConfig,
106
- responseInterceptors,
107
- requestInterceptors,
108
- useDevTool: true,
109
- };
110
-
111
- setCommonServiceConfig(config);
112
-
113
- } catch (error) {
114
- console.error(error);
87
+ }
115
88
  }
116
- }
117
-
118
- initClient();
119
- }, [queryClient]);
120
-
121
- if (!commonServiceConfig) {
122
- return <></>
123
- }
124
-
125
- return (
126
- <PGLCoreServiceProvider
127
- {...commonServiceConfig}
128
- baseURL={`${serviceApiUrl[environment].API_BASE_URL}/${version}`}
129
- baseCoreURL={`${serviceApiUrl[environment].API_BASE_CORE_URL}/${version}`}
89
+ },
90
+ };
91
+
92
+ const requestInterceptors = {
93
+ onFulfilled: (config: any) => config,
94
+ onRejected: (error: any) => Promise.reject(error),
95
+ };
96
+
97
+ const axiosConfig = {
98
+ headers: {
99
+ "Content-Type": "application/json",
100
+ "Device-UID": deviceUid,
101
+ },
102
+ };
103
+
104
+ const config: ServiceConfig = {
105
+ queryClient,
106
+ axiosConfig,
107
+ responseInterceptors,
108
+ requestInterceptors,
109
+ useDevTool: true,
110
+ };
111
+
112
+ setCommonServiceConfig(config);
113
+ } catch (error) {
114
+ console.error(error);
115
+ }
116
+ };
117
+
118
+ initClient();
119
+ }, [queryClient]);
120
+
121
+ if (!commonServiceConfig) {
122
+ return <></>;
123
+ }
124
+
125
+ return (
126
+ <PGLCoreServiceProvider
127
+ {...commonServiceConfig}
128
+ baseURL={`${serviceApiUrl[environment].API_BASE_URL}/${version}`}
129
+ baseCoreURL={`${serviceApiUrl[environment].API_BASE_CORE_URL}/${version}`}
130
+ >
131
+ <RewardServiceProvider
132
+ {...commonServiceConfig}
133
+ // baseURL={`${serviceApiUrl[environment].API_REWARD_URL}/${version}`}
134
+ baseURL={`${serviceApiUrl[environment].API_REWARD_URL}/v1`} // todo: using v1 until backend fully migrate
135
+ >
136
+ <AchievementServiceProvider
137
+ {...commonServiceConfig}
138
+ baseURL={`${serviceApiUrl[environment].API_ACHIEVEMENT_URL}/${version}`}
130
139
  >
131
- <RewardServiceProvider
132
- {...commonServiceConfig}
133
- // baseURL={`${serviceApiUrl[environment].API_REWARD_URL}/${version}`}
134
- baseURL={`${serviceApiUrl[environment].API_REWARD_URL}/v1`} // todo: using v1 until backend fully migrate
140
+ <GenerateCertificateServiceProvider
141
+ {...commonServiceConfig}
142
+ baseURL={`${serviceApiUrl[environment].API_GENERATE_CERTIFICATE_URL}/v1`}
143
+ >
144
+ <PhygitalConsentProvider
145
+ {...commonServiceConfig}
146
+ deviceId={deviceUid}
147
+ baseURL={`${serviceApiUrl[environment].API_CONSENT_URL}/v1`}
148
+ axiosConfig={{
149
+ headers: {
150
+ "Content-Type": "application/json",
151
+ },
152
+ }}
135
153
  >
136
- <AchievementServiceProvider
137
- {...commonServiceConfig}
138
- baseURL={`${serviceApiUrl[environment].API_ACHIEVEMENT_URL}/${version}`}
139
- >
140
- <GenerateCertificateServiceProvider
141
- {...commonServiceConfig}
142
- baseURL={`${serviceApiUrl[environment].API_GENERATE_CERTIFICATE_URL}/v1`}
143
- >
144
- <PhygitalConsentProvider
145
- {...commonServiceConfig}
146
- deviceId={deviceUid}
147
- baseURL={`${serviceApiUrl[environment].API_CONSENT_URL}/v1`}
148
- axiosConfig={{
149
- headers: {
150
- "Content-Type": "application/json",
151
- }
152
- }}
153
- >
154
- {children}
155
- </PhygitalConsentProvider>
156
- </GenerateCertificateServiceProvider>
157
- </AchievementServiceProvider>
158
- </RewardServiceProvider>
159
- </PGLCoreServiceProvider>
160
- );
161
- };
154
+ <GtagConsentSync />
155
+ {children}
156
+ </PhygitalConsentProvider>
157
+ </GenerateCertificateServiceProvider>
158
+ </AchievementServiceProvider>
159
+ </RewardServiceProvider>
160
+ </PGLCoreServiceProvider>
161
+ );
162
+ };