From e0c87cf66497decd36b04cd2e7714dd46d68d32f Mon Sep 17 00:00:00 2001 From: Denzil Samuel <71846487+samueldenzil@users.noreply.github.com> Date: Sat, 23 Sep 2023 04:42:35 +0530 Subject: [PATCH] feat: Add ability to edit avatar (#11399) Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Udit Takkar --- apps/web/public/static/locales/en/common.json | 1 + .../UserTable/EditSheet/EditUserForm.tsx | 33 ++++++++++++++++--- .../UserTable/EditSheet/EditUserSheet.tsx | 2 +- .../components/UserTable/UserListTable.tsx | 2 +- .../lib/server/queries/organisations/index.ts | 5 +-- .../organizations/updateUser.handler.ts | 15 ++++++++- .../viewer/organizations/updateUser.schema.ts | 3 +- packages/ui/components/sheet/sheet.tsx | 2 +- 8 files changed, 51 insertions(+), 12 deletions(-) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index a265afb628..2c096b465e 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1276,6 +1276,7 @@ "personal_cal_url": "My personal {{appName}} URL", "bio_hint": "A few sentences about yourself. this will appear on your personal url page.", "user_has_no_bio": "This user has not added a bio yet.", + "bio":"Bio", "delete_account_modal_title": "Delete Account", "confirm_delete_account_modal": "Are you sure you want to delete your {{appName}} account?", "delete_my_account": "Delete my account", diff --git a/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx b/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx index 8e5858241b..3202758a10 100644 --- a/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx +++ b/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx @@ -1,8 +1,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import type { Dispatch } from "react"; -import { useForm } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; -import shallow from "zustand/shallow"; +import { shallow } from "zustand/shallow"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc, type RouterOutputs } from "@calcom/trpc/react"; @@ -15,6 +15,7 @@ import { Label, showToast, Avatar, + ImageUploader, } from "@calcom/ui"; import type { Action } from "../UserListTable"; @@ -23,8 +24,9 @@ import { useEditMode } from "./store"; const editSchema = z.object({ name: z.string(), email: z.string().email(), + avatar: z.string(), bio: z.string(), - role: z.enum(["ADMIN", "MEMBER"]), + role: z.enum(["ADMIN", "MEMBER", "OWNER"]), timeZone: z.string(), // schedules: z.array(z.string()), // teams: z.array(z.string()), @@ -51,6 +53,7 @@ export function EditForm({ defaultValues: { name: selectedUser?.name ?? "", email: selectedUser?.email ?? "", + avatar: avatarUrl, bio: selectedUser?.bio ?? "", role: selectedUser?.role ?? "", timeZone: selectedUser?.timeZone ?? "", @@ -88,12 +91,32 @@ export function EditForm({ role: values.role as "ADMIN" | "MEMBER", // Cast needed as we dont provide an option for owner name: values.name, email: values.email, + avatar: values.avatar, bio: values.bio, timeZone: values.timeZone, }); }}> -
- +
+ ( +
+ +
+ { + form.setValue("avatar", newAvatar, { shouldDirty: true }); + }} + imageSrc={value || undefined} + /> +
+
+ )} + />
{selectedUser?.name ?? "Nameless User"}

diff --git a/packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx b/packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx index c0ab958b19..1c14a0c273 100644 --- a/packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx +++ b/packages/features/users/components/UserTable/EditSheet/EditUserSheet.tsx @@ -97,7 +97,7 @@ export function EditUserSheet({ state, dispatch }: { state: State; dispatch: Dis

) : ( -
+
{username || "No username"}
-
{email}
+
{email}
); diff --git a/packages/lib/server/queries/organisations/index.ts b/packages/lib/server/queries/organisations/index.ts index 16d7606718..57127f5d98 100644 --- a/packages/lib/server/queries/organisations/index.ts +++ b/packages/lib/server/queries/organisations/index.ts @@ -1,4 +1,5 @@ import prisma from "@calcom/prisma"; +import { MembershipRole } from "@calcom/prisma/enums"; // export type OrganisationWithMembers = Awaited>; @@ -9,7 +10,7 @@ export async function isOrganisationAdmin(userId: number, orgId: number) { where: { userId, teamId: orgId, - OR: [{ role: "ADMIN" }, { role: "OWNER" }], + OR: [{ role: MembershipRole.ADMIN }, { role: MembershipRole.OWNER }], }, })) || false ); @@ -19,7 +20,7 @@ export async function isOrganisationOwner(userId: number, orgId: number) { where: { userId, teamId: orgId, - role: "OWNER", + role: MembershipRole.OWNER, }, })); } diff --git a/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts b/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts index 115324f1b5..35f17b9435 100644 --- a/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts @@ -1,5 +1,7 @@ -import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations"; +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 type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; import { TRPCError } from "@trpc/server"; @@ -21,6 +23,11 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => { if (!(await isOrganisationAdmin(userId, organizationId))) throw new TRPCError({ code: "UNAUTHORIZED" }); + // only OWNER can update the role to OWNER + if (input.role === MembershipRole.OWNER && !(await isOrganisationOwner(userId, organizationId))) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + // Is requested user a member of the organization? const requestedMember = await prisma.membership.findFirst({ where: { @@ -33,6 +40,11 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => { 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); + } + // Update user await prisma.$transaction([ prisma.user.update({ @@ -44,6 +56,7 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => { email: input.email, name: input.name, timeZone: input.timeZone, + avatar, }, }), prisma.membership.update({ diff --git a/packages/trpc/server/routers/viewer/organizations/updateUser.schema.ts b/packages/trpc/server/routers/viewer/organizations/updateUser.schema.ts index 3f7fcde07b..e12658864e 100644 --- a/packages/trpc/server/routers/viewer/organizations/updateUser.schema.ts +++ b/packages/trpc/server/routers/viewer/organizations/updateUser.schema.ts @@ -5,7 +5,8 @@ export const ZUpdateUserInputSchema = z.object({ bio: z.string().optional(), name: z.string().optional(), email: z.string().optional(), - role: z.enum(["ADMIN", "MEMBER"]), + avatar: z.string().optional(), + role: z.enum(["ADMIN", "MEMBER", "OWNER"]), timeZone: z.string(), }); diff --git a/packages/ui/components/sheet/sheet.tsx b/packages/ui/components/sheet/sheet.tsx index e0e6827780..4e51112030 100644 --- a/packages/ui/components/sheet/sheet.tsx +++ b/packages/ui/components/sheet/sheet.tsx @@ -150,7 +150,7 @@ const SheetContent = React.forwardRef -
{children}
+
{children}
{bottomActions && (
{bottomActions}