fix: Use org logo for organization's teams

This commit is contained in:
Hariom 2023-12-29 13:26:54 +05:30
parent c4792c55fe
commit ea3cd09be0
7 changed files with 101 additions and 15 deletions

View File

@ -0,0 +1,22 @@
import { getTeamAvatarUrl } from "@calcom/lib/getAvatarUrl";
import type { Team } from "@calcom/prisma/client";
import { Avatar } from "@calcom/ui";
type TeamAvatarProps = Omit<React.ComponentProps<typeof Avatar>, "alt" | "imageSrc"> & {
team: Pick<Team, "slug" | "name"> & {
organizationId?: number | null;
requestedSlug: string | null;
};
/**
* Useful when allowing the user to upload their own avatar and showing the avatar before it's uploaded
*/
previewSrc?: string | null;
};
/**
* It is aware of the user's organization to correctly show the avatar from the correct URL
*/
export function TeamAvatar(props: TeamAvatarProps) {
const { team, previewSrc = getTeamAvatarUrl(team), ...rest } = props;
return <Avatar {...rest} alt={team.name || "Nameless Team"} imageSrc={previewSrc} />;
}

View File

@ -100,6 +100,22 @@ async function getIdentityData(req: NextApiRequest) {
avatar: getPlaceholderAvatar(org?.logo, org?.name),
};
}
// If just orgId is specified, we return the org avatar
if (orgId) {
const org = await prisma.team.findUnique({
where: {
id: orgId,
},
});
return {
org: org?.slug,
name: org?.name,
email: null,
avatar: getPlaceholderAvatar(org?.logo, org?.name),
};
}
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {

View File

@ -30,7 +30,6 @@ import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import {
Alert,
Avatar,
Badge,
Button,
ButtonGroup,
@ -72,6 +71,8 @@ import useMeQuery from "@lib/hooks/useMeQuery";
import PageWrapper from "@components/PageWrapper";
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
import { TeamAvatar } from "@components/ui/avatar/TeamAvatar";
import { UserAvatar } from "@components/ui/avatar/UserAvatar";
import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup";
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
@ -83,6 +84,7 @@ interface EventTypeListHeadingProps {
membershipCount: number;
teamId?: number | null;
bookerUrl: string;
organizationId: number | null;
}
type EventTypeGroup = EventTypeGroups[number];
@ -693,6 +695,7 @@ export const EventTypeList = ({
const EventTypeListHeading = ({
profile,
organizationId,
membershipCount,
teamId,
bookerUrl,
@ -711,13 +714,32 @@ const EventTypeListHeading = ({
return (
<div className="mb-4 flex items-center space-x-2">
<Avatar
alt={profile?.name || ""}
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
imageSrc={`${bookerUrl}${teamId ? "/team" : ""}/${profile.slug}/avatar.png`}
size="md"
className="mt-1 inline-flex justify-center"
/>
{!teamId ? (
<UserAvatar
href="/settings/my-account/profile"
user={{
name: profile.name,
username: profile.slug,
organizationId,
}}
size="md"
className="mt-1 inline-flex justify-center"
/>
) : (
<TeamAvatar
href={`/settings/teams/${teamId}/profile`}
team={{
name: profile.name || "",
// I think profile.slug shouldn't contain team/ prefix for a team because that's a path and not a slug
// But we need to handle it for now instead of changing at the source to avoid side effects at other places.
slug: profile.slug?.replace(/^team\//, "") || null,
organizationId,
requestedSlug: profile.requestedSlug || null,
}}
size="md"
className="mt-1 inline-flex justify-center"
/>
)}
<div>
<Link
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
@ -861,6 +883,7 @@ const Main = ({
data-testid={`slug-${group.profile.slug}`}
key={group.profile.slug}>
<EventTypeListHeading
organizationId={group.organizationId}
profile={group.profile}
membershipCount={group.metadata.membershipCount}
teamId={group.teamId}

View File

@ -5,7 +5,6 @@ import { useState } from "react";
import InviteLinkSettingsModal from "@calcom/ee/teams/components/InviteLinkSettingsModal";
import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal";
import classNames from "@calcom/lib/classNames";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { getTeamUrlSync } from "@calcom/lib/getBookerUrl/client";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -13,7 +12,6 @@ import { MembershipRole } from "@calcom/prisma/enums";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import {
Avatar,
Badge,
Button,
ButtonGroup,
@ -42,6 +40,8 @@ import {
X,
} from "@calcom/ui/components/icon";
import { TeamAvatar } from "@components/ui/avatar/TeamAvatar";
import { useOrgBranding } from "../../organizations/context/provider";
import { TeamRole } from "./TeamPill";
@ -97,10 +97,14 @@ export default function TeamListItem(props: Props) {
const teamInfo = (
<div className="item-center flex px-5 py-5">
<Avatar
<TeamAvatar
size="md"
imageSrc={getPlaceholderAvatar(team?.logo, team?.name as string)}
alt="Team Logo"
team={{
name: team.name,
slug: team.slug,
organizationId: team.parentId,
requestedSlug: team.requestedSlug || null,
}}
className="inline-flex justify-center"
/>
<div className="ms-3 inline-block truncate">

View File

@ -29,6 +29,11 @@ export function getTeamAvatarUrl(
if (team.logoUrl) {
return team.logoUrl;
}
if (team.organizationId) {
// For an organization, all it's teams have the same logo as the organization
return `${WEBAPP_URL}/api/user/avatar?orgId=${team.organizationId}`;
}
const slug = team.slug ?? team.requestedSlug;
return `${WEBAPP_URL}/team/${slug}/avatar.png${team.organizationId ? `?orgId=${team.organizationId}` : ""}`;
}

View File

@ -186,10 +186,12 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
type EventTypeGroup = {
teamId?: number | null;
parentId?: number | null;
organizationId: number | null;
bookerUrl: string;
membershipRole?: MembershipRole | null;
profile: {
slug: (typeof user)["username"];
requestedSlug?: string | null;
name: (typeof user)["name"];
image: string;
};
@ -212,6 +214,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
teamId: null,
bookerUrl,
membershipRole: null,
organizationId: user.organizationId,
profile: {
slug: user.username,
name: user.name,
@ -272,6 +275,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
}
return {
teamId: team.id,
organizationId: team.parentId,
parentId: team.parentId,
bookerUrl: getBookerBaseUrlSync(team.parent?.slug ?? null),
membershipRole:
@ -285,6 +289,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
organizationId: team.parentId,
}),
name: team.name,
requestedSlug: team.metadata?.requestedSlug ?? null,
slug,
},
metadata: {

View File

@ -30,14 +30,25 @@ export const listHandler = async ({ ctx }: ListOptions) => {
});
return memberships
.filter((mmship) => {
.map((mmship) => {
const metadata = teamMetadataSchema.parse(mmship.team.metadata);
return !metadata?.isOrganization;
mmship.team.metadata = metadata;
return {
...mmship,
team: {
...mmship.team,
metadata,
},
};
})
.filter((mmship) => {
return !mmship.team.metadata?.isOrganization;
})
.map(({ team: { inviteTokens, ..._team }, ...membership }) => ({
role: membership.role,
accepted: membership.accepted,
..._team,
requestedSlug: _team.metadata?.requestedSlug,
/** 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}`),
}));