@mission-studio/puck 1.0.0 → 1.0.2

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.
@@ -158,6 +158,75 @@ function Paragraph({
158
158
  return /* @__PURE__ */ jsx2("p", { id, style, children: resolvedText });
159
159
  }
160
160
 
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
+
161
230
  // components/page/Button.tsx
162
231
  import { jsx as jsx3 } from "react/jsx-runtime";
163
232
  var sizeStyles = {
@@ -194,6 +263,8 @@ function Button({
194
263
  }) {
195
264
  const { resolveColor: resolveColor2 } = useTheme();
196
265
  const { getEntryValue } = useEntries();
266
+ const sendEvent = useGtmEvent();
267
+ const utm = useUtmParams();
197
268
  const resolvedText = (() => {
198
269
  if (!text) return "Button";
199
270
  if (typeof text === "string") return text;
@@ -205,6 +276,14 @@ function Button({
205
276
  }
206
277
  return "Button";
207
278
  })();
279
+ const handleClick = () => {
280
+ sendEvent("button_click", {
281
+ text: resolvedText,
282
+ href: href || void 0,
283
+ variant,
284
+ ...utm
285
+ });
286
+ };
208
287
  const resolvedColor = (() => {
209
288
  if (!color) return resolveColor2("primary");
210
289
  if (typeof color === "string") return { color, opacity: 100 };
@@ -278,11 +357,12 @@ function Button({
278
357
  target,
279
358
  style,
280
359
  rel: target === "_blank" ? "noopener noreferrer" : void 0,
360
+ onClick: handleClick,
281
361
  children: resolvedText
282
362
  }
283
363
  ) });
284
364
  }
285
- return /* @__PURE__ */ jsx3("div", { style: wrapperStyle, children: /* @__PURE__ */ jsx3("button", { id, type: "button", style, children: resolvedText }) });
365
+ return /* @__PURE__ */ jsx3("div", { style: wrapperStyle, children: /* @__PURE__ */ jsx3("button", { id, type: "button", style, onClick: handleClick, children: resolvedText }) });
286
366
  }
287
367
 
288
368
  // components/page/Image.tsx
@@ -415,7 +495,7 @@ function Image({
415
495
  }
416
496
 
417
497
  // components/page/ImageCarousel.tsx
418
- import { useState } from "react";
498
+ import { useState as useState2 } from "react";
419
499
  import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
420
500
  var aspectRatioMap2 = {
421
501
  "16:9": "16 / 9",
@@ -442,8 +522,10 @@ function ImageCarousel({
442
522
  dotColor,
443
523
  id
444
524
  }) {
445
- const [currentIndex, setCurrentIndex] = useState(0);
525
+ const [currentIndex, setCurrentIndex] = useState2(0);
446
526
  const { resolveColor: resolveColor2 } = useTheme();
527
+ const sendEvent = useGtmEvent();
528
+ const utm = useUtmParams();
447
529
  const resolvedArrowColor = (() => {
448
530
  if (!arrowColor) return { color: "#FFFFFF", opacity: 100 };
449
531
  if (typeof arrowColor === "string")
@@ -464,13 +546,33 @@ function ImageCarousel({
464
546
  return resolveColor2("primary");
465
547
  })();
466
548
  const goToPrevious = () => {
467
- setCurrentIndex((prev) => prev === 0 ? images.length - 1 : prev - 1);
549
+ const newIndex = currentIndex === 0 ? images.length - 1 : currentIndex - 1;
550
+ setCurrentIndex(newIndex);
551
+ sendEvent("carousel_navigate", {
552
+ direction: "previous",
553
+ slideIndex: newIndex,
554
+ totalSlides: images.length,
555
+ ...utm
556
+ });
468
557
  };
469
558
  const goToNext = () => {
470
- setCurrentIndex((prev) => prev === images.length - 1 ? 0 : prev + 1);
559
+ const newIndex = currentIndex === images.length - 1 ? 0 : currentIndex + 1;
560
+ setCurrentIndex(newIndex);
561
+ sendEvent("carousel_navigate", {
562
+ direction: "next",
563
+ slideIndex: newIndex,
564
+ totalSlides: images.length,
565
+ ...utm
566
+ });
471
567
  };
472
568
  const goToSlide = (index) => {
473
569
  setCurrentIndex(index);
570
+ sendEvent("carousel_navigate", {
571
+ direction: "direct",
572
+ slideIndex: index,
573
+ totalSlides: images.length,
574
+ ...utm
575
+ });
474
576
  };
475
577
  if (images.length === 0) {
476
578
  return /* @__PURE__ */ jsx5(
@@ -1575,11 +1677,27 @@ function Footer({
1575
1677
  puck
1576
1678
  }) {
1577
1679
  const DropZone = puck?.renderDropZone;
1680
+ const sendEvent = useGtmEvent();
1681
+ const utm = useUtmParams();
1682
+ const getSocialPlatform = (url) => {
1683
+ if (url.includes("facebook")) return "facebook";
1684
+ if (url.includes("instagram")) return "instagram";
1685
+ if (url.includes("twitter")) return "twitter";
1686
+ return "social";
1687
+ };
1688
+ const handleSocialClick = (url) => {
1689
+ const platform = getSocialPlatform(url);
1690
+ sendEvent("social_click", {
1691
+ platform,
1692
+ url,
1693
+ ...utm
1694
+ });
1695
+ };
1578
1696
  const socialLinks = [
1579
1697
  { url: facebookUrl, Icon: Facebook },
1580
1698
  { url: instagramUrl, Icon: Instagram },
1581
1699
  { url: twitterUrl, Icon: Twitter }
1582
- ].filter((link) => link.url);
1700
+ ].filter((link) => !!link.url);
1583
1701
  return /* @__PURE__ */ jsx18(
1584
1702
  "footer",
1585
1703
  {
@@ -1595,6 +1713,7 @@ function Footer({
1595
1713
  target: "_blank",
1596
1714
  rel: "noopener noreferrer",
1597
1715
  className: "transition-opacity hover:opacity-80",
1716
+ onClick: () => handleSocialClick(url),
1598
1717
  children: /* @__PURE__ */ jsx18(Icon3, { size: 24, style: { color: textColor } })
1599
1718
  },
1600
1719
  index
@@ -1606,7 +1725,7 @@ function Footer({
1606
1725
  }
1607
1726
 
1608
1727
  // components/page/Topbar.tsx
1609
- import { useState as useState2 } from "react";
1728
+ import { useState as useState3 } from "react";
1610
1729
  import Link from "next/link";
1611
1730
  import { Menu, X } from "lucide-react";
1612
1731
  import { jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
@@ -1620,7 +1739,25 @@ function Topbar({
1620
1739
  puck
1621
1740
  }) {
1622
1741
  const DropZone = puck?.renderDropZone;
1623
- const [mobileMenuOpen, setMobileMenuOpen] = useState2(false);
1742
+ const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
1743
+ const sendEvent = useGtmEvent();
1744
+ const utm = useUtmParams();
1745
+ const handleNavClick = (item) => {
1746
+ sendEvent("nav_click", {
1747
+ name: item.name,
1748
+ url: item.url,
1749
+ linkType: item.linkType || "internal",
1750
+ ...utm
1751
+ });
1752
+ };
1753
+ const handleMobileMenuToggle = () => {
1754
+ const newState = !mobileMenuOpen;
1755
+ setMobileMenuOpen(newState);
1756
+ sendEvent("mobile_menu_toggle", {
1757
+ open: newState,
1758
+ ...utm
1759
+ });
1760
+ };
1624
1761
  const renderLink = (item, index) => {
1625
1762
  const className = "hover:opacity-80 transition-opacity";
1626
1763
  if (item.linkType === "external") {
@@ -1631,15 +1768,34 @@ function Topbar({
1631
1768
  target: "_blank",
1632
1769
  rel: "noopener noreferrer",
1633
1770
  className,
1771
+ onClick: () => handleNavClick(item),
1634
1772
  children: item.name
1635
1773
  },
1636
1774
  index
1637
1775
  );
1638
1776
  }
1639
1777
  if (item.linkType === "scrollTo") {
1640
- return /* @__PURE__ */ jsx19("a", { href: item.url, className, children: item.name }, index);
1778
+ return /* @__PURE__ */ jsx19(
1779
+ "a",
1780
+ {
1781
+ href: item.url,
1782
+ className,
1783
+ onClick: () => handleNavClick(item),
1784
+ children: item.name
1785
+ },
1786
+ index
1787
+ );
1641
1788
  }
1642
- return /* @__PURE__ */ jsx19(Link, { href: item.url, className, children: item.name }, index);
1789
+ return /* @__PURE__ */ jsx19(
1790
+ Link,
1791
+ {
1792
+ href: item.url,
1793
+ className,
1794
+ onClick: () => handleNavClick(item),
1795
+ children: item.name
1796
+ },
1797
+ index
1798
+ );
1643
1799
  };
1644
1800
  return /* @__PURE__ */ jsxs8(
1645
1801
  "nav",
@@ -1653,19 +1809,25 @@ function Topbar({
1653
1809
  className: "mx-auto flex items-center justify-between",
1654
1810
  style: { maxWidth },
1655
1811
  children: [
1656
- /* @__PURE__ */ jsx19(Link, { href: logoUrl, className: "flex-shrink-0", children: logo ? /* @__PURE__ */ jsx19("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ jsx19("span", { className: "text-xl font-bold", children: "Logo" }) }),
1812
+ /* @__PURE__ */ jsx19(
1813
+ Link,
1814
+ {
1815
+ href: logoUrl,
1816
+ className: "flex-shrink-0",
1817
+ onClick: () => sendEvent("nav_click", {
1818
+ name: "logo",
1819
+ url: logoUrl,
1820
+ linkType: "internal",
1821
+ ...utm
1822
+ }),
1823
+ children: logo ? /* @__PURE__ */ jsx19("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ jsx19("span", { className: "text-xl font-bold", children: "Logo" })
1824
+ }
1825
+ ),
1657
1826
  /* @__PURE__ */ jsxs8("div", { className: "hidden items-center gap-8 md:flex", children: [
1658
1827
  navItems.map(renderLink),
1659
1828
  DropZone && /* @__PURE__ */ jsx19(DropZone, { zone: "cta" })
1660
1829
  ] }),
1661
- /* @__PURE__ */ jsx19(
1662
- "button",
1663
- {
1664
- className: "md:hidden",
1665
- onClick: () => setMobileMenuOpen(!mobileMenuOpen),
1666
- children: mobileMenuOpen ? /* @__PURE__ */ jsx19(X, { size: 24 }) : /* @__PURE__ */ jsx19(Menu, { size: 24 })
1667
- }
1668
- )
1830
+ /* @__PURE__ */ jsx19("button", { className: "md:hidden", onClick: handleMobileMenuToggle, children: mobileMenuOpen ? /* @__PURE__ */ jsx19(X, { size: 24 }) : /* @__PURE__ */ jsx19(Menu, { size: 24 }) })
1669
1831
  ]
1670
1832
  }
1671
1833
  ),
@@ -1686,7 +1848,7 @@ function Topbar({
1686
1848
  }
1687
1849
 
1688
1850
  // components/page/Popup.tsx
1689
- import { useState as useState3 } from "react";
1851
+ import { useState as useState4 } from "react";
1690
1852
  import { icons as icons4, X as X2 } from "lucide-react";
1691
1853
  import { Fragment as Fragment2, jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
1692
1854
  function Icon2({ name, ...props }) {
@@ -1716,11 +1878,25 @@ function Popup({
1716
1878
  textLink = false,
1717
1879
  puck
1718
1880
  }) {
1719
- const [isOpen, setIsOpen] = useState3(false);
1881
+ const [isOpen, setIsOpen] = useState4(false);
1882
+ const sendEvent = useGtmEvent();
1883
+ const utm = useUtmParams();
1884
+ const handleOpen = () => {
1885
+ setIsOpen(true);
1886
+ sendEvent("popup_open", {
1887
+ ctaText,
1888
+ type: textLink ? "link" : "button",
1889
+ ...utm
1890
+ });
1891
+ };
1892
+ const handleClose = () => {
1893
+ setIsOpen(false);
1894
+ sendEvent("popup_close", { ctaText, ...utm });
1895
+ };
1720
1896
  const trigger = textLink ? /* @__PURE__ */ jsx20(
1721
1897
  "button",
1722
1898
  {
1723
- onClick: () => setIsOpen(true),
1899
+ onClick: handleOpen,
1724
1900
  className: "underline hover:opacity-80",
1725
1901
  style: { color: buttonColor },
1726
1902
  children: ctaText
@@ -1728,7 +1904,7 @@ function Popup({
1728
1904
  ) : /* @__PURE__ */ jsxs9(
1729
1905
  "button",
1730
1906
  {
1731
- onClick: () => setIsOpen(true),
1907
+ onClick: handleOpen,
1732
1908
  className: cn(
1733
1909
  "flex items-center gap-2 rounded-full font-medium",
1734
1910
  sizeMap8[size]
@@ -1747,7 +1923,7 @@ function Popup({
1747
1923
  "div",
1748
1924
  {
1749
1925
  className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
1750
- onClick: () => setIsOpen(false),
1926
+ onClick: handleClose,
1751
1927
  children: /* @__PURE__ */ jsxs9(
1752
1928
  "div",
1753
1929
  {
@@ -1760,7 +1936,7 @@ function Popup({
1760
1936
  /* @__PURE__ */ jsx20(
1761
1937
  "button",
1762
1938
  {
1763
- onClick: () => setIsOpen(false),
1939
+ onClick: handleClose,
1764
1940
  className: "absolute top-4 right-4 text-gray-500 hover:text-gray-700",
1765
1941
  children: /* @__PURE__ */ jsx20(X2, { size: 24 })
1766
1942
  }