@carlonicora/nextjs-jsonapi 1.66.0 → 1.68.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.
- package/dist/{BlockNoteEditor-KCJMA6LW.mjs → BlockNoteEditor-6FDECIS2.mjs} +4 -4
- package/dist/{BlockNoteEditor-GQM2TZG2.js → BlockNoteEditor-DXHROT4C.js} +14 -14
- package/dist/{BlockNoteEditor-GQM2TZG2.js.map → BlockNoteEditor-DXHROT4C.js.map} +1 -1
- package/dist/billing/index.js +346 -346
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-NVXYOQFW.js → chunk-37KYO2UD.js} +20 -5
- package/dist/chunk-37KYO2UD.js.map +1 -0
- package/dist/{chunk-QIFM4G7T.js → chunk-ELTHSXBI.js} +1476 -1298
- package/dist/chunk-ELTHSXBI.js.map +1 -0
- package/dist/{chunk-4E74ZTRT.mjs → chunk-H4ZS3R76.mjs} +2606 -2428
- package/dist/chunk-H4ZS3R76.mjs.map +1 -0
- package/dist/{chunk-35GWVOYZ.mjs → chunk-IOMDNRX5.mjs} +20 -5
- package/dist/{chunk-35GWVOYZ.mjs.map → chunk-IOMDNRX5.mjs.map} +1 -1
- package/dist/{chunk-OQRBY22T.js → chunk-WOJIRXIP.js} +11 -11
- package/dist/{chunk-OQRBY22T.js.map → chunk-WOJIRXIP.js.map} +1 -1
- package/dist/{chunk-UXGPZZ6V.mjs → chunk-WVTBEVAL.mjs} +2 -2
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +29 -7
- package/dist/components/index.d.ts +29 -7
- package/dist/components/index.js +8 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +9 -5
- package/dist/contexts/index.d.mts +1 -1
- package/dist/contexts/index.d.ts +1 -1
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/core/index.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{s3.service-XchHd3ii.d.mts → s3.service-CHOTwfWA.d.mts} +7 -0
- package/dist/{s3.service-DIR6Su9B.d.ts → s3.service-N1g0piXD.d.ts} +7 -0
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/EditableAvatar.tsx +175 -0
- package/src/components/index.ts +1 -0
- package/src/features/company/components/forms/CompanyEditor.tsx +1 -3
- package/src/features/role/components/forms/FormRoles.tsx +5 -4
- package/src/features/user/components/containers/AllUsersListContainer.tsx +36 -0
- package/src/features/user/components/containers/UserContainer.tsx +10 -13
- package/src/features/user/components/containers/UsersListContainer.tsx +15 -24
- package/src/features/user/components/containers/index.ts +1 -0
- package/src/features/user/components/details/UserContent.tsx +92 -0
- package/src/features/user/components/details/index.ts +1 -1
- package/src/features/user/components/forms/UserEditor.tsx +233 -233
- package/src/features/user/components/lists/CompanyUsersList.tsx +3 -1
- package/src/features/user/contexts/UserContext.tsx +1 -6
- package/src/features/user/data/user.service.ts +18 -0
- package/src/features/user/data/user.ts +3 -4
- package/dist/chunk-4E74ZTRT.mjs.map +0 -1
- package/dist/chunk-NVXYOQFW.js.map +0 -1
- package/dist/chunk-QIFM4G7T.js.map +0 -1
- package/src/features/user/components/details/UserDetails.tsx +0 -74
- /package/dist/{BlockNoteEditor-KCJMA6LW.mjs.map → BlockNoteEditor-6FDECIS2.mjs.map} +0 -0
- /package/dist/{chunk-UXGPZZ6V.mjs.map → chunk-WVTBEVAL.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 {
|
|
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
|
|
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?:
|
|
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({
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
186
|
+
const response = await fetch(s3.url, {
|
|
187
|
+
method: "PUT",
|
|
188
|
+
headers: s3.headers,
|
|
189
|
+
body: file,
|
|
190
|
+
});
|
|
199
191
|
|
|
200
|
-
|
|
201
|
-
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
throw new Error(`S3 upload failed: ${response.status}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
202
196
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
const updatedUser = user
|
|
213
|
+
? ((await UserService.update(payload)) as UserInterface)
|
|
214
|
+
: ((await UserService.create(payload)) as UserInterface);
|
|
212
215
|
|
|
213
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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:
|
|
41
|
+
href: generateUrl({ page: Modules.User }),
|
|
47
42
|
});
|
|
48
43
|
}
|
|
49
44
|
|
|
@@ -225,6 +225,24 @@ 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
|
+
|
|
237
|
+
static async patchAvatar(params: { id: string; avatar: string }): Promise<UserInterface> {
|
|
238
|
+
return this.callApi({
|
|
239
|
+
type: Modules.User,
|
|
240
|
+
method: HttpMethod.PATCH,
|
|
241
|
+
endpoint: new EndpointCreator({ endpoint: Modules.User, id: params.id, childEndpoint: "avatar" }).generate(),
|
|
242
|
+
input: params,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
228
246
|
static async patchRate(params: UserInput): Promise<UserInterface> {
|
|
229
247
|
return this.callApi({
|
|
230
248
|
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) {
|