Invites (#6665)
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
"rejection_confirmation": "Reject the booking",
|
||||
"manage_this_event": "Manage this event",
|
||||
"invite_team_member": "Invite team member",
|
||||
"invite_team_notifcation_badge":"Inv.",
|
||||
"your_event_has_been_scheduled": "Your event has been scheduled",
|
||||
"your_event_has_been_scheduled_recurring": "Your recurring event has been scheduled",
|
||||
"accept_our_license": "Accept our license by changing the .env variable <1>NEXT_PUBLIC_LICENSE_CONSENT</1> to '{{agree}}'.",
|
||||
@@ -1313,6 +1314,7 @@
|
||||
"password_reset_leading": "If you don't receive an email soon, check that the email address you entered is correct, check your spam folder or reach out to support if the issue persists.",
|
||||
"password_updated": "Password updated!",
|
||||
"pending_payment": "Pending payment",
|
||||
"pending_invites":"Pending Invites",
|
||||
"confirmation_page_rainbow": "Token gate your event with tokens or NFTs on Ethereum, Polygon, and more.",
|
||||
"not_on_cal": "Not on {{appName}}",
|
||||
"no_calendar_installed": "No calendar installed",
|
||||
|
||||
@@ -36,19 +36,17 @@ export default function TeamList(props: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ul className="mb-2 divide-y divide-neutral-200 rounded border bg-white">
|
||||
{props.teams.map((team) => (
|
||||
<TeamListItem
|
||||
key={team?.id as number}
|
||||
team={team}
|
||||
onActionSelect={(action: string) => selectAction(action, team?.id as number)}
|
||||
isLoading={deleteTeamMutation.isLoading}
|
||||
hideDropdown={hideDropdown}
|
||||
setHideDropdown={setHideDropdown}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<ul className="mb-2 divide-y divide-neutral-200 rounded border bg-white">
|
||||
{props.teams.map((team) => (
|
||||
<TeamListItem
|
||||
key={team?.id as number}
|
||||
team={team}
|
||||
onActionSelect={(action: string) => selectAction(action, team?.id as number)}
|
||||
isLoading={deleteTeamMutation.isLoading}
|
||||
hideDropdown={hideDropdown}
|
||||
setHideDropdown={setHideDropdown}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ export default function TeamListItem(props: Props) {
|
||||
const acceptOrLeaveMutation = trpc.viewer.teams.acceptOrLeave.useMutation({
|
||||
onSuccess: () => {
|
||||
utils.viewer.teams.list.invalidate();
|
||||
utils.viewer.teams.listInvites.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -88,12 +89,12 @@ export default function TeamListItem(props: Props) {
|
||||
if (!team) return <></>;
|
||||
|
||||
const teamInfo = (
|
||||
<div className="flex px-5 py-5">
|
||||
<div className="item-center flex px-5 py-5">
|
||||
<Avatar
|
||||
size="sm"
|
||||
size="md"
|
||||
imageSrc={getPlaceholderAvatar(team?.logo, team?.name as string)}
|
||||
alt="Team Logo"
|
||||
className="min-h-9 min-w-9 h-9 w-9 rounded-full"
|
||||
className="inline-flex justify-center"
|
||||
/>
|
||||
<div className="inline-block ltr:ml-3 rtl:mr-3">
|
||||
<span className="text-sm font-bold text-gray-700">{team.name}</span>
|
||||
@@ -105,7 +106,7 @@ export default function TeamListItem(props: Props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<li className="divide-y">
|
||||
<li className="">
|
||||
<MemberInvitationModal
|
||||
isOpen={openMemberInvitationModal}
|
||||
onExit={() => {
|
||||
@@ -140,13 +141,14 @@ export default function TeamListItem(props: Props) {
|
||||
<div className="px-5 py-5">
|
||||
{isInvitee ? (
|
||||
<>
|
||||
<div className="hidden sm:block">
|
||||
<div className="hidden justify-center sm:flex">
|
||||
<Button type="button" color="secondary" onClick={declineInvite}>
|
||||
{t("reject")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
color="secondary"
|
||||
StartIcon={FiCheck}
|
||||
className="ltr:ml-2 ltr:mr-2 rtl:ml-2"
|
||||
onClick={acceptInvite}>
|
||||
{t("accept")}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Alert, Button, ButtonGroup } from "@calcom/ui";
|
||||
import { Alert, Button, ButtonGroup, Label } from "@calcom/ui";
|
||||
import { FiUsers, FiRefreshCcw, FiUserPlus, FiMail, FiVideo, FiEyeOff } from "@calcom/ui/components/icon";
|
||||
|
||||
import { UpgradeTip } from "../../../tips";
|
||||
@@ -21,7 +21,8 @@ export function TeamsListing() {
|
||||
},
|
||||
});
|
||||
|
||||
const teams = data?.filter((m) => m.accepted) || [];
|
||||
const teams = useMemo(() => data?.filter((m) => m.accepted) || [], [data]);
|
||||
const invites = useMemo(() => data?.filter((m) => !m.accepted) || [], [data]);
|
||||
|
||||
const features = [
|
||||
{
|
||||
@@ -56,15 +57,19 @@ export function TeamsListing() {
|
||||
},
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLoaderTeamList />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!errorMessage && <Alert severity="error" title={errorMessage} />}
|
||||
|
||||
<UpgradeTip
|
||||
title="calcom_is_better_with_team"
|
||||
description="add_your_team_members"
|
||||
features={features}
|
||||
background="/team-banner-background.jpg"
|
||||
isParentLoading={isLoading && <SkeletonLoaderTeamList />}
|
||||
buttons={
|
||||
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
|
||||
<ButtonGroup>
|
||||
@@ -77,7 +82,17 @@ export function TeamsListing() {
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
}>
|
||||
<TeamList teams={teams} />
|
||||
<>
|
||||
{invites.length > 0 && (
|
||||
<div className="mb-6 rounded-md bg-gray-100 p-5">
|
||||
<Label className="dark:text-darkgray-900 pb-2 font-semibold text-gray-900">
|
||||
{t("pending_invites")}
|
||||
</Label>
|
||||
<TeamList teams={invites} />
|
||||
</div>
|
||||
)}
|
||||
{teams.length > 0 && <TeamList teams={teams} />}
|
||||
</>
|
||||
</UpgradeTip>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -61,6 +61,8 @@ import {
|
||||
FiArrowLeft,
|
||||
} from "@calcom/ui/components/icon";
|
||||
|
||||
import { TeamInviteBadge } from "./TeamInviteBadge";
|
||||
|
||||
/* TODO: Migate this */
|
||||
|
||||
export const ONBOARDING_INTRODUCED_AT = dayjs("September 1 2021").toISOString();
|
||||
@@ -474,6 +476,7 @@ const navigation: NavigationItemType[] = [
|
||||
href: "/teams",
|
||||
icon: FiUsers,
|
||||
onlyDesktop: true,
|
||||
badge: <TeamInviteBadge />,
|
||||
},
|
||||
{
|
||||
name: "apps",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useTeamInvites } from "@calcom/lib/hooks/useHasTeamPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Badge } from "@calcom/ui";
|
||||
|
||||
export function TeamInviteBadge() {
|
||||
const { isLoading, listInvites } = useTeamInvites();
|
||||
const { t } = useLocale();
|
||||
|
||||
if (isLoading || !listInvites || listInvites.length === 0) return null;
|
||||
|
||||
return <Badge variant="default">{t("invite_team_notifcation_badge")}</Badge>;
|
||||
}
|
||||
@@ -4,14 +4,10 @@ import type { ReactNode } from "react";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasTeamPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
// import isCalcom from "@calcom/lib/isCalcom";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import isCalcom from "@calcom/lib/isCalcom";
|
||||
import { EmptyScreen } from "@calcom/ui";
|
||||
import { FiUsers } from "@calcom/ui/components/icon";
|
||||
|
||||
import TeamList from "../ee/teams/components/TeamList";
|
||||
|
||||
const isCalcom = true;
|
||||
export function UpgradeTip({
|
||||
dark,
|
||||
title,
|
||||
@@ -32,19 +28,16 @@ export function UpgradeTip({
|
||||
children: JSX.Element;
|
||||
isParentLoading?: ReactNode;
|
||||
}) {
|
||||
const { data } = trpc.viewer.teams.list.useQuery();
|
||||
|
||||
const invites = useMemo(() => data?.filter((m) => !m.accepted) || [], [data]);
|
||||
const { t } = useLocale();
|
||||
const { isLoading, hasTeamPlan } = useHasTeamPlan();
|
||||
|
||||
if (hasTeamPlan) return children;
|
||||
|
||||
if (isParentLoading || isLoading) return <>{isParentLoading}</>;
|
||||
|
||||
if (!isCalcom)
|
||||
return <EmptyScreen Icon={FiUsers} headline={title} description={description} buttonRaw={buttons} />;
|
||||
|
||||
if (isParentLoading || isLoading) return <>{isParentLoading}</>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="-mt-10 rtl:ml-4 sm:mt-0 md:rtl:ml-0 lg:-mt-6">
|
||||
@@ -63,23 +56,15 @@ export function UpgradeTip({
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
{invites.length > 0 && (
|
||||
<div className="my-4">
|
||||
<h3 className="font-cal mb-4 text-xl">{t("open_invitations")}</h3>
|
||||
<TeamList teams={invites} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 grid-cols-3 md:grid md:gap-4">
|
||||
{invites.length === 0 &&
|
||||
features.map((feature) => (
|
||||
<div
|
||||
key={feature.title}
|
||||
className="mb-4 min-h-[180px] w-full rounded-md bg-gray-50 p-8 md:mb-0">
|
||||
{feature.icon}
|
||||
<h2 className="font-cal mt-4 text-lg">{feature.title}</h2>
|
||||
<p className="text-gray-700">{feature.description}</p>
|
||||
</div>
|
||||
))}
|
||||
{features.map((feature) => (
|
||||
<div key={feature.title} className="mb-4 min-h-[180px] w-full rounded-md bg-gray-50 p-8 md:mb-0">
|
||||
{feature.icon}
|
||||
<h2 className="font-cal mt-4 text-lg">{feature.title}</h2>
|
||||
<p className="text-gray-700">{feature.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -6,4 +6,10 @@ export function useHasTeamPlan() {
|
||||
return { isLoading: hasTeam.isLoading, hasTeamPlan: hasTeam.data?.hasTeamPlan || false };
|
||||
}
|
||||
|
||||
export function useTeamInvites() {
|
||||
const listInvites = trpc.viewer.teams.listInvites.useQuery();
|
||||
|
||||
return { isLoading: listInvites.isLoading, listInvites: listInvites.data };
|
||||
}
|
||||
|
||||
export default useHasTeamPlan;
|
||||
|
||||
@@ -700,4 +700,15 @@ export const viewerTeamsRouter = router({
|
||||
});
|
||||
return { hasTeamPlan: !!hasTeamPlan };
|
||||
}),
|
||||
listInvites: authedProcedure.query(async ({ ctx }) => {
|
||||
const userId = ctx.user.id;
|
||||
return await ctx.prisma.membership.findMany({
|
||||
where: {
|
||||
user: {
|
||||
id: userId,
|
||||
},
|
||||
accepted: false,
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user