@carlonicora/nextjs-jsonapi 3.24.2 → 3.26.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 (220) 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-35D4R7HJ.js → BlockNoteEditor-4EIKMNY3.js} +31 -21
  4. package/dist/BlockNoteEditor-4EIKMNY3.js.map +1 -0
  5. package/dist/{BlockNoteEditor-65HR6ULV.mjs → BlockNoteEditor-AXYVDDXM.mjs} +16 -6
  6. package/dist/{BlockNoteEditor-65HR6ULV.mjs.map → BlockNoteEditor-AXYVDDXM.mjs.map} +1 -1
  7. package/dist/{BlockNoteViewer-QDXDVYEM.js → BlockNoteViewer-7BFNTN4E.js} +4 -4
  8. package/dist/{BlockNoteViewer-QDXDVYEM.js.map → BlockNoteViewer-7BFNTN4E.js.map} +1 -1
  9. package/dist/{BlockNoteViewer-KDKGUIG5.mjs → BlockNoteViewer-KRTAFTUX.mjs} +3 -3
  10. package/dist/{HandbookSectionInterface-OePKPbqN.d.ts → HandbookSectionInterface-Ck1wZJ81.d.ts} +1 -1
  11. package/dist/{HandbookSectionInterface-BYsmnPEz.d.mts → HandbookSectionInterface-DgYH--Ik.d.mts} +1 -1
  12. package/dist/JsonApiRequest-TE5WD3WS.js +26 -0
  13. package/dist/{JsonApiRequest-G625QAH5.js.map → JsonApiRequest-TE5WD3WS.js.map} +1 -1
  14. package/dist/{JsonApiRequest-RRMNV7YE.mjs → JsonApiRequest-UHOWNI3O.mjs} +2 -2
  15. package/dist/analytics.modules-CfczU12P.d.ts +25 -0
  16. package/dist/analytics.modules-pzGwMASI.d.mts +25 -0
  17. package/dist/{auth.interface-BC1jp_ni.d.ts → auth.interface-BzI4onI_.d.ts} +1 -1
  18. package/dist/{auth.interface-dR2-6gPM.d.mts → auth.interface-Df4vXbNz.d.mts} +1 -1
  19. package/dist/billing/index.js +402 -402
  20. package/dist/billing/index.mjs +4 -4
  21. package/dist/{chunk-YXRVPEEJ.js → chunk-26MQIJNB.js} +1 -1
  22. package/dist/{chunk-YXRVPEEJ.js.map → chunk-26MQIJNB.js.map} +1 -1
  23. package/dist/{chunk-T76WLDE5.mjs → chunk-37DMFVLN.mjs} +1 -1
  24. package/dist/{chunk-3AR4KA5D.js → chunk-4Z45NYPR.js} +13 -13
  25. package/dist/{chunk-3AR4KA5D.js.map → chunk-4Z45NYPR.js.map} +1 -1
  26. package/dist/chunk-7B4E42R5.mjs +280 -0
  27. package/dist/chunk-7B4E42R5.mjs.map +1 -0
  28. package/dist/{chunk-2XCUAHQT.js → chunk-ABVTYQIJ.js} +1563 -1336
  29. package/dist/chunk-ABVTYQIJ.js.map +1 -0
  30. package/dist/{chunk-U7QVISQS.js → chunk-CAKOFJZD.js} +450 -40
  31. package/dist/chunk-CAKOFJZD.js.map +1 -0
  32. package/dist/{chunk-IBVKPLHT.mjs → chunk-E3I4MT53.mjs} +422 -12
  33. package/dist/chunk-E3I4MT53.mjs.map +1 -0
  34. package/dist/chunk-GHFMKH3M.js +280 -0
  35. package/dist/chunk-GHFMKH3M.js.map +1 -0
  36. package/dist/chunk-GKDHYB2R.js +110 -0
  37. package/dist/chunk-GKDHYB2R.js.map +1 -0
  38. package/dist/{chunk-ZJI5ZZW5.mjs → chunk-Q6J2ICKO.mjs} +3 -3
  39. package/dist/{chunk-4O7R2Z2O.mjs → chunk-QWZAGQWJ.mjs} +2175 -1948
  40. package/dist/chunk-QWZAGQWJ.mjs.map +1 -0
  41. package/dist/chunk-U65JPKO3.mjs +110 -0
  42. package/dist/chunk-U65JPKO3.mjs.map +1 -0
  43. package/dist/client/index.d.mts +6 -5
  44. package/dist/client/index.d.ts +6 -5
  45. package/dist/client/index.js +5 -5
  46. package/dist/client/index.js.map +1 -1
  47. package/dist/client/index.mjs +4 -4
  48. package/dist/components/index.d.mts +33 -10
  49. package/dist/components/index.d.ts +33 -10
  50. package/dist/components/index.js +7 -5
  51. package/dist/components/index.js.map +1 -1
  52. package/dist/components/index.mjs +6 -4
  53. package/dist/{config-B6UB7j69.d.ts → config-3N1LSrcC.d.ts} +1 -1
  54. package/dist/{config-Cd5xTEfZ.d.ts → config-AVtuvfM1.d.ts} +1 -1
  55. package/dist/{config-n-ZpPmOL.d.mts → config-BSKTYDoh.d.mts} +1 -1
  56. package/dist/{config-YyhFHxkU.d.mts → config-BmufyEj6.d.mts} +1 -1
  57. package/dist/contexts/index.d.mts +5 -4
  58. package/dist/contexts/index.d.ts +5 -4
  59. package/dist/contexts/index.js +5 -5
  60. package/dist/contexts/index.mjs +4 -4
  61. package/dist/core/index.d.mts +31 -14
  62. package/dist/core/index.d.ts +31 -14
  63. package/dist/core/index.js +9 -5
  64. package/dist/core/index.js.map +1 -1
  65. package/dist/core/index.mjs +8 -4
  66. package/dist/features/analytics/index.css +30 -0
  67. package/dist/features/analytics/index.css.map +1 -0
  68. package/dist/features/analytics/index.d.mts +508 -0
  69. package/dist/features/analytics/index.d.ts +508 -0
  70. package/dist/features/analytics/index.js +1166 -0
  71. package/dist/features/analytics/index.js.map +1 -0
  72. package/dist/features/analytics/index.mjs +1166 -0
  73. package/dist/features/analytics/index.mjs.map +1 -0
  74. package/dist/features/analytics/server.d.mts +28 -0
  75. package/dist/features/analytics/server.d.ts +28 -0
  76. package/dist/features/analytics/server.js +41 -0
  77. package/dist/features/analytics/server.js.map +1 -0
  78. package/dist/features/analytics/server.mjs +41 -0
  79. package/dist/features/analytics/server.mjs.map +1 -0
  80. package/dist/features/help/index.d.mts +2 -2
  81. package/dist/features/help/index.d.ts +2 -2
  82. package/dist/features/help/index.js +43 -43
  83. package/dist/features/help/index.js.map +1 -1
  84. package/dist/features/help/index.mjs +9 -9
  85. package/dist/features/help/index.mjs.map +1 -1
  86. package/dist/features/tokenusage/index.d.mts +2 -2
  87. package/dist/features/tokenusage/index.d.ts +2 -2
  88. package/dist/features/tokenusage/index.js +141 -223
  89. package/dist/features/tokenusage/index.js.map +1 -1
  90. package/dist/features/tokenusage/index.mjs +28 -110
  91. package/dist/features/tokenusage/index.mjs.map +1 -1
  92. package/dist/index.d.mts +11 -9
  93. package/dist/index.d.ts +11 -9
  94. package/dist/index.js +10 -6
  95. package/dist/index.js.map +1 -1
  96. package/dist/index.mjs +9 -5
  97. package/dist/notification.interface-BNCvnVCW.d.ts +34 -0
  98. package/dist/notification.interface-P_pNfzZQ.d.mts +34 -0
  99. package/dist/{s3.service-Bqr-fQmi.d.ts → s3.service-CbUa6a-8.d.ts} +3 -2
  100. package/dist/{s3.service-Buiy1ncj.d.mts → s3.service-DAp8FbuG.d.mts} +3 -2
  101. package/dist/server/index.d.mts +5 -4
  102. package/dist/server/index.d.ts +5 -4
  103. package/dist/server/index.js +39 -279
  104. package/dist/server/index.js.map +1 -1
  105. package/dist/server/index.mjs +20 -260
  106. package/dist/server/index.mjs.map +1 -1
  107. package/dist/{types-bsm8Fp55.d.ts → types-Cn153-mH.d.ts} +1 -1
  108. package/dist/{types-Bq_UA8Lg.d.mts → types-D0uLYV2P.d.mts} +1 -1
  109. package/dist/{usePageUrlGenerator-J_ECv-oQ.d.ts → usePageUrlGenerator-B-3FRKBD.d.ts} +1 -1
  110. package/dist/{usePageUrlGenerator-uKBjAKqC.d.mts → usePageUrlGenerator-CCJ_cdQy.d.mts} +1 -1
  111. package/dist/{useSocket-BcH7l7B8.d.mts → useSocket-B9gTbuCj.d.mts} +1 -1
  112. package/dist/{useSocket-DCZd_eJk.d.ts → useSocket-BHVIyigI.d.ts} +1 -1
  113. package/dist/{notification.interface-MnRt1Tjm.d.mts → user.interface-CSaGWFbg.d.mts} +1 -31
  114. package/dist/{notification.interface-CjzV_KII.d.ts → user.interface-CrmwJLYW.d.ts} +1 -31
  115. package/package.json +11 -1
  116. package/src/client/index.ts +2 -0
  117. package/src/components/containers/PageContainer.tsx +1 -1
  118. package/src/components/containers/RoundPageContainer.tsx +60 -16
  119. package/src/components/containers/RoundPageContainerTitle.tsx +75 -35
  120. package/src/components/editors/BlockNoteEditor.tsx +13 -1
  121. package/src/components/forms/EntityMultiSelector.tsx +4 -4
  122. package/src/components/forms/EntitySelector.tsx +4 -4
  123. package/src/components/forms/FormBlockNote.tsx +21 -1
  124. package/src/components/forms/FormCheckbox.tsx +6 -2
  125. package/src/components/forms/__tests__/useEditorDialog.test.ts +47 -0
  126. package/src/components/forms/useEditorDialog.ts +22 -0
  127. package/src/components/navigations/Breadcrumb.tsx +45 -16
  128. package/src/components/navigations/Header.tsx +16 -3
  129. package/src/components/navigations/MobileNavigationBar.tsx +12 -0
  130. package/src/components/navigations/__tests__/MobileNavigationBar.spec.tsx +17 -0
  131. package/src/components/tables/ContentListTable.tsx +52 -32
  132. package/src/core/index.ts +4 -0
  133. package/src/core/registry/ModuleRegistry.ts +7 -0
  134. package/src/features/__tests__/i18n-keys.contract.spec.ts +1 -0
  135. package/src/features/analytics/__tests__/server-entry.spec.tsx +64 -0
  136. package/src/features/analytics/analytics.modules.ts +30 -0
  137. package/src/features/analytics/components/AnalyticsAdminContainer.tsx +97 -0
  138. package/src/features/analytics/components/AnalyticsAdminFilterBar.tsx +120 -0
  139. package/src/features/analytics/components/AnalyticsAdminPageContainer.tsx +25 -0
  140. package/src/features/analytics/components/AnalyticsAdminTiles.tsx +191 -0
  141. package/src/features/analytics/components/AnalyticsBreakdownTable.tsx +105 -0
  142. package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +246 -0
  143. package/src/features/analytics/components/AnalyticsJourneyPageContainer.tsx +14 -0
  144. package/src/features/analytics/components/AnalyticsSessionsTable.tsx +107 -0
  145. package/src/features/analytics/components/AnalyticsTimelineChart.tsx +175 -0
  146. package/src/features/analytics/components/AnalyticsTracker.tsx +112 -0
  147. package/src/features/analytics/components/__tests__/AnalyticsAdminTiles.spec.tsx +39 -0
  148. package/src/features/analytics/components/__tests__/AnalyticsBreakdownTable.spec.tsx +39 -0
  149. package/src/features/analytics/components/__tests__/AnalyticsJourneyContainer.spec.tsx +124 -0
  150. package/src/features/analytics/components/__tests__/AnalyticsPageContainers.spec.tsx +117 -0
  151. package/src/features/analytics/components/__tests__/AnalyticsSessionsTable.spec.tsx +121 -0
  152. package/src/features/analytics/components/__tests__/AnalyticsTimelineChart.spec.tsx +35 -0
  153. package/src/features/analytics/components/__tests__/AnalyticsTracker.spec.tsx +248 -0
  154. package/src/features/analytics/components/server/AnalyticsAdminPage.tsx +28 -0
  155. package/src/features/analytics/components/server/AnalyticsJourneyPage.tsx +25 -0
  156. package/src/features/analytics/contexts/AnalyticsAdminContext.tsx +273 -0
  157. package/src/features/analytics/contexts/__tests__/AnalyticsAdminContext.spec.tsx +140 -0
  158. package/src/features/analytics/data/AnalyticsAdminService.ts +91 -0
  159. package/src/features/analytics/data/AnalyticsService.ts +23 -0
  160. package/src/features/analytics/data/__tests__/AnalyticsAdminService.spec.ts +117 -0
  161. package/src/features/analytics/data/__tests__/AnalyticsService.spec.ts +56 -0
  162. package/src/features/analytics/data/__tests__/models.spec.ts +300 -0
  163. package/src/features/analytics/data/analytics-breakdown.interface.ts +9 -0
  164. package/src/features/analytics/data/analytics-breakdown.ts +65 -0
  165. package/src/features/analytics/data/analytics-event.interface.ts +7 -0
  166. package/src/features/analytics/data/analytics-event.ts +63 -0
  167. package/src/features/analytics/data/analytics-page-view.interface.ts +9 -0
  168. package/src/features/analytics/data/analytics-page-view.ts +57 -0
  169. package/src/features/analytics/data/analytics-session.interface.ts +23 -0
  170. package/src/features/analytics/data/analytics-session.ts +144 -0
  171. package/src/features/analytics/data/analytics-summary.interface.ts +11 -0
  172. package/src/features/analytics/data/analytics-summary.ts +79 -0
  173. package/src/features/analytics/data/analytics-timeline.interface.ts +10 -0
  174. package/src/features/analytics/data/analytics-timeline.ts +67 -0
  175. package/src/features/analytics/data/analytics.types.ts +97 -0
  176. package/src/features/analytics/data/index.ts +15 -0
  177. package/src/features/analytics/i18n-keys.ts +76 -0
  178. package/src/features/analytics/index.ts +31 -0
  179. package/src/features/analytics/lib/__tests__/utm.spec.ts +20 -0
  180. package/src/features/analytics/lib/__tests__/visitorCookie.spec.ts +63 -0
  181. package/src/features/analytics/lib/utm.ts +29 -0
  182. package/src/features/analytics/lib/visitorCookie.ts +32 -0
  183. package/src/features/analytics/server-entry.ts +8 -0
  184. package/src/features/assistant/components/parts/AssistantBlockNoteComposer.tsx +1 -1
  185. package/src/features/assistant/components/parts/AssistantComposer.tsx +1 -1
  186. package/src/features/assistant-message/components/MessageItem.tsx +2 -2
  187. package/src/features/assistant-message/components/parts/tabs/CitationsTab.tsx +23 -5
  188. package/src/features/auth/components/containers/AuthContainer.tsx +8 -1
  189. package/src/features/company/components/details/CompanyContent.tsx +3 -1
  190. package/src/features/company/components/details/CompanyDetails.tsx +9 -1
  191. package/src/features/company/components/details/TokenStatusIndicator.tsx +42 -13
  192. package/src/features/help/components/HelpHeader.tsx +7 -5
  193. package/src/features/tokenusage/components/TokenUsageAdminFilterBar.tsx +9 -6
  194. package/src/features/tokenusage/lib/__tests__/formatters.spec.ts +21 -0
  195. package/src/features/tokenusage/lib/formatters.ts +5 -1
  196. package/src/features/waitlist/components/lists/WaitlistList.tsx +68 -121
  197. package/src/features/waitlist/data/WaitlistService.ts +2 -2
  198. package/src/features/waitlist/data/index.ts +1 -0
  199. package/src/features/waitlist/data/waitlist.fields.ts +8 -0
  200. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +69 -30
  201. package/src/shadcnui/ui/button.tsx +1 -1
  202. package/src/shadcnui/ui/checkbox.tsx +1 -1
  203. package/src/shadcnui/ui/command.tsx +4 -4
  204. package/src/shadcnui/ui/dialog.tsx +2 -0
  205. package/src/shadcnui/ui/input.tsx +1 -1
  206. package/src/shadcnui/ui/popover.tsx +9 -0
  207. package/src/shadcnui/ui/select.tsx +1 -1
  208. package/src/shadcnui/ui/sheet.tsx +8 -1
  209. package/src/shadcnui/ui/table.tsx +2 -2
  210. package/src/shadcnui/ui/textarea.tsx +1 -1
  211. package/dist/BlockNoteEditor-35D4R7HJ.js.map +0 -1
  212. package/dist/JsonApiRequest-G625QAH5.js +0 -26
  213. package/dist/chunk-2XCUAHQT.js.map +0 -1
  214. package/dist/chunk-4O7R2Z2O.mjs.map +0 -1
  215. package/dist/chunk-IBVKPLHT.mjs.map +0 -1
  216. package/dist/chunk-U7QVISQS.js.map +0 -1
  217. /package/dist/{BlockNoteViewer-KDKGUIG5.mjs.map → BlockNoteViewer-KRTAFTUX.mjs.map} +0 -0
  218. /package/dist/{JsonApiRequest-RRMNV7YE.mjs.map → JsonApiRequest-UHOWNI3O.mjs.map} +0 -0
  219. /package/dist/{chunk-T76WLDE5.mjs.map → chunk-37DMFVLN.mjs.map} +0 -0
  220. /package/dist/{chunk-ZJI5ZZW5.mjs.map → chunk-Q6J2ICKO.mjs.map} +0 -0
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ // Server-safe public barrel for the analytics feature.
2
+ // Consumed via `@carlonicora/nextjs-jsonapi/analytics/server`. Must NOT receive a
3
+ // "use client" directive — these are async server components that read the
4
+ // session cookie before rendering the client containers from
5
+ // `@carlonicora/nextjs-jsonapi/analytics`.
6
+
7
+ export { AnalyticsAdminPage } from "./components/server/AnalyticsAdminPage";
8
+ export { AnalyticsJourneyPage } from "./components/server/AnalyticsJourneyPage";
@@ -129,7 +129,7 @@ export function AssistantBlockNoteComposer({
129
129
  <ArrowUp className="me-1 h-4 w-4" /> {t("features.assistant.send")}
130
130
  </Button>
131
131
  </div>
132
- <div className="text-muted-foreground text-end text-xs">{t("features.assistant.keyboard_hint")}</div>
132
+ <div className="text-muted-foreground text-end text-xs max-md:hidden">{t("features.assistant.keyboard_hint")}</div>
133
133
  </div>
134
134
  );
135
135
  }
@@ -92,7 +92,7 @@ export function AssistantComposer({
92
92
  {sendButton}
93
93
  </div>
94
94
  )}
95
- <div className="text-muted-foreground text-end text-xs">{t("features.assistant.keyboard_hint")}</div>
95
+ <div className="text-muted-foreground text-end text-xs max-md:hidden">{t("features.assistant.keyboard_hint")}</div>
96
96
  </div>
97
97
  );
98
98
  }
@@ -82,7 +82,7 @@ export function MessageItem({
82
82
  renders invisible here, so force the on-primary colour and lean on
83
83
  weight for the affordance (links are never underlined in this UI).
84
84
  */}
85
- <div className="bg-primary text-primary-foreground max-w-[72%] rounded-2xl rounded-ee-sm px-3.5 py-2 text-sm [&_a]:text-primary-foreground [&_a]:font-semibold [&_p]:m-0">
85
+ <div className="bg-primary text-primary-foreground max-w-[72%] max-md:max-w-[92%] rounded-2xl rounded-ee-sm px-3.5 py-2 text-sm [&_a]:text-primary-foreground [&_a]:font-semibold [&_p]:m-0">
86
86
  <ReactMarkdown remarkPlugins={[remarkGfm]} urlTransform={mentionUrlTransform} components={markdownComponents}>
87
87
  {message.content}
88
88
  </ReactMarkdown>
@@ -103,7 +103,7 @@ export function MessageItem({
103
103
  const isApprovalRequest = message.messageType === "approval-request" && !!renderApprovalAction;
104
104
 
105
105
  return (
106
- <div className="flex min-w-0 max-w-[78%] flex-col gap-1.5">
106
+ <div className="flex min-w-0 max-w-[78%] max-md:max-w-[92%] flex-col gap-1.5">
107
107
  <div className="text-muted-foreground flex items-center gap-2 ps-1 text-xs">
108
108
  <span className="flex h-4 w-4 items-center justify-center rounded-full bg-gradient-to-br from-blue-400 to-violet-500 text-white">
109
109
  <Sparkles className="h-2.5 w-2.5" />
@@ -9,6 +9,8 @@ import type { ApiDataInterface } from "../../../../../core";
9
9
  import type { ChunkInterface, ChunkRelationshipMeta } from "../../../../chunk/data/ChunkInterface";
10
10
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../../../shadcnui/ui/table";
11
11
  import { Tooltip, TooltipContent, TooltipTrigger } from "../../../../../shadcnui/ui/tooltip";
12
+ import { Popover, PopoverContent, PopoverTrigger } from "../../../../../shadcnui/ui/popover";
13
+ import { useIsMobile } from "../../../../../utils/use-mobile";
12
14
  import { cn } from "@/lib/utils";
13
15
  import { RelevanceMeter } from "../RelevanceMeter";
14
16
  import { useEntityLabel } from "./useEntityLabel";
@@ -26,6 +28,8 @@ interface Props {
26
28
  export function CitationsTab({ citations, sources }: Props) {
27
29
  const t = useTranslations();
28
30
  const entityLabel = useEntityLabel();
31
+ // A tooltip never opens on touch, so on a phone the reason is a tap popover.
32
+ const isMobile = useIsMobile();
29
33
  const [expanded, setExpanded] = useState<Set<string>>(new Set());
30
34
  if (citations.length === 0) return null;
31
35
 
@@ -45,7 +49,7 @@ export function CitationsTab({ citations, sources }: Props) {
45
49
  <TableHeader>
46
50
  <TableRow>
47
51
  <TableHead>{t("features.assistant.message.sources.source")}</TableHead>
48
- <TableHead className="w-28 text-center">{t("features.assistant.message.sources.relevance")}</TableHead>
52
+ <TableHead className="w-28 text-center max-md:w-20">{t("features.assistant.message.sources.relevance")}</TableHead>
49
53
  </TableRow>
50
54
  </TableHeader>
51
55
  <TableBody>
@@ -76,9 +80,23 @@ export function CitationsTab({ citations, sources }: Props) {
76
80
  aria-expanded={isOpen}
77
81
  className="flex w-full cursor-pointer items-center justify-start gap-x-2"
78
82
  >
79
- <ChevronDown className={cn("h-4 w-4 transition-transform", !isOpen && "-rotate-90")} />
80
- <span className="font-semibold">{sourceName}</span>
81
- {chunk.reason && (
83
+ <ChevronDown
84
+ className={cn("h-4 w-4 transition-transform max-md:shrink-0", !isOpen && "-rotate-90")}
85
+ />
86
+ <span className="font-semibold max-md:min-w-0 max-md:truncate">{sourceName}</span>
87
+ {chunk.reason && isMobile && (
88
+ <Popover>
89
+ <PopoverTrigger
90
+ aria-label={chunk.reason}
91
+ onClick={(e) => e.stopPropagation()}
92
+ className="text-muted-foreground -m-2 inline-flex shrink-0 p-2"
93
+ >
94
+ <HelpCircle className="h-3.5 w-3.5" />
95
+ </PopoverTrigger>
96
+ <PopoverContent className="max-w-64 text-xs">{chunk.reason}</PopoverContent>
97
+ </Popover>
98
+ )}
99
+ {chunk.reason && !isMobile && (
82
100
  <Tooltip>
83
101
  <TooltipTrigger className="text-muted-foreground inline-flex">
84
102
  <HelpCircle className="h-3.5 w-3.5" />
@@ -89,7 +107,7 @@ export function CitationsTab({ citations, sources }: Props) {
89
107
  </div>
90
108
  </TableCell>
91
109
  <TableCell className="text-center">
92
- <RelevanceMeter value={chunk.relevance ?? 0} />
110
+ <RelevanceMeter value={chunk.relevance ?? 0} className="max-md:w-16" />
93
111
  </TableCell>
94
112
  </TableRow>
95
113
  {isOpen && (
@@ -28,5 +28,12 @@ function InnerAuthContainer() {
28
28
  </div>
29
29
  );
30
30
 
31
- return <Card className="w-full max-w-md">{activeComponent}</Card>;
31
+ // Phone: 40px buttons and inputs on the auth forms (login, register, reset…)
32
+ // so they are comfortable to tap. Input-group buttons (show password) carry
33
+ // data-size and keep their own size. md+ is unchanged.
34
+ return (
35
+ <Card className="w-full max-w-md max-md:[&_[data-slot=button]:not([data-size])]:h-10 max-md:[&_[data-slot=input-group-control]]:h-full max-md:[&_[data-slot=input-group]]:h-10 max-md:[&_[data-slot=input]]:h-10">
36
+ {activeComponent}
37
+ </Card>
38
+ );
32
39
  }