@yourbright/emdash-analytics-plugin 0.1.3 → 0.1.4

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,45 @@ 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("div", { className: "flex flex-wrap gap-2", children: tabs.map((tab) => {
191
+ const active = tab.key === section;
192
+ return /* @__PURE__ */ jsx(
193
+ "button",
194
+ {
195
+ type: "button",
196
+ onClick: () => onChange(tab.key),
197
+ className: `rounded-full border px-3 py-2 text-sm font-medium transition ${active ? "border-foreground bg-foreground text-background" : "border-border bg-background text-foreground hover:bg-accent"}`,
198
+ children: tab.label
199
+ },
200
+ tab.key
201
+ );
202
+ }) });
203
+ }
152
204
  function ErrorBanner({ message }) {
153
205
  if (!message) return null;
154
206
  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 +456,7 @@ function MoversTable({
404
456
  ] }, item.urlPath)) })
405
457
  ] }) });
406
458
  }
407
- function OverviewPage() {
459
+ function OverviewPage({ embedded = false }) {
408
460
  const [status, setStatus] = React.useState(null);
409
461
  const [overview, setOverview] = React.useState(null);
410
462
  const [loading, setLoading] = React.useState(true);
@@ -434,65 +486,79 @@ function OverviewPage() {
434
486
  }, [load]);
435
487
  const summary = overview?.summary ?? status?.summary ?? null;
436
488
  const freshness = overview?.freshness ?? status?.freshness ?? idleFreshness();
437
- return /* @__PURE__ */ jsxs(
489
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
490
+ /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
491
+ !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,
492
+ /* @__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: [
493
+ /* @__PURE__ */ jsx(StatCard, { label: "Last Sync", value: formatDateTime(freshness.lastSyncedAt), note: statusLabel(freshness.lastStatus) }),
494
+ /* @__PURE__ */ jsx(StatCard, { label: "GSC Final Date", value: freshness.lastGscDate || "-" }),
495
+ /* @__PURE__ */ jsx(StatCard, { label: "GA Final Date", value: freshness.lastGaDate || "-" }),
496
+ /* @__PURE__ */ jsx(StatCard, { label: "Service Account", value: status?.config?.serviceAccountEmail || "-" })
497
+ ] }) }),
498
+ /* @__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)) }) }),
499
+ summary ? /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
500
+ /* @__PURE__ */ jsx(
501
+ TrendPanel,
502
+ {
503
+ title: "Search Trend",
504
+ subtitle: "Daily search demand and click capture for the current 28-day window.",
505
+ metrics: (overview?.kpiDeltas ?? []).filter(
506
+ (metric) => metric.key === "gscClicks" || metric.key === "gscImpressions"
507
+ ),
508
+ trend: summary.trend
509
+ }
510
+ ),
511
+ /* @__PURE__ */ jsx(
512
+ TrendPanel,
513
+ {
514
+ title: "Traffic Trend",
515
+ subtitle: "Daily traffic movement from GA4 for the current 28-day window.",
516
+ metrics: (overview?.kpiDeltas ?? []).filter(
517
+ (metric) => metric.key === "gaViews" || metric.key === "gaSessions"
518
+ ),
519
+ trend: summary.trend
520
+ }
521
+ )
522
+ ] }) : null,
523
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
524
+ /* @__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." }) }),
525
+ /* @__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." }) })
526
+ ] }),
527
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 xl:grid-cols-2", children: [
528
+ /* @__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." }) }),
529
+ /* @__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." }) })
530
+ ] }),
531
+ /* @__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: [
532
+ /* @__PURE__ */ jsx(WindowCard, { label: "GSC Current", value: summary?.window.gscCurrent }),
533
+ /* @__PURE__ */ jsx(WindowCard, { label: "GSC Previous", value: summary?.window.gscPrevious }),
534
+ /* @__PURE__ */ jsx(WindowCard, { label: "GA Current", value: summary?.window.gaCurrent }),
535
+ /* @__PURE__ */ jsx(WindowCard, { label: "GA Previous", value: summary?.window.gaPrevious })
536
+ ] }) })
537
+ ] });
538
+ if (embedded) {
539
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
540
+ /* @__PURE__ */ jsx(
541
+ InlineHeader,
542
+ {
543
+ title: "Dashboard",
544
+ description: "Monitor site health, compare the last 28 days to the previous window, and spot pages that changed fastest.",
545
+ actions: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void load(), disabled: loading, children: "Reload" })
546
+ }
547
+ ),
548
+ content
549
+ ] });
550
+ }
551
+ return /* @__PURE__ */ jsx(
438
552
  Shell,
439
553
  {
440
554
  title: "Content Insights",
441
555
  description: "Monitor site health, compare the last 28 days to the previous window, and spot pages that changed fastest.",
442
556
  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
- ]
557
+ children: content
492
558
  }
493
559
  );
494
560
  }
495
- function PagesPage() {
561
+ function PagesPage({ embedded = false }) {
496
562
  const [managed, setManaged] = React.useState("all");
497
563
  const [pageKind, setPageKind] = React.useState("all");
498
564
  const [hasOpportunity, setHasOpportunity] = React.useState(false);
@@ -548,7 +614,7 @@ function PagesPage() {
548
614
  cancelled = true;
549
615
  };
550
616
  }, [selected]);
551
- return /* @__PURE__ */ jsxs(Shell, { title: "Pages", description: "Explore all public pages and filter down to the content that needs attention.", children: [
617
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
552
618
  /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
553
619
  /* @__PURE__ */ jsx(Section, { title: "Filters", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-4", children: [
554
620
  /* @__PURE__ */ jsx(Field, { label: "Scope", children: /* @__PURE__ */ jsx(
@@ -637,8 +703,21 @@ function PagesPage() {
637
703
  ] }) })
638
704
  ] })
639
705
  ] });
706
+ if (embedded) {
707
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
708
+ /* @__PURE__ */ jsx(
709
+ InlineHeader,
710
+ {
711
+ title: "Pages",
712
+ description: "Explore all public pages and filter down to the content that needs attention."
713
+ }
714
+ ),
715
+ content
716
+ ] });
717
+ }
718
+ return /* @__PURE__ */ jsx(Shell, { title: "Pages", description: "Explore all public pages and filter down to the content that needs attention.", children: content });
640
719
  }
641
- function SettingsPage() {
720
+ function SettingsPage({ embedded = false }) {
642
721
  const [draft, setDraft] = React.useState(EMPTY_CONFIG);
643
722
  const [storedConfig, setStoredConfig] = React.useState({
644
723
  siteOrigin: "",
@@ -792,70 +871,108 @@ function SettingsPage() {
792
871
  setBusy(null);
793
872
  }
794
873
  };
795
- return /* @__PURE__ */ jsxs(
874
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
875
+ /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
876
+ /* @__PURE__ */ jsx(SuccessBanner, { message: success }),
877
+ /* @__PURE__ */ jsxs(Section, { title: "Google Connection", children: [
878
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
879
+ /* @__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 })) }) }),
880
+ /* @__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 })) }) }),
881
+ /* @__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 })) }) }) }),
882
+ /* @__PURE__ */ jsx("div", { className: "md:col-span-2", children: /* @__PURE__ */ jsx(
883
+ Field,
884
+ {
885
+ label: "Service Account JSON",
886
+ hint: hasStoredServiceAccount ? `Current: ${storedServiceAccountEmail || "configured"}. Leave blank to keep the current secret.` : "Required on the first save.",
887
+ children: /* @__PURE__ */ jsx(
888
+ TextArea,
889
+ {
890
+ value: draft.serviceAccountJson,
891
+ onChange: (value) => setDraft((current) => ({ ...current, serviceAccountJson: value })),
892
+ placeholder: '{"client_email":"...","private_key":"..."}',
893
+ rows: 12
894
+ }
895
+ )
896
+ }
897
+ ) })
898
+ ] }),
899
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 flex flex-wrap gap-3 border-t border-border pt-4", children: [
900
+ /* @__PURE__ */ jsx(Button, { onClick: () => void save(), disabled: !!busy, children: busy === "save" ? "Saving..." : "Save Settings" }),
901
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void testConnection(), disabled: !!busy, children: busy === "test" ? "Testing..." : "Test Connection" }),
902
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void syncNow(), disabled: !!busy, children: busy === "sync" ? "Syncing..." : "Run Manual Sync" })
903
+ ] })
904
+ ] }),
905
+ /* @__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: [
906
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]", children: [
907
+ /* @__PURE__ */ jsx(Field, { label: "New key label", children: /* @__PURE__ */ jsx(Input, { value: newKeyLabel, onChange: setNewKeyLabel, placeholder: "content-feedback-agent" }) }),
908
+ /* @__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" }) })
909
+ ] }),
910
+ generatedKey ? /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-amber-200 bg-amber-50 p-4", children: [
911
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-amber-900", children: "Generated Key" }),
912
+ /* @__PURE__ */ jsx("div", { className: "mt-2 break-all font-mono text-sm text-amber-900", children: generatedKey })
913
+ ] }) : null,
914
+ /* @__PURE__ */ jsx("div", { className: "mt-4 overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full text-sm", children: [
915
+ /* @__PURE__ */ jsx("thead", { className: "text-left text-xs uppercase tracking-[0.16em] text-muted-foreground", children: /* @__PURE__ */ jsxs("tr", { children: [
916
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Prefix" }),
917
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Label" }),
918
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Created" }),
919
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Last Used" }),
920
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4", children: "Status" }),
921
+ /* @__PURE__ */ jsx("th", { className: "pb-3 pr-4" })
922
+ ] }) }),
923
+ /* @__PURE__ */ jsx("tbody", { children: keys.map((key) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/80", children: [
924
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4 font-mono text-xs", children: key.prefix }),
925
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: key.label }),
926
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: formatDateTime(key.createdAt) }),
927
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: formatDateTime(key.lastUsedAt) }),
928
+ /* @__PURE__ */ jsx("td", { className: "py-3 pr-4", children: key.revokedAt ? "Revoked" : "Active" }),
929
+ /* @__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" }) })
930
+ ] }, key.prefix)) })
931
+ ] }) })
932
+ ] })
933
+ ] });
934
+ if (embedded) {
935
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
936
+ /* @__PURE__ */ jsx(
937
+ InlineHeader,
938
+ {
939
+ title: "Settings",
940
+ description: "Manage Google connection settings, manual sync, and agent API keys."
941
+ }
942
+ ),
943
+ content
944
+ ] });
945
+ }
946
+ return /* @__PURE__ */ jsx(
796
947
  Shell,
797
948
  {
798
949
  title: "Analytics",
799
950
  description: "Manage Google connection settings, manual sync, and agent API keys.",
951
+ children: content
952
+ }
953
+ );
954
+ }
955
+ function AnalyticsPage() {
956
+ const [section, setSection] = React.useState(sectionFromLocation);
957
+ React.useEffect(() => {
958
+ const handlePopstate = () => setSection(sectionFromLocation());
959
+ window.addEventListener("popstate", handlePopstate);
960
+ return () => window.removeEventListener("popstate", handlePopstate);
961
+ }, []);
962
+ const selectSection = (nextSection) => {
963
+ setSection(nextSection);
964
+ updateSectionInUrl(nextSection);
965
+ };
966
+ return /* @__PURE__ */ jsxs(
967
+ Shell,
968
+ {
969
+ title: "Analytics",
970
+ description: "Use one workspace for dashboard review, page drilldown, and connection management.",
971
+ actions: /* @__PURE__ */ jsx(AnalyticsTabs, { section, onChange: selectSection }),
800
972
  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
- ] })
973
+ section === "dashboard" ? /* @__PURE__ */ jsx(OverviewPage, { embedded: true }) : null,
974
+ section === "pages" ? /* @__PURE__ */ jsx(PagesPage, { embedded: true }) : null,
975
+ section === "settings" ? /* @__PURE__ */ jsx(SettingsPage, { embedded: true }) : null
859
976
  ]
860
977
  }
861
978
  );
@@ -1004,7 +1121,7 @@ function numberValue(value) {
1004
1121
  return typeof value === "number" ? value : 0;
1005
1122
  }
1006
1123
  var pages = {
1007
- "/": OverviewPage,
1124
+ "/": AnalyticsPage,
1008
1125
  "/pages": PagesPage,
1009
1126
  "/settings": SettingsPage
1010
1127
  };
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.4",
4
4
  "description": "Google Search Console and GA4 analytics plugin for EmDash",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",