@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,72 @@
1
+ "use client";
2
+
3
+ import {
4
+ FormControl,
5
+ FormField,
6
+ FormItem,
7
+ FormLabel,
8
+ FormMessage,
9
+ Select,
10
+ SelectContent,
11
+ SelectItem,
12
+ SelectTrigger,
13
+ SelectValue,
14
+ } from "../../shadcnui";
15
+
16
+ export function FormSelect({
17
+ form,
18
+ id,
19
+ name,
20
+ placeholder,
21
+ disabled,
22
+ values,
23
+ onChange,
24
+ useRows,
25
+ testId,
26
+ }: {
27
+ form: any;
28
+ id: string;
29
+ name?: string;
30
+ placeholder?: string;
31
+ disabled?: boolean;
32
+ values: { id: string; text: string }[];
33
+ onChange?: (value: string) => void;
34
+ useRows?: boolean;
35
+ testId?: string;
36
+ }) {
37
+ return (
38
+ <div className={`flex w-full flex-col`}>
39
+ <FormField
40
+ control={form.control}
41
+ name={id}
42
+ render={({ field }) => (
43
+ <FormItem className={`flex w-full ${useRows ? `flex-row items-center justify-between gap-x-4` : `flex-col`}`}>
44
+ {name && <FormLabel className={`${useRows ? `min-w-28` : ``}`}>{name}</FormLabel>}
45
+ <Select
46
+ onValueChange={(e) => {
47
+ field.onChange(e);
48
+ if (onChange) onChange(e);
49
+ }}
50
+ defaultValue={field.value}
51
+ data-testid={testId}
52
+ >
53
+ <FormControl className="w-full">
54
+ <SelectTrigger>
55
+ <SelectValue placeholder={placeholder} />
56
+ </SelectTrigger>
57
+ </FormControl>
58
+ <SelectContent>
59
+ {values.map((type: { id: string; text: string }) => (
60
+ <SelectItem key={type.id} value={type.id}>
61
+ {type.text}
62
+ </SelectItem>
63
+ ))}
64
+ </SelectContent>
65
+ </Select>
66
+ <FormMessage />
67
+ </FormItem>
68
+ )}
69
+ />
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,51 @@
1
+ "use client";
2
+
3
+ import { useWatch } from "react-hook-form";
4
+ import { FormControl, FormField, FormItem, FormLabel, FormMessage, Slider } from "../../shadcnui";
5
+
6
+ export function FormSlider({
7
+ form,
8
+ id,
9
+ name,
10
+ disabled,
11
+ showPercentage,
12
+ }: {
13
+ form: any;
14
+ id: string;
15
+ name?: string;
16
+ placeholder?: string;
17
+ disabled?: boolean;
18
+ showPercentage?: boolean;
19
+ }) {
20
+ const value = useWatch({ control: form.control, name: id });
21
+
22
+ return (
23
+ <div className="flex w-full flex-col">
24
+ <FormField
25
+ control={form.control}
26
+ name={id}
27
+ render={({ field }) => (
28
+ <FormItem className={`${name ? "mb-5" : "mb-1"}`}>
29
+ {name && <FormLabel>{name}</FormLabel>}
30
+ <FormControl>
31
+ <div>
32
+ {showPercentage && (
33
+ <div className="text-muted-foreground mb-2 flex w-full justify-center text-xs">{`${value}%`}</div>
34
+ )}
35
+ <Slider
36
+ onValueChange={(value: number[]) => form.setValue(id, value[0])}
37
+ value={[value]}
38
+ max={100}
39
+ step={5}
40
+ disabled={disabled === true || form.formState.isSubmitting}
41
+ />
42
+ {/* </div> */}
43
+ </div>
44
+ </FormControl>
45
+ <FormMessage />
46
+ </FormItem>
47
+ )}
48
+ />
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import { FormControl, FormField, FormItem, FormLabel, FormMessage, Switch } from "../../shadcnui";
4
+
5
+ export function FormSwitch({ form, id, name, disabled }: { form: any; id: string; name?: string; disabled?: boolean }) {
6
+ return (
7
+ <div className="flex w-full flex-col">
8
+ <FormField
9
+ control={form.control}
10
+ name={id}
11
+ render={({ field }) => (
12
+ <FormItem className={`${name ? "mb-5" : "mb-1"}`}>
13
+ <FormControl>
14
+ <div className="flex flex-row gap-x-4">
15
+ <Switch checked={field.value} onCheckedChange={field.onChange} />
16
+ {name && <FormLabel>{name}</FormLabel>}
17
+ </div>
18
+ </FormControl>
19
+ <FormMessage />
20
+ </FormItem>
21
+ )}
22
+ />
23
+ </div>
24
+ );
25
+ }
@@ -0,0 +1,44 @@
1
+ "use client";
2
+
3
+ import { FormControl, FormField, FormItem, FormLabel, FormMessage, Textarea } from "../../shadcnui";
4
+ import { cn } from "../../utils";
5
+
6
+ export function FormTextarea({
7
+ form,
8
+ id,
9
+ name,
10
+ className,
11
+ placeholder,
12
+ testId,
13
+ }: {
14
+ form: any;
15
+ id: string;
16
+ name: string;
17
+ placeholder?: string;
18
+ className?: string;
19
+ testId?: string;
20
+ }) {
21
+ return (
22
+ <div className="flex w-full flex-col">
23
+ <FormField
24
+ control={form.control}
25
+ name={id}
26
+ render={({ field }) => (
27
+ <FormItem className="mb-5">
28
+ <FormLabel>{name}</FormLabel>
29
+ <FormControl>
30
+ <Textarea
31
+ {...field}
32
+ className={cn("min-h-96 w-full", className)}
33
+ disabled={form.formState.isSubmitting}
34
+ placeholder={placeholder}
35
+ data-testid={testId}
36
+ />
37
+ </FormControl>
38
+ <FormMessage data-testid={testId ? `${testId}-error` : undefined} />
39
+ </FormItem>
40
+ )}
41
+ />
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,107 @@
1
+ "use client";
2
+
3
+ import { FileIcon, FileImageIcon, FileSpreadsheetIcon, FileTextIcon, UploadIcon, XIcon } from "lucide-react";
4
+ import Image from "next/image";
5
+ import { useEffect, useState } from "react";
6
+ import { DropzoneOptions } from "react-dropzone";
7
+ import { cn } from "../../utils";
8
+ import { FileInput, FileUploader } from "./FileUploader";
9
+
10
+ type MultiFileUploaderProps = {
11
+ files: File[];
12
+ setFiles: (files: File[]) => void;
13
+ };
14
+
15
+ const dropzone = {
16
+ multiple: false,
17
+ maxSize: 100 * 1024 * 1024,
18
+ preventDropOnDocument: false,
19
+ accept: {
20
+ "application/images": [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".docx", ".xslx", ".pdf", ".txt", ".md"],
21
+ },
22
+ } satisfies DropzoneOptions;
23
+
24
+ export default function MultiFileUploader({ files, setFiles }: MultiFileUploaderProps) {
25
+ const uploadFiles = (newFiles: File[] | null) => {
26
+ if (!newFiles) return;
27
+
28
+ setFiles([...files, ...newFiles]);
29
+ };
30
+
31
+ const handleRemoveFile = (indexToRemove: number) => {
32
+ setFiles(files.filter((_, index) => index !== indexToRemove));
33
+ };
34
+
35
+ return (
36
+ <div className="grid w-full grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6">
37
+ <FileUploader
38
+ value={files}
39
+ onValueChange={uploadFiles}
40
+ dropzoneOptions={dropzone}
41
+ className="text-muted-foreground hover:text-primary relative aspect-square h-full cursor-pointer overflow-hidden rounded-lg border"
42
+ >
43
+ <FileInput className={cn("text-muted-foreground flex h-full w-full flex-col items-center justify-center")}>
44
+ <UploadIcon className="my-4 h-8 w-8" />
45
+ </FileInput>
46
+ </FileUploader>
47
+ {files.map((file, index) => (
48
+ <FilePreviewItem key={file.name + "-" + index} file={file} onRemoveClick={() => handleRemoveFile(index)} />
49
+ ))}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ const FilePreviewItem = ({ file, onRemoveClick }: { file: File; onRemoveClick: () => void }) => {
55
+ const [objectUrl, setObjectUrl] = useState<string | null>(null);
56
+
57
+ useEffect(() => {
58
+ if (file.type.startsWith("image/")) {
59
+ const url = URL.createObjectURL(file);
60
+ setObjectUrl(url);
61
+ return () => URL.revokeObjectURL(url);
62
+ }
63
+ setObjectUrl(null);
64
+ }, [file]);
65
+
66
+ const getFileIcon = () => {
67
+ const extension = file.name.split(".").pop()?.toLowerCase();
68
+ if (file.type.startsWith("image/")) {
69
+ return <FileImageIcon className="text-muted h-10 w-10 sm:h-12 sm:w-12" />;
70
+ }
71
+ if (file.type === "application/pdf") {
72
+ return <FileTextIcon className="text-destructive h-10 w-10 sm:h-12 sm:w-12" />;
73
+ }
74
+ if (file.type.includes("wordprocessingml") || extension === "docx" || extension === "doc") {
75
+ return <FileTextIcon className="text-primary h-10 w-10 sm:h-12 sm:w-12" />;
76
+ }
77
+ if (file.type.includes("spreadsheetml") || extension === "xlsx" || extension === "xls") {
78
+ return <FileSpreadsheetIcon className="text-secondary h-10 w-10 sm:h-12 sm:w-12" />;
79
+ }
80
+ if (file.type.startsWith("text/")) {
81
+ return <FileTextIcon className="h-10 w-10 sm:h-12 sm:w-12" />;
82
+ }
83
+ return <FileIcon className="text-muted h-10 w-10 sm:h-12 sm:w-12" />;
84
+ };
85
+
86
+ return (
87
+ <div className="text-muted-foreground group relative aspect-square h-full overflow-hidden rounded-lg border">
88
+ <button
89
+ onClick={onRemoveClick}
90
+ className="absolute top-1.5 right-1.5 z-20 rounded-full bg-black/60 p-1 text-white opacity-0 transition-all duration-200 ease-in-out group-hover:opacity-100 hover:bg-red-600 focus:outline-none"
91
+ aria-label="Remove file"
92
+ >
93
+ <XIcon className="h-3.5 w-3.5" />
94
+ </button>
95
+ {objectUrl ? (
96
+ <Image src={objectUrl} alt={file.name} className="h-full w-full object-cover" width={200} height={200} />
97
+ ) : (
98
+ <div className="bg-muted/30 flex h-full w-full flex-col items-center justify-center p-2">
99
+ {getFileIcon()}
100
+ <span className="text-foreground/80 mt-2 w-full truncate px-1 text-center text-[10px] sm:text-xs">
101
+ {file.name}
102
+ </span>
103
+ </div>
104
+ )}
105
+ </div>
106
+ );
107
+ };
@@ -0,0 +1,47 @@
1
+ import * as React from "react";
2
+
3
+ import { EyeIcon, EyeOffIcon } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { Button, Input } from "../../shadcnui";
6
+ import { cn } from "../../utils";
7
+
8
+ export interface PasswordInputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
9
+
10
+ const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(({ className, type, ...props }, ref) => {
11
+ const [showPassword, setShowPassword] = React.useState(false);
12
+ const disabled = props.value === "" || props.value === undefined || props.disabled;
13
+ const t = useTranslations();
14
+
15
+ return (
16
+ <div className="relative">
17
+ <Input type={showPassword ? "text" : "password"} className={cn("", className)} ref={ref} {...props} />
18
+ <Button
19
+ type="button"
20
+ variant="ghost"
21
+ size="sm"
22
+ className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
23
+ onClick={() => setShowPassword((prev) => !prev)}
24
+ disabled={disabled}
25
+ >
26
+ {showPassword && !disabled ? (
27
+ <EyeIcon className="h-4 w-4" aria-hidden="true" />
28
+ ) : (
29
+ <EyeOffIcon className="h-4 w-4" aria-hidden="true" />
30
+ )}
31
+ <span className="sr-only">{showPassword ? t(`generic.hide_password`) : t(`generic.show_password`)}</span>
32
+ </Button>
33
+
34
+ <style>{`
35
+ .hide-password-toggle::-ms-reveal,
36
+ .hide-password-toggle::-ms-clear {
37
+ visibility: hidden;
38
+ pointer-events: none;
39
+ display: none;
40
+ }
41
+ `}</style>
42
+ </div>
43
+ );
44
+ });
45
+ PasswordInput.displayName = "PasswordInput";
46
+
47
+ export { PasswordInput };
@@ -0,0 +1,21 @@
1
+ export * from "./CommonAssociationForm";
2
+ export * from "./CommonDeleter";
3
+ export * from "./CommonEditorButtons";
4
+ export * from "./CommonEditorHeader";
5
+ export * from "./CommonEditorTrigger";
6
+ export * from "./DatePickerPopover";
7
+ export * from "./DateRangeSelector";
8
+ export * from "./FileUploader";
9
+ export * from "./FormCheckbox";
10
+ export * from "./FormContainerGeneric";
11
+ export * from "./FormDate";
12
+ export * from "./FormDateTime";
13
+ export * from "./FormInput";
14
+ export * from "./FormPassword";
15
+ export * from "./FormPlaceAutocomplete";
16
+ export * from "./FormSelect";
17
+ export * from "./FormSlider";
18
+ export * from "./FormSwitch";
19
+ export * from "./FormTextarea";
20
+ export * from "./MultiFileUploader";
21
+ export * from "./PasswordInput";
@@ -0,0 +1,11 @@
1
+ export * from "./containers";
2
+ export * from "./contents";
3
+ export * from "./details";
4
+ export * from "./editors";
5
+ export * from "./errors";
6
+ export * from "./forms";
7
+ export * from "./navigations";
8
+ export * from "./pages";
9
+ export * from "./tables";
10
+
11
+ export * from "../features/user/components";
@@ -0,0 +1,83 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { Fragment, useState } from "react";
5
+ import { usePageUrlGenerator } from "../../hooks";
6
+ import { BreadcrumbItemData } from "../../interfaces";
7
+ import {
8
+ BreadcrumbEllipsis,
9
+ BreadcrumbItem,
10
+ BreadcrumbList,
11
+ BreadcrumbSeparator,
12
+ DropdownMenu,
13
+ DropdownMenuContent,
14
+ DropdownMenuItem,
15
+ DropdownMenuTrigger,
16
+ Link,
17
+ Breadcrumb as UIBreadcrumb,
18
+ } from "../../shadcnui";
19
+
20
+ type BreadcrumbProps = { items: BreadcrumbItemData[] };
21
+
22
+ const ITEMS_TO_DISPLAY = 3;
23
+
24
+ export function Breadcrumb({ items }: BreadcrumbProps) {
25
+ const generateUrl = usePageUrlGenerator();
26
+ const t = useTranslations();
27
+
28
+ const [open, setOpen] = useState<boolean>(false);
29
+
30
+ return (
31
+ <UIBreadcrumb>
32
+ <BreadcrumbList>
33
+ <BreadcrumbItem>
34
+ <Link href={generateUrl({ page: `/` })}>{t(`generic.home`)}</Link>
35
+ </BreadcrumbItem>
36
+ {items.length > 0 && <BreadcrumbSeparator />}
37
+
38
+ {items.length > ITEMS_TO_DISPLAY ? (
39
+ <>
40
+ <BreadcrumbItem>
41
+ {items[0].href ? <Link href={items[0].href}>{items[0].name}</Link> : <>{items[0].name}</>}
42
+ </BreadcrumbItem>
43
+ <BreadcrumbSeparator />
44
+ <BreadcrumbItem>
45
+ <DropdownMenu open={open} onOpenChange={setOpen}>
46
+ <DropdownMenuTrigger className="flex items-center gap-1" aria-label="Toggle menu">
47
+ <BreadcrumbEllipsis className="h-4 w-4" />
48
+ </DropdownMenuTrigger>
49
+ <DropdownMenuContent align="start">
50
+ {items.slice(1, -ITEMS_TO_DISPLAY + 1).map((item, index) => (
51
+ <DropdownMenuItem key={index}>
52
+ <Link href={item.href ? item.href : "#"}>{item.name}</Link>
53
+ </DropdownMenuItem>
54
+ ))}
55
+ </DropdownMenuContent>
56
+ </DropdownMenu>
57
+ </BreadcrumbItem>
58
+ <BreadcrumbSeparator />
59
+ {items.slice(-ITEMS_TO_DISPLAY + 1).map((item, index) => (
60
+ <Fragment key={index}>
61
+ <BreadcrumbItem>
62
+ {item.href ? <Link href={item.href}>{item.name}</Link> : <>{item.name}</>}
63
+ </BreadcrumbItem>
64
+ {index < items.slice(-ITEMS_TO_DISPLAY + 1).length - 1 && <BreadcrumbSeparator />}
65
+ </Fragment>
66
+ ))}
67
+ </>
68
+ ) : (
69
+ <>
70
+ {items.map((item, index) => (
71
+ <Fragment key={index}>
72
+ <BreadcrumbItem>
73
+ {item.href ? <Link href={item.href}>{item.name}</Link> : <>{item.name}</>}
74
+ </BreadcrumbItem>
75
+ {index < items.length - 1 && <BreadcrumbSeparator />}
76
+ </Fragment>
77
+ ))}
78
+ </>
79
+ )}
80
+ </BreadcrumbList>
81
+ </UIBreadcrumb>
82
+ );
83
+ }
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import { ReactNode, useEffect, useState } from "react";
4
+ import { cn } from "../../utils";
5
+
6
+ type TitleProps = {
7
+ type?: string | string[];
8
+ element?: string;
9
+ functions?: ReactNode;
10
+ className?: string;
11
+ };
12
+
13
+ export function ContentTitle({ type, element, functions, className }: TitleProps) {
14
+ const [clientFunctions, setClientFunctions] = useState<ReactNode>(null);
15
+ const [isClient, setIsClient] = useState(false);
16
+
17
+ // Defer function rendering to client-side only to prevent hydration mismatches
18
+ // caused by Radix UI's dynamic ID generation in Dialog/AlertDialog components
19
+ useEffect(() => {
20
+ setIsClient(true);
21
+ setClientFunctions(functions);
22
+ }, [functions]);
23
+
24
+ if (!element) return null;
25
+
26
+ return (
27
+ <div className={cn(`mb-4 flex w-full flex-col`, className)}>
28
+ {(type || isClient) && (
29
+ <div className="flex flex-row items-center justify-between gap-x-4">
30
+ {type && <div className={`text-muted-foreground text-xl font-light`}>{type}</div>}
31
+ {isClient && clientFunctions && (
32
+ <div className="flex flex-row items-center justify-start">{clientFunctions}</div>
33
+ )}
34
+ </div>
35
+ )}
36
+ <div className={`text-primary w-full text-3xl font-semibold`}>{element}</div>
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { useSharedContext } from "../../contexts/SharedContext";
4
+ import { SidebarTrigger } from "../../shadcnui";
5
+ import { Breadcrumb } from "./Breadcrumb";
6
+
7
+ type HeaderProps = {
8
+ children?: React.ReactNode;
9
+ };
10
+
11
+ export function Header({ children }: HeaderProps) {
12
+ const { breadcrumbs } = useSharedContext();
13
+
14
+ return (
15
+ <header className={`sticky top-0 z-10 flex h-12 flex-col items-center justify-start gap-x-4 border-b`}>
16
+ <div className="bg-sidebar flex h-12 w-full flex-row items-center justify-between pl-2 pr-4">
17
+ <SidebarTrigger aria-label="Toggle sidebar" />
18
+ <div className="flex w-full flex-row items-center justify-start">
19
+ <Breadcrumb items={breadcrumbs} />
20
+ </div>
21
+ <div className="flex w-64 flex-row items-center justify-end gap-x-4 whitespace-nowrap">
22
+ {children ? children : null}
23
+ </div>
24
+ </div>
25
+ </header>
26
+ );
27
+ }
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import { MoonIcon, SunIcon } from "lucide-react";
4
+ import { useTheme } from "next-themes";
5
+ import { Switch } from "../../shadcnui";
6
+
7
+ export function ModeToggleSwitch() {
8
+ const { theme, setTheme } = useTheme();
9
+
10
+ const handleToggle = () => {
11
+ setTheme(theme === "light" ? "dark" : "light");
12
+ };
13
+
14
+ return (
15
+ <div className="flex items-center">
16
+ <Switch checked={theme === "dark"} onCheckedChange={handleToggle} className="relative">
17
+ {theme === "dark" ? (
18
+ <MoonIcon className="text-primary-foreground h-4 w-4" />
19
+ ) : (
20
+ <SunIcon className="text-primary h-4 w-4" />
21
+ )}
22
+ </Switch>
23
+ </div>
24
+ );
25
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import { ChevronDownIcon, ChevronRightIcon } from "lucide-react";
4
+ import { ReactNode, useEffect, useState } from "react";
5
+ import { v4 } from "uuid";
6
+
7
+ type PageSectionProps = {
8
+ children: ReactNode;
9
+ title?: string;
10
+ options?: ReactNode[];
11
+ open?: boolean;
12
+ small?: boolean;
13
+ onToggle?: (isOpen: boolean) => void;
14
+ };
15
+
16
+ export function PageSection({ children, title, options, open, small, onToggle }: PageSectionProps) {
17
+ const [isOpen, setIsOpen] = useState<boolean>(open ?? true);
18
+ const [shouldRender, setShouldRender] = useState<boolean>(open ?? true);
19
+
20
+ useEffect(() => {
21
+ if (onToggle) {
22
+ onToggle(isOpen);
23
+ }
24
+ }, [isOpen]);
25
+
26
+ const toggleOpen = () => setIsOpen(!isOpen);
27
+
28
+ useEffect(() => {
29
+ if (isOpen) {
30
+ setShouldRender(true);
31
+ } else {
32
+ const timer = setTimeout(() => setShouldRender(false), 300);
33
+ return () => clearTimeout(timer);
34
+ }
35
+ }, [isOpen]);
36
+
37
+ return (
38
+ <section
39
+ id={title ? title.toLowerCase().replaceAll(" ", "") : v4()}
40
+ className={`${isOpen ? "mb-4" : "my-0"} flex w-full scroll-mt-40 flex-col`}
41
+ >
42
+ {title && (
43
+ <div
44
+ className={`${isOpen ? "mb-4" : "mb-0"} flex w-full justify-between border-b ${small ? `border-muted` : `border-primary`} pb-1`}
45
+ >
46
+ <div className="flex w-full cursor-pointer items-center justify-start gap-x-2" onClick={toggleOpen}>
47
+ {isOpen ? (
48
+ <ChevronDownIcon className={`text-primary h-4 w-4`} />
49
+ ) : (
50
+ <ChevronRightIcon className="text-primary h-4 w-4" />
51
+ )}
52
+ <h2 className={`flex w-full ${small === true ? `text-sm` : `text-lg`} text-primary font-semibold`}>
53
+ {title}
54
+ </h2>
55
+ </div>
56
+ {options && <div className="flex gap-2">{options}</div>}
57
+ </div>
58
+ )}
59
+ <div className={`overflow-hidden transition-all duration-300 ${isOpen ? "" : "max-h-0"}`}>
60
+ {shouldRender && children}
61
+ </div>
62
+ </section>
63
+ );
64
+ }
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { useAtomValue } from "jotai";
4
+ import { HistoryIcon } from "lucide-react";
5
+ import { useTranslations } from "next-intl";
6
+ import { recentPagesAtom } from "../../atoms";
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuLabel,
12
+ DropdownMenuSeparator,
13
+ DropdownMenuTrigger,
14
+ Link,
15
+ useSidebar,
16
+ } from "../../shadcnui";
17
+
18
+ export function RecentPagesNavigator() {
19
+ const recentPages = useAtomValue(recentPagesAtom);
20
+ const t = useTranslations();
21
+ const { state } = useSidebar();
22
+
23
+ if (recentPages.length === 0) {
24
+ return null;
25
+ }
26
+
27
+ return (
28
+ <DropdownMenu>
29
+ <DropdownMenuTrigger asChild>
30
+ <div className="flex w-full cursor-pointer items-center gap-2">
31
+ {state === "collapsed" ? <HistoryIcon className="h-4 w-4" /> : <span>{t(`generic.recent_pages`)}</span>}
32
+ </div>
33
+ </DropdownMenuTrigger>
34
+ <DropdownMenuContent align="start" className="w-96">
35
+ <DropdownMenuLabel>{t(`generic.recent_pages`)}</DropdownMenuLabel>
36
+ <DropdownMenuSeparator />
37
+ {recentPages.map((page, index) => (
38
+ <DropdownMenuItem key={`${page.url}-${index}`} asChild>
39
+ <Link href={page.url} className="flex items-center gap-2">
40
+ <div className="flex flex-col">
41
+ <div className="truncate text-sm">{page.title}</div>
42
+ <div className="text-muted-foreground text-xs font-normal">
43
+ {t(`types.${page.moduleType}`, { count: 1 })}
44
+ </div>
45
+ </div>
46
+ </Link>
47
+ </DropdownMenuItem>
48
+ ))}
49
+ </DropdownMenuContent>
50
+ </DropdownMenu>
51
+ );
52
+ }