Compare commits
1 Commits
main
...
12-29-fix_
Author | SHA1 | Date | |
---|---|---|---|
ea3cd09be0 |
|
@ -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} />;
|
||||||
|
}
|
|
@ -100,6 +100,22 @@ async function getIdentityData(req: NextApiRequest) {
|
||||||
avatar: getPlaceholderAvatar(org?.logo, org?.name),
|
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) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
|
|
@ -30,7 +30,6 @@ import type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Avatar,
|
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
|
@ -72,6 +71,8 @@ import useMeQuery from "@lib/hooks/useMeQuery";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
|
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";
|
import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup";
|
||||||
|
|
||||||
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
|
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
|
||||||
|
@ -83,6 +84,7 @@ interface EventTypeListHeadingProps {
|
||||||
membershipCount: number;
|
membershipCount: number;
|
||||||
teamId?: number | null;
|
teamId?: number | null;
|
||||||
bookerUrl: string;
|
bookerUrl: string;
|
||||||
|
organizationId: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventTypeGroup = EventTypeGroups[number];
|
type EventTypeGroup = EventTypeGroups[number];
|
||||||
|
@ -693,6 +695,7 @@ export const EventTypeList = ({
|
||||||
|
|
||||||
const EventTypeListHeading = ({
|
const EventTypeListHeading = ({
|
||||||
profile,
|
profile,
|
||||||
|
organizationId,
|
||||||
membershipCount,
|
membershipCount,
|
||||||
teamId,
|
teamId,
|
||||||
bookerUrl,
|
bookerUrl,
|
||||||
|
@ -711,13 +714,32 @@ const EventTypeListHeading = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 flex items-center space-x-2">
|
<div className="mb-4 flex items-center space-x-2">
|
||||||
<Avatar
|
{!teamId ? (
|
||||||
alt={profile?.name || ""}
|
<UserAvatar
|
||||||
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
|
href="/settings/my-account/profile"
|
||||||
imageSrc={`${bookerUrl}${teamId ? "/team" : ""}/${profile.slug}/avatar.png`}
|
user={{
|
||||||
size="md"
|
name: profile.name,
|
||||||
className="mt-1 inline-flex justify-center"
|
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>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
|
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
|
||||||
|
@ -861,6 +883,7 @@ const Main = ({
|
||||||
data-testid={`slug-${group.profile.slug}`}
|
data-testid={`slug-${group.profile.slug}`}
|
||||||
key={group.profile.slug}>
|
key={group.profile.slug}>
|
||||||
<EventTypeListHeading
|
<EventTypeListHeading
|
||||||
|
organizationId={group.organizationId}
|
||||||
profile={group.profile}
|
profile={group.profile}
|
||||||
membershipCount={group.metadata.membershipCount}
|
membershipCount={group.metadata.membershipCount}
|
||||||
teamId={group.teamId}
|
teamId={group.teamId}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { useState } from "react";
|
||||||
import InviteLinkSettingsModal from "@calcom/ee/teams/components/InviteLinkSettingsModal";
|
import InviteLinkSettingsModal from "@calcom/ee/teams/components/InviteLinkSettingsModal";
|
||||||
import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal";
|
import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal";
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
|
||||||
import { getTeamUrlSync } from "@calcom/lib/getBookerUrl/client";
|
import { getTeamUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
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 type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
|
@ -42,6 +40,8 @@ import {
|
||||||
X,
|
X,
|
||||||
} from "@calcom/ui/components/icon";
|
} from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import { TeamAvatar } from "@components/ui/avatar/TeamAvatar";
|
||||||
|
|
||||||
import { useOrgBranding } from "../../organizations/context/provider";
|
import { useOrgBranding } from "../../organizations/context/provider";
|
||||||
import { TeamRole } from "./TeamPill";
|
import { TeamRole } from "./TeamPill";
|
||||||
|
|
||||||
|
@ -97,10 +97,14 @@ export default function TeamListItem(props: Props) {
|
||||||
|
|
||||||
const teamInfo = (
|
const teamInfo = (
|
||||||
<div className="item-center flex px-5 py-5">
|
<div className="item-center flex px-5 py-5">
|
||||||
<Avatar
|
<TeamAvatar
|
||||||
size="md"
|
size="md"
|
||||||
imageSrc={getPlaceholderAvatar(team?.logo, team?.name as string)}
|
team={{
|
||||||
alt="Team Logo"
|
name: team.name,
|
||||||
|
slug: team.slug,
|
||||||
|
organizationId: team.parentId,
|
||||||
|
requestedSlug: team.requestedSlug || null,
|
||||||
|
}}
|
||||||
className="inline-flex justify-center"
|
className="inline-flex justify-center"
|
||||||
/>
|
/>
|
||||||
<div className="ms-3 inline-block truncate">
|
<div className="ms-3 inline-block truncate">
|
||||||
|
|
|
@ -29,6 +29,11 @@ export function getTeamAvatarUrl(
|
||||||
if (team.logoUrl) {
|
if (team.logoUrl) {
|
||||||
return 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;
|
const slug = team.slug ?? team.requestedSlug;
|
||||||
return `${WEBAPP_URL}/team/${slug}/avatar.png${team.organizationId ? `?orgId=${team.organizationId}` : ""}`;
|
return `${WEBAPP_URL}/team/${slug}/avatar.png${team.organizationId ? `?orgId=${team.organizationId}` : ""}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,10 +186,12 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
|
||||||
type EventTypeGroup = {
|
type EventTypeGroup = {
|
||||||
teamId?: number | null;
|
teamId?: number | null;
|
||||||
parentId?: number | null;
|
parentId?: number | null;
|
||||||
|
organizationId: number | null;
|
||||||
bookerUrl: string;
|
bookerUrl: string;
|
||||||
membershipRole?: MembershipRole | null;
|
membershipRole?: MembershipRole | null;
|
||||||
profile: {
|
profile: {
|
||||||
slug: (typeof user)["username"];
|
slug: (typeof user)["username"];
|
||||||
|
requestedSlug?: string | null;
|
||||||
name: (typeof user)["name"];
|
name: (typeof user)["name"];
|
||||||
image: string;
|
image: string;
|
||||||
};
|
};
|
||||||
|
@ -212,6 +214,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
|
||||||
teamId: null,
|
teamId: null,
|
||||||
bookerUrl,
|
bookerUrl,
|
||||||
membershipRole: null,
|
membershipRole: null,
|
||||||
|
organizationId: user.organizationId,
|
||||||
profile: {
|
profile: {
|
||||||
slug: user.username,
|
slug: user.username,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
|
@ -272,6 +275,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
teamId: team.id,
|
teamId: team.id,
|
||||||
|
organizationId: team.parentId,
|
||||||
parentId: team.parentId,
|
parentId: team.parentId,
|
||||||
bookerUrl: getBookerBaseUrlSync(team.parent?.slug ?? null),
|
bookerUrl: getBookerBaseUrlSync(team.parent?.slug ?? null),
|
||||||
membershipRole:
|
membershipRole:
|
||||||
|
@ -285,6 +289,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
|
||||||
organizationId: team.parentId,
|
organizationId: team.parentId,
|
||||||
}),
|
}),
|
||||||
name: team.name,
|
name: team.name,
|
||||||
|
requestedSlug: team.metadata?.requestedSlug ?? null,
|
||||||
slug,
|
slug,
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
|
|
|
@ -30,14 +30,25 @@ export const listHandler = async ({ ctx }: ListOptions) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return memberships
|
return memberships
|
||||||
.filter((mmship) => {
|
.map((mmship) => {
|
||||||
const metadata = teamMetadataSchema.parse(mmship.team.metadata);
|
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 }) => ({
|
.map(({ team: { inviteTokens, ..._team }, ...membership }) => ({
|
||||||
role: membership.role,
|
role: membership.role,
|
||||||
accepted: membership.accepted,
|
accepted: membership.accepted,
|
||||||
..._team,
|
..._team,
|
||||||
|
requestedSlug: _team.metadata?.requestedSlug,
|
||||||
/** To prevent breaking we only return non-email attached token here, if we have one */
|
/** 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}`),
|
inviteToken: inviteTokens.find((token) => token.identifier === `invite-link-for-teamId-${_team.id}`),
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user