diff --git a/apps/web/modules/event-types/views/event-types-listing-view.tsx b/apps/web/modules/event-types/views/event-types-listing-view.tsx index f5a32dc031..6842c6827d 100644 --- a/apps/web/modules/event-types/views/event-types-listing-view.tsx +++ b/apps/web/modules/event-types/views/event-types-listing-view.tsx @@ -425,7 +425,7 @@ export const EventTypeList = ({
- {type.team && !isManagedEventType && ( + {!!type.teamId && !isManagedEventType && ( ); + const profileImageSrc = + isValidOrgDomain || team.metadata?.isOrganization + ? getOrgAvatarUrl({ slug: currentOrgDomain, logoUrl: team.logoUrl }) + : getTeamAvatarUrl({ slug: team.slug, logoUrl: team.logoUrl, organizationId: team.parent?.id }); + return ( <>
- +

{team.parent && `${team.parent.name} `} diff --git a/packages/features/ee/teams/pages/team-profile-view.tsx b/packages/features/ee/teams/pages/team-profile-view.tsx index baacdb347e..02b46b3cd1 100644 --- a/packages/features/ee/teams/pages/team-profile-view.tsx +++ b/packages/features/ee/teams/pages/team-profile-view.tsx @@ -325,7 +325,7 @@ const TeamProfileForm = ({ team }: TeamProfileFormProps) => { { + render={({ field: { value, onChange } }) => { const showRemoveLogoButton = !!value; return ( @@ -340,9 +340,7 @@ const TeamProfileForm = ({ team }: TeamProfileFormProps) => { target="avatar" id="avatar-upload" buttonMsg={t("upload_logo")} - handleAvatarChange={(newLogo) => { - form.setValue("logo", newLogo, { shouldDirty: true }); - }} + handleAvatarChange={onChange} triggerButtonColor={showRemoveLogoButton ? "secondary" : "primary"} imageSrc={value ?? undefined} /> @@ -350,7 +348,7 @@ const TeamProfileForm = ({ team }: TeamProfileFormProps) => { diff --git a/packages/lib/getAvatarUrl.ts b/packages/lib/getAvatarUrl.ts index f4f1f0fa2d..76b6156984 100644 --- a/packages/lib/getAvatarUrl.ts +++ b/packages/lib/getAvatarUrl.ts @@ -29,7 +29,7 @@ export function getTeamAvatarUrl( team: Pick & { organizationId?: number | null; logoUrl?: string | null; - requestedSlug: string | null; + requestedSlug?: string | null; } ) { if (team.logoUrl) { @@ -42,7 +42,7 @@ export function getTeamAvatarUrl( export const getOrgAvatarUrl = ( org: Pick & { logoUrl?: string | null; - requestedSlug: string | null; + requestedSlug?: string | null; } ) => { if (org.logoUrl) { diff --git a/packages/lib/server/queries/teams/index.ts b/packages/lib/server/queries/teams/index.ts index 032841a708..edfb943fee 100644 --- a/packages/lib/server/queries/teams/index.ts +++ b/packages/lib/server/queries/teams/index.ts @@ -87,6 +87,7 @@ export async function getTeamWithMembers(args: { name: true, slug: true, ...(!!includeTeamLogo ? { logo: true } : {}), + logoUrl: true, bio: true, hideBranding: true, hideBookATeamMember: true, @@ -232,6 +233,7 @@ export async function getTeamWithMembers(args: { return { ...teamWithoutInviteTokens, + ...(teamWithoutInviteTokens.logoUrl ? { logo: teamWithoutInviteTokens.logoUrl } : {}), /** To prevent breaking we only return non-email attached token here, if we have one */ inviteToken: inviteTokens.find( (token) => diff --git a/packages/lib/server/repository/eventType.ts b/packages/lib/server/repository/eventType.ts index fdba24993f..b348d2d383 100644 --- a/packages/lib/server/repository/eventType.ts +++ b/packages/lib/server/repository/eventType.ts @@ -99,12 +99,6 @@ export class EventTypeRepository { const profileId = lookupTarget.type === LookupTarget.User ? null : lookupTarget.id; const select = { ...eventTypeSelect, - // TODO: As required by getByViewHandler - Make it configurable - team: { - select: { - id: true, - }, - }, hashedLink: true, users: { select: userSelect }, children: { diff --git a/packages/lib/server/repository/membership.ts b/packages/lib/server/repository/membership.ts index aedc24c85d..dc1f94be3c 100644 --- a/packages/lib/server/repository/membership.ts +++ b/packages/lib/server/repository/membership.ts @@ -103,11 +103,6 @@ export class MembershipRepository { eventTypes: { select: { ...eventTypeSelect, - team: { - select: { - id: true, - }, - }, hashedLink: true, users: { select: userSelect }, children: { diff --git a/packages/lib/server/uploadLogo.ts b/packages/lib/server/uploadLogo.ts new file mode 100644 index 0000000000..3002bbd7fc --- /dev/null +++ b/packages/lib/server/uploadLogo.ts @@ -0,0 +1,33 @@ +import { v4 as uuidv4 } from "uuid"; + +import { prisma } from "@calcom/prisma"; + +export const uploadLogo = async ({ + teamId, + logo: data, +}: { + teamId: number; + logo: string; +}): Promise => { + const objectKey = uuidv4(); + + await prisma.avatar.upsert({ + where: { + teamId_userId: { + teamId, + userId: 0, + }, + }, + create: { + teamId, + data, + objectKey, + }, + update: { + data, + objectKey, + }, + }); + + return `/api/avatar/${objectKey}.png`; +}; diff --git a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts index 82435014e8..0e4fea76bd 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts @@ -207,7 +207,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => if (!input?.filters || !hasFilter(input?.filters)) { return true; } - return input?.filters?.teamIds?.includes(eventType?.team?.id || 0) ?? false; + return input?.filters?.teamIds?.includes(eventType?.teamId || 0) ?? false; }; eventTypeGroups = ([] as EventTypeGroup[]).concat( eventTypeGroups, @@ -262,6 +262,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => }) : getTeamAvatarUrl({ slug: team.slug, + logoUrl: team.logoUrl, requestedSlug: team.metadata?.requestedSlug ?? null, organizationId: team.parentId, }), diff --git a/packages/trpc/server/routers/viewer/organizations/update.handler.ts b/packages/trpc/server/routers/viewer/organizations/update.handler.ts index 2049455820..3170d92aad 100644 --- a/packages/trpc/server/routers/viewer/organizations/update.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/update.handler.ts @@ -3,6 +3,7 @@ import type { Prisma } from "@prisma/client"; import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; import { getMetadataHelpers } from "@calcom/lib/getMetadataHelpers"; import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations"; +import { uploadLogo } from "@calcom/lib/server/uploadLogo"; import { closeComUpdateTeam } from "@calcom/lib/sync/SyncServiceManager"; import { prisma } from "@calcom/prisma"; import { UserPermissionRole } from "@calcom/prisma/enums"; @@ -60,7 +61,6 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { const data: Prisma.TeamUpdateArgs["data"] = { name: input.name, - logo: input.logo, calVideoLogo: input.calVideoLogo, bio: input.bio, hideBranding: input.hideBranding, @@ -74,6 +74,16 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { metadata: mergeMetadata({ ...input.metadata }), }; + if (input.logo && input.logo.startsWith("data:image/png;base64,")) { + data.logo = input.logo; + data.logoUrl = await uploadLogo({ + logo: input.logo, + teamId: currentOrgId, + }); + } else if (typeof input.logo !== "undefined" && !input.logo) { + data.logo = data.logoUrl = null; + } + if (input.slug) { if ( IS_TEAM_BILLING_ENABLED && diff --git a/packages/trpc/server/routers/viewer/organizations/update.schema.ts b/packages/trpc/server/routers/viewer/organizations/update.schema.ts index 40e577de6d..3872859979 100644 --- a/packages/trpc/server/routers/viewer/organizations/update.schema.ts +++ b/packages/trpc/server/routers/viewer/organizations/update.schema.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; export const ZUpdateInputSchema = z.object({ @@ -13,9 +14,9 @@ export const ZUpdateInputSchema = z.object({ bio: z.string().optional(), logo: z .string() + .transform(async (val) => await resizeBase64Image(val)) .optional() - .nullable() - .transform((v) => v || null), + .nullable(), calVideoLogo: z .string() .optional() diff --git a/packages/trpc/server/routers/viewer/teams/list.handler.ts b/packages/trpc/server/routers/viewer/teams/list.handler.ts index 9824eb0d64..ab4436d6b2 100644 --- a/packages/trpc/server/routers/viewer/teams/list.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/list.handler.ts @@ -37,10 +37,11 @@ export const listHandler = async ({ ctx, input }: ListOptions) => { const metadata = teamMetadataSchema.parse(mmship.team.metadata); return !metadata?.isOrganization; }) - .map(({ team: { inviteTokens, ..._team }, ...membership }) => ({ + .map(({ team: { inviteTokens, logo, logoUrl, ..._team }, ...membership }) => ({ role: membership.role, accepted: membership.accepted, ..._team, + logo: logoUrl || logo, metadata: teamMetadataSchema.parse(_team.metadata), /** To prevent breaking we only return non-email attached token here, if we have one */ inviteToken: inviteTokens.find((token) => token.identifier === `invite-link-for-teamId-${_team.id}`), diff --git a/packages/trpc/server/routers/viewer/teams/update.handler.ts b/packages/trpc/server/routers/viewer/teams/update.handler.ts index bcb9fbce9e..f7d251e424 100644 --- a/packages/trpc/server/routers/viewer/teams/update.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/update.handler.ts @@ -2,6 +2,7 @@ import type { Prisma } from "@prisma/client"; import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; import { isTeamAdmin } from "@calcom/lib/server/queries/teams"; +import { uploadLogo } from "@calcom/lib/server/uploadLogo"; import { closeComUpdateTeam } from "@calcom/lib/sync/SyncServiceManager"; import { prisma } from "@calcom/prisma"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; @@ -46,7 +47,6 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { const data: Prisma.TeamUpdateArgs["data"] = { name: input.name, - logo: input.logo, bio: input.bio, hideBranding: input.hideBranding, isPrivate: input.isPrivate, @@ -56,6 +56,13 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { theme: input.theme, }; + if (input.logo && input.logo.startsWith("data:image/png;base64,")) { + data.logo = input.logo; + data.logoUrl = await uploadLogo({ teamId: input.id, logo: input.logo }); + } else if (typeof input.logo !== "undefined" && !input.logo) { + data.logo = data.logoUrl = null; + } + if ( input.slug && IS_TEAM_BILLING_ENABLED && diff --git a/packages/trpc/server/routers/viewer/teams/update.schema.ts b/packages/trpc/server/routers/viewer/teams/update.schema.ts index 3eb58f5024..6db6a0b5b0 100644 --- a/packages/trpc/server/routers/viewer/teams/update.schema.ts +++ b/packages/trpc/server/routers/viewer/teams/update.schema.ts @@ -1,12 +1,17 @@ import { z } from "zod"; +import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; import slugify from "@calcom/lib/slugify"; export const ZUpdateInputSchema = z.object({ id: z.number(), bio: z.string().optional(), name: z.string().optional(), - logo: z.string().nullable().optional(), + logo: z + .string() + .transform(async (val) => await resizeBase64Image(val)) + .nullable() + .optional(), slug: z .string() .transform((val) => slugify(val.trim()))