@carlonicora/nextjs-jsonapi 1.0.4 → 1.0.5

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 (282) hide show
  1. package/dist/{AbstractService-BKlpJA61.d.mts → AbstractService-B2n_JdiC.d.mts} +1 -1
  2. package/dist/{AbstractService-D9eSVKNa.d.ts → AbstractService-DtQTYovo.d.ts} +1 -1
  3. package/dist/{content.interface-Dg2lt_An.d.mts → AuthComponent-CPLvEerw.d.mts} +11 -15
  4. package/dist/{content.interface-BhyAiOFq.d.ts → AuthComponent-m6Qp4Hz6.d.ts} +11 -15
  5. package/dist/{BlockNoteEditor-UVO3VZZE.mjs → BlockNoteEditor-BLVXQPXV.mjs} +14 -18
  6. package/dist/{BlockNoteEditor-UVO3VZZE.mjs.map → BlockNoteEditor-BLVXQPXV.mjs.map} +1 -1
  7. package/dist/{BlockNoteEditor-VFWG6LXI.js → BlockNoteEditor-ZTDHULFT.js} +15 -19
  8. package/dist/BlockNoteEditor-ZTDHULFT.js.map +1 -0
  9. package/dist/JsonApiRequest-O7BGUMFO.mjs +23 -0
  10. package/dist/JsonApiRequest-VARLNKAF.js +23 -0
  11. package/dist/JsonApiRequest-VARLNKAF.js.map +1 -0
  12. package/dist/chunk-2LM6LCJW.mjs +1 -0
  13. package/dist/chunk-3APORDYP.mjs +7783 -0
  14. package/dist/chunk-3APORDYP.mjs.map +1 -0
  15. package/dist/{chunk-TMVHSY3Y.js → chunk-5ZEADNNP.js} +36 -17
  16. package/dist/chunk-5ZEADNNP.js.map +1 -0
  17. package/dist/{chunk-ECDTZBYO.mjs → chunk-74F6BBHH.mjs} +21 -2
  18. package/dist/chunk-74F6BBHH.mjs.map +1 -0
  19. package/dist/{chunk-GYWPEPOH.mjs → chunk-7C5RAEBO.mjs} +72 -68
  20. package/dist/chunk-7C5RAEBO.mjs.map +1 -0
  21. package/dist/chunk-A5DDIABK.js +1 -0
  22. package/dist/{chunk-TEGF6ZWG.js → chunk-AGAJMJ4T.js} +47 -9
  23. package/dist/chunk-AGAJMJ4T.js.map +1 -0
  24. package/dist/{chunk-CXQOWQSY.js → chunk-AYHKQWHH.js} +15 -2
  25. package/dist/chunk-AYHKQWHH.js.map +1 -0
  26. package/dist/{chunk-I2REI7OA.js → chunk-HMHGLXWC.js} +33 -15
  27. package/dist/chunk-HMHGLXWC.js.map +1 -0
  28. package/dist/chunk-IWFGEPAA.mjs +1 -0
  29. package/dist/chunk-JC3WJK65.js +1 -0
  30. package/dist/{chunk-L6EQEAXU.mjs → chunk-PYF2U6WG.mjs} +25 -7
  31. package/dist/chunk-PYF2U6WG.mjs.map +1 -0
  32. package/dist/{chunk-YDVTFM7X.mjs → chunk-RBIVEH2K.mjs} +42 -4
  33. package/dist/chunk-RBIVEH2K.mjs.map +1 -0
  34. package/dist/{chunk-V2JJPI7N.js → chunk-RZO2LOW4.js} +237 -233
  35. package/dist/chunk-RZO2LOW4.js.map +1 -0
  36. package/dist/{chunk-X4BIHJ2B.mjs → chunk-SM63SZCP.mjs} +15 -2
  37. package/dist/chunk-SM63SZCP.mjs.map +1 -0
  38. package/dist/chunk-WEC4YMOS.js +7783 -0
  39. package/dist/chunk-WEC4YMOS.js.map +1 -0
  40. package/dist/client/index.d.mts +21 -2
  41. package/dist/client/index.d.ts +21 -2
  42. package/dist/client/index.js +18 -245
  43. package/dist/client/index.js.map +1 -1
  44. package/dist/client/index.mjs +28 -255
  45. package/dist/client/index.mjs.map +1 -1
  46. package/dist/components/index.d.mts +293 -8
  47. package/dist/components/index.d.ts +293 -8
  48. package/dist/components/index.js +78 -2323
  49. package/dist/components/index.js.map +1 -1
  50. package/dist/components/index.mjs +172 -2417
  51. package/dist/components/index.mjs.map +1 -1
  52. package/dist/{config-hXufftVS.d.ts → config-BmnK65TD.d.mts} +1 -0
  53. package/dist/{config-hXufftVS.d.mts → config-BmnK65TD.d.ts} +1 -0
  54. package/dist/config-DQeAo9Kf.d.mts +49 -0
  55. package/dist/config-DQeAo9Kf.d.ts +49 -0
  56. package/dist/contexts/index.d.mts +109 -21
  57. package/dist/contexts/index.d.ts +109 -21
  58. package/dist/contexts/index.js +39 -7
  59. package/dist/contexts/index.js.map +1 -1
  60. package/dist/contexts/index.mjs +40 -8
  61. package/dist/core/index.d.mts +3 -4
  62. package/dist/core/index.d.ts +3 -4
  63. package/dist/core/index.js +3 -7
  64. package/dist/core/index.js.map +1 -1
  65. package/dist/core/index.mjs +4 -8
  66. package/dist/{d3.link.interface-QMdB22bC.d.mts → d3.link.interface-ClC4Irqp.d.mts} +2 -1
  67. package/dist/{d3.link.interface-QMdB22bC.d.ts → d3.link.interface-ClC4Irqp.d.ts} +2 -1
  68. package/dist/features/index.d.mts +17 -86
  69. package/dist/features/index.d.ts +17 -86
  70. package/dist/features/index.js +7 -16
  71. package/dist/features/index.js.map +1 -1
  72. package/dist/features/index.mjs +10 -19
  73. package/dist/hooks/index.d.mts +18 -43
  74. package/dist/hooks/index.d.ts +18 -43
  75. package/dist/hooks/index.js +20 -7
  76. package/dist/hooks/index.js.map +1 -1
  77. package/dist/hooks/index.mjs +19 -6
  78. package/dist/index.d.mts +10 -6
  79. package/dist/index.d.ts +10 -6
  80. package/dist/index.js +13 -10
  81. package/dist/index.js.map +1 -1
  82. package/dist/index.mjs +22 -19
  83. package/dist/interfaces/index.d.mts +2 -1
  84. package/dist/interfaces/index.d.ts +2 -1
  85. package/dist/notification.interface-BBgMUdLR.d.mts +14 -0
  86. package/dist/notification.interface-gyvT-Z2F.d.ts +14 -0
  87. package/dist/permissions/index.d.mts +2 -3
  88. package/dist/permissions/index.d.ts +2 -3
  89. package/dist/server/index.d.mts +38 -18
  90. package/dist/server/index.d.ts +38 -18
  91. package/dist/server/index.js +70 -2
  92. package/dist/server/index.js.map +1 -1
  93. package/dist/server/index.mjs +68 -0
  94. package/dist/server/index.mjs.map +1 -1
  95. package/dist/types-BUAlgqqh.d.ts +39 -0
  96. package/dist/{types-DluCaP1I.d.ts → types-Bl61ob-7.d.mts} +19 -2
  97. package/dist/{types-lQVA8d_P.d.mts → types-Bl61ob-7.d.ts} +19 -2
  98. package/dist/types-iVdVY7ba.d.mts +39 -0
  99. package/dist/useSocket-Cn7fB_B1.d.mts +25 -0
  100. package/dist/useSocket-DzMKRKCA.d.ts +25 -0
  101. package/dist/user.fields-CbdObSmS.d.mts +18 -0
  102. package/dist/user.fields-CbdObSmS.d.ts +18 -0
  103. package/dist/utils/index.d.mts +1 -2
  104. package/dist/utils/index.d.ts +1 -2
  105. package/package.json +5 -3
  106. package/src/client/index.ts +13 -0
  107. package/src/components/forms/index.ts +1 -0
  108. package/src/components/index.ts +5 -0
  109. package/src/components/tables/ContentListTable.tsx +1 -0
  110. package/src/contexts/CommonContext.tsx +52 -0
  111. package/src/contexts/SharedContext.tsx +2 -0
  112. package/src/contexts/SocketContext.tsx +65 -0
  113. package/src/contexts/index.ts +6 -1
  114. package/src/features/auth/components/containers/AuthContainer.tsx +32 -0
  115. package/src/features/auth/components/containers/index.ts +1 -0
  116. package/src/features/auth/components/details/LandingComponent.tsx +39 -0
  117. package/src/features/auth/components/details/index.ts +1 -0
  118. package/src/features/auth/components/forms/AcceptInvitation.tsx +136 -0
  119. package/src/features/auth/components/forms/ActivateAccount.tsx +75 -0
  120. package/src/features/auth/components/forms/Cookies.tsx +32 -0
  121. package/src/features/auth/components/forms/ForgotPassword.tsx +108 -0
  122. package/src/features/auth/components/forms/Login.tsx +118 -0
  123. package/src/features/auth/components/forms/Logout.tsx +19 -0
  124. package/src/features/auth/components/forms/RefreshUser.tsx +39 -0
  125. package/src/features/auth/components/forms/Register.tsx +150 -0
  126. package/src/features/auth/components/forms/ResetPassword.tsx +126 -0
  127. package/src/features/auth/components/forms/index.ts +9 -0
  128. package/src/features/auth/components/index.ts +3 -0
  129. package/src/features/auth/contexts/AuthContext.tsx +77 -0
  130. package/src/features/auth/contexts/index.ts +1 -0
  131. package/src/features/auth/enums/AuthComponent.ts +9 -0
  132. package/src/features/auth/enums/index.ts +1 -0
  133. package/src/features/auth/index.ts +2 -1
  134. package/src/features/auth/utils/AuthCookies.ts +134 -0
  135. package/src/features/auth/utils/index.ts +1 -0
  136. package/src/features/company/components/containers/AdminCompanyContainer.tsx +26 -0
  137. package/src/features/company/components/containers/CompanyContainer.tsx +17 -0
  138. package/src/features/company/components/containers/index.ts +2 -0
  139. package/src/features/company/components/details/CompanyDetails.tsx +26 -0
  140. package/src/features/company/components/details/index.ts +1 -0
  141. package/src/features/company/components/forms/CompanyConfigurationEditor.tsx +151 -0
  142. package/src/features/company/components/forms/CompanyConfigurationSecurityForm.tsx +97 -0
  143. package/src/features/company/components/forms/CompanyDeleter.tsx +121 -0
  144. package/src/features/company/components/forms/CompanyEditor.tsx +245 -0
  145. package/src/features/company/components/forms/CompanyLicense.tsx +213 -0
  146. package/src/features/company/components/forms/index.ts +5 -0
  147. package/src/features/company/components/index.ts +4 -0
  148. package/src/features/company/components/lists/CompaniesList.tsx +31 -0
  149. package/src/features/company/components/lists/index.ts +1 -0
  150. package/src/features/company/contexts/CompanyContext.tsx +99 -0
  151. package/src/features/company/contexts/index.ts +0 -0
  152. package/src/features/company/hooks/index.ts +1 -0
  153. package/src/features/company/hooks/useCompanyTableStructure.tsx +82 -0
  154. package/src/features/feature/components/forms/FormFeatures.tsx +141 -140
  155. package/src/features/feature/components/forms/index.ts +1 -0
  156. package/src/features/feature/components/index.ts +1 -1
  157. package/src/features/feature/index.ts +1 -2
  158. package/src/features/module/index.ts +1 -1
  159. package/src/features/notification/components/common/NotificationErrorBoundary.tsx +51 -0
  160. package/src/features/notification/components/common/index.ts +1 -0
  161. package/src/features/notification/components/containers/NotificationsListContainer.tsx +44 -0
  162. package/src/features/notification/components/containers/index.ts +1 -0
  163. package/src/features/notification/components/index.ts +5 -0
  164. package/src/features/notification/components/lists/NotificationsList.tsx +129 -0
  165. package/src/features/notification/components/lists/index.ts +1 -0
  166. package/src/features/notification/components/modals/NotificationModal.tsx +220 -0
  167. package/src/features/notification/components/modals/index.ts +1 -0
  168. package/src/features/notification/components/notifications/Notification.tsx +120 -0
  169. package/src/features/notification/components/notifications/PushNotificationProvider.tsx +9 -0
  170. package/src/features/notification/components/notifications/index.ts +2 -0
  171. package/src/features/notification/contexts/NotificationContext.tsx +187 -0
  172. package/src/features/notification/contexts/index.ts +1 -0
  173. package/src/features/notification/index.ts +1 -1
  174. package/src/features/push/index.ts +1 -1
  175. package/src/features/role/components/containers/RoleContainer.tsx +18 -0
  176. package/src/features/role/components/containers/index.ts +1 -0
  177. package/src/features/role/components/details/RoleDetails.tsx +21 -0
  178. package/src/features/role/components/details/index.ts +1 -0
  179. package/src/features/role/components/forms/FormRoles.tsx +82 -0
  180. package/src/features/role/components/forms/RemoveUserFromRole.tsx +108 -0
  181. package/src/features/role/components/forms/UserRoleAdd.tsx +128 -0
  182. package/src/features/role/components/forms/index.ts +3 -0
  183. package/src/features/role/components/index.ts +4 -0
  184. package/src/features/role/components/lists/RolesList.tsx +27 -0
  185. package/src/features/role/components/lists/UserRolesList.tsx +31 -0
  186. package/src/features/role/components/lists/index.ts +2 -0
  187. package/src/features/role/contexts/RoleContext.tsx +84 -0
  188. package/src/features/role/contexts/index.ts +1 -0
  189. package/src/features/role/hooks/index.ts +1 -0
  190. package/src/features/role/hooks/useRoleTableStructure.tsx +72 -0
  191. package/src/features/s3/index.ts +1 -1
  192. package/src/features/user/components/containers/UserContainer.tsx +23 -0
  193. package/src/features/user/components/containers/UserIndexContainer.tsx +12 -0
  194. package/src/features/user/components/containers/UsersListContainer.tsx +36 -0
  195. package/src/features/user/components/containers/index.ts +3 -0
  196. package/src/features/user/components/details/UserDetails.tsx +74 -0
  197. package/src/features/user/components/details/UserIndexDetails.tsx +28 -0
  198. package/src/features/user/components/details/index.ts +2 -0
  199. package/src/features/user/components/forms/RoleUserAdd.tsx +93 -0
  200. package/src/features/user/components/forms/UserAvatarEditor.tsx +78 -0
  201. package/src/features/user/components/forms/UserDeleter.tsx +49 -0
  202. package/src/features/user/components/forms/UserEditor.tsx +319 -0
  203. package/src/features/user/components/forms/UserMultiSelect.tsx +218 -0
  204. package/src/features/user/components/forms/UserReactivator.tsx +79 -0
  205. package/src/features/user/components/forms/UserResentInvitationEmail.tsx +88 -0
  206. package/src/features/user/components/forms/UserSelector.tsx +185 -0
  207. package/src/features/user/components/forms/index.ts +8 -0
  208. package/src/features/user/components/index.ts +3 -0
  209. package/src/features/user/components/lists/AdminUsersList.tsx +41 -0
  210. package/src/features/user/components/lists/CompanyUsersList.tsx +44 -0
  211. package/src/features/user/components/lists/RelevantUsersList.tsx +30 -0
  212. package/src/features/user/components/lists/RoleUsersList.tsx +31 -0
  213. package/src/features/user/components/lists/UserListInAdd.tsx +53 -0
  214. package/src/features/user/components/lists/UsersList.tsx +30 -0
  215. package/src/features/user/components/lists/UsersListByContentIds.tsx +30 -0
  216. package/src/features/user/components/lists/index.ts +7 -0
  217. package/src/features/user/components/widgets/UserAvatarList.tsx +31 -0
  218. package/src/features/user/components/widgets/UserSearchPopover.tsx +89 -0
  219. package/src/features/user/contexts/UserContext.tsx +106 -0
  220. package/src/features/user/contexts/index.ts +1 -0
  221. package/src/features/user/hooks/index.ts +2 -0
  222. package/src/features/user/hooks/useUserSearch.ts +53 -0
  223. package/src/features/user/hooks/useUserTableStructure.tsx +115 -0
  224. package/src/features/user/index.ts +0 -1
  225. package/src/hooks/index.ts +4 -0
  226. package/src/hooks/useCustomD3Graph.tsx +2 -0
  227. package/src/hooks/useNotificationSync.ts +20 -0
  228. package/src/hooks/usePageTracker.ts +69 -0
  229. package/src/hooks/usePushNotifications.ts +82 -0
  230. package/src/hooks/useSocket.ts +201 -0
  231. package/src/hooks/useTableGenerator.ts +6 -2
  232. package/src/i18n/config.ts +1 -0
  233. package/src/index.ts +4 -0
  234. package/src/interfaces/d3.link.interface.ts +2 -1
  235. package/src/server/ServerSession.ts +103 -0
  236. package/src/server/index.ts +2 -1
  237. package/src/unified/JsonApiRequest.ts +23 -0
  238. package/dist/ApiRequestDataTypeInterface-CUKFDBx2.d.mts +0 -20
  239. package/dist/ApiRequestDataTypeInterface-CUKFDBx2.d.ts +0 -20
  240. package/dist/BlockNoteEditor-VFWG6LXI.js.map +0 -1
  241. package/dist/JsonApiRequest-S3ICLM7B.mjs +0 -20
  242. package/dist/JsonApiRequest-ZZLSP26T.js +0 -20
  243. package/dist/JsonApiRequest-ZZLSP26T.js.map +0 -1
  244. package/dist/chunk-366S2JCC.mjs +0 -31
  245. package/dist/chunk-366S2JCC.mjs.map +0 -1
  246. package/dist/chunk-5W6AKZE6.mjs +0 -131
  247. package/dist/chunk-5W6AKZE6.mjs.map +0 -1
  248. package/dist/chunk-A3J3AAYM.mjs +0 -97
  249. package/dist/chunk-A3J3AAYM.mjs.map +0 -1
  250. package/dist/chunk-AWONBQQP.js +0 -97
  251. package/dist/chunk-AWONBQQP.js.map +0 -1
  252. package/dist/chunk-CXQOWQSY.js.map +0 -1
  253. package/dist/chunk-DKKMWBP4.mjs +0 -1
  254. package/dist/chunk-DKKMWBP4.mjs.map +0 -1
  255. package/dist/chunk-DO2HLAZO.js +0 -48
  256. package/dist/chunk-DO2HLAZO.js.map +0 -1
  257. package/dist/chunk-DZXDB3K2.mjs +0 -17
  258. package/dist/chunk-DZXDB3K2.mjs.map +0 -1
  259. package/dist/chunk-ECDTZBYO.mjs.map +0 -1
  260. package/dist/chunk-FY4SXJGU.js +0 -806
  261. package/dist/chunk-FY4SXJGU.js.map +0 -1
  262. package/dist/chunk-GYWPEPOH.mjs.map +0 -1
  263. package/dist/chunk-H6FMOA6B.js +0 -1
  264. package/dist/chunk-H6FMOA6B.js.map +0 -1
  265. package/dist/chunk-I2REI7OA.js.map +0 -1
  266. package/dist/chunk-J4Q36PMP.js +0 -31
  267. package/dist/chunk-J4Q36PMP.js.map +0 -1
  268. package/dist/chunk-L6EQEAXU.mjs.map +0 -1
  269. package/dist/chunk-MFO27OHB.mjs +0 -48
  270. package/dist/chunk-MFO27OHB.mjs.map +0 -1
  271. package/dist/chunk-RAF7PNLG.js +0 -131
  272. package/dist/chunk-RAF7PNLG.js.map +0 -1
  273. package/dist/chunk-RUR22SVM.js +0 -17
  274. package/dist/chunk-RUR22SVM.js.map +0 -1
  275. package/dist/chunk-TEGF6ZWG.js.map +0 -1
  276. package/dist/chunk-TMVHSY3Y.js.map +0 -1
  277. package/dist/chunk-V2JJPI7N.js.map +0 -1
  278. package/dist/chunk-WWWMJZEF.mjs +0 -806
  279. package/dist/chunk-WWWMJZEF.mjs.map +0 -1
  280. package/dist/chunk-X4BIHJ2B.mjs.map +0 -1
  281. package/dist/chunk-YDVTFM7X.mjs.map +0 -1
  282. /package/dist/{JsonApiRequest-S3ICLM7B.mjs.map → JsonApiRequest-O7BGUMFO.mjs.map} +0 -0
@@ -0,0 +1,218 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { useWatch } from "react-hook-form";
5
+ import { Modules } from "../../../../core";
6
+ import { DataListRetriever, useDataListRetriever, useDebounce } from "../../../../hooks";
7
+ import {
8
+ Avatar,
9
+ AvatarFallback,
10
+ AvatarImage,
11
+ FormControl,
12
+ FormField,
13
+ FormItem,
14
+ FormLabel,
15
+ FormMessage,
16
+ MultiSelect,
17
+ } from "../../../../shadcnui";
18
+ import { useCurrentUserContext } from "../../contexts";
19
+ import { UserInterface, UserService } from "../../data";
20
+
21
+ // Type for user objects in the form
22
+ type UserSelectType = {
23
+ id: string;
24
+ name: string;
25
+ avatar?: string;
26
+ };
27
+
28
+ type UserMultiSelectProps = {
29
+ id: string;
30
+ form: any;
31
+ currentUser?: UserInterface;
32
+ label?: string;
33
+ placeholder?: string;
34
+ onChange?: (users?: UserInterface[]) => void;
35
+ maxCount?: number;
36
+ isRequired?: boolean;
37
+ };
38
+
39
+ function UserAvatarIcon({ className, url, name }: { className?: string; url?: string; name?: string }) {
40
+ return (
41
+ <Avatar className={`${className || "h-4 w-4"}`}>
42
+ <AvatarImage src={url} />
43
+ <AvatarFallback>
44
+ {name
45
+ ? name
46
+ .split(" ")
47
+ .map((n: string) => n.charAt(0).toUpperCase())
48
+ .join("")
49
+ : "U"}
50
+ </AvatarFallback>
51
+ </Avatar>
52
+ );
53
+ }
54
+
55
+ export function UserMultiSelect({
56
+ id,
57
+ form,
58
+ currentUser,
59
+ label,
60
+ placeholder,
61
+ onChange,
62
+ maxCount = 3,
63
+ isRequired = false,
64
+ }: UserMultiSelectProps) {
65
+ const { company } = useCurrentUserContext<UserInterface>();
66
+
67
+ const searchTermRef = useRef<string>("");
68
+ const [searchTerm, setSearchTerm] = useState<string>("");
69
+ const [isSearching, setIsSearching] = useState<boolean>(false);
70
+ const [userOptions, setUserOptions] = useState<any[]>([]);
71
+
72
+ // Get the current selected users from the form
73
+ const selectedUsers: UserSelectType[] = useWatch({ control: form.control, name: id }) || [];
74
+
75
+ const data: DataListRetriever<UserInterface> = useDataListRetriever({
76
+ ready: !!company,
77
+ retriever: (params) => {
78
+ return UserService.findAllUsers(params);
79
+ },
80
+ retrieverParams: { companyId: company?.id },
81
+ module: Modules.User,
82
+ }) as DataListRetriever<UserInterface>;
83
+
84
+ useEffect(() => {
85
+ if (company) data.setReady(true);
86
+ }, [company]);
87
+
88
+ const search = useCallback(
89
+ async (searchedTerm: string) => {
90
+ try {
91
+ if (searchedTerm === searchTermRef.current) return;
92
+ setIsSearching(true);
93
+ searchTermRef.current = searchedTerm;
94
+ await data.search(searchedTerm);
95
+ } finally {
96
+ setIsSearching(false);
97
+ }
98
+ },
99
+ [searchTermRef, data],
100
+ );
101
+
102
+ const updateSearchTerm = useDebounce(search, 500);
103
+
104
+ useEffect(() => {
105
+ setIsSearching(true);
106
+ updateSearchTerm(searchTerm);
107
+ }, [updateSearchTerm, searchTerm]);
108
+
109
+ // Update userOptions when data changes or when initial selected users are available
110
+ useEffect(() => {
111
+ if (data.data && data.data.length > 0) {
112
+ const users = data.data as UserInterface[];
113
+ const filteredUsers = users.filter((user) => user.id !== currentUser?.id);
114
+
115
+ const options = filteredUsers.map((user) => ({
116
+ label: user.name,
117
+ value: user.id,
118
+ icon: ({ className }: { className?: string }) => (
119
+ <UserAvatarIcon className={className} url={user.avatar} name={user.name} />
120
+ ),
121
+ userData: user,
122
+ }));
123
+
124
+ setUserOptions(options);
125
+ }
126
+ }, [data.data, currentUser]);
127
+
128
+ // Add options for any already selected users that aren't in search results
129
+ useEffect(() => {
130
+ if (selectedUsers.length > 0) {
131
+ // Create a map of existing option IDs for quick lookup
132
+ const existingOptionIds = new Set(userOptions.map((option) => option.value));
133
+
134
+ // Find selected users that don't have an option yet
135
+ const missingOptions = selectedUsers
136
+ .filter((user) => !existingOptionIds.has(user.id))
137
+ .map((user) => ({
138
+ label: user.name,
139
+ value: user.id,
140
+ icon: ({ className }: { className?: string }) => (
141
+ <UserAvatarIcon className={className} url={user.avatar} name={user.name} />
142
+ ),
143
+ userData: user,
144
+ }));
145
+
146
+ // Add missing options if there are any
147
+ if (missingOptions.length > 0) {
148
+ setUserOptions((prev) => [...prev, ...missingOptions]);
149
+ }
150
+ }
151
+ }, [selectedUsers, userOptions]);
152
+
153
+ const handleValueChange = (selectedIds: string[]) => {
154
+ // Map selected IDs to user objects for the form
155
+ const updatedSelectedUsers = selectedIds.map((id) => {
156
+ // First check if user is already in the selected users (preserve existing data)
157
+ const existingUser = selectedUsers.find((user) => user.id === id);
158
+ if (existingUser) {
159
+ return existingUser;
160
+ }
161
+
162
+ // Otherwise, get user data from the options
163
+ const option = userOptions.find((option) => option.value === id);
164
+ if (option?.userData) {
165
+ return {
166
+ id: option.userData.id,
167
+ name: option.userData.name,
168
+ avatar: option.userData.avatar,
169
+ };
170
+ }
171
+
172
+ // Fallback to just the ID if no data is available
173
+ return { id, name: id };
174
+ });
175
+
176
+ form.setValue(id, updatedSelectedUsers);
177
+
178
+ if (onChange) {
179
+ const fullSelectedUsers = selectedIds
180
+ .map((id) => userOptions.find((option) => option.value === id)?.userData)
181
+ .filter(Boolean) as UserInterface[];
182
+ onChange(fullSelectedUsers);
183
+ }
184
+ };
185
+
186
+ // Extract just the IDs for the MultiSelect component
187
+ const selectedUserIds = selectedUsers.map((user: UserSelectType) => user.id);
188
+
189
+ return (
190
+ <div className="flex w-full flex-col">
191
+ <FormField
192
+ control={form.control}
193
+ name={id}
194
+ render={({ field }) => (
195
+ <FormItem className={`${label ? "mb-5" : "mb-1"}`}>
196
+ {label && (
197
+ <FormLabel className="flex items-center">
198
+ {label}
199
+ {isRequired && <span className="text-destructive ml-2 font-semibold">*</span>}
200
+ </FormLabel>
201
+ )}
202
+ <FormControl>
203
+ <MultiSelect
204
+ options={userOptions}
205
+ onValueChange={handleValueChange}
206
+ defaultValue={selectedUserIds}
207
+ placeholder={placeholder}
208
+ maxCount={maxCount}
209
+ animation={0}
210
+ />
211
+ </FormControl>
212
+ <FormMessage />
213
+ </FormItem>
214
+ )}
215
+ />
216
+ </div>
217
+ );
218
+ }
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ import { UserCheckIcon } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { useState } from "react";
6
+ import { errorToast } from "../../../../components";
7
+ import { Modules } from "../../../../core";
8
+ import { Action } from "../../../../permissions";
9
+ import {
10
+ Button,
11
+ Dialog,
12
+ DialogContent,
13
+ DialogDescription,
14
+ DialogHeader,
15
+ DialogTitle,
16
+ DialogTrigger,
17
+ } from "../../../../shadcnui";
18
+ import { useCurrentUserContext } from "../../contexts";
19
+ import { UserInterface, UserService } from "../../data";
20
+
21
+ type UserReactivatorProps = {
22
+ user: UserInterface;
23
+ propagateChanges: (user: UserInterface) => void;
24
+ };
25
+
26
+ function UserReactivatorInterface({ user, propagateChanges }: UserReactivatorProps) {
27
+ const [open, setOpen] = useState<boolean>(false);
28
+ const t = useTranslations();
29
+
30
+ const reactivateUser = async () => {
31
+ try {
32
+ const updatedUser = (await UserService.reactivate({ userId: user.id })) as UserInterface;
33
+
34
+ setOpen(false);
35
+ propagateChanges(updatedUser);
36
+ } catch (error) {
37
+ errorToast({ title: t(`generic.errors.error`), error: error });
38
+ }
39
+ };
40
+
41
+ return (
42
+ <Dialog open={open} onOpenChange={setOpen}>
43
+ <DialogTrigger asChild>
44
+ <Button size="sm">
45
+ <UserCheckIcon className="mr-3 h-3.5 w-3.5" />
46
+ {t(`foundations.user.buttons.reactivate`)}
47
+ </Button>
48
+ </DialogTrigger>
49
+ <DialogContent className={`flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto`}>
50
+ <DialogHeader>
51
+ <DialogTitle>{t(`foundations.user.reactivate.title`)}</DialogTitle>
52
+ <DialogDescription>{t(`foundations.user.reactivate.subtitle`)}</DialogDescription>
53
+ </DialogHeader>
54
+ {t(`foundations.user.reactivate.description`, { name: user.name })}
55
+ <div className="flex justify-end">
56
+ <Button className="mr-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
57
+ {t(`generic.buttons.cancel`)}
58
+ </Button>
59
+ <Button
60
+ type="submit"
61
+ onClick={(e) => {
62
+ e.preventDefault();
63
+ reactivateUser();
64
+ }}
65
+ >
66
+ {t(`foundations.user.buttons.reactivate`)}
67
+ </Button>
68
+ </div>
69
+ </DialogContent>
70
+ </Dialog>
71
+ );
72
+ }
73
+
74
+ export function UserReactivator(props: UserReactivatorProps) {
75
+ const { hasPermissionToModule } = useCurrentUserContext<UserInterface>();
76
+ if (!hasPermissionToModule({ module: Modules.User, action: Action.Update, data: props.user })) return null;
77
+
78
+ return <UserReactivatorInterface {...props} />;
79
+ }
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+ import { MailIcon } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { useState } from "react";
6
+ import { toast } from "sonner";
7
+ import { errorToast } from "../../../../components";
8
+ import { Modules } from "../../../../core";
9
+ import { Action } from "../../../../permissions";
10
+ import {
11
+ Button,
12
+ Dialog,
13
+ DialogContent,
14
+ DialogDescription,
15
+ DialogHeader,
16
+ DialogTitle,
17
+ DialogTrigger,
18
+ Tooltip,
19
+ TooltipContent,
20
+ TooltipTrigger,
21
+ } from "../../../../shadcnui";
22
+ import { useCurrentUserContext } from "../../contexts";
23
+ import { UserInterface, UserService } from "../../data";
24
+
25
+ type UserResentInvitationEmailProps = {
26
+ user: UserInterface;
27
+ };
28
+
29
+ function UserResentInvitationEmailInternal({ user }: UserResentInvitationEmailProps) {
30
+ const [open, setOpen] = useState<boolean>(false);
31
+ const t = useTranslations();
32
+
33
+ const sendInvitationEmail = async () => {
34
+ try {
35
+ await UserService.sendInvitation({ userId: user.id, companyId: user.company!.id });
36
+
37
+ setOpen(false);
38
+ toast.message(t(`foundations.user.resend_activation.email_sent`), {
39
+ description: t(`foundations.user.resend_activation.email_sent_description`, { email: user.email }),
40
+ });
41
+ } catch (error) {
42
+ errorToast({ title: t(`generic.errors.error`), error: error });
43
+ }
44
+ };
45
+
46
+ return (
47
+ <Dialog open={open} onOpenChange={setOpen}>
48
+ <Tooltip>
49
+ <TooltipTrigger asChild>
50
+ <DialogTrigger asChild>
51
+ <Button size="sm" variant={`ghost`} className="text-muted-foreground">
52
+ <MailIcon />
53
+ </Button>
54
+ </DialogTrigger>
55
+ </TooltipTrigger>
56
+ <TooltipContent>{t(`foundations.user.buttons.resend_activation`)}</TooltipContent>
57
+ </Tooltip>
58
+ <DialogContent className={`flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto`}>
59
+ <DialogHeader>
60
+ <DialogTitle>{t(`foundations.user.resend_activation.title`)}</DialogTitle>
61
+ <DialogDescription>{t(`foundations.user.resend_activation.subtitle`)}</DialogDescription>
62
+ </DialogHeader>
63
+ {t(`foundations.user.resend_activation.description`, { email: user.email })}
64
+ <div className="flex justify-end">
65
+ <Button className="mr-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
66
+ {t(`generic.buttons.cancel`)}
67
+ </Button>
68
+ <Button
69
+ type="submit"
70
+ onClick={(e) => {
71
+ e.preventDefault();
72
+ sendInvitationEmail();
73
+ }}
74
+ >
75
+ {t(`foundations.user.buttons.resend_activation`)}
76
+ </Button>
77
+ </div>
78
+ </DialogContent>
79
+ </Dialog>
80
+ );
81
+ }
82
+
83
+ export function UserResentInvitationEmail(props: UserResentInvitationEmailProps) {
84
+ const { hasPermissionToModule } = useCurrentUserContext<UserInterface>();
85
+ if (!hasPermissionToModule({ module: Modules.User, action: Action.Update, data: props.user })) return null;
86
+
87
+ return <UserResentInvitationEmailInternal {...props} />;
88
+ }
@@ -0,0 +1,185 @@
1
+ "use client";
2
+
3
+ import { CircleX, RefreshCwIcon, SearchIcon, XIcon } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { useCallback, useEffect, useRef, useState } from "react";
6
+ import { Modules } from "../../../../core";
7
+ import { DataListRetriever, useDataListRetriever, useDebounce } from "../../../../hooks";
8
+ import {
9
+ Avatar,
10
+ AvatarFallback,
11
+ AvatarImage,
12
+ Command,
13
+ CommandItem,
14
+ CommandList,
15
+ FormControl,
16
+ FormField,
17
+ FormItem,
18
+ FormLabel,
19
+ FormMessage,
20
+ Input,
21
+ Popover,
22
+ PopoverContent,
23
+ PopoverTrigger,
24
+ } from "../../../../shadcnui";
25
+ import { UserInterface, UserService } from "../../data";
26
+ import { UserAvatar } from "../widgets";
27
+
28
+ type UserSelectorProps = {
29
+ id: string;
30
+ form: any;
31
+ label?: string;
32
+ placeholder?: string;
33
+ onChange?: (user?: UserInterface) => void;
34
+ isRequired?: boolean;
35
+ };
36
+
37
+ export function UserSelector({ id, form, label, placeholder, onChange, isRequired = false }: UserSelectorProps) {
38
+ const t = useTranslations();
39
+
40
+ const [open, setOpen] = useState<boolean>(false);
41
+
42
+ const searchTermRef = useRef<string>("");
43
+ const [searchTerm, setSearchTerm] = useState<string>("");
44
+
45
+ const [isSearching, setIsSearching] = useState<boolean>(false);
46
+
47
+ const data: DataListRetriever<UserInterface> = useDataListRetriever({
48
+ retriever: (params) => {
49
+ return UserService.findMany(params);
50
+ },
51
+ retrieverParams: {},
52
+ module: Modules.User,
53
+ });
54
+
55
+ const search = useCallback(
56
+ async (searchedTerm: string) => {
57
+ try {
58
+ if (searchedTerm === searchTermRef.current) return;
59
+ setIsSearching(true);
60
+ searchTermRef.current = searchedTerm;
61
+ await data.search(searchedTerm);
62
+ } finally {
63
+ setIsSearching(false);
64
+ }
65
+ },
66
+ [searchTermRef, data],
67
+ );
68
+
69
+ const updateSearchTerm = useDebounce(search, 500);
70
+
71
+ useEffect(() => {
72
+ setIsSearching(true);
73
+ updateSearchTerm(searchTerm);
74
+ }, [updateSearchTerm, searchTerm]);
75
+
76
+ const setUser = (user?: UserInterface) => {
77
+ if (onChange) onChange(user);
78
+ if (!user) {
79
+ form.setValue(id, undefined);
80
+ setOpen(false);
81
+ return;
82
+ }
83
+
84
+ form.setValue(id, { id: user.id, name: user.name, avatar: user.avatar });
85
+ setOpen(false);
86
+
87
+ setTimeout(() => {
88
+ setOpen(false);
89
+ }, 0);
90
+ };
91
+
92
+ return (
93
+ <div className="flex w-full flex-col">
94
+ <FormField
95
+ control={form.control}
96
+ name={id}
97
+ render={({ field }) => (
98
+ <FormItem className={`${label ? "mb-5" : "mb-1"}`}>
99
+ {label && (
100
+ <FormLabel className="flex items-center">
101
+ {label}
102
+ {isRequired && <span className="text-destructive ml-2 font-semibold">*</span>}
103
+ </FormLabel>
104
+ )}
105
+ <FormControl>
106
+ <Popover open={open} onOpenChange={setOpen} modal={true}>
107
+ <div className="flex w-full flex-row items-center justify-between">
108
+ <PopoverTrigger className="w-full">
109
+ <div className="flex w-full flex-row items-center justify-start rounded-md">
110
+ {field.value ? (
111
+ <>
112
+ <div className="flex w-full flex-row items-center justify-start rounded-md border p-2">
113
+ <div className="*:ring-border *:ring-1">
114
+ <Avatar className={`mr-2 h-6 w-6`}>
115
+ <AvatarImage src={field.value?.avatar} />
116
+ <AvatarFallback>
117
+ {field.value?.name
118
+ ? field.value?.name.split(" ").map((name: string) => name.charAt(0).toUpperCase())
119
+ : "X"}
120
+ </AvatarFallback>
121
+ </Avatar>
122
+ </div>
123
+ <span className="">{field.value?.name ?? ""}</span>
124
+ </div>
125
+ </>
126
+ ) : (
127
+ <div className="text-muted-foreground mr-7 flex h-10 w-full flex-row items-center justify-start rounded-md border p-2 text-sm">
128
+ {placeholder ?? t(`generic.search.placeholder`, { type: t(`types.users`, { count: 1 }) })}
129
+ </div>
130
+ )}
131
+ </div>
132
+ </PopoverTrigger>
133
+ {field.value && (
134
+ <CircleX
135
+ className="text-muted hover:text-destructive ml-2 h-6 w-6 cursor-pointer"
136
+ onClick={() => setUser()}
137
+ />
138
+ )}
139
+ </div>
140
+ <PopoverContent>
141
+ <Command shouldFilter={false}>
142
+ <div className="relative mb-2 w-full">
143
+ <SearchIcon className="text-muted-foreground absolute top-2.5 left-2.5 h-4 w-4" />
144
+ <Input
145
+ placeholder={t(`generic.search.placeholder`, { type: t(`types.users`, { count: 1 }) })}
146
+ type="text"
147
+ className="w-full pr-8 pl-8"
148
+ onChange={(e) => setSearchTerm(e.target.value)}
149
+ value={searchTerm}
150
+ />
151
+ {isSearching ? (
152
+ <RefreshCwIcon className="text-muted-foreground absolute top-2.5 right-2.5 h-4 w-4 animate-spin" />
153
+ ) : searchTermRef.current ? (
154
+ <XIcon
155
+ className={`absolute top-2.5 right-2.5 h-4 w-4 ${searchTermRef.current ? "cursor-pointer" : "text-muted-foreground"}`}
156
+ onClick={() => {
157
+ setSearchTerm("");
158
+ search("");
159
+ }}
160
+ />
161
+ ) : (
162
+ <></>
163
+ )}
164
+ </div>
165
+ <CommandList>
166
+ {data.data &&
167
+ data.data.length > 0 &&
168
+ (data.data as UserInterface[]).map((user: UserInterface) => (
169
+ <CommandItem className="cursor-pointer" key={user.id} onSelect={() => setUser(user)}>
170
+ <UserAvatar user={user} className={`mr-2 h-4 w-4`} />
171
+ <span className="">{user.name}</span>
172
+ </CommandItem>
173
+ ))}
174
+ </CommandList>
175
+ </Command>
176
+ </PopoverContent>
177
+ </Popover>
178
+ </FormControl>
179
+ <FormMessage />
180
+ </FormItem>
181
+ )}
182
+ />
183
+ </div>
184
+ );
185
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./RoleUserAdd";
2
+ export * from "./UserAvatarEditor";
3
+ export * from "./UserDeleter";
4
+ export * from "./UserEditor";
5
+ export * from "./UserMultiSelect";
6
+ export * from "./UserReactivator";
7
+ export * from "./UserResentInvitationEmail";
8
+ export * from "./UserSelector";
@@ -1,2 +1,5 @@
1
+ export * from "./containers";
2
+ export * from "./details";
3
+ export * from "./forms";
1
4
  export * from "./lists";
2
5
  export * from "./widgets";
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { ContentListTable } from "../../../../components";
5
+ import { useCompanyContext } from "../../../../contexts";
6
+ import { Modules } from "../../../../core";
7
+ import { DataListRetriever, useDataListRetriever } from "../../../../hooks";
8
+ import { CompanyInterface } from "../../../company";
9
+ import { UserFields, UserInterface, UserService } from "../../data";
10
+ import { UserEditor } from "../forms";
11
+
12
+ type AdminUsersListProps = {
13
+ company: CompanyInterface;
14
+ };
15
+
16
+ function AdminUsersListInternal({ company }: AdminUsersListProps) {
17
+ const t = useTranslations();
18
+
19
+ const data: DataListRetriever<UserInterface> = useDataListRetriever({
20
+ retriever: (params) => UserService.findManyForAmin(params),
21
+ retrieverParams: { companyId: company.id },
22
+ module: Modules.User,
23
+ });
24
+
25
+ return (
26
+ <ContentListTable
27
+ title={t(`types.users`, { count: 2 })}
28
+ data={data}
29
+ fields={[UserFields.name, UserFields.email, UserFields.createdAt]}
30
+ tableGeneratorType={Modules.User}
31
+ functions={<UserEditor propagateChanges={data.refresh} adminCreated />}
32
+ />
33
+ );
34
+ }
35
+
36
+ export function AdminUsersList() {
37
+ const { company } = useCompanyContext();
38
+ if (!company) return null;
39
+
40
+ return <AdminUsersListInternal company={company} />;
41
+ }
@@ -0,0 +1,44 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { ReactNode, useEffect } from "react";
5
+ import { ContentListTable } from "../../../../components";
6
+ import { Modules } from "../../../../core";
7
+ import { DataListRetriever, useDataListRetriever } from "../../../../hooks";
8
+ import { useCurrentUserContext } from "../../contexts";
9
+ import { UserFields, UserInterface, UserService } from "../../data";
10
+ import { UserEditor } from "../forms";
11
+
12
+ type CompanyUsersListProps = {
13
+ isDeleted?: boolean;
14
+ };
15
+
16
+ export function CompanyUsersList({ isDeleted }: CompanyUsersListProps) {
17
+ const { company } = useCurrentUserContext<UserInterface>();
18
+ const t = useTranslations();
19
+
20
+ const data: DataListRetriever<UserInterface> = useDataListRetriever({
21
+ ready: !!company,
22
+ retriever: (params) => UserService.findAllUsers(params),
23
+ retrieverParams: { companyId: company?.id, isDeleted: isDeleted },
24
+ module: Modules.User,
25
+ }) as DataListRetriever<UserInterface>;
26
+
27
+ useEffect(() => {
28
+ if (company) data.setReady(true);
29
+ }, [company]);
30
+
31
+ const functions: ReactNode[] = [
32
+ isDeleted ? undefined : <UserEditor key="create-user" propagateChanges={data.refresh} />,
33
+ ];
34
+
35
+ return (
36
+ <ContentListTable
37
+ data={data}
38
+ fields={[UserFields.name, UserFields.email]}
39
+ tableGeneratorType={Modules.User}
40
+ functions={functions}
41
+ title={t(`types.users`, { count: 2 })}
42
+ />
43
+ );
44
+ }