@carlonicora/nextjs-jsonapi 0.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/dist/BlockNoteEditor-VFWG6LXI.js.map +1 -1
  2. package/dist/JsonApiRequest-ZZLSP26T.js.map +1 -1
  3. package/dist/atoms/index.js.map +1 -1
  4. package/dist/chunk-2K3Q24UF.js.map +1 -1
  5. package/dist/chunk-3FBCC4G3.js.map +1 -1
  6. package/dist/chunk-4HCRAOS5.js.map +1 -1
  7. package/dist/chunk-6GKHCVF6.js.map +1 -1
  8. package/dist/chunk-7QVYU63E.js.map +1 -1
  9. package/dist/chunk-A5DDIABK.js.map +1 -1
  10. package/dist/chunk-AWONBQQP.js.map +1 -1
  11. package/dist/chunk-CXQOWQSY.js.map +1 -1
  12. package/dist/chunk-DO2HLAZO.js.map +1 -1
  13. package/dist/chunk-EFJEWLRL.js.map +1 -1
  14. package/dist/chunk-FY4SXJGU.js.map +1 -1
  15. package/dist/chunk-H6FMOA6B.js.map +1 -1
  16. package/dist/chunk-I2REI7OA.js.map +1 -1
  17. package/dist/chunk-IBS6NI7D.js.map +1 -1
  18. package/dist/chunk-J4Q36PMP.js.map +1 -1
  19. package/dist/chunk-JC3WJK65.js.map +1 -1
  20. package/dist/chunk-LXKSUWAV.js.map +1 -1
  21. package/dist/chunk-RAF7PNLG.js.map +1 -1
  22. package/dist/chunk-RUR22SVM.js.map +1 -1
  23. package/dist/chunk-TEGF6ZWG.js.map +1 -1
  24. package/dist/chunk-TMVHSY3Y.js.map +1 -1
  25. package/dist/chunk-V2JJPI7N.js.map +1 -1
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/components/index.js.map +1 -1
  28. package/dist/contexts/index.js.map +1 -1
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/features/index.js.map +1 -1
  31. package/dist/hooks/index.js.map +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/interfaces/index.js.map +1 -1
  34. package/dist/permissions/index.js.map +1 -1
  35. package/dist/request-QFS7NEIE.js.map +1 -1
  36. package/dist/request-ZYY6RI5X.js.map +1 -1
  37. package/dist/roles/index.js.map +1 -1
  38. package/dist/server/index.js.map +1 -1
  39. package/dist/shadcnui/index.js.map +1 -1
  40. package/dist/token-MJMC26ON.js.map +1 -1
  41. package/dist/token-UYE7CV6X.js.map +1 -1
  42. package/dist/utils/index.js.map +1 -1
  43. package/package.json +6 -1
  44. package/src/atoms/index.ts +1 -0
  45. package/src/atoms/recentPagesAtom.ts +10 -0
  46. package/src/client/context/JsonApiContext.ts +61 -0
  47. package/src/client/context/JsonApiProvider.tsx +27 -0
  48. package/src/client/context/index.ts +2 -0
  49. package/src/client/hooks/index.ts +3 -0
  50. package/src/client/hooks/useJsonApiGet.ts +188 -0
  51. package/src/client/hooks/useJsonApiMutation.ts +193 -0
  52. package/src/client/hooks/useRehydration.ts +47 -0
  53. package/src/client/index.ts +11 -0
  54. package/src/client/request.ts +97 -0
  55. package/src/client/token.ts +10 -0
  56. package/src/components/containers/PageContainer.tsx +15 -0
  57. package/src/components/containers/ReactMarkdownContainer.tsx +119 -0
  58. package/src/components/containers/TabsContainer.tsx +93 -0
  59. package/src/components/containers/index.ts +3 -0
  60. package/src/components/contents/AttributeElement.tsx +20 -0
  61. package/src/components/contents/index.ts +1 -0
  62. package/src/components/details/AllowedUsersDetails.tsx +23 -0
  63. package/src/components/details/index.ts +1 -0
  64. package/src/components/editors/BlockNoteDiffInlineContent.tsx +152 -0
  65. package/src/components/editors/BlockNoteEditor.tsx +404 -0
  66. package/src/components/editors/BlockNoteEditorContainer.tsx +13 -0
  67. package/src/components/editors/BlockNoteEditorFormattingToolbar.tsx +38 -0
  68. package/src/components/editors/index.ts +1 -0
  69. package/src/components/errors/ErrorDetails.tsx +41 -0
  70. package/src/components/errors/errorToast.ts +9 -0
  71. package/src/components/errors/index.ts +2 -0
  72. package/src/components/forms/CommonAssociationForm.tsx +162 -0
  73. package/src/components/forms/CommonDeleter.tsx +94 -0
  74. package/src/components/forms/CommonEditorButtons.tsx +30 -0
  75. package/src/components/forms/CommonEditorHeader.tsx +35 -0
  76. package/src/components/forms/CommonEditorTrigger.tsx +26 -0
  77. package/src/components/forms/DatePickerPopover.tsx +219 -0
  78. package/src/components/forms/DateRangeSelector.tsx +110 -0
  79. package/src/components/forms/FileUploader.tsx +324 -0
  80. package/src/components/forms/FormCheckbox.tsx +66 -0
  81. package/src/components/forms/FormContainerGeneric.tsx +39 -0
  82. package/src/components/forms/FormDate.tsx +247 -0
  83. package/src/components/forms/FormDateTime.tsx +231 -0
  84. package/src/components/forms/FormInput.tsx +110 -0
  85. package/src/components/forms/FormPassword.tsx +54 -0
  86. package/src/components/forms/FormPlaceAutocomplete.tsx +286 -0
  87. package/src/components/forms/FormSelect.tsx +72 -0
  88. package/src/components/forms/FormSlider.tsx +51 -0
  89. package/src/components/forms/FormSwitch.tsx +25 -0
  90. package/src/components/forms/FormTextarea.tsx +44 -0
  91. package/src/components/forms/MultiFileUploader.tsx +107 -0
  92. package/src/components/forms/PasswordInput.tsx +47 -0
  93. package/src/components/forms/index.ts +21 -0
  94. package/src/components/index.ts +11 -0
  95. package/src/components/navigations/Breadcrumb.tsx +83 -0
  96. package/src/components/navigations/ContentTitle.tsx +39 -0
  97. package/src/components/navigations/Header.tsx +27 -0
  98. package/src/components/navigations/ModeToggleSwitch.tsx +25 -0
  99. package/src/components/navigations/PageSection.tsx +64 -0
  100. package/src/components/navigations/RecentPagesNavigator.tsx +52 -0
  101. package/src/components/navigations/index.ts +6 -0
  102. package/src/components/pages/PageContainerContentDetails.tsx +76 -0
  103. package/src/components/pages/PageContentContainer.tsx +31 -0
  104. package/src/components/pages/index.ts +2 -0
  105. package/src/components/tables/ContentListTable.tsx +165 -0
  106. package/src/components/tables/ContentTableSearch.tsx +105 -0
  107. package/src/components/tables/cells/cell.component.tsx +18 -0
  108. package/src/components/tables/cells/cell.date.tsx +16 -0
  109. package/src/components/tables/cells/cell.id.tsx +27 -0
  110. package/src/components/tables/cells/cell.link.tsx +18 -0
  111. package/src/components/tables/cells/cell.text.tsx +12 -0
  112. package/src/components/tables/cells/cell.url.tsx +13 -0
  113. package/src/components/tables/cells/index.ts +5 -0
  114. package/src/components/tables/index.ts +3 -0
  115. package/src/contexts/SharedContext.tsx +35 -0
  116. package/src/contexts/index.ts +2 -0
  117. package/src/core/abstracts/AbstractApiData.ts +138 -0
  118. package/src/core/abstracts/AbstractService.ts +263 -0
  119. package/src/core/abstracts/index.ts +2 -0
  120. package/src/core/endpoint/EndpointCreator.ts +97 -0
  121. package/src/core/endpoint/index.ts +1 -0
  122. package/src/core/factories/JsonApiDataFactory.ts +12 -0
  123. package/src/core/factories/RehydrationFactory.ts +30 -0
  124. package/src/core/factories/index.ts +2 -0
  125. package/src/core/fields/FieldSelector.ts +15 -0
  126. package/src/core/fields/index.ts +1 -0
  127. package/src/core/index.ts +20 -0
  128. package/src/core/interfaces/ApiData.ts +8 -0
  129. package/src/core/interfaces/ApiDataInterface.ts +15 -0
  130. package/src/core/interfaces/ApiRequestDataTypeInterface.ts +14 -0
  131. package/src/core/interfaces/ApiResponseInterface.ts +17 -0
  132. package/src/core/interfaces/JsonApiHydratedDataInterface.ts +5 -0
  133. package/src/core/interfaces/index.ts +5 -0
  134. package/src/core/registry/DataClassRegistry.ts +51 -0
  135. package/src/core/registry/ModuleRegistrar.ts +43 -0
  136. package/src/core/registry/ModuleRegistry.ts +64 -0
  137. package/src/core/registry/index.ts +3 -0
  138. package/src/core/utils/index.ts +2 -0
  139. package/src/core/utils/rehydrate.ts +24 -0
  140. package/src/core/utils/translateResponse.ts +125 -0
  141. package/src/features/auth/auth.module.ts +9 -0
  142. package/src/features/auth/config.ts +57 -0
  143. package/src/features/auth/data/auth.interface.ts +31 -0
  144. package/src/features/auth/data/auth.service.ts +159 -0
  145. package/src/features/auth/data/auth.ts +54 -0
  146. package/src/features/auth/data/index.ts +3 -0
  147. package/src/features/auth/index.ts +3 -0
  148. package/src/features/company/company.module.ts +10 -0
  149. package/src/features/company/data/company.fields.ts +6 -0
  150. package/src/features/company/data/company.interface.ts +28 -0
  151. package/src/features/company/data/company.service.ts +73 -0
  152. package/src/features/company/data/company.ts +104 -0
  153. package/src/features/company/data/index.ts +4 -0
  154. package/src/features/company/index.ts +2 -0
  155. package/src/features/content/content.module.ts +20 -0
  156. package/src/features/content/data/content.fields.ts +13 -0
  157. package/src/features/content/data/content.interface.ts +23 -0
  158. package/src/features/content/data/content.service.ts +75 -0
  159. package/src/features/content/data/content.ts +85 -0
  160. package/src/features/content/data/index.ts +4 -0
  161. package/src/features/content/index.ts +2 -0
  162. package/src/features/feature/components/forms/FormFeatures.tsx +149 -0
  163. package/src/features/feature/components/index.ts +1 -0
  164. package/src/features/feature/data/feature.interface.ts +9 -0
  165. package/src/features/feature/data/feature.service.ts +19 -0
  166. package/src/features/feature/data/feature.ts +33 -0
  167. package/src/features/feature/data/index.ts +3 -0
  168. package/src/features/feature/feature.module.ts +10 -0
  169. package/src/features/feature/index.ts +3 -0
  170. package/src/features/index.ts +12 -0
  171. package/src/features/module/data/index.ts +2 -0
  172. package/src/features/module/data/module.interface.ts +12 -0
  173. package/src/features/module/data/module.ts +42 -0
  174. package/src/features/module/index.ts +2 -0
  175. package/src/features/module/module.module.ts +10 -0
  176. package/src/features/notification/data/index.ts +4 -0
  177. package/src/features/notification/data/notification.fields.ts +8 -0
  178. package/src/features/notification/data/notification.interface.ts +14 -0
  179. package/src/features/notification/data/notification.service.ts +34 -0
  180. package/src/features/notification/data/notification.ts +51 -0
  181. package/src/features/notification/index.ts +2 -0
  182. package/src/features/notification/notification.module.ts +10 -0
  183. package/src/features/push/data/index.ts +3 -0
  184. package/src/features/push/data/push.interface.ts +8 -0
  185. package/src/features/push/data/push.service.ts +17 -0
  186. package/src/features/push/data/push.ts +18 -0
  187. package/src/features/push/index.ts +2 -0
  188. package/src/features/push/push.module.ts +10 -0
  189. package/src/features/role/data/index.ts +4 -0
  190. package/src/features/role/data/role.fields.ts +8 -0
  191. package/src/features/role/data/role.interface.ts +16 -0
  192. package/src/features/role/data/role.service.ts +117 -0
  193. package/src/features/role/data/role.ts +62 -0
  194. package/src/features/role/index.ts +2 -0
  195. package/src/features/role/role.module.ts +10 -0
  196. package/src/features/s3/data/index.ts +3 -0
  197. package/src/features/s3/data/s3.interface.ts +11 -0
  198. package/src/features/s3/data/s3.service.ts +30 -0
  199. package/src/features/s3/data/s3.ts +60 -0
  200. package/src/features/s3/index.ts +2 -0
  201. package/src/features/s3/s3.module.ts +10 -0
  202. package/src/features/search/index.ts +1 -0
  203. package/src/features/search/interfaces/index.ts +1 -0
  204. package/src/features/search/interfaces/search.result.interface.ts +3 -0
  205. package/src/features/user/author.module.ts +10 -0
  206. package/src/features/user/components/index.ts +2 -0
  207. package/src/features/user/components/lists/ContributorsList.tsx +41 -0
  208. package/src/features/user/components/lists/index.ts +1 -0
  209. package/src/features/user/components/widgets/UserAvatar.tsx +86 -0
  210. package/src/features/user/components/widgets/index.ts +1 -0
  211. package/src/features/user/contexts/CurrentUserContext.tsx +156 -0
  212. package/src/features/user/contexts/index.ts +1 -0
  213. package/src/features/user/data/index.ts +4 -0
  214. package/src/features/user/data/user.fields.ts +8 -0
  215. package/src/features/user/data/user.interface.ts +41 -0
  216. package/src/features/user/data/user.service.ts +246 -0
  217. package/src/features/user/data/user.ts +162 -0
  218. package/src/features/user/index.ts +4 -0
  219. package/src/features/user/user.module.ts +21 -0
  220. package/src/hooks/TableGeneratorRegistry.ts +53 -0
  221. package/src/hooks/index.ts +33 -0
  222. package/src/hooks/types.ts +35 -0
  223. package/src/hooks/url.rewriter.ts +22 -0
  224. package/src/hooks/useCustomD3Graph.tsx +705 -0
  225. package/src/hooks/useDataListRetriever.ts +349 -0
  226. package/src/hooks/useDebounce.ts +33 -0
  227. package/src/hooks/usePageUrlGenerator.ts +50 -0
  228. package/src/hooks/useTableGenerator.ts +16 -0
  229. package/src/i18n/config.ts +73 -0
  230. package/src/i18n/index.ts +18 -0
  231. package/src/index.ts +16 -0
  232. package/src/interfaces/breadcrumb.item.data.interface.ts +4 -0
  233. package/src/interfaces/d3.link.interface.ts +7 -0
  234. package/src/interfaces/d3.node.interface.ts +12 -0
  235. package/src/interfaces/index.ts +3 -0
  236. package/src/permissions/check.ts +127 -0
  237. package/src/permissions/index.ts +2 -0
  238. package/src/permissions/types.ts +109 -0
  239. package/src/roles/config.ts +46 -0
  240. package/src/roles/index.ts +1 -0
  241. package/src/server/cache.ts +28 -0
  242. package/src/server/index.ts +3 -0
  243. package/src/server/request.ts +113 -0
  244. package/src/server/token.ts +10 -0
  245. package/src/shadcnui/custom/kanban.tsx +1001 -0
  246. package/src/shadcnui/custom/link.tsx +18 -0
  247. package/src/shadcnui/custom/multi-select.tsx +382 -0
  248. package/src/shadcnui/index.ts +49 -0
  249. package/src/shadcnui/ui/accordion.tsx +52 -0
  250. package/src/shadcnui/ui/alert-dialog.tsx +141 -0
  251. package/src/shadcnui/ui/alert.tsx +43 -0
  252. package/src/shadcnui/ui/avatar.tsx +50 -0
  253. package/src/shadcnui/ui/badge.tsx +40 -0
  254. package/src/shadcnui/ui/breadcrumb.tsx +115 -0
  255. package/src/shadcnui/ui/button.tsx +51 -0
  256. package/src/shadcnui/ui/calendar.tsx +73 -0
  257. package/src/shadcnui/ui/card.tsx +43 -0
  258. package/src/shadcnui/ui/carousel.tsx +225 -0
  259. package/src/shadcnui/ui/chart.tsx +320 -0
  260. package/src/shadcnui/ui/checkbox.tsx +29 -0
  261. package/src/shadcnui/ui/collapsible.tsx +11 -0
  262. package/src/shadcnui/ui/command.tsx +155 -0
  263. package/src/shadcnui/ui/context-menu.tsx +179 -0
  264. package/src/shadcnui/ui/dialog.tsx +96 -0
  265. package/src/shadcnui/ui/drawer.tsx +89 -0
  266. package/src/shadcnui/ui/dropdown-menu.tsx +205 -0
  267. package/src/shadcnui/ui/form.tsx +138 -0
  268. package/src/shadcnui/ui/hover-card.tsx +29 -0
  269. package/src/shadcnui/ui/input.tsx +21 -0
  270. package/src/shadcnui/ui/label.tsx +26 -0
  271. package/src/shadcnui/ui/navigation-menu.tsx +168 -0
  272. package/src/shadcnui/ui/popover.tsx +33 -0
  273. package/src/shadcnui/ui/progress.tsx +25 -0
  274. package/src/shadcnui/ui/radio-group.tsx +37 -0
  275. package/src/shadcnui/ui/resizable.tsx +47 -0
  276. package/src/shadcnui/ui/scroll-area.tsx +40 -0
  277. package/src/shadcnui/ui/select.tsx +164 -0
  278. package/src/shadcnui/ui/separator.tsx +28 -0
  279. package/src/shadcnui/ui/sheet.tsx +139 -0
  280. package/src/shadcnui/ui/sidebar.tsx +677 -0
  281. package/src/shadcnui/ui/skeleton.tsx +13 -0
  282. package/src/shadcnui/ui/slider.tsx +25 -0
  283. package/src/shadcnui/ui/sonner.tsx +25 -0
  284. package/src/shadcnui/ui/switch.tsx +31 -0
  285. package/src/shadcnui/ui/table.tsx +120 -0
  286. package/src/shadcnui/ui/tabs.tsx +55 -0
  287. package/src/shadcnui/ui/textarea.tsx +24 -0
  288. package/src/shadcnui/ui/toggle.tsx +39 -0
  289. package/src/shadcnui/ui/tooltip.tsx +61 -0
  290. package/src/unified/JsonApiRequest.ts +325 -0
  291. package/src/unified/index.ts +1 -0
  292. package/src/utils/blocknote-diff.util.ts +815 -0
  293. package/src/utils/blocknote-word-diff-renderer.util.ts +413 -0
  294. package/src/utils/cn.ts +6 -0
  295. package/src/utils/compose-refs.ts +61 -0
  296. package/src/utils/date-formatter.ts +53 -0
  297. package/src/utils/exists.ts +7 -0
  298. package/src/utils/index.ts +15 -0
  299. package/src/utils/schemas/entity.object.schema.ts +8 -0
  300. package/src/utils/schemas/index.ts +2 -0
  301. package/src/utils/schemas/user.object.schema.ts +9 -0
  302. package/src/utils/table-options.ts +67 -0
  303. package/src/utils/use-mobile.tsx +21 -0
@@ -0,0 +1,47 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { ApiDataInterface } from "../../core/interfaces/ApiDataInterface";
5
+ import { ApiRequestDataTypeInterface } from "../../core/interfaces/ApiRequestDataTypeInterface";
6
+ import { JsonApiHydratedDataInterface } from "../../core/interfaces/JsonApiHydratedDataInterface";
7
+ import { RehydrationFactory } from "../../core/factories/RehydrationFactory";
8
+
9
+ /**
10
+ * Hook to rehydrate server-passed data into typed objects.
11
+ * Use this when passing data from server components to client components.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * // In server component
16
+ * const article = await ArticleService.findOne(id);
17
+ * return <ArticleDetails data={article.dehydrate()} />;
18
+ *
19
+ * // In client component
20
+ * function ArticleDetails({ data }: { data: JsonApiHydratedDataInterface }) {
21
+ * const article = useRehydration<Article>(Modules.Article, data);
22
+ * return <div>{article.title}</div>;
23
+ * }
24
+ * ```
25
+ */
26
+ export function useRehydration<T extends ApiDataInterface>(
27
+ classKey: ApiRequestDataTypeInterface,
28
+ data: JsonApiHydratedDataInterface | null | undefined,
29
+ ): T | null {
30
+ return useMemo(() => {
31
+ if (!data) return null;
32
+ return RehydrationFactory.rehydrate<T>(classKey, data);
33
+ }, [classKey, data]);
34
+ }
35
+
36
+ /**
37
+ * Hook to rehydrate a list of server-passed data into typed objects.
38
+ */
39
+ export function useRehydrationList<T extends ApiDataInterface>(
40
+ classKey: ApiRequestDataTypeInterface,
41
+ data: JsonApiHydratedDataInterface[] | null | undefined,
42
+ ): T[] {
43
+ return useMemo(() => {
44
+ if (!data || data.length === 0) return [];
45
+ return RehydrationFactory.rehydrateList<T>(classKey, data);
46
+ }, [classKey, data]);
47
+ }
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ // Context and Provider
4
+ export * from "./context";
5
+
6
+ // Hooks
7
+ export * from "./hooks";
8
+
9
+ // Client-side request utilities
10
+ export * from "./request";
11
+ export * from "./token";
@@ -0,0 +1,97 @@
1
+ "use client";
2
+
3
+ import { ApiData } from "../core/interfaces/ApiData";
4
+
5
+ export interface DirectFetchParams {
6
+ method: string;
7
+ url: string;
8
+ token?: string;
9
+ body?: any;
10
+ files?: { [key: string]: File | Blob } | File | Blob;
11
+ companyId?: string;
12
+ language: string;
13
+ additionalHeaders?: Record<string, string>;
14
+ }
15
+
16
+ /**
17
+ * Client-side direct fetch to bypass server action overhead.
18
+ * Use this for client-side API calls.
19
+ */
20
+ export async function directFetch(params: DirectFetchParams): Promise<ApiData> {
21
+ const response: ApiData = {
22
+ data: undefined,
23
+ ok: false,
24
+ status: 0,
25
+ statusText: "",
26
+ };
27
+
28
+ const additionalHeaders: Record<string, string> = { ...params.additionalHeaders };
29
+
30
+ if (params.companyId) {
31
+ additionalHeaders["x-companyid"] = params.companyId;
32
+ }
33
+ additionalHeaders["x-language"] = params.language;
34
+
35
+ let requestBody: BodyInit | undefined = undefined;
36
+
37
+ if (params.files) {
38
+ const formData = new FormData();
39
+ if (params.body && typeof params.body === "object") {
40
+ for (const key in params.body) {
41
+ if (Object.prototype.hasOwnProperty.call(params.body, key)) {
42
+ formData.append(
43
+ key,
44
+ typeof params.body[key] === "object" ? JSON.stringify(params.body[key]) : params.body[key],
45
+ );
46
+ }
47
+ }
48
+ }
49
+
50
+ if (params.files instanceof Blob) {
51
+ formData.append("file", params.files);
52
+ } else if (typeof params.files === "object" && params.files !== null) {
53
+ for (const key in params.files) {
54
+ if (Object.prototype.hasOwnProperty.call(params.files, key)) {
55
+ formData.append(key, params.files[key]);
56
+ }
57
+ }
58
+ }
59
+
60
+ requestBody = formData;
61
+ } else if (params.body !== undefined) {
62
+ requestBody = JSON.stringify(params.body);
63
+ additionalHeaders["Content-Type"] = "application/json";
64
+ }
65
+
66
+ const options: RequestInit = {
67
+ method: params.method,
68
+ headers: { Accept: "application/json", ...additionalHeaders },
69
+ };
70
+
71
+ if (requestBody !== undefined) {
72
+ options.body = requestBody;
73
+ }
74
+
75
+ if (params.token) {
76
+ options.headers = { ...options.headers, Authorization: `Bearer ${params.token}` };
77
+ }
78
+
79
+ try {
80
+ const apiResponse = await fetch(params.url, options);
81
+
82
+ response.ok = apiResponse.ok;
83
+ response.status = apiResponse.status;
84
+ response.statusText = apiResponse.statusText;
85
+ try {
86
+ response.data = await apiResponse.json();
87
+ } catch {
88
+ response.data = undefined;
89
+ }
90
+ } catch {
91
+ response.ok = false;
92
+ response.status = 500;
93
+ response.data = undefined;
94
+ }
95
+
96
+ return response;
97
+ }
@@ -0,0 +1,10 @@
1
+ "use client";
2
+
3
+ import { getCookie } from "cookies-next";
4
+
5
+ /**
6
+ * Get the authentication token from cookies (client-side only)
7
+ */
8
+ export async function getClientToken(): Promise<string | undefined> {
9
+ return await getCookie("token");
10
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import { cn } from "../../utils";
4
+ import { Header } from "../navigations";
5
+
6
+ type PageContainerProps = { children: React.ReactNode; testId?: string; className?: string };
7
+
8
+ export function PageContainer({ children, testId, className }: PageContainerProps) {
9
+ return (
10
+ <div className={`flex h-full w-full flex-col`} data-testid={testId}>
11
+ <Header />
12
+ <main className={cn(`flex w-full flex-1 flex-col gap-y-4 pt-4 pl-4 pr-4`, className)}>{children}</main>
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,119 @@
1
+ "use client";
2
+
3
+ import { ChevronDown, ChevronUp } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import ReactMarkdown from "react-markdown";
7
+ import remarkGfm from "remark-gfm";
8
+
9
+ type ReactMarkdownContainerProps = {
10
+ content: string;
11
+ collapsible?: boolean;
12
+ initialLines?: number;
13
+ size?: "small" | "normal";
14
+ };
15
+
16
+ export function ReactMarkdownContainer({
17
+ content,
18
+ collapsible = false,
19
+ initialLines = 4,
20
+ size = "normal",
21
+ }: ReactMarkdownContainerProps) {
22
+ const t = useTranslations("generic.buttons");
23
+ const [isExpanded, setIsExpanded] = useState(false);
24
+ const [showExpandButton, setShowExpandButton] = useState(false);
25
+ const contentRef = useRef<HTMLDivElement>(null);
26
+
27
+ useEffect(() => {
28
+ if (collapsible && contentRef.current && !isExpanded) {
29
+ // Check if content exceeds the clamped height
30
+ const isOverflowing = contentRef.current.scrollHeight > contentRef.current.clientHeight;
31
+ setShowExpandButton(isOverflowing);
32
+ }
33
+ }, [collapsible, content, isExpanded]);
34
+
35
+ const handleToggle = () => {
36
+ setIsExpanded(!isExpanded);
37
+ };
38
+
39
+ const clampStyle =
40
+ collapsible && !isExpanded
41
+ ? {
42
+ display: "-webkit-box",
43
+ WebkitLineClamp: initialLines,
44
+ WebkitBoxOrient: "vertical" as const,
45
+ overflow: "hidden",
46
+ }
47
+ : {};
48
+
49
+ return (
50
+ <div className="flex flex-col">
51
+ <div className="relative">
52
+ <div ref={contentRef} style={clampStyle} className="transition-all duration-300 ease-in-out">
53
+ <ReactMarkdown
54
+ remarkPlugins={[remarkGfm]}
55
+ components={{
56
+ p: ({ children }) => <p className={size === "small" ? "text-xs" : ""}>{children}</p>,
57
+ li: ({ children }) => <li className={size === "small" ? "text-xs" : ""}>{children}</li>,
58
+ table: ({ children }) => <table className="w-full table-auto border-collapse border">{children}</table>,
59
+ th: ({ children }) => (
60
+ <th className={`border px-4 py-2 text-left ${size === "small" ? "px-2 py-1 text-xs" : ""}`}>
61
+ {children}
62
+ </th>
63
+ ),
64
+ td: ({ children }) => (
65
+ <td className={`border px-4 py-2 ${size === "small" ? "px-2 py-1 text-xs" : ""}`}>{children}</td>
66
+ ),
67
+ tr: ({ children }) => <tr className="even:bg-gray-50">{children}</tr>,
68
+ ul: ({ children }) => <ul className={`list-disc ${size === "small" ? "pl-3" : "pl-4"}`}>{children}</ul>,
69
+ ol: ({ children }) => (
70
+ <ol className={`list-decimal ${size === "small" ? "pl-3" : "pl-4"}`}>{children}</ol>
71
+ ),
72
+ h1: ({ children }) => (
73
+ <h1 className={size === "small" ? "my-1 mt-2 text-sm font-bold" : "my-2 mt-4 text-3xl font-medium"}>
74
+ {children}
75
+ </h1>
76
+ ),
77
+ h2: ({ children }) => (
78
+ <h2
79
+ className={size === "small" ? "my-1 mt-2 text-sm font-semibold" : "my-2 mt-4 text-2xl font-semibold"}
80
+ >
81
+ {children}
82
+ </h2>
83
+ ),
84
+ h3: ({ children }) => (
85
+ <h3 className={size === "small" ? "my-1 mt-2 text-sm font-medium" : "my-2 mt-4 text-xl font-semibold"}>
86
+ {children}
87
+ </h3>
88
+ ),
89
+ h4: ({ children }) => (
90
+ <h4 className={size === "small" ? "my-1 mt-2 text-sm font-medium" : "my-2 mt-4 text-lg font-semibold"}>
91
+ {children}
92
+ </h4>
93
+ ),
94
+ }}
95
+ >
96
+ {content}
97
+ </ReactMarkdown>
98
+ </div>
99
+
100
+ {collapsible && !isExpanded && showExpandButton && (
101
+ <div className="pointer-events-none absolute right-0 bottom-0 left-0 h-12 bg-gradient-to-t from-white to-transparent" />
102
+ )}
103
+ </div>
104
+
105
+ {collapsible && showExpandButton && (
106
+ <div className="mt-2 flex justify-end">
107
+ <button
108
+ onClick={handleToggle}
109
+ className="flex items-center gap-1 rounded-md px-3 py-1.5 text-sm text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
110
+ aria-label={isExpanded ? t("show_less") : t("show_more")}
111
+ >
112
+ <span>{isExpanded ? t("show_less") : t("show_more")}</span>
113
+ {isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
114
+ </button>
115
+ </div>
116
+ )}
117
+ </div>
118
+ );
119
+ }
@@ -0,0 +1,93 @@
1
+ "use client";
2
+
3
+ import { useCurrentUserContext } from "../../contexts";
4
+ import { UserInterface } from "../../features";
5
+ import { Action, ModuleWithPermissions } from "../../permissions";
6
+ import { ScrollArea, Tabs, TabsContent, TabsList, TabsTrigger } from "../../shadcnui";
7
+ import { cn } from "../../utils";
8
+
9
+ export type Tab = {
10
+ label: string;
11
+ contentLabel?: React.ReactNode;
12
+ content: React.ReactNode;
13
+ modules?: ModuleWithPermissions[];
14
+ action?: Action;
15
+ };
16
+
17
+ type TabsContainerProps = {
18
+ tabs: Tab[];
19
+ defaultTab?: string;
20
+ tabsListClassName?: string;
21
+ tabsTriggerClassName?: string;
22
+ scrollAreaClassName?: string;
23
+ style?: "navigation";
24
+ additionalComponent?: React.ReactNode;
25
+ };
26
+
27
+ export function TabsContainer({
28
+ tabs,
29
+ defaultTab,
30
+ tabsListClassName,
31
+ tabsTriggerClassName,
32
+ scrollAreaClassName,
33
+ style,
34
+ additionalComponent,
35
+ }: TabsContainerProps) {
36
+ const { hasPermissionToModules } = useCurrentUserContext<UserInterface>();
37
+
38
+ const validTabs = tabs.filter((tab) =>
39
+ tab.modules && tab.action ? hasPermissionToModules({ modules: tab.modules, action: tab.action }) : true,
40
+ );
41
+
42
+ if (validTabs.length === 0) return null;
43
+
44
+ const defaultValue = defaultTab ?? tabs[0].label;
45
+
46
+ if (validTabs.length === 1) {
47
+ return validTabs[0].content;
48
+ }
49
+
50
+ return (
51
+ <Tabs defaultValue={defaultValue} className="w-full">
52
+ <div className="flex w-full items-center justify-between">
53
+ <TabsList
54
+ className={cn(
55
+ `${style ? `my-4 flex w-full justify-start rounded-none border-b bg-transparent pb-0` : ``}`,
56
+ tabsListClassName,
57
+ )}
58
+ >
59
+ {validTabs.map((tab) => (
60
+ <TabsTrigger
61
+ key={tab.label}
62
+ value={tab.label}
63
+ className={cn(
64
+ `${style ? `text-muted-foreground border-accent data-[state=active]:text-foreground hover:text-foreground cursor-pointer rounded-none bg-transparent pb-2 text-sm font-light hover:border-0 data-[state=active]:border-b data-[state=active]:font-medium data-[state=active]:shadow-none` : `text-primary text-xs`}`,
65
+ tabsTriggerClassName,
66
+ )}
67
+ >
68
+ {tab.contentLabel ?? tab.label}
69
+ </TabsTrigger>
70
+ ))}
71
+ </TabsList>
72
+ {additionalComponent && additionalComponent}
73
+ </div>
74
+ {scrollAreaClassName ? (
75
+ <ScrollArea className={scrollAreaClassName}>
76
+ {validTabs.map((tab) => (
77
+ <TabsContent key={tab.label} value={tab.label}>
78
+ {tab.content}
79
+ </TabsContent>
80
+ ))}
81
+ </ScrollArea>
82
+ ) : (
83
+ <>
84
+ {validTabs.map((tab) => (
85
+ <TabsContent key={tab.label} value={tab.label}>
86
+ {tab.content}
87
+ </TabsContent>
88
+ ))}
89
+ </>
90
+ )}
91
+ </Tabs>
92
+ );
93
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./PageContainer";
2
+ export * from "./ReactMarkdownContainer";
3
+ export * from "./TabsContainer";
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import { ReactElement } from "react";
4
+ import { cn } from "../../utils";
5
+
6
+ type AttributeElementProps = {
7
+ inline?: boolean;
8
+ title?: string | ReactElement<any>;
9
+ value?: string | ReactElement<any>;
10
+ className?: string;
11
+ };
12
+
13
+ export function AttributeElement({ inline, title, value, className }: AttributeElementProps) {
14
+ return (
15
+ <div className={cn(`flex ${inline === true ? "flex-row" : "flex-col"} my-1 justify-start`, className)}>
16
+ {title && <div className={`${inline === true ? "min-w-48 pr-4" : "w-full"} text-sm font-semibold`}>{title}</div>}
17
+ {value && <div className="flex w-full flex-col text-sm">{value}</div>}
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1 @@
1
+ export * from "./AttributeElement";
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { ContentInterface } from "../../features/content/data";
5
+ import { ContributorsList } from "../../features/user/components";
6
+
7
+ type AllowedUsersDetailsProps = {
8
+ showTitle?: boolean;
9
+ content: ContentInterface;
10
+ };
11
+
12
+ export function AllowedUsersDetails({ showTitle, content }: AllowedUsersDetailsProps) {
13
+ const t = useTranslations();
14
+
15
+ return (
16
+ <div className="mb-2 flex w-full flex-col gap-y-2">
17
+ {showTitle && <h3 className="text-xs font-semibold">{t("generic.permissions")}</h3>}
18
+ <div className="flex w-full items-center justify-start gap-x-4">
19
+ <ContributorsList content={content} />
20
+ </div>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1 @@
1
+ export * from "./AllowedUsersDetails";
@@ -0,0 +1,152 @@
1
+ import { createReactInlineContentSpec } from "@blocknote/react";
2
+ import { CheckIcon, XIcon } from "lucide-react";
3
+ import { Button } from "../../shadcnui";
4
+ import { cn } from "../../utils";
5
+
6
+ // Word-level diff with accept/reject functionality
7
+ export const diffWordInlineContentSpec = createReactInlineContentSpec(
8
+ {
9
+ type: "diffWord",
10
+ propSchema: {
11
+ text: {
12
+ default: "",
13
+ },
14
+ diffType: {
15
+ default: "unchanged",
16
+ },
17
+ diffId: {
18
+ default: "",
19
+ },
20
+ accepted: {
21
+ default: false,
22
+ },
23
+ rejected: {
24
+ default: false,
25
+ },
26
+ onAccept: {
27
+ default: "",
28
+ type: "string" as const,
29
+ },
30
+ onReject: {
31
+ default: "",
32
+ type: "string" as const,
33
+ },
34
+ },
35
+ content: "none",
36
+ },
37
+ {
38
+ render: (props) => {
39
+ const { text, diffType, diffId, accepted, rejected, onAccept, onReject } = props.inlineContent as any;
40
+
41
+ const handleAccept =
42
+ typeof onAccept === "function" ? onAccept : (window as any).blockNoteDiffHandlers?.handleAcceptChange;
43
+ const handleReject =
44
+ typeof onReject === "function" ? onReject : (window as any).blockNoteDiffHandlers?.handleRejectChange;
45
+
46
+ if (diffType === "unchanged") {
47
+ return <span>{text}</span>;
48
+ }
49
+
50
+ // Handle button-only diffType for rendering accept/reject buttons
51
+ if (diffType === "buttons") {
52
+ const diffIdList = diffId.split(",");
53
+ return (
54
+ <span className="ml-2 inline-flex items-center gap-1">
55
+ <Button
56
+ size="sm"
57
+ variant="ghost"
58
+ className="m-0 h-6 w-6 p-0 text-green-600 hover:bg-green-50 hover:text-green-700"
59
+ onClick={(e) => {
60
+ e.preventDefault();
61
+ e.stopPropagation();
62
+ if (handleAccept) {
63
+ diffIdList.forEach((id: string) => handleAccept(id.trim()));
64
+ }
65
+ }}
66
+ title="Accept change"
67
+ >
68
+ <CheckIcon className="h-3 w-3" />
69
+ </Button>
70
+ <Button
71
+ size="sm"
72
+ variant="ghost"
73
+ className="m-0 h-6 w-6 p-0 text-red-600 hover:bg-red-50 hover:text-red-700"
74
+ onClick={(e) => {
75
+ e.preventDefault();
76
+ e.stopPropagation();
77
+ if (handleReject) {
78
+ diffIdList.forEach((id: string) => handleReject(id.trim()));
79
+ }
80
+ }}
81
+ title="Reject change"
82
+ >
83
+ <XIcon className="h-3 w-3" />
84
+ </Button>
85
+ </span>
86
+ );
87
+ }
88
+
89
+ const isAddition = diffType === "added";
90
+ const isRemoval = diffType === "removed";
91
+
92
+ return (
93
+ <span className="group relative inline-flex items-center">
94
+ <span
95
+ className={cn("rounded-sm px-1 transition-all duration-200", {
96
+ "border border-green-300 bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300":
97
+ isAddition && !accepted && !rejected,
98
+ "border border-green-400 bg-green-200 text-green-900": isAddition && accepted,
99
+ "border border-gray-300 bg-gray-100 text-gray-500 line-through": isAddition && rejected,
100
+
101
+ "border border-red-300 bg-red-100 text-red-800 line-through dark:bg-red-900/30 dark:text-red-300":
102
+ isRemoval && !accepted && !rejected,
103
+ "border border-gray-400 bg-gray-200 text-gray-600 line-through": isRemoval && accepted,
104
+ "border border-red-400 bg-red-200 text-red-900 line-through": isRemoval && rejected,
105
+ })}
106
+ >
107
+ {text}
108
+ </span>
109
+
110
+ {!accepted && !rejected && (diffType === "added" || diffType === "removed") && (
111
+ <span className="absolute -top-8 left-0 z-10 hidden items-center gap-1 rounded border bg-white p-1 shadow-lg group-hover:flex">
112
+ <Button
113
+ size="sm"
114
+ variant="ghost"
115
+ className="m-0 h-6 w-6 p-0 text-green-600 hover:bg-green-50 hover:text-green-700"
116
+ onClick={(e) => {
117
+ e.preventDefault();
118
+ e.stopPropagation();
119
+ if (handleAccept) {
120
+ handleAccept(diffId);
121
+ }
122
+ }}
123
+ title="Accept change"
124
+ >
125
+ <CheckIcon className="h-5 w-5" />
126
+ </Button>
127
+ <Button
128
+ size="sm"
129
+ variant="ghost"
130
+ className="m-0 h-6 w-6 p-0 text-red-600 hover:bg-red-50 hover:text-red-700"
131
+ onClick={(e) => {
132
+ e.preventDefault();
133
+ e.stopPropagation();
134
+ if (handleReject) {
135
+ handleReject(diffId);
136
+ }
137
+ }}
138
+ title="Reject change"
139
+ >
140
+ <XIcon className="h-5 w-5" />
141
+ </Button>
142
+ </span>
143
+ )}
144
+ </span>
145
+ );
146
+ },
147
+ },
148
+ );
149
+
150
+ export const diffInlineContentSpecs = {
151
+ diffWord: diffWordInlineContentSpec,
152
+ };