@nodewrite-labs/analytics-sdk 0.2.0 → 0.3.0

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.
@@ -1,6 +1,6 @@
1
1
  import { Access, Config, Payload } from 'payload';
2
- import { AnalyticsModules, AnalyticsServerOptions, ReadGeoFromHeaders } from '../index.js';
3
- import { AnalyticsSummary, AnalyticsEventStore, NewAnalyticsEvent, StoredAnalyticsEvent, AnalyticsSummaryQuery } from '../index.js';
2
+ import { AnalyticsServerOptions, ReadGeoFromHeaders, AnalyticsModules } from '../index.js';
3
+ import { AnalyticsEventStore, NewAnalyticsEvent, StoredAnalyticsEvent, AnalyticsSummaryQuery, AnalyticsSummary } from '../index.js';
4
4
 
5
5
  /**
6
6
  * Read is allowed for a signed-in user, or by the function the plugin was given.
@@ -13,38 +13,54 @@ declare function analyticsEventsAccess(read?: Access): {
13
13
  delete: Access;
14
14
  };
15
15
 
16
- interface AdminSectionsQuery {
17
- session?: string | null;
18
- }
19
- type AdminSection = {
20
- kind: "totals";
21
- totalViews: number;
22
- averageDurationSeconds: number;
23
- } | {
24
- kind: "paths";
25
- rows: AnalyticsSummary["byPath"];
26
- } | {
27
- kind: "countries";
28
- rows: NonNullable<AnalyticsSummary["byCountry"]>;
29
- } | {
30
- kind: "cities";
31
- rows: NonNullable<AnalyticsSummary["byCity"]>;
32
- } | {
33
- kind: "namedClicks";
34
- rows: NonNullable<AnalyticsSummary["byEventName"]>;
35
- } | {
36
- kind: "sessions";
37
- rows: NonNullable<AnalyticsSummary["sessions"]>;
38
- } | {
39
- kind: "trail";
40
- events: NonNullable<AnalyticsSummary["events"]>;
16
+ /** The English labels, shown in every admin language the host does not translate. */
17
+ declare const ENGLISH_ANALYTICS_LABELS: {
18
+ readonly navGroup: "Statistics";
19
+ readonly navLabel: "Analytics";
20
+ readonly title: "Analytics";
21
+ readonly period: "Period";
22
+ readonly period_24h: "Last 24 hours";
23
+ readonly period_7d: "Last 7 days";
24
+ readonly period_30d: "Last 30 days";
25
+ readonly period_12m: "Last 12 months";
26
+ readonly country: "Country";
27
+ readonly allCountries: "All countries";
28
+ readonly views: "Views";
29
+ readonly averageTime: "Average time on page";
30
+ readonly clicks: "Clicks";
31
+ readonly viewsOverTime: "Views over time";
32
+ readonly topPages: "Top pages";
33
+ readonly allPages: "All pages";
34
+ readonly showAllPages: "Show all pages";
35
+ readonly showTopPages: "Show top pages only";
36
+ readonly page: "Page";
37
+ readonly pageFilter: "Page: {{path}}";
38
+ readonly clearFilter: "Clear";
39
+ readonly countries: "Countries";
40
+ readonly cities: "Cities";
41
+ readonly city: "City";
42
+ readonly event: "Event";
43
+ readonly count: "Count";
44
+ readonly sessions: "Sessions";
45
+ readonly session: "Session";
46
+ readonly started: "Started";
47
+ readonly pages: "Pages";
48
+ readonly sessionEvents: "Session events";
49
+ readonly allSessions: "All sessions";
50
+ readonly time: "Time";
51
+ readonly kind: "Kind";
52
+ readonly kindView: "View";
53
+ readonly kindClick: "Click";
54
+ readonly empty: "Nothing in this period.";
41
55
  };
42
- /**
43
- * Blocks for the admin screen, in display order.
44
- * A module that is off contributes no block.
45
- * A session query replaces the session list with one trail of that session's events, in time order.
46
- */
47
- declare function adminSections(summary: AnalyticsSummary, modules: AnalyticsModules, query: AdminSectionsQuery): AdminSection[];
56
+ type AnalyticsLabelKey = keyof typeof ENGLISH_ANALYTICS_LABELS;
57
+ type AnalyticsLabels = Record<AnalyticsLabelKey, string>;
58
+ /** One admin language's labels. Missing keys show English. `events` names named clicks. */
59
+ type AnalyticsTranslation = Partial<AnalyticsLabels> & {
60
+ events?: Record<string, string>;
61
+ };
62
+ /** Keyed by Payload admin language, such as `en` or `rs-latin`. */
63
+ type AnalyticsTranslations = Record<string, AnalyticsTranslation>;
48
64
 
49
65
  interface AnalyticsEndpointOptions extends AnalyticsServerOptions {
50
66
  /** Without it, the country and city modules store null. */
@@ -53,10 +69,22 @@ interface AnalyticsEndpointOptions extends AnalyticsServerOptions {
53
69
 
54
70
  interface AnalyticsPluginOptions extends AnalyticsEndpointOptions {
55
71
  read?: Access;
72
+ /** IANA time zone the report's hours, days and months follow. Defaults to UTC. */
73
+ timeZone?: string;
74
+ /**
75
+ * Labels per Payload admin language, over the English defaults. A language with no entry
76
+ * shows English, so the package never needs to know the host's languages.
77
+ */
78
+ translations?: AnalyticsTranslations;
79
+ /**
80
+ * The BCP 47 tag per admin language for numbers, dates and country names, for languages whose
81
+ * Payload code is not one (`rs-latin` → `sr-Latn`). Others use Payload's date locale.
82
+ */
83
+ formatLocales?: Record<string, string>;
56
84
  }
57
85
  /**
58
- * Adds the analytics collection, the ingest endpoint, and the admin screen.
59
- * Existing collections, endpoints, and views stay in place.
86
+ * Adds the analytics collection, the ingest endpoint, the admin screen, and its sidebar link.
87
+ * Existing collections, endpoints, views, and translations stay in place.
60
88
  */
61
89
  declare function analyticsPlugin(options: AnalyticsPluginOptions): (config: Config) => Config;
62
90
 
@@ -73,5 +101,5 @@ declare class PayloadAnalyticsEventStore implements AnalyticsEventStore {
73
101
  summarizeAnalytics(query: AnalyticsSummaryQuery, modules: AnalyticsModules): Promise<AnalyticsSummary>;
74
102
  }
75
103
 
76
- export { PayloadAnalyticsEventStore, adminSections, analyticsEventsAccess, analyticsPlugin };
77
- export type { AnalyticsEndpointOptions, AnalyticsPluginOptions };
104
+ export { ENGLISH_ANALYTICS_LABELS, PayloadAnalyticsEventStore, analyticsEventsAccess, analyticsPlugin };
105
+ export type { AnalyticsEndpointOptions, AnalyticsLabelKey, AnalyticsLabels, AnalyticsPluginOptions, AnalyticsTranslation, AnalyticsTranslations };
@@ -3,12 +3,19 @@ import {
3
3
  } from "../chunk-GWOVVKP3.js";
4
4
  import {
5
5
  ANALYTICS_CUSTOM_KEY,
6
+ ANALYTICS_FILTERS_DEPENDENCY,
7
+ ANALYTICS_FILTERS_PATH,
8
+ DEFAULT_ANALYTICS_TIME_ZONE,
9
+ ENGLISH_ANALYTICS_LABELS,
6
10
  PayloadAnalyticsEventStore,
7
- adminSections,
8
11
  analyticsEventsAccess,
9
- analyticsEventsCollection
10
- } from "../chunk-GVOESWHZ.js";
11
- import "../chunk-Q4JRXZ6M.js";
12
+ analyticsEventsCollection,
13
+ withAnalyticsTranslations
14
+ } from "../chunk-QPHMFHRE.js";
15
+ import {
16
+ assertTimeZone
17
+ } from "../chunk-MOCOVYQK.js";
18
+ import "../chunk-YAC3MRH3.js";
12
19
  import "../chunk-F7BY2JQ3.js";
13
20
 
14
21
  // src/payload/endpoint.ts
@@ -64,6 +71,14 @@ function headerRecord(headers) {
64
71
  var DEFAULT_ADMIN_ROUTE = "/admin";
65
72
  var ANALYTICS_VIEW_PATH = "/analytics";
66
73
  function analyticsPlugin(options) {
74
+ const timeZone = options.timeZone ?? DEFAULT_ANALYTICS_TIME_ZONE;
75
+ assertTimeZone(timeZone);
76
+ const custom = {
77
+ enabled: options.enabled,
78
+ modules: options.modules,
79
+ timeZone,
80
+ formatLocales: options.formatLocales ?? {}
81
+ };
67
82
  return (config) => {
68
83
  const adminRoute = config.routes?.admin ?? DEFAULT_ADMIN_ROUTE;
69
84
  const admin = config.admin ?? {};
@@ -72,8 +87,13 @@ function analyticsPlugin(options) {
72
87
  ...config,
73
88
  collections: [...config.collections ?? [], analyticsEventsCollection(options.read)],
74
89
  endpoints: [...config.endpoints ?? [], analyticsEventsEndpoint(options)],
90
+ i18n: withAnalyticsTranslations(config.i18n, options.translations),
75
91
  admin: {
76
92
  ...admin,
93
+ dependencies: {
94
+ ...admin.dependencies,
95
+ [ANALYTICS_FILTERS_DEPENDENCY]: { type: "component", path: ANALYTICS_FILTERS_PATH }
96
+ },
77
97
  components: {
78
98
  ...components,
79
99
  views: {
@@ -98,17 +118,14 @@ function analyticsPlugin(options) {
98
118
  },
99
119
  custom: {
100
120
  ...config.custom,
101
- [ANALYTICS_CUSTOM_KEY]: {
102
- enabled: options.enabled,
103
- modules: options.modules
104
- }
121
+ [ANALYTICS_CUSTOM_KEY]: custom
105
122
  }
106
123
  };
107
124
  };
108
125
  }
109
126
  export {
127
+ ENGLISH_ANALYTICS_LABELS,
110
128
  PayloadAnalyticsEventStore,
111
- adminSections,
112
129
  analyticsEventsAccess,
113
130
  analyticsPlugin
114
131
  };
@@ -1,7 +1,29 @@
1
+ "use client";
2
+
1
3
  // src/payload/admin/nav-link.tsx
2
- import { jsx } from "react/jsx-runtime";
4
+ import { Link, NavGroup, useTranslation } from "@payloadcms/ui";
5
+ import { usePathname } from "next/navigation.js";
6
+
7
+ // src/payload/admin/translations.ts
8
+ var ANALYTICS_TRANSLATION_NAMESPACE = "analytics";
9
+ function analyticsLabel(t, key, vars) {
10
+ return t(`${ANALYTICS_TRANSLATION_NAMESPACE}:${key}`, vars);
11
+ }
12
+
13
+ // src/payload/admin/nav-link.tsx
14
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
+ var NAV_LINK_CLASS = "nav__link";
16
+ var NAV_LINK_ID = "nav-analytics";
3
17
  function AnalyticsNavLink({ href }) {
4
- return /* @__PURE__ */ jsx("a", { href, children: "Analytics" });
18
+ const { t } = useTranslation();
19
+ const pathname = usePathname();
20
+ const translate = t;
21
+ const isActive = pathname === href || pathname.startsWith(`${href}/`);
22
+ const label = /* @__PURE__ */ jsxs(Fragment, { children: [
23
+ isActive ? /* @__PURE__ */ jsx("div", { className: "nav__link-indicator" }) : null,
24
+ /* @__PURE__ */ jsx("span", { className: "nav__link-label", children: analyticsLabel(translate, "navLabel") })
25
+ ] });
26
+ return /* @__PURE__ */ jsx(NavGroup, { label: analyticsLabel(translate, "navGroup"), children: pathname === href ? /* @__PURE__ */ jsx("div", { className: NAV_LINK_CLASS, id: NAV_LINK_ID, children: label }) : /* @__PURE__ */ jsx(Link, { className: NAV_LINK_CLASS, href, id: NAV_LINK_ID, prefetch: false, children: label }) });
5
27
  }
6
28
  export {
7
29
  AnalyticsNavLink
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  acceptDurationSeconds,
3
3
  summarizeAnalyticsEvents
4
- } from "../chunk-Q4JRXZ6M.js";
4
+ } from "../chunk-YAC3MRH3.js";
5
5
  import {
6
6
  AnalyticsEventKind
7
7
  } from "../chunk-F7BY2JQ3.js";
package/package.json CHANGED
@@ -1,90 +1,113 @@
1
- {
2
- "name": "@nodewrite-labs/analytics-sdk",
3
- "version": "0.2.0",
4
- "description": "Analytics SDK for page views and named clicks, with Postgres storage and a Payload admin screen.",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
- "packageManager": "pnpm@12.11.2",
9
- "engines": {
10
- "node": ">=22"
11
- },
12
- "workspaces": [
13
- "playground"
14
- ],
15
- "type": "module",
16
- "main": "./dist/index.js",
17
- "types": "./dist/index.d.ts",
18
- "exports": {
19
- ".": {
20
- "types": "./dist/index.d.ts",
21
- "import": "./dist/index.js"
22
- },
23
- "./client": {
24
- "types": "./dist/client/index.d.ts",
25
- "import": "./dist/client/index.js"
26
- },
27
- "./server": {
28
- "types": "./dist/server/index.d.ts",
29
- "import": "./dist/server/index.js"
30
- },
31
- "./postgres": {
32
- "types": "./dist/postgres/index.d.ts",
33
- "import": "./dist/postgres/index.js"
34
- },
35
- "./payload": {
36
- "types": "./dist/payload/index.d.ts",
37
- "import": "./dist/payload/index.js"
38
- },
39
- "./payload/admin-view": {
40
- "types": "./dist/payload/admin-view.d.ts",
41
- "import": "./dist/payload/admin-view.js"
42
- },
43
- "./payload/nav-link": {
44
- "types": "./dist/payload/nav-link.d.ts",
45
- "import": "./dist/payload/nav-link.js"
46
- }
47
- },
48
- "files": [
49
- "dist",
50
- "src/postgres/schema.sql",
51
- "README.md"
52
- ],
53
- "scripts": {
54
- "typecheck": "tsc --noEmit",
55
- "test": "vitest run",
56
- "build": "tsup"
57
- },
58
- "peerDependencies": {
59
- "@payloadcms/next": "*",
60
- "payload": "*",
61
- "pg": "*"
62
- },
63
- "peerDependenciesMeta": {
64
- "@payloadcms/next": {
65
- "optional": true
66
- },
67
- "payload": {
68
- "optional": true
69
- },
70
- "pg": {
71
- "optional": true
72
- }
73
- },
74
- "devDependencies": {
75
- "@payloadcms/next": "3.92.0",
76
- "@types/node": "^22.20.5",
77
- "@types/pg": "^8.23.1",
78
- "@types/react": "19.2.14",
79
- "esbuild": "^0.28.2",
80
- "graphql": "^16.14.2",
81
- "jsdom": "^30.1.3",
82
- "payload": "3.92.0",
83
- "pg": "^8.23.1",
84
- "react": "^19.3.1",
85
- "tsup": "^9.0.0",
86
- "typescript": "6.0.3",
87
- "vite": "^8.3.5",
88
- "vitest": "^5.0.5"
89
- }
90
- }
1
+ {
2
+ "name": "@nodewrite-labs/analytics-sdk",
3
+ "version": "0.3.0",
4
+ "description": "Analytics SDK for page views and named clicks, with Postgres storage and a Payload admin screen.",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "packageManager": "pnpm@12.11.2",
9
+ "engines": {
10
+ "node": ">=22"
11
+ },
12
+ "workspaces": [
13
+ "playground"
14
+ ],
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./client": {
24
+ "types": "./dist/client/index.d.ts",
25
+ "import": "./dist/client/index.js"
26
+ },
27
+ "./server": {
28
+ "types": "./dist/server/index.d.ts",
29
+ "import": "./dist/server/index.js"
30
+ },
31
+ "./postgres": {
32
+ "types": "./dist/postgres/index.d.ts",
33
+ "import": "./dist/postgres/index.js"
34
+ },
35
+ "./payload": {
36
+ "types": "./dist/payload/index.d.ts",
37
+ "import": "./dist/payload/index.js"
38
+ },
39
+ "./payload/admin-view": {
40
+ "types": "./dist/payload/admin-view.d.ts",
41
+ "import": "./dist/payload/admin-view.js"
42
+ },
43
+ "./payload/nav-link": {
44
+ "types": "./dist/payload/nav-link.d.ts",
45
+ "import": "./dist/payload/nav-link.js"
46
+ },
47
+ "./payload/admin-filters": {
48
+ "types": "./dist/payload/admin-filters.d.ts",
49
+ "import": "./dist/payload/admin-filters.js"
50
+ }
51
+ },
52
+ "files": [
53
+ "dist",
54
+ "src/postgres/schema.sql",
55
+ "README.md"
56
+ ],
57
+ "scripts": {
58
+ "typecheck": "tsc --noEmit",
59
+ "test": "vitest run",
60
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsup"
61
+ },
62
+ "peerDependencies": {
63
+ "@payloadcms/next": "*",
64
+ "@payloadcms/ui": "*",
65
+ "next": "*",
66
+ "payload": "*",
67
+ "pg": "*",
68
+ "react": "*"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "@payloadcms/next": {
72
+ "optional": true
73
+ },
74
+ "@payloadcms/ui": {
75
+ "optional": true
76
+ },
77
+ "next": {
78
+ "optional": true
79
+ },
80
+ "react": {
81
+ "optional": true
82
+ },
83
+ "payload": {
84
+ "optional": true
85
+ },
86
+ "pg": {
87
+ "optional": true
88
+ }
89
+ },
90
+ "devDependencies": {
91
+ "@payloadcms/next": "3.92.0",
92
+ "@payloadcms/ui": "3.92.0",
93
+ "@types/node": "^22.20.5",
94
+ "@types/pg": "^8.23.1",
95
+ "@types/react": "19.2.14",
96
+ "@types/react-dom": "19.2.3",
97
+ "esbuild": "^0.28.2",
98
+ "graphql": "^16.14.2",
99
+ "jsdom": "^30.1.3",
100
+ "next": "16.3.3",
101
+ "payload": "3.92.0",
102
+ "pg": "^8.23.1",
103
+ "react": "^19.3.1",
104
+ "react-dom": "19.3.1",
105
+ "tsup": "^9.0.0",
106
+ "typescript": "6.0.3",
107
+ "vite": "^8.3.5",
108
+ "vitest": "^5.0.5"
109
+ },
110
+ "dependencies": {
111
+ "country-flag-icons": "1.6.20"
112
+ }
113
+ }