Compare commits
13
Commits
v3.7.17
...
v3.8.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bd0428d18 | ||
|
|
c7089b9db6 | ||
|
|
81ad00a816 | ||
|
|
b5730da56d | ||
|
|
9455d14ad5 | ||
|
|
a056160404 | ||
|
|
6d53cd84f8 | ||
|
|
30cb8f745d | ||
|
|
bafa3c97ac | ||
|
|
bb48ccbad9 | ||
|
|
0a68c6d048 | ||
|
|
bb9f8e5d38 | ||
|
|
c99bb190f1 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "3.7.17",
|
||||
"version": "3.8.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -53,11 +53,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
|
||||
const hashedPassword = await hashPassword(newPassword);
|
||||
await prisma.userPassword.update({
|
||||
await prisma.userPassword.upsert({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
data: {
|
||||
create: {
|
||||
hash: hashedPassword,
|
||||
userId: user.id,
|
||||
},
|
||||
update: {
|
||||
hash: hashedPassword,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -42,8 +42,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
},
|
||||
data: {
|
||||
password: {
|
||||
update: {
|
||||
hash: hashedPassword,
|
||||
upsert: {
|
||||
create: { hash: hashedPassword },
|
||||
update: { hash: hashedPassword },
|
||||
},
|
||||
},
|
||||
emailVerified: new Date(),
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function Apps({ apps }: PageProps) {
|
||||
<div className="mb-16">
|
||||
<div className="grid-col-1 grid grid-cols-1 gap-3 md:grid-cols-3">
|
||||
{apps
|
||||
.sort((a, b) => (b.installCount || 0) - (a.installCount || 0))
|
||||
?.sort((a, b) => (b.installCount || 0) - (a.installCount || 0))
|
||||
.map((app) => {
|
||||
return <AppCard key={app.slug} app={app} />;
|
||||
})}
|
||||
|
||||
@@ -2195,7 +2195,7 @@
|
||||
"create_your_org_description": "Upgrade to Enterprise and receive a subdomain, unified billing, Insights, extensive whitelabeling and more",
|
||||
"other_payment_app_enabled": "You can only enable one payment app per event type",
|
||||
"admin_delete_organization_description": "<ul><li>Teams that are member of this organization will also be deleted along with their event-types</li><li>Users that were part of the organization will not be deleted and their event-types will also remain intact.</li><li>Usernames would be changed to allow them to exist outside the organization</li></ul>",
|
||||
"admin_delete_organization_title": "Delete organization?",
|
||||
"admin_delete_organization_title": "Delete {{organizationName}}?",
|
||||
"published": "Published",
|
||||
"unpublished": "Unpublished",
|
||||
"publish": "Publish",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
|
||||
export const scopes = [
|
||||
"https://www.googleapis.com/auth/calendar.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
];
|
||||
|
||||
let client_id = "";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Auth } from "googleapis";
|
||||
import { google } from "googleapis";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { WEBAPP_URL_FOR_OAUTH, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
@@ -91,6 +93,10 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
throw new HttpError({ message: "Internal Error", statusCode: 500 });
|
||||
}
|
||||
|
||||
// Update the user's profile photo with google profile photo if it's null
|
||||
// Since we don't want to block the user from using the app if this fails, we don't await this
|
||||
updateProfilePhoto(oAuth2Client, req.session.user.id);
|
||||
|
||||
const credential = await prisma.credential.create({
|
||||
data: {
|
||||
type: "google_calendar",
|
||||
@@ -156,6 +162,23 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
);
|
||||
}
|
||||
|
||||
async function updateProfilePhoto(oAuth2Client: Auth.OAuth2Client, userId: number) {
|
||||
try {
|
||||
const oauth2 = google.oauth2({ version: "v2", auth: oAuth2Client });
|
||||
const userDetails = await oauth2.userinfo.get();
|
||||
if (userDetails.data?.picture) {
|
||||
await prisma.user.update({
|
||||
where: { id: userId, avatarUrl: null, avatar: null },
|
||||
data: {
|
||||
avatarUrl: userDetails.data.picture,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error updating avatarUrl from google calendar connect", error);
|
||||
}
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import Link from "next/link";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import React, { useCallback, useState, useEffect } from "react";
|
||||
import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder";
|
||||
// types
|
||||
import type { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder";
|
||||
@@ -98,7 +98,7 @@ const Route = ({
|
||||
|
||||
const index = routes.indexOf(route);
|
||||
|
||||
const { data: eventTypesByGroup } = trpc.viewer.eventTypes.getByViewer.useQuery({
|
||||
const { data: eventTypesByGroup, isLoading } = trpc.viewer.eventTypes.getByViewer.useQuery({
|
||||
forRoutingForms: true,
|
||||
});
|
||||
|
||||
@@ -138,9 +138,15 @@ const Route = ({
|
||||
? eventOptions[0].value.substring(0, eventOptions[0].value.lastIndexOf("/") + 1)
|
||||
: "";
|
||||
|
||||
const [customEventTypeSlug, setCustomEventTypeSlug] = useState<string>(
|
||||
!isRouter(route) ? route.action.value.split("/").pop() ?? "" : ""
|
||||
);
|
||||
const [customEventTypeSlug, setCustomEventTypeSlug] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
const isCustom =
|
||||
!isRouter(route) && !eventOptions.find((eventOption) => eventOption.value === route.action.value);
|
||||
setCustomEventTypeSlug(isCustom && !isRouter(route) ? route.action.value.split("/").pop() ?? "" : "");
|
||||
}
|
||||
}, [isLoading]);
|
||||
|
||||
const onChange = (route: Route, immutableTree: ImmutableTree, config: QueryBuilderUpdatedConfig) => {
|
||||
const jsonTree = QbUtils.getTree(immutableTree);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Membership, Team, UserPermissionRole } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||
import type { AuthOptions, Session } from "next-auth";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
import { encode } from "next-auth/jwt";
|
||||
@@ -806,18 +807,32 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||
existingUserWithEmail.identityProvider === IdentityProvider.CAL &&
|
||||
(idP === IdentityProvider.GOOGLE || idP === IdentityProvider.SAML)
|
||||
) {
|
||||
await prisma.user.update({
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { email: existingUserWithEmail.email },
|
||||
// also update email to the IdP email
|
||||
data: {
|
||||
password: {
|
||||
delete: true,
|
||||
},
|
||||
email: user.email,
|
||||
identityProvider: idP,
|
||||
identityProviderId: account.providerAccountId,
|
||||
},
|
||||
});
|
||||
|
||||
// safely delete password from UserPassword table if it exists
|
||||
try {
|
||||
await prisma.userPassword.delete({
|
||||
where: { userId: updatedUser.id },
|
||||
});
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof PrismaClientKnownRequestError &&
|
||||
(err.code === "P2025" || err.code === "P2016")
|
||||
) {
|
||||
log.warn("UserPassword not found for user", safeStringify(existingUserWithEmail));
|
||||
} else {
|
||||
log.warn("Could not delete UserPassword for user", safeStringify(existingUserWithEmail));
|
||||
}
|
||||
}
|
||||
|
||||
if (existingUserWithEmail.twoFactorEnabled) {
|
||||
return loginWithTotp(existingUserWithEmail.email);
|
||||
} else {
|
||||
|
||||
@@ -73,7 +73,7 @@ function AdminOrgTable() {
|
||||
});
|
||||
};
|
||||
|
||||
const [orgToDelete, setOrgToDelete] = useState<number | null>(null);
|
||||
const [orgToDelete, setOrgToDelete] = useState<(typeof data)[number] | null>(null);
|
||||
return (
|
||||
<div>
|
||||
<Table>
|
||||
@@ -189,7 +189,7 @@ function AdminOrgTable() {
|
||||
id: "delete",
|
||||
label: t("delete"),
|
||||
onClick: () => {
|
||||
setOrgToDelete(org.id);
|
||||
setOrgToDelete(org);
|
||||
},
|
||||
icon: Trash,
|
||||
},
|
||||
@@ -202,12 +202,12 @@ function AdminOrgTable() {
|
||||
</Body>
|
||||
</Table>
|
||||
<DeleteOrgDialog
|
||||
orgId={orgToDelete}
|
||||
org={orgToDelete}
|
||||
onClose={() => setOrgToDelete(null)}
|
||||
onConfirm={() => {
|
||||
if (!orgToDelete) return;
|
||||
deleteMutation.mutate({
|
||||
orgId: orgToDelete,
|
||||
orgId: orgToDelete.id,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -232,28 +232,35 @@ AdminOrgList.getLayout = getLayout;
|
||||
export default AdminOrgList;
|
||||
|
||||
const DeleteOrgDialog = ({
|
||||
orgId,
|
||||
org,
|
||||
onConfirm,
|
||||
onClose,
|
||||
}: {
|
||||
orgId: number | null;
|
||||
org: {
|
||||
id: number;
|
||||
name: string;
|
||||
} | null;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
if (!org) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- noop
|
||||
<Dialog name="delete-user" open={!!orgId} onOpenChange={(open) => (open ? () => {} : onClose())}>
|
||||
<Dialog name="delete-user" open={!!org.id} onOpenChange={(open) => (open ? () => {} : onClose())}>
|
||||
<ConfirmationDialogContent
|
||||
title={t("admin_delete_organization_title")}
|
||||
title={t("admin_delete_organization_title", {
|
||||
organizationName: org.name,
|
||||
})}
|
||||
confirmBtnText={t("delete")}
|
||||
cancelBtnText={t("cancel")}
|
||||
variety="danger"
|
||||
onConfirm={onConfirm}>
|
||||
<Trans
|
||||
i18nKey="admin_delete_organization_description"
|
||||
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
|
||||
components={{ li: <li />, ul: <ul className="ml-4 mt-5 list-disc space-y-2" /> }}>
|
||||
<ul>
|
||||
<li>
|
||||
Teams that are member of this organization will also be deleted along with their event-types
|
||||
|
||||
@@ -17,9 +17,10 @@ import { filterQuerySchema } from "../lib/getTeamsFiltersFromQuery";
|
||||
export type IEventTypesFilters = RouterOutputs["viewer"]["eventTypes"]["listWithTeam"];
|
||||
export type IEventTypeFilter = IEventTypesFilters[0];
|
||||
|
||||
export const TEAMS_FILTER_KEY = "TEAMS_FILTER_KEY";
|
||||
function useFilterQuery() {
|
||||
// passthrough allows additional params to not be removed
|
||||
return useTypedQuery(filterQuerySchema.passthrough());
|
||||
return useTypedQuery(filterQuerySchema.passthrough(), TEAMS_FILTER_KEY);
|
||||
}
|
||||
|
||||
export const TeamsFilter = ({
|
||||
|
||||
@@ -80,9 +80,13 @@ export const deleteDnsRecord = async (domain: string) => {
|
||||
assertCloudflareEnvVars(process.env);
|
||||
|
||||
const dnsRecordToDelete = await getDnsRecordToDelete();
|
||||
await deleteDnsRecord(dnsRecordToDelete);
|
||||
if (dnsRecordToDelete) {
|
||||
await deleteDnsRecord(dnsRecordToDelete);
|
||||
log.info(`Deleted dns-record in Cloudflare: ${domain}`);
|
||||
} else {
|
||||
log.info(`CNAME not found in Cloudflare: ${domain}. Nothing to delete`);
|
||||
}
|
||||
|
||||
log.info(`Deleted dns-record in Cloudflare: ${domain}`);
|
||||
return true;
|
||||
|
||||
async function getDnsRecordToDelete() {
|
||||
@@ -134,7 +138,7 @@ export const deleteDnsRecord = async (domain: string) => {
|
||||
});
|
||||
}
|
||||
|
||||
return searchResult.result[0];
|
||||
return searchResult.result[0] as (typeof searchResult.result)[0] | null;
|
||||
}
|
||||
|
||||
async function deleteDnsRecord(dnsRecordToDelete: { id: string }) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { subdomainSuffix } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
import { deleteDnsRecord, addDnsRecord } from "./deploymentServices/cloudflare";
|
||||
import {
|
||||
@@ -6,6 +7,7 @@ import {
|
||||
createDomain as createVercelDomain,
|
||||
} from "./deploymentServices/vercel";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["domainManager/organization"] });
|
||||
export const deleteDomain = async (slug: string) => {
|
||||
const domain = `${slug}.${subdomainSuffix()}`;
|
||||
// We must have some domain deleted
|
||||
@@ -22,6 +24,7 @@ export const deleteDomain = async (slug: string) => {
|
||||
isDnsRecordDeleted = await deleteDnsRecord(domain);
|
||||
}
|
||||
return isDomainDeleted && isDnsRecordDeleted;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const createDomain = async (slug: string) => {
|
||||
@@ -48,6 +51,10 @@ export const renameDomain = async (oldSlug: string | null, newSlug: string) => {
|
||||
// First create new domain so that if it fails we still have the old domain
|
||||
await createDomain(newSlug);
|
||||
if (oldSlug) {
|
||||
await deleteDomain(oldSlug);
|
||||
try {
|
||||
await deleteDomain(oldSlug);
|
||||
} catch (e) {
|
||||
log.error(`renameDomain: Failed to delete old domain ${oldSlug}. Do a manual deletion if needed`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { usePathname, useRouter } from "next/navigation";
|
||||
import { useCallback, useMemo, useEffect } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { localStorage } from "../webstorage";
|
||||
import { useRouterQuery } from "./useRouterQuery";
|
||||
|
||||
type OptionalKeys<T> = {
|
||||
@@ -32,7 +33,7 @@ export const queryStringArray = z
|
||||
.preprocess((a) => z.string().parse(a).split(","), z.string().array())
|
||||
.or(z.string().array());
|
||||
|
||||
export function useTypedQuery<T extends z.AnyZodObject>(schema: T) {
|
||||
export function useTypedQuery<T extends z.AnyZodObject>(schema: T, localStorageKey: string | null = null) {
|
||||
type Output = z.infer<typeof schema>;
|
||||
type FullOutput = Required<Output>;
|
||||
type OutputKeys = Required<keyof FullOutput>;
|
||||
@@ -43,7 +44,7 @@ export function useTypedQuery<T extends z.AnyZodObject>(schema: T) {
|
||||
const unparsedQuery = useRouterQuery();
|
||||
const pathname = usePathname();
|
||||
const parsedQuerySchema = schema.safeParse(unparsedQuery);
|
||||
|
||||
const useLocalStorage = !!localStorageKey;
|
||||
let parsedQuery: Output = useMemo(() => {
|
||||
return {} as Output;
|
||||
}, []);
|
||||
@@ -73,6 +74,39 @@ export function useTypedQuery<T extends z.AnyZodObject>(schema: T) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[parsedQuery, router]
|
||||
);
|
||||
//If the value for {localStorageKey} exist in localStorage set it to queryParams
|
||||
useEffect(() => {
|
||||
if (!useLocalStorage) return;
|
||||
const storedValueString = localStorage.getItem(localStorageKey);
|
||||
if (!storedValueString || storedValueString == "{}") return;
|
||||
const storedValue: Output = JSON.parse(storedValueString);
|
||||
if (storedValue) {
|
||||
const searchParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(storedValue)) {
|
||||
searchParams.set(String(key), String(value));
|
||||
}
|
||||
router.replace(`${pathname}?${searchParams.toString()}`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function setLocalStorage<J extends OutputKeys>(key: J, value: Output[J]) {
|
||||
if (!useLocalStorage) return;
|
||||
const storedValue: Output = JSON.parse(localStorage.getItem(localStorageKey) || "{}");
|
||||
const newValue = { ...storedValue, [key]: value };
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(newValue));
|
||||
}
|
||||
function removeByKeyFromLocalStorage(key: OutputOptionalKeys) {
|
||||
if (!useLocalStorage) return;
|
||||
const storedValue: Output = JSON.parse(localStorage.getItem(localStorageKey) || "{}");
|
||||
const newValue = storedValue;
|
||||
delete newValue[key];
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(newValue));
|
||||
}
|
||||
//Remove {localStorageKey} from the localStorage
|
||||
function removeAllValuesFromLocalStorage() {
|
||||
if (!useLocalStorage) return;
|
||||
localStorage.removeItem(localStorageKey);
|
||||
}
|
||||
|
||||
// Delete a key from the query
|
||||
function removeByKey(key: OutputOptionalKeys) {
|
||||
@@ -88,9 +122,13 @@ export function useTypedQuery<T extends z.AnyZodObject>(schema: T) {
|
||||
if (existingValue.includes(value)) return; // prevent adding the same value to the array
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
setQuery(key, [...existingValue, value]);
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
setLocalStorage(key, [...existingValue, value]);
|
||||
} else {
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
setQuery(key, [value]);
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
setLocalStorage(key, [value]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +139,12 @@ export function useTypedQuery<T extends z.AnyZodObject>(schema: T) {
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
const newValue = existingValue.filter((item) => item !== value);
|
||||
setQuery(key, newValue);
|
||||
setLocalStorage(key, newValue);
|
||||
} else {
|
||||
// @ts-expect-error this is too much for TS it seems
|
||||
removeByKey(key);
|
||||
//@ts-expect-error this is too much for TS it seems
|
||||
removeByKeyFromLocalStorage(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +152,7 @@ export function useTypedQuery<T extends z.AnyZodObject>(schema: T) {
|
||||
function removeAllQueryParams() {
|
||||
if (pathname !== null) {
|
||||
router.replace(pathname);
|
||||
removeAllValuesFromLocalStorage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,11 +49,15 @@ export const changePasswordHandler = async ({ input, ctx }: ChangePasswordOption
|
||||
}
|
||||
|
||||
const hashedPassword = await hashPassword(newPassword);
|
||||
await prisma.userPassword.update({
|
||||
await prisma.userPassword.upsert({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
data: {
|
||||
create: {
|
||||
hash: hashedPassword,
|
||||
userId: user.id,
|
||||
},
|
||||
update: {
|
||||
hash: hashedPassword,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,7 +40,11 @@ export const adminDeleteHandler = async ({ input }: AdminDeleteOption) => {
|
||||
});
|
||||
|
||||
if (foundOrg.slug) {
|
||||
await deleteDomain(foundOrg.slug);
|
||||
try {
|
||||
await deleteDomain(foundOrg.slug);
|
||||
} catch (e) {
|
||||
log.error(`Failed to delete domain ${foundOrg.slug}. Do a manual deletion if needed`);
|
||||
}
|
||||
}
|
||||
|
||||
await renameUsersToAvoidUsernameConflicts(foundOrg.members.map((member) => member.user));
|
||||
|
||||
@@ -45,11 +45,15 @@ export const setPasswordHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
});
|
||||
|
||||
const hashedPassword = await hashPassword(newPassword);
|
||||
await prisma.userPassword.update({
|
||||
await prisma.userPassword.upsert({
|
||||
where: {
|
||||
userId: ctx.user.id,
|
||||
},
|
||||
data: {
|
||||
create: {
|
||||
hash: hashedPassword,
|
||||
userId: ctx.user.id,
|
||||
},
|
||||
update: {
|
||||
hash: hashedPassword,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ export type AvatarProps = {
|
||||
imageSrc?: Maybe<string>;
|
||||
title?: string;
|
||||
alt: string;
|
||||
href?: string;
|
||||
href?: string | null;
|
||||
fallback?: React.ReactNode;
|
||||
accepted?: boolean;
|
||||
asChild?: boolean; // Added to ignore the outer span on the fallback component - messes up styling
|
||||
|
||||
@@ -8,7 +8,7 @@ export type AvatarGroupProps = {
|
||||
image: string;
|
||||
title?: string;
|
||||
alt?: string;
|
||||
href?: string;
|
||||
href?: string | null;
|
||||
}[];
|
||||
className?: string;
|
||||
truncateAfter?: number;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||
@@ -15,9 +16,12 @@ type UserAvatarProps = Omit<React.ComponentProps<typeof AvatarGroup>, "items"> &
|
||||
|
||||
export function UserAvatarGroupWithOrg(props: UserAvatarProps) {
|
||||
const { users, organization, ...rest } = props;
|
||||
const isEmbed = useIsEmbed();
|
||||
|
||||
const items = [
|
||||
{
|
||||
href: getBookerBaseUrlSync(organization.slug),
|
||||
// We don't want booker to be able to see the list of other users or teams inside the embed
|
||||
href: isEmbed ? null : getBookerBaseUrlSync(organization.slug),
|
||||
image: `${WEBAPP_URL}/team/${organization.slug}/avatar.png`,
|
||||
alt: organization.name || undefined,
|
||||
title: organization.name,
|
||||
|
||||
Reference in New Issue
Block a user