feat: team admin: see connected apps of team members (#11036)
* added feature:team admin can see connected apps of members * fixed the type error * Update packages/lib/server/queries/teams/index.ts * Minor fixes --------- Co-authored-by: alannnc <alannnc@gmail.com>
This commit is contained in:
1 parent
25684f9040
commit
3204d0af34
2 files changed
+68
-13
No files matched your search
@@ -109,7 +109,22 @@ export default function MemberListItem(props: Props) {
|
||||
const bookerUrl = useBookerUrl();
|
||||
const bookerUrlWithoutProtocol = bookerUrl.replace(/^https?:\/\//, "");
|
||||
const bookingLink = !!props.member.username && `${bookerUrlWithoutProtocol}/${props.member.username}`;
|
||||
|
||||
const isAdmin = props.team && ["ADMIN", "OWNER"].includes(props.team.membership?.role);
|
||||
const appList = props.member.connectedApps.map(({ logo, name, externalId }) => {
|
||||
return logo ? (
|
||||
externalId ? (
|
||||
<div className="ltr:mr-2 rtl:ml-2 ">
|
||||
<Tooltip content={externalId}>
|
||||
<img className="h-5 w-5" src={logo} alt={`${name} logo`} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="ltr:mr-2 rtl:ml-2">
|
||||
<img className="h-5 w-5" src={logo} alt={`${name} logo`} />
|
||||
</div>
|
||||
)
|
||||
) : null;
|
||||
});
|
||||
return (
|
||||
<li className="divide-subtle divide-y px-5">
|
||||
<div className="my-4 flex justify-between">
|
||||
@@ -124,9 +139,9 @@ export default function MemberListItem(props: Props) {
|
||||
|
||||
<div className="ms-3 inline-block">
|
||||
<div className="mb-1 flex">
|
||||
<span className="text-default mr-1 text-sm font-bold leading-4">{name}</span>
|
||||
|
||||
<span className="text-default mr-2 text-sm font-bold leading-4">{name}</span>
|
||||
{!props.member.accepted && <TeamPill color="orange" text={t("pending")} />}
|
||||
{isAdmin && props.member.accepted && appList}
|
||||
{props.member.role && <TeamRole role={props.member.role} />}
|
||||
</div>
|
||||
<div className="text-default flex items-center">
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
import { getAppFromSlug } from "@calcom/app-store/utils";
|
||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
||||
import { getSlugOrRequestedSlug } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import prisma, { baseEventTypeSelect } from "@calcom/prisma";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { AppCategories, SchedulingType } from "@calcom/prisma/enums";
|
||||
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { WEBAPP_URL } from "../../../constants";
|
||||
@@ -22,6 +24,17 @@ export async function getTeamWithMembers(args: {
|
||||
name: true,
|
||||
id: true,
|
||||
bio: true,
|
||||
destinationCalendar: {
|
||||
select: {
|
||||
externalId: true,
|
||||
},
|
||||
},
|
||||
selectedCalendars: true,
|
||||
credentials: {
|
||||
include: {
|
||||
app: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
const teamSelect = Prisma.validator<Prisma.TeamSelect>()({
|
||||
id: true,
|
||||
@@ -111,16 +124,43 @@ export async function getTeamWithMembers(args: {
|
||||
});
|
||||
|
||||
if (!team) return null;
|
||||
const members = team.members.map((obj) => {
|
||||
return {
|
||||
...obj.user,
|
||||
role: obj.role,
|
||||
accepted: obj.accepted,
|
||||
disableImpersonation: obj.disableImpersonation,
|
||||
avatar: `${WEBAPP_URL}/${obj.user.username}/avatar.png`,
|
||||
};
|
||||
});
|
||||
const members = await Promise.all(
|
||||
team.members.map(async (obj) => {
|
||||
const calendarCredentials = getCalendarCredentials(obj.user.credentials);
|
||||
|
||||
const { connectedCalendars } = await getConnectedCalendars(
|
||||
calendarCredentials,
|
||||
obj.user.selectedCalendars,
|
||||
obj.user.destinationCalendar?.externalId
|
||||
);
|
||||
const connectedApps = obj.user.credentials
|
||||
.map(({ app, id }) => {
|
||||
const appMetaData = getAppFromSlug(app?.slug);
|
||||
|
||||
if (app?.categories.includes(AppCategories.calendar)) {
|
||||
const externalId = connectedCalendars.find((cal) => cal.credentialId == id)?.primary?.email;
|
||||
return { name: appMetaData?.name, logo: appMetaData?.logo, slug: appMetaData?.slug, externalId };
|
||||
}
|
||||
return { name: appMetaData?.name, logo: appMetaData?.logo, slug: appMetaData?.slug };
|
||||
})
|
||||
.sort((a, b) => (a.slug ?? "").localeCompare(b.slug ?? ""));
|
||||
// Prevent credentials from leaking to frontend
|
||||
const {
|
||||
credentials: _credentials,
|
||||
destinationCalendar: _destinationCalendar,
|
||||
selectedCalendars: _selectedCalendars,
|
||||
...rest
|
||||
} = {
|
||||
...obj.user,
|
||||
role: obj.role,
|
||||
accepted: obj.accepted,
|
||||
disableImpersonation: obj.disableImpersonation,
|
||||
avatar: `${WEBAPP_URL}/${obj.user.username}/avatar.png`,
|
||||
connectedApps,
|
||||
};
|
||||
return rest;
|
||||
})
|
||||
);
|
||||
const eventTypes = team.eventTypes.map((eventType) => ({
|
||||
...eventType,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
|
||||
|
||||
Reference in new issue
Block a user