feat: let org admins change usernames (#13384)
This commit is contained in:
1 parent
1546f9115d
commit
11fb387370
4 files changed
+70
-13
No files matched your search
@@ -30,6 +30,7 @@ type MembershipOption = {
|
||||
|
||||
const editSchema = z.object({
|
||||
name: z.string(),
|
||||
username: z.string(),
|
||||
email: z.string().email(),
|
||||
avatar: z.string(),
|
||||
bio: z.string(),
|
||||
@@ -59,6 +60,7 @@ export function EditForm({
|
||||
resolver: zodResolver(editSchema),
|
||||
defaultValues: {
|
||||
name: selectedUser?.name ?? "",
|
||||
username: selectedUser?.username ?? "",
|
||||
email: selectedUser?.email ?? "",
|
||||
avatar: avatarUrl,
|
||||
bio: selectedUser?.bio ?? "",
|
||||
@@ -121,6 +123,7 @@ export function EditForm({
|
||||
mutation.mutate({
|
||||
userId: selectedUser?.id ?? "",
|
||||
role: values.role,
|
||||
username: values.username,
|
||||
name: values.name,
|
||||
email: values.email,
|
||||
avatar: values.avatar,
|
||||
@@ -157,6 +160,7 @@ export function EditForm({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col space-y-3">
|
||||
<TextField label={t("username")} {...form.register("username")} />
|
||||
<TextField label={t("name")} {...form.register("name")} />
|
||||
<TextField label={t("email")} {...form.register("email")} />
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ProfileRepository } from "./repository/profile";
|
||||
|
||||
export async function checkRegularUsername(_username: string, currentOrgDomain?: string | null) {
|
||||
const username = slugify(_username);
|
||||
|
||||
const premium = !!process.env.NEXT_PUBLIC_IS_E2E && username.length < 5;
|
||||
|
||||
const profiles = currentOrgDomain
|
||||
@@ -13,7 +14,7 @@ export async function checkRegularUsername(_username: string, currentOrgDomain?:
|
||||
})
|
||||
: null;
|
||||
|
||||
const user = profiles ? profiles[0].user : null;
|
||||
const user = profiles?.length ? profiles[0].user : null;
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { Prisma, PrismaPromise, User, Membership, Profile } from "@prisma/client";
|
||||
|
||||
import { checkRegularUsername } from "@calcom/lib/server/checkRegularUsername";
|
||||
import { isOrganisationAdmin, isOrganisationOwner } from "@calcom/lib/server/queries/organisations";
|
||||
import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { uploadAvatar } from "@calcom/trpc/server/routers/loggedInViewer/updateProfile.handler";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -32,6 +36,7 @@ const applyRoleToAllTeams = async (userId: number, teamIds: number[], role: Memb
|
||||
export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => {
|
||||
const { user } = ctx;
|
||||
const { id: userId, organizationId } = user;
|
||||
|
||||
if (!organizationId)
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "You must be a memeber of an organizaiton" });
|
||||
|
||||
@@ -74,30 +79,58 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
select: {
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!requestedMember)
|
||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "User does not belong to your organization" });
|
||||
|
||||
let avatar = input.avatar;
|
||||
if (input.avatar) {
|
||||
avatar = await resizeBase64Image(input.avatar);
|
||||
const hasUsernameUpdated = input.username !== requestedMember.user.username;
|
||||
|
||||
if (input.username && hasUsernameUpdated && user.profile.organization?.slug) {
|
||||
const checkRegularUsernameRes = await checkRegularUsername(
|
||||
input.username,
|
||||
user.profile.organization.slug
|
||||
);
|
||||
if (!checkRegularUsernameRes.available) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: checkRegularUsernameRes.message });
|
||||
}
|
||||
}
|
||||
|
||||
const data: Prisma.UserUpdateInput = {
|
||||
bio: input.bio,
|
||||
email: input.email,
|
||||
name: input.name,
|
||||
timeZone: input.timeZone,
|
||||
username: input.username,
|
||||
avatar: undefined,
|
||||
};
|
||||
|
||||
if (input.avatar && input.avatar.startsWith("data:image/png;base64,")) {
|
||||
const avatar = await resizeBase64Image(input.avatar);
|
||||
data.avatarUrl = await uploadAvatar({
|
||||
avatar,
|
||||
userId: user.id,
|
||||
});
|
||||
data.avatar = avatar;
|
||||
}
|
||||
if (input.avatar === "") {
|
||||
data.avatarUrl = null;
|
||||
data.avatar = null;
|
||||
}
|
||||
|
||||
// Update user
|
||||
await prisma.$transaction([
|
||||
const transactions: PrismaPromise<User | Membership | Profile>[] = [
|
||||
prisma.user.update({
|
||||
where: {
|
||||
id: input.userId,
|
||||
},
|
||||
data: {
|
||||
bio: input.bio,
|
||||
email: input.email,
|
||||
name: input.name,
|
||||
timeZone: input.timeZone,
|
||||
avatar,
|
||||
},
|
||||
data,
|
||||
}),
|
||||
prisma.membership.update({
|
||||
where: {
|
||||
@@ -110,7 +143,25 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => {
|
||||
role: input.role,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
];
|
||||
|
||||
if (hasUsernameUpdated) {
|
||||
transactions.push(
|
||||
prisma.profile.update({
|
||||
where: {
|
||||
userId_organizationId: {
|
||||
userId: input.userId,
|
||||
organizationId,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
username: input.username,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.$transaction(transactions);
|
||||
|
||||
if (input.role === MembershipRole.ADMIN || input.role === MembershipRole.OWNER) {
|
||||
const teamIds = requestedMember.team.children
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
export const ZUpdateUserInputSchema = z.object({
|
||||
userId: z.number(),
|
||||
username: z.string().optional(),
|
||||
bio: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
|
||||
Reference in new issue
Block a user