@yourbright/emdash-analytics-plugin 0.1.3 → 0.1.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.
package/dist/admin.js CHANGED
@@ -19,7 +19,7 @@ var ADMIN_ROUTES = {
19
19
  };
20
20
 
21
21
  // src/admin.tsx
22
- import { jsx, jsxs } from "react/jsx-runtime";
22
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
23
23
  var API_BASE = `/_emdash/api/plugins/${PLUGIN_ID}`;
24
24
  var EMPTY_CONFIG = {
25
25
  siteOrigin: "",
@@ -138,6 +138,19 @@ function Section({
138
138
  children
139
139
  ] });
140
140
  }
141
+ function InlineHeader({
142
+ title,
143
+ description,
144
+ actions
145
+ }) {
146
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 border-b border-border pb-4 md:flex-row md:items-end md:justify-between", children: [
147
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
148
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold tracking-tight", children: title }),
149
+ description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
150
+ ] }),
151
+ actions ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: actions }) : null
152
+ ] });
153
+ }
141
154
  function StatCard({
142
155
  label,
143
156
  value,
@@ -149,6 +162,55 @@ function StatCard({
149
162
  note ? /* @__PURE__ */ jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: note }) : null
150
163
  ] });
151
164
  }
165
+ function sectionFromLocation() {
166
+ if (typeof window === "undefined") return "dashboard";
167
+ const url = new URL(window.location.href);
168
+ const section = url.searchParams.get("section");
169
+ return section === "pages" || section === "settings" ? section : "dashboard";
170
+ }
171
+ function updateSectionInUrl(section) {
172
+ if (typeof window === "undefined") return;
173
+ const url = new URL(window.location.href);
174
+ if (section === "dashboard") {
175
+ url.searchParams.delete("section");
176
+ } else {
177
+ url.searchParams.set("section", section);
178
+ }
179
+ window.history.replaceState({}, "", `${url.pathname}${url.search}${url.hash}`);
180
+ }
181
+ function AnalyticsTabs({
182
+ section,
183
+ onChange
184
+ }) {
185
+ const tabs = [
186
+ { key: "dashboard", label: "Dashboard" },
187
+ { key: "pages", label: "Pages" },
188
+ { key: "settings", label: "Settings" }
189
+ ];
190
+ return /* @__PURE__ */ jsx(
191
+ "div",
192
+ {
193
+ role: "tablist",
194
+ "aria-label": "Analytics sections",
195
+ className: "inline-flex flex-wrap items-center gap-1 rounded-xl border border-border bg-accent/60 p-1 shadow-sm",
196
+ children: tabs.map((tab) => {
197
+ const active = tab.key === section;
198
+ return /* @__PURE__ */ jsx(
199
+ "button",
200
+ {
201
+ type: "button",
202
+ role: "tab",
203
+ "aria-selected": active,
204
+ onClick: () => onChange(tab.key),
205
+ className: `min-h-10 rounded-lg px-4 py-2 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-foreground/20 ${active ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:bg-background/80 hover:text-foreground"}`,
206
+ children: tab.label
207
+ },
208
+ tab.key
209
+ );
210
+ })
211
+ }
212
+ );
213
+ }
152
214
  function ErrorBanner({ message }) {
153
215
  if (!message) return null;
154
216
  return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700", children: message });
@@ -404,7 +466,7 @@ function MoversTable({
404
466
  ] }, item.urlPath)) })
405
467
  ] }) });
406
468
  }
407
- function OverviewPage() {
469
+ function OverviewPage({ embedded = false }) {
408
470
  const [status, setStatus] = React.useState(null);
409
471
  const [overview, setOverview] = React.useState(null);
410
472
  const [loading, setLoading] = React.useState(true);
@@ -434,65 +496,79 @@ function OverviewPage() {
434
496
  }, [load]);
435
497
  const summary = overview?.summary ?? status?.summary ?? null;
436
498
  const freshness = overview?.freshness ?? status?.freshness ?? idleFreshness();
437
- return /* @__PURE__ */ jsxs(
499
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
500
+ /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
501
+ !status?.config ? /* @__PURE__ */ jsx(Section, { title: "Not Configured", subtitle: "Save your Google connection settings first.", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "After saving the configuration, run a manual sync to populate this dashboard." }) }) : null,
502
+ /* @__PURE__ */ jsx(Section, { title: "Freshness", subtitle: "Track the latest sync and the effective source dates.", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-4", children: [
503
+ /* @__PURE__ */ jsx(StatCard, { label: "Last Sync", value: formatDateTime(freshness.lastSyncedAt), note: statusLabel(freshness.lastStatus) }),
504
+ /* @__PURE__ */ jsx(StatCard, { label: "GSC Final Date", value: freshness.lastGscDate || "-" }),
505
+ /* @__PURE__ */ jsx(StatCard, { label: "GA Final Date", value: freshness.lastGaDate || "-" }),
506
+ /* @__PURE__ */ jsx(StatCard, { label: "Service Account", value: status?.config?.serviceAccountEmail || "-" })
507
+ ] }) }),
508
+ /* @__PURE__ */ jsx(Section, { title: "KPI Snapshot", subtitle: "Current 28 days versus the previous 28 days across all tracked public pages.", children: /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-5", children: (overview?.kpiDeltas ?? []).map((metric) => /* @__PURE__ */ jsx(KpiDeltaCard, { metric }, metric.key)) }) }),
509
+ summary ? /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
510
+ /* @__PURE__ */ jsx(
511
+ TrendPanel,
512
+ {
513
+ title: "Search Trend",
514
+ subtitle: "Daily search demand and click capture for the current 28-day window.",
515
+ metrics: (overview?.kpiDeltas ?? []).filter(
516
+ (metric) => metric.key === "gscClicks" || metric.key === "gscImpressions"
517
+ ),
518
+ trend: summary.trend
519
+ }
520
+ ),
521
+ /* @__PURE__ */ jsx(
522
+ TrendPanel,
523
+ {
524
+ title: "Traffic Trend",
525
+ subtitle: "Daily traffic movement from GA4 for the current 28-day window.",
526
+ metrics: (overview?.kpiDeltas ?? []).filter(
527
+ (metric) => metric.key === "gaViews" || metric.key === "gaSessions"
528
+ ),
529
+ trend: summary.trend
530
+ }
531
+ )
532
+ ] }) : null,
533
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
534
+ /* @__PURE__ */ jsx(Section, { title: "Page Mix", subtitle: "Compare page groups by current volume and change from the previous window.", children: /* @__PURE__ */ jsx(BreakdownTable, { rows: overview?.pageKindBreakdown ?? [], emptyMessage: "No tracked pages yet." }) }),
535
+ /* @__PURE__ */ jsx(Section, { title: "Managed Coverage", subtitle: "See whether growth is coming from EmDash-managed content or unmanaged pages.", children: /* @__PURE__ */ jsx(BreakdownTable, { rows: overview?.managedBreakdown ?? [], emptyMessage: "No tracked pages yet." }) })
536
+ ] }),
537
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
538
+ /* @__PURE__ */ jsx(Section, { title: "Top Gainers", subtitle: "Pages with the strongest positive movement in the last 28 days.", children: /* @__PURE__ */ jsx(MoversTable, { items: overview?.topGainers ?? [], emptyMessage: "No gaining pages yet." }) }),
539
+ /* @__PURE__ */ jsx(Section, { title: "Top Decliners", subtitle: "Pages with the sharpest drop and the clearest candidates for investigation.", children: /* @__PURE__ */ jsx(MoversTable, { items: overview?.topDecliners ?? [], emptyMessage: "No declining pages yet." }) })
540
+ ] }),
541
+ /* @__PURE__ */ jsx(Section, { title: "Reporting Windows", subtitle: "The agent API returns the same windows.", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
542
+ /* @__PURE__ */ jsx(WindowCard, { label: "GSC Current", value: summary?.window.gscCurrent }),
543
+ /* @__PURE__ */ jsx(WindowCard, { label: "GSC Previous", value: summary?.window.gscPrevious }),
544
+ /* @__PURE__ */ jsx(WindowCard, { label: "GA Current", value: summary?.window.gaCurrent }),
545
+ /* @__PURE__ */ jsx(WindowCard, { label: "GA Previous", value: summary?.window.gaPrevious })
546
+ ] }) })
547
+ ] });
548
+ if (embedded) {
549
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
550
+ /* @__PURE__ */ jsx(
551
+ InlineHeader,
552
+ {
553
+ title: "Dashboard",
554
+ description: "Monitor site health, compare the last 28 days to the previous window, and spot pages that changed fastest.",
555
+ actions: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void load(), disabled: loading, children: "Reload" })
556
+ }
557
+ ),
558
+ content
559
+ ] });
560
+ }
561
+ return /* @__PURE__ */ jsx(
438
562
  Shell,
439
563
  {
440
564
  title: "Content Insights",
441
565
  description: "Monitor site health, compare the last 28 days to the previous window, and spot pages that changed fastest.",
442
566
  actions: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void load(), disabled: loading, children: "Reload" }),
443
- children: [
444
- /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
445
- !status?.config ? /* @__PURE__ */ jsx(Section, { title: "Not Configured", subtitle: "Save your Google connection settings first.", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "After saving the configuration, run a manual sync to populate this dashboard." }) }) : null,
446
- /* @__PURE__ */ jsx(Section, { title: "Freshness", subtitle: "Track the latest sync and the effective source dates.", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-4", children: [
447
- /* @__PURE__ */ jsx(StatCard, { label: "Last Sync", value: formatDateTime(freshness.lastSyncedAt), note: statusLabel(freshness.lastStatus) }),
448
- /* @__PURE__ */ jsx(StatCard, { label: "GSC Final Date", value: freshness.lastGscDate || "-" }),
449
- /* @__PURE__ */ jsx(StatCard, { label: "GA Final Date", value: freshness.lastGaDate || "-" }),
450
- /* @__PURE__ */ jsx(StatCard, { label: "Service Account", value: status?.config?.serviceAccountEmail || "-" })
451
- ] }) }),
452
- /* @__PURE__ */ jsx(Section, { title: "KPI Snapshot", subtitle: "Current 28 days versus the previous 28 days across all tracked public pages.", children: /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-5", children: (overview?.kpiDeltas ?? []).map((metric) => /* @__PURE__ */ jsx(KpiDeltaCard, { metric }, metric.key)) }) }),
453
- summary ? /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
454
- /* @__PURE__ */ jsx(
455
- TrendPanel,
456
- {
457
- title: "Search Trend",
458
- subtitle: "Daily search demand and click capture for the current 28-day window.",
459
- metrics: (overview?.kpiDeltas ?? []).filter(
460
- (metric) => metric.key === "gscClicks" || metric.key === "gscImpressions"
461
- ),
462
- trend: summary.trend
463
- }
464
- ),
465
- /* @__PURE__ */ jsx(
466
- TrendPanel,
467
- {
468
- title: "Traffic Trend",
469
- subtitle: "Daily traffic movement from GA4 for the current 28-day window.",
470
- metrics: (overview?.kpiDeltas ?? []).filter(
471
- (metric) => metric.key === "gaViews" || metric.key === "gaSessions"
472
- ),
473
- trend: summary.trend
474
- }
475
- )
476
- ] }) : null,
477
- /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
478
- /* @__PURE__ */ jsx(Section, { title: "Page Mix", subtitle: "Compare page groups by current volume and change from the previous window.", children: /* @__PURE__ */ jsx(BreakdownTable, { rows: overview?.pageKindBreakdown ?? [], emptyMessage: "No tracked pages yet." }) }),
479
- /* @__PURE__ */ jsx(Section, { title: "Managed Coverage", subtitle: "See whether growth is coming from EmDash-managed content or unmanaged pages.", children: /* @__PURE__ */ jsx(BreakdownTable, { rows: overview?.managedBreakdown ?? [], emptyMessage: "No tracked pages yet." }) })
480
- ] }),
481
- /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
482
- /* @__PURE__ */ jsx(Section, { title: "Top Gainers", subtitle: "Pages with the strongest positive movement in the last 28 days.", children: /* @__PURE__ */ jsx(MoversTable, { items: overview?.topGainers ?? [], emptyMessage: "No gaining pages yet." }) }),
483
- /* @__PURE__ */ jsx(Section, { title: "Top Decliners", subtitle: "Pages with the sharpest drop and the clearest candidates for investigation.", children: /* @__PURE__ */ jsx(MoversTable, { items: overview?.topDecliners ?? [], emptyMessage: "No declining pages yet." }) })
484
- ] }),
485
- /* @__PURE__ */ jsx(Section, { title: "Reporting Windows", subtitle: "The agent API returns the same windows.", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
486
- /* @__PURE__ */ jsx(WindowCard, { label: "GSC Current", value: summary?.window.gscCurrent }),
487
- /* @__PURE__ */ jsx(WindowCard, { label: "GSC Previous", value: summary?.window.gscPrevious }),
488
- /* @__PURE__ */ jsx(WindowCard, { label: "GA Current", value: summary?.window.gaCurrent }),
489
- /* @__PURE__ */ jsx(WindowCard, { label: "GA Previous", value: summary?.window.gaPrevious })
490
- ] }) })
491
- ]
567
+ children: content
492
568
  }
493
569
  );
494
570
  }
495
- function PagesPage() {
571
+ function PagesPage({ embedded = false }) {
496
572
  const [managed, setManaged] = React.useState("all");
497
573
  const [pageKind, setPageKind] = React.useState("all");
498
574
  const [hasOpportunity, setHasOpportunity] = React.useState(false);
@@ -548,7 +624,7 @@ function PagesPage() {
548
624
  cancelled = true;
549
625
  };
550
626
  }, [selected]);
551
- return /* @__PURE__ */ jsxs(Shell, { title: "Pages", description: "Explore all public pages and filter down to the content that needs attention.", children: [
627
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
552
628
  /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
553
629
  /* @__PURE__ */ jsx(Section, { title: "Filters", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-4", children: [
554
630
  /* @__PURE__ */ jsx(Field, { label: "Scope", children: /* @__PURE__ */ jsx(
@@ -637,8 +713,21 @@ function PagesPage() {
637
713
  ] }) })
638
714
  ] })
639
715
  ] });
716
+ if (embedded) {
717
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
718
+ /* @__PURE__ */ jsx(
719
+ InlineHeader,
720
+ {
721
+ title: "Pages",
722
+ description: "Explore all public pages and filter down to the content that needs attention."
723
+ }
724
+ ),
725
+ content
726
+ ] });
727
+ }
728
+ return /* @__PURE__ */ jsx(Shell, { title: "Pages", description: "Explore all public pages and filter down to the content that needs attention.", children: content });
640
729
  }
641
- function SettingsPage() {
730
+ function SettingsPage({ embedded = false }) {
642
731
  const [draft, setDraft] = React.useState(EMPTY_CONFIG);
643
732
  const [storedConfig, setStoredConfig] = React.useState({
644
733
  siteOrigin: "",
@@ -792,70 +881,108 @@ function SettingsPage() {
792
881
  setBusy(null);
793
882
  }
794
883
  };
795
- return /* @__PURE__ */ jsxs(
884
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
885
+ /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
886
+ /* @__PURE__ */ jsx(SuccessBanner, { message: success }),
887
+ /* @__PURE__ */ jsxs(Section, { title: "Google Connection", children: [
888
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
889
+ /* @__PURE__ */ jsx(Field, { label: "Canonical Site Origin", hint: "Example: https://www.yourbright.co.jp", children: /* @__PURE__ */ jsx(Input, { value: draft.siteOrigin, onChange: (value) => setDraft((current) => ({ ...current, siteOrigin: value })) }) }),
890
+ /* @__PURE__ */ jsx(Field, { label: "GA4 Property ID", hint: "Enter the numeric property ID.", children: /* @__PURE__ */ jsx(Input, { value: draft.ga4PropertyId, onChange: (value) => setDraft((current) => ({ ...current, ga4PropertyId: value })) }) }),
891
+ /* @__PURE__ */ jsx("div", { className: "md:col-span-2", children: /* @__PURE__ */ jsx(Field, { label: "Search Console Property", hint: "Example: https://www.yourbright.co.jp/ or sc-domain:yourbright.co.jp", children: /* @__PURE__ */ jsx(Input, { value: draft.gscSiteUrl, onChange: (value) => setDraft((current) => ({ ...current, gscSiteUrl: value })) }) }) }),
892
+ /* @__PURE__ */ jsx("div", { className: "md:col-span-2", children: /* @__PURE__ */ jsx(
893
+ Field,
894
+ {
895
+ label: "Service Account JSON",
896
+ hint: hasStoredServiceAccount ? `Current: ${storedServiceAccountEmail || "configured"}. Leave blank to keep the current secret.` : "Required on the first save.",
897
+ children: /* @__PURE__ */ jsx(
898
+ TextArea,
899
+ {
900
+ value: draft.serviceAccountJson,
901
+ onChange: (value) => setDraft((current) => ({ ...current, serviceAccountJson: value })),
902
+ placeholder: '{"client_email":"...","private_key":"..."}',
903
+ rows: 12
904
+ }
905
+ )
906
+ }
907
+ ) })
908
+ ] }),
909
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 flex flex-wrap gap-3 border-t border-border pt-4", children: [
910
+ /* @__PURE__ */ jsx(Button, { onClick: () => void save(), disabled: !!busy, children: busy === "save" ? "Saving..." : "Save Settings" }),
911
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void testConnection(), disabled: !!busy, children: busy === "test" ? "Testing..." : "Test Connection" }),
912
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void syncNow(), disabled: !!busy, children: busy === "sync" ? "Syncing..." : "Run Manual Sync" })
913
+ ] })
914
+ ] }),
915
+ /* @__PURE__ */ jsxs(Section, { title: "Agent API Keys", subtitle: "Use these with Authorization: AgentKey yb_ins_... or X-Emdash-Agent-Key. Raw keys are shown only once.", children: [
916
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]", children: [
917
+ /* @__PURE__ */ jsx(Field, { label: "New key label", children: /* @__PURE__ */ jsx(Input, { value: newKeyLabel, onChange: setNewKeyLabel, placeholder: "content-feedback-agent" }) }),
918
+ /* @__PURE__ */ jsx("div", { className: "flex items-end", children: /* @__PURE__ */ jsx(Button, { onClick: () => void createKey(), disabled: !!busy || !newKeyLabel.trim(), children: busy === "create-key" ? "Creating..." : "Create Key" }) })
919
+ ] }),
920
+ generatedKey ? /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-amber-200 bg-amber-50 p-4", children: [
921
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-amber-900", children: "Generated Key" }),
922
+ /* @__PURE__ */ jsx("div", { className: "mt-2 break-all font-mono text-sm text-amber-900", children: generatedKey })
923
+ ] }) : null,
924
+ /* @__PURE__ */ jsx("div", { className: "mt-4 overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full text-sm", children: [
925
+ /* @__PURE__ */ jsx("thead", { className: "text-left text-xs uppercase tracking-[0.16em] text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
926
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Prefix" }),
927
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Label" }),
928
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Created" }),
929
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Last Used" }),
930
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Status" }),
931
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4" })
932
+ ] }) }),
933
+ /* @__PURE__ */ jsx("tbody", { children: keys.map((key) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/80", children: [
934
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4 font-mono text-xs", children: key.prefix }),
935
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: key.label }),
936
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: formatDateTime(key.createdAt) }),
937
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: formatDateTime(key.lastUsedAt) }),
938
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: key.revokedAt ? "Revoked" : "Active" }),
939
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: key.revokedAt ? null : /* @__PURE__ */ jsx(Button, { variant: "danger", onClick: () => void revokeKey(key.prefix), disabled: busy === key.prefix, children: "Revoke" }) })
940
+ ] }, key.prefix)) })
941
+ ] }) })
942
+ ] })
943
+ ] });
944
+ if (embedded) {
945
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
946
+ /* @__PURE__ */ jsx(
947
+ InlineHeader,
948
+ {
949
+ title: "Settings",
950
+ description: "Manage Google connection settings, manual sync, and agent API keys."
951
+ }
952
+ ),
953
+ content
954
+ ] });
955
+ }
956
+ return /* @__PURE__ */ jsx(
796
957
  Shell,
797
958
  {
798
959
  title: "Analytics",
799
960
  description: "Manage Google connection settings, manual sync, and agent API keys.",
961
+ children: content
962
+ }
963
+ );
964
+ }
965
+ function AnalyticsPage() {
966
+ const [section, setSection] = React.useState(sectionFromLocation);
967
+ React.useEffect(() => {
968
+ const handlePopstate = () => setSection(sectionFromLocation());
969
+ window.addEventListener("popstate", handlePopstate);
970
+ return () => window.removeEventListener("popstate", handlePopstate);
971
+ }, []);
972
+ const selectSection = (nextSection) => {
973
+ setSection(nextSection);
974
+ updateSectionInUrl(nextSection);
975
+ };
976
+ return /* @__PURE__ */ jsxs(
977
+ Shell,
978
+ {
979
+ title: "Analytics",
980
+ description: "Use one workspace for dashboard review, page drilldown, and connection management.",
981
+ actions: /* @__PURE__ */ jsx(AnalyticsTabs, { section, onChange: selectSection }),
800
982
  children: [
801
- /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
802
- /* @__PURE__ */ jsx(SuccessBanner, { message: success }),
803
- /* @__PURE__ */ jsxs(Section, { title: "Google Connection", children: [
804
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
805
- /* @__PURE__ */ jsx(Field, { label: "Canonical Site Origin", hint: "Example: https://www.yourbright.co.jp", children: /* @__PURE__ */ jsx(Input, { value: draft.siteOrigin, onChange: (value) => setDraft((current) => ({ ...current, siteOrigin: value })) }) }),
806
- /* @__PURE__ */ jsx(Field, { label: "GA4 Property ID", hint: "Enter the numeric property ID.", children: /* @__PURE__ */ jsx(Input, { value: draft.ga4PropertyId, onChange: (value) => setDraft((current) => ({ ...current, ga4PropertyId: value })) }) }),
807
- /* @__PURE__ */ jsx("div", { className: "md:col-span-2", children: /* @__PURE__ */ jsx(Field, { label: "Search Console Property", hint: "Example: https://www.yourbright.co.jp/ or sc-domain:yourbright.co.jp", children: /* @__PURE__ */ jsx(Input, { value: draft.gscSiteUrl, onChange: (value) => setDraft((current) => ({ ...current, gscSiteUrl: value })) }) }) }),
808
- /* @__PURE__ */ jsx("div", { className: "md:col-span-2", children: /* @__PURE__ */ jsx(
809
- Field,
810
- {
811
- label: "Service Account JSON",
812
- hint: hasStoredServiceAccount ? `Current: ${storedServiceAccountEmail || "configured"}. Leave blank to keep the current secret.` : "Required on the first save.",
813
- children: /* @__PURE__ */ jsx(
814
- TextArea,
815
- {
816
- value: draft.serviceAccountJson,
817
- onChange: (value) => setDraft((current) => ({ ...current, serviceAccountJson: value })),
818
- placeholder: '{"client_email":"...","private_key":"..."}',
819
- rows: 12
820
- }
821
- )
822
- }
823
- ) })
824
- ] }),
825
- /* @__PURE__ */ jsxs("div", { className: "mt-6 flex flex-wrap gap-3 border-t border-border pt-4", children: [
826
- /* @__PURE__ */ jsx(Button, { onClick: () => void save(), disabled: !!busy, children: busy === "save" ? "Saving..." : "Save Settings" }),
827
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void testConnection(), disabled: !!busy, children: busy === "test" ? "Testing..." : "Test Connection" }),
828
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void syncNow(), disabled: !!busy, children: busy === "sync" ? "Syncing..." : "Run Manual Sync" })
829
- ] })
830
- ] }),
831
- /* @__PURE__ */ jsxs(Section, { title: "Agent API Keys", subtitle: "Use these with Authorization: AgentKey yb_ins_... or X-Emdash-Agent-Key. Raw keys are shown only once.", children: [
832
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]", children: [
833
- /* @__PURE__ */ jsx(Field, { label: "New key label", children: /* @__PURE__ */ jsx(Input, { value: newKeyLabel, onChange: setNewKeyLabel, placeholder: "content-feedback-agent" }) }),
834
- /* @__PURE__ */ jsx("div", { className: "flex items-end", children: /* @__PURE__ */ jsx(Button, { onClick: () => void createKey(), disabled: !!busy || !newKeyLabel.trim(), children: busy === "create-key" ? "Creating..." : "Create Key" }) })
835
- ] }),
836
- generatedKey ? /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-amber-200 bg-amber-50 p-4", children: [
837
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-amber-900", children: "Generated Key" }),
838
- /* @__PURE__ */ jsx("div", { className: "mt-2 break-all font-mono text-sm text-amber-900", children: generatedKey })
839
- ] }) : null,
840
- /* @__PURE__ */ jsx("div", { className: "mt-4 overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full text-sm", children: [
841
- /* @__PURE__ */ jsx("thead", { className: "text-left text-xs uppercase tracking-[0.16em] text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
842
- /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Prefix" }),
843
- /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Label" }),
844
- /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Created" }),
845
- /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Last Used" }),
846
- /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Status" }),
847
- /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4" })
848
- ] }) }),
849
- /* @__PURE__ */ jsx("tbody", { children: keys.map((key) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/80", children: [
850
- /* @__PURE__ */ jsx("td", { className: "py-3 pr-4 font-mono text-xs", children: key.prefix }),
851
- /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: key.label }),
852
- /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: formatDateTime(key.createdAt) }),
853
- /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: formatDateTime(key.lastUsedAt) }),
854
- /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: key.revokedAt ? "Revoked" : "Active" }),
855
- /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: key.revokedAt ? null : /* @__PURE__ */ jsx(Button, { variant: "danger", onClick: () => void revokeKey(key.prefix), disabled: busy === key.prefix, children: "Revoke" }) })
856
- ] }, key.prefix)) })
857
- ] }) })
858
- ] })
983
+ section === "dashboard" ? /* @__PURE__ */ jsx(OverviewPage, { embedded: true }) : null,
984
+ section === "pages" ? /* @__PURE__ */ jsx(PagesPage, { embedded: true }) : null,
985
+ section === "settings" ? /* @__PURE__ */ jsx(SettingsPage, { embedded: true }) : null
859
986
  ]
860
987
  }
861
988
  );
@@ -1004,7 +1131,7 @@ function numberValue(value) {
1004
1131
  return typeof value === "number" ? value : 0;
1005
1132
  }
1006
1133
  var pages = {
1007
- "/": OverviewPage,
1134
+ "/": AnalyticsPage,
1008
1135
  "/pages": PagesPage,
1009
1136
  "/settings": SettingsPage
1010
1137
  };
package/dist/index.js CHANGED
@@ -1513,9 +1513,7 @@ function contentInsightsPlugin() {
1513
1513
  "www.googleapis.com"
1514
1514
  ],
1515
1515
  adminPages: [
1516
- { path: "/", label: "Overview", icon: "chart-bar" },
1517
- { path: "/pages", label: "Pages", icon: "list" },
1518
- { path: "/settings", label: "Analytics", icon: "gear" }
1516
+ { path: "/", label: "Analytics", icon: "chart-bar" }
1519
1517
  ],
1520
1518
  adminWidgets: [
1521
1519
  { id: "content-opportunities", title: "Content Opportunities", size: "full" }
@@ -1696,9 +1694,7 @@ function createPlugin() {
1696
1694
  },
1697
1695
  admin: {
1698
1696
  pages: [
1699
- { path: "/", label: "Overview", icon: "chart-bar" },
1700
- { path: "/pages", label: "Pages", icon: "list" },
1701
- { path: "/settings", label: "Analytics", icon: "gear" }
1697
+ { path: "/", label: "Analytics", icon: "chart-bar" }
1702
1698
  ],
1703
1699
  widgets: [
1704
1700
  { id: "content-opportunities", title: "Content Opportunities", size: "full" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yourbright/emdash-analytics-plugin",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Google Search Console and GA4 analytics plugin for EmDash",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",