@uxf/analytics 11.52.2 → 11.53.0
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/README.md +92 -2
- package/ab-testing/index.d.ts +4 -0
- package/ab-testing/index.js +19 -0
- package/package.json +2 -2
- package/src/ab-testing/ab-testing-context.d.ts +10 -0
- package/src/ab-testing/ab-testing-context.js +48 -0
- package/src/ab-testing/ab-testing.d.ts +36 -0
- package/src/ab-testing/ab-testing.js +80 -0
- package/src/ab-testing/ab-testing.test.d.ts +1 -0
- package/src/ab-testing/ab-testing.test.js +31 -0
- package/src/ab-testing/constants.d.ts +2 -0
- package/src/ab-testing/constants.js +5 -0
- package/src/ab-testing/types.d.ts +10 -0
- package/src/ab-testing/types.js +2 -0
- package/src/gtm/gtm-event.d.ts +1 -0
- package/src/gtm/gtm-event.js +9 -0
- package/src/gtm/gtm-script.d.ts +6 -0
- package/src/gtm/gtm-script.js +13 -0
package/README.md
CHANGED
|
@@ -59,12 +59,21 @@ const isSet = isCookieConsentSet(
|
|
|
59
59
|
|
|
60
60
|
This reads the consent from cookie and initializes the GTM dataLayer.
|
|
61
61
|
|
|
62
|
+
```tsx
|
|
63
|
+
import { GtmScript } from "@uxf/analytics/gtm";
|
|
64
|
+
|
|
65
|
+
// in your head component (not directly in _app)
|
|
66
|
+
<GtmScript gtmId="GTM-YOURID" />
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### or with hook
|
|
70
|
+
|
|
62
71
|
```tsx
|
|
63
72
|
import { useGtmScript } from "@uxf/analytics/gtm";
|
|
64
73
|
|
|
65
74
|
const gtmScript = useGtmScript("GTM-YOURID");
|
|
66
75
|
|
|
67
|
-
// in your head component
|
|
76
|
+
// in your head component (not directly in _app)
|
|
68
77
|
<script dangerouslySetInnerHTML={{ __html: gtmScript }} />
|
|
69
78
|
```
|
|
70
79
|
|
|
@@ -84,4 +93,85 @@ updateGtmConsent(
|
|
|
84
93
|
},
|
|
85
94
|
1
|
|
86
95
|
);
|
|
87
|
-
```
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## AB testing
|
|
99
|
+
|
|
100
|
+
A set of components and helpers prepared to implement AB testing to React & NextJS applications.
|
|
101
|
+
|
|
102
|
+
### How to start
|
|
103
|
+
|
|
104
|
+
#### 1. Define your experiments
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import type { ExperimentConfig } from "@uxf/analytics/ab-testing";
|
|
108
|
+
|
|
109
|
+
const experiments = [
|
|
110
|
+
{
|
|
111
|
+
id: "1",
|
|
112
|
+
traffic: 1,
|
|
113
|
+
variants: [
|
|
114
|
+
{ name: "Control", traffic: 0.5 },
|
|
115
|
+
{ name: "B", traffic: 0.5 },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "2",
|
|
120
|
+
traffic: 0.5,
|
|
121
|
+
variants: [
|
|
122
|
+
{ name: "Control", traffic: 0.25 },
|
|
123
|
+
{ name: "B", traffic: 0.75 },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
] as const satisfies ExperimentConfig[];
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### 2. Use the `ABTestingProvider` component
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { ABTestingProvider, AB_TESTING_VARIANT_PROP_NAME } from "@uxf/analytics/ab-testing";
|
|
133
|
+
|
|
134
|
+
export default function App({ Component, pageProps }) {
|
|
135
|
+
return (
|
|
136
|
+
<ABTestingProvider experiments={props.pageProps[AB_TESTING_VARIANT_PROP_NAME]}>
|
|
137
|
+
<Component {...pageProps} />
|
|
138
|
+
</ABTestingProvider>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### 3. Handle AB testing in `middleware.ts`
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { handleABTesting } from "@uxf/analytics/ab-testing";
|
|
147
|
+
|
|
148
|
+
export async function middleware(request: NextRequest) {
|
|
149
|
+
const nextResponse = NextResponse.next();
|
|
150
|
+
|
|
151
|
+
handleABTesting(request, nextResponse, experiments);
|
|
152
|
+
|
|
153
|
+
return nextResponse;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### 4. Implement SSR support in page with AB testing
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { addExperimentsSSR } from "@uxf/analytics/ab-testing";
|
|
161
|
+
|
|
162
|
+
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
163
|
+
return addExperimentsSSR(ctx, {
|
|
164
|
+
props: {},
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### 5. Use the `useABTestingVariant` hook in your component
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { useABTestingVariant } from "@uxf/analytics/ab-testing";
|
|
173
|
+
|
|
174
|
+
const abTestingVariant = useABTestingVariant<typeof experiments>("1");
|
|
175
|
+
|
|
176
|
+
console.log(abTestingVariant); // "Control", "B", etc.
|
|
177
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("../src/ab-testing/ab-testing"), exports);
|
|
18
|
+
__exportStar(require("../src/ab-testing/ab-testing-context"), exports);
|
|
19
|
+
__exportStar(require("../src/ab-testing/constants"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uxf/analytics",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.53.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsc -P tsconfig.json",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"author": "",
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@uxf/core": "11.
|
|
18
|
+
"@uxf/core": "11.53.0"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": ">=18.0.2"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import type { ExperimentConfig } from "./types";
|
|
3
|
+
interface Props {
|
|
4
|
+
experiments: [string, string][];
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
export declare function ABTestingProvider(props: Props): React.JSX.Element;
|
|
8
|
+
export declare function useABTesting(): [string, string][];
|
|
9
|
+
export declare function useABTestingVariant<Config extends ExperimentConfig[]>(experimentId: Config[number]["id"]): Config[number]["variants"][number]["name"] | null;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.ABTestingProvider = ABTestingProvider;
|
|
28
|
+
exports.useABTesting = useABTesting;
|
|
29
|
+
exports.useABTestingVariant = useABTestingVariant;
|
|
30
|
+
const use_on_mount_1 = require("@uxf/core-react/hooks/use-on-mount");
|
|
31
|
+
const react_1 = __importStar(require("react"));
|
|
32
|
+
const ab_testing_1 = require("./ab-testing");
|
|
33
|
+
const ABTestingContext = (0, react_1.createContext)([]);
|
|
34
|
+
function ABTestingProvider(props) {
|
|
35
|
+
(0, use_on_mount_1.useOnMount)(() => {
|
|
36
|
+
(0, ab_testing_1.sendABTestingEvent)();
|
|
37
|
+
});
|
|
38
|
+
return react_1.default.createElement(ABTestingContext.Provider, { value: props.experiments }, props.children);
|
|
39
|
+
}
|
|
40
|
+
function useABTesting() {
|
|
41
|
+
return (0, react_1.useContext)(ABTestingContext);
|
|
42
|
+
}
|
|
43
|
+
function useABTestingVariant(experimentId) {
|
|
44
|
+
var _a;
|
|
45
|
+
const experiments = useABTesting();
|
|
46
|
+
const experiment = experiments.find(([name]) => name === experimentId);
|
|
47
|
+
return ((_a = experiment === null || experiment === void 0 ? void 0 : experiment.at(1)) !== null && _a !== void 0 ? _a : null);
|
|
48
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ExperimentConfig, ExperimentVariant } from "./types";
|
|
2
|
+
type NextRequest = {
|
|
3
|
+
cookies: {
|
|
4
|
+
get: (cookieName: string) => {
|
|
5
|
+
name: string;
|
|
6
|
+
value: string;
|
|
7
|
+
} | undefined;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
type NextResponse = {
|
|
11
|
+
cookies: {
|
|
12
|
+
set: (cookie: {
|
|
13
|
+
name: string;
|
|
14
|
+
value: string;
|
|
15
|
+
maxAge: number;
|
|
16
|
+
}) => void;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type GetServerSidePropsContext = {
|
|
20
|
+
req: {
|
|
21
|
+
cookies: Partial<{
|
|
22
|
+
[key: string]: string;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
type GetServerSidePropsResult<P> = {
|
|
27
|
+
props: P;
|
|
28
|
+
};
|
|
29
|
+
export declare function getExperimentVariant(config: ExperimentConfig, randomNumberForTesting?: number | null): ExperimentVariant | null;
|
|
30
|
+
export declare function handleABTesting(request: NextRequest, response: NextResponse, experiments: ExperimentConfig[]): void;
|
|
31
|
+
export declare function getExperimentsFromContext(cookies: Partial<{
|
|
32
|
+
[p: string]: string;
|
|
33
|
+
}>): [string, string][];
|
|
34
|
+
export declare function addExperimentsSSR<P>(ctx: GetServerSidePropsContext, pageProps: GetServerSidePropsResult<P>): GetServerSidePropsResult<P>;
|
|
35
|
+
export declare function sendABTestingEvent(): void;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getExperimentVariant = getExperimentVariant;
|
|
4
|
+
exports.handleABTesting = handleABTesting;
|
|
5
|
+
exports.getExperimentsFromContext = getExperimentsFromContext;
|
|
6
|
+
exports.addExperimentsSSR = addExperimentsSSR;
|
|
7
|
+
exports.sendABTestingEvent = sendABTestingEvent;
|
|
8
|
+
const is_not_nil_1 = require("@uxf/core/utils/is-not-nil");
|
|
9
|
+
const gtm_event_1 = require("../gtm/gtm-event");
|
|
10
|
+
const constants_1 = require("./constants");
|
|
11
|
+
function getExperimentVariant(config, randomNumberForTesting = null) {
|
|
12
|
+
const randomNumber = randomNumberForTesting !== null && randomNumberForTesting !== void 0 ? randomNumberForTesting : Math.random();
|
|
13
|
+
const trafficWithoutABTesting = Math.fround(1 - config.traffic);
|
|
14
|
+
if (randomNumber <= trafficWithoutABTesting) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
let sumTraffic = trafficWithoutABTesting;
|
|
18
|
+
for (const variant of config.variants) {
|
|
19
|
+
sumTraffic += variant.traffic * config.traffic;
|
|
20
|
+
if (randomNumber <= sumTraffic) {
|
|
21
|
+
return variant;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function getCookieName(experimentId) {
|
|
27
|
+
return `${constants_1.EXPERIMENT_COOKIE_PREFIX}${experimentId}`;
|
|
28
|
+
}
|
|
29
|
+
function handleExperimentCookie(request, response, experimentConfig) {
|
|
30
|
+
var _a;
|
|
31
|
+
const cookieValue = request.cookies.get(getCookieName(experimentConfig.id));
|
|
32
|
+
if (cookieValue) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const experimentVariant = getExperimentVariant(experimentConfig);
|
|
36
|
+
response.cookies.set({
|
|
37
|
+
name: getCookieName(experimentConfig.id),
|
|
38
|
+
value: (_a = experimentVariant === null || experimentVariant === void 0 ? void 0 : experimentVariant.name) !== null && _a !== void 0 ? _a : "not-participate",
|
|
39
|
+
maxAge: 60 * 60 * 24 * 365, // 365 days
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function handleABTesting(request, response, experiments) {
|
|
43
|
+
experiments.forEach((experimentConfig) => handleExperimentCookie(request, response, experimentConfig));
|
|
44
|
+
}
|
|
45
|
+
function getExperimentsFromContext(cookies) {
|
|
46
|
+
return Object.entries(cookies)
|
|
47
|
+
.filter(([cookieName]) => cookieName.startsWith(constants_1.EXPERIMENT_COOKIE_PREFIX))
|
|
48
|
+
.filter(([, cookieValue]) => (0, is_not_nil_1.isNotNil)(cookieValue))
|
|
49
|
+
.map(([cookieName, cookieValue]) => [cookieName.replace(constants_1.EXPERIMENT_COOKIE_PREFIX, ""), cookieValue]);
|
|
50
|
+
}
|
|
51
|
+
function addExperimentsSSR(ctx, pageProps) {
|
|
52
|
+
if (pageProps.props !== undefined) {
|
|
53
|
+
pageProps.props[constants_1.AB_TESTING_VARIANT_PROP_NAME] = getExperimentsFromContext(ctx.req.cookies);
|
|
54
|
+
}
|
|
55
|
+
return pageProps;
|
|
56
|
+
}
|
|
57
|
+
function sendABTestingEvent() {
|
|
58
|
+
if (typeof document === "undefined") {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.error("Document is undefined. This method can be called only on client.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (typeof window.dataLayer === "undefined") {
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.error("DataLayer is undefined. GTM must be implemented.");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
decodeURIComponent(document.cookie)
|
|
69
|
+
.split(";")
|
|
70
|
+
.map((cookie) => cookie.trim().split("="))
|
|
71
|
+
.map(([cookieName, cookieValue]) => ({ name: cookieName, value: cookieValue }))
|
|
72
|
+
.forEach((cookie) => {
|
|
73
|
+
if (cookie.name.startsWith(constants_1.EXPERIMENT_COOKIE_PREFIX) && typeof window !== "undefined") {
|
|
74
|
+
(0, gtm_event_1.gtmEvent)({
|
|
75
|
+
event: "experience_impression",
|
|
76
|
+
exp_variant_string: `${cookie.name}-${cookie.value}`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ab_testing_1 = require("./ab-testing");
|
|
4
|
+
const A = { name: "A", traffic: 0.1 };
|
|
5
|
+
const B = { name: "B", traffic: 0.3 };
|
|
6
|
+
const C = { name: "C", traffic: 0.6 };
|
|
7
|
+
const config = {
|
|
8
|
+
id: "experiment-1",
|
|
9
|
+
traffic: 0.8,
|
|
10
|
+
variants: [A, B, C],
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* 100% --- 20% | 0.20
|
|
14
|
+
* \-- 80% --- 10% (0.8 * 0.1 = 0.08) | 0.28
|
|
15
|
+
* \-- 30% (0.8 * 0.3 = 0.24) | 0.52
|
|
16
|
+
* \- 60% (0.8 * 0.6 = 0.48) | 0.100
|
|
17
|
+
*/
|
|
18
|
+
test("getExperimentVariant", () => {
|
|
19
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0)).toBe(null);
|
|
20
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0.1)).toBe(null);
|
|
21
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0.2)).toBe(null);
|
|
22
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0.21)).toBe(A);
|
|
23
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0.28)).toBe(A);
|
|
24
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0.29)).toBe(B);
|
|
25
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0.52)).toBe(B);
|
|
26
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0.53)).toBe(C);
|
|
27
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 0.99)).toBe(C);
|
|
28
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 1)).toBe(C);
|
|
29
|
+
// tohle by nemělo nastat
|
|
30
|
+
expect((0, ab_testing_1.getExperimentVariant)(config, 1.1)).toBe(null);
|
|
31
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AB_TESTING_VARIANT_PROP_NAME = exports.EXPERIMENT_COOKIE_PREFIX = void 0;
|
|
4
|
+
exports.EXPERIMENT_COOKIE_PREFIX = "uxf-experiment-";
|
|
5
|
+
exports.AB_TESTING_VARIANT_PROP_NAME = "__AB_TESTING_VARIANT__";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function gtmEvent(gtmEventData: any): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.gtmEvent = gtmEvent;
|
|
4
|
+
function gtmEvent(gtmEventData) {
|
|
5
|
+
if (typeof window.dataLayer === "undefined") {
|
|
6
|
+
window.dataLayer = [];
|
|
7
|
+
}
|
|
8
|
+
window.dataLayer.push(gtmEventData);
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GtmScript = GtmScript;
|
|
7
|
+
const is_server_1 = require("@uxf/core/utils/is-server");
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const use_gtm_script_1 = require("./use-gtm-script");
|
|
10
|
+
function GtmScript(props) {
|
|
11
|
+
const gtmScript = (0, use_gtm_script_1.useGtmScript)(props.gtmId);
|
|
12
|
+
return react_1.default.createElement(react_1.default.Fragment, null, !is_server_1.isServer && react_1.default.createElement("script", { dangerouslySetInnerHTML: { __html: gtmScript } }));
|
|
13
|
+
}
|