@carlonicora/nextjs-jsonapi 1.65.1 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/{AuthComponent-B4rNZRYE.d.ts → AuthComponent-DL1D3y7f.d.ts} +1 -1
  2. package/dist/{AuthComponent-nzabiz68.d.mts → AuthComponent-NwQ_ZXsv.d.mts} +1 -1
  3. package/dist/{BlockNoteEditor-QGNV6E4X.js → BlockNoteEditor-QHWPE3BJ.js} +14 -14
  4. package/dist/{BlockNoteEditor-QGNV6E4X.js.map → BlockNoteEditor-QHWPE3BJ.js.map} +1 -1
  5. package/dist/{BlockNoteEditor-CZOW7J5K.mjs → BlockNoteEditor-TIX3GDVZ.mjs} +4 -4
  6. package/dist/{auth.interface-C1WjZ0fM.d.ts → auth.interface-BX_1qZZJ.d.ts} +1 -1
  7. package/dist/{auth.interface-fBFqIrw4.d.mts → auth.interface-yeLelxdI.d.mts} +1 -1
  8. package/dist/billing/index.js +346 -346
  9. package/dist/billing/index.mjs +3 -3
  10. package/dist/{chunk-CDCGQFIA.js → chunk-3BWYWS3A.js} +2118 -1659
  11. package/dist/chunk-3BWYWS3A.js.map +1 -0
  12. package/dist/{chunk-LRXJT656.js → chunk-CJY63D6U.js} +72 -5
  13. package/dist/chunk-CJY63D6U.js.map +1 -0
  14. package/dist/{chunk-RA4RYKYB.js → chunk-KFIQTY4O.js} +11 -11
  15. package/dist/{chunk-RA4RYKYB.js.map → chunk-KFIQTY4O.js.map} +1 -1
  16. package/dist/{chunk-G7PGWMFO.mjs → chunk-RIG2BEXJ.mjs} +72 -5
  17. package/dist/{chunk-G7PGWMFO.mjs.map → chunk-RIG2BEXJ.mjs.map} +1 -1
  18. package/dist/{chunk-ESGUCYJS.mjs → chunk-WWP32QYC.mjs} +3534 -3075
  19. package/dist/chunk-WWP32QYC.mjs.map +1 -0
  20. package/dist/{chunk-5KMKI23S.mjs → chunk-ZYAAJMZZ.mjs} +2 -2
  21. package/dist/client/index.d.mts +6 -6
  22. package/dist/client/index.d.ts +6 -6
  23. package/dist/client/index.js +4 -4
  24. package/dist/client/index.mjs +3 -3
  25. package/dist/components/index.d.mts +69 -12
  26. package/dist/components/index.d.ts +69 -12
  27. package/dist/components/index.js +18 -4
  28. package/dist/components/index.js.map +1 -1
  29. package/dist/components/index.mjs +19 -5
  30. package/dist/{config-DZWAFB7H.d.ts → config-CyCAWW-d.d.ts} +1 -1
  31. package/dist/{config-ndRJIQsP.d.mts → config-D-mqttuF.d.mts} +1 -1
  32. package/dist/{content.interface-B5ySfiOE.d.mts → content.interface-8T5-G84c.d.mts} +1 -1
  33. package/dist/{content.interface-mmz0uMwm.d.ts → content.interface-D-xdYxjt.d.ts} +1 -1
  34. package/dist/contexts/index.d.mts +3 -3
  35. package/dist/contexts/index.d.ts +3 -3
  36. package/dist/contexts/index.js +4 -4
  37. package/dist/contexts/index.mjs +3 -3
  38. package/dist/core/index.d.mts +29 -9
  39. package/dist/core/index.d.ts +29 -9
  40. package/dist/core/index.js +2 -2
  41. package/dist/core/index.mjs +1 -1
  42. package/dist/index.d.mts +8 -8
  43. package/dist/index.d.ts +8 -8
  44. package/dist/index.js +3 -3
  45. package/dist/index.mjs +2 -2
  46. package/dist/{notification.interface-DG7cq9oG.d.mts → notification.interface-C6UcmJqu.d.mts} +20 -0
  47. package/dist/{notification.interface-COKHDQeE.d.ts → notification.interface-ItBxq2au.d.ts} +20 -0
  48. package/dist/{s3.service-ppn9iGJU.d.ts → s3.service-Cg5TmbU_.d.ts} +6 -3
  49. package/dist/{s3.service-BoRPFx82.d.mts → s3.service-DLf_a0xS.d.mts} +6 -3
  50. package/dist/server/index.d.mts +4 -4
  51. package/dist/server/index.d.ts +4 -4
  52. package/dist/server/index.js +3 -3
  53. package/dist/server/index.mjs +1 -1
  54. package/dist/{useRbacState-DhuYYr0S.d.mts → useRbacState-Btk1gkQg.d.mts} +1 -1
  55. package/dist/{useRbacState-NnzNL2ED.d.ts → useRbacState-CUj0hp8t.d.ts} +1 -1
  56. package/dist/{useSocket-bsV-K4qR.d.ts → useSocket-BSUN9s3p.d.ts} +1 -1
  57. package/dist/{useSocket-CtfuR5wD.d.mts → useSocket-DKI92Fbg.d.mts} +1 -1
  58. package/package.json +2 -1
  59. package/src/components/EditableAvatar.tsx +175 -0
  60. package/src/components/containers/RoundPageContainer.tsx +1 -1
  61. package/src/components/fiscal/FiscalDataDisplay.tsx +26 -0
  62. package/src/components/fiscal/ItalianFiscalData.tsx +120 -0
  63. package/src/components/fiscal/ItalianFiscalDataDisplay.tsx +24 -0
  64. package/src/components/fiscal/index.ts +4 -0
  65. package/src/components/index.ts +3 -0
  66. package/src/components/navigations/RecentPagesNavigator.tsx +3 -3
  67. package/src/features/company/components/details/CompanyContent.tsx +105 -0
  68. package/src/features/company/components/details/CompanyDetails.tsx +2 -19
  69. package/src/features/company/components/details/index.ts +1 -0
  70. package/src/features/company/components/forms/CompanyConfigurationEditor.tsx +38 -70
  71. package/src/features/company/components/forms/CompanyEditor.tsx +212 -172
  72. package/src/features/company/data/company.interface.ts +20 -0
  73. package/src/features/company/data/company.ts +73 -0
  74. package/src/features/role/components/forms/FormRoles.tsx +5 -4
  75. package/src/features/user/components/containers/AllUsersListContainer.tsx +36 -0
  76. package/src/features/user/components/containers/UserContainer.tsx +10 -13
  77. package/src/features/user/components/containers/UsersListContainer.tsx +15 -24
  78. package/src/features/user/components/containers/index.ts +1 -0
  79. package/src/features/user/components/details/UserContent.tsx +92 -0
  80. package/src/features/user/components/details/index.ts +1 -1
  81. package/src/features/user/components/forms/UserEditor.tsx +233 -233
  82. package/src/features/user/components/lists/CompanyUsersList.tsx +3 -1
  83. package/src/features/user/contexts/UserContext.tsx +1 -6
  84. package/src/features/user/data/user.service.ts +9 -0
  85. package/src/features/user/data/user.ts +3 -4
  86. package/src/utils/fiscal-utils.ts +7 -0
  87. package/src/utils/italian-validators.ts +79 -0
  88. package/dist/chunk-CDCGQFIA.js.map +0 -1
  89. package/dist/chunk-ESGUCYJS.mjs.map +0 -1
  90. package/dist/chunk-LRXJT656.js.map +0 -1
  91. package/src/features/user/components/details/UserDetails.tsx +0 -74
  92. /package/dist/{BlockNoteEditor-CZOW7J5K.mjs.map → BlockNoteEditor-TIX3GDVZ.mjs.map} +0 -0
  93. /package/dist/{chunk-5KMKI23S.mjs.map → chunk-ZYAAJMZZ.mjs.map} +0 -0
@@ -2,27 +2,16 @@
2
2
 
3
3
  import { zodResolver } from "@hookform/resolvers/zod";
4
4
  import { useTranslations } from "next-intl";
5
- import { useEffect, useState } from "react";
6
- import { SubmitHandler, useForm } from "react-hook-form";
5
+ import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
6
+ import { useForm } from "react-hook-form";
7
7
  import { v4 } from "uuid";
8
8
  import { z } from "zod";
9
- import {
10
- CommonEditorButtons,
11
- CommonEditorHeader,
12
- CommonEditorTrigger,
13
- errorToast,
14
- FormCheckbox,
15
- FormInput,
16
- FormPassword,
17
- FormRoles,
18
- FormTextarea,
19
- } from "../../../../components";
9
+ import { EditorSheet, FormCheckbox, FormInput, FormPassword, FormRoles, FormTextarea } from "../../../../components";
20
10
  import { useCompanyContext } from "../../../../contexts";
21
11
  import { Modules } from "../../../../core";
22
- import { useI18nRouter, usePageUrlGenerator } from "../../../../hooks";
12
+ import { useI18nRouter } from "../../../../hooks";
23
13
  import { Action } from "../../../../permissions";
24
14
  import { getRoleId } from "../../../../roles";
25
- import { Dialog, DialogContent, DialogTrigger, Form } from "../../../../shadcnui";
26
15
  import { CompanyInterface } from "../../../company/data/company.interface";
27
16
  import { RoleInterface } from "../../../role";
28
17
  import { RoleService } from "../../../role/data/role.service";
@@ -32,17 +21,30 @@ import { useCurrentUserContext } from "../../contexts";
32
21
  import { UserInput, UserInterface } from "../../data";
33
22
  import { UserService } from "../../data/user.service";
34
23
  import { UserAvatarEditor } from "./UserAvatarEditor";
35
- import { UserDeleter } from "./UserDeleter";
36
24
 
37
25
  type UserEditorProps = {
38
26
  user?: UserInterface;
39
27
  propagateChanges?: (user: UserInterface) => void;
40
28
  adminCreated?: boolean;
41
- trigger?: React.ReactNode;
29
+ trigger?: ReactNode;
42
30
  onRevalidate?: (path: string) => Promise<void>;
31
+ forceShow?: boolean;
32
+ onClose?: () => void;
33
+ dialogOpen?: boolean;
34
+ onDialogOpenChange?: (open: boolean) => void;
43
35
  };
44
36
 
45
- function UserEditorInternal({ user, propagateChanges, adminCreated, trigger, onRevalidate }: UserEditorProps) {
37
+ function UserEditorInternal({
38
+ user,
39
+ propagateChanges,
40
+ adminCreated,
41
+ trigger,
42
+ onRevalidate,
43
+ forceShow,
44
+ onClose,
45
+ dialogOpen,
46
+ onDialogOpenChange,
47
+ }: UserEditorProps) {
46
48
  const {
47
49
  company: userCompany,
48
50
  hasPermissionToModule,
@@ -51,36 +53,77 @@ function UserEditorInternal({ user, propagateChanges, adminCreated, trigger, onR
51
53
  setUser,
52
54
  } = useCurrentUserContext<UserInterface>();
53
55
  const { company: companyFromContext } = useCompanyContext();
54
- const generateUrl = usePageUrlGenerator();
55
56
  const router = useI18nRouter();
56
- const [open, setOpen] = useState<boolean>(false);
57
57
  const [roles, setRoles] = useState<RoleInterface[]>([]);
58
58
  const [file, setFile] = useState<File | null>(null);
59
59
  const [contentType, setContentType] = useState<string | null>(null);
60
- const t = useTranslations();
61
60
  const [resetImage, setResetImage] = useState<boolean>(false);
61
+ const t = useTranslations();
62
62
  const [company, setCompany] = useState<CompanyInterface | null>(companyFromContext || userCompany);
63
63
 
64
64
  useEffect(() => {
65
65
  if (!companyFromContext && userCompany) setCompany(userCompany);
66
66
  }, [company]);
67
67
 
68
- const formSchema = z.object({
69
- id: z.uuidv4(),
70
- name: z.string().min(1, { message: t(`user.fields.name.error`) }),
71
- email: z.string().min(1, { message: t(`common.fields.email.error`) }),
72
- password: z.string().optional(),
73
- title: z.string().optional(),
74
- bio: z.string().optional(),
75
- phone: z.string().optional(),
76
- roleIds: z.array(z.string()).optional(),
77
- sendInvitationEmail: z.boolean().optional(),
78
- avatar: z.string().optional(),
79
- });
68
+ // Fetch roles when sheet opens
69
+ const handleDialogOpenChange = useCallback(
70
+ (open: boolean) => {
71
+ if (
72
+ open &&
73
+ (company || currentUser?.roles.find((role: RoleInterface) => role.id === getRoleId().Administrator)) &&
74
+ roles.length === 0
75
+ ) {
76
+ async function fetchRoles() {
77
+ const allRoles = await RoleService.findAllRoles({});
78
+ const availableRoles = allRoles.filter(
79
+ (role: RoleInterface) =>
80
+ role.id !== getRoleId().Administrator &&
81
+ (role.requiredFeature === undefined ||
82
+ company?.features.some((feature) => feature.id === role.requiredFeature?.id)),
83
+ );
84
+ setRoles(availableRoles);
85
+ }
86
+ fetchRoles();
87
+ }
88
+ onDialogOpenChange?.(open);
89
+ },
90
+ [company, currentUser, roles.length, onDialogOpenChange],
91
+ );
80
92
 
81
- const form = useForm<z.infer<typeof formSchema>>({
82
- resolver: zodResolver(formSchema),
83
- defaultValues: {
93
+ // Generate S3 path when file is selected
94
+ useEffect(() => {
95
+ if (file && company) {
96
+ const id = form.getValues("id");
97
+ const fileType = file.type;
98
+ const extension = fileType.split("/").pop() ?? "";
99
+ const timestamp = new Date().toISOString().replace(/[-:T]/g, "").split(".")[0];
100
+ const fileUrl = `companies/${company.id}/users/${id}/${id}.${timestamp}.${extension}`;
101
+ form.setValue("avatar", fileUrl);
102
+ setContentType(fileType);
103
+ } else {
104
+ setContentType(null);
105
+ }
106
+ }, [file, company]);
107
+
108
+ const formSchema = useMemo(
109
+ () =>
110
+ z.object({
111
+ id: z.uuidv4(),
112
+ name: z.string().min(1, { message: t(`user.fields.name.error`) }),
113
+ email: z.string().min(1, { message: t(`common.fields.email.error`) }),
114
+ password: z.string().optional(),
115
+ title: z.string().optional(),
116
+ bio: z.string().optional(),
117
+ phone: z.string().optional(),
118
+ roleIds: z.array(z.string()).optional(),
119
+ sendInvitationEmail: z.boolean().optional(),
120
+ avatar: z.string().optional(),
121
+ }),
122
+ [t],
123
+ );
124
+
125
+ const getDefaultValues = useCallback(() => {
126
+ return {
84
127
  id: user?.id || v4(),
85
128
  name: user?.name || "",
86
129
  title: user?.title || "",
@@ -91,215 +134,172 @@ function UserEditorInternal({ user, propagateChanges, adminCreated, trigger, onR
91
134
  roleIds: user?.roles.map((role: RoleInterface) => role.id) || [],
92
135
  sendInvitationEmail: false,
93
136
  avatar: user?.avatarUrl || "",
94
- },
95
- });
96
-
97
- const onSubmit: SubmitHandler<z.infer<typeof formSchema>> = async (values: z.infer<typeof formSchema>) => {
98
- if (!user) {
99
- try {
100
- const existingUser = await UserService.findByEmail({ email: values.email });
101
- if (existingUser) {
102
- form.setError("email", {
103
- type: "manual",
104
- message: t(`user.errors.email_exists`),
105
- });
106
- errorToast({ title: t(`user.errors.email_exists`), error: "" });
107
- return;
108
- }
109
- } catch (_error) {
110
- // User does not exist, proceed
111
- }
112
- }
113
-
114
- if (values.avatar && contentType) {
115
- const s3: S3Interface = await S3Service.getPreSignedUrl({
116
- key: values.avatar,
117
- contentType: contentType,
118
- isPublic: true,
119
- });
120
-
121
- await fetch(s3.url, {
122
- method: "PUT",
123
- headers: s3.headers,
124
- body: file,
125
- });
126
- }
127
-
128
- const payload: UserInput = {
129
- id: values.id,
130
- email: values.email,
131
- name: values.name,
132
- title: values.title,
133
- bio: values.bio,
134
- phone: values.phone,
135
- password: values.password,
136
- avatar: resetImage ? undefined : values.avatar,
137
- roleIds: values.roleIds,
138
- sendInvitationEmail: values.sendInvitationEmail,
139
- companyId: company!.id,
140
- adminCreated: adminCreated,
141
137
  };
138
+ }, [user]);
142
139
 
143
- try {
144
- const updatedUser = user
145
- ? ((await UserService.update(payload)) as UserInterface)
146
- : ((await UserService.create(payload)) as UserInterface);
147
-
148
- if (currentUser?.id === updatedUser.id) setUser(updatedUser);
149
-
150
- if (onRevalidate) {
151
- await onRevalidate(generateUrl({ page: Modules.User, id: updatedUser.id, language: `[locale]` }));
152
- }
153
- if (propagateChanges) {
154
- propagateChanges(updatedUser);
155
- setOpen(false);
156
- } else {
157
- router.push(generateUrl({ page: Modules.User, id: updatedUser.id }));
158
- }
159
- } catch (error) {
160
- errorToast({ title: user ? t(`common.errors.update`) : t(`common.errors.create`), error });
161
- }
162
- };
163
-
164
- useEffect(() => {
165
- async function fetchRoles() {
166
- const roles = await RoleService.findAllRoles({});
167
-
168
- const availableRoles = roles.filter(
169
- (role: RoleInterface) =>
170
- role.id !== getRoleId().Administrator &&
171
- (role.requiredFeature === undefined ||
172
- company?.features.some((feature) => feature.id === role.requiredFeature?.id)),
173
- );
174
-
175
- setRoles(availableRoles);
176
- }
140
+ const form = useForm<z.infer<typeof formSchema>>({
141
+ resolver: zodResolver(formSchema),
142
+ defaultValues: getDefaultValues(),
143
+ });
177
144
 
178
- if (
179
- open &&
180
- (company || currentUser?.roles.find((role: RoleInterface) => role.id === getRoleId().Administrator)) &&
181
- roles.length === 0
182
- )
183
- fetchRoles();
184
- }, [company, open]);
145
+ const canChangeRoles =
146
+ !(currentUser?.id === user?.id && hasRole(getRoleId().Administrator)) &&
147
+ (hasPermissionToModule({ module: Modules.User, action: Action.Update }) || hasRole(getRoleId().Administrator));
185
148
 
186
- useEffect(() => {
187
- if (file && company) {
188
- const id = form.getValues("id");
189
- const fileType = file.type;
190
- let extension = "";
149
+ return (
150
+ <EditorSheet
151
+ form={form}
152
+ entityType={t(`entities.users`, { count: 1 })}
153
+ entityName={user?.name}
154
+ isEdit={!!user}
155
+ module={Modules.User}
156
+ propagateChanges={propagateChanges}
157
+ size="md"
158
+ onSubmit={async (values) => {
159
+ // Check for existing email on create
160
+ if (!user) {
161
+ try {
162
+ const existingUser = await UserService.findByEmail({ email: values.email });
163
+ if (existingUser) {
164
+ form.setError("email", {
165
+ type: "manual",
166
+ message: t(`user.errors.email_exists`),
167
+ });
168
+ throw new Error(t(`user.errors.email_exists`));
169
+ }
170
+ } catch (error) {
171
+ if (error instanceof Error && error.message === t(`user.errors.email_exists`)) {
172
+ throw error;
173
+ }
174
+ // User does not exist, proceed
175
+ }
176
+ }
191
177
 
192
- switch (fileType) {
193
- default:
194
- extension = file.type.split("/").pop() ?? "";
195
- break;
196
- }
178
+ // Upload avatar to S3 if present
179
+ if (values.avatar && contentType && file) {
180
+ const s3: S3Interface = await S3Service.getPreSignedUrl({
181
+ key: values.avatar,
182
+ contentType: contentType,
183
+ isPublic: true,
184
+ });
197
185
 
198
- const timestamp = new Date().toISOString().replace(/[-:T]/g, "").split(".")[0];
186
+ const response = await fetch(s3.url, {
187
+ method: "PUT",
188
+ headers: s3.headers,
189
+ body: file,
190
+ });
199
191
 
200
- const fileUrl = `companies/${company.id}/users/${id}/${id}.${timestamp}.${extension}`;
201
- form.setValue("avatar", fileUrl);
192
+ if (!response.ok) {
193
+ throw new Error(`S3 upload failed: ${response.status}`);
194
+ }
195
+ }
202
196
 
203
- setContentType(fileType);
204
- } else {
205
- setContentType(null);
206
- }
207
- }, [file]);
197
+ const payload: UserInput = {
198
+ id: values.id,
199
+ email: values.email,
200
+ name: values.name,
201
+ title: values.title,
202
+ bio: values.bio,
203
+ phone: values.phone,
204
+ password: values.password,
205
+ avatar: resetImage ? undefined : values.avatar,
206
+ roleIds: values.roleIds,
207
+ sendInvitationEmail: values.sendInvitationEmail,
208
+ companyId: company!.id,
209
+ adminCreated: adminCreated,
210
+ };
208
211
 
209
- const canChangeRoles =
210
- !(currentUser?.id === user?.id && hasRole(getRoleId().Administrator)) &&
211
- (hasPermissionToModule({ module: Modules.User, action: Action.Update }) || hasRole(getRoleId().Administrator));
212
+ const updatedUser = user
213
+ ? ((await UserService.update(payload)) as UserInterface)
214
+ : ((await UserService.create(payload)) as UserInterface);
212
215
 
213
- return (
214
- <Dialog open={open} onOpenChange={setOpen}>
215
- {trigger ? <DialogTrigger>{trigger}</DialogTrigger> : <CommonEditorTrigger isEdit={!!user} />}
216
- <DialogContent
217
- className={`flex max-h-[70vh] ${canChangeRoles ? `max-w-[90vw]` : `max-w-3xl`} min-h-3xl max-h-[90vh] flex-col overflow-y-auto`}
218
- >
219
- <CommonEditorHeader type={t(`entities.users`, { count: 1 })} name={user?.name} />
220
- <Form {...form}>
221
- <form onSubmit={form.handleSubmit(onSubmit)} className={`flex w-full flex-col gap-y-4`}>
222
- <div className={`flex flex-row gap-x-4`}>
223
- <div className={`flex w-40 flex-col justify-start gap-y-2`}>
224
- <UserAvatarEditor
225
- user={user}
226
- file={file}
227
- setFile={setFile}
228
- resetImage={resetImage}
229
- setResetImage={setResetImage}
230
- />
231
- </div>
232
- <div className={`flex w-full flex-col justify-start`}>
233
- <FormInput
234
- form={form}
235
- id="name"
236
- name={t(`user.fields.name.label`)}
237
- placeholder={t(`user.fields.name.placeholder`)}
238
- />
239
- <FormInput
240
- form={form}
241
- id="email"
242
- name={t(`common.fields.email.label`)}
243
- placeholder={t(`common.fields.email.placeholder`)}
244
- />
245
- <FormInput
246
- form={form}
247
- id="phone"
248
- name={t(`user.fields.phone.label`)}
249
- placeholder={t(`user.fields.phone.placeholder`)}
250
- />
251
- <FormPassword
252
- form={form}
253
- id="password"
254
- name={t(`user.fields.password.label`)}
255
- placeholder={t(`user.fields.password.placeholder`)}
256
- />
257
- <FormInput
258
- form={form}
259
- id="title"
260
- name={t(`user.fields.title.label`)}
261
- placeholder={t(`user.fields.title.placeholder`)}
262
- />
263
- <FormTextarea
264
- form={form}
265
- id="bio"
266
- name={t(`user.fields.bio.label`)}
267
- placeholder={t(`user.fields.bio.placeholder`)}
268
- className="min-h-40"
269
- />
270
- </div>
271
- {canChangeRoles && (
272
- <div className="flex w-1/3 flex-col">
273
- {canChangeRoles && (
274
- <FormRoles form={form} id="roleIds" name={t(`entities.roles`, { count: 2 })} roles={roles} />
275
- )}
276
- {!user && (
277
- <div className="flex flex-col gap-y-4">
278
- <div className="text-sm font-semibold">{t(`user.send_activation_email`)}</div>
279
- <FormCheckbox form={form} id="sendInvitationEmail" name={t(`user.send_activation_email`)} />
280
- </div>
281
- )}
282
- </div>
283
- )}
284
- </div>
216
+ if (currentUser?.id === updatedUser.id) setUser(updatedUser);
285
217
 
286
- <div className="flex justify-end gap-x-4">
287
- {user && currentUser?.roles.find((role: RoleInterface) => role.id === getRoleId().Administrator) && (
288
- <UserDeleter
289
- companyId={user.company?.id}
290
- user={user}
291
- onDeleted={() => {
292
- setOpen(false);
293
- if (propagateChanges) propagateChanges(user);
294
- }}
295
- />
296
- )}
297
- <CommonEditorButtons form={form} setOpen={setOpen} isEdit={!!user} />
218
+ return updatedUser;
219
+ }}
220
+ onRevalidate={onRevalidate}
221
+ onNavigate={(url) => router.push(url)}
222
+ onReset={() => {
223
+ setFile(null);
224
+ setContentType(null);
225
+ setResetImage(false);
226
+ setRoles([]);
227
+ return getDefaultValues();
228
+ }}
229
+ onClose={onClose}
230
+ trigger={trigger}
231
+ forceShow={forceShow}
232
+ dialogOpen={dialogOpen}
233
+ onDialogOpenChange={handleDialogOpenChange}
234
+ >
235
+ <div className="flex w-full flex-col gap-y-4">
236
+ <div className="flex w-full gap-x-4">
237
+ <div className="flex w-40 flex-col gap-y-4">
238
+ <UserAvatarEditor
239
+ user={user}
240
+ file={file}
241
+ setFile={setFile}
242
+ resetImage={resetImage}
243
+ setResetImage={setResetImage}
244
+ />
245
+ </div>
246
+ <div className="flex w-full flex-col justify-start gap-y-4">
247
+ <FormInput
248
+ form={form}
249
+ id="name"
250
+ name={t(`user.fields.name.label`)}
251
+ placeholder={t(`user.fields.name.placeholder`)}
252
+ isRequired
253
+ />
254
+ <FormInput
255
+ form={form}
256
+ id="email"
257
+ name={t(`common.fields.email.label`)}
258
+ placeholder={t(`common.fields.email.placeholder`)}
259
+ isRequired
260
+ />
261
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
262
+ <FormInput
263
+ form={form}
264
+ id="phone"
265
+ name={t(`user.fields.phone.label`)}
266
+ placeholder={t(`user.fields.phone.placeholder`)}
267
+ />
268
+ <FormPassword
269
+ form={form}
270
+ id="password"
271
+ name={t(`user.fields.password.label`)}
272
+ placeholder={t(`user.fields.password.placeholder`)}
273
+ />
298
274
  </div>
299
- </form>
300
- </Form>
301
- </DialogContent>
302
- </Dialog>
275
+ <FormInput
276
+ form={form}
277
+ id="title"
278
+ name={t(`user.fields.title.label`)}
279
+ placeholder={t(`user.fields.title.placeholder`)}
280
+ />
281
+ <FormTextarea
282
+ form={form}
283
+ id="bio"
284
+ name={t(`user.fields.bio.label`)}
285
+ placeholder={t(`user.fields.bio.placeholder`)}
286
+ className="min-h-40"
287
+ />
288
+ {canChangeRoles && (
289
+ <div className="flex flex-col gap-y-4">
290
+ <FormRoles form={form} id="roleIds" name={t(`entities.roles`, { count: 2 })} roles={roles} />
291
+ {!user && (
292
+ <>
293
+ <div className="text-sm font-semibold">{t(`user.send_activation_email`)}</div>
294
+ <FormCheckbox form={form} id="sendInvitationEmail" name={t(`user.send_activation_email`)} />
295
+ </>
296
+ )}
297
+ </div>
298
+ )}
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </EditorSheet>
303
303
  );
304
304
  }
305
305
 
@@ -12,9 +12,10 @@ import { UserEditor } from "../forms";
12
12
 
13
13
  type CompanyUsersListProps = {
14
14
  isDeleted?: boolean;
15
+ fullWidth?: boolean;
15
16
  };
16
17
 
17
- export function CompanyUsersList({ isDeleted }: CompanyUsersListProps) {
18
+ export function CompanyUsersList({ isDeleted, fullWidth }: CompanyUsersListProps) {
18
19
  const { company } = useCurrentUserContext<UserInterface>();
19
20
  const t = useTranslations();
20
21
 
@@ -39,6 +40,7 @@ export function CompanyUsersList({ isDeleted }: CompanyUsersListProps) {
39
40
  fields={[UserFields.name, UserFields.email]}
40
41
  tableGeneratorType={Modules.User}
41
42
  functions={functions}
43
+ fullWidth={fullWidth}
42
44
  title={t(`entities.users`, { count: 2 })}
43
45
  />
44
46
  );
@@ -36,14 +36,9 @@ export const UserProvider = ({ children, dehydratedUser }: UserProviderProps) =>
36
36
  const response: BreadcrumbItemData[] = [];
37
37
 
38
38
  if (hasPermissionToModule({ module: Modules.User, action: Action.Update })) {
39
- response.push({
40
- name: t(`common.settings`, { item: "settings" }),
41
- href: generateUrl({ page: `/settings` }),
42
- });
43
-
44
39
  response.push({
45
40
  name: t(`entities.users`, { count: 2 }),
46
- href: generateUrl({ page: `/settings`, id: Modules.User.pageUrl }),
41
+ href: generateUrl({ page: Modules.User }),
47
42
  });
48
43
  }
49
44
 
@@ -225,6 +225,15 @@ export class UserService extends AbstractService {
225
225
  });
226
226
  }
227
227
 
228
+ static async patch(params: { id: string } & Partial<Omit<UserInput, "id">>): Promise<UserInterface> {
229
+ return this.callApi({
230
+ type: Modules.User,
231
+ method: HttpMethod.PATCH,
232
+ endpoint: new EndpointCreator({ endpoint: Modules.User, id: params.id }).generate(),
233
+ input: params,
234
+ });
235
+ }
236
+
228
237
  static async patchRate(params: UserInput): Promise<UserInterface> {
229
238
  return this.callApi({
230
239
  type: Modules.User,
@@ -126,15 +126,14 @@ export class User extends AbstractApiData implements UserInterface, SearchResult
126
126
  data: {
127
127
  type: Modules.User.name,
128
128
  id: data.id,
129
- attributes: {
130
- name: data.name,
131
- },
129
+ attributes: {},
132
130
  meta: {},
133
131
  relationships: {},
134
132
  },
135
133
  included: [],
136
134
  };
137
135
 
136
+ if (data.name !== undefined) response.data.attributes.name = data.name;
138
137
  if (data.email !== undefined) response.data.attributes.email = data.email;
139
138
  if (data.title !== undefined) response.data.attributes.title = data.title;
140
139
  if (data.bio !== undefined) response.data.attributes.bio = data.bio;
@@ -142,7 +141,7 @@ export class User extends AbstractApiData implements UserInterface, SearchResult
142
141
  if (data.password !== undefined) response.data.attributes.password = data.password;
143
142
  if (data.sendInvitationEmail) response.data.attributes.sendInvitationEmail = true;
144
143
  if (data.adminCreated) response.data.attributes.adminCreated = true;
145
- if (data.avatar) response.data.attributes.avatar = data.avatar;
144
+ if (data.avatar !== undefined) response.data.attributes.avatar = data.avatar;
146
145
  if (data.rate !== undefined) response.data.attributes.rate = data.rate;
147
146
 
148
147
  if (data.roleIds) {
@@ -0,0 +1,7 @@
1
+ export function parseFiscalData(fiscalDataJson?: string): Record<string, string> {
2
+ try {
3
+ return fiscalDataJson ? JSON.parse(fiscalDataJson) : {};
4
+ } catch {
5
+ return {};
6
+ }
7
+ }