@mosierdata/emdash-plugin-analytics 1.0.1 → 1.0.3

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/dist/admin.mjs CHANGED
@@ -1,10 +1,36 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import { usePluginAPI } from "@emdash-cms/admin";
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { API_BASE, apiFetch, parseApiResponse } from "@emdash-cms/admin";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
 
5
+ //#region src/lib/usePluginAPI.ts
6
+ const PLUGIN_ID = "roi-insights";
7
+ /**
8
+ * Shim for usePluginAPI — @emdash-cms/admin 0.1.0 does not export this hook.
9
+ * Builds a stable { get, post } helper that routes through the plugin API
10
+ * at /_emdash/api/plugins/roi-insights/<path> using the admin apiFetch
11
+ * wrapper (adds CSRF header) and parseApiResponse (handles error shapes).
12
+ *
13
+ * Remove this file once @emdash-cms/admin exports usePluginAPI natively.
14
+ */
15
+ function usePluginAPI() {
16
+ return useMemo(() => ({
17
+ get(path) {
18
+ return apiFetch(`${API_BASE}/plugins/${PLUGIN_ID}/${path}`).then((r) => parseApiResponse(r));
19
+ },
20
+ post(path, body) {
21
+ return apiFetch(`${API_BASE}/plugins/${PLUGIN_ID}/${path}`, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json" },
24
+ body: JSON.stringify(body)
25
+ }).then((r) => parseApiResponse(r));
26
+ }
27
+ }), []);
28
+ }
29
+
30
+ //#endregion
5
31
  //#region src/admin/Dashboard.tsx
6
32
  const DASHBOARD_BASE_URL = "https://my.roiknowledge.com/embed";
7
- function AdminDashboard() {
33
+ function AdminDashboard({ onNavigateToSettings }) {
8
34
  const api = usePluginAPI();
9
35
  const [dashboardUrl, setDashboardUrl] = useState(null);
10
36
  const [error, setError] = useState(null);
@@ -41,9 +67,17 @@ function AdminDashboard() {
41
67
  children: [
42
68
  /* @__PURE__ */ jsx("h3", { children: "ROI Insights Dashboard" }),
43
69
  /* @__PURE__ */ jsx("p", { children: error }),
44
- /* @__PURE__ */ jsx("a", {
45
- href: "../settings",
46
- children: "Go to Settings →"
70
+ onNavigateToSettings && /* @__PURE__ */ jsx("button", {
71
+ onClick: onNavigateToSettings,
72
+ style: {
73
+ background: "none",
74
+ border: "none",
75
+ color: "#2563eb",
76
+ cursor: "pointer",
77
+ padding: 0,
78
+ fontSize: "inherit"
79
+ },
80
+ children: "Go to License & Google →"
47
81
  })
48
82
  ]
49
83
  });
@@ -74,146 +108,6 @@ function AdminDashboard() {
74
108
  });
75
109
  }
76
110
 
77
- //#endregion
78
- //#region src/admin/Settings.tsx
79
- function SettingsPage() {
80
- const api = usePluginAPI();
81
- const [license, setLicense] = useState(null);
82
- const [googleConnected, setGoogleConnected] = useState(false);
83
- const [activating, setActivating] = useState(false);
84
- const [message, setMessage] = useState("");
85
- useEffect(() => {
86
- Promise.all([api.get("license/status").then(setLicense), api.get("google-oauth/status").then((r) => setGoogleConnected(r.connected))]);
87
- }, [api]);
88
- useEffect(() => {
89
- const params = new URLSearchParams(window.location.search);
90
- if (params.get("oauth_callback") !== "1" || params.get("google_connected") !== "true") return;
91
- api.post("google-oauth/connected", {}).then(() => {
92
- setGoogleConnected(true);
93
- setMessage("Google Services connected successfully.");
94
- });
95
- }, [api]);
96
- const handleActivateLicense = useCallback(async () => {
97
- setActivating(true);
98
- setMessage("");
99
- try {
100
- const fresh = await api.get("license/validate");
101
- setLicense(fresh);
102
- setMessage(fresh.isValid ? "License activated." : `License invalid: ${fresh.reason ?? "unknown"}`);
103
- } catch {
104
- setMessage("Error validating license.");
105
- } finally {
106
- setActivating(false);
107
- }
108
- }, [api]);
109
- const handleGoogleOAuth = useCallback(async () => {
110
- try {
111
- const result = await api.post("google-oauth/initiate", {});
112
- if (result.error) {
113
- setMessage(result.error);
114
- return;
115
- }
116
- if (result.authUrl) window.location.href = result.authUrl;
117
- } catch {
118
- setMessage("Error initiating Google connection.");
119
- }
120
- }, [api]);
121
- const licenseStatusLabel = () => {
122
- if (!license) return "Not checked";
123
- if (license.isFallback) return "Active (offline)";
124
- if (!license.isValid) return `Invalid — ${license.reason ?? "unknown"}`;
125
- return `Active — ${license.tier ?? "free"} tier`;
126
- };
127
- return /* @__PURE__ */ jsxs("div", {
128
- style: {
129
- maxWidth: 640,
130
- padding: "1.5rem"
131
- },
132
- children: [
133
- /* @__PURE__ */ jsx("h2", { children: "License & Google" }),
134
- /* @__PURE__ */ jsxs("p", {
135
- style: {
136
- fontSize: "0.9rem",
137
- color: "#555",
138
- marginBottom: "1.5rem"
139
- },
140
- children: [
141
- "Enter your license key in the ",
142
- /* @__PURE__ */ jsx("strong", { children: "Settings" }),
143
- " tab above, then click Activate below."
144
- ]
145
- }),
146
- /* @__PURE__ */ jsxs("section", {
147
- style: { marginBottom: "1.5rem" },
148
- children: [
149
- /* @__PURE__ */ jsx("h3", { children: "License" }),
150
- license && /* @__PURE__ */ jsxs("p", {
151
- style: {
152
- fontSize: "0.9rem",
153
- marginBottom: "0.75rem"
154
- },
155
- children: ["Status: ", /* @__PURE__ */ jsx("strong", { children: licenseStatusLabel() })]
156
- }),
157
- /* @__PURE__ */ jsx("button", {
158
- onClick: () => void handleActivateLicense(),
159
- disabled: activating,
160
- children: activating ? "Activating…" : "Activate License"
161
- })
162
- ]
163
- }),
164
- /* @__PURE__ */ jsxs("section", {
165
- style: { marginBottom: "1.5rem" },
166
- children: [/* @__PURE__ */ jsx("h3", { children: "Google Services (Free Tier)" }), googleConnected ? /* @__PURE__ */ jsx("p", { children: "Connected to Google Analytics & Search Console ✅" }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", {
167
- style: {
168
- fontSize: "0.9rem",
169
- color: "#555"
170
- },
171
- children: "Grant read access to GA4 and Search Console. Tokens are stored securely on the MosierData backend."
172
- }), /* @__PURE__ */ jsx("button", {
173
- onClick: () => void handleGoogleOAuth(),
174
- children: "Connect Google Services"
175
- })] })]
176
- }),
177
- message && /* @__PURE__ */ jsx("p", {
178
- style: {
179
- fontSize: "0.9rem",
180
- color: "#444",
181
- marginTop: "1rem"
182
- },
183
- children: message
184
- }),
185
- license?.isValid && license.tier === "free" && /* @__PURE__ */ jsxs("div", {
186
- style: {
187
- marginTop: "2rem",
188
- padding: "1rem",
189
- background: "#f0f7ff",
190
- border: "1px solid #c0d8f5",
191
- borderRadius: 6
192
- },
193
- children: [
194
- /* @__PURE__ */ jsx("h4", {
195
- style: { margin: "0 0 0.5rem" },
196
- children: "Upgrade to Professional"
197
- }),
198
- /* @__PURE__ */ jsx("p", {
199
- style: {
200
- margin: "0 0 0.75rem",
201
- fontSize: "0.9rem"
202
- },
203
- children: "Unlock AI Call Transcription, Lead Scoring, and Call Recording."
204
- }),
205
- /* @__PURE__ */ jsx("a", {
206
- href: "https://quotedash.io/upgrade",
207
- target: "_blank",
208
- rel: "noreferrer",
209
- children: "Upgrade Now →"
210
- })
211
- ]
212
- })
213
- ]
214
- });
215
- }
216
-
217
111
  //#endregion
218
112
  //#region src/admin/TrackingSettings.tsx
219
113
  const HELP_CONTENT = {
@@ -1526,18 +1420,469 @@ function TrackingSettingsAdmin() {
1526
1420
  })] });
1527
1421
  }
1528
1422
 
1423
+ //#endregion
1424
+ //#region src/admin/Settings.tsx
1425
+ function SettingsPage() {
1426
+ const api = usePluginAPI();
1427
+ const [license, setLicense] = useState(null);
1428
+ const [googleConnected, setGoogleConnected] = useState(false);
1429
+ const [activating, setActivating] = useState(false);
1430
+ const [message, setMessage] = useState("");
1431
+ const [licenseKeyInput, setLicenseKeyInput] = useState("");
1432
+ const [savingKey, setSavingKey] = useState(false);
1433
+ const [advanced, setAdvanced] = useState({
1434
+ dniSwapNumber: "",
1435
+ dniScriptUrl: "",
1436
+ customHeadCode: "",
1437
+ customFooterCode: "",
1438
+ debug: false
1439
+ });
1440
+ const [savingAdvanced, setSavingAdvanced] = useState(false);
1441
+ useEffect(() => {
1442
+ Promise.all([
1443
+ api.get("license/status").then(setLicense),
1444
+ api.get("google-oauth/status").then((r) => setGoogleConnected(r.connected)),
1445
+ api.get("settings/load").then(setAdvanced)
1446
+ ]);
1447
+ }, [api]);
1448
+ useEffect(() => {
1449
+ const params = new URLSearchParams(window.location.search);
1450
+ if (params.get("oauth_callback") !== "1" || params.get("google_connected") !== "true") return;
1451
+ api.post("google-oauth/connected", {}).then(() => {
1452
+ setGoogleConnected(true);
1453
+ setMessage("Google Services connected successfully.");
1454
+ });
1455
+ }, [api]);
1456
+ const handleActivateLicense = useCallback(async () => {
1457
+ setActivating(true);
1458
+ setMessage("");
1459
+ try {
1460
+ const fresh = await api.get("license/validate");
1461
+ setLicense(fresh);
1462
+ setMessage(fresh.isValid ? "License activated." : `License invalid: ${fresh.reason ?? "unknown"}`);
1463
+ } catch {
1464
+ setMessage("Error validating license.");
1465
+ } finally {
1466
+ setActivating(false);
1467
+ }
1468
+ }, [api]);
1469
+ const handleSaveLicenseKey = useCallback(async () => {
1470
+ if (!licenseKeyInput.trim()) return;
1471
+ setSavingKey(true);
1472
+ setMessage("");
1473
+ try {
1474
+ await api.post("settings/save", { licenseKey: licenseKeyInput.trim() });
1475
+ setMessage("License key saved. Click Activate to validate.");
1476
+ setLicenseKeyInput("");
1477
+ } catch {
1478
+ setMessage("Error saving license key.");
1479
+ } finally {
1480
+ setSavingKey(false);
1481
+ }
1482
+ }, [api, licenseKeyInput]);
1483
+ const handleSaveAdvanced = useCallback(async () => {
1484
+ setSavingAdvanced(true);
1485
+ setMessage("");
1486
+ try {
1487
+ await api.post("settings/save", advanced);
1488
+ setMessage("Settings saved.");
1489
+ } catch {
1490
+ setMessage("Error saving settings.");
1491
+ } finally {
1492
+ setSavingAdvanced(false);
1493
+ }
1494
+ }, [api, advanced]);
1495
+ const handleGoogleOAuth = useCallback(async () => {
1496
+ try {
1497
+ const result = await api.post("google-oauth/initiate", {});
1498
+ if (result.error) {
1499
+ setMessage(result.error);
1500
+ return;
1501
+ }
1502
+ if (result.authUrl) window.location.href = result.authUrl;
1503
+ } catch {
1504
+ setMessage("Error initiating Google connection.");
1505
+ }
1506
+ }, [api]);
1507
+ const licenseStatusLabel = () => {
1508
+ if (!license) return "Not checked";
1509
+ if (license.isFallback) return "Active (offline)";
1510
+ if (!license.isValid) return `Invalid — ${license.reason ?? "unknown"}`;
1511
+ return `Active — ${license.tier ?? "free"} tier`;
1512
+ };
1513
+ return /* @__PURE__ */ jsxs("div", {
1514
+ style: {
1515
+ maxWidth: 640,
1516
+ padding: "1.5rem"
1517
+ },
1518
+ children: [
1519
+ /* @__PURE__ */ jsx("h2", { children: "License & Google" }),
1520
+ /* @__PURE__ */ jsxs("section", {
1521
+ style: { marginBottom: "1.5rem" },
1522
+ children: [
1523
+ /* @__PURE__ */ jsx("h3", { children: "License Key" }),
1524
+ /* @__PURE__ */ jsxs("p", {
1525
+ style: {
1526
+ fontSize: "0.9rem",
1527
+ color: "#555",
1528
+ marginBottom: "0.75rem"
1529
+ },
1530
+ children: ["Paste your license key from the MosierData portal. Prefix: ", /* @__PURE__ */ jsx("code", { children: "qdsh_" })]
1531
+ }),
1532
+ /* @__PURE__ */ jsxs("div", {
1533
+ style: {
1534
+ display: "flex",
1535
+ gap: "0.5rem",
1536
+ alignItems: "center"
1537
+ },
1538
+ children: [/* @__PURE__ */ jsx("input", {
1539
+ type: "password",
1540
+ value: licenseKeyInput,
1541
+ onChange: (e) => setLicenseKeyInput(e.target.value),
1542
+ placeholder: "qdsh_…",
1543
+ style: {
1544
+ flex: 1,
1545
+ padding: "0.5rem 0.75rem",
1546
+ border: "1px solid #d1d5db",
1547
+ borderRadius: 4,
1548
+ fontSize: "0.9rem"
1549
+ }
1550
+ }), /* @__PURE__ */ jsx("button", {
1551
+ onClick: () => void handleSaveLicenseKey(),
1552
+ disabled: savingKey || !licenseKeyInput.trim(),
1553
+ children: savingKey ? "Saving…" : "Save Key"
1554
+ })]
1555
+ })
1556
+ ]
1557
+ }),
1558
+ /* @__PURE__ */ jsxs("section", {
1559
+ style: { marginBottom: "1.5rem" },
1560
+ children: [
1561
+ /* @__PURE__ */ jsx("h3", { children: "License" }),
1562
+ license && /* @__PURE__ */ jsxs("p", {
1563
+ style: {
1564
+ fontSize: "0.9rem",
1565
+ marginBottom: "0.75rem"
1566
+ },
1567
+ children: ["Status: ", /* @__PURE__ */ jsx("strong", { children: licenseStatusLabel() })]
1568
+ }),
1569
+ /* @__PURE__ */ jsx("button", {
1570
+ onClick: () => void handleActivateLicense(),
1571
+ disabled: activating,
1572
+ children: activating ? "Activating…" : "Activate License"
1573
+ })
1574
+ ]
1575
+ }),
1576
+ /* @__PURE__ */ jsxs("section", {
1577
+ style: { marginBottom: "1.5rem" },
1578
+ children: [/* @__PURE__ */ jsx("h3", { children: "Google Services (Free Tier)" }), googleConnected ? /* @__PURE__ */ jsx("p", { children: "Connected to Google Analytics & Search Console ✅" }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", {
1579
+ style: {
1580
+ fontSize: "0.9rem",
1581
+ color: "#555"
1582
+ },
1583
+ children: "Grant read access to GA4 and Search Console. Tokens are stored securely on the MosierData backend."
1584
+ }), /* @__PURE__ */ jsx("button", {
1585
+ onClick: () => void handleGoogleOAuth(),
1586
+ children: "Connect Google Services"
1587
+ })] })]
1588
+ }),
1589
+ /* @__PURE__ */ jsxs("section", {
1590
+ style: { marginBottom: "1.5rem" },
1591
+ children: [
1592
+ /* @__PURE__ */ jsx("h3", { children: "Call Tracking (AvidTrak DNI)" }),
1593
+ /* @__PURE__ */ jsx("p", {
1594
+ style: {
1595
+ fontSize: "0.9rem",
1596
+ color: "#555",
1597
+ marginBottom: "0.75rem"
1598
+ },
1599
+ children: "Dynamic Number Insertion replaces a phone number on your site with a tracked number."
1600
+ }),
1601
+ /* @__PURE__ */ jsx("label", {
1602
+ style: {
1603
+ display: "block",
1604
+ fontSize: "0.875rem",
1605
+ marginBottom: "0.25rem"
1606
+ },
1607
+ children: "Phone Number to Swap"
1608
+ }),
1609
+ /* @__PURE__ */ jsx("input", {
1610
+ type: "text",
1611
+ value: advanced.dniSwapNumber,
1612
+ onChange: (e) => setAdvanced((a) => ({
1613
+ ...a,
1614
+ dniSwapNumber: e.target.value
1615
+ })),
1616
+ placeholder: "e.g. (555) 867-5309",
1617
+ style: {
1618
+ width: "100%",
1619
+ padding: "0.5rem 0.75rem",
1620
+ border: "1px solid #d1d5db",
1621
+ borderRadius: 4,
1622
+ fontSize: "0.9rem",
1623
+ marginBottom: "0.75rem",
1624
+ boxSizing: "border-box"
1625
+ }
1626
+ }),
1627
+ /* @__PURE__ */ jsx("label", {
1628
+ style: {
1629
+ display: "block",
1630
+ fontSize: "0.875rem",
1631
+ marginBottom: "0.25rem"
1632
+ },
1633
+ children: "AvidTrak Script URL"
1634
+ }),
1635
+ /* @__PURE__ */ jsx("input", {
1636
+ type: "text",
1637
+ value: advanced.dniScriptUrl,
1638
+ onChange: (e) => setAdvanced((a) => ({
1639
+ ...a,
1640
+ dniScriptUrl: e.target.value
1641
+ })),
1642
+ placeholder: "https://…",
1643
+ style: {
1644
+ width: "100%",
1645
+ padding: "0.5rem 0.75rem",
1646
+ border: "1px solid #d1d5db",
1647
+ borderRadius: 4,
1648
+ fontSize: "0.9rem",
1649
+ boxSizing: "border-box"
1650
+ }
1651
+ })
1652
+ ]
1653
+ }),
1654
+ /* @__PURE__ */ jsxs("section", {
1655
+ style: { marginBottom: "1.5rem" },
1656
+ children: [
1657
+ /* @__PURE__ */ jsx("h3", { children: "Custom Code" }),
1658
+ /* @__PURE__ */ jsx("label", {
1659
+ style: {
1660
+ display: "block",
1661
+ fontSize: "0.875rem",
1662
+ marginBottom: "0.25rem"
1663
+ },
1664
+ children: "Custom <head> Code"
1665
+ }),
1666
+ /* @__PURE__ */ jsx("textarea", {
1667
+ value: advanced.customHeadCode,
1668
+ onChange: (e) => setAdvanced((a) => ({
1669
+ ...a,
1670
+ customHeadCode: e.target.value
1671
+ })),
1672
+ rows: 4,
1673
+ style: {
1674
+ width: "100%",
1675
+ padding: "0.5rem 0.75rem",
1676
+ border: "1px solid #d1d5db",
1677
+ borderRadius: 4,
1678
+ fontSize: "0.85rem",
1679
+ fontFamily: "monospace",
1680
+ marginBottom: "0.75rem",
1681
+ boxSizing: "border-box"
1682
+ }
1683
+ }),
1684
+ /* @__PURE__ */ jsx("label", {
1685
+ style: {
1686
+ display: "block",
1687
+ fontSize: "0.875rem",
1688
+ marginBottom: "0.25rem"
1689
+ },
1690
+ children: "Custom Footer Code"
1691
+ }),
1692
+ /* @__PURE__ */ jsx("textarea", {
1693
+ value: advanced.customFooterCode,
1694
+ onChange: (e) => setAdvanced((a) => ({
1695
+ ...a,
1696
+ customFooterCode: e.target.value
1697
+ })),
1698
+ rows: 4,
1699
+ style: {
1700
+ width: "100%",
1701
+ padding: "0.5rem 0.75rem",
1702
+ border: "1px solid #d1d5db",
1703
+ borderRadius: 4,
1704
+ fontSize: "0.85rem",
1705
+ fontFamily: "monospace",
1706
+ boxSizing: "border-box"
1707
+ }
1708
+ })
1709
+ ]
1710
+ }),
1711
+ /* @__PURE__ */ jsxs("section", {
1712
+ style: { marginBottom: "1.5rem" },
1713
+ children: [
1714
+ /* @__PURE__ */ jsx("h3", { children: "Developer" }),
1715
+ /* @__PURE__ */ jsxs("label", {
1716
+ style: {
1717
+ display: "flex",
1718
+ alignItems: "center",
1719
+ gap: "0.5rem",
1720
+ fontSize: "0.9rem",
1721
+ cursor: "pointer"
1722
+ },
1723
+ children: [/* @__PURE__ */ jsx("input", {
1724
+ type: "checkbox",
1725
+ checked: advanced.debug,
1726
+ onChange: (e) => setAdvanced((a) => ({
1727
+ ...a,
1728
+ debug: e.target.checked
1729
+ }))
1730
+ }), "Debug Mode"]
1731
+ }),
1732
+ /* @__PURE__ */ jsx("p", {
1733
+ style: {
1734
+ fontSize: "0.8rem",
1735
+ color: "#888",
1736
+ marginTop: "0.25rem"
1737
+ },
1738
+ children: "Logs injected script activity to the browser console on every page load."
1739
+ })
1740
+ ]
1741
+ }),
1742
+ /* @__PURE__ */ jsx("button", {
1743
+ onClick: () => void handleSaveAdvanced(),
1744
+ disabled: savingAdvanced,
1745
+ children: savingAdvanced ? "Saving…" : "Save Settings"
1746
+ }),
1747
+ message && /* @__PURE__ */ jsx("p", {
1748
+ style: {
1749
+ fontSize: "0.9rem",
1750
+ color: "#444",
1751
+ marginTop: "1rem"
1752
+ },
1753
+ children: message
1754
+ }),
1755
+ license?.isValid && license.tier === "free" && /* @__PURE__ */ jsxs("div", {
1756
+ style: {
1757
+ marginTop: "2rem",
1758
+ padding: "1rem",
1759
+ background: "#f0f7ff",
1760
+ border: "1px solid #c0d8f5",
1761
+ borderRadius: 6
1762
+ },
1763
+ children: [
1764
+ /* @__PURE__ */ jsx("h4", {
1765
+ style: { margin: "0 0 0.5rem" },
1766
+ children: "Upgrade to Professional"
1767
+ }),
1768
+ /* @__PURE__ */ jsx("p", {
1769
+ style: {
1770
+ margin: "0 0 0.75rem",
1771
+ fontSize: "0.9rem"
1772
+ },
1773
+ children: "Unlock AI Call Transcription, Lead Scoring, and Call Recording."
1774
+ }),
1775
+ /* @__PURE__ */ jsx("a", {
1776
+ href: "https://quotedash.io/upgrade",
1777
+ target: "_blank",
1778
+ rel: "noreferrer",
1779
+ children: "Upgrade Now →"
1780
+ })
1781
+ ]
1782
+ })
1783
+ ]
1784
+ });
1785
+ }
1786
+
1787
+ //#endregion
1788
+ //#region src/admin/PluginLayout.tsx
1789
+ const TABS = [
1790
+ {
1791
+ id: "dashboard",
1792
+ label: "Marketing ROI"
1793
+ },
1794
+ {
1795
+ id: "tracking",
1796
+ label: "Tracking Pixels"
1797
+ },
1798
+ {
1799
+ id: "settings",
1800
+ label: "License & Google"
1801
+ }
1802
+ ];
1803
+ function getTabFromHash() {
1804
+ const hash = window.location.hash.slice(1);
1805
+ return hash === "tracking" || hash === "settings" ? hash : "dashboard";
1806
+ }
1807
+ function PluginLayout() {
1808
+ const [activeTab, setActiveTab] = useState(getTabFromHash);
1809
+ const handleTabChange = (tab) => {
1810
+ setActiveTab(tab);
1811
+ window.location.hash = tab === "dashboard" ? "" : tab;
1812
+ };
1813
+ useEffect(() => {
1814
+ const onHashChange = () => setActiveTab(getTabFromHash());
1815
+ window.addEventListener("hashchange", onHashChange);
1816
+ return () => window.removeEventListener("hashchange", onHashChange);
1817
+ }, []);
1818
+ return /* @__PURE__ */ jsxs("div", {
1819
+ style: {
1820
+ display: "flex",
1821
+ flexDirection: "column",
1822
+ height: "100%"
1823
+ },
1824
+ children: [/* @__PURE__ */ jsx("div", {
1825
+ role: "tablist",
1826
+ style: {
1827
+ display: "flex",
1828
+ borderBottom: "1px solid #e2e8f0",
1829
+ background: "#fff",
1830
+ padding: "0 1.25rem",
1831
+ gap: "0.25rem",
1832
+ flexShrink: 0
1833
+ },
1834
+ children: TABS.map((tab) => /* @__PURE__ */ jsx("button", {
1835
+ role: "tab",
1836
+ "aria-selected": activeTab === tab.id,
1837
+ onClick: () => handleTabChange(tab.id),
1838
+ style: {
1839
+ padding: "0.75rem 1rem",
1840
+ border: "none",
1841
+ borderBottom: activeTab === tab.id ? "2px solid #2563eb" : "2px solid transparent",
1842
+ background: "none",
1843
+ cursor: "pointer",
1844
+ fontSize: "0.875rem",
1845
+ fontWeight: activeTab === tab.id ? 600 : 400,
1846
+ color: activeTab === tab.id ? "#2563eb" : "#64748b",
1847
+ marginBottom: "-1px"
1848
+ },
1849
+ children: tab.label
1850
+ }, tab.id))
1851
+ }), /* @__PURE__ */ jsxs("div", {
1852
+ style: {
1853
+ flex: 1,
1854
+ overflow: "auto"
1855
+ },
1856
+ children: [
1857
+ /* @__PURE__ */ jsx("div", {
1858
+ style: {
1859
+ display: activeTab === "dashboard" ? "block" : "none",
1860
+ height: "100%"
1861
+ },
1862
+ children: /* @__PURE__ */ jsx(AdminDashboard, { onNavigateToSettings: () => handleTabChange("settings") })
1863
+ }),
1864
+ /* @__PURE__ */ jsx("div", {
1865
+ style: { display: activeTab === "tracking" ? "block" : "none" },
1866
+ children: /* @__PURE__ */ jsx(TrackingSettingsAdmin, {})
1867
+ }),
1868
+ /* @__PURE__ */ jsx("div", {
1869
+ style: { display: activeTab === "settings" ? "block" : "none" },
1870
+ children: /* @__PURE__ */ jsx(SettingsPage, {})
1871
+ })
1872
+ ]
1873
+ })]
1874
+ });
1875
+ }
1876
+
1529
1877
  //#endregion
1530
1878
  //#region src/admin.tsx
1531
1879
  /**
1532
1880
  * Admin UI entry point — loaded in the browser by EmDash's admin panel.
1533
1881
  * Exports `pages` (keyed by path) and `widgets` (keyed by ID).
1534
- * Paths must match the `admin.pages` declared in src/index.ts.
1882
+ * All plugin tabs (Dashboard, Tracking Pixels, License & Google) are rendered
1883
+ * inside PluginLayout so only one sidebar entry appears.
1535
1884
  */
1536
- const pages = {
1537
- "/dashboard": AdminDashboard,
1538
- "/settings": SettingsPage,
1539
- "/tracking": TrackingSettingsAdmin
1540
- };
1885
+ const pages = { "/dashboard": PluginLayout };
1541
1886
  const widgets = {};
1542
1887
 
1543
1888
  //#endregion