@carlonicora/nextjs-jsonapi 3.24.2 → 3.25.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.
Files changed (178) hide show
  1. package/dist/{AssistantMessageInterface-1Kbz8RVs.d.ts → AssistantMessageInterface-CubxuBIJ.d.ts} +1 -1
  2. package/dist/{AssistantMessageInterface-CoxJyuFM.d.mts → AssistantMessageInterface-sHjI2zPN.d.mts} +1 -1
  3. package/dist/{BlockNoteEditor-65HR6ULV.mjs → BlockNoteEditor-46O3JC4Z.mjs} +5 -5
  4. package/dist/{BlockNoteEditor-35D4R7HJ.js → BlockNoteEditor-HKS6AXDA.js} +20 -20
  5. package/dist/{BlockNoteEditor-35D4R7HJ.js.map → BlockNoteEditor-HKS6AXDA.js.map} +1 -1
  6. package/dist/{BlockNoteViewer-QDXDVYEM.js → BlockNoteViewer-7BFNTN4E.js} +4 -4
  7. package/dist/{BlockNoteViewer-QDXDVYEM.js.map → BlockNoteViewer-7BFNTN4E.js.map} +1 -1
  8. package/dist/{BlockNoteViewer-KDKGUIG5.mjs → BlockNoteViewer-KRTAFTUX.mjs} +3 -3
  9. package/dist/{HandbookSectionInterface-OePKPbqN.d.ts → HandbookSectionInterface-Ck1wZJ81.d.ts} +1 -1
  10. package/dist/{HandbookSectionInterface-BYsmnPEz.d.mts → HandbookSectionInterface-DgYH--Ik.d.mts} +1 -1
  11. package/dist/JsonApiRequest-TE5WD3WS.js +26 -0
  12. package/dist/{JsonApiRequest-G625QAH5.js.map → JsonApiRequest-TE5WD3WS.js.map} +1 -1
  13. package/dist/{JsonApiRequest-RRMNV7YE.mjs → JsonApiRequest-UHOWNI3O.mjs} +2 -2
  14. package/dist/analytics.modules-CfczU12P.d.ts +25 -0
  15. package/dist/analytics.modules-pzGwMASI.d.mts +25 -0
  16. package/dist/{auth.interface-BC1jp_ni.d.ts → auth.interface-BzI4onI_.d.ts} +1 -1
  17. package/dist/{auth.interface-dR2-6gPM.d.mts → auth.interface-Df4vXbNz.d.mts} +1 -1
  18. package/dist/billing/index.js +402 -402
  19. package/dist/billing/index.mjs +4 -4
  20. package/dist/{chunk-YXRVPEEJ.js → chunk-26MQIJNB.js} +1 -1
  21. package/dist/{chunk-YXRVPEEJ.js.map → chunk-26MQIJNB.js.map} +1 -1
  22. package/dist/{chunk-T76WLDE5.mjs → chunk-37DMFVLN.mjs} +1 -1
  23. package/dist/chunk-4Z2UONN2.mjs +110 -0
  24. package/dist/chunk-4Z2UONN2.mjs.map +1 -0
  25. package/dist/{chunk-3AR4KA5D.js → chunk-4Z45NYPR.js} +13 -13
  26. package/dist/{chunk-3AR4KA5D.js.map → chunk-4Z45NYPR.js.map} +1 -1
  27. package/dist/chunk-7B4E42R5.mjs +280 -0
  28. package/dist/chunk-7B4E42R5.mjs.map +1 -0
  29. package/dist/{chunk-U7QVISQS.js → chunk-CAKOFJZD.js} +450 -40
  30. package/dist/chunk-CAKOFJZD.js.map +1 -0
  31. package/dist/{chunk-IBVKPLHT.mjs → chunk-E3I4MT53.mjs} +422 -12
  32. package/dist/chunk-E3I4MT53.mjs.map +1 -0
  33. package/dist/chunk-GHFMKH3M.js +280 -0
  34. package/dist/chunk-GHFMKH3M.js.map +1 -0
  35. package/dist/chunk-KLV6RLDE.js +110 -0
  36. package/dist/chunk-KLV6RLDE.js.map +1 -0
  37. package/dist/{chunk-ZJI5ZZW5.mjs → chunk-Q6J2ICKO.mjs} +3 -3
  38. package/dist/{chunk-4O7R2Z2O.mjs → chunk-XDYILJ2S.mjs} +1749 -1737
  39. package/dist/{chunk-4O7R2Z2O.mjs.map → chunk-XDYILJ2S.mjs.map} +1 -1
  40. package/dist/{chunk-2XCUAHQT.js → chunk-Y3OKEQGL.js} +1126 -1114
  41. package/dist/chunk-Y3OKEQGL.js.map +1 -0
  42. package/dist/client/index.d.mts +6 -5
  43. package/dist/client/index.d.ts +6 -5
  44. package/dist/client/index.js +5 -5
  45. package/dist/client/index.js.map +1 -1
  46. package/dist/client/index.mjs +4 -4
  47. package/dist/components/index.d.mts +17 -7
  48. package/dist/components/index.d.ts +17 -7
  49. package/dist/components/index.js +5 -5
  50. package/dist/components/index.mjs +4 -4
  51. package/dist/{config-B6UB7j69.d.ts → config-3N1LSrcC.d.ts} +1 -1
  52. package/dist/{config-Cd5xTEfZ.d.ts → config-AVtuvfM1.d.ts} +1 -1
  53. package/dist/{config-n-ZpPmOL.d.mts → config-BSKTYDoh.d.mts} +1 -1
  54. package/dist/{config-YyhFHxkU.d.mts → config-BmufyEj6.d.mts} +1 -1
  55. package/dist/contexts/index.d.mts +5 -4
  56. package/dist/contexts/index.d.ts +5 -4
  57. package/dist/contexts/index.js +5 -5
  58. package/dist/contexts/index.mjs +4 -4
  59. package/dist/core/index.d.mts +31 -14
  60. package/dist/core/index.d.ts +31 -14
  61. package/dist/core/index.js +9 -5
  62. package/dist/core/index.js.map +1 -1
  63. package/dist/core/index.mjs +8 -4
  64. package/dist/features/analytics/index.css +30 -0
  65. package/dist/features/analytics/index.css.map +1 -0
  66. package/dist/features/analytics/index.d.mts +508 -0
  67. package/dist/features/analytics/index.d.ts +508 -0
  68. package/dist/features/analytics/index.js +1166 -0
  69. package/dist/features/analytics/index.js.map +1 -0
  70. package/dist/features/analytics/index.mjs +1166 -0
  71. package/dist/features/analytics/index.mjs.map +1 -0
  72. package/dist/features/analytics/server.d.mts +28 -0
  73. package/dist/features/analytics/server.d.ts +28 -0
  74. package/dist/features/analytics/server.js +41 -0
  75. package/dist/features/analytics/server.js.map +1 -0
  76. package/dist/features/analytics/server.mjs +41 -0
  77. package/dist/features/analytics/server.mjs.map +1 -0
  78. package/dist/features/help/index.d.mts +2 -2
  79. package/dist/features/help/index.d.ts +2 -2
  80. package/dist/features/help/index.js +38 -38
  81. package/dist/features/help/index.mjs +4 -4
  82. package/dist/features/tokenusage/index.d.mts +2 -2
  83. package/dist/features/tokenusage/index.d.ts +2 -2
  84. package/dist/features/tokenusage/index.js +139 -221
  85. package/dist/features/tokenusage/index.js.map +1 -1
  86. package/dist/features/tokenusage/index.mjs +26 -108
  87. package/dist/features/tokenusage/index.mjs.map +1 -1
  88. package/dist/index.d.mts +11 -9
  89. package/dist/index.d.ts +11 -9
  90. package/dist/index.js +10 -6
  91. package/dist/index.js.map +1 -1
  92. package/dist/index.mjs +9 -5
  93. package/dist/notification.interface-BNCvnVCW.d.ts +34 -0
  94. package/dist/notification.interface-P_pNfzZQ.d.mts +34 -0
  95. package/dist/{s3.service-Bqr-fQmi.d.ts → s3.service-CbUa6a-8.d.ts} +3 -2
  96. package/dist/{s3.service-Buiy1ncj.d.mts → s3.service-DAp8FbuG.d.mts} +3 -2
  97. package/dist/server/index.d.mts +5 -4
  98. package/dist/server/index.d.ts +5 -4
  99. package/dist/server/index.js +39 -279
  100. package/dist/server/index.js.map +1 -1
  101. package/dist/server/index.mjs +20 -260
  102. package/dist/server/index.mjs.map +1 -1
  103. package/dist/{types-bsm8Fp55.d.ts → types-Cn153-mH.d.ts} +1 -1
  104. package/dist/{types-Bq_UA8Lg.d.mts → types-D0uLYV2P.d.mts} +1 -1
  105. package/dist/{usePageUrlGenerator-J_ECv-oQ.d.ts → usePageUrlGenerator-B-3FRKBD.d.ts} +1 -1
  106. package/dist/{usePageUrlGenerator-uKBjAKqC.d.mts → usePageUrlGenerator-CCJ_cdQy.d.mts} +1 -1
  107. package/dist/{useSocket-BcH7l7B8.d.mts → useSocket-B9gTbuCj.d.mts} +1 -1
  108. package/dist/{useSocket-DCZd_eJk.d.ts → useSocket-BHVIyigI.d.ts} +1 -1
  109. package/dist/{notification.interface-MnRt1Tjm.d.mts → user.interface-CSaGWFbg.d.mts} +1 -31
  110. package/dist/{notification.interface-CjzV_KII.d.ts → user.interface-CrmwJLYW.d.ts} +1 -31
  111. package/package.json +11 -1
  112. package/src/client/index.ts +2 -0
  113. package/src/core/index.ts +4 -0
  114. package/src/core/registry/ModuleRegistry.ts +7 -0
  115. package/src/features/__tests__/i18n-keys.contract.spec.ts +1 -0
  116. package/src/features/analytics/__tests__/server-entry.spec.tsx +64 -0
  117. package/src/features/analytics/analytics.modules.ts +30 -0
  118. package/src/features/analytics/components/AnalyticsAdminContainer.tsx +97 -0
  119. package/src/features/analytics/components/AnalyticsAdminFilterBar.tsx +117 -0
  120. package/src/features/analytics/components/AnalyticsAdminPageContainer.tsx +25 -0
  121. package/src/features/analytics/components/AnalyticsAdminTiles.tsx +191 -0
  122. package/src/features/analytics/components/AnalyticsBreakdownTable.tsx +104 -0
  123. package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +246 -0
  124. package/src/features/analytics/components/AnalyticsJourneyPageContainer.tsx +14 -0
  125. package/src/features/analytics/components/AnalyticsSessionsTable.tsx +107 -0
  126. package/src/features/analytics/components/AnalyticsTimelineChart.tsx +175 -0
  127. package/src/features/analytics/components/AnalyticsTracker.tsx +112 -0
  128. package/src/features/analytics/components/__tests__/AnalyticsAdminTiles.spec.tsx +39 -0
  129. package/src/features/analytics/components/__tests__/AnalyticsBreakdownTable.spec.tsx +39 -0
  130. package/src/features/analytics/components/__tests__/AnalyticsJourneyContainer.spec.tsx +124 -0
  131. package/src/features/analytics/components/__tests__/AnalyticsPageContainers.spec.tsx +117 -0
  132. package/src/features/analytics/components/__tests__/AnalyticsSessionsTable.spec.tsx +121 -0
  133. package/src/features/analytics/components/__tests__/AnalyticsTimelineChart.spec.tsx +35 -0
  134. package/src/features/analytics/components/__tests__/AnalyticsTracker.spec.tsx +248 -0
  135. package/src/features/analytics/components/server/AnalyticsAdminPage.tsx +28 -0
  136. package/src/features/analytics/components/server/AnalyticsJourneyPage.tsx +25 -0
  137. package/src/features/analytics/contexts/AnalyticsAdminContext.tsx +273 -0
  138. package/src/features/analytics/contexts/__tests__/AnalyticsAdminContext.spec.tsx +140 -0
  139. package/src/features/analytics/data/AnalyticsAdminService.ts +91 -0
  140. package/src/features/analytics/data/AnalyticsService.ts +23 -0
  141. package/src/features/analytics/data/__tests__/AnalyticsAdminService.spec.ts +117 -0
  142. package/src/features/analytics/data/__tests__/AnalyticsService.spec.ts +56 -0
  143. package/src/features/analytics/data/__tests__/models.spec.ts +300 -0
  144. package/src/features/analytics/data/analytics-breakdown.interface.ts +9 -0
  145. package/src/features/analytics/data/analytics-breakdown.ts +65 -0
  146. package/src/features/analytics/data/analytics-event.interface.ts +7 -0
  147. package/src/features/analytics/data/analytics-event.ts +63 -0
  148. package/src/features/analytics/data/analytics-page-view.interface.ts +9 -0
  149. package/src/features/analytics/data/analytics-page-view.ts +57 -0
  150. package/src/features/analytics/data/analytics-session.interface.ts +23 -0
  151. package/src/features/analytics/data/analytics-session.ts +144 -0
  152. package/src/features/analytics/data/analytics-summary.interface.ts +11 -0
  153. package/src/features/analytics/data/analytics-summary.ts +79 -0
  154. package/src/features/analytics/data/analytics-timeline.interface.ts +10 -0
  155. package/src/features/analytics/data/analytics-timeline.ts +67 -0
  156. package/src/features/analytics/data/analytics.types.ts +97 -0
  157. package/src/features/analytics/data/index.ts +15 -0
  158. package/src/features/analytics/i18n-keys.ts +76 -0
  159. package/src/features/analytics/index.ts +31 -0
  160. package/src/features/analytics/lib/__tests__/utm.spec.ts +20 -0
  161. package/src/features/analytics/lib/__tests__/visitorCookie.spec.ts +63 -0
  162. package/src/features/analytics/lib/utm.ts +29 -0
  163. package/src/features/analytics/lib/visitorCookie.ts +32 -0
  164. package/src/features/analytics/server-entry.ts +8 -0
  165. package/src/features/waitlist/components/lists/WaitlistList.tsx +68 -121
  166. package/src/features/waitlist/data/WaitlistService.ts +2 -2
  167. package/src/features/waitlist/data/index.ts +1 -0
  168. package/src/features/waitlist/data/waitlist.fields.ts +8 -0
  169. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +69 -30
  170. package/dist/JsonApiRequest-G625QAH5.js +0 -26
  171. package/dist/chunk-2XCUAHQT.js.map +0 -1
  172. package/dist/chunk-IBVKPLHT.mjs.map +0 -1
  173. package/dist/chunk-U7QVISQS.js.map +0 -1
  174. /package/dist/{BlockNoteEditor-65HR6ULV.mjs.map → BlockNoteEditor-46O3JC4Z.mjs.map} +0 -0
  175. /package/dist/{BlockNoteViewer-KDKGUIG5.mjs.map → BlockNoteViewer-KRTAFTUX.mjs.map} +0 -0
  176. /package/dist/{JsonApiRequest-RRMNV7YE.mjs.map → JsonApiRequest-UHOWNI3O.mjs.map} +0 -0
  177. /package/dist/{chunk-T76WLDE5.mjs.map → chunk-37DMFVLN.mjs.map} +0 -0
  178. /package/dist/{chunk-ZJI5ZZW5.mjs.map → chunk-Q6J2ICKO.mjs.map} +0 -0
@@ -0,0 +1,144 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { UserInterface } from "../../user/data/user.interface";
3
+ import { AnalyticsSessionInterface } from "./analytics-session.interface";
4
+ import { AnalyticsSessionInput } from "./analytics.types";
5
+
6
+ /** One visit: a run of page views from one visitor with no 30-minute gap. */
7
+ export class AnalyticsSession extends AbstractApiData implements AnalyticsSessionInterface {
8
+ private _visitorId: string = "";
9
+ private _consented: boolean = false;
10
+ private _startedAt: Date = new Date(0);
11
+ private _lastSeenAt: Date = new Date(0);
12
+ private _pageViews: number = 0;
13
+ private _section: string = "";
14
+ private _landingRoute: string = "";
15
+ private _referrerHost?: string;
16
+ private _utmSource?: string;
17
+ private _utmMedium?: string;
18
+ private _utmCampaign?: string;
19
+ private _utmTerm?: string;
20
+ private _utmContent?: string;
21
+ private _deviceType: string = "";
22
+ private _user?: UserInterface;
23
+
24
+ get visitorId(): string {
25
+ return this._visitorId;
26
+ }
27
+
28
+ get consented(): boolean {
29
+ return this._consented;
30
+ }
31
+
32
+ get startedAt(): Date {
33
+ return this._startedAt;
34
+ }
35
+
36
+ get lastSeenAt(): Date {
37
+ return this._lastSeenAt;
38
+ }
39
+
40
+ get pageViews(): number {
41
+ return this._pageViews;
42
+ }
43
+
44
+ get section(): string {
45
+ return this._section;
46
+ }
47
+
48
+ get landingRoute(): string {
49
+ return this._landingRoute;
50
+ }
51
+
52
+ get referrerHost(): string | undefined {
53
+ return this._referrerHost;
54
+ }
55
+
56
+ get utmSource(): string | undefined {
57
+ return this._utmSource;
58
+ }
59
+
60
+ get utmMedium(): string | undefined {
61
+ return this._utmMedium;
62
+ }
63
+
64
+ get utmCampaign(): string | undefined {
65
+ return this._utmCampaign;
66
+ }
67
+
68
+ get utmTerm(): string | undefined {
69
+ return this._utmTerm;
70
+ }
71
+
72
+ get utmContent(): string | undefined {
73
+ return this._utmContent;
74
+ }
75
+
76
+ get deviceType(): string {
77
+ return this._deviceType;
78
+ }
79
+
80
+ get user(): UserInterface | undefined {
81
+ return this._user;
82
+ }
83
+
84
+ rehydrate(data: JsonApiHydratedDataInterface): this {
85
+ super.rehydrate(data);
86
+
87
+ const attrs = data.jsonApi.attributes;
88
+ this._visitorId = attrs.visitorId ?? "";
89
+ this._consented = attrs.consented ?? false;
90
+ // Wire format is an ISO 8601 instant; the interface promises a Date.
91
+ this._startedAt = attrs.startedAt ? new Date(attrs.startedAt) : new Date(0);
92
+ this._lastSeenAt = attrs.lastSeenAt ? new Date(attrs.lastSeenAt) : new Date(0);
93
+ this._pageViews = attrs.pageViews ?? 0;
94
+ this._section = attrs.section ?? "";
95
+ this._landingRoute = attrs.landingRoute ?? "";
96
+ this._referrerHost = attrs.referrerHost ?? undefined;
97
+ this._utmSource = attrs.utmSource ?? undefined;
98
+ this._utmMedium = attrs.utmMedium ?? undefined;
99
+ this._utmCampaign = attrs.utmCampaign ?? undefined;
100
+ this._utmTerm = attrs.utmTerm ?? undefined;
101
+ this._utmContent = attrs.utmContent ?? undefined;
102
+ this._deviceType = attrs.deviceType ?? "";
103
+
104
+ this._user = this._readIncluded(data, "user", Modules.User) as UserInterface | undefined;
105
+
106
+ return this;
107
+ }
108
+
109
+ createJsonApi(data: AnalyticsSessionInput) {
110
+ const response: any = {
111
+ data: {
112
+ type: Modules.AnalyticsSession.name,
113
+ id: data.id,
114
+ attributes: {},
115
+ relationships: {},
116
+ },
117
+ included: [],
118
+ };
119
+
120
+ if (data.visitorId !== undefined) response.data.attributes.visitorId = data.visitorId;
121
+ if (data.consented !== undefined) response.data.attributes.consented = data.consented;
122
+ // Both are type: "datetime" on the backend descriptor: ISO 8601 instants.
123
+ if (data.startedAt !== undefined) response.data.attributes.startedAt = data.startedAt.toISOString();
124
+ if (data.lastSeenAt !== undefined) response.data.attributes.lastSeenAt = data.lastSeenAt.toISOString();
125
+ if (data.pageViews !== undefined) response.data.attributes.pageViews = data.pageViews;
126
+ if (data.section !== undefined) response.data.attributes.section = data.section;
127
+ if (data.landingRoute !== undefined) response.data.attributes.landingRoute = data.landingRoute;
128
+ if (data.referrerHost !== undefined) response.data.attributes.referrerHost = data.referrerHost;
129
+ if (data.utmSource !== undefined) response.data.attributes.utmSource = data.utmSource;
130
+ if (data.utmMedium !== undefined) response.data.attributes.utmMedium = data.utmMedium;
131
+ if (data.utmCampaign !== undefined) response.data.attributes.utmCampaign = data.utmCampaign;
132
+ if (data.utmTerm !== undefined) response.data.attributes.utmTerm = data.utmTerm;
133
+ if (data.utmContent !== undefined) response.data.attributes.utmContent = data.utmContent;
134
+ if (data.deviceType !== undefined) response.data.attributes.deviceType = data.deviceType;
135
+
136
+ if (data.userId) {
137
+ response.data.relationships.user = {
138
+ data: { type: Modules.User.name, id: data.userId },
139
+ };
140
+ }
141
+
142
+ return response;
143
+ }
144
+ }
@@ -0,0 +1,11 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface AnalyticsSummaryInterface extends ApiDataInterface {
4
+ get section(): string;
5
+ get window(): string;
6
+ get visitors(): number;
7
+ get sessions(): number;
8
+ get pageViews(): number;
9
+ get pagesPerSession(): number;
10
+ get consentShare(): number;
11
+ }
@@ -0,0 +1,79 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { AnalyticsSummaryInterface } from "./analytics-summary.interface";
3
+ import { AnalyticsSummaryInput } from "./analytics.types";
4
+
5
+ /** One (section, window) row of the headline tiles: current or previous period. */
6
+ export class AnalyticsSummary extends AbstractApiData implements AnalyticsSummaryInterface {
7
+ private _section: string = "";
8
+ private _window: string = "";
9
+ private _visitors: number = 0;
10
+ private _sessions: number = 0;
11
+ private _pageViews: number = 0;
12
+ private _pagesPerSession: number = 0;
13
+ private _consentShare: number = 0;
14
+
15
+ get section(): string {
16
+ return this._section;
17
+ }
18
+
19
+ get window(): string {
20
+ return this._window;
21
+ }
22
+
23
+ get visitors(): number {
24
+ return this._visitors;
25
+ }
26
+
27
+ get sessions(): number {
28
+ return this._sessions;
29
+ }
30
+
31
+ get pageViews(): number {
32
+ return this._pageViews;
33
+ }
34
+
35
+ get pagesPerSession(): number {
36
+ return this._pagesPerSession;
37
+ }
38
+
39
+ get consentShare(): number {
40
+ return this._consentShare;
41
+ }
42
+
43
+ rehydrate(data: JsonApiHydratedDataInterface): this {
44
+ super.rehydrate(data);
45
+
46
+ const attrs = data.jsonApi.attributes;
47
+ this._section = attrs.section ?? "";
48
+ this._window = attrs.window ?? "";
49
+ this._visitors = attrs.visitors ?? 0;
50
+ this._sessions = attrs.sessions ?? 0;
51
+ this._pageViews = attrs.pageViews ?? 0;
52
+ this._pagesPerSession = attrs.pagesPerSession ?? 0;
53
+ this._consentShare = attrs.consentShare ?? 0;
54
+
55
+ return this;
56
+ }
57
+
58
+ createJsonApi(data: AnalyticsSummaryInput) {
59
+ const response: any = {
60
+ data: {
61
+ type: Modules.AnalyticsSummary.name,
62
+ id: data.id,
63
+ attributes: {},
64
+ relationships: {},
65
+ },
66
+ included: [],
67
+ };
68
+
69
+ if (data.section !== undefined) response.data.attributes.section = data.section;
70
+ if (data.window !== undefined) response.data.attributes.window = data.window;
71
+ if (data.visitors !== undefined) response.data.attributes.visitors = data.visitors;
72
+ if (data.sessions !== undefined) response.data.attributes.sessions = data.sessions;
73
+ if (data.pageViews !== undefined) response.data.attributes.pageViews = data.pageViews;
74
+ if (data.pagesPerSession !== undefined) response.data.attributes.pagesPerSession = data.pagesPerSession;
75
+ if (data.consentShare !== undefined) response.data.attributes.consentShare = data.consentShare;
76
+
77
+ return response;
78
+ }
79
+ }
@@ -0,0 +1,10 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface AnalyticsTimelineInterface extends ApiDataInterface {
4
+ /** Backend declares this `type: "date"`, so it is a Date in memory. */
5
+ get bucket(): Date;
6
+ get section(): string;
7
+ get visitors(): number;
8
+ get sessions(): number;
9
+ get pageViews(): number;
10
+ }
@@ -0,0 +1,67 @@
1
+ import { AbstractApiData, formatLocalDate, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { AnalyticsTimelineInterface } from "./analytics-timeline.interface";
3
+ import { AnalyticsTimelineInput } from "./analytics.types";
4
+
5
+ /** One (bucket, section) point of the visitors-over-time chart. */
6
+ export class AnalyticsTimeline extends AbstractApiData implements AnalyticsTimelineInterface {
7
+ private _bucket: Date = new Date(0);
8
+ private _section: string = "";
9
+ private _visitors: number = 0;
10
+ private _sessions: number = 0;
11
+ private _pageViews: number = 0;
12
+
13
+ get bucket(): Date {
14
+ return this._bucket;
15
+ }
16
+
17
+ get section(): string {
18
+ return this._section;
19
+ }
20
+
21
+ get visitors(): number {
22
+ return this._visitors;
23
+ }
24
+
25
+ get sessions(): number {
26
+ return this._sessions;
27
+ }
28
+
29
+ get pageViews(): number {
30
+ return this._pageViews;
31
+ }
32
+
33
+ rehydrate(data: JsonApiHydratedDataInterface): this {
34
+ super.rehydrate(data);
35
+
36
+ const attrs = data.jsonApi.attributes;
37
+ // Wire format is "YYYY-MM-DD"; the interface promises a Date.
38
+ this._bucket = attrs.bucket ? new Date(attrs.bucket) : new Date(0);
39
+ this._section = attrs.section ?? "";
40
+ this._visitors = attrs.visitors ?? 0;
41
+ this._sessions = attrs.sessions ?? 0;
42
+ this._pageViews = attrs.pageViews ?? 0;
43
+
44
+ return this;
45
+ }
46
+
47
+ createJsonApi(data: AnalyticsTimelineInput) {
48
+ const response: any = {
49
+ data: {
50
+ type: Modules.AnalyticsTimeline.name,
51
+ id: data.id,
52
+ attributes: {},
53
+ relationships: {},
54
+ },
55
+ included: [],
56
+ };
57
+
58
+ // `bucket` is type: "date" on the backend descriptor: formatLocalDate, never the raw Date.
59
+ if (data.bucket !== undefined) response.data.attributes.bucket = formatLocalDate(data.bucket);
60
+ if (data.section !== undefined) response.data.attributes.section = data.section;
61
+ if (data.visitors !== undefined) response.data.attributes.visitors = data.visitors;
62
+ if (data.sessions !== undefined) response.data.attributes.sessions = data.sessions;
63
+ if (data.pageViews !== undefined) response.data.attributes.pageViews = data.pageViews;
64
+
65
+ return response;
66
+ }
67
+ }
@@ -0,0 +1,97 @@
1
+ // Each value list is the single source of its union type: components iterate
2
+ // the constant, and the type is derived from it so the two cannot drift.
3
+
4
+ export const ANALYTICS_SECTIONS = ["all", "public", "app"] as const;
5
+ export type AnalyticsSection = (typeof ANALYTICS_SECTIONS)[number];
6
+
7
+ export const ANALYTICS_GRANULARITIES = ["day", "week", "month"] as const;
8
+ export type AnalyticsGranularity = (typeof ANALYTICS_GRANULARITIES)[number];
9
+
10
+ export const ANALYTICS_DIMENSIONS = ["source", "medium", "campaign", "referrer", "route", "landing"] as const;
11
+ export type AnalyticsDimension = (typeof ANALYTICS_DIMENSIONS)[number];
12
+
13
+ export type AnalyticsFilters = {
14
+ /** ISO 8601 instant. */
15
+ from: string;
16
+ /** ISO 8601 instant. */
17
+ to: string;
18
+ section: AnalyticsSection;
19
+ };
20
+
21
+ /** One page view, as the tracker posts it to `analytics/events`. */
22
+ export type AnalyticsEventInput = {
23
+ id: string;
24
+ path: string;
25
+ section: "public" | "app";
26
+ referrer?: string;
27
+ utmSource?: string;
28
+ utmMedium?: string;
29
+ utmCampaign?: string;
30
+ utmTerm?: string;
31
+ utmContent?: string;
32
+ visitorId?: string;
33
+ screenWidth?: number;
34
+ };
35
+
36
+ // Input types for the read-only models' createJsonApi(). `id` is always
37
+ // required — JSON:API resource objects are identified.
38
+
39
+ export type AnalyticsSummaryInput = {
40
+ id: string;
41
+ section: string;
42
+ window: string;
43
+ visitors: number;
44
+ sessions: number;
45
+ pageViews: number;
46
+ pagesPerSession: number;
47
+ consentShare: number;
48
+ };
49
+
50
+ export type AnalyticsTimelineInput = {
51
+ id: string;
52
+ /** Backend field type is "date" — emitted via formatLocalDate, never raw. */
53
+ bucket: Date;
54
+ section: string;
55
+ visitors: number;
56
+ sessions: number;
57
+ pageViews: number;
58
+ };
59
+
60
+ export type AnalyticsBreakdownInput = {
61
+ id: string;
62
+ dimension: string;
63
+ key: string;
64
+ visitors: number;
65
+ sessions: number;
66
+ pageViews: number;
67
+ };
68
+
69
+ export type AnalyticsSessionInput = {
70
+ id: string;
71
+ visitorId: string;
72
+ consented: boolean;
73
+ /** Backend field type is "datetime" — emitted via toISOString(). */
74
+ startedAt: Date;
75
+ /** Backend field type is "datetime" — emitted via toISOString(). */
76
+ lastSeenAt: Date;
77
+ pageViews: number;
78
+ section: string;
79
+ landingRoute: string;
80
+ referrerHost?: string;
81
+ utmSource?: string;
82
+ utmMedium?: string;
83
+ utmCampaign?: string;
84
+ utmTerm?: string;
85
+ utmContent?: string;
86
+ deviceType: string;
87
+ userId?: string;
88
+ };
89
+
90
+ export type AnalyticsPageViewInput = {
91
+ id: string;
92
+ /** Backend field type is "datetime" — emitted via toISOString(). */
93
+ createdAt: Date;
94
+ path: string;
95
+ route: string;
96
+ section: string;
97
+ };
@@ -0,0 +1,15 @@
1
+ export * from "./analytics.types";
2
+ export { AnalyticsEvent } from "./analytics-event";
3
+ export type { AnalyticsEventInterface } from "./analytics-event.interface";
4
+ export { AnalyticsSummary } from "./analytics-summary";
5
+ export type { AnalyticsSummaryInterface } from "./analytics-summary.interface";
6
+ export { AnalyticsTimeline } from "./analytics-timeline";
7
+ export type { AnalyticsTimelineInterface } from "./analytics-timeline.interface";
8
+ export { AnalyticsBreakdown } from "./analytics-breakdown";
9
+ export type { AnalyticsBreakdownInterface } from "./analytics-breakdown.interface";
10
+ export { AnalyticsSession } from "./analytics-session";
11
+ export type { AnalyticsSessionInterface } from "./analytics-session.interface";
12
+ export { AnalyticsPageView } from "./analytics-page-view";
13
+ export type { AnalyticsPageViewInterface } from "./analytics-page-view.interface";
14
+ export { AnalyticsService } from "./AnalyticsService";
15
+ export { AnalyticsAdminService } from "./AnalyticsAdminService";
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The fixed namespace of i18n keys the administrative analytics feature reads
3
+ * via useTranslations. Consuming apps must define each entry in their
4
+ * messages/<locale>.json — this list is the contract between the package and
5
+ * the app.
6
+ *
7
+ * The description key is read by the consuming app's page metadata only, not by
8
+ * a component in this package, which is why the contract spec lists it among
9
+ * the keys declared for app-rendered surfaces.
10
+ */
11
+ export const ANALYTICS_ADMIN_I18N_KEYS = [
12
+ // Page
13
+ "analytics.admin.title",
14
+ "analytics.admin.description",
15
+
16
+ // KPI tiles
17
+ "analytics.admin.tiles.visitors",
18
+ "analytics.admin.tiles.sessions",
19
+ "analytics.admin.tiles.page_views",
20
+ "analytics.admin.tiles.pages_per_session",
21
+ "analytics.admin.tiles.consent_share",
22
+ "analytics.admin.tiles.vs_previous",
23
+ "analytics.admin.tiles.summarised_note",
24
+
25
+ // Filter bar
26
+ "analytics.admin.filter.section",
27
+ "analytics.admin.filter.section_all",
28
+ "analytics.admin.filter.section_public",
29
+ "analytics.admin.filter.section_app",
30
+ "analytics.admin.filter.granularity",
31
+ "analytics.admin.filter.granularity_day",
32
+ "analytics.admin.filter.granularity_week",
33
+ "analytics.admin.filter.granularity_month",
34
+
35
+ // Panel titles
36
+ "analytics.admin.panels.over_time",
37
+ "analytics.admin.panels.breakdown",
38
+ "analytics.admin.panels.sessions",
39
+ "analytics.admin.panels.journey",
40
+ "analytics.admin.panels.other_sessions",
41
+
42
+ // Breakdown tabs
43
+ "analytics.admin.dimensions.source",
44
+ "analytics.admin.dimensions.medium",
45
+ "analytics.admin.dimensions.campaign",
46
+ "analytics.admin.dimensions.referrer",
47
+ "analytics.admin.dimensions.route",
48
+ "analytics.admin.dimensions.landing",
49
+
50
+ // Table columns
51
+ "analytics.admin.columns.key",
52
+ "analytics.admin.columns.visitors",
53
+ "analytics.admin.columns.sessions",
54
+ "analytics.admin.columns.page_views",
55
+ "analytics.admin.columns.started",
56
+ "analytics.admin.columns.visitor",
57
+ "analytics.admin.columns.source",
58
+ "analytics.admin.columns.landing",
59
+ "analytics.admin.columns.pages",
60
+ "analytics.admin.columns.device",
61
+ "analytics.admin.columns.time",
62
+ "analytics.admin.columns.gap",
63
+ "analytics.admin.columns.path",
64
+
65
+ // Shared states and markers
66
+ "analytics.admin.anonymous",
67
+ "analytics.admin.consented",
68
+ "analytics.admin.not_consented",
69
+ "analytics.admin.load_more",
70
+ "analytics.admin.no_data",
71
+ "analytics.admin.other",
72
+
73
+ // Timeline chart series
74
+ "analytics.admin.series.visitors",
75
+ "analytics.admin.series.page_views",
76
+ ] as const;
@@ -0,0 +1,31 @@
1
+ // Client-only public barrel for the first-party analytics feature.
2
+ // Consumed via `@carlonicora/nextjs-jsonapi/analytics`. Tsup adds a top-level
3
+ // "use client" directive to the bundled output via clientEntries.
4
+ //
5
+ // Deliberately NOT re-exported from `src/index.ts` — keeping the feature off the
6
+ // main barrel is what keeps its chart (recharts) out of the main client bundle,
7
+ // exactly as the `help` and `tokenusage` features do.
8
+ //
9
+ // The feature is inert until the app mounts `AnalyticsTracker` and registers
10
+ // `analyticsModules` in its bootstrapper.
11
+
12
+ export { AnalyticsTracker } from "./components/AnalyticsTracker";
13
+
14
+ export { AnalyticsAdminProvider, useAnalyticsAdmin } from "./contexts/AnalyticsAdminContext";
15
+ export type { AnalyticsAdminContextType, AnalyticsAdminFilterState } from "./contexts/AnalyticsAdminContext";
16
+
17
+ export { AnalyticsAdminContainer } from "./components/AnalyticsAdminContainer";
18
+ export { AnalyticsAdminFilterBar } from "./components/AnalyticsAdminFilterBar";
19
+ export { AnalyticsAdminTiles } from "./components/AnalyticsAdminTiles";
20
+ export { AnalyticsTimelineChart } from "./components/AnalyticsTimelineChart";
21
+ export { AnalyticsBreakdownTable } from "./components/AnalyticsBreakdownTable";
22
+ export { AnalyticsSessionsTable } from "./components/AnalyticsSessionsTable";
23
+ export { AnalyticsJourneyContainer } from "./components/AnalyticsJourneyContainer";
24
+ export { AnalyticsAdminPageContainer } from "./components/AnalyticsAdminPageContainer";
25
+ export { AnalyticsJourneyPageContainer } from "./components/AnalyticsJourneyPageContainer";
26
+
27
+ export * from "./data";
28
+
29
+ export { analyticsModules } from "./analytics.modules";
30
+
31
+ export { ANALYTICS_ADMIN_I18N_KEYS } from "./i18n-keys";
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { readUtm } from "../utm";
3
+
4
+ describe("readUtm", () => {
5
+ it("reads the utm parameters and ignores everything else", () => {
6
+ expect(readUtm("?utm_source=linkedin&utm_medium=social&utm_campaign=launch&x=1")).toEqual({
7
+ utmSource: "linkedin",
8
+ utmMedium: "social",
9
+ utmCampaign: "launch",
10
+ });
11
+ });
12
+
13
+ it("returns an empty object for an empty query string", () => {
14
+ expect(readUtm("")).toEqual({});
15
+ });
16
+
17
+ it("truncates a value to 255 characters", () => {
18
+ expect(readUtm("?utm_term=" + "a".repeat(300)).utmTerm).toHaveLength(255);
19
+ });
20
+ });
@@ -0,0 +1,63 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ // Records every setCookie call while still writing the real cookie to jsdom,
4
+ // so the options (SameSite, maxAge) and document.cookie can both be asserted.
5
+ const recorded = vi.hoisted(() => ({ calls: [] as unknown[][] }));
6
+
7
+ vi.mock("cookies-next", async () => {
8
+ const actual = await vi.importActual<typeof import("cookies-next")>("cookies-next");
9
+ return {
10
+ ...actual,
11
+ setCookie: (...args: unknown[]) => {
12
+ recorded.calls.push(args);
13
+ return (actual.setCookie as (...a: unknown[]) => unknown)(...args);
14
+ },
15
+ };
16
+ });
17
+
18
+ import { ANALYTICS_VISITOR_COOKIE, clearVisitorCookie, ensureVisitorCookie, readVisitorCookie } from "../visitorCookie";
19
+
20
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
21
+
22
+ describe("visitorCookie", () => {
23
+ afterEach(() => {
24
+ clearVisitorCookie();
25
+ recorded.calls = [];
26
+ });
27
+
28
+ it("names the cookie analytics_visitor", () => {
29
+ expect(ANALYTICS_VISITOR_COOKIE).toBe("analytics_visitor");
30
+ });
31
+
32
+ it("creates a UUID and writes it with SameSite=Lax and a ~395-day expiry", () => {
33
+ const id = ensureVisitorCookie();
34
+
35
+ expect(id).toMatch(UUID);
36
+ expect(document.cookie).toContain(`analytics_visitor=${id}`);
37
+
38
+ expect(recorded.calls).toHaveLength(1);
39
+ const [name, value, options] = recorded.calls[0] as [string, string, Record<string, unknown>];
40
+ expect(name).toBe("analytics_visitor");
41
+ expect(value).toBe(id);
42
+ expect(options.sameSite).toBe("lax");
43
+ expect(options.path).toBe("/");
44
+ expect(options.maxAge).toBe(395 * 24 * 3600);
45
+ });
46
+
47
+ it("returns the same id on a second call without rewriting the cookie", () => {
48
+ const first = ensureVisitorCookie();
49
+ const second = ensureVisitorCookie();
50
+
51
+ expect(second).toBe(first);
52
+ expect(recorded.calls).toHaveLength(1);
53
+ });
54
+
55
+ it("clears the cookie so readVisitorCookie returns undefined", () => {
56
+ ensureVisitorCookie();
57
+ expect(readVisitorCookie()).toBeDefined();
58
+
59
+ clearVisitorCookie();
60
+
61
+ expect(readVisitorCookie()).toBeUndefined();
62
+ });
63
+ });
@@ -0,0 +1,29 @@
1
+ import { AnalyticsEventInput } from "../data/analytics.types";
2
+
3
+ type UtmFields = Pick<AnalyticsEventInput, "utmSource" | "utmMedium" | "utmCampaign" | "utmTerm" | "utmContent">;
4
+
5
+ const MAX_LENGTH = 255;
6
+
7
+ const UTM_PARAMS: [string, keyof UtmFields][] = [
8
+ ["utm_source", "utmSource"],
9
+ ["utm_medium", "utmMedium"],
10
+ ["utm_campaign", "utmCampaign"],
11
+ ["utm_term", "utmTerm"],
12
+ ["utm_content", "utmContent"],
13
+ ];
14
+
15
+ /**
16
+ * Reads the five utm_* parameters from a query string. Absent or empty values
17
+ * are left out; every value is cut to 255 characters.
18
+ */
19
+ export function readUtm(search: string): UtmFields {
20
+ const params = new URLSearchParams(search);
21
+ const response: UtmFields = {};
22
+
23
+ for (const [param, field] of UTM_PARAMS) {
24
+ const value = params.get(param);
25
+ if (value) response[field] = value.slice(0, MAX_LENGTH);
26
+ }
27
+
28
+ return response;
29
+ }
@@ -0,0 +1,32 @@
1
+ import { deleteCookie, getCookie, setCookie } from "cookies-next";
2
+ import { v4 } from "uuid";
3
+
4
+ export const ANALYTICS_VISITOR_COOKIE = "analytics_visitor";
5
+
6
+ /** 395 days: the 13-month retention window. */
7
+ const MAX_AGE_SECONDS = 395 * 24 * 3600;
8
+
9
+ export function readVisitorCookie(): string | undefined {
10
+ const value = getCookie(ANALYTICS_VISITOR_COOKIE);
11
+ return typeof value === "string" && value !== "" ? value : undefined;
12
+ }
13
+
14
+ /** Returns the existing visitor id, or creates and stores a new one. */
15
+ export function ensureVisitorCookie(): string {
16
+ const existing = readVisitorCookie();
17
+ if (existing) return existing;
18
+
19
+ const id = v4();
20
+ setCookie(ANALYTICS_VISITOR_COOKIE, id, {
21
+ maxAge: MAX_AGE_SECONDS,
22
+ sameSite: "lax",
23
+ path: "/",
24
+ secure: typeof location !== "undefined" && location.protocol === "https:",
25
+ });
26
+
27
+ return id;
28
+ }
29
+
30
+ export function clearVisitorCookie(): void {
31
+ deleteCookie(ANALYTICS_VISITOR_COOKIE, { path: "/" });
32
+ }