perf: SSR for /teams (#20824)
* finish * improve * refactor * add revalidations * /settings/teams is not needed * refactor * rename * redundant type casting
This commit is contained in:
+2
-11
@@ -1,15 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { TeamsListing } from "@calcom/features/ee/teams/components";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
export const TeamsCTA = () => {
|
||||
const { t } = useLocale();
|
||||
const [user] = trpc.viewer.me.get.useSuspenseQuery();
|
||||
return !user.organizationId || user.organization.isOrgAdmin ? (
|
||||
return (
|
||||
<Button
|
||||
data-testid="new-team-btn"
|
||||
variant="fab"
|
||||
@@ -18,11 +15,5 @@ export const TeamsCTA = () => {
|
||||
href={`${WEBAPP_URL}/settings/teams/new?returnTo=${WEBAPP_URL}/teams`}>
|
||||
{t("new")}
|
||||
</Button>
|
||||
) : null;
|
||||
);
|
||||
};
|
||||
|
||||
function Teams() {
|
||||
return <TeamsListing />;
|
||||
}
|
||||
|
||||
export default Teams;
|
||||
@@ -0,0 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export async function revalidateTeamsList() {
|
||||
revalidatePath("/teams");
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { TeamsListSkeleton } from "./skeleton";
|
||||
|
||||
export default function Loading() {
|
||||
return <TeamsListSkeleton />;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
||||
|
||||
import TeamsView, { TeamsCTA } from "~/teams/teams-view";
|
||||
import { ServerTeamsListing } from "./server-page";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -30,13 +30,11 @@ const ServerPage = async ({ searchParams: _searchParams }: ServerPageProps) => {
|
||||
}
|
||||
|
||||
const t = await getTranslate();
|
||||
const { Main, CTA } = await ServerTeamsListing({ searchParams });
|
||||
|
||||
return (
|
||||
<ShellMainAppDir
|
||||
CTA={<TeamsCTA />}
|
||||
heading={t("teams")}
|
||||
subtitle={t("create_manage_teams_collaborative")}>
|
||||
<TeamsView />
|
||||
<ShellMainAppDir CTA={CTA} heading={t("teams")} subtitle={t("create_manage_teams_collaborative")}>
|
||||
{Main}
|
||||
</ShellMainAppDir>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createRouterCaller } from "app/_trpc/context";
|
||||
import type { SearchParams } from "app/_types";
|
||||
|
||||
import { TeamsListing } from "@calcom/features/ee/teams/components/TeamsListing";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
import { meRouter } from "@calcom/trpc/server/routers/viewer/me/_router";
|
||||
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { TeamsCTA } from "./CTA";
|
||||
|
||||
export const ServerTeamsListing = async ({ searchParams }: { searchParams: SearchParams }) => {
|
||||
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
|
||||
|
||||
const [teamsCaller, meCaller] = await Promise.all([
|
||||
createRouterCaller(viewerTeamsRouter),
|
||||
createRouterCaller(meRouter),
|
||||
]);
|
||||
|
||||
let teamNameFromInvite,
|
||||
errorMsgFromInvite = null;
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
teamNameFromInvite = await teamsCaller.inviteMemberByToken({
|
||||
token,
|
||||
creationSource: CreationSource.WEBAPP,
|
||||
});
|
||||
} catch (e) {
|
||||
errorMsgFromInvite = "Error while fetching teams";
|
||||
if (e instanceof TRPCError) errorMsgFromInvite = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
const [user, teams] = await Promise.all([
|
||||
meCaller.get(),
|
||||
teamsCaller.list({
|
||||
includeOrgs: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
Main: (
|
||||
<TeamsListing
|
||||
teams={teams}
|
||||
user={user}
|
||||
teamNameFromInvite={teamNameFromInvite ?? null}
|
||||
errorMsgFromInvite={errorMsgFromInvite}
|
||||
/>
|
||||
),
|
||||
CTA: !user.organizationId || user.organization.isOrgAdmin ? <TeamsCTA /> : null,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { ShellMainAppDir } from "app/(use-page-wrapper)/(main-nav)/ShellMainAppDir";
|
||||
import { TeamsCTA } from "app/(use-page-wrapper)/(main-nav)/teams/CTA";
|
||||
|
||||
import SkeletonLoaderTeamList from "@calcom/features/ee/teams/components/SkeletonloaderTeamList";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
export const TeamsListSkeleton = () => {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<ShellMainAppDir
|
||||
heading={t("teams")}
|
||||
subtitle={t("create_manage_teams_collaborative")}
|
||||
CTA={<TeamsCTA />}>
|
||||
<SkeletonLoaderTeamList />
|
||||
</ShellMainAppDir>
|
||||
);
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
|
||||
import LegacyPage from "@calcom/features/ee/teams/pages/team-listing-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("teams"),
|
||||
(t) => t("create_manage_teams_collaborative"),
|
||||
undefined,
|
||||
undefined,
|
||||
"/settings/teams"
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const t = await getTranslate();
|
||||
|
||||
return (
|
||||
<SettingsHeader title={t("teams")} description={t("create_manage_teams_collaborative")}>
|
||||
<LegacyPage />
|
||||
</SettingsHeader>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -21,6 +21,7 @@ export const POST_METHODS_ALLOWED_APP_ROUTES = [
|
||||
"/settings/my-account/general",
|
||||
"/settings/developer/webhooks",
|
||||
"/settings/developer/api-keys",
|
||||
"/teams",
|
||||
];
|
||||
export function checkPostMethod(req: NextRequest) {
|
||||
const pathname = req.nextUrl.pathname;
|
||||
@@ -194,6 +195,7 @@ export const config = {
|
||||
"/settings/my-account/general",
|
||||
"/settings/developer/webhooks",
|
||||
"/settings/developer/api-keys",
|
||||
"/teams",
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CreateANewTeamForm } from "@calcom/features/ee/teams/components";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { DialogContent } from "@calcom/ui/components/dialog";
|
||||
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
||||
|
||||
interface CreateTeamDialogProps {
|
||||
open: boolean;
|
||||
@@ -24,6 +25,7 @@ const CreateTeamDialog = (props: CreateTeamDialogProps) => {
|
||||
onSuccess={async () => {
|
||||
await utils.viewer.dsync.teamGroupMapping.get.invalidate();
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { trpc } from "@calcom/trpc/react";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
||||
|
||||
import MakeTeamPrivateSwitch from "../../../teams/components/MakeTeamPrivateSwitch";
|
||||
import MemberListItem from "../components/MemberListItem";
|
||||
@@ -159,6 +160,7 @@ const MembersView = () => {
|
||||
utils.viewer.organizations.getMembers.invalidate();
|
||||
utils.viewer.organizations.listOtherTeams.invalidate();
|
||||
utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
utils.viewer.organizations.listOtherTeamMembers.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ import { TextField } from "@calcom/ui/components/form";
|
||||
import { ImageUploader } from "@calcom/ui/components/image-uploader";
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
||||
|
||||
import { subdomainSuffix } from "../../../organizations/lib/orgDomains";
|
||||
|
||||
@@ -128,6 +129,7 @@ const OtherTeamProfileView = () => {
|
||||
async onSuccess() {
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
await utils.viewer.eventTypes.invalidate();
|
||||
showToast(t("success"), "success");
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { SkeletonText } from "@calcom/ui/components/skeleton";
|
||||
|
||||
function SkeletonLoaderTeamList() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { trackFormbricksAction } from "@calcom/lib/formbricks-client";
|
||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
||||
|
||||
import TeamInviteListItem from "./TeamInviteListItem";
|
||||
|
||||
@@ -36,6 +37,7 @@ export default function TeamInviteList(props: Props) {
|
||||
const deleteTeamMutation = trpc.viewer.teams.delete.useMutation({
|
||||
async onSuccess() {
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
await utils.viewer.organizations.listMembers.invalidate();
|
||||
trackFormbricksAction("team_disbanded");
|
||||
|
||||
@@ -2,6 +2,9 @@ import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
@@ -9,10 +12,8 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@calcom/ui/components/dropdown";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
||||
|
||||
interface Props {
|
||||
team: {
|
||||
@@ -43,6 +44,7 @@ export default function TeamInviteListItem(props: Props) {
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
await utils.viewer.teams.hasTeamPlan.invalidate();
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
await utils.viewer.organizations.listMembers.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,11 +11,13 @@ import { trpc } from "@calcom/trpc/react";
|
||||
import { Card } from "@calcom/ui/components/card";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
||||
|
||||
import TeamListItem from "./TeamListItem";
|
||||
|
||||
interface Props {
|
||||
teams: RouterOutputs["viewer"]["teams"]["list"];
|
||||
user: RouterOutputs["viewer"]["me"]["get"];
|
||||
/**
|
||||
* True for teams that are pending invite acceptance
|
||||
*/
|
||||
@@ -26,7 +28,7 @@ export default function TeamList(props: Props) {
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
const { t } = useLocale();
|
||||
const { data: user } = trpc.viewer.me.get.useQuery();
|
||||
const { user } = props;
|
||||
|
||||
const [hideDropdown, setHideDropdown] = useState(false);
|
||||
|
||||
@@ -41,6 +43,7 @@ export default function TeamList(props: Props) {
|
||||
const deleteTeamMutation = trpc.viewer.teams.delete.useMutation({
|
||||
async onSuccess() {
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
await utils.viewer.teams.hasTeamPlan.invalidate();
|
||||
trackFormbricksAction("team_disbanded");
|
||||
},
|
||||
@@ -113,6 +116,7 @@ export default function TeamList(props: Props) {
|
||||
<TeamListItem
|
||||
key={team?.id as number}
|
||||
team={team}
|
||||
user={user}
|
||||
onActionSelect={(action: string) => selectAction(action, team?.id as number)}
|
||||
isPending={deleteTeamMutation.isPending}
|
||||
hideDropdown={hideDropdown}
|
||||
|
||||
@@ -30,11 +30,13 @@ import {
|
||||
} from "@calcom/ui/components/dropdown";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
import { Tooltip } from "@calcom/ui/components/tooltip";
|
||||
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
||||
|
||||
import { TeamRole } from "./TeamPill";
|
||||
|
||||
interface Props {
|
||||
team: RouterOutputs["viewer"]["teams"]["list"][number];
|
||||
user: RouterOutputs["viewer"]["me"]["get"];
|
||||
key: number;
|
||||
onActionSelect: (text: string) => void;
|
||||
isPending?: boolean;
|
||||
@@ -46,8 +48,7 @@ export default function TeamListItem(props: Props) {
|
||||
const searchParams = useCompatSearchParams();
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useUtils();
|
||||
const user = trpc.viewer.me.get.useQuery().data;
|
||||
const team = props.team;
|
||||
const { team, user } = props;
|
||||
|
||||
const showDialog = searchParams?.get("inviteModal") === "true";
|
||||
const [openMemberInvitationModal, setOpenMemberInvitationModal] = useState(showDialog);
|
||||
@@ -59,6 +60,7 @@ export default function TeamListItem(props: Props) {
|
||||
showToast(t("success"), "success");
|
||||
utils.viewer.teams.get.invalidate();
|
||||
utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
utils.viewer.teams.hasTeamPlan.invalidate();
|
||||
utils.viewer.teams.listInvites.invalidate();
|
||||
const userOrganizationId = user?.profile?.organization?.id;
|
||||
|
||||
@@ -1,56 +1,40 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
"use client";
|
||||
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
import SkeletonLoaderTeamList from "@calcom/features/ee/teams/components/SkeletonloaderTeamList";
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
import { Label } from "@calcom/ui/components/form";
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { ButtonGroup } from "@calcom/ui/components/buttonGroup";
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
import { Label } from "@calcom/ui/components/form";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
import { UpgradeTip } from "../../../tips";
|
||||
import SkeletonLoaderTeamList from "./SkeletonloaderTeamList";
|
||||
import TeamList from "./TeamList";
|
||||
|
||||
export function TeamsListing() {
|
||||
const searchParams = useCompatSearchParams();
|
||||
const token = searchParams?.get("token");
|
||||
type TeamsListingProps = {
|
||||
user: RouterOutputs["viewer"]["me"]["get"];
|
||||
teams: RouterOutputs["viewer"]["teams"]["list"];
|
||||
teamNameFromInvite: string | null;
|
||||
errorMsgFromInvite: string | null;
|
||||
};
|
||||
|
||||
export function TeamsListing({
|
||||
user,
|
||||
teams: data,
|
||||
teamNameFromInvite,
|
||||
errorMsgFromInvite,
|
||||
}: TeamsListingProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const token = searchParams.get("token");
|
||||
const { t } = useLocale();
|
||||
const trpcContext = trpc.useUtils();
|
||||
const router = useRouter();
|
||||
|
||||
const [inviteTokenChecked, setInviteTokenChecked] = useState(false);
|
||||
|
||||
const { data, isPending, error } = trpc.viewer.teams.list.useQuery(
|
||||
{
|
||||
includeOrgs: true,
|
||||
},
|
||||
{
|
||||
enabled: inviteTokenChecked,
|
||||
}
|
||||
);
|
||||
|
||||
const { data: user } = trpc.viewer.me.get.useQuery();
|
||||
|
||||
const { mutate: inviteMemberByToken } = trpc.viewer.teams.inviteMemberByToken.useMutation({
|
||||
onSuccess: (teamName) => {
|
||||
trpcContext.viewer.teams.list.invalidate();
|
||||
showToast(t("team_invite_received", { teamName }), "success");
|
||||
},
|
||||
onError: (e) => {
|
||||
showToast(e.message, "error");
|
||||
},
|
||||
onSettled: () => {
|
||||
setInviteTokenChecked(true);
|
||||
},
|
||||
});
|
||||
|
||||
const teams = useMemo(() => data?.filter((m) => m.accepted && !m.isOrganization) || [], [data]);
|
||||
|
||||
const teamInvites = useMemo(() => data?.filter((m) => !m.accepted && !m.isOrganization) || [], [data]);
|
||||
@@ -101,58 +85,63 @@ export function TeamsListing() {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (!router) return;
|
||||
if (token) inviteMemberByToken({ token, creationSource: CreationSource.WEBAPP });
|
||||
else setInviteTokenChecked(true);
|
||||
}, [router, inviteMemberByToken, setInviteTokenChecked, token]);
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPending || !inviteTokenChecked) {
|
||||
return <SkeletonLoaderTeamList />;
|
||||
}
|
||||
if (teamNameFromInvite) {
|
||||
showToast(t("team_invite_received", { teamName: teamNameFromInvite }), "success");
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorMsgFromInvite) {
|
||||
showToast(errorMsgFromInvite, "error");
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!error && <Alert severity="error" title={error.message} />}
|
||||
|
||||
{organizationInvites.length > 0 && (
|
||||
<div className="bg-subtle mb-6 rounded-md p-5">
|
||||
<Label className="text-emphasis pb-2 font-semibold">{t("pending_organization_invites")}</Label>
|
||||
<TeamList teams={organizationInvites} pending />
|
||||
<TeamList user={user} teams={organizationInvites} pending />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{teamInvites.length > 0 && (
|
||||
<div className="bg-subtle mb-6 rounded-md p-5">
|
||||
<Label className="text-emphasis pb-2 font-semibold">{t("pending_invites")}</Label>
|
||||
<TeamList teams={teamInvites} pending />
|
||||
<TeamList user={user} teams={teamInvites} pending />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<UpgradeTip
|
||||
plan="team"
|
||||
title={t("calcom_is_better_with_team", { appName: APP_NAME })}
|
||||
description="add_your_team_members"
|
||||
features={features}
|
||||
background="/tips/teams"
|
||||
buttons={
|
||||
!user?.organizationId || user?.organization.isOrgAdmin ? (
|
||||
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
|
||||
<ButtonGroup>
|
||||
<Button color="primary" href={`${WEBAPP_URL}/settings/teams/new`}>
|
||||
{t("create_team")}
|
||||
</Button>
|
||||
<Button color="minimal" href="https://go.cal.com/teams-video" target="_blank">
|
||||
{t("learn_more")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
) : (
|
||||
<p>{t("org_admins_can_create_new_teams")}</p>
|
||||
)
|
||||
}>
|
||||
{teams.length > 0 ? (
|
||||
<TeamList teams={teams} />
|
||||
) : (
|
||||
{teams.length > 0 && <TeamList user={user} teams={teams} />}
|
||||
|
||||
{teams.length === 0 && (
|
||||
<UpgradeTip
|
||||
plan="team"
|
||||
title={t("calcom_is_better_with_team", { appName: APP_NAME })}
|
||||
description={t("add_your_team_members")}
|
||||
features={features}
|
||||
background="/tips/teams"
|
||||
buttons={
|
||||
!user?.organizationId || user?.organization.isOrgAdmin ? (
|
||||
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
|
||||
<ButtonGroup>
|
||||
<Button color="primary" href={`${WEBAPP_URL}/settings/teams/new`}>
|
||||
{t("create_team")}
|
||||
</Button>
|
||||
<Button color="minimal" href="https://go.cal.com/teams-video" target="_blank">
|
||||
{t("learn_more")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
) : (
|
||||
<p>{t("org_admins_can_create_new_teams")}</p>
|
||||
)
|
||||
}
|
||||
isParentLoading={<SkeletonLoaderTeamList />}>
|
||||
<EmptyScreen
|
||||
Icon="users"
|
||||
headline={t("create_team_to_get_started")}
|
||||
@@ -170,8 +159,8 @@ export function TeamsListing() {
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</UpgradeTip>
|
||||
</UpgradeTip>
|
||||
)}
|
||||
|
||||
<p className="text-subtle mb-8 mt-4 flex w-full items-center gap-1 text-sm md:justify-center md:text-center">
|
||||
<Icon className="hidden sm:block" name="info" /> {t("tip_username_plus")}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { TopBanner } from "@calcom/ui/components/top-banner";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
import { TopBanner } from "@calcom/ui/components/top-banner";
|
||||
|
||||
export type TeamsUpgradeBannerProps = {
|
||||
data: RouterOutputs["viewer"]["me"]["getUserTopBanners"]["teamUpgradeBanner"];
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { TeamsListing } from "../components";
|
||||
|
||||
const TeamListingView = () => {
|
||||
return <TeamsListing />;
|
||||
};
|
||||
|
||||
export default TeamListingView;
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
} from "@calcom/ui/components/skeleton";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
import { Tooltip } from "@calcom/ui/components/tooltip";
|
||||
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
||||
|
||||
const regex = new RegExp("^[a-zA-Z0-9-]*$");
|
||||
|
||||
@@ -123,6 +124,7 @@ const ProfileView = () => {
|
||||
const deleteTeamMutation = trpc.viewer.teams.delete.useMutation({
|
||||
async onSuccess() {
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
await utils.viewer.eventTypes.getUserEventGroups.invalidate();
|
||||
await utils.viewer.eventTypes.getByViewer.invalidate();
|
||||
showToast(t("your_team_disbanded_successfully"), "success");
|
||||
@@ -138,6 +140,7 @@ const ProfileView = () => {
|
||||
async onSuccess() {
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
await utils.viewer.eventTypes.invalidate();
|
||||
showToast(t("success"), "success");
|
||||
},
|
||||
@@ -276,6 +279,7 @@ const TeamProfileForm = ({ team, teamId }: TeamProfileFormProps) => {
|
||||
await utils.viewer.eventTypes.getUserEventGroups.invalidate();
|
||||
// TODO: Not all changes require list invalidation
|
||||
await utils.viewer.teams.list.invalidate();
|
||||
revalidateTeamsList();
|
||||
showToast(t("your_team_updated_successfully"), "success");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ function UpgradeTeamTip() {
|
||||
<UpgradeTip
|
||||
plan="team"
|
||||
title={t("calcom_is_better_with_team", { appName: APP_NAME }) as string}
|
||||
description="add_your_team_members"
|
||||
description={t("add_your_team_members")}
|
||||
background="/tips/teams"
|
||||
features={[]}
|
||||
buttons={
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useGetTheme } from "@calcom/lib/hooks/useTheme";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
@@ -28,7 +27,6 @@ export function UpgradeTip({
|
||||
plan: "team" | "enterprise";
|
||||
}) {
|
||||
const { resolvedTheme } = useGetTheme();
|
||||
const { t } = useLocale();
|
||||
const { isPending, hasTeamPlan } = useHasTeamPlan();
|
||||
const { data } = trpc.viewer.teams.getUpgradeable.useQuery();
|
||||
const imageSrc = `${background}${resolvedTheme === "dark" ? "-dark" : ""}.jpg`;
|
||||
@@ -57,8 +55,8 @@ export function UpgradeTip({
|
||||
/>
|
||||
</picture>
|
||||
<div className="relative my-4 px-8 sm:px-14">
|
||||
<h1 className={classNames("font-cal mt-4 text-3xl")}>{t(title)}</h1>
|
||||
<p className={classNames("mb-8 mt-4 max-w-sm")}>{t(description)}</p>
|
||||
<h1 className={classNames("font-cal mt-4 text-3xl")}>{title}</h1>
|
||||
<p className={classNames("mb-8 mt-4 max-w-sm")}>{description}</p>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user