@carlonicora/nextjs-jsonapi 5.4.0 → 5.5.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 (148) hide show
  1. package/dist/{BlockNoteEditor-YQL2SGFR.mjs → BlockNoteEditor-GVQCKRMU.mjs} +5 -5
  2. package/dist/{BlockNoteEditor-4YCAIEPT.js → BlockNoteEditor-THBJH2UR.js} +20 -20
  3. package/dist/{BlockNoteEditor-4YCAIEPT.js.map → BlockNoteEditor-THBJH2UR.js.map} +1 -1
  4. package/dist/{BlockNoteViewer-AEJKJ27Q.mjs → BlockNoteViewer-U6E3DS4F.mjs} +3 -3
  5. package/dist/{BlockNoteViewer-YL3J7J2Y.js → BlockNoteViewer-VZOVKTFO.js} +4 -4
  6. package/dist/{BlockNoteViewer-YL3J7J2Y.js.map → BlockNoteViewer-VZOVKTFO.js.map} +1 -1
  7. package/dist/billing/index.js +442 -442
  8. package/dist/billing/index.mjs +4 -4
  9. package/dist/{chunk-XCHSX3VC.mjs → chunk-3CGROPF3.mjs} +2 -2
  10. package/dist/{chunk-QMYQRPQC.mjs → chunk-6SQYXUVP.mjs} +2 -2
  11. package/dist/{chunk-NFBOSPNJ.js → chunk-G6UE6MEP.js} +59 -19
  12. package/dist/chunk-G6UE6MEP.js.map +1 -0
  13. package/dist/{chunk-YNCQSWQW.js → chunk-S4YJ5WCW.js} +8 -8
  14. package/dist/{chunk-YNCQSWQW.js.map → chunk-S4YJ5WCW.js.map} +1 -1
  15. package/dist/{chunk-3NLAXVIN.js → chunk-TY5FYP3G.js} +917 -881
  16. package/dist/chunk-TY5FYP3G.js.map +1 -0
  17. package/dist/{chunk-WC63PJSC.mjs → chunk-VC5A3AUZ.mjs} +61 -21
  18. package/dist/chunk-VC5A3AUZ.mjs.map +1 -0
  19. package/dist/{chunk-6DC2WVVB.mjs → chunk-W7F5BDSV.mjs} +2 -2
  20. package/dist/{chunk-5PPG6PL7.js → chunk-WPYT4UKJ.js} +3 -3
  21. package/dist/{chunk-5PPG6PL7.js.map → chunk-WPYT4UKJ.js.map} +1 -1
  22. package/dist/{chunk-EUKTBOI2.mjs → chunk-X5WIUUNG.mjs} +1471 -1435
  23. package/dist/chunk-X5WIUUNG.mjs.map +1 -0
  24. package/dist/{chunk-YW4ZCEWB.js → chunk-YDYVJM4I.js} +4 -4
  25. package/dist/{chunk-YW4ZCEWB.js.map → chunk-YDYVJM4I.js.map} +1 -1
  26. package/dist/client/index.js +5 -5
  27. package/dist/client/index.js.map +1 -1
  28. package/dist/client/index.mjs +4 -4
  29. package/dist/components/editors/server.d.mts +15 -0
  30. package/dist/components/editors/server.d.ts +15 -0
  31. package/dist/components/editors/server.js +38 -0
  32. package/dist/components/editors/server.js.map +1 -0
  33. package/dist/components/editors/server.mjs +38 -0
  34. package/dist/components/editors/server.mjs.map +1 -0
  35. package/dist/components/index.d.mts +33 -10
  36. package/dist/components/index.d.ts +33 -10
  37. package/dist/components/index.js +7 -5
  38. package/dist/components/index.js.map +1 -1
  39. package/dist/components/index.mjs +8 -6
  40. package/dist/contexts/index.d.mts +9 -1
  41. package/dist/contexts/index.d.ts +9 -1
  42. package/dist/contexts/index.js +5 -5
  43. package/dist/contexts/index.mjs +4 -4
  44. package/dist/core/index.js +4 -4
  45. package/dist/core/index.js.map +1 -1
  46. package/dist/core/index.mjs +3 -3
  47. package/dist/email-automations/index.d.mts +3 -0
  48. package/dist/email-automations/index.d.ts +3 -0
  49. package/dist/email-automations/index.js +99 -95
  50. package/dist/email-automations/index.js.map +1 -1
  51. package/dist/email-automations/index.mjs +30 -26
  52. package/dist/email-automations/index.mjs.map +1 -1
  53. package/dist/features/analytics/index.d.mts +11 -4
  54. package/dist/features/analytics/index.d.ts +11 -4
  55. package/dist/features/analytics/index.js +127 -135
  56. package/dist/features/analytics/index.js.map +1 -1
  57. package/dist/features/analytics/index.mjs +16 -24
  58. package/dist/features/analytics/index.mjs.map +1 -1
  59. package/dist/features/analytics/server.d.mts +5 -2
  60. package/dist/features/analytics/server.d.ts +5 -2
  61. package/dist/features/analytics/server.js +7 -7
  62. package/dist/features/analytics/server.js.map +1 -1
  63. package/dist/features/analytics/server.mjs +5 -5
  64. package/dist/features/analytics/server.mjs.map +1 -1
  65. package/dist/features/blog/index.d.mts +12 -2
  66. package/dist/features/blog/index.d.ts +12 -2
  67. package/dist/features/blog/index.js +206 -195
  68. package/dist/features/blog/index.js.map +1 -1
  69. package/dist/features/blog/index.mjs +71 -60
  70. package/dist/features/blog/index.mjs.map +1 -1
  71. package/dist/features/blog/server.d.mts +4 -1
  72. package/dist/features/blog/server.d.ts +4 -1
  73. package/dist/features/blog/server.js +49 -21
  74. package/dist/features/blog/server.js.map +1 -1
  75. package/dist/features/blog/server.mjs +36 -8
  76. package/dist/features/blog/server.mjs.map +1 -1
  77. package/dist/features/help/index.js +38 -38
  78. package/dist/features/help/index.mjs +4 -4
  79. package/dist/features/tokenusage/index.d.mts +1 -3
  80. package/dist/features/tokenusage/index.d.ts +1 -3
  81. package/dist/features/tokenusage/index.js +127 -135
  82. package/dist/features/tokenusage/index.js.map +1 -1
  83. package/dist/features/tokenusage/index.mjs +10 -18
  84. package/dist/features/tokenusage/index.mjs.map +1 -1
  85. package/dist/index.js +5 -5
  86. package/dist/index.js.map +1 -1
  87. package/dist/index.mjs +4 -4
  88. package/dist/server/index.js +4 -4
  89. package/dist/server/index.mjs +3 -3
  90. package/package.json +9 -1
  91. package/src/components/editors/server/BlockNoteServerContent.tsx +28 -0
  92. package/src/components/editors/server/__tests__/renderBlockNoteHTML.spec.ts +44 -0
  93. package/src/components/editors/server/renderBlockNoteHTML.ts +26 -0
  94. package/src/components/editors/server-entry.ts +7 -0
  95. package/src/components/index.ts +1 -0
  96. package/src/components/viewers/BlockNoteStaticViewer.tsx +44 -0
  97. package/src/features/administration/components/AdminIndexContainer.tsx +61 -33
  98. package/src/features/administration/components/__tests__/AdminIndexContainer.spec.tsx +61 -1
  99. package/src/features/administration/components/index.ts +2 -1
  100. package/src/features/administration/contexts/AdministrationContext.tsx +11 -3
  101. package/src/features/administration/contexts/__tests__/AdministrationContext.spec.tsx +40 -0
  102. package/src/features/ai-connection/ai-connection.module.ts +2 -0
  103. package/src/features/ai-connection/contexts/AiConnectionsContext.tsx +3 -16
  104. package/src/features/ai-connection/contexts/__tests__/AiConnectionsContext.spec.tsx +33 -0
  105. package/src/features/analytics/__tests__/server-entry.spec.tsx +2 -1
  106. package/src/features/analytics/analytics.modules.ts +22 -5
  107. package/src/features/analytics/components/AnalyticsAdminContainer.tsx +4 -3
  108. package/src/features/analytics/components/AnalyticsAdminPageContainer.tsx +6 -3
  109. package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +4 -3
  110. package/src/features/analytics/components/__tests__/AnalyticsJourneyContainer.spec.tsx +11 -0
  111. package/src/features/analytics/components/__tests__/AnalyticsPageContainers.spec.tsx +12 -1
  112. package/src/features/analytics/components/server/AnalyticsAdminPage.tsx +6 -3
  113. package/src/features/analytics/contexts/AnalyticsAdminContext.tsx +8 -17
  114. package/src/features/analytics/contexts/__tests__/AnalyticsAdminContext.spec.tsx +17 -0
  115. package/src/features/billing/stripe-price/stripe-price.module.ts +2 -0
  116. package/src/features/billing/stripe-product/stripe-product.module.ts +2 -0
  117. package/src/features/blog/components/admin/BlogAdminContainer.tsx +13 -6
  118. package/src/features/blog/components/admin/BlogCategoriesList.tsx +1 -1
  119. package/src/features/blog/components/admin/BlogCoverImageField.tsx +1 -1
  120. package/src/features/blog/components/admin/BlogPostContainer.tsx +0 -1
  121. package/src/features/blog/components/admin/BlogPostEditor.tsx +42 -33
  122. package/src/features/blog/components/admin/BlogPostTranslationEditor.tsx +12 -4
  123. package/src/features/blog/components/admin/BlogPostsList.tsx +1 -1
  124. package/src/features/blog/components/public/BlogPostCard.tsx +2 -9
  125. package/src/features/blog/components/public/BlogPostMeta.tsx +2 -2
  126. package/src/features/blog/components/public/BlogPostView.tsx +9 -3
  127. package/src/features/blog/components/public/__tests__/BlogList.spec.tsx +2 -0
  128. package/src/features/blog/components/public/__tests__/BlogPostView.spec.tsx +17 -11
  129. package/src/features/blog/server/fetchers.ts +57 -8
  130. package/src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx +12 -3
  131. package/src/features/rbac/contexts/RbacContext.tsx +4 -7
  132. package/src/features/rbac/contexts/__tests__/RbacContext.spec.tsx +42 -0
  133. package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +4 -3
  134. package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +3 -18
  135. package/src/features/tokenusage/contexts/__tests__/TokenUsageAdminContext.spec.tsx +17 -0
  136. package/src/features/tokenusage/tokenusage-admin.module.ts +4 -3
  137. package/src/features/tokenusage/tokenusage.modules.ts +12 -2
  138. package/src/features/user/contexts/UserContext.tsx +18 -3
  139. package/src/features/user/contexts/__tests__/UserContext.spec.tsx +95 -0
  140. package/dist/chunk-3NLAXVIN.js.map +0 -1
  141. package/dist/chunk-EUKTBOI2.mjs.map +0 -1
  142. package/dist/chunk-NFBOSPNJ.js.map +0 -1
  143. package/dist/chunk-WC63PJSC.mjs.map +0 -1
  144. /package/dist/{BlockNoteEditor-YQL2SGFR.mjs.map → BlockNoteEditor-GVQCKRMU.mjs.map} +0 -0
  145. /package/dist/{BlockNoteViewer-AEJKJ27Q.mjs.map → BlockNoteViewer-U6E3DS4F.mjs.map} +0 -0
  146. /package/dist/{chunk-XCHSX3VC.mjs.map → chunk-3CGROPF3.mjs.map} +0 -0
  147. /package/dist/{chunk-QMYQRPQC.mjs.map → chunk-6SQYXUVP.mjs.map} +0 -0
  148. /package/dist/{chunk-6DC2WVVB.mjs.map → chunk-W7F5BDSV.mjs.map} +0 -0
@@ -1,3 +1,4 @@
1
+ import { CoinsIcon } from "lucide-react";
1
2
  import { ModuleFactory, ModuleWithPermissions } from "../../permissions";
2
3
  import { TokenUsageAdminBreakdown } from "./data/tokenusage-admin-breakdown";
3
4
  import { TokenUsageAdminSummary } from "./data/tokenusage-admin-summary";
@@ -28,11 +29,20 @@ import { TokenUsageReportTimeline } from "./data/tokenusage-report-timeline";
28
29
  */
29
30
  export const tokenUsageModules = (factory: ModuleFactory) =>
30
31
  ({
31
- TokenUsageAdminSummary: factory({ name: "tokenusages/administration/summary", model: TokenUsageAdminSummary }),
32
- TokenUsageAdminTimeline: factory({ name: "tokenusages/administration/timeline", model: TokenUsageAdminTimeline }),
32
+ TokenUsageAdminSummary: factory({
33
+ name: "tokenusages/administration/summary",
34
+ model: TokenUsageAdminSummary,
35
+ icon: CoinsIcon,
36
+ }),
37
+ TokenUsageAdminTimeline: factory({
38
+ name: "tokenusages/administration/timeline",
39
+ model: TokenUsageAdminTimeline,
40
+ icon: CoinsIcon,
41
+ }),
33
42
  TokenUsageAdminBreakdown: factory({
34
43
  name: "tokenusages/administration/breakdown",
35
44
  model: TokenUsageAdminBreakdown,
45
+ icon: CoinsIcon,
36
46
  }),
37
47
  TokenUsageReportSummary: factory({ name: "tokenusages/reports/summary", model: TokenUsageReportSummary }),
38
48
  TokenUsageReportTimeline: factory({ name: "tokenusages/reports/timeline", model: TokenUsageReportTimeline }),
@@ -7,6 +7,7 @@ import { JsonApiHydratedDataInterface, Modules, rehydrate } from "../../../core"
7
7
  import { usePageUrlGenerator } from "../../../hooks";
8
8
  import { BreadcrumbItemData } from "../../../interfaces";
9
9
  import { Action } from "../../../permissions";
10
+ import { getRoleId } from "../../../roles";
10
11
  import { UserDeleter, UserEditor, UserReactivator, UserResentInvitationEmail } from "../components/forms";
11
12
  import { UserInterface } from "../data";
12
13
  import { useCurrentUserContext } from "./CurrentUserContext";
@@ -18,6 +19,14 @@ interface UserContextType {
18
19
 
19
20
  const UserContext = createContext<UserContextType | undefined>(undefined);
20
21
 
22
+ /**
23
+ * The platform-wide user list administrators use (PlatformUsersContainer). The
24
+ * User module's own `pageUrl` (/users) is the company-scoped list, which a
25
+ * system administrator, who belongs to no company, sees empty. Same path
26
+ * convention as the hrefs in AdminIndexContainer.
27
+ */
28
+ const USERS_ADMIN_PAGE_URL = "/administration/users";
29
+
21
30
  type UserProviderProps = {
22
31
  children: ReactNode;
23
32
  dehydratedUser?: JsonApiHydratedDataInterface;
@@ -25,7 +34,7 @@ type UserProviderProps = {
25
34
 
26
35
  export const UserProvider = ({ children, dehydratedUser }: UserProviderProps) => {
27
36
  const generateUrl = usePageUrlGenerator();
28
- const { hasPermissionToModule } = useCurrentUserContext<UserInterface>();
37
+ const { hasPermissionToModule, hasRole } = useCurrentUserContext<UserInterface>();
29
38
  const t = useTranslations();
30
39
 
31
40
  const [user, setUser] = useState<UserInterface | undefined>(
@@ -35,10 +44,16 @@ export const UserProvider = ({ children, dehydratedUser }: UserProviderProps) =>
35
44
  const breadcrumb = () => {
36
45
  const response: BreadcrumbItemData[] = [];
37
46
 
38
- if (hasPermissionToModule({ module: Modules.User, action: Action.Update })) {
47
+ // Administrators are checked by role, as CompanyProvider does: an app may
48
+ // grant them no module permission, and their list is the administration one.
49
+ const isAdministrator = hasRole(getRoleId().Administrator);
50
+
51
+ // The list root comes first, linked ONLY when it is not the current page
52
+ // (same rule as CompanyProvider).
53
+ if (isAdministrator || hasPermissionToModule({ module: Modules.User, action: Action.Update })) {
39
54
  response.push({
40
55
  name: t(`entities.users`, { count: 2 }),
41
- href: generateUrl({ page: Modules.User }),
56
+ ...(user ? { href: generateUrl({ page: isAdministrator ? USERS_ADMIN_PAGE_URL : Modules.User }) } : {}),
42
57
  });
43
58
  }
44
59
 
@@ -0,0 +1,95 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { useSharedContext } from "../../../../contexts/SharedContext";
4
+ import { UserProvider } from "../UserContext";
5
+
6
+ const access = vi.hoisted(() => ({ isAdministrator: false, canUpdateUsers: false }));
7
+
8
+ vi.mock("../CurrentUserContext", () => ({
9
+ useCurrentUserContext: () => ({
10
+ hasRole: (roleId: string) => access.isAdministrator && roleId === "administrator",
11
+ hasPermissionToModule: () => access.canUpdateUsers,
12
+ }),
13
+ }));
14
+
15
+ vi.mock("../../../../roles", () => ({
16
+ getRoleId: () => ({ Administrator: "administrator" }),
17
+ }));
18
+
19
+ // The title functions are not under test; the real forms pull in the editor stack.
20
+ vi.mock("../../components/forms", () => ({
21
+ UserDeleter: () => null,
22
+ UserEditor: () => null,
23
+ UserReactivator: () => null,
24
+ UserResentInvitationEmail: () => null,
25
+ }));
26
+
27
+ // The registry throws for modules nobody registered. The dehydrated user is
28
+ // handed back as-is so the spec does not depend on the User model.
29
+ vi.mock("../../../../core", async (importOriginal) => {
30
+ const actual = await importOriginal<typeof import("../../../../core")>();
31
+ return {
32
+ ...actual,
33
+ Modules: { User: { name: "users", pageUrl: "/users" } },
34
+ rehydrate: (_module: unknown, data: unknown) => data,
35
+ };
36
+ });
37
+
38
+ // next-intl is globally mocked in vitest.setup.ts with `t = (key) => key`, so a
39
+ // translated label renders as its own key.
40
+ function BreadcrumbProbe() {
41
+ const { breadcrumbs } = useSharedContext();
42
+ return <span data-testid="breadcrumbs">{JSON.stringify(breadcrumbs)}</span>;
43
+ }
44
+
45
+ const user = { id: "u1", name: "Ada", isDeleted: false, isActivated: true } as any;
46
+
47
+ const renderBreadcrumbs = (dehydratedUser?: any) => {
48
+ render(
49
+ <UserProvider dehydratedUser={dehydratedUser}>
50
+ <BreadcrumbProbe />
51
+ </UserProvider>,
52
+ );
53
+ return JSON.parse(screen.getByTestId("breadcrumbs").textContent ?? "[]");
54
+ };
55
+
56
+ describe("UserContext breadcrumb", () => {
57
+ beforeEach(() => {
58
+ access.isAdministrator = false;
59
+ access.canUpdateUsers = false;
60
+ });
61
+
62
+ it("shows an administrator the users list as an unlinked root, even without the module permission", () => {
63
+ access.isAdministrator = true;
64
+
65
+ expect(renderBreadcrumbs()).toEqual([{ name: "entities.users" }]);
66
+ });
67
+
68
+ it("links an administrator's user detail back to the administration users list", () => {
69
+ access.isAdministrator = true;
70
+
71
+ expect(renderBreadcrumbs(user)).toEqual([
72
+ { name: "entities.users", href: "/administration/users" },
73
+ { name: "Ada", href: "/users/u1" },
74
+ ]);
75
+ });
76
+
77
+ it("shows a user manager the company users list as an unlinked root", () => {
78
+ access.canUpdateUsers = true;
79
+
80
+ expect(renderBreadcrumbs()).toEqual([{ name: "entities.users" }]);
81
+ });
82
+
83
+ it("links a user manager's user detail back to the company users list", () => {
84
+ access.canUpdateUsers = true;
85
+
86
+ expect(renderBreadcrumbs(user)).toEqual([
87
+ { name: "entities.users", href: "/users" },
88
+ { name: "Ada", href: "/users/u1" },
89
+ ]);
90
+ });
91
+
92
+ it("shows only the user to a viewer who can neither manage users nor administer", () => {
93
+ expect(renderBreadcrumbs(user)).toEqual([{ name: "Ada", href: "/users/u1" }]);
94
+ });
95
+ });