fix: Avatar slug and cal links for cross org users (#12031)
parent
96810b5ba1
commit
b934c74c30
|
@ -3,12 +3,13 @@ import type { FormEvent } from "react";
|
|||
import { useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import OrganizationAvatar from "@calcom/features/ee/organizations/components/OrganizationAvatar";
|
||||
import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import turndown from "@calcom/lib/turndownService";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { Ensure } from "@calcom/types/utils";
|
||||
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
|
@ -96,16 +97,19 @@ const UserProfile = () => {
|
|||
},
|
||||
];
|
||||
|
||||
const organization =
|
||||
user.organization && user.organization.id
|
||||
? {
|
||||
...(user.organization as Ensure<typeof user.organization, "id">),
|
||||
slug: user.organization.slug || null,
|
||||
requestedSlug: user.organization.metadata?.requestedSlug || null,
|
||||
}
|
||||
: null;
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="flex flex-row items-center justify-start rtl:justify-end">
|
||||
{user && (
|
||||
<OrganizationAvatar
|
||||
alt={user.username || "user avatar"}
|
||||
size="lg"
|
||||
imageSrc={imageSrc}
|
||||
organizationSlug={user.organization?.slug}
|
||||
/>
|
||||
<OrganizationMemberAvatar size="lg" user={user} previewSrc={imageSrc} organization={organization} />
|
||||
)}
|
||||
<input
|
||||
ref={avatarRef}
|
||||
|
|
|
@ -5,11 +5,15 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
|||
import { md } from "@calcom/lib/markdownIt";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import type { TeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
|
||||
import { UserAvatar } from "@components/ui/avatar/UserAvatar";
|
||||
|
||||
type TeamType = Omit<NonNullable<TeamWithMembers>, "inviteToken">;
|
||||
type MembersType = TeamType["members"];
|
||||
type MemberType = Pick<MembersType[number], "id" | "name" | "bio" | "username"> & { safeBio: string | null };
|
||||
type MemberType = Pick<MembersType[number], "id" | "name" | "bio" | "username" | "organizationId"> & {
|
||||
safeBio: string | null;
|
||||
orgOrigin: string;
|
||||
};
|
||||
|
||||
const Member = ({ member, teamName }: { member: MemberType; teamName: string | null }) => {
|
||||
const routerQuery = useRouterQuery();
|
||||
|
@ -20,9 +24,11 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
|||
const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = routerQuery;
|
||||
|
||||
return (
|
||||
<Link key={member.id} href={{ pathname: `/${member.username}`, query: queryParamsToForward }}>
|
||||
<Link
|
||||
key={member.id}
|
||||
href={{ pathname: `${member.orgOrigin}/${member.username}`, query: queryParamsToForward }}>
|
||||
<div className="sm:min-w-80 sm:max-w-80 bg-default hover:bg-muted border-subtle group flex min-h-full flex-col space-y-2 rounded-md border p-4 hover:cursor-pointer">
|
||||
<Avatar size="md" alt={member.name || ""} imageSrc={`/${member.username}/avatar.png`} />
|
||||
<UserAvatar size="md" user={member} />
|
||||
<section className="mt-2 line-clamp-4 w-full space-y-1">
|
||||
<p className="text-default font-medium">{member.name}</p>
|
||||
<div className="text-subtle line-clamp-3 overflow-ellipsis text-sm font-normal">
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
|
||||
type UserAvatarProps = Omit<React.ComponentProps<typeof Avatar>, "alt" | "imageSrc"> & {
|
||||
user: Pick<User, "organizationId" | "name" | "username">;
|
||||
/**
|
||||
* 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 UserAvatar(props: UserAvatarProps) {
|
||||
const { user, previewSrc, ...rest } = props;
|
||||
return <Avatar {...rest} alt={user.name || ""} imageSrc={previewSrc ?? getUserAvatarUrl(user)} />;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { AvatarGroup } from "@calcom/ui";
|
||||
|
||||
type UserAvatarProps = Omit<React.ComponentProps<typeof AvatarGroup>, "items"> & {
|
||||
users: Pick<User, "organizationId" | "name" | "username">[];
|
||||
};
|
||||
export function UserAvatarGroup(props: UserAvatarProps) {
|
||||
const { users, ...rest } = props;
|
||||
return (
|
||||
<AvatarGroup
|
||||
{...rest}
|
||||
items={users.map((user) => ({
|
||||
alt: user.name || "",
|
||||
title: user.name || "",
|
||||
image: getUserAvatarUrl(user),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { AvatarGroup } from "@calcom/ui";
|
||||
|
||||
type UserAvatarProps = Omit<React.ComponentProps<typeof AvatarGroup>, "items"> & {
|
||||
users: Pick<User, "organizationId" | "name" | "username">[];
|
||||
organization: Pick<Team, "slug" | "name">;
|
||||
};
|
||||
|
||||
export function UserAvatarGroupWithOrg(props: UserAvatarProps) {
|
||||
const { users, organization, ...rest } = props;
|
||||
const items = [
|
||||
{
|
||||
image: `${WEBAPP_URL}/team/${organization.slug}/avatar.png`,
|
||||
alt: organization.name || undefined,
|
||||
title: organization.name,
|
||||
},
|
||||
].concat(
|
||||
users.map((user) => {
|
||||
return {
|
||||
image: getUserAvatarUrl(user),
|
||||
alt: user.name || undefined,
|
||||
title: user.name || user.username || "",
|
||||
};
|
||||
})
|
||||
);
|
||||
users.unshift();
|
||||
return <AvatarGroup {...rest} items={items} />;
|
||||
}
|
|
@ -11,7 +11,7 @@ import {
|
|||
useEmbedStyles,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import OrganizationAvatar from "@calcom/features/ee/organizations/components/OrganizationAvatar";
|
||||
import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar";
|
||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||
|
@ -25,7 +25,7 @@ import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
|||
import prisma from "@calcom/prisma";
|
||||
import { RedirectType, type EventType, type User } from "@calcom/prisma/client";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||
import { Verified, ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
|
@ -99,11 +99,22 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
|
|||
"max-w-3xl px-4 py-24"
|
||||
)}>
|
||||
<div className="mb-8 text-center">
|
||||
<OrganizationAvatar
|
||||
imageSrc={profile.image}
|
||||
<OrganizationMemberAvatar
|
||||
size="xl"
|
||||
alt={profile.name}
|
||||
organizationSlug={profile.organizationSlug}
|
||||
user={{
|
||||
organizationId: profile.organization?.id,
|
||||
name: profile.name,
|
||||
username: profile.username,
|
||||
}}
|
||||
organization={
|
||||
profile.organization?.id
|
||||
? {
|
||||
id: profile.organization.id,
|
||||
slug: profile.organization.slug,
|
||||
requestedSlug: null,
|
||||
}
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<h1 className="font-cal text-emphasis mb-1 text-3xl" data-testid="name-title">
|
||||
{profile.name}
|
||||
|
@ -226,8 +237,13 @@ export type UserPageProps = {
|
|||
theme: string | null;
|
||||
brandColor: string;
|
||||
darkBrandColor: string;
|
||||
organizationSlug: string | null;
|
||||
organization: {
|
||||
requestedSlug: string | null;
|
||||
slug: string | null;
|
||||
id: number | null;
|
||||
};
|
||||
allowSEOIndexing: boolean;
|
||||
username: string | null;
|
||||
};
|
||||
users: Pick<User, "away" | "name" | "username" | "bio" | "verified">[];
|
||||
themeBasis: string | null;
|
||||
|
@ -286,6 +302,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
|||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
theme: true,
|
||||
|
@ -313,6 +330,10 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
|||
|
||||
const users = usersWithoutAvatar.map((user) => ({
|
||||
...user,
|
||||
organization: {
|
||||
...user.organization,
|
||||
metadata: user.organization?.metadata ? teamMetadataSchema.parse(user.organization.metadata) : null,
|
||||
},
|
||||
avatar: `/${user.username}/avatar.png`,
|
||||
}));
|
||||
|
||||
|
@ -344,8 +365,13 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
|||
theme: user.theme,
|
||||
brandColor: user.brandColor,
|
||||
darkBrandColor: user.darkBrandColor,
|
||||
organizationSlug: user.organization?.slug ?? null,
|
||||
allowSEOIndexing: user.allowSEOIndexing ?? true,
|
||||
username: user.username,
|
||||
organization: {
|
||||
id: user.organizationId,
|
||||
slug: user.organization?.slug ?? null,
|
||||
requestedSlug: user.organization?.metadata?.requestedSlug ?? null,
|
||||
},
|
||||
};
|
||||
|
||||
const eventTypesWithHidden = await getEventTypesWithHiddenFromDB(user.id);
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getSlugOrRequestedSlug, orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import {
|
||||
orgDomainConfig,
|
||||
whereClauseForOrgWithSlugOrRequestedSlug,
|
||||
} from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { AVATAR_FALLBACK } from "@calcom/lib/constants";
|
||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["team/[slug]"] });
|
||||
const querySchema = z
|
||||
.object({
|
||||
username: z.string(),
|
||||
teamname: z.string(),
|
||||
/**
|
||||
* Passed when we want to fetch avatar of a particular organization
|
||||
*/
|
||||
orgSlug: z.string(),
|
||||
/**
|
||||
* Allow fetching avatar of a particular organization
|
||||
|
@ -30,11 +38,11 @@ async function getIdentityData(req: NextApiRequest) {
|
|||
id: orgId,
|
||||
}
|
||||
: org
|
||||
? getSlugOrRequestedSlug(org)
|
||||
? whereClauseForOrgWithSlugOrRequestedSlug(org)
|
||||
: null;
|
||||
|
||||
if (username) {
|
||||
let user = await prisma.user.findFirst({
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username,
|
||||
organization: orgQuery,
|
||||
|
@ -42,27 +50,6 @@ async function getIdentityData(req: NextApiRequest) {
|
|||
select: { avatar: true, email: true },
|
||||
});
|
||||
|
||||
/**
|
||||
* TEMPORARY CODE STARTS - TO BE REMOVED after mono-user schema is implemented
|
||||
* Try the non-org user temporarily to support users part of a team but not part of the organization
|
||||
* This is needed because of a situation where we migrate a user and the team to ORG but not all the users in the team to the ORG.
|
||||
* Eventually, all users will be migrated to the ORG but this is when user by user migration happens initially.
|
||||
*/
|
||||
// No user found in the org, try the non-org user that might be part of the team that's part of an org
|
||||
if (!user && orgQuery) {
|
||||
// The only side effect this code could have is that it could serve the avatar of a non-org member from the org domain but as long as the username isn't taken by an org member.
|
||||
user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username,
|
||||
organization: null,
|
||||
},
|
||||
select: { avatar: true, email: true },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* TEMPORARY CODE ENDS
|
||||
*/
|
||||
|
||||
return {
|
||||
name: username,
|
||||
email: user?.email,
|
||||
|
@ -79,6 +66,7 @@ async function getIdentityData(req: NextApiRequest) {
|
|||
},
|
||||
select: { logo: true },
|
||||
});
|
||||
|
||||
return {
|
||||
org,
|
||||
name: teamname,
|
||||
|
@ -86,15 +74,25 @@ async function getIdentityData(req: NextApiRequest) {
|
|||
avatar: getPlaceholderAvatar(team?.logo, teamname),
|
||||
};
|
||||
}
|
||||
|
||||
if (orgSlug) {
|
||||
const org = await prisma.team.findFirst({
|
||||
where: getSlugOrRequestedSlug(orgSlug),
|
||||
const orgs = await prisma.team.findMany({
|
||||
where: {
|
||||
...whereClauseForOrgWithSlugOrRequestedSlug(orgSlug),
|
||||
},
|
||||
select: {
|
||||
slug: true,
|
||||
logo: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (orgs.length > 1) {
|
||||
// This should never happen, but instead of throwing error, we are just logging to be able to observe when it happens.
|
||||
log.error("More than one organization found for slug", orgSlug);
|
||||
}
|
||||
|
||||
const org = orgs[0];
|
||||
return {
|
||||
org: org?.slug,
|
||||
name: org?.name,
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { z } from "zod";
|
||||
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import OrganizationAvatar from "@calcom/features/ee/organizations/components/OrganizationAvatar";
|
||||
import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar";
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { APP_NAME, FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants";
|
||||
|
@ -19,6 +19,7 @@ import type { TRPCClientErrorLike } from "@calcom/trpc/client";
|
|||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
import type { Ensure } from "@calcom/types/utils";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
|
@ -251,6 +252,7 @@ const ProfileView = () => {
|
|||
isLoading={updateProfileMutation.isLoading}
|
||||
isFallbackImg={checkIfItFallbackImage(fetchedImgSrc)}
|
||||
userAvatar={user.avatar}
|
||||
user={user}
|
||||
userOrganization={user.organization}
|
||||
onSubmit={(values) => {
|
||||
if (values.email !== user.email && isCALIdentityProvider) {
|
||||
|
@ -396,6 +398,7 @@ const ProfileForm = ({
|
|||
isLoading = false,
|
||||
isFallbackImg,
|
||||
userAvatar,
|
||||
user,
|
||||
userOrganization,
|
||||
}: {
|
||||
defaultValues: FormValues;
|
||||
|
@ -404,6 +407,7 @@ const ProfileForm = ({
|
|||
isLoading: boolean;
|
||||
isFallbackImg: boolean;
|
||||
userAvatar: string;
|
||||
user: RouterOutputs["viewer"]["me"];
|
||||
userOrganization: RouterOutputs["viewer"]["me"]["organization"];
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
|
@ -443,13 +447,21 @@ const ProfileForm = ({
|
|||
name="avatar"
|
||||
render={({ field: { value } }) => {
|
||||
const showRemoveAvatarButton = !isFallbackImg || (value && userAvatar !== value);
|
||||
const organization =
|
||||
userOrganization && userOrganization.id
|
||||
? {
|
||||
...(userOrganization as Ensure<typeof user.organization, "id">),
|
||||
slug: userOrganization.slug || null,
|
||||
requestedSlug: userOrganization.metadata?.requestedSlug || null,
|
||||
}
|
||||
: null;
|
||||
return (
|
||||
<>
|
||||
<OrganizationAvatar
|
||||
alt={formMethods.getValues("username")}
|
||||
imageSrc={value}
|
||||
<OrganizationMemberAvatar
|
||||
previewSrc={value}
|
||||
size="lg"
|
||||
organizationSlug={userOrganization.slug}
|
||||
user={user}
|
||||
organization={organization}
|
||||
/>
|
||||
<div className="ms-4">
|
||||
<h2 className="mb-2 text-sm font-medium">{t("profile_picture")}</h2>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { FormProvider, useForm } from "react-hook-form";
|
|||
import { z } from "zod";
|
||||
|
||||
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
|
||||
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
|
@ -159,7 +159,7 @@ export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoA
|
|||
<TextField
|
||||
addOnLeading={
|
||||
orgSlug
|
||||
? `${getOrgFullDomain(orgSlug, { protocol: true })}/`
|
||||
? `${getOrgFullOrigin(orgSlug, { protocol: true })}/`
|
||||
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/`
|
||||
}
|
||||
{...register("username")}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { usePathname } from "next/navigation";
|
|||
import { useEffect } from "react";
|
||||
|
||||
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig, getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
@ -27,7 +27,7 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
|
|||
import prisma from "@calcom/prisma";
|
||||
import { RedirectType } from "@calcom/prisma/client";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { Avatar, AvatarGroup, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||
import { Avatar, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
|
||||
|
@ -35,6 +35,7 @@ import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
|||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
import Team from "@components/team/screens/Team";
|
||||
import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
|
@ -111,15 +112,11 @@ function TeamPage({
|
|||
<EventTypeDescription className="text-sm" eventType={type} />
|
||||
</div>
|
||||
<div className="mt-1 self-center">
|
||||
<AvatarGroup
|
||||
<UserAvatarGroup
|
||||
truncateAfter={4}
|
||||
className="flex flex-shrink-0"
|
||||
size="sm"
|
||||
items={type.users.map((user) => ({
|
||||
alt: user.name || "",
|
||||
title: user.name || "",
|
||||
image: `/${user.username}/avatar.png` || "",
|
||||
}))}
|
||||
users={type.users}
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
|
@ -149,17 +146,11 @@ function TeamPage({
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<AvatarGroup
|
||||
<UserAvatarGroup
|
||||
className="mr-6"
|
||||
size="sm"
|
||||
truncateAfter={4}
|
||||
items={team.members
|
||||
.filter((mem) => mem.subteams?.includes(ch.slug) && mem.accepted)
|
||||
.map((member) => ({
|
||||
alt: member.name || "",
|
||||
image: `/${member.username}/avatar.png`,
|
||||
title: member.name || "",
|
||||
}))}
|
||||
users={team.members.filter((mem) => mem.subteams?.includes(ch.slug) && mem.accepted)}
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -373,7 +364,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
subteams: member.subteams,
|
||||
username: member.username,
|
||||
accepted: member.accepted,
|
||||
organizationId: member.organizationId,
|
||||
safeBio: markdownToSafeHTML(member.bio || ""),
|
||||
orgOrigin: getOrgFullOrigin(member.organization?.slug || ""),
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
|
|
@ -8,7 +8,7 @@ import GoogleProvider from "next-auth/providers/google";
|
|||
|
||||
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
||||
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
|
||||
import { getOrgFullDomain, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
@ -471,7 +471,7 @@ export const AUTH_OPTIONS: AuthOptions = {
|
|||
id: organization.id,
|
||||
name: organization.name,
|
||||
slug: organization.slug ?? parsedOrgMetadata?.requestedSlug ?? "",
|
||||
fullDomain: getOrgFullDomain(organization.slug ?? parsedOrgMetadata?.requestedSlug ?? ""),
|
||||
fullDomain: getOrgFullOrigin(organization.slug ?? parsedOrgMetadata?.requestedSlug ?? ""),
|
||||
domainSuffix: subdomainSuffix(),
|
||||
}
|
||||
: undefined,
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { AvatarGroup } from "@calcom/ui";
|
||||
import { UserAvatarGroup } from "@calcom/web/components/ui/avatar/UserAvatarGroup";
|
||||
import { UserAvatarGroupWithOrg } from "@calcom/web/components/ui/avatar/UserAvatarGroupWithOrg";
|
||||
|
||||
import type { PublicEvent } from "../../types";
|
||||
|
||||
|
@ -18,17 +15,7 @@ export interface EventMembersProps {
|
|||
entity: PublicEvent["entity"];
|
||||
}
|
||||
|
||||
type Avatar = {
|
||||
title: string;
|
||||
image: string | undefined;
|
||||
alt: string | undefined;
|
||||
href: string | undefined;
|
||||
};
|
||||
|
||||
type AvatarWithRequiredImage = Avatar & { image: string };
|
||||
|
||||
export const EventMembers = ({ schedulingType, users, profile, entity }: EventMembersProps) => {
|
||||
const pathname = usePathname();
|
||||
const showMembers = schedulingType !== SchedulingType.ROUND_ROBIN;
|
||||
const shownUsers = showMembers ? users : [];
|
||||
|
||||
|
@ -38,40 +25,22 @@ export const EventMembers = ({ schedulingType, users, profile, entity }: EventMe
|
|||
!users.length ||
|
||||
(profile.name !== users[0].name && schedulingType === SchedulingType.COLLECTIVE);
|
||||
|
||||
const avatars: Avatar[] = shownUsers.map((user) => ({
|
||||
title: `${user.name || user.username}`,
|
||||
image: "image" in user ? `${user.image}` : `/${user.username}/avatar.png`,
|
||||
alt: user.name || undefined,
|
||||
href: `/${user.username}`,
|
||||
}));
|
||||
|
||||
// Add organization avatar
|
||||
if (entity.orgSlug) {
|
||||
avatars.unshift({
|
||||
title: `${entity.name}`,
|
||||
image: `${WEBAPP_URL}/team/${entity.orgSlug}/avatar.png`,
|
||||
alt: entity.name || undefined,
|
||||
href: getOrgFullDomain(entity.orgSlug),
|
||||
});
|
||||
}
|
||||
|
||||
// Add profile later since we don't want to force creating an avatar for this if it doesn't exist.
|
||||
avatars.unshift({
|
||||
title: `${profile.name || profile.username}`,
|
||||
image: "logo" in profile && profile.logo ? `${profile.logo}` : undefined,
|
||||
alt: profile.name || undefined,
|
||||
href: profile.username
|
||||
? `${CAL_URL}${pathname?.indexOf("/team/") !== -1 ? "/team" : ""}/${profile.username}`
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const uniqueAvatars = avatars
|
||||
.filter((item): item is AvatarWithRequiredImage => !!item.image)
|
||||
.filter((item, index, self) => self.findIndex((t) => t.image === item.image) === index);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AvatarGroup size="sm" className="border-muted" items={uniqueAvatars} />
|
||||
{entity.orgSlug ? (
|
||||
<UserAvatarGroupWithOrg
|
||||
size="sm"
|
||||
className="border-muted"
|
||||
organization={{
|
||||
slug: entity.orgSlug,
|
||||
name: entity.name || "",
|
||||
}}
|
||||
users={shownUsers}
|
||||
/>
|
||||
) : (
|
||||
<UserAvatarGroup size="sm" className="border-muted" users={shownUsers} />
|
||||
)}
|
||||
|
||||
<p className="text-subtle text-sm font-semibold">
|
||||
{showOnlyProfileName
|
||||
? profile.name
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import classNames from "@calcom/lib/classNames";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
import type { AvatarProps } from "@calcom/ui";
|
||||
|
||||
type OrganizationAvatarProps = AvatarProps & {
|
||||
organizationSlug: string | null | undefined;
|
||||
};
|
||||
|
||||
const OrganizationAvatar = ({ size, imageSrc, alt, organizationSlug, ...rest }: OrganizationAvatarProps) => {
|
||||
return (
|
||||
<Avatar
|
||||
data-testid="organization-avatar"
|
||||
size={size}
|
||||
imageSrc={imageSrc}
|
||||
alt={alt}
|
||||
indicator={
|
||||
organizationSlug ? (
|
||||
<div
|
||||
className={classNames("absolute bottom-0 right-0 z-10", size === "lg" ? "h-6 w-6" : "h-10 w-10")}>
|
||||
<img
|
||||
src={`/org/${organizationSlug}/avatar.png`}
|
||||
alt={alt}
|
||||
className="flex h-full items-center justify-center rounded-full"
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationAvatar;
|
|
@ -0,0 +1,47 @@
|
|||
import classNames from "@calcom/lib/classNames";
|
||||
import { getOrgAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
// import { Avatar } from "@calcom/ui";
|
||||
import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar";
|
||||
|
||||
type OrganizationMemberAvatarProps = React.ComponentProps<typeof UserAvatar> & {
|
||||
organization: {
|
||||
id: number;
|
||||
slug: string | null;
|
||||
requestedSlug: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the user's avatar along with a small organization's avatar
|
||||
*/
|
||||
const OrganizationMemberAvatar = ({
|
||||
size,
|
||||
user,
|
||||
organization,
|
||||
previewSrc,
|
||||
...rest
|
||||
}: OrganizationMemberAvatarProps) => {
|
||||
return (
|
||||
<UserAvatar
|
||||
data-testid="organization-avatar"
|
||||
size={size}
|
||||
user={user}
|
||||
previewSrc={previewSrc}
|
||||
indicator={
|
||||
organization ? (
|
||||
<div
|
||||
className={classNames("absolute bottom-0 right-0 z-10", size === "lg" ? "h-6 w-6" : "h-10 w-10")}>
|
||||
<img
|
||||
src={getOrgAvatarUrl(organization)}
|
||||
alt={user.username || ""}
|
||||
className="flex h-full items-center justify-center rounded-full"
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationMemberAvatar;
|
|
@ -1,6 +1,7 @@
|
|||
import type { Prisma } from "@prisma/client";
|
||||
|
||||
import { ALLOWED_HOSTNAMES, RESERVED_SUBDOMAINS, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
|
||||
/**
|
||||
|
@ -18,6 +19,12 @@ export function getOrgSlug(hostname: string) {
|
|||
const testHostname = `${url.hostname}${url.port ? `:${url.port}` : ""}`;
|
||||
return testHostname.endsWith(`.${ahn}`);
|
||||
});
|
||||
logger.debug(`getOrgSlug: ${hostname} ${currentHostname}`, {
|
||||
ALLOWED_HOSTNAMES,
|
||||
WEBAPP_URL,
|
||||
currentHostname,
|
||||
hostname,
|
||||
});
|
||||
if (currentHostname) {
|
||||
// Define which is the current domain/subdomain
|
||||
const slug = hostname.replace(`.${currentHostname}` ?? "", "");
|
||||
|
@ -29,6 +36,7 @@ export function getOrgSlug(hostname: string) {
|
|||
export function orgDomainConfig(hostname: string, fallback?: string | string[]) {
|
||||
const currentOrgDomain = getOrgSlug(hostname);
|
||||
const isValidOrgDomain = currentOrgDomain !== null && !RESERVED_SUBDOMAINS.includes(currentOrgDomain);
|
||||
logger.debug(`orgDomainConfig: ${hostname} ${currentOrgDomain} ${isValidOrgDomain}`);
|
||||
if (isValidOrgDomain || !fallback) {
|
||||
return {
|
||||
currentOrgDomain: isValidOrgDomain ? currentOrgDomain : null,
|
||||
|
@ -48,10 +56,14 @@ export function subdomainSuffix() {
|
|||
return urlSplit.length === 3 ? urlSplit.slice(1).join(".") : urlSplit.join(".");
|
||||
}
|
||||
|
||||
export function getOrgFullDomain(slug: string, options: { protocol: boolean } = { protocol: true }) {
|
||||
export function getOrgFullOrigin(slug: string, options: { protocol: boolean } = { protocol: true }) {
|
||||
if (!slug) return WEBAPP_URL;
|
||||
return `${options.protocol ? `${new URL(WEBAPP_URL).protocol}//` : ""}${slug}.${subdomainSuffix()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated You most probably intend to query for an organization only, use `whereClauseForOrgWithSlugOrRequestedSlug` instead which will only return the organization and not a team accidentally.
|
||||
*/
|
||||
export function getSlugOrRequestedSlug(slug: string) {
|
||||
const slugifiedValue = slugify(slug);
|
||||
return {
|
||||
|
@ -67,6 +79,26 @@ export function getSlugOrRequestedSlug(slug: string) {
|
|||
} satisfies Prisma.TeamWhereInput;
|
||||
}
|
||||
|
||||
export function whereClauseForOrgWithSlugOrRequestedSlug(slug: string) {
|
||||
const slugifiedValue = slugify(slug);
|
||||
|
||||
return {
|
||||
OR: [
|
||||
{ slug: slugifiedValue },
|
||||
{
|
||||
metadata: {
|
||||
path: ["requestedSlug"],
|
||||
equals: slug,
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
path: ["isOrganization"],
|
||||
equals: true,
|
||||
},
|
||||
} satisfies Prisma.TeamWhereInput;
|
||||
}
|
||||
|
||||
export function userOrgQuery(hostname: string, fallback?: string | string[]) {
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(hostname, fallback);
|
||||
return isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null;
|
||||
|
|
|
@ -11,7 +11,6 @@ import type { RouterOutputs } from "@calcom/trpc/react";
|
|||
import { trpc } from "@calcom/trpc/react";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ConfirmationDialogContent,
|
||||
|
@ -29,6 +28,7 @@ import {
|
|||
Tooltip,
|
||||
} from "@calcom/ui";
|
||||
import { ExternalLink, MoreHorizontal, Edit2, Lock, UserX } from "@calcom/ui/components/icon";
|
||||
import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar";
|
||||
|
||||
import MemberChangeRoleModal from "./MemberChangeRoleModal";
|
||||
import TeamAvailabilityModal from "./TeamAvailabilityModal";
|
||||
|
@ -141,13 +141,7 @@ export default function MemberListItem(props: Props) {
|
|||
<div className="my-4 flex justify-between">
|
||||
<div className="flex w-full flex-col justify-between truncate sm:flex-row">
|
||||
<div className="flex">
|
||||
<Avatar
|
||||
size="sm"
|
||||
imageSrc={`${bookerUrl}/${props.member.username}/avatar.png`}
|
||||
alt={name || ""}
|
||||
className="h-10 w-10 rounded-full"
|
||||
/>
|
||||
|
||||
<UserAvatar size="sm" user={props.member} className="h-10 w-10 rounded-full" />
|
||||
<div className="ms-3 inline-block">
|
||||
<div className="mb-1 flex">
|
||||
<span className="text-default mr-2 text-sm font-bold leading-4">{name}</span>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { z } from "zod";
|
||||
|
||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
@ -235,7 +235,7 @@ const ProfileView = () => {
|
|||
value={value}
|
||||
addOnLeading={
|
||||
team.parent && orgBranding
|
||||
? `${getOrgFullDomain(orgBranding?.slug, { protocol: false })}/`
|
||||
? `${getOrgFullOrigin(orgBranding?.slug, { protocol: false })}/`
|
||||
: `${WEBAPP_URL}/team/`
|
||||
}
|
||||
onChange={(e) => {
|
||||
|
|
|
@ -33,6 +33,9 @@ const userBodySchema = User.pick({
|
|||
avatar: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated in favour of @calcom/lib/getAvatarUrl
|
||||
*/
|
||||
/** This helps to prevent reaching the 4MB payload limit by avoiding base64 and instead passing the avatar url */
|
||||
export function getAvatarUrlFromUser(user: {
|
||||
avatar: string | null;
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
|||
import type { Props } from "react-select";
|
||||
|
||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
@ -63,7 +63,7 @@ export const ChildrenEventTypeSelect = ({
|
|||
<Avatar
|
||||
size="mdLg"
|
||||
className="overflow-visible"
|
||||
imageSrc={`${orgBranding ? getOrgFullDomain(orgBranding.slug) : CAL_URL}/${
|
||||
imageSrc={`${orgBranding ? getOrgFullOrigin(orgBranding.slug) : CAL_URL}/${
|
||||
children.owner.username
|
||||
}/avatar.png`}
|
||||
alt={children.owner.name || children.owner.email || ""}
|
||||
|
|
|
@ -79,6 +79,7 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
|||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
theme: true,
|
||||
organizationId: true,
|
||||
metadata: true,
|
||||
},
|
||||
},
|
||||
|
@ -93,6 +94,7 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
|||
metadata: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
organizationId: true,
|
||||
organization: {
|
||||
select: {
|
||||
name: true,
|
||||
|
@ -130,6 +132,7 @@ export const getPublicEvent = async (
|
|||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
theme: true,
|
||||
organizationId: true,
|
||||
organization: {
|
||||
select: {
|
||||
slug: true,
|
||||
|
@ -291,23 +294,24 @@ function getUsersFromEvent(event: Event) {
|
|||
if (!owner) {
|
||||
return null;
|
||||
}
|
||||
const { username, name, weekStart } = owner;
|
||||
return [{ username, name, weekStart }];
|
||||
const { username, name, weekStart, organizationId } = owner;
|
||||
return [{ username, name, weekStart, organizationId }];
|
||||
}
|
||||
|
||||
async function getOwnerFromUsersArray(prisma: PrismaClient, eventTypeId: number) {
|
||||
const { users } = await prisma.eventType.findUniqueOrThrow({
|
||||
where: { id: eventTypeId },
|
||||
select: { users: { select: { username: true, name: true, weekStart: true } } },
|
||||
select: { users: { select: { username: true, name: true, weekStart: true, organizationId: true } } },
|
||||
});
|
||||
if (!users.length) return null;
|
||||
return [users[0]];
|
||||
}
|
||||
|
||||
function mapHostsToUsers(host: { user: Pick<User, "username" | "name" | "weekStart"> }) {
|
||||
function mapHostsToUsers(host: { user: Pick<User, "username" | "name" | "weekStart" | "organizationId"> }) {
|
||||
return {
|
||||
username: host.user.username,
|
||||
name: host.user.name,
|
||||
weekStart: host.user.weekStart,
|
||||
organizationId: host.user.organizationId,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
|||
import UnconfirmedBookingBadge from "@calcom/features/bookings/UnconfirmedBookingBadge";
|
||||
import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner";
|
||||
import { OrgUpgradeBanner } from "@calcom/features/ee/organizations/components/OrgUpgradeBanner";
|
||||
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem";
|
||||
import { TeamsUpgradeBanner } from "@calcom/features/ee/teams/components";
|
||||
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
||||
|
@ -797,7 +797,7 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
|
|||
|
||||
const publicPageUrl = useMemo(() => {
|
||||
if (!user?.org?.id) return `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user?.username}`;
|
||||
const publicPageUrl = orgBranding?.slug ? getOrgFullDomain(orgBranding.slug) : "";
|
||||
const publicPageUrl = orgBranding?.slug ? getOrgFullOrigin(orgBranding.slug) : "";
|
||||
return publicPageUrl;
|
||||
}, [orgBranding?.slug, user?.username, user?.org?.id]);
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ import type { DateRange } from "@calcom/lib/date-ranges";
|
|||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { Avatar, Button, ButtonGroup, DataTable } from "@calcom/ui";
|
||||
import { Button, ButtonGroup, DataTable } from "@calcom/ui";
|
||||
import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar";
|
||||
|
||||
import { UpgradeTip } from "../../tips/UpgradeTip";
|
||||
import { TBContext, createTimezoneBuddyStore } from "../store";
|
||||
|
@ -18,6 +19,8 @@ import { TimeDial } from "./TimeDial";
|
|||
export interface SliderUser {
|
||||
id: number;
|
||||
username: string | null;
|
||||
name: string | null;
|
||||
organizationId: number;
|
||||
email: string;
|
||||
timeZone: string;
|
||||
role: MembershipRole;
|
||||
|
@ -78,10 +81,17 @@ export function AvailabilitySliderTable() {
|
|||
accessorFn: (data) => data.email,
|
||||
header: "Member",
|
||||
cell: ({ row }) => {
|
||||
const { username, email, timeZone } = row.original;
|
||||
const { username, email, timeZone, name, organizationId } = row.original;
|
||||
return (
|
||||
<div className="max-w-64 flex flex-shrink-0 items-center gap-2 overflow-hidden">
|
||||
<Avatar size="sm" alt={username || email} imageSrc={`/${username}/avatar.png`} />
|
||||
<UserAvatar
|
||||
size="sm"
|
||||
user={{
|
||||
username,
|
||||
name,
|
||||
organizationId,
|
||||
}}
|
||||
/>
|
||||
<div className="">
|
||||
<div className="text-emphasis max-w-64 truncate text-sm font-medium" title={email}>
|
||||
{username || "No username"}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { AVATAR_FALLBACK } from "@calcom/lib/constants";
|
||||
import type { User, Team } from "@calcom/prisma/client";
|
||||
|
||||
/**
|
||||
* Gives an organization aware avatar url for a user
|
||||
* It ensures that the wrong avatar isn't fetched by ensuring that organizationId is always passed
|
||||
*/
|
||||
export const getUserAvatarUrl = (user: Pick<User, "username" | "organizationId">) => {
|
||||
if (!user.username) return AVATAR_FALLBACK;
|
||||
// avatar.png automatically redirects to fallback avatar if user doesn't have one
|
||||
return `${WEBAPP_URL}/${user.username}/avatar.png${
|
||||
user.organizationId ? `?orgId=${user.organizationId}` : ""
|
||||
}`;
|
||||
};
|
||||
|
||||
export const getOrgAvatarUrl = (org: {
|
||||
id: Team["id"];
|
||||
slug: Team["slug"];
|
||||
requestedSlug: string | null;
|
||||
}) => {
|
||||
const slug = org.slug ?? org.requestedSlug;
|
||||
return `${WEBAPP_URL}/org/${slug}/avatar.png`;
|
||||
};
|
|
@ -4,7 +4,7 @@ import { getLocationGroupedOptions } from "@calcom/app-store/server";
|
|||
import type { StripeData } from "@calcom/app-store/stripepayment/lib/server";
|
||||
import { getEventTypeAppData } from "@calcom/app-store/utils";
|
||||
import type { LocationObject } from "@calcom/core/location";
|
||||
import { getOrgFullDomain } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { parseBookingLimit, parseDurationLimit, parseRecurringEvent } from "@calcom/lib";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
|
@ -298,7 +298,7 @@ export default async function getEventTypeById({
|
|||
const eventTypeUsers: ((typeof eventType.users)[number] & { avatar: string })[] = eventType.users.map(
|
||||
(user) => ({
|
||||
...user,
|
||||
avatar: `${eventType.team?.parent?.slug ? getOrgFullDomain(eventType.team?.parent?.slug) : CAL_URL}/${
|
||||
avatar: `${eventType.team?.parent?.slug ? getOrgFullOrigin(eventType.team?.parent?.slug) : CAL_URL}/${
|
||||
user.username
|
||||
}/avatar.png`,
|
||||
})
|
||||
|
@ -348,7 +348,7 @@ export default async function getEventTypeById({
|
|||
...member.user,
|
||||
avatar: `${
|
||||
eventTypeObject.team?.parent?.slug
|
||||
? getOrgFullDomain(eventTypeObject.team?.parent?.slug)
|
||||
? getOrgFullOrigin(eventTypeObject.team?.parent?.slug)
|
||||
: CAL_URL
|
||||
}/${member.user.username}/avatar.png`,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { subdomainSuffix, getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { subdomainSuffix, getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
|
@ -19,7 +19,7 @@ export const getBrand = async (orgId: number | null) => {
|
|||
});
|
||||
const metadata = teamMetadataSchema.parse(org?.metadata);
|
||||
const slug = (org?.slug || metadata?.requestedSlug) as string;
|
||||
const fullDomain = getOrgFullDomain(slug);
|
||||
const fullDomain = getOrgFullOrigin(slug);
|
||||
const domainSuffix = subdomainSuffix();
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { getAppFromSlug } from "@calcom/app-store/utils";
|
||||
import { getSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { getOrgFullOrigin, getSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import prisma, { baseEventTypeSelect } from "@calcom/prisma";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
@ -30,6 +30,12 @@ export async function getTeamWithMembers(args: {
|
|||
name: true,
|
||||
id: true,
|
||||
bio: true,
|
||||
organizationId: true,
|
||||
organization: {
|
||||
select: {
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
select: {
|
||||
team: {
|
||||
|
@ -163,6 +169,7 @@ export async function getTeamWithMembers(args: {
|
|||
? obj.user.teams.filter((obj) => obj.team.slug !== orgSlug).map((obj) => obj.team.slug)
|
||||
: null,
|
||||
avatar: `${WEBAPP_URL}/${obj.user.username}/avatar.png`,
|
||||
orgOrigin: getOrgFullOrigin(obj.user.organization?.slug || ""),
|
||||
connectedApps: !isTeamView
|
||||
? credentials?.map((cred) => {
|
||||
const appSlug = cred.app?.slug;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { getOrgFullDomain } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
type MeOptions = {
|
||||
|
@ -25,9 +24,7 @@ export const meHandler = async ({ ctx }: MeOptions) => {
|
|||
locale: user.locale,
|
||||
timeFormat: user.timeFormat,
|
||||
timeZone: user.timeZone,
|
||||
avatar: `${user.organization?.slug ? getOrgFullDomain(user.organization.slug) : WEBAPP_URL}/${
|
||||
user.username
|
||||
}/avatar.png`,
|
||||
avatar: getUserAvatarUrl(user),
|
||||
createdDate: user.createdDate,
|
||||
trialEndsAt: user.trialEndsAt,
|
||||
defaultScheduleId: user.defaultScheduleId,
|
||||
|
|
|
@ -41,7 +41,9 @@ async function getTeamMembers({
|
|||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
organizationId: true,
|
||||
username: true,
|
||||
name: true,
|
||||
email: true,
|
||||
timeZone: true,
|
||||
defaultScheduleId: true,
|
||||
|
@ -63,6 +65,8 @@ async function buildMember(member: Member, dateFrom: Dayjs, dateTo: Dayjs) {
|
|||
if (!member.user.defaultScheduleId) {
|
||||
return {
|
||||
id: member.user.id,
|
||||
organizationId: member.user.organizationId,
|
||||
name: member.user.name,
|
||||
username: member.user.username,
|
||||
email: member.user.email,
|
||||
timeZone: member.user.timeZone,
|
||||
|
@ -89,6 +93,8 @@ async function buildMember(member: Member, dateFrom: Dayjs, dateTo: Dayjs) {
|
|||
id: member.user.id,
|
||||
username: member.user.username,
|
||||
email: member.user.email,
|
||||
organizationId: member.user.organizationId,
|
||||
name: member.user.name,
|
||||
timeZone,
|
||||
role: member.role,
|
||||
defaultScheduleId: member.user.defaultScheduleId ?? -1,
|
||||
|
|
Loading…
Reference in New Issue