@mission-studio/puck 1.0.2 → 1.0.5

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.
@@ -0,0 +1,67 @@
1
+ // hooks/useGtmEvent.ts
2
+ function useGtmEvent() {
3
+ return (eventName, data) => {
4
+ if (typeof window === "undefined") return;
5
+ if (typeof window.gtag === "function") {
6
+ window.gtag("event", eventName, data || {});
7
+ }
8
+ };
9
+ }
10
+
11
+ // hooks/useUtmParams.ts
12
+ import { useEffect, useState } from "react";
13
+ function useUtmParams() {
14
+ const [utmParams, setUtmParams] = useState({});
15
+ useEffect(() => {
16
+ if (typeof window === "undefined") return;
17
+ const urlParams = new URLSearchParams(window.location.search);
18
+ const source = urlParams.get("utm_source");
19
+ const medium = urlParams.get("utm_medium");
20
+ const campaign = urlParams.get("utm_campaign");
21
+ const content = urlParams.get("utm_content");
22
+ const term = urlParams.get("utm_term");
23
+ const params = {};
24
+ if (source) {
25
+ params.source = source;
26
+ sessionStorage.setItem("utm_source", source);
27
+ } else {
28
+ const stored = sessionStorage.getItem("utm_source");
29
+ if (stored) params.source = stored;
30
+ }
31
+ if (medium) {
32
+ params.medium = medium;
33
+ sessionStorage.setItem("utm_medium", medium);
34
+ } else {
35
+ const stored = sessionStorage.getItem("utm_medium");
36
+ if (stored) params.medium = stored;
37
+ }
38
+ if (campaign) {
39
+ params.campaign = campaign;
40
+ sessionStorage.setItem("utm_campaign", campaign);
41
+ } else {
42
+ const stored = sessionStorage.getItem("utm_campaign");
43
+ if (stored) params.campaign = stored;
44
+ }
45
+ if (content) {
46
+ params.content = content;
47
+ sessionStorage.setItem("utm_content", content);
48
+ } else {
49
+ const stored = sessionStorage.getItem("utm_content");
50
+ if (stored) params.content = stored;
51
+ }
52
+ if (term) {
53
+ params.term = term;
54
+ sessionStorage.setItem("utm_term", term);
55
+ } else {
56
+ const stored = sessionStorage.getItem("utm_term");
57
+ if (stored) params.term = stored;
58
+ }
59
+ setUtmParams(params);
60
+ }, []);
61
+ return utmParams;
62
+ }
63
+
64
+ export {
65
+ useGtmEvent,
66
+ useUtmParams
67
+ };
@@ -5,6 +5,9 @@ import {
5
5
  useEntries,
6
6
  useTheme
7
7
  } from "./chunk-TTKY3YGP.mjs";
8
+ import {
9
+ useUtmParams
10
+ } from "./chunk-QSWQDR6M.mjs";
8
11
 
9
12
  // components/page/Heading.tsx
10
13
  import { jsx } from "react/jsx-runtime";
@@ -158,76 +161,8 @@ function Paragraph({
158
161
  return /* @__PURE__ */ jsx2("p", { id, style, children: resolvedText });
159
162
  }
160
163
 
161
- // hooks/useGtmEvent.ts
162
- function useGtmEvent() {
163
- return (eventName, data) => {
164
- if (typeof window === "undefined") return;
165
- if (typeof window.gtag !== "function") {
166
- console.warn("GTM not initialized. Make sure @next/third-parties/google GoogleTagManager is added to your layout.");
167
- return;
168
- }
169
- const eventData = {
170
- event: eventName,
171
- ...data && { value: data }
172
- };
173
- window.gtag("event", eventName, data || {});
174
- };
175
- }
176
-
177
- // hooks/useUtmParams.ts
178
- import { useEffect, useState } from "react";
179
- function useUtmParams() {
180
- const [utmParams, setUtmParams] = useState({});
181
- useEffect(() => {
182
- if (typeof window === "undefined") return;
183
- const urlParams = new URLSearchParams(window.location.search);
184
- const source = urlParams.get("utm_source");
185
- const medium = urlParams.get("utm_medium");
186
- const campaign = urlParams.get("utm_campaign");
187
- const content = urlParams.get("utm_content");
188
- const term = urlParams.get("utm_term");
189
- const params = {};
190
- if (source) {
191
- params.source = source;
192
- sessionStorage.setItem("utm_source", source);
193
- } else {
194
- const stored = sessionStorage.getItem("utm_source");
195
- if (stored) params.source = stored;
196
- }
197
- if (medium) {
198
- params.medium = medium;
199
- sessionStorage.setItem("utm_medium", medium);
200
- } else {
201
- const stored = sessionStorage.getItem("utm_medium");
202
- if (stored) params.medium = stored;
203
- }
204
- if (campaign) {
205
- params.campaign = campaign;
206
- sessionStorage.setItem("utm_campaign", campaign);
207
- } else {
208
- const stored = sessionStorage.getItem("utm_campaign");
209
- if (stored) params.campaign = stored;
210
- }
211
- if (content) {
212
- params.content = content;
213
- sessionStorage.setItem("utm_content", content);
214
- } else {
215
- const stored = sessionStorage.getItem("utm_content");
216
- if (stored) params.content = stored;
217
- }
218
- if (term) {
219
- params.term = term;
220
- sessionStorage.setItem("utm_term", term);
221
- } else {
222
- const stored = sessionStorage.getItem("utm_term");
223
- if (stored) params.term = stored;
224
- }
225
- setUtmParams(params);
226
- }, []);
227
- return utmParams;
228
- }
229
-
230
164
  // components/page/Button.tsx
165
+ import { sendGTMEvent } from "@next/third-parties/google";
231
166
  import { jsx as jsx3 } from "react/jsx-runtime";
232
167
  var sizeStyles = {
233
168
  sm: { padding: "8px 16px", fontSize: "0.875rem" },
@@ -263,7 +198,6 @@ function Button({
263
198
  }) {
264
199
  const { resolveColor: resolveColor2 } = useTheme();
265
200
  const { getEntryValue } = useEntries();
266
- const sendEvent = useGtmEvent();
267
201
  const utm = useUtmParams();
268
202
  const resolvedText = (() => {
269
203
  if (!text) return "Button";
@@ -277,11 +211,16 @@ function Button({
277
211
  return "Button";
278
212
  })();
279
213
  const handleClick = () => {
280
- sendEvent("button_click", {
281
- text: resolvedText,
282
- href: href || void 0,
283
- variant,
284
- ...utm
214
+ const sessionId = typeof window !== "undefined" ? sessionStorage.getItem("session_id") : null;
215
+ sendGTMEvent({
216
+ event: "button_click",
217
+ value: {
218
+ text: resolvedText,
219
+ href: href || void 0,
220
+ variant,
221
+ session_id: sessionId,
222
+ ...utm
223
+ }
285
224
  });
286
225
  };
287
226
  const resolvedColor = (() => {
@@ -495,7 +434,8 @@ function Image({
495
434
  }
496
435
 
497
436
  // components/page/ImageCarousel.tsx
498
- import { useState as useState2 } from "react";
437
+ import { useState } from "react";
438
+ import { sendGTMEvent as sendGTMEvent2 } from "@next/third-parties/google";
499
439
  import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
500
440
  var aspectRatioMap2 = {
501
441
  "16:9": "16 / 9",
@@ -522,9 +462,8 @@ function ImageCarousel({
522
462
  dotColor,
523
463
  id
524
464
  }) {
525
- const [currentIndex, setCurrentIndex] = useState2(0);
465
+ const [currentIndex, setCurrentIndex] = useState(0);
526
466
  const { resolveColor: resolveColor2 } = useTheme();
527
- const sendEvent = useGtmEvent();
528
467
  const utm = useUtmParams();
529
468
  const resolvedArrowColor = (() => {
530
469
  if (!arrowColor) return { color: "#FFFFFF", opacity: 100 };
@@ -548,30 +487,39 @@ function ImageCarousel({
548
487
  const goToPrevious = () => {
549
488
  const newIndex = currentIndex === 0 ? images.length - 1 : currentIndex - 1;
550
489
  setCurrentIndex(newIndex);
551
- sendEvent("carousel_navigate", {
552
- direction: "previous",
553
- slideIndex: newIndex,
554
- totalSlides: images.length,
555
- ...utm
490
+ sendGTMEvent2({
491
+ event: "carousel_navigate",
492
+ value: {
493
+ direction: "previous",
494
+ slideIndex: newIndex,
495
+ totalSlides: images.length,
496
+ ...utm
497
+ }
556
498
  });
557
499
  };
558
500
  const goToNext = () => {
559
501
  const newIndex = currentIndex === images.length - 1 ? 0 : currentIndex + 1;
560
502
  setCurrentIndex(newIndex);
561
- sendEvent("carousel_navigate", {
562
- direction: "next",
563
- slideIndex: newIndex,
564
- totalSlides: images.length,
565
- ...utm
503
+ sendGTMEvent2({
504
+ event: "carousel_navigate",
505
+ value: {
506
+ direction: "next",
507
+ slideIndex: newIndex,
508
+ totalSlides: images.length,
509
+ ...utm
510
+ }
566
511
  });
567
512
  };
568
513
  const goToSlide = (index) => {
569
514
  setCurrentIndex(index);
570
- sendEvent("carousel_navigate", {
571
- direction: "direct",
572
- slideIndex: index,
573
- totalSlides: images.length,
574
- ...utm
515
+ sendGTMEvent2({
516
+ event: "carousel_navigate",
517
+ value: {
518
+ direction: "direct",
519
+ slideIndex: index,
520
+ totalSlides: images.length,
521
+ ...utm
522
+ }
575
523
  });
576
524
  };
577
525
  if (images.length === 0) {
@@ -1665,6 +1613,7 @@ function FeatureGrid({
1665
1613
 
1666
1614
  // components/page/Footer.tsx
1667
1615
  import { Facebook, Instagram, Twitter } from "lucide-react";
1616
+ import { sendGTMEvent as sendGTMEvent3 } from "@next/third-parties/google";
1668
1617
  import { jsx as jsx18, jsxs as jsxs7 } from "react/jsx-runtime";
1669
1618
  function Footer({
1670
1619
  logo,
@@ -1677,7 +1626,6 @@ function Footer({
1677
1626
  puck
1678
1627
  }) {
1679
1628
  const DropZone = puck?.renderDropZone;
1680
- const sendEvent = useGtmEvent();
1681
1629
  const utm = useUtmParams();
1682
1630
  const getSocialPlatform = (url) => {
1683
1631
  if (url.includes("facebook")) return "facebook";
@@ -1687,17 +1635,22 @@ function Footer({
1687
1635
  };
1688
1636
  const handleSocialClick = (url) => {
1689
1637
  const platform = getSocialPlatform(url);
1690
- sendEvent("social_click", {
1691
- platform,
1692
- url,
1693
- ...utm
1638
+ sendGTMEvent3({
1639
+ event: "social_click",
1640
+ value: {
1641
+ platform,
1642
+ url,
1643
+ ...utm
1644
+ }
1694
1645
  });
1695
1646
  };
1696
1647
  const socialLinks = [
1697
1648
  { url: facebookUrl, Icon: Facebook },
1698
1649
  { url: instagramUrl, Icon: Instagram },
1699
1650
  { url: twitterUrl, Icon: Twitter }
1700
- ].filter((link) => !!link.url);
1651
+ ].filter(
1652
+ (link) => !!link.url
1653
+ );
1701
1654
  return /* @__PURE__ */ jsx18(
1702
1655
  "footer",
1703
1656
  {
@@ -1725,9 +1678,10 @@ function Footer({
1725
1678
  }
1726
1679
 
1727
1680
  // components/page/Topbar.tsx
1728
- import { useState as useState3 } from "react";
1681
+ import { useState as useState2 } from "react";
1729
1682
  import Link from "next/link";
1730
1683
  import { Menu, X } from "lucide-react";
1684
+ import { sendGTMEvent as sendGTMEvent4 } from "@next/third-parties/google";
1731
1685
  import { jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
1732
1686
  function Topbar({
1733
1687
  logo,
@@ -1739,23 +1693,28 @@ function Topbar({
1739
1693
  puck
1740
1694
  }) {
1741
1695
  const DropZone = puck?.renderDropZone;
1742
- const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
1743
- const sendEvent = useGtmEvent();
1696
+ const [mobileMenuOpen, setMobileMenuOpen] = useState2(false);
1744
1697
  const utm = useUtmParams();
1745
1698
  const handleNavClick = (item) => {
1746
- sendEvent("nav_click", {
1747
- name: item.name,
1748
- url: item.url,
1749
- linkType: item.linkType || "internal",
1750
- ...utm
1699
+ sendGTMEvent4({
1700
+ event: "nav_click",
1701
+ value: {
1702
+ name: item.name,
1703
+ url: item.url,
1704
+ linkType: item.linkType || "internal",
1705
+ ...utm
1706
+ }
1751
1707
  });
1752
1708
  };
1753
1709
  const handleMobileMenuToggle = () => {
1754
1710
  const newState = !mobileMenuOpen;
1755
1711
  setMobileMenuOpen(newState);
1756
- sendEvent("mobile_menu_toggle", {
1757
- open: newState,
1758
- ...utm
1712
+ sendGTMEvent4({
1713
+ event: "mobile_menu_toggle",
1714
+ value: {
1715
+ open: newState,
1716
+ ...utm
1717
+ }
1759
1718
  });
1760
1719
  };
1761
1720
  const renderLink = (item, index) => {
@@ -1814,11 +1773,14 @@ function Topbar({
1814
1773
  {
1815
1774
  href: logoUrl,
1816
1775
  className: "flex-shrink-0",
1817
- onClick: () => sendEvent("nav_click", {
1818
- name: "logo",
1819
- url: logoUrl,
1820
- linkType: "internal",
1821
- ...utm
1776
+ onClick: () => sendGTMEvent4({
1777
+ event: "nav_click",
1778
+ value: {
1779
+ name: "logo",
1780
+ url: logoUrl,
1781
+ linkType: "internal",
1782
+ ...utm
1783
+ }
1822
1784
  }),
1823
1785
  children: logo ? /* @__PURE__ */ jsx19("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ jsx19("span", { className: "text-xl font-bold", children: "Logo" })
1824
1786
  }
@@ -1848,8 +1810,9 @@ function Topbar({
1848
1810
  }
1849
1811
 
1850
1812
  // components/page/Popup.tsx
1851
- import { useState as useState4 } from "react";
1813
+ import { useState as useState3 } from "react";
1852
1814
  import { icons as icons4, X as X2 } from "lucide-react";
1815
+ import { sendGTMEvent as sendGTMEvent5 } from "@next/third-parties/google";
1853
1816
  import { Fragment as Fragment2, jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
1854
1817
  function Icon2({ name, ...props }) {
1855
1818
  const formatted = name.charAt(0).toUpperCase() + name.slice(1);
@@ -1878,20 +1841,25 @@ function Popup({
1878
1841
  textLink = false,
1879
1842
  puck
1880
1843
  }) {
1881
- const [isOpen, setIsOpen] = useState4(false);
1882
- const sendEvent = useGtmEvent();
1844
+ const [isOpen, setIsOpen] = useState3(false);
1883
1845
  const utm = useUtmParams();
1884
1846
  const handleOpen = () => {
1885
1847
  setIsOpen(true);
1886
- sendEvent("popup_open", {
1887
- ctaText,
1888
- type: textLink ? "link" : "button",
1889
- ...utm
1848
+ sendGTMEvent5({
1849
+ event: "popup_open",
1850
+ value: {
1851
+ ctaText,
1852
+ type: textLink ? "link" : "button",
1853
+ ...utm
1854
+ }
1890
1855
  });
1891
1856
  };
1892
1857
  const handleClose = () => {
1893
1858
  setIsOpen(false);
1894
- sendEvent("popup_close", { ctaText, ...utm });
1859
+ sendGTMEvent5({
1860
+ event: "popup_close",
1861
+ value: { ctaText, ...utm }
1862
+ });
1895
1863
  };
1896
1864
  const trigger = textLink ? /* @__PURE__ */ jsx20(
1897
1865
  "button",
@@ -292,21 +292,8 @@ function Paragraph({
292
292
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id, style, children: resolvedText });
293
293
  }
294
294
 
295
- // hooks/useGtmEvent.ts
296
- function useGtmEvent() {
297
- return (eventName, data) => {
298
- if (typeof window === "undefined") return;
299
- if (typeof window.gtag !== "function") {
300
- console.warn("GTM not initialized. Make sure @next/third-parties/google GoogleTagManager is added to your layout.");
301
- return;
302
- }
303
- const eventData = {
304
- event: eventName,
305
- ...data && { value: data }
306
- };
307
- window.gtag("event", eventName, data || {});
308
- };
309
- }
295
+ // components/page/Button.tsx
296
+ var import_google = require("@next/third-parties/google");
310
297
 
311
298
  // hooks/useUtmParams.ts
312
299
  var import_react3 = require("react");
@@ -397,7 +384,6 @@ function Button({
397
384
  }) {
398
385
  const { resolveColor: resolveColor2 } = useTheme();
399
386
  const { getEntryValue } = useEntries();
400
- const sendEvent = useGtmEvent();
401
387
  const utm = useUtmParams();
402
388
  const resolvedText = (() => {
403
389
  if (!text) return "Button";
@@ -411,11 +397,16 @@ function Button({
411
397
  return "Button";
412
398
  })();
413
399
  const handleClick = () => {
414
- sendEvent("button_click", {
415
- text: resolvedText,
416
- href: href || void 0,
417
- variant,
418
- ...utm
400
+ const sessionId = typeof window !== "undefined" ? sessionStorage.getItem("session_id") : null;
401
+ (0, import_google.sendGTMEvent)({
402
+ event: "button_click",
403
+ value: {
404
+ text: resolvedText,
405
+ href: href || void 0,
406
+ variant,
407
+ session_id: sessionId,
408
+ ...utm
409
+ }
419
410
  });
420
411
  };
421
412
  const resolvedColor = (() => {
@@ -630,6 +621,7 @@ function Image({
630
621
 
631
622
  // components/page/ImageCarousel.tsx
632
623
  var import_react4 = require("react");
624
+ var import_google2 = require("@next/third-parties/google");
633
625
  var import_jsx_runtime7 = require("react/jsx-runtime");
634
626
  var aspectRatioMap2 = {
635
627
  "16:9": "16 / 9",
@@ -658,7 +650,6 @@ function ImageCarousel({
658
650
  }) {
659
651
  const [currentIndex, setCurrentIndex] = (0, import_react4.useState)(0);
660
652
  const { resolveColor: resolveColor2 } = useTheme();
661
- const sendEvent = useGtmEvent();
662
653
  const utm = useUtmParams();
663
654
  const resolvedArrowColor = (() => {
664
655
  if (!arrowColor) return { color: "#FFFFFF", opacity: 100 };
@@ -682,30 +673,39 @@ function ImageCarousel({
682
673
  const goToPrevious = () => {
683
674
  const newIndex = currentIndex === 0 ? images.length - 1 : currentIndex - 1;
684
675
  setCurrentIndex(newIndex);
685
- sendEvent("carousel_navigate", {
686
- direction: "previous",
687
- slideIndex: newIndex,
688
- totalSlides: images.length,
689
- ...utm
676
+ (0, import_google2.sendGTMEvent)({
677
+ event: "carousel_navigate",
678
+ value: {
679
+ direction: "previous",
680
+ slideIndex: newIndex,
681
+ totalSlides: images.length,
682
+ ...utm
683
+ }
690
684
  });
691
685
  };
692
686
  const goToNext = () => {
693
687
  const newIndex = currentIndex === images.length - 1 ? 0 : currentIndex + 1;
694
688
  setCurrentIndex(newIndex);
695
- sendEvent("carousel_navigate", {
696
- direction: "next",
697
- slideIndex: newIndex,
698
- totalSlides: images.length,
699
- ...utm
689
+ (0, import_google2.sendGTMEvent)({
690
+ event: "carousel_navigate",
691
+ value: {
692
+ direction: "next",
693
+ slideIndex: newIndex,
694
+ totalSlides: images.length,
695
+ ...utm
696
+ }
700
697
  });
701
698
  };
702
699
  const goToSlide = (index2) => {
703
700
  setCurrentIndex(index2);
704
- sendEvent("carousel_navigate", {
705
- direction: "direct",
706
- slideIndex: index2,
707
- totalSlides: images.length,
708
- ...utm
701
+ (0, import_google2.sendGTMEvent)({
702
+ event: "carousel_navigate",
703
+ value: {
704
+ direction: "direct",
705
+ slideIndex: index2,
706
+ totalSlides: images.length,
707
+ ...utm
708
+ }
709
709
  });
710
710
  };
711
711
  if (images.length === 0) {
@@ -1835,6 +1835,7 @@ function FeatureGrid({
1835
1835
 
1836
1836
  // components/page/Footer.tsx
1837
1837
  var import_lucide_react3 = require("lucide-react");
1838
+ var import_google3 = require("@next/third-parties/google");
1838
1839
  var import_jsx_runtime20 = require("react/jsx-runtime");
1839
1840
  function Footer({
1840
1841
  logo,
@@ -1847,7 +1848,6 @@ function Footer({
1847
1848
  puck
1848
1849
  }) {
1849
1850
  const DropZone = puck?.renderDropZone;
1850
- const sendEvent = useGtmEvent();
1851
1851
  const utm = useUtmParams();
1852
1852
  const getSocialPlatform = (url) => {
1853
1853
  if (url.includes("facebook")) return "facebook";
@@ -1857,17 +1857,22 @@ function Footer({
1857
1857
  };
1858
1858
  const handleSocialClick = (url) => {
1859
1859
  const platform2 = getSocialPlatform(url);
1860
- sendEvent("social_click", {
1861
- platform: platform2,
1862
- url,
1863
- ...utm
1860
+ (0, import_google3.sendGTMEvent)({
1861
+ event: "social_click",
1862
+ value: {
1863
+ platform: platform2,
1864
+ url,
1865
+ ...utm
1866
+ }
1864
1867
  });
1865
1868
  };
1866
1869
  const socialLinks = [
1867
1870
  { url: facebookUrl, Icon: import_lucide_react3.Facebook },
1868
1871
  { url: instagramUrl, Icon: import_lucide_react3.Instagram },
1869
1872
  { url: twitterUrl, Icon: import_lucide_react3.Twitter }
1870
- ].filter((link) => !!link.url);
1873
+ ].filter(
1874
+ (link) => !!link.url
1875
+ );
1871
1876
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1872
1877
  "footer",
1873
1878
  {
@@ -1898,6 +1903,7 @@ function Footer({
1898
1903
  var import_react5 = require("react");
1899
1904
  var import_link = __toESM(require("next/link"));
1900
1905
  var import_lucide_react4 = require("lucide-react");
1906
+ var import_google4 = require("@next/third-parties/google");
1901
1907
  var import_jsx_runtime21 = require("react/jsx-runtime");
1902
1908
  function Topbar({
1903
1909
  logo,
@@ -1910,22 +1916,27 @@ function Topbar({
1910
1916
  }) {
1911
1917
  const DropZone = puck?.renderDropZone;
1912
1918
  const [mobileMenuOpen, setMobileMenuOpen] = (0, import_react5.useState)(false);
1913
- const sendEvent = useGtmEvent();
1914
1919
  const utm = useUtmParams();
1915
1920
  const handleNavClick = (item) => {
1916
- sendEvent("nav_click", {
1917
- name: item.name,
1918
- url: item.url,
1919
- linkType: item.linkType || "internal",
1920
- ...utm
1921
+ (0, import_google4.sendGTMEvent)({
1922
+ event: "nav_click",
1923
+ value: {
1924
+ name: item.name,
1925
+ url: item.url,
1926
+ linkType: item.linkType || "internal",
1927
+ ...utm
1928
+ }
1921
1929
  });
1922
1930
  };
1923
1931
  const handleMobileMenuToggle = () => {
1924
1932
  const newState = !mobileMenuOpen;
1925
1933
  setMobileMenuOpen(newState);
1926
- sendEvent("mobile_menu_toggle", {
1927
- open: newState,
1928
- ...utm
1934
+ (0, import_google4.sendGTMEvent)({
1935
+ event: "mobile_menu_toggle",
1936
+ value: {
1937
+ open: newState,
1938
+ ...utm
1939
+ }
1929
1940
  });
1930
1941
  };
1931
1942
  const renderLink = (item, index2) => {
@@ -1984,11 +1995,14 @@ function Topbar({
1984
1995
  {
1985
1996
  href: logoUrl,
1986
1997
  className: "flex-shrink-0",
1987
- onClick: () => sendEvent("nav_click", {
1988
- name: "logo",
1989
- url: logoUrl,
1990
- linkType: "internal",
1991
- ...utm
1998
+ onClick: () => (0, import_google4.sendGTMEvent)({
1999
+ event: "nav_click",
2000
+ value: {
2001
+ name: "logo",
2002
+ url: logoUrl,
2003
+ linkType: "internal",
2004
+ ...utm
2005
+ }
1992
2006
  }),
1993
2007
  children: logo ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xl font-bold", children: "Logo" })
1994
2008
  }
@@ -2020,6 +2034,7 @@ function Topbar({
2020
2034
  // components/page/Popup.tsx
2021
2035
  var import_react6 = require("react");
2022
2036
  var import_lucide_react5 = require("lucide-react");
2037
+ var import_google5 = require("@next/third-parties/google");
2023
2038
  var import_jsx_runtime22 = require("react/jsx-runtime");
2024
2039
  function Icon2({ name, ...props }) {
2025
2040
  const formatted = name.charAt(0).toUpperCase() + name.slice(1);
@@ -2049,19 +2064,24 @@ function Popup({
2049
2064
  puck
2050
2065
  }) {
2051
2066
  const [isOpen, setIsOpen] = (0, import_react6.useState)(false);
2052
- const sendEvent = useGtmEvent();
2053
2067
  const utm = useUtmParams();
2054
2068
  const handleOpen = () => {
2055
2069
  setIsOpen(true);
2056
- sendEvent("popup_open", {
2057
- ctaText,
2058
- type: textLink ? "link" : "button",
2059
- ...utm
2070
+ (0, import_google5.sendGTMEvent)({
2071
+ event: "popup_open",
2072
+ value: {
2073
+ ctaText,
2074
+ type: textLink ? "link" : "button",
2075
+ ...utm
2076
+ }
2060
2077
  });
2061
2078
  };
2062
2079
  const handleClose = () => {
2063
2080
  setIsOpen(false);
2064
- sendEvent("popup_close", { ctaText, ...utm });
2081
+ (0, import_google5.sendGTMEvent)({
2082
+ event: "popup_close",
2083
+ value: { ctaText, ...utm }
2084
+ });
2065
2085
  };
2066
2086
  const trigger = textLink ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2067
2087
  "button",
@@ -29,8 +29,9 @@ import {
29
29
  Topbar,
30
30
  VideoEmbed,
31
31
  availableIcons
32
- } from "./chunk-RVOG4KFI.mjs";
32
+ } from "./chunk-R7TH6TWG.mjs";
33
33
  import "./chunk-TTKY3YGP.mjs";
34
+ import "./chunk-QSWQDR6M.mjs";
34
35
 
35
36
  // config.tsx
36
37
  import { jsx } from "react/jsx-runtime";
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Hook to send events to Google Tag Manager
3
+ * Assumes GTM is initialized in the parent app via @next/third-parties/google
4
+ * @example
5
+ * const sendEvent = useGtmEvent();
6
+ * sendEvent('button_click', { text: 'Sign Up', href: '/signup' });
7
+ */
8
+ declare function useGtmEvent(): (eventName: string, data?: Record<string, any>) => void;
9
+ declare global {
10
+ interface Window {
11
+ gtag?: (...args: any[]) => void;
12
+ }
13
+ }
14
+
15
+ /**
16
+ * UTM parameter object
17
+ */
18
+ interface UtmParams {
19
+ source?: string;
20
+ medium?: string;
21
+ campaign?: string;
22
+ content?: string;
23
+ term?: string;
24
+ }
25
+ /**
26
+ * Hook to capture and persist UTM parameters from URL
27
+ * Stores in sessionStorage so they persist across navigation
28
+ * @returns Current UTM parameters
29
+ */
30
+ declare function useUtmParams(): UtmParams;
31
+
32
+ export { type UtmParams, useGtmEvent, useUtmParams };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Hook to send events to Google Tag Manager
3
+ * Assumes GTM is initialized in the parent app via @next/third-parties/google
4
+ * @example
5
+ * const sendEvent = useGtmEvent();
6
+ * sendEvent('button_click', { text: 'Sign Up', href: '/signup' });
7
+ */
8
+ declare function useGtmEvent(): (eventName: string, data?: Record<string, any>) => void;
9
+ declare global {
10
+ interface Window {
11
+ gtag?: (...args: any[]) => void;
12
+ }
13
+ }
14
+
15
+ /**
16
+ * UTM parameter object
17
+ */
18
+ interface UtmParams {
19
+ source?: string;
20
+ medium?: string;
21
+ campaign?: string;
22
+ content?: string;
23
+ term?: string;
24
+ }
25
+ /**
26
+ * Hook to capture and persist UTM parameters from URL
27
+ * Stores in sessionStorage so they persist across navigation
28
+ * @returns Current UTM parameters
29
+ */
30
+ declare function useUtmParams(): UtmParams;
31
+
32
+ export { type UtmParams, useGtmEvent, useUtmParams };
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // hooks/index.ts
21
+ var hooks_exports = {};
22
+ __export(hooks_exports, {
23
+ useGtmEvent: () => useGtmEvent,
24
+ useUtmParams: () => useUtmParams
25
+ });
26
+ module.exports = __toCommonJS(hooks_exports);
27
+
28
+ // hooks/useGtmEvent.ts
29
+ function useGtmEvent() {
30
+ return (eventName, data) => {
31
+ if (typeof window === "undefined") return;
32
+ if (typeof window.gtag === "function") {
33
+ window.gtag("event", eventName, data || {});
34
+ }
35
+ };
36
+ }
37
+
38
+ // hooks/useUtmParams.ts
39
+ var import_react = require("react");
40
+ function useUtmParams() {
41
+ const [utmParams, setUtmParams] = (0, import_react.useState)({});
42
+ (0, import_react.useEffect)(() => {
43
+ if (typeof window === "undefined") return;
44
+ const urlParams = new URLSearchParams(window.location.search);
45
+ const source = urlParams.get("utm_source");
46
+ const medium = urlParams.get("utm_medium");
47
+ const campaign = urlParams.get("utm_campaign");
48
+ const content = urlParams.get("utm_content");
49
+ const term = urlParams.get("utm_term");
50
+ const params = {};
51
+ if (source) {
52
+ params.source = source;
53
+ sessionStorage.setItem("utm_source", source);
54
+ } else {
55
+ const stored = sessionStorage.getItem("utm_source");
56
+ if (stored) params.source = stored;
57
+ }
58
+ if (medium) {
59
+ params.medium = medium;
60
+ sessionStorage.setItem("utm_medium", medium);
61
+ } else {
62
+ const stored = sessionStorage.getItem("utm_medium");
63
+ if (stored) params.medium = stored;
64
+ }
65
+ if (campaign) {
66
+ params.campaign = campaign;
67
+ sessionStorage.setItem("utm_campaign", campaign);
68
+ } else {
69
+ const stored = sessionStorage.getItem("utm_campaign");
70
+ if (stored) params.campaign = stored;
71
+ }
72
+ if (content) {
73
+ params.content = content;
74
+ sessionStorage.setItem("utm_content", content);
75
+ } else {
76
+ const stored = sessionStorage.getItem("utm_content");
77
+ if (stored) params.content = stored;
78
+ }
79
+ if (term) {
80
+ params.term = term;
81
+ sessionStorage.setItem("utm_term", term);
82
+ } else {
83
+ const stored = sessionStorage.getItem("utm_term");
84
+ if (stored) params.term = stored;
85
+ }
86
+ setUtmParams(params);
87
+ }, []);
88
+ return utmParams;
89
+ }
90
+ // Annotate the CommonJS export names for ESM import in node:
91
+ 0 && (module.exports = {
92
+ useGtmEvent,
93
+ useUtmParams
94
+ });
@@ -0,0 +1,8 @@
1
+ import {
2
+ useGtmEvent,
3
+ useUtmParams
4
+ } from "../chunk-QSWQDR6M.mjs";
5
+ export {
6
+ useGtmEvent,
7
+ useUtmParams
8
+ };
package/dist/index.js CHANGED
@@ -332,21 +332,8 @@ function Paragraph({
332
332
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id, style, children: resolvedText });
333
333
  }
334
334
 
335
- // hooks/useGtmEvent.ts
336
- function useGtmEvent() {
337
- return (eventName, data) => {
338
- if (typeof window === "undefined") return;
339
- if (typeof window.gtag !== "function") {
340
- console.warn("GTM not initialized. Make sure @next/third-parties/google GoogleTagManager is added to your layout.");
341
- return;
342
- }
343
- const eventData = {
344
- event: eventName,
345
- ...data && { value: data }
346
- };
347
- window.gtag("event", eventName, data || {});
348
- };
349
- }
335
+ // components/page/Button.tsx
336
+ var import_google = require("@next/third-parties/google");
350
337
 
351
338
  // hooks/useUtmParams.ts
352
339
  var import_react3 = require("react");
@@ -437,7 +424,6 @@ function Button({
437
424
  }) {
438
425
  const { resolveColor: resolveColor2 } = useTheme();
439
426
  const { getEntryValue } = useEntries();
440
- const sendEvent = useGtmEvent();
441
427
  const utm = useUtmParams();
442
428
  const resolvedText = (() => {
443
429
  if (!text) return "Button";
@@ -451,11 +437,16 @@ function Button({
451
437
  return "Button";
452
438
  })();
453
439
  const handleClick = () => {
454
- sendEvent("button_click", {
455
- text: resolvedText,
456
- href: href || void 0,
457
- variant,
458
- ...utm
440
+ const sessionId = typeof window !== "undefined" ? sessionStorage.getItem("session_id") : null;
441
+ (0, import_google.sendGTMEvent)({
442
+ event: "button_click",
443
+ value: {
444
+ text: resolvedText,
445
+ href: href || void 0,
446
+ variant,
447
+ session_id: sessionId,
448
+ ...utm
449
+ }
459
450
  });
460
451
  };
461
452
  const resolvedColor = (() => {
@@ -670,6 +661,7 @@ function Image({
670
661
 
671
662
  // components/page/ImageCarousel.tsx
672
663
  var import_react4 = require("react");
664
+ var import_google2 = require("@next/third-parties/google");
673
665
  var import_jsx_runtime7 = require("react/jsx-runtime");
674
666
  var aspectRatioMap2 = {
675
667
  "16:9": "16 / 9",
@@ -698,7 +690,6 @@ function ImageCarousel({
698
690
  }) {
699
691
  const [currentIndex, setCurrentIndex] = (0, import_react4.useState)(0);
700
692
  const { resolveColor: resolveColor2 } = useTheme();
701
- const sendEvent = useGtmEvent();
702
693
  const utm = useUtmParams();
703
694
  const resolvedArrowColor = (() => {
704
695
  if (!arrowColor) return { color: "#FFFFFF", opacity: 100 };
@@ -722,30 +713,39 @@ function ImageCarousel({
722
713
  const goToPrevious = () => {
723
714
  const newIndex = currentIndex === 0 ? images.length - 1 : currentIndex - 1;
724
715
  setCurrentIndex(newIndex);
725
- sendEvent("carousel_navigate", {
726
- direction: "previous",
727
- slideIndex: newIndex,
728
- totalSlides: images.length,
729
- ...utm
716
+ (0, import_google2.sendGTMEvent)({
717
+ event: "carousel_navigate",
718
+ value: {
719
+ direction: "previous",
720
+ slideIndex: newIndex,
721
+ totalSlides: images.length,
722
+ ...utm
723
+ }
730
724
  });
731
725
  };
732
726
  const goToNext = () => {
733
727
  const newIndex = currentIndex === images.length - 1 ? 0 : currentIndex + 1;
734
728
  setCurrentIndex(newIndex);
735
- sendEvent("carousel_navigate", {
736
- direction: "next",
737
- slideIndex: newIndex,
738
- totalSlides: images.length,
739
- ...utm
729
+ (0, import_google2.sendGTMEvent)({
730
+ event: "carousel_navigate",
731
+ value: {
732
+ direction: "next",
733
+ slideIndex: newIndex,
734
+ totalSlides: images.length,
735
+ ...utm
736
+ }
740
737
  });
741
738
  };
742
739
  const goToSlide = (index) => {
743
740
  setCurrentIndex(index);
744
- sendEvent("carousel_navigate", {
745
- direction: "direct",
746
- slideIndex: index,
747
- totalSlides: images.length,
748
- ...utm
741
+ (0, import_google2.sendGTMEvent)({
742
+ event: "carousel_navigate",
743
+ value: {
744
+ direction: "direct",
745
+ slideIndex: index,
746
+ totalSlides: images.length,
747
+ ...utm
748
+ }
749
749
  });
750
750
  };
751
751
  if (images.length === 0) {
@@ -1875,6 +1875,7 @@ function FeatureGrid({
1875
1875
 
1876
1876
  // components/page/Footer.tsx
1877
1877
  var import_lucide_react3 = require("lucide-react");
1878
+ var import_google3 = require("@next/third-parties/google");
1878
1879
  var import_jsx_runtime20 = require("react/jsx-runtime");
1879
1880
  function Footer({
1880
1881
  logo,
@@ -1887,7 +1888,6 @@ function Footer({
1887
1888
  puck
1888
1889
  }) {
1889
1890
  const DropZone = puck?.renderDropZone;
1890
- const sendEvent = useGtmEvent();
1891
1891
  const utm = useUtmParams();
1892
1892
  const getSocialPlatform = (url) => {
1893
1893
  if (url.includes("facebook")) return "facebook";
@@ -1897,17 +1897,22 @@ function Footer({
1897
1897
  };
1898
1898
  const handleSocialClick = (url) => {
1899
1899
  const platform = getSocialPlatform(url);
1900
- sendEvent("social_click", {
1901
- platform,
1902
- url,
1903
- ...utm
1900
+ (0, import_google3.sendGTMEvent)({
1901
+ event: "social_click",
1902
+ value: {
1903
+ platform,
1904
+ url,
1905
+ ...utm
1906
+ }
1904
1907
  });
1905
1908
  };
1906
1909
  const socialLinks = [
1907
1910
  { url: facebookUrl, Icon: import_lucide_react3.Facebook },
1908
1911
  { url: instagramUrl, Icon: import_lucide_react3.Instagram },
1909
1912
  { url: twitterUrl, Icon: import_lucide_react3.Twitter }
1910
- ].filter((link) => !!link.url);
1913
+ ].filter(
1914
+ (link) => !!link.url
1915
+ );
1911
1916
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1912
1917
  "footer",
1913
1918
  {
@@ -1938,6 +1943,7 @@ function Footer({
1938
1943
  var import_react5 = require("react");
1939
1944
  var import_link = __toESM(require("next/link"));
1940
1945
  var import_lucide_react4 = require("lucide-react");
1946
+ var import_google4 = require("@next/third-parties/google");
1941
1947
  var import_jsx_runtime21 = require("react/jsx-runtime");
1942
1948
  function Topbar({
1943
1949
  logo,
@@ -1950,22 +1956,27 @@ function Topbar({
1950
1956
  }) {
1951
1957
  const DropZone = puck?.renderDropZone;
1952
1958
  const [mobileMenuOpen, setMobileMenuOpen] = (0, import_react5.useState)(false);
1953
- const sendEvent = useGtmEvent();
1954
1959
  const utm = useUtmParams();
1955
1960
  const handleNavClick = (item) => {
1956
- sendEvent("nav_click", {
1957
- name: item.name,
1958
- url: item.url,
1959
- linkType: item.linkType || "internal",
1960
- ...utm
1961
+ (0, import_google4.sendGTMEvent)({
1962
+ event: "nav_click",
1963
+ value: {
1964
+ name: item.name,
1965
+ url: item.url,
1966
+ linkType: item.linkType || "internal",
1967
+ ...utm
1968
+ }
1961
1969
  });
1962
1970
  };
1963
1971
  const handleMobileMenuToggle = () => {
1964
1972
  const newState = !mobileMenuOpen;
1965
1973
  setMobileMenuOpen(newState);
1966
- sendEvent("mobile_menu_toggle", {
1967
- open: newState,
1968
- ...utm
1974
+ (0, import_google4.sendGTMEvent)({
1975
+ event: "mobile_menu_toggle",
1976
+ value: {
1977
+ open: newState,
1978
+ ...utm
1979
+ }
1969
1980
  });
1970
1981
  };
1971
1982
  const renderLink = (item, index) => {
@@ -2024,11 +2035,14 @@ function Topbar({
2024
2035
  {
2025
2036
  href: logoUrl,
2026
2037
  className: "flex-shrink-0",
2027
- onClick: () => sendEvent("nav_click", {
2028
- name: "logo",
2029
- url: logoUrl,
2030
- linkType: "internal",
2031
- ...utm
2038
+ onClick: () => (0, import_google4.sendGTMEvent)({
2039
+ event: "nav_click",
2040
+ value: {
2041
+ name: "logo",
2042
+ url: logoUrl,
2043
+ linkType: "internal",
2044
+ ...utm
2045
+ }
2032
2046
  }),
2033
2047
  children: logo ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-xl font-bold", children: "Logo" })
2034
2048
  }
@@ -2060,6 +2074,7 @@ function Topbar({
2060
2074
  // components/page/Popup.tsx
2061
2075
  var import_react6 = require("react");
2062
2076
  var import_lucide_react5 = require("lucide-react");
2077
+ var import_google5 = require("@next/third-parties/google");
2063
2078
  var import_jsx_runtime22 = require("react/jsx-runtime");
2064
2079
  function Icon2({ name, ...props }) {
2065
2080
  const formatted = name.charAt(0).toUpperCase() + name.slice(1);
@@ -2089,19 +2104,24 @@ function Popup({
2089
2104
  puck
2090
2105
  }) {
2091
2106
  const [isOpen, setIsOpen] = (0, import_react6.useState)(false);
2092
- const sendEvent = useGtmEvent();
2093
2107
  const utm = useUtmParams();
2094
2108
  const handleOpen = () => {
2095
2109
  setIsOpen(true);
2096
- sendEvent("popup_open", {
2097
- ctaText,
2098
- type: textLink ? "link" : "button",
2099
- ...utm
2110
+ (0, import_google5.sendGTMEvent)({
2111
+ event: "popup_open",
2112
+ value: {
2113
+ ctaText,
2114
+ type: textLink ? "link" : "button",
2115
+ ...utm
2116
+ }
2100
2117
  });
2101
2118
  };
2102
2119
  const handleClose = () => {
2103
2120
  setIsOpen(false);
2104
- sendEvent("popup_close", { ctaText, ...utm });
2121
+ (0, import_google5.sendGTMEvent)({
2122
+ event: "popup_close",
2123
+ value: { ctaText, ...utm }
2124
+ });
2105
2125
  };
2106
2126
  const trigger = textLink ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2107
2127
  "button",
package/dist/index.mjs CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  Topbar,
21
21
  VideoEmbed,
22
22
  availableIcons
23
- } from "./chunk-RVOG4KFI.mjs";
23
+ } from "./chunk-R7TH6TWG.mjs";
24
24
  import {
25
25
  allColorPresets,
26
26
  neutralColors
@@ -41,6 +41,7 @@ import {
41
41
  spacingScale,
42
42
  useTheme
43
43
  } from "./chunk-TTKY3YGP.mjs";
44
+ import "./chunk-QSWQDR6M.mjs";
44
45
  export {
45
46
  Button,
46
47
  Card,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mission-studio/puck",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "description": "A collection of puck components and configurations for internal use at Mission Studio.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,6 +37,11 @@
37
37
  "import": "./dist/config-entry.mjs",
38
38
  "require": "./dist/config-entry.js"
39
39
  },
40
+ "./hooks": {
41
+ "types": "./dist/hooks/index.d.ts",
42
+ "import": "./dist/hooks/index.mjs",
43
+ "require": "./dist/hooks/index.js"
44
+ },
40
45
  "./styles.css": "./dist/styles.css"
41
46
  },
42
47
  "scripts": {
@@ -52,6 +57,7 @@
52
57
  },
53
58
  "dependencies": {
54
59
  "@measured/puck": "^0.20.2",
60
+ "@next/third-parties": "^16.1.6",
55
61
  "@radix-ui/react-label": "^2.1.8",
56
62
  "@radix-ui/react-slot": "^1.2.4",
57
63
  "@tailwindcss/postcss": "^4.1.18",